Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 5 additions & 1 deletion app/graphql/types/customers/usage/charge.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,11 @@ def grouped_usage
end

def presentation_breakdowns
Types::Fees::PresentationBreakdownBuilder.call(object, filter: Types::Fees::PresentationBreakdownBuilder::UNGROUPED)
Types::Fees::PresentationBreakdownBuilder.call(
object,
filter: Types::Fees::PresentationBreakdownBuilder::UNGROUPED,
filter_breakdown: Types::Fees::PresentationBreakdownBuilder::ALL
)
end
end
end
Expand Down
6 changes: 5 additions & 1 deletion app/graphql/types/customers/usage/grouped_usage.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,11 @@ def filters
end

def presentation_breakdowns
Types::Fees::PresentationBreakdownBuilder.call(object, filter: Types::Fees::PresentationBreakdownBuilder::GROUPED)
Types::Fees::PresentationBreakdownBuilder.call(
object,
filter: Types::Fees::PresentationBreakdownBuilder::GROUPED,
filter_breakdown: Types::Fees::PresentationBreakdownBuilder::ALL
)
end
end
end
Expand Down
6 changes: 5 additions & 1 deletion app/graphql/types/customers/usage/projected_charge.rb
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,11 @@ def projected_amount_cents
end

def presentation_breakdowns
Types::Fees::PresentationBreakdownBuilder.call(object, filter: Types::Fees::PresentationBreakdownBuilder::UNGROUPED)
Types::Fees::PresentationBreakdownBuilder.call(
object,
filter: Types::Fees::PresentationBreakdownBuilder::UNGROUPED,
filter_breakdown: Types::Fees::PresentationBreakdownBuilder::ALL
)
end

private
Expand Down
6 changes: 5 additions & 1 deletion app/graphql/types/customers/usage/projected_grouped_usage.rb
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,11 @@ def filters
end

def presentation_breakdowns
Types::Fees::PresentationBreakdownBuilder.call(object, filter: Types::Fees::PresentationBreakdownBuilder::GROUPED)
Types::Fees::PresentationBreakdownBuilder.call(
object,
filter: Types::Fees::PresentationBreakdownBuilder::GROUPED,
filter_breakdown: Types::Fees::PresentationBreakdownBuilder::ALL
)
end

private
Expand Down
6 changes: 5 additions & 1 deletion app/graphql/types/fees/object.rb
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,11 @@ def adjusted_fee_type
end

def presentation_breakdowns
Types::Fees::PresentationBreakdownBuilder.call([object], filter: Types::Fees::PresentationBreakdownBuilder::ALL)
Types::Fees::PresentationBreakdownBuilder.call(
[object],
filter: Types::Fees::PresentationBreakdownBuilder::ALL,
filter_breakdown: Types::Fees::PresentationBreakdownBuilder::DISPLAY_IN_INVOICE
)
end
end
end
Expand Down
15 changes: 10 additions & 5 deletions app/graphql/types/fees/presentation_breakdown_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,26 @@ class PresentationBreakdownBuilder
UNGROUPED = :ungrouped
GROUPED = :grouped

def self.call(fees, filter:)
new(fees, filter:).call
DISPLAY_IN_INVOICE = :display_in_invoice

def self.call(fees, filter:, filter_breakdown:)
new(fees, filter:, filter_breakdown:).call
end

def initialize(fees, filter:)
def initialize(fees, filter:, filter_breakdown:)
@fees = fees
@filter = filter
@filter_breakdown = filter_breakdown
end

def call
Array(fees).flat_map do |fee|
next [] if filter == UNGROUPED && fee.grouped_by.present?
next [] if filter == GROUPED && fee.grouped_by.blank?

fee.presentation_breakdowns.map do |breakdown|
breakdowns = (filter_breakdown == DISPLAY_IN_INVOICE) ? fee.presentation_breakdowns_displayed_in_invoice : fee.presentation_breakdowns

