From f2bd12adf9f94bcb0cd7968192c296a6e0d68184 Mon Sep 17 00:00:00 2001 From: Ivan Novosad Date: Fri, 30 Aug 2024 10:30:45 +0200 Subject: [PATCH] feat(precise_amount): Add migration, model methods and serializer (#2516) ## Context For some customers some amounts are not enough precise as Lago convert fees to `amount_cent`. ## Description Add migration for columns for precise amount cents, add model methods and serializer attributes. --- app/models/fee.rb | 14 +++++++++++ app/models/fee/applied_tax.rb | 23 ++++++++++--------- app/serializers/v1/fee_serializer.rb | 5 ++++ ...093425_add_precise_amount_cents_columns.rb | 16 +++++++++++++ db/schema.rb | 5 +++- spec/factories/fees.rb | 2 ++ spec/models/fee_spec.rb | 20 ++++++++++++++++ spec/serializers/v1/fee_serializer_spec.rb | 3 +++ 8 files changed, 76 insertions(+), 12 deletions(-) create mode 100644 db/migrate/20240829093425_add_precise_amount_cents_columns.rb diff --git a/app/models/fee.rb b/app/models/fee.rb index a0084c9c709..ab115511f2f 100644 --- a/app/models/fee.rb +++ b/app/models/fee.rb @@ -31,6 +31,9 @@ class Fee < ApplicationRecord monetize :amount_cents monetize :taxes_amount_cents, with_model_currency: :currency monetize :total_amount_cents + monetize :precise_amount_cents, with_model_currency: :currency + monetize :taxes_precise_amount_cents, with_model_currency: :currency + monetize :precise_total_amount_cents monetize :unit_amount_cents, disable_validation: true, allow_nil: true, with_model_currency: :currency # TODO: Deprecate add_on type in the near future @@ -132,11 +135,20 @@ def sub_total_excluding_taxes_amount_cents amount_cents - precise_coupons_amount_cents end + def sub_total_excluding_taxes_precise_amount_cents + precise_amount_cents - precise_coupons_amount_cents + end + def total_amount_cents amount_cents + taxes_amount_cents end alias_method :total_amount_currency, :currency + def precise_total_amount_cents + precise_amount_cents + taxes_precise_amount_cents + end + alias_method :precise_total_amount_currency, :currency + def creditable_amount_cents amount_cents - credit_note_items.sum(:amount_cents) end @@ -175,12 +187,14 @@ def has_charge_filters? # invoiceable_type :string # pay_in_advance :boolean default(FALSE), not null # payment_status :integer default("pending"), not null +# precise_amount_cents :decimal(40, 15) default(0.0), not null # precise_coupons_amount_cents :decimal(30, 5) default(0.0), not null # precise_unit_amount :decimal(30, 15) default(0.0), not null # properties :jsonb not null # refunded_at :datetime # succeeded_at :datetime # taxes_amount_cents :bigint not null +# taxes_precise_amount_cents :decimal(40, 15) default(0.0), not null # taxes_rate :float default(0.0), not null # total_aggregated_units :decimal(, ) # unit_amount_cents :bigint default(0), not null diff --git a/app/models/fee/applied_tax.rb b/app/models/fee/applied_tax.rb index 9dc2b61c6ba..98ff6df9069 100644 --- a/app/models/fee/applied_tax.rb +++ b/app/models/fee/applied_tax.rb @@ -15,17 +15,18 @@ class AppliedTax < ApplicationRecord # # Table name: fees_taxes # -# id :uuid not null, primary key -# amount_cents :bigint default(0), not null -# amount_currency :string not null -# tax_code :string not null -# tax_description :string -# tax_name :string not null -# tax_rate :float default(0.0), not null -# created_at :datetime not null -# updated_at :datetime not null -# fee_id :uuid not null -# tax_id :uuid +# id :uuid not null, primary key +# amount_cents :bigint default(0), not null +# amount_currency :string not null +# precise_amount_cents :decimal(40, 15) default(0.0), not null +# tax_code :string not null +# tax_description :string +# tax_name :string not null +# tax_rate :float default(0.0), not null +# created_at :datetime not null +# updated_at :datetime not null +# fee_id :uuid not null +# tax_id :uuid # # Indexes # diff --git a/app/serializers/v1/fee_serializer.rb b/app/serializers/v1/fee_serializer.rb index a050ebf4d06..c454770b07f 100644 --- a/app/serializers/v1/fee_serializer.rb +++ b/app/serializers/v1/fee_serializer.rb @@ -3,6 +3,8 @@ module V1 class FeeSerializer < ModelSerializer def serialize + # subunit_to_unit = model.amount.currency.subunit_to_unit + payload = { lago_id: model.id, lago_charge_filter_id: model.charge_filter_id, @@ -29,7 +31,10 @@ def serialize invoiceable:, amount_cents: model.amount_cents, amount_currency: model.amount_currency, + # precise_amount: model.precise_amount_cents.fdiv(subunit_to_unit), + # precise_total_amount: model.precise_total_amount_cents.fdiv(subunit_to_unit), taxes_amount_cents: model.taxes_amount_cents, + # taxes_precise_amount: model.taxes_precise_amount_cents.fdiv(subunit_to_unit), taxes_rate: model.taxes_rate, total_amount_cents: model.total_amount_cents, total_amount_currency: model.amount_currency, diff --git a/db/migrate/20240829093425_add_precise_amount_cents_columns.rb b/db/migrate/20240829093425_add_precise_amount_cents_columns.rb new file mode 100644 index 00000000000..a4a7e99ba55 --- /dev/null +++ b/db/migrate/20240829093425_add_precise_amount_cents_columns.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class AddPreciseAmountCentsColumns < ActiveRecord::Migration[7.1] + def change + safety_assured do + change_table :fees, bulk: true do |t| + t.decimal :precise_amount_cents, precision: 40, scale: 15, default: '0.0', null: false + t.decimal :taxes_precise_amount_cents, precision: 40, scale: 15, default: '0.0', null: false + end + + change_table :fees_taxes, bulk: true do |t| + t.decimal :precise_amount_cents, precision: 40, scale: 15, default: '0.0', null: false + end + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 33b5180436d..158da626866 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2024_08_23_092643) do +ActiveRecord::Schema[7.1].define(version: 2024_08_29_093425) do # These are extensions that must be enabled in order to support this database enable_extension "pgcrypto" enable_extension "plpgsql" @@ -551,6 +551,8 @@ t.jsonb "grouped_by", default: {}, null: false t.string "pay_in_advance_event_transaction_id" t.datetime "deleted_at" + t.decimal "precise_amount_cents", precision: 40, scale: 15, default: "0.0", null: false + t.decimal "taxes_precise_amount_cents", precision: 40, scale: 15, default: "0.0", null: false t.index ["add_on_id"], name: "index_fees_on_add_on_id" t.index ["applied_add_on_id"], name: "index_fees_on_applied_add_on_id" t.index ["charge_filter_id"], name: "index_fees_on_charge_filter_id" @@ -576,6 +578,7 @@ t.string "amount_currency", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.decimal "precise_amount_cents", precision: 40, scale: 15, default: "0.0", null: false t.index ["fee_id", "tax_id"], name: "index_fees_taxes_on_fee_id_and_tax_id", unique: true, where: "((tax_id IS NOT NULL) AND (created_at >= '2023-09-12 00:00:00'::timestamp without time zone))" t.index ["fee_id"], name: "index_fees_taxes_on_fee_id" t.index ["tax_id"], name: "index_fees_taxes_on_tax_id" diff --git a/spec/factories/fees.rb b/spec/factories/fees.rb index c45e6e9e61d..e9b0cd81284 100644 --- a/spec/factories/fees.rb +++ b/spec/factories/fees.rb @@ -9,8 +9,10 @@ subscription amount_cents { 200 } + precise_amount_cents { 200.0000000001 } amount_currency { 'EUR' } taxes_amount_cents { 2 } + taxes_precise_amount_cents { 2.0000000001 } invoiceable_type { 'Subscription' } invoiceable_id { subscription.id } diff --git a/spec/models/fee_spec.rb b/spec/models/fee_spec.rb index 72fe5899b02..2e08b398b63 100644 --- a/spec/models/fee_spec.rb +++ b/spec/models/fee_spec.rb @@ -326,6 +326,26 @@ it { expect(fee.total_amount_currency).to eq('EUR') } end + describe '#precise_total_amount_cents' do + subject(:method_call) { fee.precise_total_amount_cents } + + let(:fee) { create(:fee, precise_amount_cents: 200.0000000123, taxes_precise_amount_cents: 20.00000000012) } + + it 'returns sum of precise amount cents and taxes precise amount cents' do + expect(subject).to eq(220.00000001242) + end + end + + describe '#sub_total_excluding_taxes_precise_amount_cents' do + subject(:method_call) { fee.sub_total_excluding_taxes_precise_amount_cents } + + let(:fee) { create(:fee, precise_amount_cents: 200.00456000123, precise_coupons_amount_cents: 150.00123) } + + it 'returns sub total minus coupons amount cents' do + expect(subject).to eq(50.00333000123) + end + end + describe '#invoice_sorting_clause' do let(:charge) { create(:standard_charge, properties:) } let(:fee) { fee_model.new(charge:, fee_type: 'charge', grouped_by:) } diff --git a/spec/serializers/v1/fee_serializer_spec.rb b/spec/serializers/v1/fee_serializer_spec.rb index 3655be3832e..73a1aab0e30 100644 --- a/spec/serializers/v1/fee_serializer_spec.rb +++ b/spec/serializers/v1/fee_serializer_spec.rb @@ -36,6 +36,9 @@ 'taxes_rate' => fee.taxes_rate, 'total_amount_cents' => fee.total_amount_cents, 'total_amount_currency' => fee.amount_currency, + # 'precise_amount' => fee.precise_amount_cents.fdiv(100), + # 'taxes_precise_amount' => fee.taxes_precise_amount_cents.fdiv(100), + # 'precise_total_amount' => fee.precise_total_amount_cents.fdiv(100), 'units' => fee.units.to_s, 'precise_unit_amount' => fee.precise_unit_amount.to_s, 'precise_coupons_amount_cents' => fee.precise_coupons_amount_cents.to_s,