Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat(invoice_custom_sections): use custom sections in pdf #3013

Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 subscriptions fees credits metadata applied_taxes error_details]
includes: %i[customer 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
Comment on lines +19 to +23
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blocker here but maybe this should be turned into a scope. What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's not needed, because this sorting - by selected for organization, and then alphabetically - only needed in the graphql response... I can't see where else it will be useful... or do you mean it will be better for readability?
(in all other places we're usually using the selected sections and only sort them alphabetically)

).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
2 changes: 1 addition & 1 deletion app/serializers/v1/invoice_custom_section_serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ def serialize
description: model.description,
details: model.details,
display_name: model.display_name,
selected_for_organization: model.selected_for_organization?,
applied_for_organization: model.selected_for_organization?,
organization: {
lago_id: model.organization_id
}
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
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)
3 changes: 3 additions & 0 deletions app/views/templates/invoices/v4/charge.slim
Original file line number Diff line number Diff line change
Expand Up @@ -560,6 +560,9 @@ html

== SlimHelper.render('templates/invoices/v4/_eu_tax_management', self)

- 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
3 changes: 3 additions & 0 deletions app/views/templates/invoices/v4/one_off.slim
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,9 @@ html

== SlimHelper.render('templates/invoices/v4/_eu_tax_management', self)

- 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
span.body-2
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,14 @@

before do
organization.selected_invoice_custom_sections.concat(invoice_custom_sections[2..4])
customer.selected_invoice_custom_sections.concat(invoice_custom_sections[0..1])
end

it_behaves_like 'requires current user'
it_behaves_like 'requires current organization'
it_behaves_like 'requires permission', 'invoice_custom_sections:view'

it 'returns a list of sorted invoice_custom_sections: alphabetical, selected first' do
it 'returns a list of sorted invoice_custom_sections: alphabetical, selected first without duplicates' do
result = execute_graphql(
current_user: membership.user,
current_organization: organization,
Expand Down
4 changes: 2 additions & 2 deletions spec/models/customer_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -433,8 +433,8 @@
context 'when customer does not have any selected_invoice_custom_sections but organization has' do
before { organization.selected_invoice_custom_sections << organization_section }

it 'returns the organization\'s invoice_custom_sections' do
expect(customer.applicable_invoice_custom_sections).to eq([organization_section])
it "returns the organization's invoice_custom_sections" do
expect(customer.applicable_invoice_custom_sections).to match_array([organization_section])
end
end

Expand Down
2 changes: 1 addition & 1 deletion spec/requests/api/v1/customers_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@
sections = json[:customer][:applicable_invoice_custom_sections]
expect(sections).to be_present
expect(sections.length).to eq(2)
expect(sections.map { |sec| sec[:code] }).to eq(invoice_custom_sections.map(&:code))
expect(sections.map { |sec| sec[:code] }).to match_array(invoice_custom_section_codes)
end
end

Expand Down
3 changes: 2 additions & 1 deletion spec/requests/api/v1/invoices_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,8 @@
customer: Hash,
subscriptions: [],
credits: [],
applied_taxes: []
applied_taxes: [],
applied_invoice_custom_sections: []
)
expect(json[:invoice][:fees].first).to include(lago_charge_filter_id: charge_filter.id)
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
'description' => invoice_custom_section.description,
'details' => invoice_custom_section.details,
'display_name' => invoice_custom_section.display_name,
'selected_for_organization' => invoice_custom_section.selected_for_organization?,
'applied_for_organization' => invoice_custom_section.selected_for_organization?,
'organization' => {
'lago_id' => invoice_custom_section.organization_id
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe V1::Invoices::AppliedInvoiceCustomSectionSerializer, type: :serializer do
subject(:serializer) { described_class.new(applied_invoice_custom_section) }

let(:invoice) { create(:invoice) }
let(:applied_invoice_custom_section) do
create(:applied_invoice_custom_section,
invoice:,
code: 'custom_code',
details: 'custom_details',
display_name: 'Custom Display Name',
created_at: Time.current)
end

describe '#serialize' do
it 'serializes the applied invoice custom section correctly' do
serialized_data = serializer.serialize

expect(serialized_data).to include(
lago_id: applied_invoice_custom_section.id,
lago_invoice_id: applied_invoice_custom_section.invoice_id,
code: 'custom_code',
details: 'custom_details',
display_name: 'Custom Display Name',
created_at: applied_invoice_custom_section.created_at.iso8601
)
end
end
end
2 changes: 1 addition & 1 deletion spec/services/invoices/refresh_draft_service_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@

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))
expect(invoice.applied_invoice_custom_sections.map(&:code)).to match_array(customer.selected_invoice_custom_sections.map(&:code))
end
end

Expand Down
Loading