diff --git a/app/services/standing_order_form.rb b/app/services/standing_order_form.rb index e2b5bd069b..b749861c06 100644 --- a/app/services/standing_order_form.rb +++ b/app/services/standing_order_form.rb @@ -1,7 +1,7 @@ require 'open_food_network/proxy_order_syncer' class StandingOrderForm - attr_accessor :standing_order, :params, :fee_calculator, :order_update_issues, :validator + attr_accessor :standing_order, :params, :fee_calculator, :order_update_issues, :validator, :order_updater delegate :orders, :order_cycles, :bill_address, :ship_address, :standing_line_items, to: :standing_order delegate :shop, :shop_id, :customer, :customer_id, :begins_at, :ends_at, :proxy_orders, to: :standing_order @@ -11,13 +11,14 @@ class StandingOrderForm delegate :credit_card_id, :credit_card, to: :standing_order delegate :json_errors, :valid?, to: :validator + delegate :order_update_issues, to: :order_updater def initialize(standing_order, params = {}, fee_calculator = nil) @standing_order = standing_order @params = params @fee_calculator = fee_calculator - @order_update_issues = {} @validator = StandingOrderValidator.new(standing_order) + @order_updater = StandingOrderUpdater.new(standing_order) end def save @@ -26,101 +27,17 @@ class StandingOrderForm return false unless valid? standing_order.transaction do proxy_order_syncer.sync! - update_initialised_orders + order_updater.update! standing_order.save! end end private - def update_initialised_orders - future_and_undated_orders.each do |order| - order.assign_attributes(customer_id: customer_id, email: customer.andand.email, distributor_id: shop_id) - - update_bill_address_for(order) if (bill_address.changes.keys & relevant_address_attrs).any? - update_ship_address_for(order) if (ship_address.changes.keys & relevant_address_attrs).any? - update_shipment_for(order) if shipping_method_id_changed? - update_payment_for(order) if payment_method_id_changed? - - changed_standing_line_items.each do |sli| - line_item = order.line_items.find_by_variant_id(sli.variant_id) - if line_item.quantity == sli.quantity_was - line_item.update_attributes(quantity: sli.quantity, skip_stock_check: true) - else - unless line_item.quantity == sli.quantity - product_name = "#{line_item.product.name} - #{line_item.full_name}" - add_order_update_issue(order, product_name) - end - end - end - - new_standing_line_items.each do |sli| - order.line_items.create(variant_id: sli.variant_id, quantity: sli.quantity, skip_stock_check: true) - end - - order.line_items.where(variant_id: standing_line_items.select(&:marked_for_destruction?).map(&:variant_id)).destroy_all - - order.save - end - end - - def future_and_undated_orders - return @future_and_undated_orders unless @future_and_undated_orders.nil? - @future_and_undated_orders = orders.joins(:order_cycle).merge(OrderCycle.not_closed).readonly(false) - end - - def update_bill_address_for(order) - unless addresses_match?(order.bill_address, bill_address) - return add_order_update_issue(order, I18n.t('bill_address')) - end - order.bill_address.update_attributes(bill_address.attributes.slice(*relevant_address_attrs)) - end - - def update_ship_address_for(order) - force_update = force_ship_address_update_for?(order) - return unless force_update || order.shipping_method.require_ship_address? - unless force_update || addresses_match?(order.ship_address, ship_address) - return add_order_update_issue(order, I18n.t('ship_address')) - end - order.ship_address.update_attributes(ship_address.attributes.slice(*relevant_address_attrs)) - end - - def update_payment_for(order) - payment = order.payments.with_state('checkout').where(payment_method_id: payment_method_id_was).last - if payment - payment.andand.void_transaction! - order.payments.create(payment_method_id: payment_method_id, amount: order.reload.total) - else - unless order.payments.with_state('checkout').where(payment_method_id: payment_method_id).any? - add_order_update_issue(order, I18n.t('admin.payment_method')) - end - end - end - - def update_shipment_for(order) - shipment = order.shipments.with_state('pending').where(shipping_method_id: shipping_method_id_was).last - if shipment - shipment.update_attributes(shipping_method_id: shipping_method_id) - order.update_attribute(:shipping_method_id, shipping_method_id) - else - unless order.shipments.with_state('pending').where(shipping_method_id: shipping_method_id).any? - add_order_update_issue(order, I18n.t('admin.shipping_method')) - end - end - end - def proxy_order_syncer OpenFoodNetwork::ProxyOrderSyncer.new(standing_order) end - def changed_standing_line_items - standing_line_items.select{ |sli| sli.changed? && sli.persisted? } - end - - def new_standing_line_items - standing_line_items.select(&:new_record?) - end - def validate_price_estimates item_attributes = params[:standing_line_items_attributes] return if item_attributes.blank? @@ -141,35 +58,4 @@ class StandingOrderForm fees = fee_calculator.indexed_fees_for(variant) (variant.price + fees).to_d end - - def add_order_update_issue(order, issue) - order_update_issues[order.id] ||= [] - order_update_issues[order.id] << issue - end - - def relevant_address_attrs - ["firstname", "lastname", "address1", "zipcode", "city", "state_id", "country_id", "phone"] - end - - def addresses_match?(order_address, standing_order_address) - relevant_address_attrs.all? do |attr| - order_address[attr] == standing_order_address.send("#{attr}_was") || - order_address[attr] == standing_order_address[attr] - end - end - - def force_ship_address_update_for?(order) - return false unless shipping_method.require_ship_address? - distributor_address = order.send(:address_from_distributor) - relevant_address_attrs.all? do |attr| - order.ship_address[attr] == distributor_address[attr] - end - end - - def standing_order_valid? - return if standing_order.valid? - standing_order.errors.each do |k, msg| - errors.add(k, msg) - end - end end diff --git a/app/services/standing_order_updater.rb b/app/services/standing_order_updater.rb new file mode 100644 index 0000000000..94e62f22a2 --- /dev/null +++ b/app/services/standing_order_updater.rb @@ -0,0 +1,129 @@ +# Responsible for ensuring that any updates to a Standing Order are propagated to any +# orders belonging to that Standing Order which have been instantiated + +class StandingOrderUpdater + attr_reader :order_update_issues + + def initialize(standing_order) + @standing_order = standing_order + @order_update_issues = {} + end + + def update! + future_and_undated_orders.all? do |order| + order.assign_attributes(customer_id: customer_id, email: customer.andand.email, distributor_id: shop_id) + + update_bill_address_for(order) if (bill_address.changes.keys & relevant_address_attrs).any? + update_ship_address_for(order) if (ship_address.changes.keys & relevant_address_attrs).any? + update_shipment_for(order) if shipping_method_id_changed? + update_payment_for(order) if payment_method_id_changed? + + changed_standing_line_items.each do |sli| + line_item = order.line_items.find_by_variant_id(sli.variant_id) + if line_item.quantity == sli.quantity_was + line_item.update_attributes(quantity: sli.quantity, skip_stock_check: true) + else + unless line_item.quantity == sli.quantity + product_name = "#{line_item.product.name} - #{line_item.full_name}" + add_order_update_issue(order, product_name) + end + end + end + + new_standing_line_items.each do |sli| + order.line_items.create(variant_id: sli.variant_id, quantity: sli.quantity, skip_stock_check: true) + end + + order.line_items.where(variant_id: standing_line_items.select(&:marked_for_destruction?).map(&:variant_id)).destroy_all + + order.save + end + end + + private + + attr_reader :standing_order + + delegate :orders, :bill_address, :ship_address, :standing_line_items, to: :standing_order + delegate :shop_id, :customer, :customer_id, to: :standing_order + delegate :shipping_method, :shipping_method_id, :payment_method, :payment_method_id, to: :standing_order + delegate :shipping_method_id_changed?, :shipping_method_id_was, to: :standing_order + delegate :payment_method_id_changed?, :payment_method_id_was, to: :standing_order + + def future_and_undated_orders + return @future_and_undated_orders unless @future_and_undated_orders.nil? + @future_and_undated_orders = orders.joins(:order_cycle).merge(OrderCycle.not_closed).readonly(false) + end + + def update_bill_address_for(order) + unless addresses_match?(order.bill_address, bill_address) + return add_order_update_issue(order, I18n.t('bill_address')) + end + order.bill_address.update_attributes(bill_address.attributes.slice(*relevant_address_attrs)) + end + + def update_ship_address_for(order) + force_update = force_ship_address_update_for?(order) + return unless force_update || order.shipping_method.require_ship_address? + unless force_update || addresses_match?(order.ship_address, ship_address) + return add_order_update_issue(order, I18n.t('ship_address')) + end + order.ship_address.update_attributes(ship_address.attributes.slice(*relevant_address_attrs)) + end + + def update_payment_for(order) + payment = order.payments.with_state('checkout').where(payment_method_id: payment_method_id_was).last + if payment + payment.andand.void_transaction! + order.payments.create(payment_method_id: payment_method_id, amount: order.reload.total) + else + unless order.payments.with_state('checkout').where(payment_method_id: payment_method_id).any? + add_order_update_issue(order, I18n.t('admin.payment_method')) + end + end + end + + def update_shipment_for(order) + shipment = order.shipments.with_state('pending').where(shipping_method_id: shipping_method_id_was).last + if shipment + shipment.update_attributes(shipping_method_id: shipping_method_id) + order.update_attribute(:shipping_method_id, shipping_method_id) + else + unless order.shipments.with_state('pending').where(shipping_method_id: shipping_method_id).any? + add_order_update_issue(order, I18n.t('admin.shipping_method')) + end + end + end + + def changed_standing_line_items + standing_line_items.select{ |sli| sli.changed? && sli.persisted? } + end + + def new_standing_line_items + standing_line_items.select(&:new_record?) + end + + def add_order_update_issue(order, issue) + order_update_issues[order.id] ||= [] + order_update_issues[order.id] << issue + end + + def relevant_address_attrs + ["firstname", "lastname", "address1", "zipcode", "city", "state_id", "country_id", "phone"] + end + + def addresses_match?(order_address, standing_order_address) + relevant_address_attrs.all? do |attr| + order_address[attr] == standing_order_address.send("#{attr}_was") || + order_address[attr] == standing_order_address[attr] + end + end + + def force_ship_address_update_for?(order) + return false unless shipping_method.require_ship_address? + distributor_address = order.send(:address_from_distributor) + relevant_address_attrs.all? do |attr| + order.ship_address[attr] == distributor_address[attr] + end + end +end diff --git a/spec/services/standing_order_updater_spec.rb b/spec/services/standing_order_updater_spec.rb new file mode 100644 index 0000000000..1b90a105a5 --- /dev/null +++ b/spec/services/standing_order_updater_spec.rb @@ -0,0 +1,392 @@ +describe StandingOrderUpdater do + describe "updating the shipping method" do + let(:standing_order) { create(:standing_order, with_items: true, with_proxy_orders: true) } + let(:order) { standing_order.proxy_orders.first.initialise_order! } + let(:shipping_method) { standing_order.shipping_method } + let(:new_shipping_method) { create(:shipping_method, distributors: [standing_order.shop]) } + let(:updater) { StandingOrderUpdater.new(standing_order) } + + context "when the shipping method on an order is the same as the standing order" do + let(:params) { { shipping_method_id: new_shipping_method.id } } + + it "updates the shipping_method on the order and on shipments" do + expect(order.shipments.first.shipping_method_id_was).to eq shipping_method.id + standing_order.assign_attributes(params) + expect(updater.update!).to be true + expect(order.reload.shipping_method).to eq new_shipping_method + expect(order.shipments.first.shipping_method).to eq new_shipping_method + end + end + + context "when the shipping method on a shipment is not the same as the original shipping method on the standing order" do + let(:params) { { shipping_method_id: new_shipping_method.id } } + + context "when the shipping method on a shipment is the same as the new shipping method on the standing order" do + before do + # Updating the shipping method on a shipment updates the shipping method on the order, + # and vice-versa via logic in Spree's shipments controller. So updating both here mimics that + # behaviour. + order.shipments.first.update_attributes(shipping_method_id: new_shipping_method.id) + order.update_attributes(shipping_method_id: new_shipping_method.id) + standing_order.assign_attributes(params) + expect(updater.update!).to be true + end + + it "does not update the shipping_method on the standing order or on the pre-altered shipment" do + expect(order.reload.shipping_method).to eq new_shipping_method + expect(order.reload.shipments.first.shipping_method).to eq new_shipping_method + expect(updater.order_update_issues[order.id]).to be nil + end + end + + context "when the shipping method on a shipment is not the same as the new shipping method on the standing order" do + let(:changed_shipping_method) { create(:shipping_method) } + + before do + # Updating the shipping method on a shipment updates the shipping method on the order, + # and vice-versa via logic in Spree's shipments controller. So updating both here mimics that + # behaviour. + order.shipments.first.update_attributes(shipping_method_id: changed_shipping_method.id) + order.update_attributes(shipping_method_id: changed_shipping_method.id) + standing_order.assign_attributes(params) + expect(updater.update!).to be true + end + + it "does not update the shipping_method on the standing order or on the pre-altered shipment" do + expect(order.reload.shipping_method).to eq changed_shipping_method + expect(order.reload.shipments.first.shipping_method).to eq changed_shipping_method + expect(updater.order_update_issues[order.id]).to include "Shipping Method" + end + end + end + end + + describe "changing the payment method" do + let(:standing_order) { create(:standing_order, with_items: true, with_proxy_orders: true) } + let(:order) { standing_order.proxy_orders.first.initialise_order! } + let(:payment_method) { standing_order.payment_method } + let(:new_payment_method) { create(:payment_method, distributors: [standing_order.shop]) } + let(:invalid_payment_method) { create(:payment_method, distributors: [create(:enterprise)]) } + let(:bogus_payment_method) { create(:bogus_payment_method, distributors: [standing_order.shop]) } + let(:updater) { StandingOrderUpdater.new(standing_order) } + + context "when the payment method on an order is the same as the standing order" do + let(:params) { { payment_method_id: new_payment_method.id } } + + it "voids existing payments and creates a new payment with the relevant payment method" do + expect(order.payments.reload.first.payment_method).to eq payment_method + standing_order.assign_attributes(params) + expect(updater.update!).to be true + payments = order.reload.payments + expect(payments.count).to be 2 + expect(payments.with_state('void').count).to be 1 + expect(payments.with_state('checkout').count).to be 1 + expect(payments.with_state('void').first.payment_method).to eq payment_method + expect(payments.with_state('checkout').first.payment_method).to eq new_payment_method + end + end + + context "when the payment method on a payment is not the same as the standing order" do + let(:params) { { payment_method_id: new_payment_method.id } } + + context "when the payment method on a payment is the same as the original payment method on the standing order" do + before do + order.payments.first.update_attribute(:payment_method_id, new_payment_method.id) + standing_order.assign_attributes(params) + expect(updater.update!).to be true + end + + it "keeps pre-altered payments and doesn't add an issue to order_update_issues" do + payments = order.reload.payments + expect(payments.count).to be 1 + expect(payments.first.payment_method).to eq new_payment_method + expect(updater.order_update_issues[order.id]).to be nil + end + end + + context "when the payment method on a shipment is not the same as the original payment method on the standing order" do + let(:changed_payment_method) { create(:payment_method) } + + before do + order.payments.first.update_attribute(:payment_method_id, changed_payment_method.id) + standing_order.assign_attributes(params) + expect(updater.update!).to be true + end + + it "keeps pre-altered payments and adds an issue to order_update_issues" do + payments = order.reload.payments + expect(payments.count).to be 1 + expect(payments.first.payment_method).to eq changed_payment_method + expect(updater.order_update_issues[order.id]).to include "Payment Method" + end + end + end + end + + describe "changing the billing address" do + let(:standing_order) { create(:standing_order, with_items: true, with_proxy_orders: true) } + let(:shipping_method) { standing_order.shipping_method } + let!(:order) { standing_order.proxy_orders.first.initialise_order! } + let!(:bill_address_attrs) { standing_order.bill_address.attributes } + let!(:ship_address_attrs) { standing_order.ship_address.attributes } + let(:params) { { bill_address_attributes: { id: bill_address_attrs["id"], firstname: "Bill", address1: "123 abc st", phone: "1123581321" } } } + let(:updater) { StandingOrderUpdater.new(standing_order) } + + context "when a ship address is not required" do + before { shipping_method.update_attributes(require_ship_address: false) } + + context "when the bill_address on the order matches that on the standing order" do + it "updates all bill_address attrs and ship_address names + phone" do + standing_order.assign_attributes(params) + expect(updater.update!).to be true + expect(updater.order_update_issues.keys).to_not include order.id + order.reload; + expect(order.bill_address.firstname).to eq "Bill" + expect(order.bill_address.lastname).to eq bill_address_attrs["lastname"] + expect(order.bill_address.address1).to eq "123 abc st" + expect(order.bill_address.phone).to eq "1123581321" + expect(order.ship_address.firstname).to eq "Bill" + expect(order.ship_address.lastname).to eq ship_address_attrs["lastname"] + expect(order.ship_address.address1).to eq ship_address_attrs["address1"] + expect(order.ship_address.phone).to eq "1123581321" + end + end + + context "when the bill_address on the order doesn't match that on the standing order" do + before { order.bill_address.update_attributes(firstname: "Jane") } + it "does not update bill_address or ship_address on the order" do + standing_order.assign_attributes(params) + expect(updater.update!).to be true + expect(updater.order_update_issues.keys).to include order.id + order.reload; + expect(order.bill_address.firstname).to eq "Jane" + expect(order.bill_address.lastname).to eq bill_address_attrs["lastname"] + expect(order.bill_address.address1).to eq bill_address_attrs["address1"] + expect(order.bill_address.phone).to eq bill_address_attrs["phone"] + expect(order.ship_address.firstname).to eq "Jane" + expect(order.ship_address.lastname).to eq ship_address_attrs["lastname"] + expect(order.ship_address.address1).to eq ship_address_attrs["address1"] + expect(order.ship_address.phone).to eq ship_address_attrs["phone"] + end + end + end + + context "when a ship address is required" do + before { shipping_method.update_attributes(require_ship_address: true) } + + context "when the bill_address on the order matches that on the standing order" do + it "only updates bill_address attrs" do + standing_order.assign_attributes(params) + expect(updater.update!).to be true + expect(updater.order_update_issues.keys).to_not include order.id + order.reload; + expect(order.bill_address.firstname).to eq "Bill" + expect(order.bill_address.lastname).to eq bill_address_attrs["lastname"] + expect(order.bill_address.address1).to eq "123 abc st" + expect(order.bill_address.phone).to eq "1123581321" + expect(order.ship_address.firstname).to eq ship_address_attrs["firstname"] + expect(order.ship_address.lastname).to eq ship_address_attrs["lastname"] + expect(order.ship_address.address1).to eq ship_address_attrs["address1"] + expect(order.ship_address.phone).to eq ship_address_attrs["phone"] + end + end + + context "when the bill_address on the order doesn't match that on the standing order" do + before { order.bill_address.update_attributes(firstname: "Jane") } + + it "does not update bill_address or ship_address on the order" do + standing_order.assign_attributes(params) + expect(updater.update!).to be true + expect(updater.order_update_issues.keys).to include order.id + order.reload; + expect(order.bill_address.firstname).to eq "Jane" + expect(order.bill_address.lastname).to eq bill_address_attrs["lastname"] + expect(order.bill_address.address1).to eq bill_address_attrs["address1"] + expect(order.bill_address.phone).to eq bill_address_attrs["phone"] + expect(order.ship_address.firstname).to eq ship_address_attrs["firstname"] + expect(order.ship_address.lastname).to eq ship_address_attrs["lastname"] + expect(order.ship_address.address1).to eq ship_address_attrs["address1"] + expect(order.ship_address.phone).to eq ship_address_attrs["phone"] + end + end + end + end + + describe "changing the ship address" do + let(:standing_order) { create(:standing_order, with_items: true, with_proxy_orders: true) } + let(:shipping_method) { standing_order.shipping_method } + let!(:order) { standing_order.proxy_orders.first.initialise_order! } + let!(:bill_address_attrs) { standing_order.bill_address.attributes } + let!(:ship_address_attrs) { standing_order.ship_address.attributes } + let(:params) { { ship_address_attributes: { id: ship_address_attrs["id"], firstname: "Ship", address1: "123 abc st", phone: "1123581321" } } } + let(:updater) { StandingOrderUpdater.new(standing_order) } + + context "when a ship address is not required" do + before { shipping_method.update_attributes(require_ship_address: false) } + + it "does not change the ship address" do + standing_order.assign_attributes(params) + expect(updater.update!).to be true + expect(updater.order_update_issues.keys).to_not include order.id + order.reload; + expect(order.ship_address.firstname).to eq ship_address_attrs["firstname"] + expect(order.ship_address.lastname).to eq ship_address_attrs["lastname"] + expect(order.ship_address.address1).to eq ship_address_attrs["address1"] + expect(order.ship_address.phone).to eq ship_address_attrs["phone"] + end + + context "but the shipping method is being changed to one that requires a ship_address" do + let(:new_shipping_method) { create(:shipping_method, require_ship_address: true) } + before { params.merge!(shipping_method_id: new_shipping_method.id) } + + it "updates ship_address attrs" do + standing_order.assign_attributes(params) + expect(updater.update!).to be true + expect(updater.order_update_issues.keys).to_not include order.id + order.reload; + expect(order.ship_address.firstname).to eq "Ship" + expect(order.ship_address.lastname).to eq ship_address_attrs["lastname"] + expect(order.ship_address.address1).to eq "123 abc st" + expect(order.ship_address.phone).to eq "1123581321" + end + end + end + + context "when a ship address is required" do + before { shipping_method.update_attributes(require_ship_address: true) } + + context "when the ship address on the order matches that on the standing order" do + it "updates ship_address attrs" do + standing_order.assign_attributes(params) + expect(updater.update!).to be true + expect(updater.order_update_issues.keys).to_not include order.id + order.reload; + expect(order.ship_address.firstname).to eq "Ship" + expect(order.ship_address.lastname).to eq ship_address_attrs["lastname"] + expect(order.ship_address.address1).to eq "123 abc st" + expect(order.ship_address.phone).to eq "1123581321" + end + end + + context "when the ship address on the order doesn't match that on the standing order" do + before { order.ship_address.update_attributes(firstname: "Jane") } + it "does not update ship_address on the order" do + standing_order.assign_attributes(params) + expect(updater.update!).to be true + expect(updater.order_update_issues.keys).to include order.id + order.reload; + expect(order.ship_address.firstname).to eq "Jane" + expect(order.ship_address.lastname).to eq ship_address_attrs["lastname"] + expect(order.ship_address.address1).to eq ship_address_attrs["address1"] + expect(order.ship_address.phone).to eq ship_address_attrs["phone"] + end + end + end + end + + describe "changing the quantity of a line item" do + let(:standing_order) { create(:standing_order, with_items: true, with_proxy_orders: true) } + let(:order) { standing_order.proxy_orders.first.initialise_order! } + let(:sli) { standing_order.standing_line_items.first } + let(:variant) { sli.variant } + + before { variant.update_attribute(:count_on_hand, 2) } + + context "when quantity is within available stock" do + let(:params) { { standing_line_items_attributes: [{ id: sli.id, quantity: 2}] } } + let(:updater) { StandingOrderUpdater.new(standing_order) } + + it "updates the line_item quantities and totals on all orders" do + expect(order.reload.total.to_f).to eq 59.97 + standing_order.assign_attributes(params) + expect(updater.update!).to be true + line_items = Spree::LineItem.where(order_id: standing_order.orders, variant_id: sli.variant_id) + expect(line_items.map(&:quantity)).to eq [2] + expect(order.reload.total.to_f).to eq 79.96 + end + end + + context "when quantity is greater than available stock" do + let(:params) { { standing_line_items_attributes: [{ id: sli.id, quantity: 3}] } } + let(:updater) { StandingOrderUpdater.new(standing_order) } + + it "updates the line_item quantities and totals on all orders" do + expect(order.reload.total.to_f).to eq 59.97 + standing_order.assign_attributes(params) + expect(updater.update!).to be true + line_items = Spree::LineItem.where(order_id: standing_order.orders, variant_id: sli.variant_id) + expect(line_items.map(&:quantity)).to eq [3] + expect(order.reload.total.to_f).to eq 99.95 + end + end + + context "where the quantity of the item on an initialised order has already been changed" do + let(:params) { { standing_line_items_attributes: [{ id: sli.id, quantity: 3}] } } + let(:updater) { StandingOrderUpdater.new(standing_order) } + let(:changed_line_item) { order.line_items.find_by_variant_id(sli.variant_id) } + + before { variant.update_attribute(:count_on_hand, 3) } + + context "when the changed line_item quantity matches the new quantity on the standing line item" do + before { changed_line_item.update_attributes(quantity: 3) } + + it "does not change the quantity, and doesn't add the order to order_update_issues" do + expect(order.reload.total.to_f).to eq 99.95 + standing_order.assign_attributes(params) + expect(updater.update!).to be true + expect(changed_line_item.reload.quantity).to eq 3 + expect(order.reload.total.to_f).to eq 99.95 + expect(updater.order_update_issues[order.id]).to be nil + end + end + + context "when the changed line_item quantity doesn't match the new quantity on the standing line item" do + before { changed_line_item.update_attributes(quantity: 2) } + + it "does not change the quantity, and adds the order to order_update_issues" do + expect(order.reload.total.to_f).to eq 79.96 + standing_order.assign_attributes(params) + expect(updater.update!).to be true + expect(changed_line_item.reload.quantity).to eq 2 + expect(order.reload.total.to_f).to eq 79.96 + expect(updater.order_update_issues[order.id]).to include "#{changed_line_item.product.name} - #{changed_line_item.full_name}" + end + end + end + end + + describe "adding a new line item" do + let(:standing_order) { create(:standing_order, with_items: true, with_proxy_orders: true) } + let(:order) { standing_order.proxy_orders.first.initialise_order! } + let(:variant) { create(:variant) } + let(:params) { { standing_line_items_attributes: [{ id: nil, variant_id: variant.id, quantity: 1}] } } + let(:updater) { StandingOrderUpdater.new(standing_order) } + + it "adds the line item and updates the total on all orders" do + expect(order.reload.total.to_f).to eq 59.97 + standing_order.assign_attributes(params) + expect(updater.update!).to be true + line_items = Spree::LineItem.where(order_id: standing_order.orders, variant_id: variant.id) + expect(line_items.map(&:quantity)).to eq [1] + expect(order.reload.total.to_f).to eq 79.96 + end + end + + describe "removing an existing line item" do + let(:standing_order) { create(:standing_order, with_items: true, with_proxy_orders: true) } + let(:order) { standing_order.proxy_orders.first.initialise_order! } + let(:sli) { standing_order.standing_line_items.first } + let(:variant) { sli.variant } + let(:params) { { standing_line_items_attributes: [{ id: sli.id, _destroy: true }] } } + let(:updater) { StandingOrderUpdater.new(standing_order) } + + it "removes the line item and updates totals on all orders" do + expect(order.reload.total.to_f).to eq 59.97 + standing_order.assign_attributes(params) + expect(updater.update!).to be true + line_items = Spree::LineItem.where(order_id: standing_order.orders, variant_id: variant.id) + expect(line_items.count).to be 0 + expect(order.reload.total.to_f).to eq 39.98 + end + end +end