diff --git a/app/services/customers/manage_invoice_custom_sections_service.rb b/app/services/customers/manage_invoice_custom_sections_service.rb index 19432cbb6d4..ba6585dd3da 100644 --- a/app/services/customers/manage_invoice_custom_sections_service.rb +++ b/app/services/customers/manage_invoice_custom_sections_service.rb @@ -24,8 +24,8 @@ def call if !section_ids.nil? || !section_codes.nil? customer.skip_invoice_custom_sections = false - return result if customer.applicable_invoice_custom_sections.ids == section_ids || - customer.applicable_invoice_custom_sections.map(&:code) == section_codes + return result if customer.selected_invoice_custom_sections.ids == section_ids || + customer.selected_invoice_custom_sections.map(&:code) == section_codes assign_selected_sections end diff --git a/app/services/invoices/add_on_service.rb b/app/services/invoices/add_on_service.rb index 791665b2070..bc98bdd1110 100644 --- a/app/services/invoices/add_on_service.rb +++ b/app/services/invoices/add_on_service.rb @@ -26,6 +26,7 @@ def create create_add_on_fee(invoice) compute_amounts(invoice) + Invoices::ApplyInvoiceCustomSectionsService.call(invoice:) invoice.save! diff --git a/app/services/invoices/advance_charges_service.rb b/app/services/invoices/advance_charges_service.rb index 159159ba036..a5ade782fde 100644 --- a/app/services/invoices/advance_charges_service.rb +++ b/app/services/invoices/advance_charges_service.rb @@ -69,6 +69,7 @@ def create_group_invoice end Invoices::ComputeAmountsFromFees.call(invoice:) + Invoices::ApplyInvoiceCustomSectionsService.call(invoice:) invoice.payment_status = :succeeded Invoices::TransitionToFinalStatusService.call(invoice:) diff --git a/app/services/invoices/apply_invoice_custom_sections_service.rb b/app/services/invoices/apply_invoice_custom_sections_service.rb new file mode 100644 index 00000000000..f9349474a98 --- /dev/null +++ b/app/services/invoices/apply_invoice_custom_sections_service.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module Invoices + class ApplyInvoiceCustomSectionsService < BaseService + def initialize(invoice:) + @invoice = invoice + @customer = invoice.customer + + super() + end + + def call + result.applied_sections = [] + return result if customer.skip_invoice_custom_sections + + customer.applicable_invoice_custom_sections.each do |custom_section| + invoice.applied_invoice_custom_sections.create!( + code: custom_section.code, + details: custom_section.details, + display_name: custom_section.display_name, + name: custom_section.name + ) + end + result.applied_sections = invoice.applied_invoice_custom_sections + result + rescue ActiveRecord::RecordInvalid => e + result.record_validation_failure!(record: e.record) + end + + private + + attr_reader :invoice, :customer + end +end diff --git a/app/services/invoices/create_one_off_service.rb b/app/services/invoices/create_one_off_service.rb index 44953c03332..b5719e92bdc 100644 --- a/app/services/invoices/create_one_off_service.rb +++ b/app/services/invoices/create_one_off_service.rb @@ -35,6 +35,7 @@ def call end Invoices::ComputeAmountsFromFees.call(invoice:, provider_taxes: result.fees_taxes) + Invoices::ApplyInvoiceCustomSectionsService.call(invoice:) invoice.payment_status = invoice.total_amount_cents.positive? ? :pending : :succeeded Invoices::TransitionToFinalStatusService.call(invoice:) invoice.save! diff --git a/app/services/invoices/create_pay_in_advance_charge_service.rb b/app/services/invoices/create_pay_in_advance_charge_service.rb index d78a6bdc0be..f002e8d7b5a 100644 --- a/app/services/invoices/create_pay_in_advance_charge_service.rb +++ b/app/services/invoices/create_pay_in_advance_charge_service.rb @@ -41,6 +41,7 @@ def call Invoices::ComputeAmountsFromFees.call(invoice:, provider_taxes: result.fees_taxes) create_credit_note_credit create_applied_prepaid_credit if should_create_applied_prepaid_credit? + Invoices::ApplyInvoiceCustomSectionsService.call(invoice:) invoice.payment_status = invoice.total_amount_cents.positive? ? :pending : :succeeded Invoices::TransitionToFinalStatusService.call(invoice:) diff --git a/app/services/invoices/paid_credit_service.rb b/app/services/invoices/paid_credit_service.rb index 401d04ef29d..3bf445b9735 100644 --- a/app/services/invoices/paid_credit_service.rb +++ b/app/services/invoices/paid_credit_service.rb @@ -22,6 +22,7 @@ def call ActiveRecord::Base.transaction do create_credit_fee(invoice) compute_amounts(invoice) + Invoices::ApplyInvoiceCustomSectionsService.call(invoice:) if License.premium? && wallet_transaction.invoice_requires_successful_payment? invoice.open! diff --git a/app/services/invoices/progressive_billing_service.rb b/app/services/invoices/progressive_billing_service.rb index 514b11a8f6c..e07ac6a039f 100644 --- a/app/services/invoices/progressive_billing_service.rb +++ b/app/services/invoices/progressive_billing_service.rb @@ -21,6 +21,7 @@ def call Credits::ProgressiveBillingService.call(invoice:) Credits::AppliedCouponsService.call(invoice:) + Invoices::ApplyInvoiceCustomSectionsService.call(invoice:) totals_result = Invoices::ComputeTaxesAndTotalsService.call(invoice:) return totals_result if !totals_result.success? && totals_result.error.is_a?(BaseService::UnknownTaxFailure) diff --git a/app/services/invoices/refresh_draft_service.rb b/app/services/invoices/refresh_draft_service.rb index 5a815a387f3..4f61285dea6 100644 --- a/app/services/invoices/refresh_draft_service.rb +++ b/app/services/invoices/refresh_draft_service.rb @@ -56,6 +56,7 @@ def call recurring:, context: ) + Invoices::ApplyInvoiceCustomSectionsService.call(invoice:) invoice.credit_notes.each do |credit_note| subscription_id = cn_subscription_ids.find { |h| h[:credit_note_id] == credit_note.id }[:subscription_id] @@ -121,6 +122,7 @@ def reset_invoice_values invoice_subscriptions.destroy_all invoice.applied_taxes.destroy_all invoice.error_details.discard_all + invoice.applied_invoice_custom_sections.destroy_all invoice.taxes_amount_cents = 0 invoice.total_amount_cents = 0 @@ -129,6 +131,7 @@ def reset_invoice_values invoice.sub_total_excluding_taxes_amount_cents = 0 invoice.sub_total_including_taxes_amount_cents = 0 invoice.progressive_billing_credit_amount_cents = 0 + invoice.save! end end diff --git a/app/services/invoices/subscription_service.rb b/app/services/invoices/subscription_service.rb index 2a5ce41ee3e..f4d20fd6abc 100644 --- a/app/services/invoices/subscription_service.rb +++ b/app/services/invoices/subscription_service.rb @@ -33,6 +33,7 @@ def call recurring:, context: ) + Invoices::ApplyInvoiceCustomSectionsService.call(invoice:) set_invoice_generated_status unless invoice.pending? invoice.save! diff --git a/spec/services/invoices/add_on_service_spec.rb b/spec/services/invoices/add_on_service_spec.rb index 7ca1edc1873..4430340863d 100644 --- a/spec/services/invoices/add_on_service_spec.rb +++ b/spec/services/invoices/add_on_service_spec.rb @@ -107,6 +107,10 @@ let(:service_call) { invoice_service.create } end + it_behaves_like "applies invoice_custom_sections" do + let(:service_call) { invoice_service.create } + end + context 'with customer timezone' do before { applied_add_on.customer.update!(timezone: 'America/Los_Angeles') } diff --git a/spec/services/invoices/apply_invoice_custom_sections_service_spec.rb b/spec/services/invoices/apply_invoice_custom_sections_service_spec.rb new file mode 100644 index 00000000000..e74f6111e80 --- /dev/null +++ b/spec/services/invoices/apply_invoice_custom_sections_service_spec.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Invoices::ApplyInvoiceCustomSectionsService, type: :service do + subject(:invoice_service) { described_class.new(invoice:) } + + let(:organization) { create(:organization) } + let(:customer) { create(:customer, organization:) } + let(:invoice) { create(:invoice, customer:) } + let(:custom_sections) { create_list(:invoice_custom_section, 3, organization:) } + + before do + organization.selected_invoice_custom_sections << custom_sections[1..2] + end + + describe '#call' do + context 'when the customer has skip_invoice_custom_sections flag' do + let(:customer) { create(:customer, organization:, skip_invoice_custom_sections: true) } + + it 'does not apply any custom sections' do + result = invoice_service.call + expect(result).to be_success + expect(result.applied_sections).to be_empty + expect(invoice.reload.applied_invoice_custom_sections).to be_empty + end + end + + context 'when the customer has custom sections' do + before do + customer.selected_invoice_custom_sections << custom_sections[0..1] + end + + it 'applies the custom sections to the invoice' do + result = invoice_service.call + expect(result).to be_success + sections = invoice.reload.applied_invoice_custom_sections + expect(sections.map(&:code)).to match_array(custom_sections[0..1].map(&:code)) + expect(sections.map(&:details)).to match_array(custom_sections[0..1].map(&:details)) + expect(sections.map(&:display_name)).to match_array(custom_sections[0..1].map(&:display_name)) + expect(sections.map(&:name)).to match_array(custom_sections[0..1].map(&:name)) + end + end + + context 'when the customer inherits custom sections from the organization' do + it 'applies the organization\'s sections to the invoice' do + result = invoice_service.call + expect(result).to be_success + sections = invoice.reload.applied_invoice_custom_sections + expect(sections.map(&:code)).to match_array(custom_sections[1..2].map(&:code)) + expect(sections.map(&:details)).to match_array(custom_sections[1..2].map(&:details)) + expect(sections.map(&:display_name)).to match_array(custom_sections[1..2].map(&:display_name)) + expect(sections.map(&:name)).to match_array(custom_sections[1..2].map(&:name)) + end + end + end +end diff --git a/spec/services/invoices/create_one_off_service_spec.rb b/spec/services/invoices/create_one_off_service_spec.rb index 501b0fdf269..af26b5f3f9c 100644 --- a/spec/services/invoices/create_one_off_service_spec.rb +++ b/spec/services/invoices/create_one_off_service_spec.rb @@ -67,6 +67,10 @@ let(:service_call) { create_service.call } end + it_behaves_like "applies invoice_custom_sections" do + let(:service_call) { create_service.call } + end + it 'calls SegmentTrackJob' do invoice = create_service.call.invoice diff --git a/spec/services/invoices/create_pay_in_advance_charge_service_spec.rb b/spec/services/invoices/create_pay_in_advance_charge_service_spec.rb index d7147173e9e..4667671be93 100644 --- a/spec/services/invoices/create_pay_in_advance_charge_service_spec.rb +++ b/spec/services/invoices/create_pay_in_advance_charge_service_spec.rb @@ -318,5 +318,9 @@ expect(result.invoice).to be_finalized end end + + it_behaves_like "applies invoice_custom_sections" do + let(:service_call) { invoice_service.call } + end end end diff --git a/spec/services/invoices/paid_credit_service_spec.rb b/spec/services/invoices/paid_credit_service_spec.rb index 388171cb682..10dd929980c 100644 --- a/spec/services/invoices/paid_credit_service_spec.rb +++ b/spec/services/invoices/paid_credit_service_spec.rb @@ -62,6 +62,10 @@ let(:service_call) { invoice_service.call } end + it_behaves_like "applies invoice_custom_sections" do + let(:service_call) { invoice_service.call } + end + it 'does not enqueue an SendEmailJob' do expect do invoice_service.call diff --git a/spec/services/invoices/progressive_billing_service_spec.rb b/spec/services/invoices/progressive_billing_service_spec.rb index 3977b46faf4..7d9b2e75468 100644 --- a/spec/services/invoices/progressive_billing_service_spec.rb +++ b/spec/services/invoices/progressive_billing_service_spec.rb @@ -232,5 +232,9 @@ it_behaves_like 'syncs invoice' do let(:service_call) { create_service.call } end + + it_behaves_like "applies invoice_custom_sections" do + let(:service_call) { create_service.call } + end end end diff --git a/spec/services/invoices/refresh_draft_and_finalize_service_spec.rb b/spec/services/invoices/refresh_draft_and_finalize_service_spec.rb index c16e9f10714..dfc977abbfd 100644 --- a/spec/services/invoices/refresh_draft_and_finalize_service_spec.rb +++ b/spec/services/invoices/refresh_draft_and_finalize_service_spec.rb @@ -79,6 +79,10 @@ let(:service_call) { finalize_service.call } end + it_behaves_like "applies invoice_custom_sections" do + let(:service_call) { finalize_service.call } + end + it 'enqueues a SendWebhookJob' do expect do finalize_service.call diff --git a/spec/services/invoices/refresh_draft_service_spec.rb b/spec/services/invoices/refresh_draft_service_spec.rb index 590e0d4860d..7cca769a720 100644 --- a/spec/services/invoices/refresh_draft_service_spec.rb +++ b/spec/services/invoices/refresh_draft_service_spec.rb @@ -171,6 +171,10 @@ .to change { invoice.reload.progressive_billing_credit_amount_cents }.from(1239000).to(0) end + it_behaves_like "applies invoice_custom_sections" do + let(:service_call) { refresh_service.call } + end + context 'when there is a tax_integration set up' do let(:integration) { create(:anrok_integration, organization:) } let(:integration_customer) { create(:anrok_customer, integration:, customer:) } @@ -203,6 +207,21 @@ end end + context 'when invoice has other applied invoice_custom_sections' do + let(:invoice_custom_sections) { create_list(:invoice_custom_section, 4, organization: organization) } + let(:applied_invoice_custom_sections) { create_list(:applied_invoice_custom_section, 2, invoice: invoice) } + + before do + applied_invoice_custom_sections + customer.selected_invoice_custom_sections = invoice_custom_sections.take(3) + end + + it 'creates new applied_invoice_custom_sections' do + expect { refresh_service.call }.to change { invoice.reload.applied_invoice_custom_sections.count }.from(2).to(3) + expect(invoice.applied_invoice_custom_sections.map(&:code)).to match(customer.selected_invoice_custom_sections.map(&:code)) + end + end + it 'flags lifetime usage for refresh' do create(:usage_threshold, plan: subscription.plan) diff --git a/spec/services/invoices/subscription_service_spec.rb b/spec/services/invoices/subscription_service_spec.rb index 9a53d022525..3c03494b7d1 100644 --- a/spec/services/invoices/subscription_service_spec.rb +++ b/spec/services/invoices/subscription_service_spec.rb @@ -106,6 +106,10 @@ let(:service_call) { invoice_service.call } end + it_behaves_like "applies invoice_custom_sections" do + let(:service_call) { invoice_service.call } + end + it "enqueues a SendWebhookJob" do expect do invoice_service.call diff --git a/spec/support/shared_examples/applied_invoice_custom_sections.rb b/spec/support/shared_examples/applied_invoice_custom_sections.rb index e69de29bb2d..3b46340a9a4 100644 --- a/spec/support/shared_examples/applied_invoice_custom_sections.rb +++ b/spec/support/shared_examples/applied_invoice_custom_sections.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'applies invoice_custom_sections' do + let(:invoice_custom_sections) { create_list(:invoice_custom_section, 4, organization:) } + + before do + organization.selected_invoice_custom_sections = invoice_custom_sections[2..3] + end + + context 'when the customer has :skip_invoice_custom_sections flag' do + before { customer.update(skip_invoice_custom_sections: true) } + + it 'doesn\'t create applied_invoice_custom_section' do + expect { service_call }.not_to change(AppliedInvoiceCustomSection, :count) + end + end + + context 'when customer follows organizations invoice_custom_sections' do + it 'creates applied_invoice_custom_sections' do + result = service_call + invoice = result.invoice + expect(invoice.applied_invoice_custom_sections.pluck(:code)).to match_array(organization.selected_invoice_custom_sections.pluck(:code)) + end + end +end