Skip to content

Commit

Permalink
Feat(invoice_custom_sections): add create_invoice_applied_custom_sect…
Browse files Browse the repository at this point in the history
…ions service (#3008)

## Description

Add a service to create applied invoice custom sections for invoices
  • Loading branch information
annvelents committed Jan 14, 2025
1 parent 898f811 commit 79d4545
Show file tree
Hide file tree
Showing 45 changed files with 318 additions and 32 deletions.
2 changes: 1 addition & 1 deletion app/controllers/api/v1/invoices_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ def render_invoice(invoice)
json: ::V1::InvoiceSerializer.new(
invoice,
root_name: 'invoice',
includes: %i[customer integration_customers subscriptions fees credits metadata applied_taxes error_details]
includes: %i[customer integration_customers subscriptions fees credits metadata applied_taxes error_details applied_invoice_custom_sections]
)
)
end
Expand Down
11 changes: 7 additions & 4 deletions app/graphql/resolvers/invoice_custom_sections_resolver.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,13 @@ class InvoiceCustomSectionsResolver < Resolvers::BaseResolver
type Types::InvoiceCustomSections::Object.collection_type, null: true

def resolve(page: nil, limit: nil)
current_organization.invoice_custom_sections.left_outer_joins(:invoice_custom_section_selections).order(
Arel.sql('CASE WHEN invoice_custom_section_selections.id IS NOT NULL THEN 0 ELSE 1 END'),
:name
).page(page).per(limit)
current_organization.invoice_custom_sections
.joins('LEFT JOIN invoice_custom_section_selections ON invoice_custom_sections.id = invoice_custom_section_selections.invoice_custom_section_id
AND invoice_custom_section_selections.customer_id is NULL')
.order(
Arel.sql('CASE WHEN invoice_custom_section_selections.id IS NOT NULL THEN 0 ELSE 1 END'),
:name
).page(page).per(limit)
end
end
end
2 changes: 1 addition & 1 deletion app/models/customer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ def applicable_net_payment_term
def applicable_invoice_custom_sections
return [] if skip_invoice_custom_sections?

selected_invoice_custom_sections.presence || organization.selected_invoice_custom_sections
selected_invoice_custom_sections.order(:name).presence || organization.selected_invoice_custom_sections.order(:name)
end

def editable?
Expand Down
4 changes: 3 additions & 1 deletion app/models/invoice_custom_section.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ class InvoiceCustomSection < ApplicationRecord
has_many :invoice_custom_section_selections, dependent: :destroy

validates :name, presence: true
validates :code, presence: true, uniqueness: {scope: :organization_id}
validates :code,
presence: true,
uniqueness: {conditions: -> { where(deleted_at: nil) }, scope: :organization_id}

default_scope -> { kept }

Expand Down
6 changes: 2 additions & 4 deletions app/serializers/v1/invoice_custom_section_serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,8 @@ def serialize
description: model.description,
details: model.details,
display_name: model.display_name,
selected_for_organization: model.selected_for_organization?,
organization: {
lago_id: model.organization_id
}
applied_to_organization: model.selected_for_organization?,
organization_id: model.organization_id
}
end
end
Expand Down
9 changes: 9 additions & 0 deletions app/serializers/v1/invoice_serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ def serialize
payload.merge!(applied_taxes) if include?(:applied_taxes)
payload.merge!(error_details) if include?(:error_details)
payload.merge!(applied_usage_thresholds) if model.progressive_billing?
payload.merge!(applied_invoice_custom_sections) if include?(:applied_invoice_custom_sections)

payload
end
Expand Down Expand Up @@ -112,5 +113,13 @@ def applied_usage_thresholds
collection_name: 'applied_usage_thresholds'
).serialize
end

def applied_invoice_custom_sections
::CollectionSerializer.new(
model.applied_invoice_custom_sections,
::V1::Invoices::AppliedInvoiceCustomSectionSerializer,
collection_name: 'applied_invoice_custom_sections'
).serialize
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# frozen_string_literal: true

module V1
module Invoices
class AppliedInvoiceCustomSectionSerializer < ModelSerializer
def serialize
{
lago_id: model.id,
lago_invoice_id: model.invoice_id,
code: model.code,
details: model.details,
display_name: model.display_name,
created_at: model.created_at.iso8601
}
end
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions app/services/invoices/add_on_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ def create

create_add_on_fee(invoice)
compute_amounts(invoice)
Invoices::ApplyInvoiceCustomSectionsService.call(invoice:)

invoice.save!

Expand Down
1 change: 1 addition & 0 deletions app/services/invoices/advance_charges_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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:)
Expand Down
34 changes: 34 additions & 0 deletions app/services/invoices/apply_invoice_custom_sections_service.rb
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions app/services/invoices/create_one_off_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:)
Expand Down
1 change: 1 addition & 0 deletions app/services/invoices/paid_credit_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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!
Expand Down
1 change: 1 addition & 0 deletions app/services/invoices/progressive_billing_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
3 changes: 3 additions & 0 deletions app/services/invoices/refresh_draft_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
1 change: 1 addition & 0 deletions app/services/invoices/subscription_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ def call
recurring:,
context:
)
Invoices::ApplyInvoiceCustomSectionsService.call(invoice:)

set_invoice_generated_status unless invoice.pending?
invoice.save!
Expand Down
2 changes: 1 addition & 1 deletion app/services/webhooks/invoices/add_on_created_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def object_serializer
::V1::InvoiceSerializer.new(
object,
root_name: 'invoice',
includes: %i[customer subscriptions fees]
includes: %i[customer subscriptions fees applied_invoice_custom_sections]
)
end