breakdowns.map do |breakdown|
{
presentation_by: breakdown.presentation_by,
units: breakdown.units.to_s
Expand All @@ -32,7 +37,7 @@ def call

private

attr_reader :fees, :filter
attr_reader :fees, :filter, :filter_breakdown
end
end
end
29 changes: 25 additions & 4 deletions app/models/fee.rb
Original file line number Diff line number Diff line change
Expand Up @@ -172,11 +172,32 @@ def currency
amount_currency
end

def presentation_breakdowns_displayable_in_invoice?
return false unless charge?
def presentation_group_keys_values_displayed_in_invoice
return [] unless charge

@presentation_group_keys_values_displayed_in_invoice ||= charge.presentation_group_keys_values_displayed_in_invoice
end

def presentation_breakdowns_displayed_in_invoice
keys = presentation_group_keys_values_displayed_in_invoice

return [] if keys.blank?

if defined?(@presentation_breakdowns_displayed_in_invoice)
return @presentation_breakdowns_displayed_in_invoice
end

rows = Hash.new(0)
presentation_breakdowns.each do |breakdown|
presentation_by = breakdown.presentation_by
values = keys.filter_map { |key| [key, presentation_by[key]] if presentation_by.key?(key) }

next if values.empty?

rows[values] += breakdown.units
end

displayable_keys = charge.presentation_group_keys_values_displayed_in_invoice
displayable_keys.present? && presentation_breakdowns.any? { |b| displayable_keys.any? { |k| b.presentation_by[k].present? } }
@presentation_breakdowns_displayed_in_invoice = rows.map { |values, units| PresentationBreakdown.new(fee: self, presentation_by: values.to_h, units:) }
end

def basic_rate_percentage?
Expand Down
4 changes: 2 additions & 2 deletions app/serializers/v1/customers/charge_usage_serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def serialize
billable_metric: billable_metric_data(fee),
filters: filters(fees),
grouped_usage: grouped_usage(fees),
presentation_breakdowns: PresentationBreakdownBuilder.call(fees, filter: PresentationBreakdownBuilder::UNGROUPED)
presentation_breakdowns: V1::Customers::PresentationBreakdownBuilder.call(fees, filter: V1::Customers::PresentationBreakdownBuilder::UNGROUPED, filter_breakdown: V1::Customers::PresentationBreakdownBuilder::ALL)
}
end
end
Expand Down Expand Up @@ -112,7 +112,7 @@ def build_grouped_usage_data(grouped_fees)
**usage_data.except(:amount_currency),
grouped_by: grouped_fees.first.grouped_by,
filters: filters(grouped_fees),
presentation_breakdowns: PresentationBreakdownBuilder.call(grouped_fees, filter: PresentationBreakdownBuilder::GROUPED)
presentation_breakdowns: V1::Customers::PresentationBreakdownBuilder.call(grouped_fees, filter: V1::Customers::PresentationBreakdownBuilder::GROUPED, filter_breakdown: V1::Customers::PresentationBreakdownBuilder::ALL)
}
end
end
Expand Down
15 changes: 10 additions & 5 deletions app/serializers/v1/customers/presentation_breakdown_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,34 @@ class PresentationBreakdownBuilder
UNGROUPED = :ungrouped
GROUPED = :grouped

def self.call(fees, filter:)
new(fees, filter:).call
DISPLAY_IN_INVOICE = :display_in_invoice

def self.call(fees, filter:, filter_breakdown:)
new(fees, filter:, filter_breakdown:).call
end

def initialize(fees, filter:)
def initialize(fees, filter:, filter_breakdown:)
@fees = fees
@filter = filter
@filter_breakdown = filter_breakdown
end

def call
Array(fees).flat_map do |fee|
next [] if filter == UNGROUPED && fee.grouped_by.present?
next [] if filter == GROUPED && fee.grouped_by.blank?

fee.presentation_breakdowns.map do |breakdown|
breakdowns = (filter_breakdown == DISPLAY_IN_INVOICE) ? fee.presentation_breakdowns_displayed_in_invoice : fee.presentation_breakdowns

breakdowns.map do |breakdown|
::V1::PresentationBreakdownSerializer.new(breakdown).serialize
end
end
end

private

attr_reader :fees, :filter
attr_reader :fees, :filter, :filter_breakdown
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def serialize
billable_metric: billable_metric_data(fee),
filters: cached_filters(fees),
grouped_usage: cached_grouped_usage(fees),
presentation_breakdowns: PresentationBreakdownBuilder.call(fees, filter: PresentationBreakdownBuilder::UNGROUPED)
presentation_breakdowns: V1::Customers::PresentationBreakdownBuilder.call(fees, filter: V1::Customers::PresentationBreakdownBuilder::UNGROUPED, filter_breakdown: V1::Customers::PresentationBreakdownBuilder::ALL)
}
end
end
Expand Down Expand Up @@ -210,7 +210,7 @@ def build_grouped_usage_data(grouped_fees)
**usage_data.except(:amount_currency),
grouped_by: grouped_fees.first.grouped_by,
filters: filters(grouped_fees),
presentation_breakdowns: PresentationBreakdownBuilder.call(grouped_fees, filter: PresentationBreakdownBuilder::GROUPED)
presentation_breakdowns: V1::Customers::PresentationBreakdownBuilder.call(grouped_fees, filter: V1::Customers::PresentationBreakdownBuilder::GROUPED, filter_breakdown: V1::Customers::PresentationBreakdownBuilder::ALL)
}
end

