diff --git a/promotions/app/decorators/models/solidus_friendly_promotions/line_item_decorator.rb b/promotions/app/decorators/models/solidus_friendly_promotions/line_item_decorator.rb index 832981d7..e20801e3 100644 --- a/promotions/app/decorators/models/solidus_friendly_promotions/line_item_decorator.rb +++ b/promotions/app/decorators/models/solidus_friendly_promotions/line_item_decorator.rb @@ -3,18 +3,24 @@ module SolidusFriendlyPromotions module LineItemDecorator def self.prepended(base) + base.attr_accessor :quantity_setter base.belongs_to :managed_by_order_action, class_name: "SolidusFriendlyPromotions::PromotionAction", optional: true base.validate :validate_managed_quantity_same, on: :update + base.after_save :reset_quantity_setter end private def validate_managed_quantity_same - if managed_by_order_action && quantity_changed? + if managed_by_order_action && quantity_changed? && quantity_setter != managed_by_order_action errors.add(:quantity, :cannot_be_changed_for_automated_items) end end + def reset_quantity_setter + @quantity_setter = nil + end + Spree::LineItem.prepend self Spree::LineItem.prepend SolidusFriendlyPromotions::DiscountableAmount end diff --git a/promotions/app/models/solidus_friendly_promotions/actions/create_discounted_item.rb b/promotions/app/models/solidus_friendly_promotions/actions/create_discounted_item.rb index 0f22842a..58096dff 100644 --- a/promotions/app/models/solidus_friendly_promotions/actions/create_discounted_item.rb +++ b/promotions/app/models/solidus_friendly_promotions/actions/create_discounted_item.rb @@ -6,9 +6,11 @@ class CreateDiscountedItem < PromotionAction include OrderLevelAction preference :variant_id, :integer preference :quantity, :integer, default: 1 + preference :necessary_quantity, :integer, default: 1 def perform(order) line_item = find_item(order) || create_item(order) + set_quantity(line_item, determine_item_quantity(order)) line_item.current_discounts << discount(line_item) end @@ -24,7 +26,20 @@ def find_item(order) end def create_item(order) - order.line_items.create!(quantity: preferred_quantity, variant: variant, managed_by_order_action: self) + order.line_items.create!(quantity: determine_item_quantity(order), variant: variant, managed_by_order_action: self) + end + + def determine_item_quantity(order) + applicable_line_items = promotion.applicable_line_items(order) + # Integer division will floor automatically, which is what we want here: + # 1 Item, 2 needed: 1 * 1 / 2 => 0 + # 5 items, 2 preferred, 2 needed: 5 / 2 * 2 => 4 + applicable_line_items.sum(&:quantity) / preferred_necessary_quantity * preferred_quantity + end + + def set_quantity(line_item, quantity) + line_item.quantity_setter = self + line_item.quantity = quantity end def variant diff --git a/promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/actions/_create_discounted_item.html.erb b/promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/actions/_create_discounted_item.html.erb index 87da02ec..ae6abb19 100644 --- a/promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/actions/_create_discounted_item.html.erb +++ b/promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/actions/_create_discounted_item.html.erb @@ -5,6 +5,10 @@ <%= form.label :preferred_quantity %> <%= form.number_field :preferred_quantity, class: "fullwidth" %> +
+ <%= form.label :preferred_necessary_quantity %> + <%= form.number_field :preferred_necessary_quantity, class: "fullwidth" %> +
<% end %> <%= render( diff --git a/promotions/config/locales/en.yml b/promotions/config/locales/en.yml index fc927336..20a2c836 100644 --- a/promotions/config/locales/en.yml +++ b/promotions/config/locales/en.yml @@ -203,7 +203,9 @@ en: solidus_friendly_promotions/actions/adjust_shipment: description: Creates a promotion credit on matching shipments solidus_friendly_promotions/actions/create_discounted_item: - description: Creates a discounted item + description: Creates a discounted item with the quantity the applicable line items. + preferred_quantity: Quantity per applicable line item quantity + preferred_necessary_quantity: Number of items needed for a discounted item solidus_friendly_promotions/rules/first_order: description: Must be the customer's first order solidus_friendly_promotions/rules/first_repeat_purchase_since: diff --git a/promotions/spec/models/promotion/integration_spec.rb b/promotions/spec/models/promotion/integration_spec.rb index 0d60a490..7d952744 100644 --- a/promotions/spec/models/promotion/integration_spec.rb +++ b/promotions/spec/models/promotion/integration_spec.rb @@ -38,6 +38,7 @@ let(:goodie) { create(:variant, price: 4) } let(:action) { SolidusFriendlyPromotions::Actions::CreateDiscountedItem.new(preferred_variant_id: goodie.id, calculator: hundred_percent) } let(:hundred_percent) { SolidusFriendlyPromotions::Calculators::Percent.new(preferred_percent: 100) } + let(:rule) { SolidusFriendlyPromotions::Rules::Product.new(products: [shirt], preferred_line_item_applicable: true) } it "creates a new discounted line item" do expect(order.adjustments).to be_empty @@ -48,6 +49,21 @@ expect(order.line_items.flat_map(&:adjustments).length).to eq(1) end + context "when a second base item is added" do + before do + order.contents.add(shirt.master) + end + + it "creates a new discounted line item" do + expect(order.adjustments).to be_empty + expect(order.line_items.count).to eq(3) + expect(order.total).to eq(59.97) + expect(order.item_total).to eq(67.97) + expect(order.item_total_before_tax).to eq(59.97) + expect(order.line_items.flat_map(&:adjustments).length).to eq(1) + end + end + context "when the goodie becomes unavailable" do before do order.contents.remove(shirt.master)