diff --git a/spec/factories/fees.rb b/spec/factories/fees.rb index 4e466b6ca37..cff198340e9 100644 --- a/spec/factories/fees.rb +++ b/spec/factories/fees.rb @@ -100,7 +100,7 @@ factory :minimum_commitment_fee, class: "Fee" do invoice fee_type { "commitment" } - subscription + subscription { invoice&.subscriptions&.first || association(:subscription) } organization { invoice&.organization || association(:organization) } billing_entity { invoice&.billing_entity || association(:billing_entity) } diff --git a/spec/factories/integration_collection_mappings.rb b/spec/factories/integration_collection_mappings.rb index 716ceb9b9c0..64f5df80a3c 100644 --- a/spec/factories/integration_collection_mappings.rb +++ b/spec/factories/integration_collection_mappings.rb @@ -5,6 +5,7 @@ association :integration, factory: :netsuite_integration mapping_type { %i[fallback_item coupon subscription_fee minimum_commitment tax prepaid_credit].sample } organization { integration&.organization || association(:organization) } + billing_entity { nil } settings do { @@ -22,6 +23,7 @@ association :integration, factory: :xero_integration mapping_type { %i[fallback_item coupon subscription_fee minimum_commitment tax prepaid_credit account].sample } organization { integration&.organization || association(:organization) } + billing_entity { nil } settings do { @@ -31,4 +33,34 @@ } end end + + factory :anrok_collection_mapping, class: "IntegrationCollectionMappings::AnrokCollectionMapping" do + association :integration, factory: :anrok_integration + mapping_type { %i[fallback_item coupon subscription_fee minimum_commitment tax prepaid_credit account].sample } + organization { integration&.organization || association(:organization) } + billing_entity { nil } + + settings do + { + external_id: "anrok-123", + external_account_code: "anrok-code-1", + external_name: "Credits and Discounts" + } + end + end + + factory :avalara_collection_mapping, class: "IntegrationCollectionMappings::AvalaraCollectionMapping" do + association :integration, factory: :avalara_integration + mapping_type { %i[fallback_item coupon subscription_fee minimum_commitment tax prepaid_credit account].sample } + organization { integration&.organization || association(:organization) } + billing_entity { nil } + + settings do + { + external_id: "avalara-123", + external_account_code: "avalara-code-1", + external_name: "Credits and Discounts" + } + end + end end diff --git a/spec/factories/integration_mappings.rb b/spec/factories/integration_mappings.rb index 45b543e084f..59216b9f168 100644 --- a/spec/factories/integration_mappings.rb +++ b/spec/factories/integration_mappings.rb @@ -1,31 +1,25 @@ # frozen_string_literal: true FactoryBot.define do - factory :netsuite_mapping, class: "IntegrationMappings::NetsuiteMapping" do - association :integration, factory: :netsuite_integration - association :mappable, factory: :add_on - organization { integration&.organization || association(:organization) } + [ + :netsuite, + :xero, + :anrok, + :avalara + ].each do |integration_type| + factory "#{integration_type}_mapping", class: "IntegrationMappings::#{integration_type.to_s.classify}Mapping" do + association :integration, factory: "#{integration_type}_integration" + association :mappable, factory: :add_on + organization { integration&.organization || association(:organization) } + billing_entity { nil } - settings do - { - external_id: "netsuite-123", - external_account_code: "netsuite-code-1", - external_name: "Credits and Discounts" - } - end - end - - factory :xero_mapping, class: "IntegrationMappings::XeroMapping" do - association :integration, factory: :xero_integration - association :mappable, factory: :add_on - organization { integration&.organization || association(:organization) } - - settings do - { - external_id: "xero-123", - external_account_code: "xero-code-1", - external_name: "Credits and Discounts" - } + settings do + { + external_id: "#{integration_type}-123", + external_account_code: "#{integration_type}-code-1", + external_name: "Credits and Discounts" + } + end end end end diff --git a/spec/factories/invoices.rb b/spec/factories/invoices.rb index 58c21ae32ac..3d2cd4fbfef 100644 --- a/spec/factories/invoices.rb +++ b/spec/factories/invoices.rb @@ -54,7 +54,7 @@ trait :with_subscriptions do transient do - subscriptions { [create(:subscription)] } + subscriptions { [create(:subscription, organization:)] } end after :create do |invoice, evaluator| diff --git a/spec/services/integrations/aggregator/credit_notes/payloads/anrok_spec.rb b/spec/services/integrations/aggregator/credit_notes/payloads/anrok_spec.rb new file mode 100644 index 00000000000..bce8ba8ec4c --- /dev/null +++ b/spec/services/integrations/aggregator/credit_notes/payloads/anrok_spec.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe Integrations::Aggregator::CreditNotes::Payloads::Anrok do + describe "#body" do + subject(:payload) { described_class.new(integration_customer:, credit_note:).body } + + it_behaves_like "an integration payload", :anrok do + def build_expected_payload(mapping_codes) + [ + { + "currency" => "EUR", + "external_contact_id" => integration_customer.external_customer_id, + "fees" => + [ + { + "account_code" => mapping_codes.dig(:add_on, :external_account_code), + "description" => "Add-on Fee", + "external_id" => mapping_codes.dig(:add_on, :external_id), + "precise_unit_amount" => 1.9, + "taxes_amount_cents" => 0.0, + "units" => 1 + }, + { + "account_code" => mapping_codes.dig(:billable_metric, :external_account_code), + "description" => "Standard Charge Fee", + "external_id" => mapping_codes.dig(:billable_metric, :external_id), + "precise_unit_amount" => 1.8, + "taxes_amount_cents" => 0.0, + "units" => 1 + }, + { + "account_code" => mapping_codes.dig(:minimum_commitment, :external_account_code), + "description" => "Minimum Commitment Fee", + "external_id" => mapping_codes.dig(:minimum_commitment, :external_id), + "precise_unit_amount" => 1.7, + "taxes_amount_cents" => 0.0, + "units" => 1 + }, + { + "account_code" => mapping_codes.dig(:subscription, :external_account_code), + "description" => "Subscription", + "external_id" => mapping_codes.dig(:subscription, :external_id), + "precise_unit_amount" => 1.6, + "taxes_amount_cents" => 0.0, + "units" => 1 + } + ], + "issuing_date" => "2024-07-08T00:00:00Z", + "number" => credit_note.number, + "status" => "AUTHORISED", + "type" => "ACCRECCREDIT" + } + ] + end + end + end +end diff --git a/spec/services/integrations/aggregator/credit_notes/payloads/netsuite_spec.rb b/spec/services/integrations/aggregator/credit_notes/payloads/netsuite_spec.rb new file mode 100644 index 00000000000..78af74b4fe3 --- /dev/null +++ b/spec/services/integrations/aggregator/credit_notes/payloads/netsuite_spec.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe Integrations::Aggregator::CreditNotes::Payloads::Netsuite do + describe "#body" do + subject(:payload) { described_class.new(integration_customer:, credit_note:).body } + + it_behaves_like "an integration payload", :netsuite do + def build_expected_payload(mapping_codes) + { + "columns" => { + "custbody_ava_disable_tax_calculation" => true, + "custbody_lago_id" => credit_note.id, + "entity" => integration_customer.external_customer_id, + "otherrefnum" => credit_note.number, + "taxdetailsoverride" => true, + "taxregoverride" => true, + "tranId" => credit_note.id, + "tranid" => credit_note.number + }, + "isDynamic" => true, + "lines" => [ + { + "lineItems" => [ + { + "account" => mapping_codes.dig(:add_on, :external_account_code), + "description" => "Add-on", + "item" => mapping_codes.dig(:add_on, :external_id), + "quantity" => 1, + "rate" => 1.9, + "taxdetailsreference" => add_on_credit_note_item.id + }, + { + "account" => mapping_codes.dig(:billable_metric, :external_account_code), + "description" => "Billable Metric", + "item" => mapping_codes.dig(:billable_metric, :external_id), + "quantity" => 1, + "rate" => 1.8, + "taxdetailsreference" => billable_metric_credit_note_item.id + }, + { + "account" => mapping_codes.dig(:minimum_commitment, :external_account_code), + "description" => "Plan", + "item" => mapping_codes.dig(:minimum_commitment, :external_id), + "quantity" => 1, + "rate" => 1.7, + "taxdetailsreference" => minimum_commitment_credit_note_item.id + }, + {"account" => mapping_codes.dig(:subscription, :external_account_code), + "description" => "Plan", + "item" => mapping_codes.dig(:subscription, :external_id), + "quantity" => 1, + "rate" => 1.6, + "taxdetailsreference" => subscription_credit_note_item.id} + ], + "sublistId" => "item" + } + ], + "options" => {"ignoreMandatoryFields" => false}, + "type" => "creditmemo" + } + end + end + end +end diff --git a/spec/services/integrations/aggregator/credit_notes/payloads/xero_spec.rb b/spec/services/integrations/aggregator/credit_notes/payloads/xero_spec.rb new file mode 100644 index 00000000000..18b2d165d69 --- /dev/null +++ b/spec/services/integrations/aggregator/credit_notes/payloads/xero_spec.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe Integrations::Aggregator::CreditNotes::Payloads::Xero do + describe "#body" do + subject(:payload) { described_class.new(integration_customer:, credit_note:).body } + + it_behaves_like "an integration payload", :xero do + def build_expected_payload(mapping_codes) + [ + { + "currency" => "EUR", + "external_contact_id" => integration_customer.external_customer_id, + "fees" => + [ + { + "account_code" => mapping_codes.dig(:add_on, :external_account_code), + "description" => "Add-on Fee", + "external_id" => mapping_codes.dig(:add_on, :external_id), + "precise_unit_amount" => 1.9, + "taxes_amount_cents" => 0.0, + "units" => 1 + }, + { + "account_code" => mapping_codes.dig(:billable_metric, :external_account_code), + "description" => "Standard Charge Fee", + "external_id" => mapping_codes.dig(:billable_metric, :external_id), + "precise_unit_amount" => 1.8, + "taxes_amount_cents" => 0.0, + "units" => 1 + }, + { + "account_code" => mapping_codes.dig(:minimum_commitment, :external_account_code), + "description" => "Minimum Commitment Fee", + "external_id" => mapping_codes.dig(:minimum_commitment, :external_id), + "precise_unit_amount" => 1.7, + "taxes_amount_cents" => 0.0, + "units" => 1 + }, + { + "account_code" => mapping_codes.dig(:subscription, :external_account_code), + "description" => "Subscription", + "external_id" => mapping_codes.dig(:subscription, :external_id), + "precise_unit_amount" => 1.6, + "taxes_amount_cents" => 0.0, + "units" => 1 + } + ], + "issuing_date" => "2024-07-08T00:00:00Z", + "number" => credit_note.number, + "status" => "AUTHORISED", + "type" => "ACCRECCREDIT" + } + ] + end + end + end +end diff --git a/spec/services/integrations/aggregator/invoices/payloads/anrok_spec.rb b/spec/services/integrations/aggregator/invoices/payloads/anrok_spec.rb new file mode 100644 index 00000000000..250660919a2 --- /dev/null +++ b/spec/services/integrations/aggregator/invoices/payloads/anrok_spec.rb @@ -0,0 +1,92 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe Integrations::Aggregator::Invoices::Payloads::Anrok do + describe "#body" do + subject(:payload) { described_class.new(integration_customer:, invoice:).body } + + it_behaves_like "an integration payload", :anrok do + def build_expected_payload(mapping_codes) + [ + { + "external_contact_id" => integration_customer.external_customer_id, + "status" => "AUTHORISED", + "issuing_date" => "2024-07-08T00:00:00Z", + "payment_due_date" => "2024-07-08T00:00:00Z", + "number" => invoice.number, + "currency" => "EUR", + "type" => "ACCREC", + "fees" => + [ + { + "account_code" => mapping_codes.dig(:add_on, :external_account_code), + "description" => "Add-on Fee", + "external_id" => mapping_codes.dig(:add_on, :external_id), + "precise_unit_amount" => 100.0, + "taxes_amount_cents" => 2, + "units" => 0.2e1 + }, + { + "account_code" => mapping_codes.dig(:billable_metric, :external_account_code), + "description" => "Standard Charge Fee", + "external_id" => mapping_codes.dig(:billable_metric, :external_id), + "precise_unit_amount" => 100.0, + "taxes_amount_cents" => 2, + "units" => 0.3e1 + }, + { + "account_code" => mapping_codes.dig(:minimum_commitment, :external_account_code), + "description" => "Minimum Commitment Fee", + "external_id" => mapping_codes.dig(:minimum_commitment, :external_id), + "precise_unit_amount" => 100.0, + "taxes_amount_cents" => 2, + "units" => 0.4e1 + }, + { + "account_code" => mapping_codes.dig(:subscription, :external_account_code), + "description" => "Subscription", + "external_id" => mapping_codes.dig(:subscription, :external_id), + "precise_unit_amount" => 100.0, + "taxes_amount_cents" => 2, + "units" => 0.5e1 + }, + { + "account_code" => mapping_codes.dig(:coupon, :external_account_code), + "description" => "Coupons", + "external_id" => mapping_codes.dig(:coupon, :external_id), + "precise_unit_amount" => -2.0, + "taxes_amount_cents" => -292, + "units" => 1 + }, + { + "account_code" => mapping_codes.dig(:prepaid_credit, :external_account_code), + "description" => "Prepaid credit", + "external_id" => mapping_codes.dig(:prepaid_credit, :external_id), + "precise_unit_amount" => -3.0, + "taxes_amount_cents" => 0, + "units" => 1 + }, + { + "account_code" => mapping_codes.dig(:prepaid_credit, :external_account_code), + "description" => "Usage already billed", + "external_id" => mapping_codes.dig(:prepaid_credit, :external_id), + "precise_unit_amount" => -1.0, + "taxes_amount_cents" => 0, + "units" => 1 + }, + { + "account_code" => mapping_codes.dig(:credit_note, :external_account_code), + "description" => "Credit note", + "external_id" => mapping_codes.dig(:credit_note, :external_id), + "precise_unit_amount" => -5.0, + "taxes_amount_cents" => 0, + "units" => 1 + } + ] + } + ] + end + end + end +end diff --git a/spec/services/integrations/aggregator/invoices/payloads/xero_spec.rb b/spec/services/integrations/aggregator/invoices/payloads/xero_spec.rb index 88a497ed370..1f359ee551a 100644 --- a/spec/services/integrations/aggregator/invoices/payloads/xero_spec.rb +++ b/spec/services/integrations/aggregator/invoices/payloads/xero_spec.rb @@ -4,215 +4,89 @@ RSpec.describe Integrations::Aggregator::Invoices::Payloads::Xero do describe "#body" do - subject(:body_call) { payload.body } - - let(:payload) { described_class.new(integration_customer:, invoice:) } - let(:integration_customer) { FactoryBot.create(:xero_customer, integration:, customer:) } - let(:integration) { create(:netsuite_integration, organization:) } - let(:customer) { create(:customer, organization:) } - let(:organization) { create(:organization) } - let(:add_on) { create(:add_on, organization:) } - let(:billable_metric) { create(:billable_metric, organization:) } - let(:charge) { create(:standard_charge, billable_metric:) } - let(:current_time) { Time.current } - - let(:integration_collection_mapping1) do - create( - :netsuite_collection_mapping, - integration:, - mapping_type: :fallback_item, - settings: {external_id: "1", external_account_code: "11", external_name: ""} - ) - end - - let(:integration_collection_mapping2) do - create( - :netsuite_collection_mapping, - integration:, - mapping_type: :coupon, - settings: {external_id: "2", external_account_code: "22", external_name: ""} - ) - end - - let(:integration_collection_mapping3) do - create( - :netsuite_collection_mapping, - integration:, - mapping_type: :subscription_fee, - settings: {external_id: "3", external_account_code: "33", external_name: ""} - ) - end - - let(:integration_collection_mapping4) do - create( - :netsuite_collection_mapping, - integration:, - mapping_type: :minimum_commitment, - settings: {external_id: "4", external_account_code: "44", external_name: ""} - ) - end - - let(:integration_collection_mapping6) do - create( - :netsuite_collection_mapping, - integration:, - mapping_type: :prepaid_credit, - settings: {external_id: "6", external_account_code: "66", external_name: ""} - ) - end - - let(:integration_mapping_add_on) do - create( - :netsuite_mapping, - integration:, - mappable_type: "AddOn", - mappable_id: add_on.id, - settings: {external_id: "m1", external_account_code: "m11", external_name: ""} - ) - end - - let(:integration_mapping_bm) do - create( - :netsuite_mapping, - integration:, - mappable_type: "BillableMetric", - mappable_id: billable_metric.id, - settings: {external_id: "m2", external_account_code: "m22", external_name: ""} - ) - end - - let(:invoice) do - create( - :invoice, - customer:, - organization:, - coupons_amount_cents: 2000, - prepaid_credit_amount_cents: 4000, - progressive_billing_credit_amount_cents: 100, - credit_notes_amount_cents: 6000, - taxes_amount_cents: 200, - issuing_date: DateTime.new(2024, 7, 8) - ) - end - - let(:fee_sub) do - create( - :fee, - invoice:, - amount_cents: 10_000, - taxes_amount_cents: 200, - created_at: current_time - 3.seconds - ) - end - - let(:minimum_commitment_fee) do - create( - :minimum_commitment_fee, - invoice:, - created_at: current_time - 2.seconds - ) - end - - let(:charge_fee) do - create( - :charge_fee, - invoice:, - charge:, - units: 2, - precise_unit_amount: 4.12121212123337777, - created_at: current_time - ) - end - - let(:body) do - [ - { - "external_contact_id" => integration_customer.external_customer_id, - "status" => "AUTHORISED", - "issuing_date" => "2024-07-08T00:00:00Z", - "payment_due_date" => "2024-07-08T00:00:00Z", - "number" => invoice.number, - "currency" => "EUR", - "type" => "ACCREC", - "fees" => [ - { - "external_id" => "3", - "description" => "Subscription", - "units" => 0.0, - "precise_unit_amount" => 0.0, - "account_code" => "33", - "taxes_amount_cents" => 200 - }, - { - "external_id" => "4", - "description" => minimum_commitment_fee.invoice_name, - "units" => 0.0, - "precise_unit_amount" => 0.0, - "account_code" => "44", - "taxes_amount_cents" => 2 - }, - { - "external_id" => "m2", - "description" => charge_fee.invoice_name, - "units" => 1, - "amount_cents" => charge_fee.amount_cents, - "account_code" => "m22", - "taxes_amount_cents" => 2 - }, - { - "account_code" => "22", - "description" => "Coupons", - "external_id" => "2", - "precise_unit_amount" => -20.0, - "taxes_amount_cents" => -4, - "units" => 1 - }, - { - "external_id" => "6", - "description" => "Prepaid credit", - "units" => 1, - "precise_unit_amount" => -40.0, - "taxes_amount_cents" => 0, - "account_code" => "66" - }, - { - "external_id" => "6", - "description" => "Usage already billed", - "units" => 1, - "precise_unit_amount" => -1.0, - "taxes_amount_cents" => 0, - "account_code" => "66" - }, - { - "external_id" => "1", - "description" => "Credit note", - "units" => 1, - "precise_unit_amount" => -60.0, - "taxes_amount_cents" => 0, - "account_code" => "11" - } - ] - } - ] - end - - before do - integration_customer - charge - integration_collection_mapping1 - integration_collection_mapping2 - integration_collection_mapping3 - integration_collection_mapping4 - integration_collection_mapping6 - integration_mapping_add_on - integration_mapping_bm - fee_sub - minimum_commitment_fee - charge_fee - end - - it "returns payload body" do - expect(subject).to eq(body) + subject(:payload) { described_class.new(integration_customer:, invoice:).body } + + it_behaves_like "an integration payload", :xero do + def build_expected_payload(mapping_codes) + [ + { + "external_contact_id" => integration_customer.external_customer_id, + "status" => "AUTHORISED", + "issuing_date" => "2024-07-08T00:00:00Z", + "payment_due_date" => "2024-07-08T00:00:00Z", + "number" => invoice.number, + "currency" => "EUR", + "type" => "ACCREC", + "fees" => + [ + { + "account_code" => mapping_codes.dig(:add_on, :external_account_code), + "description" => "Add-on Fee", + "external_id" => mapping_codes.dig(:add_on, :external_id), + "precise_unit_amount" => 100.0, + "taxes_amount_cents" => 2, + "units" => 0.2e1 + }, + { + "account_code" => mapping_codes.dig(:billable_metric, :external_account_code), + "description" => "Standard Charge Fee", + "external_id" => mapping_codes.dig(:billable_metric, :external_id), + "precise_unit_amount" => 100.0, + "taxes_amount_cents" => 2, + "units" => 0.3e1 + }, + { + "account_code" => mapping_codes.dig(:minimum_commitment, :external_account_code), + "description" => "Minimum Commitment Fee", + "external_id" => mapping_codes.dig(:minimum_commitment, :external_id), + "precise_unit_amount" => 100.0, + "taxes_amount_cents" => 2, + "units" => 0.4e1 + }, + { + "account_code" => mapping_codes.dig(:subscription, :external_account_code), + "description" => "Subscription", + "external_id" => mapping_codes.dig(:subscription, :external_id), + "precise_unit_amount" => 100.0, + "taxes_amount_cents" => 2, + "units" => 0.5e1 + }, + { + "account_code" => mapping_codes.dig(:coupon, :external_account_code), + "description" => "Coupons", + "external_id" => mapping_codes.dig(:coupon, :external_id), + "precise_unit_amount" => -2.0, + "taxes_amount_cents" => -292, + "units" => 1 + }, + { + "account_code" => mapping_codes.dig(:prepaid_credit, :external_account_code), + "description" => "Prepaid credit", + "external_id" => mapping_codes.dig(:prepaid_credit, :external_id), + "precise_unit_amount" => -3.0, + "taxes_amount_cents" => 0, + "units" => 1 + }, + { + "account_code" => mapping_codes.dig(:prepaid_credit, :external_account_code), + "description" => "Usage already billed", + "external_id" => mapping_codes.dig(:prepaid_credit, :external_id), + "precise_unit_amount" => -1.0, + "taxes_amount_cents" => 0, + "units" => 1 + }, + { + "account_code" => mapping_codes.dig(:credit_note, :external_account_code), + "description" => "Credit note", + "external_id" => mapping_codes.dig(:credit_note, :external_id), + "precise_unit_amount" => -5.0, + "taxes_amount_cents" => 0, + "units" => 1 + } + ] + } + ] + end end end end diff --git a/spec/services/integrations/aggregator/payments/payloads/base_payload_spec.rb b/spec/services/integrations/aggregator/payments/payloads/base_payload_spec.rb deleted file mode 100644 index 888177e8c7a..00000000000 --- a/spec/services/integrations/aggregator/payments/payloads/base_payload_spec.rb +++ /dev/null @@ -1,56 +0,0 @@ -# frozen_string_literal: true - -require "rails_helper" - -RSpec.describe Integrations::Aggregator::Payments::Payloads::BasePayload do - let(:payload) { described_class.new(integration:, payment:) } - let(:payment) { create(:payment, payable: invoice) } - let(:invoice) { create(:invoice, customer:, organization:) } - let(:integration) { create(:netsuite_integration, organization:) } - let(:integration_customer) { create(:netsuite_customer, integration:, customer:) } - let(:customer) { create(:customer, organization:) } - let(:organization) { create(:organization) } - - describe "#initialize" do - it "assigns the payment" do - expect(payload.instance_variable_get(:@payment)).to eq(payment) - end - end - - describe "#integration_customer" do - subject(:method_call) { payload.__send__(:integration_customer) } - - before do - integration_customer - create(:hubspot_customer, customer:) - end - - it "returns the first accounting kind integration customer" do - expect(subject).to eq(integration_customer) - end - - it "memoizes the integration customer" do - subject - expect(payload.instance_variable_get(:@integration_customer)).to eq(integration_customer) - end - end - - describe "#body" do - let(:integration_invoice) { create(:integration_resource, syncable: invoice, integration:) } - - before { integration_invoice } - - it "returns correct body" do - expect(payload.body).to eq( - [ - { - "invoice_id" => integration_invoice.external_id, - "account_code" => nil, - "date" => payment.created_at.utc.iso8601, - "amount_cents" => payment.amount_cents - } - ] - ) - end - end -end diff --git a/spec/services/integrations/aggregator/payments/payloads/xero_spec.rb b/spec/services/integrations/aggregator/payments/payloads/xero_spec.rb new file mode 100644 index 00000000000..a40b4729886 --- /dev/null +++ b/spec/services/integrations/aggregator/payments/payloads/xero_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe Integrations::Aggregator::Payments::Payloads::Xero do + let(:payload) { described_class.new(integration:, payment:).body } + + describe "#body" do + it_behaves_like "an integration payload", :xero do + let!(:integration_invoice) { create(:integration_resource, syncable: invoice, integration:) } + + before { integration_invoice } + + def build_expected_payload(mapping_codes) + [ + { + "invoice_id" => integration_invoice.external_id, + "account_code" => mapping_codes.dig(:account, :external_account_code), + "date" => payment.created_at.utc.iso8601, + "amount_cents" => payment.amount_cents + } + ] + end + end + end +end diff --git a/spec/services/integrations/aggregator/taxes/credit_notes/payloads/anrok_spec.rb b/spec/services/integrations/aggregator/taxes/credit_notes/payloads/anrok_spec.rb index 6fbcd14e753..25a716e415e 100644 --- a/spec/services/integrations/aggregator/taxes/credit_notes/payloads/anrok_spec.rb +++ b/spec/services/integrations/aggregator/taxes/credit_notes/payloads/anrok_spec.rb @@ -3,125 +3,51 @@ require "rails_helper" RSpec.describe Integrations::Aggregator::Taxes::CreditNotes::Payloads::Anrok do - subject(:service_call) { payload.body } - - let(:payload) { described_class.new(integration:, customer:, integration_customer:, credit_note:) } - let(:integration) { create(:anrok_integration, organization:) } - let(:integration_customer) { create(:anrok_customer, integration:, customer:, external_customer_id: nil) } - let(:customer) { create(:customer, organization:) } - let(:organization) { create(:organization) } - let(:add_on) { create(:add_on, organization:) } - let(:add_on_two) { create(:add_on, organization:) } - let(:current_time) { Time.current } - - let(:integration_collection_mapping1) do - create( - :netsuite_collection_mapping, - integration:, - mapping_type: :fallback_item, - settings: {external_id: "1", external_account_code: "11", external_name: ""} - ) - end - let(:integration_mapping_add_on) do - create( - :netsuite_mapping, - integration:, - mappable_type: "AddOn", - mappable_id: add_on.id, - settings: {external_id: "m1", external_account_code: "m11", external_name: ""} - ) - end - - let(:invoice) do - create( - :invoice, - customer:, - organization: - ) - end - let(:fee_add_on) do - create( - :fee, - invoice:, - add_on:, - created_at: current_time - 3.seconds, - amount_cents: 200, - precise_amount_cents: 200 - ) - end - let(:fee_add_on_two) do - create( - :fee, - invoice:, - add_on: add_on_two, - created_at: current_time - 2.seconds, - amount_cents: 200, - precise_amount_cents: 200, - precise_coupons_amount_cents: 20 - ) - end - let(:credit_note) do - create( - :credit_note, - customer:, - invoice:, - status: "finalized", - organization: - ) - end - - let(:credit_note_item1) do - create(:credit_note_item, credit_note:, fee: fee_add_on, amount_cents: 190) - end - let(:credit_note_item2) do - create(:credit_note_item, credit_note:, fee: fee_add_on_two, amount_cents: 180) - end + describe "#body" do + subject(:payload) { described_class.new(integration:, customer:, integration_customer:, credit_note:).body } - let(:body) do - [ - { - "id" => "cn_#{credit_note.id}", - "issuing_date" => credit_note.issuing_date, - "currency" => credit_note.currency, - "contact" => { - "external_id" => customer.external_id, - "name" => customer.name, - "address_line_1" => customer.address_line1, - "city" => customer.city, - "zip" => customer.zipcode, - "country" => customer.country, - "taxable" => false, - "tax_number" => nil - }, - "fees" => [ + it_behaves_like "an integration payload", :anrok do + def build_expected_payload(mapping_codes) + [ { - "item_id" => fee_add_on.item_id, - "item_code" => "m1", - "amount_cents" => -190 - }, - { - "item_id" => fee_add_on_two.item_id, - "item_code" => "1", - "amount_cents" => -162 + "id" => "cn_#{credit_note.id}", + "issuing_date" => credit_note.issuing_date, + "currency" => credit_note.currency, + "contact" => { + "external_id" => integration_customer.external_customer_id, + "name" => customer.name, + "address_line_1" => customer.address_line1, + "city" => customer.city, + "zip" => customer.zipcode, + "country" => customer.country, + "taxable" => false, + "tax_number" => nil + }, + "fees" => match_array([ + { + "item_id" => add_on.id, + "amount_cents" => -190, + "item_code" => mapping_codes.dig(:add_on, :external_id) + }, + { + "item_id" => billable_metric.id, + "amount_cents" => -180, + "item_code" => mapping_codes.dig(:billable_metric, :external_id) + }, + { + "item_id" => subscription.id, + "amount_cents" => -170, + "item_code" => mapping_codes.dig(:minimum_commitment, :external_id) + }, + { + "item_id" => subscription.id, + "amount_cents" => -160, + "item_code" => mapping_codes.dig(:subscription, :external_id) + } + ]) } ] - } - ] - end - - before do - integration_customer - integration_collection_mapping1 - integration_mapping_add_on - fee_add_on - fee_add_on_two - credit_note_item1 - credit_note_item2 - end - - describe "#body" do - it "returns payload" do - expect(service_call).to eq(body) + end end end end diff --git a/spec/services/integrations/aggregator/taxes/credit_notes/payloads/avalara_spec.rb b/spec/services/integrations/aggregator/taxes/credit_notes/payloads/avalara_spec.rb index 9b270538f54..d5a992de899 100644 --- a/spec/services/integrations/aggregator/taxes/credit_notes/payloads/avalara_spec.rb +++ b/spec/services/integrations/aggregator/taxes/credit_notes/payloads/avalara_spec.rb @@ -3,136 +3,62 @@ require "rails_helper" RSpec.describe Integrations::Aggregator::Taxes::CreditNotes::Payloads::Avalara do - subject(:service_call) { payload.body } + subject(:payload) { described_class.new(integration:, customer:, integration_customer:, credit_note:).body } - let(:payload) { described_class.new(integration:, customer:, integration_customer:, credit_note:) } - let(:integration) { create(:avalara_integration, organization:) } - let(:integration_customer) { create(:avalara_customer, integration:, customer:) } - let(:customer) { create(:customer, organization:) } - let(:organization) { create(:organization) } - let(:add_on) { create(:add_on, organization:) } - let(:add_on_two) { create(:add_on, organization:) } - let(:current_time) { Time.current } - - let(:integration_collection_mapping1) do - create( - :netsuite_collection_mapping, - integration:, - mapping_type: :fallback_item, - settings: {external_id: "1", external_account_code: "11", external_name: ""} - ) - end - let(:integration_mapping_add_on) do - create( - :netsuite_mapping, - integration:, - mappable_type: "AddOn", - mappable_id: add_on.id, - settings: {external_id: "m1", external_account_code: "m11", external_name: ""} - ) - end - - let(:invoice) do - create( - :invoice, - customer:, - organization: - ) - end - let(:fee_add_on) do - create( - :fee, - invoice:, - add_on:, - created_at: current_time - 3.seconds, - amount_cents: 200, - precise_amount_cents: 200 - ) - end - let(:fee_add_on_two) do - create( - :fee, - invoice:, - add_on: add_on_two, - created_at: current_time - 2.seconds, - amount_cents: 200, - precise_amount_cents: 200, - precise_coupons_amount_cents: 20 - ) - end - let(:credit_note) do - create( - :credit_note, - customer:, - invoice:, - status: "finalized", - organization: - ) - end - - let(:credit_note_item1) do - create(:credit_note_item, credit_note:, fee: fee_add_on, amount_cents: 190) - end - let(:credit_note_item2) do - create(:credit_note_item, credit_note:, fee: fee_add_on_two, amount_cents: 180) - end - - let(:body) do - [ - { - "id" => "cn_#{credit_note.id}", - "type" => "returnInvoice", - "issuing_date" => credit_note.issuing_date, - "currency" => credit_note.currency, - "contact" => { - "external_id" => integration_customer&.external_customer_id || customer.external_id, - "name" => customer.name, - "address_line_1" => customer.shipping_address_line1 || customer.address_line1, - "city" => customer.shipping_city || customer.city, - "zip" => customer.shipping_zipcode || customer.zipcode, - "region" => customer.shipping_state || customer.state, - "country" => customer.shipping_country || customer.country, - "taxable" => customer.tax_identification_number.present?, - "tax_number" => customer.tax_identification_number - }, - "billing_entity" => { - "address_line_1" => customer.billing_entity.address_line1, - "city" => customer.billing_entity.city, - "zip" => customer.billing_entity.zipcode, - "region" => customer.billing_entity.state, - "country" => customer.billing_entity.country - }, - "fees" => [ - { - "item_id" => fee_add_on.item_id, - "item_code" => "m1", - "unit" => 0.0, - "amount" => "-1.9" + it_behaves_like "an integration payload", :avalara do + def build_expected_payload(mapping_codes) + [ + { + "id" => "cn_#{credit_note.id}", + "type" => "returnInvoice", + "issuing_date" => credit_note.issuing_date, + "currency" => credit_note.currency, + "contact" => { + "external_id" => integration_customer&.external_customer_id || customer.external_id, + "name" => customer.name, + "address_line_1" => customer.shipping_address_line1 || customer.address_line1, + "city" => customer.shipping_city || customer.city, + "zip" => customer.shipping_zipcode || customer.zipcode, + "region" => customer.shipping_state || customer.state, + "country" => customer.shipping_country || customer.country, + "taxable" => customer.tax_identification_number.present?, + "tax_number" => customer.tax_identification_number }, - { - "item_id" => fee_add_on_two.item_id, - "item_code" => "1", - "unit" => 0.0, - "amount" => "-1.62" - } - ] - } - ] - end - - before do - integration_customer - integration_collection_mapping1 - integration_mapping_add_on - fee_add_on - fee_add_on_two - credit_note_item1 - credit_note_item2 - end - - describe "#body" do - it "returns payload" do - expect(service_call).to eq(body) + "billing_entity" => { + "address_line_1" => customer.billing_entity.address_line1, + "city" => customer.billing_entity.city, + "zip" => customer.billing_entity.zipcode, + "region" => customer.billing_entity.state, + "country" => customer.billing_entity.country + }, + "fees" => match_array([ + { + "item_id" => add_on.id, + "amount" => "-1.9", + "unit" => 2.0, + "item_code" => mapping_codes.dig(:add_on, :external_id) + }, + { + "item_id" => billable_metric.id, + "amount" => "-1.8", + "unit" => 3.0, + "item_code" => mapping_codes.dig(:billable_metric, :external_id) + }, + { + "item_id" => subscription.id, + "amount" => "-1.7", + "unit" => 4.0, + "item_code" => mapping_codes.dig(:minimum_commitment, :external_id) + }, + { + "item_id" => subscription.id, + "amount" => "-1.6", + "unit" => 5.0, + "item_code" => mapping_codes.dig(:subscription, :external_id) + } + ]) + } + ] end end end diff --git a/spec/services/integrations/aggregator/taxes/invoices/payloads/anrok_spec.rb b/spec/services/integrations/aggregator/taxes/invoices/payloads/anrok_spec.rb index 539a8fca651..1ad46f5d015 100644 --- a/spec/services/integrations/aggregator/taxes/invoices/payloads/anrok_spec.rb +++ b/spec/services/integrations/aggregator/taxes/invoices/payloads/anrok_spec.rb @@ -3,108 +3,60 @@ require "rails_helper" RSpec.describe Integrations::Aggregator::Taxes::Invoices::Payloads::Anrok do - let(:integration) { create(:anrok_integration, organization:) } - let(:organization) { create(:organization) } - let(:integration_customer) { create(:anrok_customer, customer:, integration:) } - let(:customer) { create(:customer, organization:) } - let(:payload) { described_class.new(integration:, customer:, invoice:, integration_customer:, fees:) } - let(:add_on) { create(:add_on, organization:) } - let(:add_on_two) { create(:add_on, organization:) } - let(:current_time) { Time.current } - let(:fees) { invoice.fees } - let(:integration_collection_mapping1) do - create( - :netsuite_collection_mapping, - integration:, - mapping_type: :fallback_item, - settings: {external_id: "1", external_account_code: "11", external_name: ""} - ) - end - let(:integration_mapping_add_on) do - create( - :netsuite_mapping, - integration:, - mappable_type: "AddOn", - mappable_id: add_on.id, - settings: {external_id: "m1", external_account_code: "m11", external_name: ""} - ) - end - let(:invoice) do - create( - :invoice, - customer:, - organization: - ) - end - let(:fee_add_on) do - create( - :fee, - invoice:, - add_on:, - created_at: current_time - 3.seconds - ) - end - let(:fee_add_on_two) do - create( - :fee, - invoice:, - add_on: add_on_two, - created_at: current_time - 2.seconds - ) - end - - before do - integration_customer - integration_collection_mapping1 - integration_mapping_add_on - fee_add_on - fee_add_on_two - end - describe "#body" do - subject(:call) { payload.body } + subject(:payload) { described_class.new(integration:, customer:, invoice:, integration_customer:, fees:).body } - let(:payload_body) do - [ - { - "issuing_date" => invoice.issuing_date, - "currency" => invoice.currency, - "contact" => { - "external_id" => integration_customer&.external_customer_id || customer.external_id, - "name" => customer.name, - "address_line_1" => customer.shipping_address_line1 || customer.address_line1, - "city" => customer.shipping_city || customer.city, - "zip" => customer.shipping_zipcode || customer.zipcode, - "country" => customer.shipping_country || customer.country, - "taxable" => customer.tax_identification_number.present?, - "tax_number" => customer.tax_identification_number - }, - "fees" => [ - { - "item_key" => fee_add_on.item_key, - "item_id" => fee_add_on.id, - "item_code" => "m1", - "amount_cents" => 200 + it_behaves_like "an integration payload", :avalara do + def build_expected_payload(mapping_codes) + [ + { + "issuing_date" => invoice.issuing_date, + "currency" => invoice.currency, + "contact" => { + "external_id" => integration_customer&.external_customer_id || customer.external_id, + "name" => customer.name, + "address_line_1" => customer.shipping_address_line1 || customer.address_line1, + "city" => customer.shipping_city || customer.city, + "zip" => customer.shipping_zipcode || customer.zipcode, + "country" => customer.shipping_country || customer.country, + "taxable" => customer.tax_identification_number.present?, + "tax_number" => customer.tax_identification_number }, - { - "item_key" => fee_add_on_two.item_key, - "item_id" => fee_add_on_two.id, - "item_code" => "1", - "amount_cents" => 200 - } - ] - } - ] - end - - it "returns the payload body" do - expect(call).to eq payload_body - end + "fees" => match_array([ + { + "item_key" => add_on_fee.item_key, + "item_id" => add_on_fee.id, + "amount_cents" => 200, + "item_code" => mapping_codes.dig(:add_on, :external_id) + }, + { + "item_key" => billable_metric_fee.item_key, + "item_id" => billable_metric_fee.id, + "amount_cents" => 300, + "item_code" => mapping_codes.dig(:billable_metric, :external_id) + }, + { + "item_key" => minimum_commitment_fee.item_key, + "item_id" => minimum_commitment_fee.id, + "amount_cents" => 400, + "item_code" => mapping_codes.dig(:minimum_commitment, :external_id) + }, + { + "item_key" => subscription_fee.item_key, + "item_id" => subscription_fee.id, + "amount_cents" => 500, + "item_code" => mapping_codes.dig(:subscription, :external_id) + } + ]) + } + ] + end - context "when invoice.issuing_date is too far in the future" do - it "uses issuing date 30 days in the future at most" do - invoice.issuing_date = 61.days.from_now.to_date - expect(call.sole["issuing_date"]).to eq 30.days.from_now.to_date + context "when invoice.issuing_date is too far in the future" do + it "uses issuing date 30 days in the future at most" do + invoice.issuing_date = 61.days.from_now.to_date + expect(payload.sole["issuing_date"]).to eq 30.days.from_now.to_date + end end end end diff --git a/spec/services/integrations/aggregator/taxes/invoices/payloads/avalara_spec.rb b/spec/services/integrations/aggregator/taxes/invoices/payloads/avalara_spec.rb index 5c4563ceae5..2ecf9e49e29 100644 --- a/spec/services/integrations/aggregator/taxes/invoices/payloads/avalara_spec.rb +++ b/spec/services/integrations/aggregator/taxes/invoices/payloads/avalara_spec.rb @@ -3,120 +3,11 @@ require "rails_helper" RSpec.describe Integrations::Aggregator::Taxes::Invoices::Payloads::Avalara do - let(:integration) { create(:avalara_integration, organization:) } - let(:organization) { create(:organization) } - let(:integration_customer) { create(:avalara_customer, customer:, integration:) } - let(:customer) { create(:customer, organization:) } - let(:payload) { described_class.new(integration:, customer:, invoice:, integration_customer:, fees:) } - let(:add_on) { create(:add_on, organization:) } - let(:add_on_two) { create(:add_on, organization:) } - let(:current_time) { Time.current } - let(:fees) { invoice.fees } - let(:integration_collection_mapping1) do - create( - :netsuite_collection_mapping, - integration:, - mapping_type: :fallback_item, - settings: {external_id: "1", external_account_code: "11", external_name: ""} - ) - end - let(:integration_mapping_add_on) do - create( - :netsuite_mapping, - integration:, - mappable_type: "AddOn", - mappable_id: add_on.id, - settings: {external_id: "m1", external_account_code: "m11", external_name: ""} - ) - end - let(:invoice) do - create( - :invoice, - customer:, - organization: - ) - end - let(:fee_add_on) do - create( - :fee, - invoice:, - add_on:, - units: 1, - amount_cents: 2035, - created_at: current_time - 3.seconds - ) - end - let(:fee_add_on_two) do - create( - :fee, - invoice:, - add_on: add_on_two, - units: 1, - amount_cents: 2035, - created_at: current_time - 2.seconds - ) - end - - before do - integration_customer - integration_collection_mapping1 - integration_mapping_add_on - fee_add_on - fee_add_on_two - end - describe "#body" do - subject(:call) { payload.body } + subject(:payload) { described_class.new(integration:, customer:, invoice:, integration_customer:, fees:).body } - let(:payload_body) do - [ - { - "issuing_date" => invoice.issuing_date, - "currency" => invoice.currency, - "contact" => { - "external_id" => integration_customer&.external_customer_id || customer.external_id, - "name" => customer.name, - "address_line_1" => customer.shipping_address_line1 || customer.address_line1, - "city" => customer.shipping_city || customer.city, - "zip" => customer.shipping_zipcode || customer.zipcode, - "region" => customer.shipping_state || customer.state, - "country" => customer.shipping_country || customer.country, - "taxable" => customer.tax_identification_number.present?, - "tax_number" => customer.tax_identification_number - }, - "billing_entity" => { - "address_line_1" => customer.billing_entity.address_line1, - "city" => customer.billing_entity.city, - "zip" => customer.billing_entity.zipcode, - "region" => customer.billing_entity.state, - "country" => customer.billing_entity.country - }, - "fees" => [ - { - "item_key" => fee_add_on.item_key, - "item_id" => fee_add_on.id, - "item_code" => "m1", - "unit" => 1, - "amount" => "20.35" - }, - { - "item_key" => fee_add_on_two.item_key, - "item_id" => fee_add_on_two.id, - "item_code" => "1", - "unit" => 1, - "amount" => "20.35" - } - ] - } - ] - end - - it "returns the payload body" do - expect(call).to eq payload_body - end - - context "when invoice is voided" do - let(:payload_body) do + it_behaves_like "an integration payload", :avalara do + def build_expected_payload(mapping_codes, negative_amount: false) [ { "issuing_date" => invoice.issuing_date, @@ -139,30 +30,46 @@ "region" => customer.billing_entity.state, "country" => customer.billing_entity.country }, - "fees" => [ + "fees" => match_array([ + { + "item_key" => add_on_fee.item_key, + "item_id" => add_on_fee.id, + "amount" => negative_amount ? "-2.0" : "2.0", + "unit" => 2.0, + "item_code" => mapping_codes.dig(:add_on, :external_id) + }, + { + "item_key" => billable_metric_fee.item_key, + "item_id" => billable_metric_fee.id, + "amount" => negative_amount ? "-3.0" : "3.0", + "unit" => 3.0, + "item_code" => mapping_codes.dig(:billable_metric, :external_id) + }, { - "item_key" => fee_add_on.item_key, - "item_id" => fee_add_on.id, - "item_code" => "m1", - "unit" => 1, - "amount" => "-20.35" + "item_key" => minimum_commitment_fee.item_key, + "item_id" => minimum_commitment_fee.id, + "amount" => negative_amount ? "-4.0" : "4.0", + "unit" => 4.0, + "item_code" => mapping_codes.dig(:minimum_commitment, :external_id) }, { - "item_key" => fee_add_on_two.item_key, - "item_id" => fee_add_on_two.id, - "item_code" => "1", - "unit" => 1, - "amount" => "-20.35" + "item_key" => subscription_fee.item_key, + "item_id" => subscription_fee.id, + "amount" => negative_amount ? "-5.0" : "5.0", + "unit" => 5.0, + "item_code" => mapping_codes.dig(:subscription, :external_id) } - ] + ]) } ] end - before { invoice.voided! } + context "when invoice is voided" do + before { invoice.voided! } - it "returns the payload body" do - expect(call).to eq payload_body + it "returns the payload body" do + expect(payload).to match_array build_expected_payload(default_mapping_codes, negative_amount: true) + end end end end diff --git a/spec/support/shared_examples/an_integration_payload.rb b/spec/support/shared_examples/an_integration_payload.rb new file mode 100644 index 00000000000..11f08681dbf --- /dev/null +++ b/spec/support/shared_examples/an_integration_payload.rb @@ -0,0 +1,299 @@ +# frozen_string_literal: true + +# This is a shared example that is used to test the payload of an integration. +# It will test the fallback behavior of the integration from billing entity to organization. +# +# It expects a `build_expected_payload` method to be defined in the spec +# ``` +# it_behaves_like "an integration payload", :avalara do +# def build_expected_payload(mapping_codes, some_extra_parameter_with_defaults: false) +# [ +# { +# "issuing_date" => invoice.issuing_date, +# "currency" => invoice.currency, +# "some_extra_parameter_with_defaults" => some_extra_parameter_with_defaults, +# "fees" => match_array([ +# { +# "item_key" => add_on_fee.item_key, +# "item_id" => add_on_fee.id, +# "amount" => "2.0", +# "unit" => 2.0, +# "item_code" => mapping_codes.dig(:add_on, :external_id) +# } +# ]) +# } +# ] +# end +# end +# ``` +# +RSpec.shared_examples "an integration payload" do |integration_type| + let(:integration_type) { integration_type.to_sym } + let(:mappings_on) { [:billing_entity, :organization] } + let(:fallback_items_on) { [:billing_entity, :organization] } + + let(:organization) { create(:organization) } + let(:billing_entity) { create(:billing_entity, organization:) } + let(:integration) { create("#{integration_type}_integration", organization:) } + let(:customer) { create(:customer, organization:, billing_entity:) } + let(:integration_customer) { create("#{integration_type}_customer", customer:, integration:) } + + let(:add_on) { create(:add_on, organization:, name: "Add-on") } + let(:billable_metric) { create(:billable_metric, organization:, name: "Billable Metric") } + let(:plan) { create(:plan, organization:, name: "Plan") } + let(:charge) { create(:standard_charge, plan:, organization:, billable_metric:) } + let(:subscription) { create(:subscription, organization:, plan:) } + + let(:invoice) do + invoice = create( + :invoice, + customer:, + organization:, + billing_entity:, + coupons_amount_cents: 200, + prepaid_credit_amount_cents: 300, + progressive_billing_credit_amount_cents: 100, + credit_notes_amount_cents: 500, + taxes_amount_cents: 300, + issuing_date: DateTime.new(2024, 7, 8) + ) + create(:invoice_subscription, invoice:, subscription:) + invoice + end + let(:payment) { create(:payment, payable: invoice) } + + let(:add_on_fee) { create(:add_on_fee, invoice:, add_on:, units: 2, amount_cents: 200, precise_unit_amount: 100.0, invoice_display_name: "Add-on Fee") } + let(:billable_metric_fee) { create(:charge_fee, invoice:, billable_metric:, units: 3, amount_cents: 300, charge:, invoice_display_name: "Standard Charge Fee", precise_unit_amount: 100.0) } + let(:minimum_commitment_fee) { create(:minimum_commitment_fee, invoice:, units: 4, amount_cents: 400, invoice_display_name: "Minimum Commitment Fee", precise_unit_amount: 100.0) } + let(:subscription_fee) { create(:fee, invoice:, subscription:, units: 5, amount_cents: 500, precise_unit_amount: 100.0) } + let(:fees) { invoice.fees } + + let(:credit_note) { create(:credit_note, customer:, invoice:, issuing_date: DateTime.new(2024, 7, 8)) } + + let(:add_on_credit_note_item) { create(:credit_note_item, credit_note:, fee: add_on_fee, amount_cents: 190) } + let(:billable_metric_credit_note_item) { create(:credit_note_item, credit_note:, fee: billable_metric_fee, amount_cents: 180) } + let(:minimum_commitment_credit_note_item) { create(:credit_note_item, credit_note:, fee: minimum_commitment_fee, amount_cents: 170) } + let(:subscription_credit_note_item) { create(:credit_note_item, credit_note:, fee: subscription_fee, amount_cents: 160) } + + let(:add_on_mapping_on_billing_entity) do + settings = {external_id: "add_on_on_billing_entity", external_account_code: "11", external_name: "add_on_on_billing_entity"} + create_mapping("AddOn", add_on.id, billing_entity:, settings:) + end + let(:billable_metric_mapping_on_billing_entity) do + settings = {external_id: "billable_metric_on_billing_entity", external_account_code: "12", external_name: "billable_metric_on_billing_entity"} + create_mapping("BillableMetric", billable_metric.id, billing_entity:, settings:) + end + let(:commitment_mapping_on_billing_entity) do + settings = {external_id: "commitment_on_billing_entity", external_account_code: "13", external_name: "commitment_on_billing_entity"} + create_collection_mapping(:minimum_commitment, billing_entity:, settings:) + end + let(:subscription_mapping_on_billing_entity) do + settings = {external_id: "subscription_on_billing_entity", external_account_code: "14", external_name: "subscription_on_billing_entity"} + create_collection_mapping(:subscription_fee, billing_entity:, settings:) + end + let(:account_mapping_on_billing_entity) do + settings = {external_id: "account_on_billing_entity", external_account_code: "15", external_name: "account_on_billing_entity"} + create_collection_mapping(:account, billing_entity:, settings:) + end + let(:credit_note_mapping_on_billing_entity) do + settings = {external_id: "credit_note_on_billing_entity", external_account_code: "16", external_name: "credit_note_on_billing_entity"} + create_collection_mapping(:credit_note, billing_entity:, settings:) + end + let(:prepaid_credit_mapping_on_billing_entity) do + settings = {external_id: "prepaid_credit_on_billing_entity", external_account_code: "17", external_name: "prepaid_credit_on_billing_entity"} + create_collection_mapping(:prepaid_credit, billing_entity:, settings:) + end + let(:tax_mapping_on_billing_entity) do + settings = {external_id: "tax_on_billing_entity", external_account_code: "18", external_name: "tax_on_billing_entity"} + create_collection_mapping(:tax, billing_entity:, settings:) + end + let(:coupon_mapping_on_billing_entity) do + settings = {external_id: "coupon_on_billing_entity", external_account_code: "19", external_name: "coupon_on_billing_entity"} + create_collection_mapping(:coupon, billing_entity:, settings:) + end + let(:fallback_item_on_billing_entity) do + settings = {external_id: "fallback_item_on_billing_entity", external_account_code: "20", external_name: "fallback_item_on_billing_entity"} + create_collection_mapping(:fallback_item, billing_entity:, settings:) + end + + let(:add_on_mapping_on_organization) do + settings = {external_id: "add_on_on_organization", external_account_code: "111", external_name: "add_on_on_organization"} + create_mapping("AddOn", add_on.id, billing_entity: nil, settings:) + end + let(:billable_metric_mapping_on_organization) do + settings = {external_id: "billable_metric_on_organization", external_account_code: "112", external_name: "billable_metric_on_organization"} + create_mapping("BillableMetric", billable_metric.id, billing_entity: nil, settings:) + end + let(:commitment_mapping_on_organization) do + settings = {external_id: "commitment_on_organization", external_account_code: "113", external_name: "commitment_on_organization"} + create_collection_mapping(:minimum_commitment, billing_entity: nil, settings:) + end + let(:subscription_mapping_on_organization) do + settings = {external_id: "subscription_on_organization", external_account_code: "114", external_name: "subscription_on_organization"} + create_collection_mapping(:subscription_fee, billing_entity: nil, settings:) + end + let(:account_mapping_on_organization) do + settings = {external_id: "account_on_organization", external_account_code: "115", external_name: "account_on_organization"} + create_collection_mapping(:account, billing_entity: nil, settings:) + end + let(:credit_note_mapping_on_organization) do + settings = {external_id: "credit_note_on_organization", external_account_code: "116", external_name: "credit_note_on_organization"} + create_collection_mapping(:credit_note, billing_entity: nil, settings:) + end + let(:prepaid_credit_mapping_on_organization) do + settings = {external_id: "prepaid_credit_on_organization", external_account_code: "117", external_name: "prepaid_credit_on_organization"} + create_collection_mapping(:prepaid_credit, billing_entity: nil, settings:) + end + let(:tax_mapping_on_organization) do + settings = {external_id: "tax_on_organization", external_account_code: "118", external_name: "tax_on_organization"} + create_collection_mapping(:tax, billing_entity: nil, settings:) + end + let(:coupon_mapping_on_organization) do + settings = {external_id: "coupon_on_organization", external_account_code: "119", external_name: "coupon_on_organization"} + create_collection_mapping(:coupon, billing_entity: nil, settings:) + end + let(:fallback_item_on_organization) do + settings = {external_id: "fallback_item_on_organization", external_account_code: "120", external_name: "fallback_item_on_organization"} + create_collection_mapping(:fallback_item, billing_entity: nil, settings:) + end + + let(:default_mapping_codes) do + { + add_on: {external_id: "add_on_on_billing_entity", external_account_code: "11", external_name: "add_on_on_billing_entity"}, + billable_metric: {external_id: "billable_metric_on_billing_entity", external_account_code: "12", external_name: "billable_metric_on_billing_entity"}, + minimum_commitment: {external_id: "commitment_on_billing_entity", external_account_code: "13", external_name: "commitment_on_billing_entity"}, + subscription: {external_id: "subscription_on_billing_entity", external_account_code: "14", external_name: "subscription_on_billing_entity"}, + account: {external_id: "account_on_billing_entity", external_account_code: "15", external_name: "account_on_billing_entity"}, + credit_note: {external_id: "credit_note_on_billing_entity", external_account_code: "16", external_name: "credit_note_on_billing_entity"}, + prepaid_credit: {external_id: "prepaid_credit_on_billing_entity", external_account_code: "17", external_name: "prepaid_credit_on_billing_entity"}, + tax: {external_id: "tax_on_billing_entity", external_account_code: "18", external_name: "tax_on_billing_entity"}, + coupon: {external_id: "coupon_on_billing_entity", external_account_code: "19", external_name: "coupon_on_billing_entity"}, + fallback_item: {external_id: "fallback_item_on_billing_entity", external_account_code: "20", external_name: "fallback_item_on_billing_entity"} + } + end + + before do + add_on_mapping_on_billing_entity + billable_metric_mapping_on_billing_entity + commitment_mapping_on_billing_entity + subscription_mapping_on_billing_entity + account_mapping_on_billing_entity + credit_note_mapping_on_billing_entity + prepaid_credit_mapping_on_billing_entity + tax_mapping_on_billing_entity + coupon_mapping_on_billing_entity + + add_on_mapping_on_organization + billable_metric_mapping_on_organization + commitment_mapping_on_organization + subscription_mapping_on_organization + account_mapping_on_organization + credit_note_mapping_on_organization + prepaid_credit_mapping_on_organization + tax_mapping_on_organization + coupon_mapping_on_organization + + fallback_item_on_billing_entity + + fallback_item_on_organization + + integration_customer + add_on_credit_note_item + billable_metric_credit_note_item + minimum_commitment_credit_note_item + subscription_credit_note_item + credit_note.reload + + payment + end + + def skip_mapping?(billing_entity) + create_mapping_for_billing_entity = (billing_entity.present? && mappings_on.include?(:billing_entity)) || + (billing_entity.blank? && mappings_on.include?(:organization)) + !create_mapping_for_billing_entity + end + + def skip_fallback_item?(billing_entity) + create_fallback_items_for_billing_entity = (billing_entity.present? && fallback_items_on.include?(:billing_entity)) || + (billing_entity.blank? && fallback_items_on.include?(:organization)) + !create_fallback_items_for_billing_entity + end + + def create_mapping(mappable_type, mappable_id, billing_entity: nil, settings: {}) + return if skip_mapping?(billing_entity) + + create("#{integration_type}_mapping", integration:, mappable_type:, mappable_id:, billing_entity:, settings:) + end + + def create_collection_mapping(mapping_type, billing_entity: nil, settings: {}) + return if mapping_type == :fallback_item && skip_fallback_item?(billing_entity) + return if mapping_type != :fallback_item && skip_mapping?(billing_entity) + + create("#{integration_type}_collection_mapping", integration:, billing_entity:, mapping_type:, settings:) + end + + context "when the mapping is on the billing entity" do + it "returns the payload body" do + expect(payload).to match build_expected_payload(default_mapping_codes) + end + end + + context "when the mapping is not on the billing entity but there are fallback items" do + let(:mappings_on) { [:organization] } + let(:fallback_items_on) { [:billing_entity] } + + it "returns the payload body" do + fallback = {external_id: "fallback_item_on_billing_entity", external_account_code: "20", external_name: "fallback_item_on_billing_entity"} + expect(payload).to match build_expected_payload({ + add_on: fallback, + billable_metric: fallback, + minimum_commitment: fallback, + subscription: fallback, + account: fallback, + credit_note: fallback, + prepaid_credit: fallback, + tax: fallback, + coupon: fallback + }) + end + end + + context "when the mapping is only on the organization" do + let(:mappings_on) { [:organization] } + let(:fallback_items_on) { [:organization] } + + it "returns the payload body" do + expect(payload).to match build_expected_payload({ + add_on: {external_id: "add_on_on_organization", external_account_code: "111", external_name: "add_on_on_organization"}, + billable_metric: {external_id: "billable_metric_on_organization", external_account_code: "112", external_name: "billable_metric_on_organization"}, + minimum_commitment: {external_id: "commitment_on_organization", external_account_code: "113", external_name: "commitment_on_organization"}, + subscription: {external_id: "subscription_on_organization", external_account_code: "114", external_name: "subscription_on_organization"}, + account: {external_id: "account_on_organization", external_account_code: "115", external_name: "account_on_organization"}, + credit_note: {external_id: "credit_note_on_organization", external_account_code: "116", external_name: "credit_note_on_organization"}, + prepaid_credit: {external_id: "prepaid_credit_on_organization", external_account_code: "117", external_name: "prepaid_credit_on_organization"}, + tax: {external_id: "tax_on_organization", external_account_code: "118", external_name: "tax_on_organization"}, + coupon: {external_id: "coupon_on_organization", external_account_code: "119", external_name: "coupon_on_organization"} + }) + end + end + + context "when there are only fallback items on the organization" do + let(:mappings_on) { [] } + let(:fallback_items_on) { [:organization] } + + it "returns the payload body" do + fallback = {external_id: "fallback_item_on_organization", external_account_code: "120", external_name: "fallback_item_on_organization"} + expect(payload).to match build_expected_payload({ + add_on: fallback, + billable_metric: fallback, + minimum_commitment: fallback, + subscription: fallback, + account: fallback, + credit_note: fallback, + prepaid_credit: fallback, + tax: fallback, + coupon: fallback + }) + end + end +end