Expand Down
2 changes: 1 addition & 1 deletion app/services/webhooks/invoices/created_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def object_serializer
::V1::InvoiceSerializer.new(
object,
root_name: 'invoice',
includes: %i[customer subscriptions fees credits applied_taxes]
includes: %i[customer subscriptions fees credits applied_taxes applied_invoice_custom_sections]
)
end

Expand Down
2 changes: 1 addition & 1 deletion app/services/webhooks/invoices/one_off_created_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def object_serializer
::V1::InvoiceSerializer.new(
object,
root_name: 'invoice',
includes: %i[customer fees applied_taxes]
includes: %i[customer fees applied_taxes applied_invoice_custom_sections]
)
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def object_serializer
::V1::InvoiceSerializer.new(
object,
root_name: 'invoice',
includes: %i[customer fees applied_taxes]
includes: %i[customer fees applied_taxes applied_invoice_custom_sections]
)
end

Expand Down
3 changes: 3 additions & 0 deletions app/views/templates/invoices/v3.slim
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,9 @@ html
- else
== SlimHelper.render('templates/invoices/v3/_subscriptions_summary', self)


- if applied_invoice_custom_sections.present?
== SlimHelper.render('templates/invoices/v3/_custom_sections', self)
p.body-3.mb-24 = LineBreakHelper.break_lines(organization.invoice_footer)

.powered-by
Expand Down
15 changes: 15 additions & 0 deletions app/views/templates/invoices/v3/_custom_sections.slim
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
css:
.invoice-custom-section {
margin-top: 24px;
border-bottom: 1px solid #D9DEE7;
}

.invoice-custom-section p.section-name {
margin-bottom: 8px;
}

.invoice-custom-sections.body-3.mb-24
- applied_invoice_custom_sections.each do |section|
.invoice-custom-section
p.body-1.section-name = section.display_name
p.body-3.mb-24 = LineBreakHelper.break_lines(section.details)
3 changes: 3 additions & 0 deletions app/views/templates/invoices/v3/charge.slim
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,9 @@ html
td.body-1 = advance_charges? ? I18n.t('invoice.already_paid') : I18n.t('invoice.total_due')
td.body-1 = MoneyHelper.format(total_amount)


- if applied_invoice_custom_sections.present?
== SlimHelper.render('templates/invoices/v3/_custom_sections', self)
p.body-3.mb-24 = LineBreakHelper.break_lines(organization.invoice_footer)

.powered-by
Expand Down
3 changes: 3 additions & 0 deletions app/views/templates/invoices/v3/one_off.slim
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,9 @@ html
td.body-1 = MoneyHelper.format(total_amount)



- if applied_invoice_custom_sections.present?
== SlimHelper.render('templates/invoices/v3/_custom_sections', self)
p.body-3.mb-24 = LineBreakHelper.break_lines(organization.invoice_footer)

.powered-by
Expand Down
18 changes: 12 additions & 6 deletions app/views/templates/invoices/v4.slim
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,9 @@ html


/* ----------------------- variable ----------------------- */
:root {
--border-color: #D9DEE7;
}

@font-face {
font-family: 'Inter var';
Expand Down Expand Up @@ -245,7 +248,7 @@ html
color: #66758F;
}
.invoice-resume-table tr {
border-bottom: 1px solid #D9DEE7;
border-bottom: 1px solid var(--border-color);
}
.invoice-resume-table tr td {
padding-top: 8px;
Expand Down Expand Up @@ -284,12 +287,12 @@ html
width: 50%;
}
.invoice-resume .total-table tr:not(:last-child) td:nth-child(2) {
border-bottom: 1px solid #D9DEE7;
border-bottom: 1px solid var(--border-color);
text-align: left;
width: 35%;
}
.invoice-resume .total-table tr:not(:last-child) td:nth-child(3) {
border-bottom: 1px solid #D9DEE7;
border-bottom: 1px solid var(--border-color);
text-align: right;
width: 15%;
}
Expand Down Expand Up @@ -320,10 +323,10 @@ html
text-align: right;
}
.breakdown-details-table tr td {
border-bottom: 1px solid #d9dee7;
border-bottom: 1px solid var(--border-color);
}
.breakdown-details-table tr:first-child td {
border-top: 1px solid #d9dee7;
border-top: 1px solid var(--border-color);
}

.powered-by {
Expand Down Expand Up @@ -365,7 +368,7 @@ html
}
.invoice-resume-table tr.details.subtotal td {
padding-bottom: 8px;
border-bottom: 1px solid #d9dee7;
border-bottom: 1px solid var(--border-color);
color: #19212e;
}

Expand Down Expand Up @@ -463,6 +466,9 @@ html
- applied_usage_threshold = applied_usage_thresholds.order(created_at: :asc).last
= I18n.t('invoice.reached_usage_threshold', usage_amount: MoneyHelper.format(applied_usage_threshold.lifetime_usage_amount), threshold_amount: MoneyHelper.format(applied_usage_threshold.passed_threshold_amount))

- if applied_invoice_custom_sections.present?
== SlimHelper.render('templates/invoices/v4/_custom_sections', self)

p.body-3.mb-24 = LineBreakHelper.break_lines(organization.invoice_footer)

.powered-by
Expand Down
15 changes: 15 additions & 0 deletions app/views/templates/invoices/v4/_custom_sections.slim
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
css:
.invoice-custom-section {
margin-top: 24px;
border-bottom: 1px solid #D9DEE7;
}

.invoice-custom-section p.section-name {
margin-bottom: 8px;
}

.invoice-custom-sections.body-3.mb-24
- applied_invoice_custom_sections.each do |section|
.invoice-custom-section
p.body-1.section-name = section.display_name
p.body-3.mb-24 = LineBreakHelper.break_lines(section.details)
Loading

0 comments on commit 79d4545

Please sign in to comment.