Expand Down
2 changes: 1 addition & 1 deletion app/serializers/v1/fee_serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def serialize
amount_details: model.amount_details,
self_billed: model.invoice&.self_billed || false,
pricing_unit_details:,
presentation_breakdowns: model.presentation_breakdowns.map { |breakdown| PresentationBreakdownSerializer.new(breakdown).serialize }
presentation_breakdowns: model.presentation_breakdowns_displayed_in_invoice.map { |breakdown| PresentationBreakdownSerializer.new(breakdown).serialize }
}

payload.merge!(model.date_boundaries) if model.charge? || model.subscription? || model.add_on? || model.fixed_charge?
Expand Down
4 changes: 2 additions & 2 deletions app/views/helpers/fee_display_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ def self.format_as_currency(fee, amount)
end
end

def self.sorted_presentation_breakdowns_rows(fee, displayable_keys)
rows = fee.presentation_breakdowns.map { |b| [displayable_keys.map { |k| b.presentation_by[k] }, b.units] }
def self.sorted_presentation_breakdowns_displayed_in_invoice(fee)
rows = fee.presentation_breakdowns_displayed_in_invoice.map { |b| [fee.presentation_group_keys_values_displayed_in_invoice.map { |k| b.presentation_by[k] }, b.units] }
clean, blank = rows.partition { |values, _| values.all?(&:present?) }
clean.sort_by { |values, _| values.map(&:to_s) } + blank.sort_by { |values, _| values.compact.map(&:to_s) }
end
Expand Down
7 changes: 3 additions & 4 deletions app/views/templates/invoices/v4/_presentation_breakdowns.slim
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
- fees_with_presentation_breakdowns = fees.select(&:presentation_breakdowns_displayable_in_invoice?)
- fees_with_presentation_breakdowns = fees.select { |fee| fee.charge? && fee.presentation_breakdowns_displayed_in_invoice.any? }

- fees_with_presentation_breakdowns.group_by(&:charge_id).each do |_charge_id, charge_group_fees|
- displayable_keys = charge_group_fees.first.charge.presentation_group_keys_values_displayed_in_invoice
- next if displayable_keys.blank?
- displayable_keys = charge_group_fees.first.presentation_group_keys_values_displayed_in_invoice

.breakdown-details.overflow-auto.mb-24.presentation-breakdowns
table.breakdown-details-table width="100%"
Expand All @@ -11,7 +10,7 @@
td.body-2 = I18n.t("invoice.units")

