diff --git a/app/serializers/invoice/line_item_serializer.rb b/app/serializers/invoice/line_item_serializer.rb index bda17dc664..b69c0df96c 100644 --- a/app/serializers/invoice/line_item_serializer.rb +++ b/app/serializers/invoice/line_item_serializer.rb @@ -1,4 +1,4 @@ class Invoice::LineItemSerializer < ActiveModel::Serializer - attributes :added_tax, :currency, :included_tax, :price_with_adjustments, :quantity, :variant_id + attributes :id, :added_tax, :currency, :included_tax, :price_with_adjustments, :quantity, :variant_id has_one :variant, serializer: Invoice::VariantSerializer end diff --git a/app/services/invoice_data_generator.rb b/app/services/invoice_data_generator.rb new file mode 100644 index 0000000000..72f990a90f --- /dev/null +++ b/app/services/invoice_data_generator.rb @@ -0,0 +1,64 @@ +class InvoiceDataGenerator + attr :order + + def initialize(order) + @order = order + end + + # Give the latest invoice's data and the currect order data + # we want to generate a new invoice data that: + # 1. keeps the immutable attributes + # 2. include the update details from the order + def generate + return new_data if old_data.nil? + + # keep the immutable attributes + update_order_attributes + update_line_items + update_payment_methods + + new_data + end + + private + + def update_order_attributes + [:distributor, :order_cycle, :customer].each do |attribute| + new_data[attribute] = old_data[attribute] + end + + return unless new_data[:shipping_method_id] == old_data[:shipping_method_id] + + new_data[:shipping_method] = old_data[:shipping_method] + end + + # if the variant, product or supplier details are updated + # we want to keep the old details in the invoice + def update_line_items + new_data[:sorted_line_items].each do |new_line_item| + old_line_item = old_data[:sorted_line_items].find { |li| li[:id] == new_line_item[:id] } + next if old_line_item.nil? + + new_line_item[:variant] = old_line_item[:variant] + end + end + + # if the payment method is updated, + # we want to keep the old payment method in the invoice + def update_payment_methods + new_data[:payments].each do |new_payment| + old_payment = old_data[:payments].find { |p| p[:id] == new_payment[:id] } + next if old_payment.nil? + + new_payment[:payment_method] = old_payment[:payment_method] + end + end + + def new_data + @new_data ||= order.serialize_for_invoice + end + + def old_data + @old_data ||= order.invoices&.last.data + end +end diff --git a/spec/services/invoice_data_generator_spec.rb b/spec/services/invoice_data_generator_spec.rb new file mode 100644 index 0000000000..8f7eb24462 --- /dev/null +++ b/spec/services/invoice_data_generator_spec.rb @@ -0,0 +1,79 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe InvoiceDataGenerator do + describe '#generate' do + let!(:order) { create(:completed_order_with_fees) } + let!(:latest_invoice){ create(:invoice, order: order, data: order.serialize_for_invoice) } + let(:new_invoice_data) { + InvoiceDataGenerator.new(order).generate + } + let(:new_invoice) { create(:invoice, order: order, data: new_invoice_data) } + let(:new_invoice_presenter) { new_invoice.presenter } + + context "mutable attribute updated" do + it "should reflect the changes" do + new_note = "This is an updated note" + order.update!(note: new_note) + + expect(new_invoice_presenter.order_note).to eq(new_note) + end + end + + context "immutable attribute updated" do + let!(:old_distributor_name) { latest_invoice.presenter.distributor.abn } + it "should not reflect the changes" do + order.distributor.update!(name: 'NEW ABN') + expect(new_invoice_presenter.distributor.abn).to eq(old_distributor_name) + end + end + + context "shipping method" do + it "should keep the old sm details if the shipping method id doesn't change" do + shipping_method = order.shipping_method + old_shipping_method_name = shipping_method.name + shipping_method.update!(name: "NEW NAME") + + expect(new_invoice_presenter.shipping_method.name).to eq(old_shipping_method_name) + end + + it "should update the sm details if the shipping method id is updated" do + new_shipping_method = create(:shipping_method) + order.distributor.shipping_methods << new_shipping_method + order.select_shipping_method new_shipping_method.id + + expect(new_invoice_presenter.shipping_method.name).to eq(new_shipping_method.name) + end + end + + context "line items" do + it "should reflect the changes" do + line_item = order.line_items.first + new_quantity = line_item.quantity + 1 + line_item.update!(quantity: new_quantity) + + expect(new_invoice_presenter.sorted_line_items.first.quantity).to eq(new_quantity) + end + + it "should not reflect variant changes" do + line_item = order.line_items.first + old_variant_name = line_item.variant.display_name + line_item.variant.update!(display_name: "NEW NAME") + + expect(new_invoice_presenter.sorted_line_items.first.variant.display_name).to eq(old_variant_name) + end + end + + context "order without invoices" do + let!(:order) { create(:completed_order_with_fees) } + let(:new_invoice_data) { + InvoiceDataGenerator.new(order).generate + } + + it "should generate a new invoice" do + expect(new_invoice_data).to eql order.serialize_for_invoice + end + end + end +end diff --git a/spec/services/order_invoice_comparator_spec.rb b/spec/services/order_invoice_comparator_spec.rb index 20e743dd61..d381ab64dd 100644 --- a/spec/services/order_invoice_comparator_spec.rb +++ b/spec/services/order_invoice_comparator_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' describe OrderInvoiceComparator do describe '#can_generate_new_invoice?' do let!(:order) { create(:completed_order_with_fees) } - let!(:invoice){ create(:invoice, order: order) } + let!(:invoice){ create(:invoice, order: order, data: order.serialize_for_invoice) } let(:current_state_invoice){ order.current_state_invoice } let(:subject) { OrderInvoiceComparator.new.can_generate_new_invoice?(current_state_invoice, invoice) @@ -47,7 +47,7 @@ describe OrderInvoiceComparator do describe '#can_update_latest_invoice?' do let!(:order) { create(:completed_order_with_fees) } - let!(:invoice){ create(:invoice, order: order) } + let!(:invoice){ create(:invoice, order: order, data: order.serialize_for_invoice) } let(:current_state_invoice){ order.current_state_invoice } let(:subject) { OrderInvoiceComparator.new.can_update_latest_invoice?(current_state_invoice, invoice)