diff --git a/config/locales/en.yml b/config/locales/en.yml index a7bfca2e21..fc1595d709 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -2697,6 +2697,8 @@ See the %{link} to find out more about %{sitename}'s features and to start using date_end_before_start_error: "must be after start" parameter_not_allowed_error: "You are not authorized to use one or more selected filters for this report." fee_calculated_on_transfer_through_all: "All" + fee_calculated_on_transfer_through_entire_orders: "Entire Orders through %{distributor}" + tax_category_various: "Various" fee_type: payment_method: "Payment Transaction" shipping_method: "Shipment" diff --git a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/data_representations/coordinator_fee.rb b/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/data_representations/coordinator_fee.rb new file mode 100644 index 0000000000..c186b9aae3 --- /dev/null +++ b/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/data_representations/coordinator_fee.rb @@ -0,0 +1,27 @@ +# This module provides EnterpriseFeeSummary::Scope DB result to report mappings for coordinator fees +# in an order cycle. + +module OrderManagement + module Reports + module EnterpriseFeeSummary + module DataRepresentations + class CoordinatorFee + include UsingEnterpriseFee + + def fee_calculated_on_transfer_through_name + i18n_translate("fee_calculated_on_transfer_through_all") + end + + def tax_category_name + return data["tax_category_name"] if data["tax_category_name"].present? + i18n_translate("tax_category_various") if inherits_tax_category? + end + + def inherits_tax_category? + data["enterprise_fee_inherits_tax_category"] == "t" + end + end + end + end + end +end diff --git a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/data_representations/exchange_order_fee.rb b/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/data_representations/exchange_order_fee.rb new file mode 100644 index 0000000000..c92d81c361 --- /dev/null +++ b/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/data_representations/exchange_order_fee.rb @@ -0,0 +1,23 @@ +# This module provides EnterpriseFeeSummary::Scope DB result to report mappings for exchange fees +# that use order-based calculators. + +module OrderManagement + module Reports + module EnterpriseFeeSummary + module DataRepresentations + class ExchangeOrderFee + include UsingEnterpriseFee + + def fee_calculated_on_transfer_through_name + i18n_translate("fee_calculated_on_transfer_through_entire_orders", + distributor: data["adjustment_source_distributor_name"]) + end + + def tax_category_name + data["tax_category_name"] || i18n_translate("tax_category_various") + end + end + end + end + end +end diff --git a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/data_representations/incoming_exchange_line_item_fee.rb b/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/data_representations/incoming_exchange_line_item_fee.rb new file mode 100644 index 0000000000..1bff07b5b0 --- /dev/null +++ b/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/data_representations/incoming_exchange_line_item_fee.rb @@ -0,0 +1,22 @@ +# This module provides EnterpriseFeeSummary::Scope DB result to report mappings for incoming +# exchange fees that use line item -based calculators. + +module OrderManagement + module Reports + module EnterpriseFeeSummary + module DataRepresentations + class IncomingExchangeLineItemFee + include UsingEnterpriseFee + + def fee_calculated_on_transfer_through_name + data["incoming_exchange_enterprise_name"] + end + + def tax_category_name + data["tax_category_name"] || data["product_tax_category_name"] + end + end + end + end + end +end diff --git a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/data_representations/outgoing_exchange_line_item_fee.rb b/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/data_representations/outgoing_exchange_line_item_fee.rb new file mode 100644 index 0000000000..c4b997c774 --- /dev/null +++ b/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/data_representations/outgoing_exchange_line_item_fee.rb @@ -0,0 +1,22 @@ +# This module provides EnterpriseFeeSummary::Scope DB result to report mappings for outgoing +# exchange fees that use line item -based calculators. + +module OrderManagement + module Reports + module EnterpriseFeeSummary + module DataRepresentations + class OutgoingExchangeLineItemFee + include UsingEnterpriseFee + + def fee_calculated_on_transfer_through_name + data["outgoing_exchange_enterprise_name"] + end + + def tax_category_name + data["tax_category_name"] || data["product_tax_category_name"] + end + end + end + end + end +end diff --git a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/data_representations/payment_method_fee.rb b/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/data_representations/payment_method_fee.rb new file mode 100644 index 0000000000..ed0d30af1f --- /dev/null +++ b/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/data_representations/payment_method_fee.rb @@ -0,0 +1,38 @@ +# This module provides EnterpriseFeeSummary::Scope DB result to report mappings for payment method +# fees. + +module OrderManagement + module Reports + module EnterpriseFeeSummary + module DataRepresentations + class PaymentMethodFee + include WithI18n + + attr_reader :data + + def initialize(data) + @data = data + end + + def fee_type + i18n_translate("fee_type.payment_method") + end + + def enterprise_name + data["hub_name"] + end + + def fee_name + data["payment_method_name"] + end + + def fee_placement; end + + def fee_calculated_on_transfer_through_name; end + + def tax_category_name; end + end + end + end + end +end diff --git a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/data_representations/shipping_method_fee.rb b/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/data_representations/shipping_method_fee.rb new file mode 100644 index 0000000000..b96fd99e34 --- /dev/null +++ b/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/data_representations/shipping_method_fee.rb @@ -0,0 +1,40 @@ +# This module provides EnterpriseFeeSummary::Scope DB result to report mappings for shipping method +# fees. + +module OrderManagement + module Reports + module EnterpriseFeeSummary + module DataRepresentations + class ShippingMethodFee + include WithI18n + + attr_reader :data + + def initialize(data) + @data = data + end + + def fee_type + i18n_translate("fee_type.shipping_method") + end + + def enterprise_name + data["hub_name"] + end + + def fee_name + data["shipping_method_name"] + end + + def fee_placement; end + + def fee_calculated_on_transfer_through_name; end + + def tax_category_name + i18n_translate("tax_category_name.shipping_instance_rate") + end + end + end + end + end +end diff --git a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/data_representations/using_enterprise_fee.rb b/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/data_representations/using_enterprise_fee.rb new file mode 100644 index 0000000000..c9c04477df --- /dev/null +++ b/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/data_representations/using_enterprise_fee.rb @@ -0,0 +1,40 @@ +# Different EnterpriseFeeSummary::Scope DB result attributes are checked when dealing with +# enterprise fees that are attached to an order cycle in different ways. +# +# This module provides DB result to report mappings that are common among all rows for enterprise +# fees. These mappings are not complete and should be supplemented with mappings that are specific +# to the way that the enterprise fee is attached to the order cycle. + +module OrderManagement + module Reports + module EnterpriseFeeSummary + module DataRepresentations + module UsingEnterpriseFee + include WithI18n + + attr_reader :data + + def initialize(data) + @data = data + end + + def fee_type + data["fee_type"].try(:capitalize) + end + + def enterprise_name + data["enterprise_name"] + end + + def fee_name + data["fee_name"] + end + + def fee_placement + i18n_translate("fee_placements.#{data['placement_enterprise_role']}") + end + end + end + end + end +end diff --git a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/data_representations/with_i18n.rb b/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/data_representations/with_i18n.rb new file mode 100644 index 0000000000..8440886020 --- /dev/null +++ b/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/data_representations/with_i18n.rb @@ -0,0 +1,15 @@ +module OrderManagement + module Reports + module EnterpriseFeeSummary + module DataRepresentations + module WithI18n + private + + def i18n_translate(translation_key, options = {}) + I18n.t("order_management.reports.enterprise_fee_summary.#{translation_key}", options) + end + end + end + end + end +end diff --git a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/enterprise_fee_type_total_summarizer.rb b/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/enterprise_fee_type_total_summarizer.rb deleted file mode 100644 index cd7875f758..0000000000 --- a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/enterprise_fee_type_total_summarizer.rb +++ /dev/null @@ -1,87 +0,0 @@ -module OrderManagement - module Reports - module EnterpriseFeeSummary - class EnterpriseFeeTypeTotalSummarizer - attr_accessor :data - - def initialize(data) - @data = data - end - - def fee_type - if for_payment_method? - i18n_translate("fee_type.payment_method") - elsif for_shipping_method? - i18n_translate("fee_type.shipping_method") - else - data["fee_type"].try(:capitalize) - end - end - - def enterprise_name - if for_payment_method? - data["hub_name"] - elsif for_shipping_method? - data["hub_name"] - else - data["enterprise_name"] - end - end - - def fee_name - if for_payment_method? - data["payment_method_name"] - elsif for_shipping_method? - data["shipping_method_name"] - else - data["fee_name"] - end - end - - def customer_name - data["customer_name"] - end - - def fee_placement - return if for_payment_method? || for_shipping_method? - - i18n_translate("fee_placements.#{data['placement_enterprise_role']}") - end - - def fee_calculated_on_transfer_through_name - return if for_payment_method? || for_shipping_method? - - transfer_through_all_string = i18n_translate("fee_calculated_on_transfer_through_all") - - data["incoming_exchange_enterprise_name"] || data["outgoing_exchange_enterprise_name"] || - (transfer_through_all_string if data["placement_enterprise_role"] == "coordinator") - end - - def tax_category_name - return if for_payment_method? - return i18n_translate("tax_category_name.shipping_instance_rate") if for_shipping_method? - - data["tax_category_name"] || data["product_tax_category_name"] - end - - def total_amount - data["total_amount"] - end - - private - - def for_payment_method? - data["payment_method_name"].present? - end - - def for_shipping_method? - data["shipping_method_name"].present? - end - - def i18n_translate(translation_key) - I18n.t("order_management.reports.enterprise_fee_summary.#{translation_key}") - end - end - end - end -end diff --git a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/report_service.rb b/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/report_service.rb index 4780629a45..d0fd952161 100644 --- a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/report_service.rb +++ b/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/report_service.rb @@ -25,7 +25,7 @@ module OrderManagement def enterprise_fee_type_total_list enterprise_fees_by_customer.map do |total_data| - summarizer = EnterpriseFeeTypeTotalSummarizer.new(total_data) + summarizer = Summarizer.new(total_data) ReportData::EnterpriseFeeTypeTotal.new.tap do |total| enterprise_fee_type_summarizer_to_total_attributes.each do |attribute| diff --git a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/scope.rb b/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/scope.rb index b3f3168518..9ebec61417 100644 --- a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/scope.rb +++ b/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/scope.rb @@ -31,7 +31,8 @@ module OrderManagement include_payment_fee_details include_shipping_fee_details include_enterprise_fee_details - include_line_item_details + include_order_source_details + include_line_item_source_details include_incoming_exchange_details include_outgoing_exchange_details @@ -169,14 +170,38 @@ module OrderManagement ) end - # If for line item - Use data only if spree_line_items.id is present + # If for order source + # + # Includes: + # * Source order + # * Distributor + def include_order_source_details + join_scope( + <<-JOIN_STRING.strip_heredoc + LEFT OUTER JOIN spree_orders AS adjustment_source_orders + ON ( + spree_adjustments.source_type = 'Spree::Order' + AND adjustment_source_orders.id = spree_adjustments.source_id + ) + JOIN_STRING + ) + + join_scope( + <<-JOIN_STRING.strip_heredoc + LEFT OUTER JOIN enterprises AS adjustment_source_distributors + ON (adjustment_source_distributors.id = adjustment_source_orders.distributor_id) + JOIN_STRING + ) + end + + # If for line item source - Use data only if spree_line_items.id is present # # Includes: # * Line item # * Variant # * Product # * Tax category of product, if enterprise fee tells to inherit - def include_line_item_details + def include_line_item_source_details join_scope( <<-JOIN_STRING.strip_heredoc LEFT OUTER JOIN spree_line_items @@ -316,7 +341,8 @@ module OrderManagement group("enterprise_fees.id", "enterprises.id", "customers.id", "hubs.id", "spree_payment_methods.id", "spree_shipping_methods.id", "adjustment_metadata.enterprise_role", "spree_tax_categories.id", - "product_tax_categories.id", "incoming_exchange_enterprises.id", + "product_tax_categories.id", "spree_adjustments.source_type", + "adjustment_source_distributors.id", "incoming_exchange_enterprises.id", "outgoing_exchange_enterprises.id") end end @@ -331,8 +357,11 @@ module OrderManagement enterprise_fees.fee_type AS fee_type, customers.name AS customer_name, customers.email AS customer_email, enterprise_fees.fee_type AS fee_type, enterprise_fees.name AS fee_name, spree_tax_categories.name AS tax_category_name, + enterprise_fees.inherits_tax_category AS enterprise_fee_inherits_tax_category, product_tax_categories.name AS product_tax_category_name, adjustment_metadata.enterprise_role AS placement_enterprise_role, + spree_adjustments.source_type AS adjustment_source_type, + adjustment_source_distributors.name AS adjustment_source_distributor_name, incoming_exchange_enterprises.name AS incoming_exchange_enterprise_name, outgoing_exchange_enterprises.name AS outgoing_exchange_enterprise_name JOIN_STRING diff --git a/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/summarizer.rb b/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/summarizer.rb new file mode 100644 index 0000000000..ad678dc33b --- /dev/null +++ b/engines/order_management/app/services/order_management/reports/enterprise_fee_summary/summarizer.rb @@ -0,0 +1,76 @@ +module OrderManagement + module Reports + module EnterpriseFeeSummary + class Summarizer + attr_reader :data + + delegate :fee_type, :enterprise_name, :fee_name, :fee_placement, + :fee_calculated_on_transfer_through_name, :tax_category_name, to: :representation + + def initialize(data) + @data = data + end + + def customer_name + data["customer_name"] + end + + def total_amount + data["total_amount"] + end + + private + + def representation + @representation ||= representation_klass.new(data) + end + + def representation_klass + return DataRepresentations::PaymentMethodFee if for_payment_method? + return DataRepresentations::ShippingMethodFee if for_shipping_method? + enterprise_fee_adjustment_presentation_klass if for_enterprise_fee? + end + + def enterprise_fee_adjustment_presentation_klass + return DataRepresentations::CoordinatorFee if for_coordinator_fee? + return DataRepresentations::ExchangeOrderFee if for_order_adjustment_source? + return unless for_line_item_adjustment_source? + return DataRepresentations::IncomingExchangeLineItemFee if for_incoming_exchange? + return DataRepresentations::OutgoingExchangeLineItemFee if for_outgoing_exchange? + end + + def for_payment_method? + data["payment_method_name"].present? + end + + def for_shipping_method? + data["shipping_method_name"].present? + end + + def for_enterprise_fee? + data["fee_name"].present? + end + + def for_coordinator_fee? + data["placement_enterprise_role"] == "coordinator" + end + + def for_incoming_exchange? + data["placement_enterprise_role"] == "supplier" + end + + def for_outgoing_exchange? + data["placement_enterprise_role"] == "distributor" + end + + def for_order_adjustment_source? + data["adjustment_source_type"] == "Spree::Order" + end + + def for_line_item_adjustment_source? + data["adjustment_source_type"] == "Spree::LineItem" + end + end + end + end +end diff --git a/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/report_service_spec.rb b/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/report_service_spec.rb index 83a3f77576..2161bbb4c6 100644 --- a/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/report_service_spec.rb +++ b/engines/order_management/spec/services/order_management/reports/enterprise_fee_summary/report_service_spec.rb @@ -124,9 +124,9 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do ["Payment Transaction", "Sample Distributor", "Sample Payment Method", "Sample Customer", nil, nil, nil, "4.00"], ["Sales", "Sample Coordinator", "Coordinator Fee 2", "Another Customer", - "Coordinator", "All", "Sample Product Tax", "1024.00"], + "Coordinator", "All", "Various", "1024.00"], ["Sales", "Sample Coordinator", "Coordinator Fee 2", "Sample Customer", - "Coordinator", "All", "Sample Product Tax", "2048.00"], + "Coordinator", "All", "Various", "2048.00"], ["Sales", "Sample Distributor", "Distributor Fee 2", "Another Customer", "Outgoing", "Sample Distributor", "Sample Product Tax", "8.00"], ["Sales", "Sample Distributor", "Distributor Fee 2", "Sample Customer", @@ -206,6 +206,98 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do end end end + + context "with order-based enterprise fee calculator" do + let!(:producer_fee) do + tax_category = create(:tax_category, name: "Producer Tax A") + create(:enterprise_fee, :flat_rate, name: "Producer Fee A", enterprise: producer, + fee_type: "sales", tax_category: tax_category, + amount: 10) + end + let!(:coordinator_fee) do + tax_category = create(:tax_category, name: "Coordinator Tax A") + create(:enterprise_fee, :flat_rate, name: "Coordinator Fee A", enterprise: coordinator, + fee_type: "admin", tax_category: tax_category, + amount: 15) + end + let!(:coordinator_fee_inheriting_product_tax_category) do + create(:enterprise_fee, :flat_rate, name: "Coordinator Fee B", enterprise: coordinator, + fee_type: "admin", inherits_tax_category: true, + amount: 20) + end + let!(:coordinator_fee_without_tax) do + create(:enterprise_fee, :flat_rate, name: "Coordinator Fee C", enterprise: coordinator, + fee_type: "admin", inherits_tax_category: false, + amount: 25) + end + let!(:distributor_fee) do + tax_category = create(:tax_category, name: "Distributor Tax A") + create(:enterprise_fee, :flat_rate, name: "Distributor Fee A", enterprise: distributor, + fee_type: "admin", inherits_tax_category: false, + amount: 30) + end + + let!(:coordinator_fees) do + [ + coordinator_fee, + coordinator_fee_inheriting_product_tax_category, + coordinator_fee_without_tax + ] + end + + let!(:order_cycle) do + create(:simple_order_cycle, coordinator: coordinator, coordinator_fees: coordinator_fees) + end + + let!(:variant_incoming_exchange_fees) { [producer_fee, coordinator_fee, distributor_fee] } + let!(:variant_outgoing_exchange_fees) { [producer_fee, coordinator_fee, distributor_fee] } + + let!(:variant) do + prepare_variant(incoming_exchange_fees: variant_incoming_exchange_fees, + outgoing_exchange_fees: variant_outgoing_exchange_fees) + end + + let!(:customer_order) { prepare_order(customer: customer) } + + it "fetches data correctly" do + totals = service.list + + expect(totals.length).to eq(11) + + entire_orders_text = i18n_translate("fee_calculated_on_transfer_through_entire_orders", + distributor: "Sample Distributor") + various_tax_categories_text = i18n_translate("tax_category_various") + + expected_result = [ + ["Admin", "Sample Coordinator", "Coordinator Fee A", "Sample Customer", + "Coordinator", "All", "Coordinator Tax A", "15.00"], + ["Admin", "Sample Coordinator", "Coordinator Fee A", "Sample Customer", + "Incoming", entire_orders_text, "Coordinator Tax A", "15.00"], + ["Admin", "Sample Coordinator", "Coordinator Fee A", "Sample Customer", + "Outgoing", entire_orders_text, "Coordinator Tax A", "15.00"], + ["Admin", "Sample Coordinator", "Coordinator Fee B", "Sample Customer", + "Coordinator", "All", various_tax_categories_text, "20.00"], + ["Admin", "Sample Coordinator", "Coordinator Fee C", "Sample Customer", + "Coordinator", "All", nil, "25.00"], + ["Admin", "Sample Distributor", "Distributor Fee A", "Sample Customer", + "Incoming", entire_orders_text, various_tax_categories_text, "30.00"], + ["Admin", "Sample Distributor", "Distributor Fee A", "Sample Customer", + "Outgoing", entire_orders_text, various_tax_categories_text, "30.00"], + ["Payment Transaction", "Sample Distributor", "Sample Payment Method", "Sample Customer", + nil, nil, nil, "2.00"], + ["Sales", "Sample Producer", "Producer Fee A", "Sample Customer", + "Incoming", entire_orders_text, "Producer Tax A", "10.00"], + ["Sales", "Sample Producer", "Producer Fee A", "Sample Customer", + "Outgoing", entire_orders_text, "Producer Tax A", "10.00"], + ["Shipment", "Sample Distributor", "Sample Shipping Method", "Sample Customer", + nil, nil, "Platform Rate", "1.00"] + ] + + expected_result.each_with_index do |expected_attributes, row_index| + expect_total_attributes(totals[row_index], expected_attributes) + end + end + end end describe "filtering results based on permissions" do @@ -488,6 +580,10 @@ describe OrderManagement::Reports::EnterpriseFeeSummary::ReportService do # Helper methods for example group + def i18n_translate(translation_key, options = {}) + I18n.t("order_management.reports.enterprise_fee_summary.#{translation_key}", options) + end + def expect_total_attributes(total, expected_attribute_list) actual_attribute_list = [total.fee_type, total.enterprise_name, total.fee_name, total.customer_name, total.fee_placement, diff --git a/spec/factories/calculated_adjustment_factory.rb b/spec/factories/calculated_adjustment_factory.rb index 50ae6f5fce..3e9c6f5cc2 100644 --- a/spec/factories/calculated_adjustment_factory.rb +++ b/spec/factories/calculated_adjustment_factory.rb @@ -1,12 +1,23 @@ -attach_per_item_trait = proc do - trait :per_item do - transient { amount 1 } - calculator { build(:calculator_per_item, preferred_amount: amount) } +FactoryBot.define do + factory :calculator_flat_rate, class: Spree::Calculator::FlatRate do + preferred_amount { generate(:calculator_amount) } end end FactoryBot.modify do - factory :payment_method, &attach_per_item_trait - factory :shipping_method, &attach_per_item_trait - factory :enterprise_fee, &attach_per_item_trait + attach_calculator_traits = proc do + trait :flat_rate do + transient { amount 1 } + calculator { build(:calculator_flat_rate, preferred_amount: amount) } + end + + trait :per_item do + transient { amount 1 } + calculator { build(:calculator_per_item, preferred_amount: amount) } + end + end + + factory :payment_method, &attach_calculator_traits + factory :shipping_method, &attach_calculator_traits + factory :enterprise_fee, &attach_calculator_traits end