- charge_group_fees.each do |fee|
- sorted_rows = FeeDisplayHelper.sorted_presentation_breakdowns_rows(fee, displayable_keys)
- sorted_rows = FeeDisplayHelper.sorted_presentation_breakdowns_displayed_in_invoice(fee)
tr.fee
td.body-1 = FeeDisplayHelper.fee_title(fee)
td.body-2 = RoundingHelper.round_decimal_part(fee.units)
Expand Down
33 changes: 16 additions & 17 deletions spec/graphql/resolvers/invoice_resolver_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,14 @@
fixed_charges_to_datetime: Time.current.end_of_month + 1.month
}, presentation_breakdowns: [build(:presentation_breakdown, organization:)])
end
let(:charge_with_display_keys) do
create(:standard_charge, properties: {
"amount" => "100",
"presentation_group_keys" => [{"value" => "department", "options" => {"display_in_invoice" => true}}]
})
end
let(:charge_fee) do
create(:charge_fee, subscription:, invoice:, amount_cents: 10, properties: {
create(:charge_fee, charge: charge_with_display_keys, subscription:, invoice:, amount_cents: 10, properties: {
from_datetime: Time.current.beginning_of_month,
to_datetime: Time.current.end_of_month,
charges_from_datetime: Time.current.beginning_of_month - 1.month,
Expand Down Expand Up @@ -185,9 +191,7 @@
expect(subscription_fee["id"]).to eq(fee.id)
expect(subscription_fee["properties"]["fromDatetime"]).to eq(Time.current.beginning_of_month.to_datetime.iso8601)
expect(subscription_fee["properties"]["toDatetime"]).to eq(Time.current.end_of_month.to_datetime.iso8601)
expect(subscription_fee["presentationBreakdowns"]).to eq([
{"presentationBy" => {"department" => "engineering"}, "units" => "60.0"}
])
expect(subscription_fee["presentationBreakdowns"]).to eq([])

charge_fee_result = data["invoiceSubscriptions"][0]["fees"].find { |f| f["itemType"] == "charge" }
expect(charge_fee_result["id"]).to eq(charge_fee.id)
Expand All @@ -201,9 +205,7 @@
expect(fixed_charge_fee_result["id"]).to eq(fixed_charge_fee.id)
expect(fixed_charge_fee_result["properties"]["fromDatetime"]).to eq((Time.current.beginning_of_month + 1.month).to_datetime.iso8601)
expect(fixed_charge_fee_result["properties"]["toDatetime"]).to eq((Time.current.end_of_month + 1.month).to_datetime.iso8601)
expect(fixed_charge_fee_result["presentationBreakdowns"]).to eq([
{"presentationBy" => {"department" => "engineering"}, "units" => "60.0"}
])
expect(fixed_charge_fee_result["presentationBreakdowns"]).to eq([])
end

it "includes filters for the fee" do
Expand All @@ -228,7 +230,10 @@
it "includes pricing unit usage when available" do
pricing_unit = create(:pricing_unit, organization:)
billable_metric = create(:billable_metric, organization:)
charge = create(:standard_charge, billable_metric:)
charge = create(:standard_charge, billable_metric:, properties: {
"amount" => "100",
"presentation_group_keys" => [{"value" => "department", "options" => {"display_in_invoice" => true}}]
})
applied_pricing_unit = create(:applied_pricing_unit, pricing_unit:, conversion_rate: 2.5)

pricing_unit_usage = build(
Expand Down Expand Up @@ -385,9 +390,7 @@
"itemCode" => add_on.code,
"itemName" => add_on.name
)
expect(add_on_fee["presentationBreakdowns"]).to eq([
{"presentationBy" => {"department" => "engineering"}, "units" => "60.0"}
])
expect(add_on_fee["presentationBreakdowns"]).to eq([])
end

context "with a deleted add_on" do
Expand Down Expand Up @@ -417,9 +420,7 @@
"itemCode" => add_on.code,
"itemName" => add_on.name
)
expect(add_on_fee["presentationBreakdowns"]).to eq([
{"presentationBy" => {"department" => "engineering"}, "units" => "60.0"}
])
expect(add_on_fee["presentationBreakdowns"]).to eq([])
end
end
end
Expand Down Expand Up @@ -472,9 +473,7 @@
graphql_wallet_transaction = data.dig("fees", 0, "walletTransaction")
expect(graphql_wallet_transaction["name"]).to eq("Custom Transaction Name")
expect(graphql_wallet_transaction["walletName"]).to eq("wallet name")
expect(data.dig("fees", 0, "presentationBreakdowns")).to eq([
{"presentationBy" => {"department" => "engineering"}, "units" => "60.0"}
])
expect(data.dig("fees", 0, "presentationBreakdowns")).to eq([])
end
end
end
17 changes: 17 additions & 0 deletions spec/graphql/types/fees/object_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,16 @@
end

context "when fee has a presentation_breakdown" do
let(:charge) do
create(:standard_charge, properties: {
"amount" => "100",
"presentation_group_keys" => [{"value" => "department", "options" => {"display_in_invoice" => true}}]
})
end
let(:fee) do
create(
:charge_fee,
charge:,
presentation_breakdowns: [build(:presentation_breakdown)]
)
end
Expand All @@ -97,9 +104,19 @@
end

context "when fee has a composite presentation_breakdown" do
let(:charge) do
create(:standard_charge, properties: {
"amount" => "100",
"presentation_group_keys" => [
{"value" => "department", "options" => {"display_in_invoice" => true}},
{"value" => "region", "options" => {"display_in_invoice" => true}}
]
})
end
let(:fee) do
create(
:charge_fee,
charge:,
presentation_breakdowns: [
build(
:presentation_breakdown,
Expand Down
Loading
Loading