From 2bef8d580c99b3d340d3cb6162105ef759d08516 Mon Sep 17 00:00:00 2001 From: Thomas von Deyen Date: Tue, 4 Jun 2024 09:40:58 +0200 Subject: [PATCH 001/834] Fix labeler.yml syntax There was a typo in the update --- .github/labeler.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/labeler.yml b/.github/labeler.yml index 51dfee3c9e0..e7f0f9028d2 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -1,38 +1,38 @@ "changelog:solidus_core": - any: - - changed_files: + - changed-files: - any-glob-to-any-file: - "core/**/*" "changelog:solidus_backend": - any: - - changed_files: + - changed-files: - any-glob-to-any-file: - "backend/**/*" "changelog:solidus_api": - any: - - changed_files: + - changed-files: - any-glob-to-any-file: - "api/**/*" "changelog:solidus_admin": - any: - - changed_files: + - changed-files: - any-glob-to-any-file: - "admin/**/*" "changelog:solidus_sample": - any: - - changed_files: + - changed-files: - any-glob-to-any-file: - "sample/**/*" "changelog:solidus": - any: - - changed_files: + - changed-files: - any-glob-to-any-file: - "lib/**/*" - "README.md" - "solidus.gemspec" "changelog:repository": - all: - - changed_files: + - changed-files: - any-glob-to-any-file: - "**/*" - all-globs-to-all-files: From f02f81216bf037e9fd6a280bfb3093cc5f5227e8 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 29 May 2024 15:17:23 +0200 Subject: [PATCH 002/834] Order spec: Stub promotion system This changes the order spec in core to rely on the stubbed null promotion system. The specs for the legacy promotion system have been moved to the legacy_promotions gem. --- core/spec/models/spree/order_spec.rb | 30 +++++++------------ .../spec/models/spree/order_spec.rb | 30 +++++++++++++++++++ 2 files changed, 41 insertions(+), 19 deletions(-) diff --git a/core/spec/models/spree/order_spec.rb b/core/spec/models/spree/order_spec.rb index a53ece6279f..f40aa63b405 100644 --- a/core/spec/models/spree/order_spec.rb +++ b/core/spec/models/spree/order_spec.rb @@ -2074,32 +2074,24 @@ def validate(line_item) end describe "#can_add_coupon?" do - let(:order) { Spree::Order.new(state: state) } + let(:order) { Spree::Order.new } subject { order.can_add_coupon? } - context "when the order is in the cart state" do - let(:state) { "cart" } - - it { is_expected.to eq(true) } - end - - context "when the order is completed" do - let(:state) { "complete" } - - it { is_expected.to eq(false) } - end - - context "when the order is returned" do - let(:state) { "returned" } + context "when the configured coupon handler allows adding coupons" do + before do + expect_any_instance_of(Spree::Config.promotions.coupon_code_handler_class).to receive(:can_apply?).and_return(true) + end - it { is_expected.to eq(false) } + it { is_expected.to be true } end - context "when the order is awaiting returns" do - let(:state) { "returned" } + context "when the configured coupon handler does not allow adding coupons" do + before do + expect_any_instance_of(Spree::Config.promotions.coupon_code_handler_class).to receive(:can_apply?).and_return(false) + end - it { is_expected.to eq(false) } + it { is_expected.to be false } end end diff --git a/legacy_promotions/spec/models/spree/order_spec.rb b/legacy_promotions/spec/models/spree/order_spec.rb index 2b24c6a0889..75f22959c97 100644 --- a/legacy_promotions/spec/models/spree/order_spec.rb +++ b/legacy_promotions/spec/models/spree/order_spec.rb @@ -76,4 +76,34 @@ expect { subject }.to change { Spree::OrderPromotion.count }.from(1).to(0) end end + + describe "#can_add_coupon?" do + let(:order) { Spree::Order.new(state: state) } + + subject { order.can_add_coupon? } + + context "when the order is in the cart state" do + let(:state) { "cart" } + + it { is_expected.to eq(true) } + end + + context "when the order is completed" do + let(:state) { "complete" } + + it { is_expected.to eq(false) } + end + + context "when the order is returned" do + let(:state) { "returned" } + + it { is_expected.to eq(false) } + end + + context "when the order is awaiting returns" do + let(:state) { "returned" } + + it { is_expected.to eq(false) } + end + end end From 214989b161724807f56b4c7b8d8f616a774d0bb2 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 16 May 2024 14:58:39 +0200 Subject: [PATCH 003/834] Move order coupon codes spec to legacy_promotions --- .../backend/order_coupon_codes_spec.rb | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) rename backend/spec/features/admin/orders/adjustments_promotions_spec.rb => legacy_promotions/spec/features/backend/order_coupon_codes_spec.rb (81%) diff --git a/backend/spec/features/admin/orders/adjustments_promotions_spec.rb b/legacy_promotions/spec/features/backend/order_coupon_codes_spec.rb similarity index 81% rename from backend/spec/features/admin/orders/adjustments_promotions_spec.rb rename to legacy_promotions/spec/features/backend/order_coupon_codes_spec.rb index 7b025901440..f65d6a651c2 100644 --- a/backend/spec/features/admin/orders/adjustments_promotions_spec.rb +++ b/legacy_promotions/spec/features/backend/order_coupon_codes_spec.rb @@ -1,18 +1,18 @@ # frozen_string_literal: true -require 'spec_helper' +require "rails_helper" -describe "Adjustments Promotions", type: :feature do +RSpec.describe "Adjustments Promotions", type: :feature do stub_authorization! before(:each) do create(:promotion_with_item_adjustment, - name: "$10 off", - path: 'test', - code: "10_off", - starts_at: 1.day.ago, - expires_at: 1.day.from_now, - adjustment_rate: 10) + name: "$10 off", + path: "test", + code: "10_off", + starts_at: 1.day.ago, + expires_at: 1.day.from_now, + adjustment_rate: 10) order = create(:order_with_totals) line_item = order.line_items.first @@ -44,7 +44,7 @@ it "should show an error message", js: true do fill_in "coupon_code", with: "10_off" click_button "Add Coupon Code" - expect(page).to have_content('-$10.00') + expect(page).to have_content("-$10.00") fill_in "coupon_code", with: "10_off" click_button "Add Coupon Code" From 34878b1ad67a0e18af72b636a7286e6015644679 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 26 Jan 2024 23:54:17 +0100 Subject: [PATCH 004/834] Move order total spec The one example here relies on the legacy promo system. --- .../spec/models/spree/order/totals_spec.rb | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {core => legacy_promotions}/spec/models/spree/order/totals_spec.rb (100%) diff --git a/core/spec/models/spree/order/totals_spec.rb b/legacy_promotions/spec/models/spree/order/totals_spec.rb similarity index 100% rename from core/spec/models/spree/order/totals_spec.rb rename to legacy_promotions/spec/models/spree/order/totals_spec.rb From eb0acf85f0baeb9fd1665a6b262a1c3132d32a67 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Mon, 3 Jun 2024 12:17:53 +0200 Subject: [PATCH 005/834] Promotion Action specs: Stop testing for concrete lists of calculators With the upcoming change to using the Null promotion system, these specs will start failing, because they really test another classes implementation (that of the legacy promotions configuration object). Just test for equality instead. --- .../spree/promotion/actions/create_adjustment_spec.rb | 8 +------- .../promotion/actions/create_item_adjustments_spec.rb | 8 +------- .../promotion/actions/create_quantity_adjustments_spec.rb | 6 ++---- .../models/spree/promotion/actions/free_shipping_spec.rb | 2 +- 4 files changed, 5 insertions(+), 19 deletions(-) diff --git a/core/spec/models/spree/promotion/actions/create_adjustment_spec.rb b/core/spec/models/spree/promotion/actions/create_adjustment_spec.rb index 1c4df937cce..c1310d7a941 100644 --- a/core/spec/models/spree/promotion/actions/create_adjustment_spec.rb +++ b/core/spec/models/spree/promotion/actions/create_adjustment_spec.rb @@ -123,13 +123,7 @@ subject { action.available_calculators } it { - is_expected.to contain_exactly( - Spree::Calculator::FlatPercentItemTotal, - Spree::Calculator::FlatRate, - Spree::Calculator::FlexiRate, - Spree::Calculator::TieredPercent, - Spree::Calculator::TieredFlatRate - ) + is_expected.to eq(Spree::Config.promotions.calculators[described_class.to_s]) } end end diff --git a/core/spec/models/spree/promotion/actions/create_item_adjustments_spec.rb b/core/spec/models/spree/promotion/actions/create_item_adjustments_spec.rb index d5bbde07c1c..762d4ea03d4 100644 --- a/core/spec/models/spree/promotion/actions/create_item_adjustments_spec.rb +++ b/core/spec/models/spree/promotion/actions/create_item_adjustments_spec.rb @@ -194,13 +194,7 @@ module Spree subject { action.available_calculators } it { - is_expected.to contain_exactly( - Spree::Calculator::DistributedAmount, - Spree::Calculator::FlatRate, - Spree::Calculator::FlexiRate, - Spree::Calculator::PercentOnLineItem, - Spree::Calculator::TieredPercent - ) + is_expected.to eq(Spree::Config.promotions.calculators[described_class.to_s]) } end end diff --git a/core/spec/models/spree/promotion/actions/create_quantity_adjustments_spec.rb b/core/spec/models/spree/promotion/actions/create_quantity_adjustments_spec.rb index d4af0e56445..b442d0fde7e 100644 --- a/core/spec/models/spree/promotion/actions/create_quantity_adjustments_spec.rb +++ b/core/spec/models/spree/promotion/actions/create_quantity_adjustments_spec.rb @@ -297,13 +297,11 @@ module Spree::Promotion::Actions describe "#available_calculators" do let(:action) { described_class.new } + subject { action.available_calculators } it { - is_expected.to contain_exactly( - Spree::Calculator::PercentOnLineItem, - Spree::Calculator::FlatRate - ) + is_expected.to eq(Spree::Config.promotions.calculators[described_class.to_s]) } end end diff --git a/core/spec/models/spree/promotion/actions/free_shipping_spec.rb b/core/spec/models/spree/promotion/actions/free_shipping_spec.rb index 5a8ef0f4e66..ff7891941e0 100644 --- a/core/spec/models/spree/promotion/actions/free_shipping_spec.rb +++ b/core/spec/models/spree/promotion/actions/free_shipping_spec.rb @@ -117,7 +117,7 @@ subject { action.available_calculators } it { - is_expected.to be_empty + is_expected.to eq(Spree::Config.promotions.calculators[described_class.to_s]) } end end From c2b9cea495900d5894281e2b927050cec922cd28 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Mon, 3 Jun 2024 12:25:06 +0200 Subject: [PATCH 006/834] Move non-unit specs from CreateQuantityAdjustments spec These specs test the entire promotion system, not just the `CreateQuantityAdjustments` class. Moving to the legacy_promotions gem. --- .../create_quantity_adjustments_spec.rb | 60 ------------- .../spree/promotion_integration_spec.rb | 85 +++++++++++++++++++ 2 files changed, 85 insertions(+), 60 deletions(-) create mode 100644 legacy_promotions/spec/models/spree/promotion_integration_spec.rb diff --git a/core/spec/models/spree/promotion/actions/create_quantity_adjustments_spec.rb b/core/spec/models/spree/promotion/actions/create_quantity_adjustments_spec.rb index b442d0fde7e..e19f7c4f78b 100644 --- a/core/spec/models/spree/promotion/actions/create_quantity_adjustments_spec.rb +++ b/core/spec/models/spree/promotion/actions/create_quantity_adjustments_spec.rb @@ -216,66 +216,6 @@ module Spree::Promotion::Actions end end - # Regression test for https://github.com/solidusio/solidus/pull/1591 - context "with unsaved line_item changes" do - let(:calculator) { FactoryBot.create :flat_rate_calculator } - let(:line_item) { order.line_items.first } - - before do - order.line_items.first.promo_total = -11 - action.compute_amount(line_item) - end - - it "doesn't reload the line_items association" do - expect(order.line_items.first.promo_total).to eq(-11) - end - end - - # Regression test for https://github.com/solidusio/solidus/pull/1591 - context "applied to the order" do - let(:calculator) { FactoryBot.create :flat_rate_calculator } - - before do - action.perform(order: order, promotion: promotion) - order.recalculate - end - - it 'updates the order totals' do - expect(order).to have_attributes( - total: 100, - adjustment_total: -10 - ) - end - - context "after updating item quantity" do - before do - order.line_items.first.update!(quantity: 2, price: 30) - order.recalculate - end - - it 'updates the order totals' do - expect(order).to have_attributes( - total: 140, - adjustment_total: -20 - ) - end - end - - context "after updating promotion amount" do - before do - calculator.update!(preferred_amount: 5) - order.recalculate - end - - it 'updates the order totals' do - expect(order).to have_attributes( - total: 105, - adjustment_total: -5 - ) - end - end - end - describe Spree::Promotion::Actions::CreateQuantityAdjustments::PartialLineItem do let!(:item) { FactoryBot.create :line_item, order: order, quantity: quantity, price: 10 } let(:quantity) { 5 } diff --git a/legacy_promotions/spec/models/spree/promotion_integration_spec.rb b/legacy_promotions/spec/models/spree/promotion_integration_spec.rb new file mode 100644 index 00000000000..5d76d2bcd25 --- /dev/null +++ b/legacy_promotions/spec/models/spree/promotion_integration_spec.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe "Legacy promotion system" do + describe "promotions with a quantity adjustment" do + let(:action) { Spree::Promotion::Actions::CreateQuantityAdjustments.create!(calculator: calculator, promotion: promotion) } + + let(:order) do + create( + :order_with_line_items, + line_items_attributes: line_items_attributes + ) + end + + let(:line_items_attributes) do + [ + { price: 10, quantity: quantity } + ] + end + + let(:quantity) { 1 } + let(:promotion) { FactoryBot.create :promotion } + + # Regression test for https://github.com/solidusio/solidus/pull/1591 + context "with unsaved line_item changes" do + let(:calculator) { FactoryBot.create :flat_rate_calculator } + let(:line_item) { order.line_items.first } + + before do + order.line_items.first.promo_total = -11 + action.compute_amount(line_item) + end + + it "doesn't reload the line_items association" do + expect(order.line_items.first.promo_total).to eq(-11) + end + end + + # Regression test for https://github.com/solidusio/solidus/pull/1591 + context "applied to the order" do + let(:calculator) { FactoryBot.create :flat_rate_calculator } + + before do + action.perform(order: order, promotion: promotion) + order.recalculate + end + + it "updates the order totals" do + expect(order).to have_attributes( + total: 100, + adjustment_total: -10 + ) + end + + context "after updating item quantity" do + before do + order.line_items.first.update!(quantity: 2, price: 30) + order.recalculate + end + + it "updates the order totals" do + expect(order).to have_attributes( + total: 140, + adjustment_total: -20 + ) + end + end + + context "after updating promotion amount" do + before do + calculator.update!(preferred_amount: 5) + order.recalculate + end + + it "updates the order totals" do + expect(order).to have_attributes( + total: 105, + adjustment_total: -5 + ) + end + end + end + end +end From f749b7876610f2f9739aedc7292ecb851da1e0d7 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Mon, 3 Jun 2024 12:29:45 +0200 Subject: [PATCH 007/834] Move integration specs from distributed amount unit spec These specs test the entire promotion system, not just this unit. --- .../calculator/distributed_amount_spec.rb | 45 ------------------- .../spree/promotion_integration_spec.rb | 45 +++++++++++++++++++ 2 files changed, 45 insertions(+), 45 deletions(-) diff --git a/core/spec/models/spree/calculator/distributed_amount_spec.rb b/core/spec/models/spree/calculator/distributed_amount_spec.rb index ffba048490c..578ddfa514a 100644 --- a/core/spec/models/spree/calculator/distributed_amount_spec.rb +++ b/core/spec/models/spree/calculator/distributed_amount_spec.rb @@ -4,51 +4,6 @@ require 'spree/testing_support/shared_examples/calculator' RSpec.describe Spree::Calculator::DistributedAmount, type: :model do - context 'applied to an order' do - let(:calculator) { Spree::Calculator::DistributedAmount.new } - let(:promotion) { - create :promotion, - name: '15 spread' - } - let(:order) { - create :completed_order_with_promotion, - promotion: promotion, - line_items_attributes: [{ price: 20 }, { price: 30 }, { price: 100 }] - } - - before do - calculator.preferred_amount = 15 - Spree::Promotion::Actions::CreateItemAdjustments.create!(calculator: calculator, promotion: promotion) - order.recalculate - end - - it 'correctly distributes the entire discount' do - expect(order.promo_total).to eq(-15) - expect(order.line_items.map(&:adjustment_total)).to eq([-2, -3, -10]) - end - - context 'with product promotion rule' do - let(:first_product) { order.line_items.first.product } - - before do - rule = Spree::Promotion::Rules::Product.create!( - promotion: promotion, - product_promotion_rules: [ - Spree::ProductPromotionRule.new(product: first_product), - ], - ) - promotion.rules << rule - promotion.save! - order.recalculate - end - - it 'still distributes the entire discount' do - expect(order.promo_total).to eq(-15) - expect(order.line_items.map(&:adjustment_total)).to eq([-15, 0, 0]) - end - end - end - describe "#compute_line_item" do subject { calculator.compute_line_item(order.line_items.first) } diff --git a/legacy_promotions/spec/models/spree/promotion_integration_spec.rb b/legacy_promotions/spec/models/spree/promotion_integration_spec.rb index 5d76d2bcd25..690ca255247 100644 --- a/legacy_promotions/spec/models/spree/promotion_integration_spec.rb +++ b/legacy_promotions/spec/models/spree/promotion_integration_spec.rb @@ -82,4 +82,49 @@ end end end + + describe "distributing amount across line items" do + let(:calculator) { Spree::Calculator::DistributedAmount.new } + let(:promotion) { + create :promotion, + name: '15 spread' + } + let(:order) { + create :completed_order_with_promotion, + promotion: promotion, + line_items_attributes: [{ price: 20 }, { price: 30 }, { price: 100 }] + } + + before do + calculator.preferred_amount = 15 + Spree::Promotion::Actions::CreateItemAdjustments.create!(calculator: calculator, promotion: promotion) + order.recalculate + end + + it 'correctly distributes the entire discount' do + expect(order.promo_total).to eq(-15) + expect(order.line_items.map(&:adjustment_total)).to eq([-2, -3, -10]) + end + + context 'with product promotion rule' do + let(:first_product) { order.line_items.first.product } + + before do + rule = Spree::Promotion::Rules::Product.create!( + promotion: promotion, + product_promotion_rules: [ + Spree::ProductPromotionRule.new(product: first_product), + ], + ) + promotion.rules << rule + promotion.save! + order.recalculate + end + + it 'still distributes the entire discount' do + expect(order.promo_total).to eq(-15) + expect(order.line_items.map(&:adjustment_total)).to eq([-15, 0, 0]) + end + end + end end From 3ac3f80a395c6913fe2dd7e5e884ca472cb45fce Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 26 Jan 2024 17:59:06 +0100 Subject: [PATCH 008/834] Move promo adjustment spec to legacy_promotions This one spec should not live here. --- core/spec/models/spree/line_item_spec.rb | 6 ++---- .../spec/models/spree/line_item_spec.rb | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 4 deletions(-) create mode 100644 legacy_promotions/spec/models/spree/line_item_spec.rb diff --git a/core/spec/models/spree/line_item_spec.rb b/core/spec/models/spree/line_item_spec.rb index 55c8b5e6d90..67c0c41add3 100644 --- a/core/spec/models/spree/line_item_spec.rb +++ b/core/spec/models/spree/line_item_spec.rb @@ -70,10 +70,8 @@ line_item.update!(price: 10, quantity: 2) end let!(:admin_adjustment) { create(:adjustment, adjustable: line_item, order: line_item.order, amount: -1, source: nil) } - let!(:promo_adjustment) { create(:adjustment, adjustable: line_item, order: line_item.order, amount: -2, source: promo_action) } - let!(:ineligible_promo_adjustment) { create(:adjustment, eligible: false, adjustable: line_item, order: line_item.order, amount: -4, source: promo_action) } - let(:promo_action) { promo.actions[0] } - let(:promo) { create(:promotion, :with_line_item_adjustment) } + let!(:other_adjustment) { create(:adjustment, adjustable: line_item, order: line_item.order, amount: -2, source: nil) } + let!(:ineligible_adjustment) { create(:adjustment, eligible: false, adjustable: line_item, order: line_item.order, amount: -4, source: nil) } it 'returns the amount minus any adjustments' do expect(line_item.total_before_tax).to eq(20 - 1 - 2) diff --git a/legacy_promotions/spec/models/spree/line_item_spec.rb b/legacy_promotions/spec/models/spree/line_item_spec.rb new file mode 100644 index 00000000000..6bd7f0ba217 --- /dev/null +++ b/legacy_promotions/spec/models/spree/line_item_spec.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe Spree::LineItem, type: :model do + describe "#total_before_tax" do + let(:line_item) { create(:line_item, price: 10, quantity: 2) } + + let!(:admin_adjustment) { create(:adjustment, adjustable: line_item, order: line_item.order, amount: -1, source: nil) } + let!(:promo_adjustment) { create(:adjustment, adjustable: line_item, order: line_item.order, amount: -2, source: promo_action) } + let!(:ineligible_promo_adjustment) { create(:adjustment, eligible: false, adjustable: line_item, order: line_item.order, amount: -4, source: promo_action) } + let(:promo_action) { promo.actions[0] } + let(:promo) { create(:promotion, :with_line_item_adjustment) } + + it "returns the amount minus any adjustments" do + expect(line_item.total_before_tax).to eq(20 - 1 - 2) + end + end +end From 68891a9126081ee46a94f5087d3457ddcfea50b8 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 26 Jan 2024 23:39:26 +0100 Subject: [PATCH 009/834] Move promotion-related order updater specs --- core/spec/models/spree/order_updater_spec.rb | 46 +----------- .../spec/models/spree/order_updater_spec.rb | 73 +++++++++++++++++++ 2 files changed, 74 insertions(+), 45 deletions(-) create mode 100644 legacy_promotions/spec/models/spree/order_updater_spec.rb diff --git a/core/spec/models/spree/order_updater_spec.rb b/core/spec/models/spree/order_updater_spec.rb index 9fc97c4b4fb..0bf623256d4 100644 --- a/core/spec/models/spree/order_updater_spec.rb +++ b/core/spec/models/spree/order_updater_spec.rb @@ -47,30 +47,6 @@ module Spree end end - context 'with order promotion followed by line item addition' do - let(:promotion) { Spree::Promotion.create!(name: "10% off") } - let(:calculator) { Calculator::FlatPercentItemTotal.new(preferred_flat_percent: 10) } - - let(:promotion_action) do - Promotion::Actions::CreateAdjustment.create!({ - calculator: calculator, - promotion: promotion - }) - end - - before do - updater.recalculate - create(:adjustment, source: promotion_action, adjustable: order, order: order) - create(:line_item, order: order, price: 10) # in addition to the two already created - order.line_items.reload # need to pick up the extra line item - updater.recalculate - end - - it "updates promotion total" do - expect(order.promo_total).to eq(-3) - end - end - it "update order adjustments" do create(:adjustment, adjustable: order, order: order, source: nil, amount: 10) @@ -86,7 +62,7 @@ module Spree describe 'promotion recalculation' do it "calls the Promotion Adjustments Recalculator" do adjuster = double(:call) - expect(Spree::Promotion::OrderAdjustmentsRecalculator).to receive(:new).and_return(adjuster) + expect(Spree::Config.promotion_adjuster_class).to receive(:new).and_return(adjuster) expect(adjuster).to receive(:call) order.recalculate end @@ -358,26 +334,6 @@ module Spree end end - describe 'updating in-memory items' do - let(:order) do - create(:order_with_line_items, line_items_count: 1, line_items_price: 10) - end - let(:line_item) { order.line_items.first } - let(:promotion) { create(:promotion, :with_line_item_adjustment, adjustment_rate: 1) } - - it 'updates in-memory items' do - promotion.activate(order: order) - - expect(line_item.promo_total).to eq(0) - expect(order.promo_total).to eq(0) - - order.recalculate - - expect(line_item.promo_total).to eq(-1) - expect(order.promo_total).to eq(-1) - end - end - context "with item with no adjustment and incorrect totals" do let!(:line_item) { create(:line_item, order: order, price: 10) } diff --git a/legacy_promotions/spec/models/spree/order_updater_spec.rb b/legacy_promotions/spec/models/spree/order_updater_spec.rb new file mode 100644 index 00000000000..d68b9dea0a7 --- /dev/null +++ b/legacy_promotions/spec/models/spree/order_updater_spec.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +require 'rails_helper' + +module Spree + RSpec.describe OrderUpdater, type: :model do + let!(:store) { create :store } + let(:order) { Spree::Order.create } + let(:updater) { Spree::OrderUpdater.new(order) } + + context "order totals" do + before do + 2.times do + create(:line_item, order: order, price: 10) + end + end + + context 'with order promotion followed by line item addition' do + let(:promotion) { Spree::Promotion.create!(name: "10% off") } + let(:calculator) { Calculator::FlatPercentItemTotal.new(preferred_flat_percent: 10) } + + let(:promotion_action) do + Promotion::Actions::CreateAdjustment.create!({ + calculator: calculator, + promotion: promotion + }) + end + + before do + updater.recalculate + create(:adjustment, source: promotion_action, adjustable: order, order: order) + create(:line_item, order: order, price: 10) # in addition to the two already created + order.line_items.reload # need to pick up the extra line item + updater.recalculate + end + + it "updates promotion total" do + expect(order.promo_total).to eq(-3) + end + end + + it "update order adjustments" do + create(:adjustment, adjustable: order, order: order, source: nil, amount: 10) + + expect { + updater.recalculate + }.to change { + order.adjustment_total + }.from(0).to(10) + end + end + + describe 'updating in-memory items' do + let(:order) do + create(:order_with_line_items, line_items_count: 1, line_items_price: 10) + end + let(:line_item) { order.line_items.first } + let(:promotion) { create(:promotion, :with_line_item_adjustment, adjustment_rate: 1) } + + it 'updates in-memory items' do + promotion.activate(order: order) + + expect(line_item.promo_total).to eq(0) + expect(order.promo_total).to eq(0) + + order.recalculate + + expect(line_item.promo_total).to eq(-1) + expect(order.promo_total).to eq(-1) + end + end + end +end From e158a214bf1131d551440b5bc13280e9bd1b5c36 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Mon, 3 Jun 2024 12:35:49 +0200 Subject: [PATCH 010/834] Promotion code batch job spec: Use configuration object This change lets this spec pass even if the promotion configuration object changes. It currently prints deprecation warnings, but it will soon be moved over to the legacy_promotions gem. --- core/spec/jobs/promotion_code_batch_job_spec.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/core/spec/jobs/promotion_code_batch_job_spec.rb b/core/spec/jobs/promotion_code_batch_job_spec.rb index 627dc33ea03..80f0727751b 100644 --- a/core/spec/jobs/promotion_code_batch_job_spec.rb +++ b/core/spec/jobs/promotion_code_batch_job_spec.rb @@ -13,7 +13,7 @@ end context "with a successful build" do before do - allow(Spree::PromotionCodeBatchMailer) + allow(Spree::Config.promotions.promotion_code_batch_mailer_class) .to receive(:promotion_code_batch_finished) .and_call_original end @@ -50,7 +50,7 @@ def codes context "with an email address" do it "sends an email" do subject.perform(promotion_code_batch) - expect(Spree::PromotionCodeBatchMailer) + expect(Spree::Config.promotions.promotion_code_batch_mailer_class) .to have_received(:promotion_code_batch_finished) end end @@ -58,7 +58,7 @@ def codes let(:email) { nil } it "sends an email" do subject.perform(promotion_code_batch) - expect(Spree::PromotionCodeBatchMailer) + expect(Spree::Config.promotions.promotion_code_batch_mailer_class) .to_not have_received(:promotion_code_batch_finished) end end @@ -70,7 +70,7 @@ def codes .to receive(:build_promotion_codes) .and_raise("Error") - allow(Spree::PromotionCodeBatchMailer) + allow(Spree::Config.promotions.promotion_code_batch_mailer_class) .to receive(:promotion_code_batch_errored) .and_call_original @@ -80,7 +80,7 @@ def codes context "with an email address" do it "sends an email" do - expect(Spree::PromotionCodeBatchMailer) + expect(Spree::Config.promotions.promotion_code_batch_mailer_class) .to have_received(:promotion_code_batch_errored) end end @@ -88,7 +88,7 @@ def codes context "with no email address" do let(:email) { nil } it "sends an email" do - expect(Spree::PromotionCodeBatchMailer) + expect(Spree::Config.promotions.promotion_code_batch_mailer_class) .to_not have_received(:promotion_code_batch_errored) end end From c53947d3681fa73072f47bccad0bdd244e9ad311 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Mon, 3 Jun 2024 12:39:21 +0200 Subject: [PATCH 011/834] Move integration spec from promo code spec to legacy_promotions These specs test not the promotion code model in isolation, but the entire promotion system's functioning. Moving to solidus_legacy_promotions. --- core/spec/models/spree/promotion_code_spec.rb | 54 ------------------- .../spree/promotion_integration_spec.rb | 54 +++++++++++++++++++ 2 files changed, 54 insertions(+), 54 deletions(-) diff --git a/core/spec/models/spree/promotion_code_spec.rb b/core/spec/models/spree/promotion_code_spec.rb index d2f6e21d79b..c04e3ae115c 100644 --- a/core/spec/models/spree/promotion_code_spec.rb +++ b/core/spec/models/spree/promotion_code_spec.rb @@ -187,60 +187,6 @@ end end - describe "completing multiple orders with the same code", slow: true do - let(:promotion) do - FactoryBot.create( - :promotion, - :with_order_adjustment, - code: "discount", - per_code_usage_limit: 1, - weighted_order_adjustment_amount: 10 - ) - end - let(:code) { promotion.codes.first } - let(:order) do - FactoryBot.create(:order_with_line_items, line_items_price: 40, shipment_cost: 0).tap do |order| - FactoryBot.create(:payment, amount: 30, order: order) - promotion.activate(order: order, promotion_code: code) - end - end - let(:promo_adjustment) { order.adjustments.promotion.first } - before do - order.next! until order.can_complete? - - FactoryBot.create(:order_with_line_items, line_items_price: 40, shipment_cost: 0).tap do |order| - FactoryBot.create(:payment, amount: 30, order: order) - promotion.activate(order: order, promotion_code: code) - order.next! until order.can_complete? - order.complete! - end - end - - it "makes the promotion ineligible" do - expect{ - order.complete - }.to change{ promo_adjustment.reload.eligible }.to(false) - end - - it "adjusts the promo_total" do - expect{ - order.complete - }.to change(order, :promo_total).by(10) - end - - it "increases the total to remove the promo" do - expect{ - order.complete - }.to change(order, :total).from(30).to(40) - end - - it "resets the state of the order" do - expect{ - order.complete - }.to change{ order.reload.state }.from("confirm").to("address") - end - end - it "cannot create promotion code on apply automatically promotion" do promotion = create(:promotion, apply_automatically: true) expect { diff --git a/legacy_promotions/spec/models/spree/promotion_integration_spec.rb b/legacy_promotions/spec/models/spree/promotion_integration_spec.rb index 690ca255247..90ad8f194da 100644 --- a/legacy_promotions/spec/models/spree/promotion_integration_spec.rb +++ b/legacy_promotions/spec/models/spree/promotion_integration_spec.rb @@ -127,4 +127,58 @@ end end end + + describe "completing multiple orders with the same code", slow: true do + let(:promotion) do + FactoryBot.create( + :promotion, + :with_order_adjustment, + code: "discount", + per_code_usage_limit: 1, + weighted_order_adjustment_amount: 10 + ) + end + let(:code) { promotion.codes.first } + let(:order) do + FactoryBot.create(:order_with_line_items, line_items_price: 40, shipment_cost: 0).tap do |order| + FactoryBot.create(:payment, amount: 30, order: order) + promotion.activate(order: order, promotion_code: code) + end + end + let(:promo_adjustment) { order.adjustments.promotion.first } + before do + order.next! until order.can_complete? + + FactoryBot.create(:order_with_line_items, line_items_price: 40, shipment_cost: 0).tap do |order| + FactoryBot.create(:payment, amount: 30, order: order) + promotion.activate(order: order, promotion_code: code) + order.next! until order.can_complete? + order.complete! + end + end + + it "makes the promotion ineligible" do + expect{ + order.complete + }.to change{ promo_adjustment.reload.eligible }.to(false) + end + + it "adjusts the promo_total" do + expect{ + order.complete + }.to change(order, :promo_total).by(10) + end + + it "increases the total to remove the promo" do + expect{ + order.complete + }.to change(order, :total).from(30).to(40) + end + + it "resets the state of the order" do + expect{ + order.complete + }.to change{ order.reload.state }.from("confirm").to("address") + end + end end From 3350f632280d13a5631c421755c8c9cff425a739 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Mon, 3 Jun 2024 12:52:42 +0200 Subject: [PATCH 012/834] Promotion Handler shipping spec: Stub promo configuration This promotion handler only handles promotions with the free shipping action, and that is configured in the legacy promotion configuration. Stub it, so that tests pass even with a Null promotion configuration configured. --- core/spec/models/spree/promotion_handler/shipping_spec.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/spec/models/spree/promotion_handler/shipping_spec.rb b/core/spec/models/spree/promotion_handler/shipping_spec.rb index 7f096b2c139..17bce06b4c8 100644 --- a/core/spec/models/spree/promotion_handler/shipping_spec.rb +++ b/core/spec/models/spree/promotion_handler/shipping_spec.rb @@ -12,6 +12,10 @@ module PromotionHandler subject { Spree::PromotionHandler::Shipping.new(order) } + before do + allow(Spree::Config.promotions).to receive(:shipping_actions) { ['Spree::Promotion::Actions::FreeShipping'] } + end + context 'with apply_automatically' do let!(:promotion) { create(:promotion, apply_automatically: true, promotion_actions: [action]) } From ad311f55665773c73ae03154dcddcb8d1a20a0da Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Mon, 3 Jun 2024 12:57:46 +0200 Subject: [PATCH 013/834] Move order_adjustments_recalculator_spec to solidus_legacy_promotions While this file looks like a unit test, it's entirely an integration test for the whole promotion system. Moving as-is. --- .../models/spree/promotion/order_adjustments_recalculator_spec.rb | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {core => legacy_promotions}/spec/models/spree/promotion/order_adjustments_recalculator_spec.rb (100%) diff --git a/core/spec/models/spree/promotion/order_adjustments_recalculator_spec.rb b/legacy_promotions/spec/models/spree/promotion/order_adjustments_recalculator_spec.rb similarity index 100% rename from core/spec/models/spree/promotion/order_adjustments_recalculator_spec.rb rename to legacy_promotions/spec/models/spree/promotion/order_adjustments_recalculator_spec.rb From 7acf34bc3e8a2d3e09544c1ec642d736f7bf1fbb Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 30 May 2024 17:11:28 +0200 Subject: [PATCH 014/834] Rename PromotionConfiguration to LegacyPromotionConfiguration Earlier in the extraction process, I've defined a configuration object for the current promotion system and called it `Spree::Core::PromotionConfiguration`. However, it is much more aptly named `SolidusLegacyPromotions::Configuration`, as it will be moved into that gem shortly. This way it's also clear when we move to the `Spree::Core::NullPromotionConfiguration` what we are changing. --- .../configuration.rb | 115 +++++++++++++++++ core/lib/spree/app_configuration.rb | 8 +- core/lib/spree/core.rb | 2 +- .../lib/spree/core/promotion_configuration.rb | 117 ------------------ .../configuration_spec.rb} | 2 +- core/spec/lib/spree/app_configuration_spec.rb | 4 +- 6 files changed, 124 insertions(+), 124 deletions(-) create mode 100644 core/lib/solidus_legacy_promotions/configuration.rb delete mode 100644 core/lib/spree/core/promotion_configuration.rb rename core/spec/lib/{spree/core/promotion_configuration_spec.rb => solidus_legacy_promotions/configuration_spec.rb} (97%) diff --git a/core/lib/solidus_legacy_promotions/configuration.rb b/core/lib/solidus_legacy_promotions/configuration.rb new file mode 100644 index 00000000000..dafba26b1fc --- /dev/null +++ b/core/lib/solidus_legacy_promotions/configuration.rb @@ -0,0 +1,115 @@ +# frozen_string_literal: true + +module SolidusLegacyPromotions + class Configuration < Spree::Preferences::Configuration + include Spree::Core::EnvironmentExtension + + # @!attribute [rw] promotions_per_page + # @return [Integer] Promotions to show per-page in the admin (default: +15+) + preference :promotions_per_page, :integer, default: 15 + + # @!attribute [rw] promotion_attributes + # @return [Array] Attributes to be returned by the API for a promotion + preference :promotion_api_attributes, :array, default: [ + :id, + :name, + :description, + :expires_at, + :starts_at, + :type, + :usage_limit, + :advertise, + :path + ] + + # promotion_chooser_class allows extensions to provide their own PromotionChooser + class_name_attribute :promotion_chooser_class, default: 'Spree::PromotionChooser' + + # order_adjuster_class allows extensions to provide their own Order Adjuster + class_name_attribute :order_adjuster_class, default: 'Spree::Promotion::OrderAdjustmentsRecalculator' + + # promotion_finder_class allows extensions to provide their own Promotion Finder + class_name_attribute :promotion_finder_class, default: 'Spree::PromotionFinder' + + # Allows providing a different shipping promotion handler. + # @!attribute [rw] shipping_promotion_handler_class + # @see Spree::PromotionHandler::Shipping + # @return [Class] an object that conforms to the API of + # the standard shipping promotion handler class + # Spree::PromotionHandler::Coupon. + class_name_attribute :shipping_promotion_handler_class, default: 'Spree::PromotionHandler::Shipping' + + # Allows providing your own Mailer for promotion code batch mailer. + # + # @!attribute [rw] promotion_code_batch_mailer_class + # @return [ActionMailer::Base] an object that responds to "promotion_code_batch_finished", + # and "promotion_code_batch_errored" + # (e.g. an ActionMailer with a "promotion_code_batch_finished" method) with the same + # signature as Spree::PromotionCodeBatchMailer.promotion_code_batch_finished. + class_name_attribute :promotion_code_batch_mailer_class, default: 'Spree::PromotionCodeBatchMailer' + + # Allows providing a different coupon code handler. + # @!attribute [rw] coupon_code_handler_class + # @see Spree::PromotionHandler::Coupon + # @return [Class] an object that conforms to the API of + # the standard coupon code handler class + # Spree::PromotionHandler::Coupon. + class_name_attribute :coupon_code_handler_class, default: 'Spree::PromotionHandler::Coupon' + + # Allows providing a different promotion advertiser. + # @!attribute [rw] advertiser_class + # @see Spree::PromotionAdvertiser + # @return [Class] an object that conforms to the API of + # the standard promotion advertiser class + # Spree::PromotionAdvertiser. + class_name_attribute :advertiser_class, default: 'Spree::PromotionAdvertiser' + + add_class_set :rules, default: %w[ + Spree::Promotion::Rules::ItemTotal + Spree::Promotion::Rules::Product + Spree::Promotion::Rules::User + Spree::Promotion::Rules::FirstOrder + Spree::Promotion::Rules::UserLoggedIn + Spree::Promotion::Rules::OneUsePerUser + Spree::Promotion::Rules::Taxon + Spree::Promotion::Rules::MinimumQuantity + Spree::Promotion::Rules::NthOrder + Spree::Promotion::Rules::OptionValue + Spree::Promotion::Rules::FirstRepeatPurchaseSince + Spree::Promotion::Rules::UserRole + Spree::Promotion::Rules::Store + ] + + add_class_set :actions, default: %w[ + Spree::Promotion::Actions::CreateAdjustment + Spree::Promotion::Actions::CreateItemAdjustments + Spree::Promotion::Actions::CreateQuantityAdjustments + Spree::Promotion::Actions::FreeShipping + ] + + add_class_set :shipping_actions, default: %w[ + Spree::Promotion::Actions::FreeShipping + ] + + add_nested_class_set :calculators, default: { + "Spree::Promotion::Actions::CreateAdjustment" => %w[ + Spree::Calculator::FlatPercentItemTotal + Spree::Calculator::FlatRate + Spree::Calculator::FlexiRate + Spree::Calculator::TieredPercent + Spree::Calculator::TieredFlatRate + ], + "Spree::Promotion::Actions::CreateItemAdjustments" => %w[ + Spree::Calculator::DistributedAmount + Spree::Calculator::FlatRate + Spree::Calculator::FlexiRate + Spree::Calculator::PercentOnLineItem + Spree::Calculator::TieredPercent + ], + "Spree::Promotion::Actions::CreateQuantityAdjustments" => %w[ + Spree::Calculator::PercentOnLineItem + Spree::Calculator::FlatRate + ] + } + end +end diff --git a/core/lib/spree/app_configuration.rb b/core/lib/spree/app_configuration.rb index ab63a48f075..b10ddde992a 100644 --- a/core/lib/spree/app_configuration.rb +++ b/core/lib/spree/app_configuration.rb @@ -590,11 +590,13 @@ def stock # Allows providing your own promotion configuration instance # @!attribute [rw] promotions - # @return [Spree::Core::PromotionConfiguration] an object that conforms to the API of - # the standard promotion configuration class Spree::Core::PromotionConfiguration. + # @return [Spree::Core::NullPromotionConfiguration] an object that conforms to the API of + # the example promotion configuration class Spree::Core::NullPromotionConfiguration. + # Currently, this defaults to the legacy promotion configuration, until that system is fully extracted + # to the `solidus_legacy_promotions` gem. attr_writer :promotions def promotions - @promotions ||= Spree::Core::PromotionConfiguration.new + @promotions ||= SolidusLegacyPromotions::Configuration.new end class << self diff --git a/core/lib/spree/core.rb b/core/lib/spree/core.rb index 2a15cafb718..0b9667109ba 100644 --- a/core/lib/spree/core.rb +++ b/core/lib/spree/core.rb @@ -120,7 +120,7 @@ class GatewayError < RuntimeError; end require 'spree/core/role_configuration' require 'spree/core/state_machines' require 'spree/core/stock_configuration' -require 'spree/core/promotion_configuration' +require 'solidus_legacy_promotions/configuration' require 'spree/core/null_promotion_configuration' require 'spree/core/validators/email' require 'spree/permission_sets' diff --git a/core/lib/spree/core/promotion_configuration.rb b/core/lib/spree/core/promotion_configuration.rb deleted file mode 100644 index d86223b8ac0..00000000000 --- a/core/lib/spree/core/promotion_configuration.rb +++ /dev/null @@ -1,117 +0,0 @@ -# frozen_string_literal: true - -module Spree - module Core - class PromotionConfiguration < Spree::Preferences::Configuration - include Core::EnvironmentExtension - - # @!attribute [rw] promotions_per_page - # @return [Integer] Promotions to show per-page in the admin (default: +15+) - preference :promotions_per_page, :integer, default: 15 - - # @!attribute [rw] promotion_attributes - # @return [Array] Attributes to be returned by the API for a promotion - preference :promotion_api_attributes, :array, default: [ - :id, - :name, - :description, - :expires_at, - :starts_at, - :type, - :usage_limit, - :advertise, - :path - ] - - # promotion_chooser_class allows extensions to provide their own PromotionChooser - class_name_attribute :promotion_chooser_class, default: 'Spree::PromotionChooser' - - # order_adjuster_class allows extensions to provide their own Order Adjuster - class_name_attribute :order_adjuster_class, default: 'Spree::Promotion::OrderAdjustmentsRecalculator' - - # promotion_finder_class allows extensions to provide their own Promotion Finder - class_name_attribute :promotion_finder_class, default: 'Spree::PromotionFinder' - - # Allows providing a different shipping promotion handler. - # @!attribute [rw] shipping_promotion_handler_class - # @see Spree::PromotionHandler::Shipping - # @return [Class] an object that conforms to the API of - # the standard shipping promotion handler class - # Spree::PromotionHandler::Coupon. - class_name_attribute :shipping_promotion_handler_class, default: 'Spree::PromotionHandler::Shipping' - - # Allows providing your own Mailer for promotion code batch mailer. - # - # @!attribute [rw] promotion_code_batch_mailer_class - # @return [ActionMailer::Base] an object that responds to "promotion_code_batch_finished", - # and "promotion_code_batch_errored" - # (e.g. an ActionMailer with a "promotion_code_batch_finished" method) with the same - # signature as Spree::PromotionCodeBatchMailer.promotion_code_batch_finished. - class_name_attribute :promotion_code_batch_mailer_class, default: 'Spree::PromotionCodeBatchMailer' - - # Allows providing a different coupon code handler. - # @!attribute [rw] coupon_code_handler_class - # @see Spree::PromotionHandler::Coupon - # @return [Class] an object that conforms to the API of - # the standard coupon code handler class - # Spree::PromotionHandler::Coupon. - class_name_attribute :coupon_code_handler_class, default: 'Spree::PromotionHandler::Coupon' - - # Allows providing a different promotion advertiser. - # @!attribute [rw] advertiser_class - # @see Spree::PromotionAdvertiser - # @return [Class] an object that conforms to the API of - # the standard promotion advertiser class - # Spree::PromotionAdvertiser. - class_name_attribute :advertiser_class, default: 'Spree::PromotionAdvertiser' - - add_class_set :rules, default: %w[ - Spree::Promotion::Rules::ItemTotal - Spree::Promotion::Rules::Product - Spree::Promotion::Rules::User - Spree::Promotion::Rules::FirstOrder - Spree::Promotion::Rules::UserLoggedIn - Spree::Promotion::Rules::OneUsePerUser - Spree::Promotion::Rules::Taxon - Spree::Promotion::Rules::MinimumQuantity - Spree::Promotion::Rules::NthOrder - Spree::Promotion::Rules::OptionValue - Spree::Promotion::Rules::FirstRepeatPurchaseSince - Spree::Promotion::Rules::UserRole - Spree::Promotion::Rules::Store - ] - - add_class_set :actions, default: %w[ - Spree::Promotion::Actions::CreateAdjustment - Spree::Promotion::Actions::CreateItemAdjustments - Spree::Promotion::Actions::CreateQuantityAdjustments - Spree::Promotion::Actions::FreeShipping - ] - - add_class_set :shipping_actions, default: %w[ - Spree::Promotion::Actions::FreeShipping - ] - - add_nested_class_set :calculators, default: { - "Spree::Promotion::Actions::CreateAdjustment" => %w[ - Spree::Calculator::FlatPercentItemTotal - Spree::Calculator::FlatRate - Spree::Calculator::FlexiRate - Spree::Calculator::TieredPercent - Spree::Calculator::TieredFlatRate - ], - "Spree::Promotion::Actions::CreateItemAdjustments" => %w[ - Spree::Calculator::DistributedAmount - Spree::Calculator::FlatRate - Spree::Calculator::FlexiRate - Spree::Calculator::PercentOnLineItem - Spree::Calculator::TieredPercent - ], - "Spree::Promotion::Actions::CreateQuantityAdjustments" => %w[ - Spree::Calculator::PercentOnLineItem - Spree::Calculator::FlatRate - ] - } - end - end -end diff --git a/core/spec/lib/spree/core/promotion_configuration_spec.rb b/core/spec/lib/solidus_legacy_promotions/configuration_spec.rb similarity index 97% rename from core/spec/lib/spree/core/promotion_configuration_spec.rb rename to core/spec/lib/solidus_legacy_promotions/configuration_spec.rb index 19305ffdb38..0e5b5389bd5 100644 --- a/core/spec/lib/spree/core/promotion_configuration_spec.rb +++ b/core/spec/lib/solidus_legacy_promotions/configuration_spec.rb @@ -2,7 +2,7 @@ require "rails_helper" -RSpec.describe Spree::Core::PromotionConfiguration do +RSpec.describe SolidusLegacyPromotions::Configuration do subject(:config) { described_class.new } it "uses base searcher class by default" do diff --git a/core/spec/lib/spree/app_configuration_spec.rb b/core/spec/lib/spree/app_configuration_spec.rb index 01fa8f8a2c3..7ffc0e84d69 100644 --- a/core/spec/lib/spree/app_configuration_spec.rb +++ b/core/spec/lib/spree/app_configuration_spec.rb @@ -41,7 +41,7 @@ end it "uses core's promotion configuration class by default" do - expect(prefs.promotions).to be_a Spree::Core::PromotionConfiguration + expect(prefs.promotions).to be_a SolidusLegacyPromotions::Configuration end context "deprecated preferences" do @@ -113,7 +113,7 @@ describe '#promotions' do subject { prefs.promotions } - it { is_expected.to be_a Spree::Core::PromotionConfiguration } + it { is_expected.to be_a SolidusLegacyPromotions::Configuration } end describe '@default_country_iso_code' do From fd5d77ed63b83239c3757799a04c7da9bef21af7 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 16 May 2024 14:48:25 +0200 Subject: [PATCH 015/834] NullPromotionHandler: return self from #apply The existing promotion handlers are activated using either `#apply` or `#activate`. The return value `#activate` is usually not read, and is thus not really important, but that of `#apply` must be the handler itself, so that e.g. the adjustments admin has access to any application errors. --- core/app/models/spree/null_promotion_handler.rb | 5 ++++- core/spec/models/spree/null_promotion_handler_spec.rb | 5 +++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/core/app/models/spree/null_promotion_handler.rb b/core/app/models/spree/null_promotion_handler.rb index f40087f745a..f1a7d0b5a11 100644 --- a/core/app/models/spree/null_promotion_handler.rb +++ b/core/app/models/spree/null_promotion_handler.rb @@ -12,7 +12,10 @@ def initialize(order) def activate @order end - alias_method :apply, :activate + + def apply + self + end def can_apply? true diff --git a/core/spec/models/spree/null_promotion_handler_spec.rb b/core/spec/models/spree/null_promotion_handler_spec.rb index 8de56a305c6..9d09f15c3c2 100644 --- a/core/spec/models/spree/null_promotion_handler_spec.rb +++ b/core/spec/models/spree/null_promotion_handler_spec.rb @@ -17,8 +17,9 @@ describe "#apply" do subject(:activate) { handler.apply } - it "returns the unchanged order" do - expect(activate).to eq(order) + it "returns the handler with the unchanged order" do + expect(activate).to eq(handler) + expect(activate.order).to eq(order) end end From 8eceec70122771a9eb2d9e18fe5387f33462989e Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 4 Jun 2024 12:32:07 +0200 Subject: [PATCH 016/834] Add :name attribute to promotion attributes in spec With the NullPromotionConfiguration, the default promotion attributes are an empty list. In order to test whether attributes can be deleted from this list, we first have to add an attribute. --- api/spec/lib/spree/api_configuration_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/api/spec/lib/spree/api_configuration_spec.rb b/api/spec/lib/spree/api_configuration_spec.rb index 4a41db5c2f0..817f25c014f 100644 --- a/api/spec/lib/spree/api_configuration_spec.rb +++ b/api/spec/lib/spree/api_configuration_spec.rb @@ -16,6 +16,7 @@ end it "can delete attributes" do + config.promotion_attributes << :name # name is not included by default with the NullPromotionConfiguration expect(promotion_attributes).to include(:name) config.promotion_attributes.delete(:name) expect(promotion_attributes).not_to include(:name) From 77a2aab82320c7329f8bdbb2b00f6cf80780d9e5 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 7 May 2024 12:00:37 +0200 Subject: [PATCH 017/834] Use Null Promotion Configuration in core We want to use the legacy `Spree::PromotionConfiguration` in the solidus_legacy_promotions test suite. --- core/lib/spree/app_configuration.rb | 4 +--- core/spec/lib/spree/app_configuration_spec.rb | 14 +++++++------- .../lib/solidus_legacy_promotions/engine.rb | 1 + 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/core/lib/spree/app_configuration.rb b/core/lib/spree/app_configuration.rb index b10ddde992a..03e818a42a7 100644 --- a/core/lib/spree/app_configuration.rb +++ b/core/lib/spree/app_configuration.rb @@ -592,11 +592,9 @@ def stock # @!attribute [rw] promotions # @return [Spree::Core::NullPromotionConfiguration] an object that conforms to the API of # the example promotion configuration class Spree::Core::NullPromotionConfiguration. - # Currently, this defaults to the legacy promotion configuration, until that system is fully extracted - # to the `solidus_legacy_promotions` gem. attr_writer :promotions def promotions - @promotions ||= SolidusLegacyPromotions::Configuration.new + @promotions ||= Spree::Core::NullPromotionConfiguration.new end class << self diff --git a/core/spec/lib/spree/app_configuration_spec.rb b/core/spec/lib/spree/app_configuration_spec.rb index 7ffc0e84d69..bf72cbcb63f 100644 --- a/core/spec/lib/spree/app_configuration_spec.rb +++ b/core/spec/lib/spree/app_configuration_spec.rb @@ -41,7 +41,7 @@ end it "uses core's promotion configuration class by default" do - expect(prefs.promotions).to be_a SolidusLegacyPromotions::Configuration + expect(prefs.promotions).to be_a Spree::Core::NullPromotionConfiguration end context "deprecated preferences" do @@ -52,23 +52,23 @@ end it "uses order adjustments recalculator class by default" do - expect(prefs.promotion_adjuster_class).to eq Spree::Promotion::OrderAdjustmentsRecalculator + expect(prefs.promotion_adjuster_class).to eq Spree::NullPromotionAdjuster end it "uses promotion handler coupon class by default" do - expect(prefs.coupon_code_handler_class).to eq Spree::PromotionHandler::Coupon + expect(prefs.coupon_code_handler_class).to eq Spree::NullPromotionHandler end it "uses promotion handler shipping class by default" do - expect(prefs.shipping_promotion_handler_class).to eq Spree::PromotionHandler::Shipping + expect(prefs.shipping_promotion_handler_class).to eq Spree::NullPromotionHandler end it "uses promotion code batch mailer class by default" do - expect(prefs.promotion_code_batch_mailer_class).to eq Spree::PromotionCodeBatchMailer + expect(prefs.promotion_code_batch_mailer_class).to eq Spree::DeprecatedConfigurableClass end it "uses promotion chooser class by default" do - expect(prefs.promotion_chooser_class).to eq Spree::PromotionChooser + expect(prefs.promotion_chooser_class).to eq Spree::DeprecatedConfigurableClass end end @@ -113,7 +113,7 @@ describe '#promotions' do subject { prefs.promotions } - it { is_expected.to be_a SolidusLegacyPromotions::Configuration } + it { is_expected.to be_a Spree::Core::NullPromotionConfiguration } end describe '@default_country_iso_code' do diff --git a/legacy_promotions/lib/solidus_legacy_promotions/engine.rb b/legacy_promotions/lib/solidus_legacy_promotions/engine.rb index 2dc0aeb1bf3..6fe8971c370 100644 --- a/legacy_promotions/lib/solidus_legacy_promotions/engine.rb +++ b/legacy_promotions/lib/solidus_legacy_promotions/engine.rb @@ -54,6 +54,7 @@ class Engine < ::Rails::Engine initializer "solidus_legacy_promotions", after: "spree.load_config_initializers" do Spree::Config.order_contents_class = "Spree::OrderContents" + Spree::Config.promotions = SolidusLegacyPromotions::Configuration.new end end end From 742b1cf302cb52041416eead914aca7f06b0bad3 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 6 Jun 2024 15:28:48 +0200 Subject: [PATCH 018/834] Disallow sprockets-rails 3.5.0 sprockets-rails 3.5.0 breaks our test suite. See https://github.com/rails/sprockets-rails/issues/524 and https://github.com/rails/sprockets-rails/pull/525 --- core/solidus_core.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/solidus_core.gemspec b/core/solidus_core.gemspec index b56b540b466..287d20fe36d 100644 --- a/core/solidus_core.gemspec +++ b/core/solidus_core.gemspec @@ -44,7 +44,7 @@ Gem::Specification.new do |s| s.add_dependency 'kt-paperclip', ['>= 6.3', '< 8'] s.add_dependency 'psych', ['>= 4.0.1', '< 5.0'] s.add_dependency 'ransack', '~> 4.0' - s.add_dependency 'sprockets-rails' + s.add_dependency 'sprockets-rails', '!= 3.5.0' s.add_dependency 'state_machines-activerecord', '~> 0.6' s.add_dependency 'omnes', '~> 0.2.2' From 17728e91e56e3d89b2809776d76064ad6828b68f Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 5 Jun 2024 15:44:17 +0200 Subject: [PATCH 019/834] Backend: Make order search fields configurable The order search depends on legacy promotion system associations to be present, but we don't want to introduce Deface as a core dependency. Therefore, we must make the search fields for orders configurable. This introduces a new configuration option on the Backend configuration: `Spree::Backend::Config.search_fields`. This Hash has controller paths as keys, and arrays that holds hashes with a `:partial` key that indicates which partial should render this search field. Optionally, one can specify an `:if` key with a lambda as the value that gets executed at runtime, so that unnecessary fields ("Store" for installations with only one store instance) are not displayed. Complex fields can be done by specifying a custom partial, such as the `:date_range_picker` partial in this PR. The third key, `:locals` contains a has of the local variables inside the partial. Anything that needs to be computed dynamically should be done with a lambda here. --- .../views/spree/admin/orders/index.html.erb | 86 +----------------- .../shared/search_fields/_checkbox.html.erb | 8 ++ .../search_fields/_date_range_picker.html.erb | 16 ++++ .../shared/search_fields/_select.html.erb | 6 ++ .../shared/search_fields/_text_field.html.erb | 6 ++ .../_variant_autocomplete.html.erb | 6 ++ backend/lib/spree/backend_configuration.rb | 90 +++++++++++++++++++ 7 files changed, 136 insertions(+), 82 deletions(-) create mode 100644 backend/app/views/spree/admin/shared/search_fields/_checkbox.html.erb create mode 100644 backend/app/views/spree/admin/shared/search_fields/_date_range_picker.html.erb create mode 100644 backend/app/views/spree/admin/shared/search_fields/_select.html.erb create mode 100644 backend/app/views/spree/admin/shared/search_fields/_text_field.html.erb create mode 100644 backend/app/views/spree/admin/shared/search_fields/_variant_autocomplete.html.erb diff --git a/backend/app/views/spree/admin/orders/index.html.erb b/backend/app/views/spree/admin/orders/index.html.erb index 18750fb2401..cd4c58c891d 100644 --- a/backend/app/views/spree/admin/orders/index.html.erb +++ b/backend/app/views/spree/admin/orders/index.html.erb @@ -16,89 +16,11 @@
<%= search_form_for [:admin, @search] do |f| %>
-
-
- <%= label_tag :q_created_at_gt, t('spree.date_range') %> -
- <%= f.text_field :created_at_gt, class: 'datepicker form-control datepicker-from', value: params[:q][:created_at_gt], placeholder: t('spree.start') %> - -
- - - -
- - <%= f.text_field :created_at_lt, class: 'datepicker form-control datepicker-to', value: params[:q][:created_at_lt], placeholder: t('spree.stop') %> -
-
- -
- <%= label_tag :q_state_eq, t('spree.status') %> - <%= f.select :state_eq, Spree::Order.state_machines[:state].states.collect {|s| [t(s.name, scope: 'spree.order_state'), s.value]}, {include_blank: true}, class: 'custom-select fullwidth' %> -
- -
- <%= label_tag nil, t('spree.promotion') %> - <%= f.text_field :order_promotions_promotion_code_value_start, size: 25 %> -
- -
- <%= label_tag nil, t('spree.shipment_number') %> - <%= f.text_field :shipments_number_start %> -
- -
- <%= label_tag :q_shipment_state, t('spree.shipment_state') %> - <%= f.select :shipment_state_eq, %i[backorder canceled partial pending ready shipped].map { |state| [t("spree.shipment_states.#{state}"), state] }, { include_blank: true }, { class: "custom-select fullwidth" } %> -
-
- -
-
-
-
- <%= label_tag :q_number_start, t('spree.order_number', number: '') %> - <%= f.text_field :number_start %> -
- -
- <%= label_tag :q_email_start, t('spree.email') %> - <%= f.text_field :email_start %> -
-
- -
-
- <%= label_tag :q_bill_address_name_cont, t('spree.name_contains') %> - <%= f.text_field :bill_address_name_cont, size: 25 %> -
-
- -
-
- <%= label_tag :q_line_items_variant_id_in, t('spree.variant') %> - <%= f.text_field :line_items_variant_id_in, class: "variant_autocomplete fullwidth" %> -
-
-
-
- -
- <% if Spree::Store.count > 1 %> -
- <%= label_tag :q_store_id_eq, t('spree.store') %> - <%= f.select :store_id_eq, Spree::Store.all.map { |s| [s.name, s.id] }, { include_blank: true }, { class: "custom-select fullwidth" } %> -
+ <% Spree::Backend::Config.search_fields[controller_path].each do |field_config| %> + <% if !field_config.has_key?(:if) || field_config[:if].call %> + <%= render field_config.except(:if).deep_merge(locals: { f: f }) %> <% end %> - -
- -
-
- + <% end %>
diff --git a/backend/app/views/spree/admin/shared/search_fields/_checkbox.html.erb b/backend/app/views/spree/admin/shared/search_fields/_checkbox.html.erb new file mode 100644 index 00000000000..76ea54dc569 --- /dev/null +++ b/backend/app/views/spree/admin/shared/search_fields/_checkbox.html.erb @@ -0,0 +1,8 @@ +
+
+ +
+
diff --git a/backend/app/views/spree/admin/shared/search_fields/_date_range_picker.html.erb b/backend/app/views/spree/admin/shared/search_fields/_date_range_picker.html.erb new file mode 100644 index 00000000000..e3a9cc26528 --- /dev/null +++ b/backend/app/views/spree/admin/shared/search_fields/_date_range_picker.html.erb @@ -0,0 +1,16 @@ +
+
+ <%= label_tag "q_#{attribute}", label.respond_to?(:call) ? label.call : label %> +
+ <%= f.text_field "#{attribute}_gt", class: 'datepicker form-control datepicker-from', value: params[:q][:created_at_gt], placeholder: t('spree.start') %> + +
+ + + +
+ + <%= f.text_field "#{attribute}_lt", class: 'datepicker form-control datepicker-to', value: params[:q][:created_at_lt], placeholder: t('spree.stop') %> +
+
+
diff --git a/backend/app/views/spree/admin/shared/search_fields/_select.html.erb b/backend/app/views/spree/admin/shared/search_fields/_select.html.erb new file mode 100644 index 00000000000..f28e2b2bcd2 --- /dev/null +++ b/backend/app/views/spree/admin/shared/search_fields/_select.html.erb @@ -0,0 +1,6 @@ +
+
+ <%= label_tag "q_#{ransack}", label.respond_to?(:call) ? label.call : label %> + <%= f.select ransack, options_for_select(options.call, params[:q][ransack]), {include_blank: true}, class: 'custom-select fullwidth' %> +
+
diff --git a/backend/app/views/spree/admin/shared/search_fields/_text_field.html.erb b/backend/app/views/spree/admin/shared/search_fields/_text_field.html.erb new file mode 100644 index 00000000000..38f54038c0f --- /dev/null +++ b/backend/app/views/spree/admin/shared/search_fields/_text_field.html.erb @@ -0,0 +1,6 @@ +
+
+ <%= label_tag "q_#{ransack}", label.respond_to?(:call) ? label.call : label %> + <%= f.text_field ransack, class: 'form-control', value: params[:q][ransack] %> +
+
diff --git a/backend/app/views/spree/admin/shared/search_fields/_variant_autocomplete.html.erb b/backend/app/views/spree/admin/shared/search_fields/_variant_autocomplete.html.erb new file mode 100644 index 00000000000..fa3b20035b2 --- /dev/null +++ b/backend/app/views/spree/admin/shared/search_fields/_variant_autocomplete.html.erb @@ -0,0 +1,6 @@ +
+
+ <%= label_tag "q_#{ransack}", label.respond_to?(:call) ? label.call : label %> + <%= f.text_field ransack, class: 'variant_autocomplete fullwidth', value: params[:q][ransack] %> +
+
diff --git a/backend/lib/spree/backend_configuration.rb b/backend/lib/spree/backend_configuration.rb index b2489dc4f72..70b8cfa9716 100644 --- a/backend/lib/spree/backend_configuration.rb +++ b/backend/lib/spree/backend_configuration.rb @@ -19,6 +19,96 @@ class BackendConfiguration < Preferences::Configuration solidus_admin: 'spree/backend/themes/solidus_admin' } + preference :search_fields, :hash, default: { + "spree/admin/orders" => [ + { + partial: "spree/admin/shared/search_fields/date_range_picker", + locals: { + attribute: :created_at, + label: -> { I18n.t(:date_range, scope: :spree) } + } + }, + { + partial: "spree/admin/shared/search_fields/select", + locals: { + ransack: :state_eq, + label: -> { I18n.t(:status, scope: :spree) }, + options: -> { + Spree::Order.state_machines[:state].states.collect { |s| [I18n.t(s.name, scope: 'spree.order_state'), s.value] } + } + } + }, + { + partial: "spree/admin/shared/search_fields/select", + locals: { + ransack: :shipment_state_eq, + label: -> { I18n.t(:shipment_state, scope: :spree) }, + options: -> { + %i[backorder canceled partial pending ready shipped].map { |state| [I18n.t("spree.shipment_states.#{state}"), state] } + } + } + }, + { + partial: "spree/admin/shared/search_fields/variant_autocomplete", + locals: { + ransack: :line_items_variant_id_in, + label: -> { I18n.t(:variant, scope: :spree) } + } + }, + { + partial: "spree/admin/shared/search_fields/text_field", + locals: { + ransack: :number_start, + label: -> { I18n.t(:order_number, scope: :spree, number: "") } + } + }, + { + partial: "spree/admin/shared/search_fields/text_field", + locals: { + ransack: :shipments_number_start, + label: -> { I18n.t(:shipment_number, scope: :spree) } + } + }, + { + partial: "spree/admin/shared/search_fields/text_field", + locals: { + ransack: :bill_address_name_cont, + label: -> { I18n.t(:name_contains, scope: :spree) } + } + }, + { + partial: "spree/admin/shared/search_fields/text_field", + locals: { + ransack: :email_start, + label: -> { I18n.t(:email, scope: :spree) } + } + }, + { + partial: "spree/admin/shared/search_fields/text_field", + locals: { + ransack: :order_promotions_promotion_code_value_start, + label: -> { I18n.t(:promotion, scope: :spree) } + } + }, + { + partial: "spree/admin/shared/search_fields/select", + locals: { + ransack: :store_id_eq, + label: -> { I18n.t(:store, scope: :spree) }, + options: -> { Spree::Store.all.map { |store| [store.name, store.id] } }, + }, + if: -> { Spree::Store.many? } + }, + { + partial: "spree/admin/shared/search_fields/checkbox", + locals: { + ransack: :completed_at_not_null, + label: -> { I18n.t(:show_only_complete_orders, scope: :spree) } + } + } + ] + } + # @!attribute [rw] theme # @return [String] Default admin theme name versioned_preference :theme, :string, initial_value: 'classic', boundaries: { "4.2.0" => "solidus_admin", "4.4.0" => "solidus" } From 800d0f8895bcd94f90752aeffffa1623978de084 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 6 Jun 2024 08:32:36 +0200 Subject: [PATCH 020/834] Move order promotions search field to legacy_promotions This search field depends on the `Spree::Order#promotions` relation to be available and searchable. This is not given with a null promotion system or the upcoming new promotion system, where relations will have a different name. So we only add it if solidus_legacy_promotions is also loaded. --- backend/lib/spree/backend_configuration.rb | 7 ---- .../features/admin/orders/listing_spec.rb | 22 ----------- .../lib/solidus_legacy_promotions/engine.rb | 15 ++++++++ .../features/backend/orders/listing_spec.rb | 38 +++++++++++++++++++ 4 files changed, 53 insertions(+), 29 deletions(-) create mode 100644 legacy_promotions/spec/features/backend/orders/listing_spec.rb diff --git a/backend/lib/spree/backend_configuration.rb b/backend/lib/spree/backend_configuration.rb index 70b8cfa9716..2b8d639cad3 100644 --- a/backend/lib/spree/backend_configuration.rb +++ b/backend/lib/spree/backend_configuration.rb @@ -83,13 +83,6 @@ class BackendConfiguration < Preferences::Configuration label: -> { I18n.t(:email, scope: :spree) } } }, - { - partial: "spree/admin/shared/search_fields/text_field", - locals: { - ransack: :order_promotions_promotion_code_value_start, - label: -> { I18n.t(:promotion, scope: :spree) } - } - }, { partial: "spree/admin/shared/search_fields/select", locals: { diff --git a/backend/spec/features/admin/orders/listing_spec.rb b/backend/spec/features/admin/orders/listing_spec.rb index d9f0ec84c24..9dfc866a087 100644 --- a/backend/spec/features/admin/orders/listing_spec.rb +++ b/backend/spec/features/admin/orders/listing_spec.rb @@ -5,9 +5,6 @@ describe "Orders Listing", type: :feature, js: true do stub_authorization! - let!(:promotion) { create(:promotion_with_item_adjustment, code: "vnskseiw") } - let(:promotion_code) { promotion.codes.first } - before(:each) do allow_any_instance_of(Spree::OrderInventory).to receive(:add_to_shipment) @order1 = create(:order_with_line_items, created_at: 1.day.from_now, completed_at: 1.day.from_now, number: "R100") @@ -144,25 +141,6 @@ within("table#listing_orders") { expect(page).not_to have_content("R200") } end - context "filter on promotions" do - before(:each) do - @order1.order_promotions.build( - promotion: promotion, - promotion_code: promotion_code - ) - @order1.save - visit spree.admin_orders_path - end - - it "only shows the orders with the selected promotion" do - click_on "Filter Results" - fill_in "q_order_promotions_promotion_code_value_start", with: promotion.codes.first.value - click_on 'Filter Results' - within_row(1) { expect(page).to have_content("R100") } - within("table#listing_orders") { expect(page).not_to have_content("R200") } - end - end - context "when toggling the completed orders checkbox" do before do create(:order, number: 'R300', completed_at: nil, state: 'cart') diff --git a/legacy_promotions/lib/solidus_legacy_promotions/engine.rb b/legacy_promotions/lib/solidus_legacy_promotions/engine.rb index 2dc0aeb1bf3..3bc9913656b 100644 --- a/legacy_promotions/lib/solidus_legacy_promotions/engine.rb +++ b/legacy_promotions/lib/solidus_legacy_promotions/engine.rb @@ -42,6 +42,21 @@ class Engine < ::Rails::Engine end end + initializer "solidus_legacy_promotions.add_order_search_field" do + if SolidusSupport.backend_available? + email_field_index = Spree::Backend::Config.search_fields["spree/admin/orders"].find_index do |field| + field.dig(:locals, :ransack) == :email_start + end + Spree::Backend::Config.search_fields["spree/admin/orders"].insert(email_field_index + 1, { + partial: "spree/admin/shared/search_fields/text_field", + locals: { + ransack: :order_promotions_promotion_code_value_start, + label: -> { I18n.t(:promotion, scope: :spree) } + } + }) + end + end + initializer 'solidus_legacy_promotions.core.pub_sub', after: 'spree.core.pub_sub' do |app| app.reloader.to_prepare do Spree::OrderPromotionSubscriber.new.subscribe_to(Spree::Bus) diff --git a/legacy_promotions/spec/features/backend/orders/listing_spec.rb b/legacy_promotions/spec/features/backend/orders/listing_spec.rb new file mode 100644 index 00000000000..bef2d81a1c7 --- /dev/null +++ b/legacy_promotions/spec/features/backend/orders/listing_spec.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe "Orders Listing", type: :feature, js: true do + stub_authorization! + + before(:each) do + allow_any_instance_of(Spree::OrderInventory).to receive(:add_to_shipment) + @order1 = create(:order_with_line_items, created_at: 1.day.from_now, completed_at: 1.day.from_now, number: "R100") + @order2 = create(:order, created_at: 1.day.ago, completed_at: 1.day.ago, number: "R200") + visit spree.admin_orders_path + end + + context "searching orders" do + context "filter on promotions" do + let!(:promotion) { create(:promotion_with_item_adjustment, code: "vnskseiw") } + let(:promotion_code) { promotion.codes.first } + + before(:each) do + @order1.order_promotions.build( + promotion: promotion, + promotion_code: promotion_code + ) + @order1.save + visit spree.admin_orders_path + end + + it "only shows the orders with the selected promotion" do + click_on "Filter Results" + fill_in "q_order_promotions_promotion_code_value_start", with: promotion.codes.first.value + click_on 'Filter Results' + within_row(1) { expect(page).to have_content("R100") } + within("table#listing_orders") { expect(page).not_to have_content("R200") } + end + end + end +end From c5303b92bfe6733b76e095a6e687095f61ed931c Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 5 Jun 2024 09:41:55 +0200 Subject: [PATCH 021/834] App configuration: Use SimpleOrderContents by default With the null promotion configuration in place, we can't use the legacy Spree::OrderContents class, as that makes references to the legacy promotion system. See https://github.com/solidusio/solidus/pull/5755 --- api/spec/requests/spree/api/orders_spec.rb | 14 ------ core/lib/spree/app_configuration.rb | 2 +- core/spec/lib/spree/app_configuration_spec.rb | 4 ++ core/spec/models/spree/promotion_spec.rb | 25 ---------- .../spree/promotion_integration_spec.rb | 25 ++++++++++ .../spec/requests/spree/api/orders_spec.rb | 50 +++++++++++++++++++ 6 files changed, 80 insertions(+), 40 deletions(-) create mode 100644 legacy_promotions/spec/requests/spree/api/orders_spec.rb diff --git a/api/spec/requests/spree/api/orders_spec.rb b/api/spec/requests/spree/api/orders_spec.rb index 4ca347a2abb..4d3a5410220 100644 --- a/api/spec/requests/spree/api/orders_spec.rb +++ b/api/spec/requests/spree/api/orders_spec.rb @@ -77,20 +77,6 @@ module Spree::Api end end end - - context "with existing promotion" do - let(:discount) { 2 } - before do - create(:promotion, :with_line_item_adjustment, apply_automatically: true, adjustment_rate: discount ) - end - - it "activates the promotion" do - post spree.api_orders_path, params: { order: { line_items: { "0" => { variant_id: variant.to_param, quantity: 1 } } } } - order = Spree::Order.last - line_item = order.line_items.first - expect(order.total).to eq(line_item.price - discount) - end - end end context "when the current user can administrate the order" do diff --git a/core/lib/spree/app_configuration.rb b/core/lib/spree/app_configuration.rb index b10ddde992a..37d48e16e2e 100644 --- a/core/lib/spree/app_configuration.rb +++ b/core/lib/spree/app_configuration.rb @@ -394,7 +394,7 @@ def default_pricing_options # @!attribute [rw] order_contents_class # @return [Class] a class with the same public interfaces as # Spree::OrderContents. - class_name_attribute :order_contents_class, default: 'Spree::OrderContents' + class_name_attribute :order_contents_class, default: 'Spree::SimpleOrderContents' # Allows providing your own class for shipping an order. # diff --git a/core/spec/lib/spree/app_configuration_spec.rb b/core/spec/lib/spree/app_configuration_spec.rb index 7ffc0e84d69..6770b9c5567 100644 --- a/core/spec/lib/spree/app_configuration_spec.rb +++ b/core/spec/lib/spree/app_configuration_spec.rb @@ -36,6 +36,10 @@ expect(prefs.variant_search_class).to eq Spree::Core::Search::Variant end + it "uses simple order contents class by default" do + expect(prefs.order_contents_class).to eq Spree::SimpleOrderContents + end + it "uses variant price selector class by default" do expect(prefs.variant_price_selector_class).to eq Spree::Variant::PriceSelector end diff --git a/core/spec/models/spree/promotion_spec.rb b/core/spec/models/spree/promotion_spec.rb index 449e06730b6..c4b696192fa 100644 --- a/core/spec/models/spree/promotion_spec.rb +++ b/core/spec/models/spree/promotion_spec.rb @@ -915,31 +915,6 @@ end end - describe "adding items to the cart" do - let(:order) { create :order } - let(:line_item) { create :line_item, order: order } - let(:promo) { create :promotion_with_item_adjustment, adjustment_rate: 5, code: 'promo' } - let(:promotion_code) { promo.codes.first } - let(:variant) { create :variant } - - it "updates the promotions for new line items" do - expect(line_item.adjustments).to be_empty - expect(order.adjustment_total).to eq 0 - - promo.activate order: order, promotion_code: promotion_code - order.recalculate - - expect(line_item.adjustments.size).to eq(1) - expect(order.adjustment_total).to eq(-5) - - other_line_item = order.contents.add(variant, 1, currency: order.currency) - - expect(other_line_item).not_to eq line_item - expect(other_line_item.adjustments.size).to eq(1) - expect(order.adjustment_total).to eq(-10) - end - end - describe "promotion deletion" do subject { promotion.destroy! } diff --git a/legacy_promotions/spec/models/spree/promotion_integration_spec.rb b/legacy_promotions/spec/models/spree/promotion_integration_spec.rb index 90ad8f194da..6ed19aba4cb 100644 --- a/legacy_promotions/spec/models/spree/promotion_integration_spec.rb +++ b/legacy_promotions/spec/models/spree/promotion_integration_spec.rb @@ -181,4 +181,29 @@ }.to change{ order.reload.state }.from("confirm").to("address") end end + + describe "adding items to the cart" do + let(:order) { create :order } + let(:line_item) { create :line_item, order: order } + let(:promo) { create :promotion_with_item_adjustment, adjustment_rate: 5, code: 'promo' } + let(:promotion_code) { promo.codes.first } + let(:variant) { create :variant } + + it "updates the promotions for new line items" do + expect(line_item.adjustments).to be_empty + expect(order.adjustment_total).to eq 0 + + promo.activate order: order, promotion_code: promotion_code + order.recalculate + + expect(line_item.adjustments.size).to eq(1) + expect(order.adjustment_total).to eq(-5) + + other_line_item = order.contents.add(variant, 1, currency: order.currency) + + expect(other_line_item).not_to eq line_item + expect(other_line_item.adjustments.size).to eq(1) + expect(order.adjustment_total).to eq(-10) + end + end end diff --git a/legacy_promotions/spec/requests/spree/api/orders_spec.rb b/legacy_promotions/spec/requests/spree/api/orders_spec.rb new file mode 100644 index 00000000000..3fc4f700f45 --- /dev/null +++ b/legacy_promotions/spec/requests/spree/api/orders_spec.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'API Orders', type: :request do + let!(:order) { create(:order) } + let(:variant) { create(:variant) } + let(:line_item) { create(:line_item) } + let(:address_params) { { country_id: Country.first.id, state_id: State.first.id } } + + let(:current_api_user) do + user = Spree.user_class.new(email: "solidus@example.com") + user.generate_spree_api_key! + user + end + + before do + stub_authentication! + end + + describe "POST create" do + let(:target_user) { create :user } + let(:attributes) { { user_id: target_user.id, email: target_user.email } } + + subject do + post spree.api_orders_path, params: { order: attributes } + response + end + + context "when the current user cannot administrate the order" do + custom_authorization! do |_| + can :create, Spree::Order + end + + context "with existing promotion" do + let(:discount) { 2 } + before do + create(:promotion, :with_line_item_adjustment, apply_automatically: true, adjustment_rate: discount ) + end + + it "activates the promotion" do + post spree.api_orders_path, params: { order: { line_items: { "0" => { variant_id: variant.to_param, quantity: 1 } } } } + order = Spree::Order.last + line_item = order.line_items.first + expect(order.total).to eq(line_item.price - discount) + end + end + end + end +end From 44387447e7625a8df480f8db3519ce7278bc474e Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 6 Jun 2024 16:53:18 +0200 Subject: [PATCH 022/834] Move Spree::OrderContents to LegacyPromotions gem This class directly references Spree::PromotionHandler::Cart, and thus needs to live in the legacy_promotions gem. If you inherit from this class, and it happens to not be available after upgrading, please add ``` gem "solidus_legacy_promotions" ``` to your Gemfile. --- backend/spec/controllers/spree/admin/orders_controller_spec.rb | 2 +- {core => legacy_promotions}/app/models/spree/order_contents.rb | 0 .../spec/models/spree/order_contents_spec.rb | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename {core => legacy_promotions}/app/models/spree/order_contents.rb (100%) rename {core => legacy_promotions}/spec/models/spree/order_contents_spec.rb (100%) diff --git a/backend/spec/controllers/spree/admin/orders_controller_spec.rb b/backend/spec/controllers/spree/admin/orders_controller_spec.rb index 491c2b9d523..a9b4028198b 100644 --- a/backend/spec/controllers/spree/admin/orders_controller_spec.rb +++ b/backend/spec/controllers/spree/admin/orders_controller_spec.rb @@ -27,7 +27,7 @@ before do allow(Spree::Order).to receive_message_chain(:includes, find_by!: order) - allow(order).to receive_messages(contents: Spree::OrderContents.new(order)) + allow(order).to receive_messages(contents: Spree::Config.order_contents_class.new(order)) end context "#approve" do diff --git a/core/app/models/spree/order_contents.rb b/legacy_promotions/app/models/spree/order_contents.rb similarity index 100% rename from core/app/models/spree/order_contents.rb rename to legacy_promotions/app/models/spree/order_contents.rb diff --git a/core/spec/models/spree/order_contents_spec.rb b/legacy_promotions/spec/models/spree/order_contents_spec.rb similarity index 100% rename from core/spec/models/spree/order_contents_spec.rb rename to legacy_promotions/spec/models/spree/order_contents_spec.rb From 79c11951f8a60058f7799c0a11d4a89943d19961 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 6 Jun 2024 16:10:49 +0200 Subject: [PATCH 023/834] Create custom orders index component for solidus_legacy_promotions The new admin allows orders to be filtered by promotion name. However, with the legacy promotion system extracted into an extension, we can't rely on `Spree::Promotion` to exist in `solidus_admin`. In order to get around this issue, we create a new index component for the admin orders page that includes the filter, and register it in an initializer. This also adds a spec for filtering by promotion name. --- .../solidus_admin/orders/index/component.rb | 9 +------ .../orders/index/component.rb | 15 ++++++++++++ .../orders/index/component.yml | 3 +++ .../lib/solidus_legacy_promotions/engine.rb | 8 +++++++ .../solidus_admin/orders/index_spec.rb | 24 +++++++++++++++++++ 5 files changed, 51 insertions(+), 8 deletions(-) create mode 100644 legacy_promotions/lib/components/admin/solidus_legacy_promotions/orders/index/component.rb create mode 100644 legacy_promotions/lib/components/admin/solidus_legacy_promotions/orders/index/component.yml create mode 100644 legacy_promotions/spec/features/solidus_admin/orders/index_spec.rb diff --git a/admin/app/components/solidus_admin/orders/index/component.rb b/admin/app/components/solidus_admin/orders/index/component.rb index 5c412db937e..92e37feadc6 100644 --- a/admin/app/components/solidus_admin/orders/index/component.rb +++ b/admin/app/components/solidus_admin/orders/index/component.rb @@ -77,14 +77,7 @@ def filters option ] end - }, - { - label: t('.filters.promotions'), - combinator: 'or', - attribute: "promotions_id", - predicate: "in", - options: Spree::Promotion.all.pluck(:name, :id), - }, + } ] end diff --git a/legacy_promotions/lib/components/admin/solidus_legacy_promotions/orders/index/component.rb b/legacy_promotions/lib/components/admin/solidus_legacy_promotions/orders/index/component.rb new file mode 100644 index 00000000000..96bebec87fc --- /dev/null +++ b/legacy_promotions/lib/components/admin/solidus_legacy_promotions/orders/index/component.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class SolidusLegacyPromotions::Orders::Index::Component < SolidusAdmin::Orders::Index::Component + def filters + super + [ + { + label: t('.filters.promotions'), + combinator: 'or', + attribute: "promotions_id", + predicate: "in", + options: Spree::Promotion.all.pluck(:name, :id), + } + ] + end +end diff --git a/legacy_promotions/lib/components/admin/solidus_legacy_promotions/orders/index/component.yml b/legacy_promotions/lib/components/admin/solidus_legacy_promotions/orders/index/component.yml new file mode 100644 index 00000000000..95af3f495d4 --- /dev/null +++ b/legacy_promotions/lib/components/admin/solidus_legacy_promotions/orders/index/component.yml @@ -0,0 +1,3 @@ +en: + filters: + promotions: Promotions diff --git a/legacy_promotions/lib/solidus_legacy_promotions/engine.rb b/legacy_promotions/lib/solidus_legacy_promotions/engine.rb index 2dc0aeb1bf3..6ec4c6cf9fd 100644 --- a/legacy_promotions/lib/solidus_legacy_promotions/engine.rb +++ b/legacy_promotions/lib/solidus_legacy_promotions/engine.rb @@ -29,6 +29,14 @@ class Engine < ::Rails::Engine end end + initializer "solidus_legacy_promotions.add_admin_order_index_component" do + if SolidusSupport.admin_available? + config.to_prepare do + SolidusAdmin::Config.components["orders/index"] = SolidusLegacyPromotions::Orders::Index::Component + end + end + end + initializer "solidus_legacy_promotions.add_solidus_admin_menu_items" do if SolidusSupport.admin_available? SolidusAdmin::Config.configure do |config| diff --git a/legacy_promotions/spec/features/solidus_admin/orders/index_spec.rb b/legacy_promotions/spec/features/solidus_admin/orders/index_spec.rb new file mode 100644 index 00000000000..00e0adbb5c1 --- /dev/null +++ b/legacy_promotions/spec/features/solidus_admin/orders/index_spec.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe "Orders", type: :feature, solidus_admin: true do + let(:promotion) { create(:promotion, name: "10OFF") } + let!(:order_with_promotion) { create(:completed_order_with_promotion, number: "R123456789", promotion: promotion) } + let!(:order_without_promotion) { create(:completed_order_with_totals, number: "R987654321") } + + before { sign_in create(:admin_user, email: "admin@example.com") } + + it "lists products", :js do + visit "/admin/orders" + find("button[aria-label=Filter]").click + within("div[role=search]") do + expect(page).to have_content("Promotions") + find(:xpath, "//summary[normalize-space(text())='Promotions']").click + end + check "10OFF" + expect(page).to have_content("R123456789") + expect(page).not_to have_content("R987654321") + expect(page).to be_axe_clean + end +end From f326ca44ffd6973f5e3c3ea02de1891f99f45423 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 7 Jun 2024 14:21:42 +0200 Subject: [PATCH 024/834] Use configurable adjustment promotion source types in Thumbnail.for There's a View Component for rendering icons, and it's got a convenience method for rendering adjustables to adjustments. This uses the `Spree::Config.adjustment_promotion_source_types` configuration option to figure out whether an adjustable needs a megaphone as an icon. With the legacy promotion system extracted, `Spree::PromotionAction` will not necessarily be available. --- .../solidus_admin/ui/thumbnail/component.rb | 2 +- .../component_preview/overview.html.erb | 1 - .../solidus_admin/ui/thumbnail/component_spec.rb | 16 ++++++++++++++++ legacy_promotions/spec/rails_helper.rb | 6 ++++++ 4 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 legacy_promotions/spec/components/solidus_admin/ui/thumbnail/component_spec.rb diff --git a/admin/app/components/solidus_admin/ui/thumbnail/component.rb b/admin/app/components/solidus_admin/ui/thumbnail/component.rb index 7776e0a2ccd..193c20885bf 100644 --- a/admin/app/components/solidus_admin/ui/thumbnail/component.rb +++ b/admin/app/components/solidus_admin/ui/thumbnail/component.rb @@ -31,7 +31,7 @@ def call def self.for(record, **attrs) case record - when Spree::PromotionAction then new(icon: "megaphone-line", **attrs) + when *Spree::Config.adjustment_promotion_source_types then new(icon: "megaphone-line", **attrs) when Spree::UnitCancel then new(icon: "close-circle-line", **attrs) when Spree::TaxRate then new(icon: "percent-line", **attrs) when Spree::LineItem then self.for(record.variant, **attrs) diff --git a/admin/spec/components/previews/solidus_admin/ui/thumbnail/component_preview/overview.html.erb b/admin/spec/components/previews/solidus_admin/ui/thumbnail/component_preview/overview.html.erb index ed774adf1e6..caa4fb043ee 100644 --- a/admin/spec/components/previews/solidus_admin/ui/thumbnail/component_preview/overview.html.erb +++ b/admin/spec/components/previews/solidus_admin/ui/thumbnail/component_preview/overview.html.erb @@ -52,7 +52,6 @@ <% attachment = Object.new.tap { def _1.url(*) "https://placekitten.com/280/200"; end } %> <% image = Spree::Image.new.tap { _1.define_singleton_method(:attachment) { attachment } } %> <% [ - Spree::PromotionAction.new, Spree::UnitCancel.new, Spree::TaxRate.new, image, diff --git a/legacy_promotions/spec/components/solidus_admin/ui/thumbnail/component_spec.rb b/legacy_promotions/spec/components/solidus_admin/ui/thumbnail/component_spec.rb new file mode 100644 index 00000000000..4190fb971a8 --- /dev/null +++ b/legacy_promotions/spec/components/solidus_admin/ui/thumbnail/component_spec.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe SolidusAdmin::UI::Thumbnail::Component, type: :component do + describe ".for" do + let(:promotion_action) { Spree::PromotionAction.new } + + subject { render_inline described_class.for(promotion_action) } + + it "displays a megaphone" do + subject + expect(page).to have_xpath("//use[contains(@*, '#ri-megaphone')]") + end + end +end diff --git a/legacy_promotions/spec/rails_helper.rb b/legacy_promotions/spec/rails_helper.rb index 6ed9b93cd59..46d34a77ebb 100644 --- a/legacy_promotions/spec/rails_helper.rb +++ b/legacy_promotions/spec/rails_helper.rb @@ -76,6 +76,10 @@ Capybara.javascript_driver = (ENV['CAPYBARA_DRIVER'] || :selenium_chrome_headless).to_sym +# VIEW COMPONENTS +Rails.application.config.view_component.test_controller = "SolidusAdmin::BaseController" +require "view_component/test_helpers" + RSpec.configure do |config| config.fixture_path = File.join(__dir__, "fixtures") @@ -101,6 +105,8 @@ DummyApp.use_solidus_admin = false end + config.include ViewComponent::TestHelpers, type: :component + config.include Spree::TestingSupport::JobHelpers config.include SolidusAdmin::TestingSupport::FeatureHelpers, type: :feature config.include FactoryBot::Syntax::Methods From 0c53451c99941aa6e3680422264d8e00dc75a322 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 11 Jun 2024 16:27:37 +0200 Subject: [PATCH 025/834] Allow Capybara to search for ARIA labels It's cumbersome to find elements by CSS selectors to click them, and the new admin has a few instances where we have no standalone label. --- admin/spec/spec_helper.rb | 1 + .../spec/features/solidus_admin/orders/index_spec.rb | 4 +++- legacy_promotions/spec/rails_helper.rb | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/admin/spec/spec_helper.rb b/admin/spec/spec_helper.rb index 8661c296e3a..f9d8250cba5 100644 --- a/admin/spec/spec_helper.rb +++ b/admin/spec/spec_helper.rb @@ -65,6 +65,7 @@ end Capybara.javascript_driver = (ENV['CAPYBARA_DRIVER'] || :selenium_chrome_headless).to_sym Capybara.default_max_wait_time = ENV['DEFAULT_MAX_WAIT_TIME'].to_f if ENV['DEFAULT_MAX_WAIT_TIME'].present? +Capybara.enable_aria_label = true # DATABASE CLEANER require 'database_cleaner' diff --git a/legacy_promotions/spec/features/solidus_admin/orders/index_spec.rb b/legacy_promotions/spec/features/solidus_admin/orders/index_spec.rb index 00e0adbb5c1..3dec15fffce 100644 --- a/legacy_promotions/spec/features/solidus_admin/orders/index_spec.rb +++ b/legacy_promotions/spec/features/solidus_admin/orders/index_spec.rb @@ -11,7 +11,9 @@ it "lists products", :js do visit "/admin/orders" - find("button[aria-label=Filter]").click + + click_button "Filter" + within("div[role=search]") do expect(page).to have_content("Promotions") find(:xpath, "//summary[normalize-space(text())='Promotions']").click diff --git a/legacy_promotions/spec/rails_helper.rb b/legacy_promotions/spec/rails_helper.rb index 6ed9b93cd59..dd9797bbaae 100644 --- a/legacy_promotions/spec/rails_helper.rb +++ b/legacy_promotions/spec/rails_helper.rb @@ -75,6 +75,7 @@ require 'axe-capybara' Capybara.javascript_driver = (ENV['CAPYBARA_DRIVER'] || :selenium_chrome_headless).to_sym +Capybara.enable_aria_label = true RSpec.configure do |config| config.fixture_path = File.join(__dir__, "fixtures") From 7937d0317b92f81bbf939edf3b064f56d8d55b6a Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 12 Jun 2024 09:21:13 +0200 Subject: [PATCH 026/834] Sleep for 1 second before clicking in order spec We need to wait for the JS to be loaded and connected here, otherwise the click doesn't do anything. There is an alternative fix for this in https://github.com/solidusio/solidus/pull/5782 --- .../spec/features/solidus_admin/orders/index_spec.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/legacy_promotions/spec/features/solidus_admin/orders/index_spec.rb b/legacy_promotions/spec/features/solidus_admin/orders/index_spec.rb index 3dec15fffce..6753742fb57 100644 --- a/legacy_promotions/spec/features/solidus_admin/orders/index_spec.rb +++ b/legacy_promotions/spec/features/solidus_admin/orders/index_spec.rb @@ -12,6 +12,8 @@ it "lists products", :js do visit "/admin/orders" + sleep 1 + click_button "Filter" within("div[role=search]") do From 040894297f55ff0941ab66a1e7449700a45d6a93 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 7 Jun 2024 07:53:50 +0200 Subject: [PATCH 027/834] Allow safe reloads of the component registry This adds a new `ComponentRegistry` class that allows: - setting component classes as strings (to allow for easy reloading) - getting nice errors when requesting components that don't exist - getting nice errors when setting a component to a non-existing class It also moves some of the logic from `SolidusAdmin::Configuration` into a separate class. I've decided not to add memoization here as I'm not sure this would make a giant difference performance-wise, and also the Rails Guide on Reloading constants very clearly says: ``` Bottom line: do not cache reloadable classes or modules. ``` --- admin/docs/customizing_components.md | 7 +--- admin/lib/solidus_admin/component_registry.rb | 40 ++++++++++++++++++ admin/lib/solidus_admin/configuration.rb | 21 +--------- .../solidus_admin/component_registry_spec.rb | 42 +++++++++++++++++++ .../lib/solidus_legacy_promotions/engine.rb | 4 +- 5 files changed, 86 insertions(+), 28 deletions(-) create mode 100644 admin/lib/solidus_admin/component_registry.rb create mode 100644 admin/spec/solidus_admin/component_registry_spec.rb diff --git a/admin/docs/customizing_components.md b/admin/docs/customizing_components.md index f95a1def3dc..e1c4a526818 100644 --- a/admin/docs/customizing_components.md +++ b/admin/docs/customizing_components.md @@ -100,14 +100,9 @@ end If you need more control, you can explicitly register your component in the Solidus Admin container instead of using an implicit path: -> ⓘ Right now, that will raise an error when the application is reloaded. We -> need to fix it. - ```ruby # config/initalizers/solidus_admin.rb -Rails.application.config.to_prepare do - SolidusAdmin::Config.components['ui/button'] = MyApplication::Button::Component -end +SolidusAdmin::Config.components['ui/button'] = "MyApplication::Button::Component" ``` ### Tweaking a component diff --git a/admin/lib/solidus_admin/component_registry.rb b/admin/lib/solidus_admin/component_registry.rb new file mode 100644 index 00000000000..b86789bbc02 --- /dev/null +++ b/admin/lib/solidus_admin/component_registry.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +module SolidusAdmin + class ComponentRegistry + ComponentNotFoundError = Class.new(NameError) + + def initialize + @names = {} + end + + def []=(key, value) + @names[key] = value + end + + def [](key) + if @names[key] + @names[key].constantize + else + infer_constant_from(key) + end + end + + private + + def infer_constant_from(key) + "solidus_admin/#{key}/component".classify.constantize + rescue NameError + prefix = "#{SolidusAdmin::Configuration::ENGINE_ROOT}/app/components/solidus_admin/" + suffix = "/component.rb" + dictionary = Dir["#{prefix}**#{suffix}"].map { _1.delete_prefix(prefix).delete_suffix(suffix) } + corrections = DidYouMean::SpellChecker.new(dictionary: dictionary).correct(key.to_s) + + raise ComponentNotFoundError.new( + "Unknown component #{key}#{DidYouMean.formatter.message_for(corrections)}", + key.classify, + receiver: ::SolidusAdmin + ) + end + end +end diff --git a/admin/lib/solidus_admin/configuration.rb b/admin/lib/solidus_admin/configuration.rb index 19f64617957..edbc1bb0af6 100644 --- a/admin/lib/solidus_admin/configuration.rb +++ b/admin/lib/solidus_admin/configuration.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'spree/preferences/configuration' +require 'solidus_admin/component_registry' module SolidusAdmin # Configuration for the admin interface. @@ -8,7 +9,6 @@ module SolidusAdmin # Ensure requiring this file after the Rails application has been created, # as some defaults depend on the application context. class Configuration < Spree::Preferences::Configuration - ComponentNotFoundError = Class.new(NameError) ENGINE_ROOT = File.expand_path("#{__dir__}/../..") # Path to the logo used in the admin interface. @@ -180,24 +180,7 @@ def import_menu_items_from_backend! end def components - @components ||= Hash.new do |_h, k| - const_name = "solidus_admin/#{k}/component".classify - - unless Object.const_defined?(const_name) - prefix = "#{ENGINE_ROOT}/app/components/solidus_admin/" - suffix = "/component.rb" - dictionary = Dir["#{prefix}**#{suffix}"].map { _1.delete_prefix(prefix).delete_suffix(suffix) } - corrections = DidYouMean::SpellChecker.new(dictionary: dictionary).correct(k.to_s) - - raise ComponentNotFoundError.new( - "Unknown component #{k}#{DidYouMean.formatter.message_for(corrections)}", - k.classify, - receiver: ::SolidusAdmin - ) - end - - const_name.constantize - end + @components ||= ComponentRegistry.new end # The method used to authenticate the user in the admin interface, it's expected to redirect the user to the login method diff --git a/admin/spec/solidus_admin/component_registry_spec.rb b/admin/spec/solidus_admin/component_registry_spec.rb new file mode 100644 index 00000000000..ae77d9d5981 --- /dev/null +++ b/admin/spec/solidus_admin/component_registry_spec.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SolidusAdmin::ComponentRegistry do + let(:registry) { described_class.new } + let(:key) { "ui/button" } + + subject { registry[key] } + + context "with a default class" do + it { is_expected.to eq(SolidusAdmin::UI::Button::Component) } + end + + context "with a spelling mistake" do + let(:key) { "ui/buton" } + + it "raises an understandable error" do + expect { subject }.to raise_error("Unknown component ui/buton\nDid you mean? ui/button") + end + end + + context "with a custom class" do + before do + # Using an existing class here so I don't have to define a new one. + # Extensions that use this should use their own. + registry["ui/button"] = "SolidusAdmin::UI::Panel::Component" + end + + it { is_expected.to eq(SolidusAdmin::UI::Panel::Component) } + end + + context "with a custom class with a spelling mistake" do + before do + registry["ui/button"] = "DoesNotExistClass" + end + + it "raises an NameError" do + expect { subject }.to raise_error("uninitialized constant DoesNotExistClass") + end + end +end diff --git a/legacy_promotions/lib/solidus_legacy_promotions/engine.rb b/legacy_promotions/lib/solidus_legacy_promotions/engine.rb index 4c31b5af913..48a90403573 100644 --- a/legacy_promotions/lib/solidus_legacy_promotions/engine.rb +++ b/legacy_promotions/lib/solidus_legacy_promotions/engine.rb @@ -31,9 +31,7 @@ class Engine < ::Rails::Engine initializer "solidus_legacy_promotions.add_admin_order_index_component" do if SolidusSupport.admin_available? - config.to_prepare do - SolidusAdmin::Config.components["orders/index"] = SolidusLegacyPromotions::Orders::Index::Component - end + SolidusAdmin::Config.components["orders/index"] = "SolidusLegacyPromotions::Orders::Index::Component" end end From 13f934616073e75a6632ba8d58028efa2a708521 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 12 Jun 2024 10:11:25 +0200 Subject: [PATCH 028/834] Add a README.md for solidus_legacy_promotions This is mostly taken from the edgeguides repo. It will need to be updated once all the models are moved, but at least we have something. --- legacy_promotions/README.md | 345 ++++++++++++++++++++++++++++++++++++ 1 file changed, 345 insertions(+) create mode 100644 legacy_promotions/README.md diff --git a/legacy_promotions/README.md b/legacy_promotions/README.md new file mode 100644 index 00000000000..b1fb3c4ca1c --- /dev/null +++ b/legacy_promotions/README.md @@ -0,0 +1,345 @@ +# Solidus Legacy Promotions + +A Rails Engine that contains the classic Spree/Solidus promotion system, extracted from the other Solidus gems. + +## Installation + +If your Gemfile contains the line `gem "solidus"`, this gem is automatically installed. If you require the individual parts of the Solidus suite, you need to add this gem to your Gemfile: + +```rb +gem "solidus_legacy_promotions" +``` + +This gem is slated for deprecation, as its name implies. We're working on integrating a new implementation for promotions and shipping it later this year. + +## Architecture overview + +Solidus Legacy Promotions ships with a powerful rule-based promotions system that allows you to grant flexible +discounts to your customers in many different scenarios. You can apply discounts to the entire +order, to a single line item or a set of line items, or to the shipping fees. + +In order to achieve this level of flexibility, the promotions system is composed of four concepts: + +* **Promotion handlers** are responsible for activating a promotion at the right step of the + customer experience. +* **Promotion rules** are responsible for checking whether an order is eligible for a promotion. +* **Promotion actions** are responsible for defining the discount(s) to be applied to eligible + orders. +* **Adjustments** are responsible for storing discount information. Promotion adjustments are + recalculated every time the order is updated, to check if their eligibility persists when the + state of the order changes. It is possible to + [customize how this recalculation behaves][how-to-use-a-custom-promotion-adjuster]. + +> [!NOTE] +> Adjustments go beyond promotions and apply to other concepts that modify the order amount. +> Taxes are another good example. + +Let's take the example of the following promotion: + +> Apply free shipping on any orders whose total is $100 USD or greater. + +Here's the flow Solidus follows to apply such a promotion: + +1. When the customer enters their shipping information, + the [`Shipping`](https://github.com/solidusio/solidus/blob/64b6b6eaf902337983c487cf10dfada8dbfc5160/core/app/models/spree/promotion\_handler/shipping.rb) + promotion handler activates the promotion on the order. +2. When activated, the promotion will perform + some [basic eligibility checks](https://github.com/solidusio/solidus/blob/64b6b6eaf902337983c487cf10dfada8dbfc5160/core/app/models/spree/promotion.rb#L149) ( + e.g. usage limit, validity dates) and + then [ensure the defined promotion rules are met.](https://github.com/solidusio/solidus/blob/64b6b6eaf902337983c487cf10dfada8dbfc5160/core/app/models/spree/promotion.rb#L149) +3. When called, + the [`ItemTotal`](https://github.com/solidusio/solidus/blob/64b6b6eaf902337983c487cf10dfada8dbfc5160/core/app/models/spree/promotion/rules/item\_total.rb) + promotion rule will ensure the order's total is $100 USD or greater. +4. Since the order is eligible for the promotion, + the [`FreeShipping`](https://github.com/solidusio/solidus/blob/64b6b6eaf902337983c487cf10dfada8dbfc5160/core/app/models/spree/promotion/actions/free\_shipping.rb) + action is applied to the order's shipment. The action creates an adjustment that cancels the cost + of the shipment. +5. The customer gets free shipping! + +This is the architecture at a glance. As you can see, Solidus already ships with some useful +handlers, rules, and actions out of the box. + +However, you're not limited to using the stock functionality. In fact, the promotions system shows +its full potential when you use it to implement your own logic. In the rest of the guide, we'll use +the promotions system to implement the following requirements: + +> We want to uphold a partnership with a new payment platform by offering a 50% shipping discount +> when customers pay with it during the checkout. + +In order to do this, we'll have to implement our own handler, rule, and action. Let's get to work! + +## Implementing a new handler + +There's nothing special about promotion handlers: technically, they're just plain old Ruby objects +that are created and called in the right places during the checkout flow. + +There is no unified API for promotion handlers, but we can take inspiration from +the [existing ones](https://github.com/solidusio/solidus/tree/64b6b6eaf902337983c487cf10dfada8dbfc5160/core/app/models/spree/promotion\_handler) +and use a similar format: + +```ruby title="app/models/amazing_store/promotion_handler/payment.rb" +# frozen_string_literal: true + +module AmazingStore + module PromotionHandler + class Payment + RULES_TYPE = 'AmazingStore::Promotion::Rules::Payment' + + attr_reader :order + + def initialize(order) + @order = order + end + + def activate + promotions.each do |promotion| + promotion.activate(order: order) if promotion.eligible?(order) + end + end + + private + + def promotions + ::Spree::Promotion. + active. + joins(:promotion_rules). + where('promotion_rules.type' => RULES_TYPE) + end + end + end +end +``` + +Our promotion handler selects a subset of promotions with a specific rule type that we haven't yet +created. Then, it activates the eligible ones, i.e., those who obey its rules. + +Remember that promotion handlers simply apply active promotions to the current order at the correct +stage of the order workflow. While other handlers might pick up our promotions, they won't be able +to activate it if they run before the payment step. With the new handler, we want to ensure that +promotions can be activated after a payment method has been selected for the order. + +Let's call our handler as a callback after the checkout flow has transitioned from the `:payment` +state (see +the [section on how to customize state machines](state-machines.mdx#customizing-core-behavior)): + +```ruby title="app/overrides/amazing_store/load_payment_promotion_handler.rb" +# frozen_string_literal: true + +module AmazingStore + module LoadPaymentPromotionHandler + def self.prepended(base) + base.state_machine.after_transition(from: :payment) do |order| + AmazingStore::PromotionHandler::Payment.new(order).activate + end + end + + ::Spree::Order.prepend(self) + end +end +``` + +## Implementing a new rule + +Now that we have our handler, let's move on and implement the promotion rule that checks whether the +customer is using the promoted payment method. + +We'll allow store admins to edit which payment method carries the discount. The best way to do that +is to create a preference for the promotion rule itself: + +```ruby title="app/models/amazing_store/promotion/rules/payment.rb" +# frozen_string_literal: true + +module AmazingStore + module Promotion + module Rules + class Payment < ::Spree::PromotionRule + DEFAULT_PREFERRED_PAYMENT_TYPE = 'AmazingStore::AmazingPaymentPlatform' + + ALLOWED_PAYMENT_TYPES = [ + DEFAULT_PREFERRED_PAYMENT_TYPE, + 'Spree::PaymentMethod::Check', + 'Spree::PaymentMethod::CreditCard' + ].freeze + + preference :payment_type, :string, default: DEFAULT_PREFERRED_PAYMENT_TYPE + + validates :preferred_payment_type, inclusion: { + in: ALLOWED_PAYMENT_TYPES, + allow_blank: true + }, on: :update + + def applicable?(promotable) + promotable.is_a?(::Spree::Order) + end + + def eligible?(order, _options = {}) + order.payments.any? do |payment| + payment.payment_method.type == preferred_payment_type + end + end + end + end + end +end +``` + +> [!CAUTION] +> You may have noticed that we allow the payment type to be blank on creation. This is because +> promotion rules are initially created without any of their preferences, so that the correct form can +> be presented to the admin when configuring the rule. If we enforced the presence of a payment type +> since the very beginning, Solidus wouldn't be able to create the promotion rule and admins would get +> an error. + +Now that we have the implementation of our promotion rule, we also need to give admins a nice UI +where they can manage the rule and enter the promoted payment type. We just need to create the right +partial, where we'll have a local variable `promotion_rule` available to access the current +promotion rule instance: + +```markup title="app/views/spree/admin/promotions/rules/_payment.html.erb" +
+
+
+ <%= promotion_rule.class.human_attribute_name(:payment_type) %> +
+
+
+
+ <%= select_tag "#{param_prefix}[preferred_payment_type]", options_for_select(promotion_rule.class::ALLOWED_PAYMENT_TYPES, promotion_rule.preferred_payment_type), class: 'fullwidth' %> +
+
+
+``` + +The last step is to register our new promotion rule in an initializer: + +```ruby title="config/initializers/promotions.rb" +# ... +Rails.application.config.spree.promotions.rules << 'AmazingStore::Promotion::Rules::Payment' +``` + +When you create a new promotion in the backend, we should now see the _Payment_ promotion rule. For +a better experience, we can associate a description so that it's rendered along its form: + +```yaml title="config/locales/en.yml" +en: + # ... + activerecord: + attributes: + amazing_store/promotion/rules/payment: + description: Must use the specified payment method +``` + +## Implementing a new action + +Finally, let's implement the promotion action that will grant customers a 50% shipping discount. In +order to do that, we can take inspiration from the +existing [`FreeShipping`](https://github.com/solidusio/solidus/blob/master/core/app/models/spree/promotion/actions/free\_shipping.rb) +action: + +```ruby title="app/models/amazing_store/promotion/actions/half_shipping.rb" +# frozen_string_literal: true + +module AmazingStore + module Promotion + module Actions + class HalfShipping < ::Spree::PromotionAction + # The `perform` method is called when an action is applied to an order or line + # item. The payload contains a lot of useful context: + # https://github.com/solidusio/solidus/blob/64b6b6eaf902337983c487cf10dfada8dbfc5160/core/app/models/spree/promotion.rb#L129 + def perform(payload = {}) + order = payload[:order] + promotion_code = payload[:promotion_code] + + results = order.shipments.map do |shipment| + # If the shipment has already been discounted by this promotion action, + # we skip it. + next false if shipment.adjustments.where(source: self).exists? + + # If not, we create an adjustment to apply a 50% discount on the shipment. + shipment.adjustments.create!( + order: shipment.order, + amount: compute_amount(shipment), + source: self, + promotion_code: promotion_code, + label: promotion.name, + ) + + # We return true here to mark that the shipment has been discounted. + true + end + + # `perform` needs to return true if any adjustments have been applied by + # the promotion action. Otherwise, it should return false. + results.any? { |result| result == true } + end + + def compute_amount(shipment) + shipment.cost * -0.5 + end + + # The `remove_from` method should undo any actions done by `perform`. It is + # used when an order becomes ineligible for a given promotion and the promotion + # needs to be removed. + def remove_from(order) + order.shipments.each do |shipment| + shipment.adjustments.each do |adjustment| + if adjustment.source == self + # Here, we simply remove any adjustments on the order's shipments + # created by this promotion action. + shipment.adjustments.destroy!(adjustment) + end + end + end + end + end + end + end +end +``` + +As you can see, there's quite a bit going on here, but hopefully, the comments help you with the +flow of the action and the purpose of the methods we implemented. + +Just like rules, promotion actions can also have preferences and allow admin to define them via the +UI. However, in this case, we don't need any of that. Still, Solidus will expect a partial for the +action, so we should create an empty ERB file. + +```erb title="app/views/spree/admin/promotions/actions/_half_shipping.html.erb" + +``` + +> [!TIP] +> You can look at +? the [`CreateQuantityAdjustments`](https://github.com/solidusio/solidus/blob/64b6b6eaf902337983c487cf10dfada8dbfc5160/core/app/models/spree/promotion/actions/create\_quantity\_adjustments.rb) +> action and +> the [corresponding view](https://github.com/solidusio/solidus/blob/64b6b6eaf902337983c487cf10dfada8dbfc5160/backend/app/views/spree/admin/promotions/actions/\_create\_quantity\_adjustments.html.erb) +for an example of actions with preferences. + +Finally, we need to register our action by adding the following to an initializer: + +```ruby title="config/initializers/promotions.rb" +# ... +Rails.application.config.spree.promotions.actions << 'AmazingStore::Promotion::Actions::HalfShipping' +``` + +Like before, let's add a human-friendly description: + +```yaml title="config/locales/en.yml" +en: + # ... + activerecord: + attributes: + amazing_store/promotion/actions/half_shipping: + description: Applies 50% discount in shipping +``` + +Restart the server and you should now see your new promotion action! + +Let's try it out! + +First of all, go to the _Promotions_ section on the backend and click _New Promotion_. In this case, +it makes sense to check the _Apply to all orders_ option, as our promotion doesn't need a code. Once +the promotion has been created, add the _Payment_ rule and the _Half shipping_ action. + +You can now go to the frontend and see how the shipment price is dropped by 50% if you select the +configured payment method. From cc09cfabeabede4bdcc6eb5eed092cc0c542b35a Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 12 Jun 2024 14:28:16 +0200 Subject: [PATCH 029/834] Move PromotionCodeBatchBuilder to solidus_legacy_promotions --- .rubocop_todo.yml | 1 - .../app/models/spree/promotion_code/batch_builder.rb | 1 + .../spec/models/spree/promotion_code/batch_builder_spec.rb | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename {core => legacy_promotions}/app/models/spree/promotion_code/batch_builder.rb (99%) rename {core => legacy_promotions}/spec/models/spree/promotion_code/batch_builder_spec.rb (100%) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index b8d47b2e57c..0157596d7d5 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -90,7 +90,6 @@ Layout/EmptyLinesAroundAttributeAccessor: - "core/app/models/spree/address/state_validator.rb" - "core/app/models/spree/order_updater.rb" - "core/app/models/spree/promotion/order_adjustments_recalculator.rb" - - "core/app/models/spree/promotion_code/batch_builder.rb" - "core/app/models/spree/stock_quantities.rb" - "core/app/models/spree/variant.rb" - "core/lib/spree/app_configuration.rb" diff --git a/core/app/models/spree/promotion_code/batch_builder.rb b/legacy_promotions/app/models/spree/promotion_code/batch_builder.rb similarity index 99% rename from core/app/models/spree/promotion_code/batch_builder.rb rename to legacy_promotions/app/models/spree/promotion_code/batch_builder.rb index 26fa1d63745..60eb7873bba 100644 --- a/core/app/models/spree/promotion_code/batch_builder.rb +++ b/legacy_promotions/app/models/spree/promotion_code/batch_builder.rb @@ -2,6 +2,7 @@ class ::Spree::PromotionCode::BatchBuilder attr_reader :promotion_code_batch, :options + delegate :promotion, :number_of_codes, :base_code, to: :promotion_code_batch DEFAULT_OPTIONS = { diff --git a/core/spec/models/spree/promotion_code/batch_builder_spec.rb b/legacy_promotions/spec/models/spree/promotion_code/batch_builder_spec.rb similarity index 100% rename from core/spec/models/spree/promotion_code/batch_builder_spec.rb rename to legacy_promotions/spec/models/spree/promotion_code/batch_builder_spec.rb From 6d0a975e2af80efc0c03b0b5786aac1df7c45724 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 12 Jun 2024 13:03:33 +0200 Subject: [PATCH 030/834] Move PromotionCodeBatch#process to solidus_legacy_promotions We need to temporarily freedom-patch Spree::PromotionCodeBatch, because the model class needs to stay in Solidus Core, but the tests, jobs and mailers are already in `solidus_legacy_promotions`. Once everything is moved, we can merge the model and the decorator again. --- core/app/models/spree/promotion_code_batch.rb | 9 --------- .../spree_promotion_code_batch_decorator.rb | 16 ++++++++++++++++ .../models/spree/promotion_code_batch_spec.rb | 0 3 files changed, 16 insertions(+), 9 deletions(-) create mode 100644 legacy_promotions/app/decorators/solidus_legacy_promotions/models/spree_promotion_code_batch_decorator.rb rename {core => legacy_promotions}/spec/models/spree/promotion_code_batch_spec.rb (100%) diff --git a/core/app/models/spree/promotion_code_batch.rb b/core/app/models/spree/promotion_code_batch.rb index 85a17be14be..66cbcc50d6a 100644 --- a/core/app/models/spree/promotion_code_batch.rb +++ b/core/app/models/spree/promotion_code_batch.rb @@ -14,14 +14,5 @@ class CantProcessStartedBatch < StandardError def finished? state == "completed" end - - def process - if state == "pending" - update!(state: "processing") - PromotionCodeBatchJob.perform_later(self) - else - raise CantProcessStartedBatch.new("Batch #{id} already started") - end - end end end diff --git a/legacy_promotions/app/decorators/solidus_legacy_promotions/models/spree_promotion_code_batch_decorator.rb b/legacy_promotions/app/decorators/solidus_legacy_promotions/models/spree_promotion_code_batch_decorator.rb new file mode 100644 index 00000000000..fdb8c85454b --- /dev/null +++ b/legacy_promotions/app/decorators/solidus_legacy_promotions/models/spree_promotion_code_batch_decorator.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module SolidusLegacyPromotions + module SpreePromotionCodeBatchDecorator + def process + if state == "pending" + update!(state: "processing") + Spree::PromotionCodeBatchJob.perform_later(self) + else + raise Spree::PromotionCodeBatch::CantProcessStartedBatch.new("Batch #{id} already started") + end + end + + Spree::PromotionCodeBatch.prepend(self) + end +end diff --git a/core/spec/models/spree/promotion_code_batch_spec.rb b/legacy_promotions/spec/models/spree/promotion_code_batch_spec.rb similarity index 100% rename from core/spec/models/spree/promotion_code_batch_spec.rb rename to legacy_promotions/spec/models/spree/promotion_code_batch_spec.rb From 83304b5df47767b0b95908546b92d84f2d6292bf Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 12 Jun 2024 10:37:31 +0200 Subject: [PATCH 031/834] Move PromotionCodeBatchMailer to solidus_legacy_promotions --- .../app/mailers/spree/promotion_code_batch_mailer.rb | 0 .../promotion_code_batch_errored.text.erb | 0 .../promotion_code_batch_finished.text.erb | 0 .../spec/mailers/spree}/promotion_code_batch_mailer_spec.rb | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename {core => legacy_promotions}/app/mailers/spree/promotion_code_batch_mailer.rb (100%) rename {core => legacy_promotions}/app/views/spree/promotion_code_batch_mailer/promotion_code_batch_errored.text.erb (100%) rename {core => legacy_promotions}/app/views/spree/promotion_code_batch_mailer/promotion_code_batch_finished.text.erb (100%) rename {core/spec/mailers => legacy_promotions/spec/mailers/spree}/promotion_code_batch_mailer_spec.rb (100%) diff --git a/core/app/mailers/spree/promotion_code_batch_mailer.rb b/legacy_promotions/app/mailers/spree/promotion_code_batch_mailer.rb similarity index 100% rename from core/app/mailers/spree/promotion_code_batch_mailer.rb rename to legacy_promotions/app/mailers/spree/promotion_code_batch_mailer.rb diff --git a/core/app/views/spree/promotion_code_batch_mailer/promotion_code_batch_errored.text.erb b/legacy_promotions/app/views/spree/promotion_code_batch_mailer/promotion_code_batch_errored.text.erb similarity index 100% rename from core/app/views/spree/promotion_code_batch_mailer/promotion_code_batch_errored.text.erb rename to legacy_promotions/app/views/spree/promotion_code_batch_mailer/promotion_code_batch_errored.text.erb diff --git a/core/app/views/spree/promotion_code_batch_mailer/promotion_code_batch_finished.text.erb b/legacy_promotions/app/views/spree/promotion_code_batch_mailer/promotion_code_batch_finished.text.erb similarity index 100% rename from core/app/views/spree/promotion_code_batch_mailer/promotion_code_batch_finished.text.erb rename to legacy_promotions/app/views/spree/promotion_code_batch_mailer/promotion_code_batch_finished.text.erb diff --git a/core/spec/mailers/promotion_code_batch_mailer_spec.rb b/legacy_promotions/spec/mailers/spree/promotion_code_batch_mailer_spec.rb similarity index 100% rename from core/spec/mailers/promotion_code_batch_mailer_spec.rb rename to legacy_promotions/spec/mailers/spree/promotion_code_batch_mailer_spec.rb From d293d0d982ffe659f8d85603b08fd4a1e2bb4585 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 12 Jun 2024 10:39:42 +0200 Subject: [PATCH 032/834] Move PromotionCodeBatchJob to legacy_promotions --- .rubocop_todo.yml | 2 +- .../app/jobs/spree/promotion_code_batch_job.rb | 0 .../spec/jobs/spree}/promotion_code_batch_job_spec.rb | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename {core => legacy_promotions}/app/jobs/spree/promotion_code_batch_job.rb (100%) rename {core/spec/jobs => legacy_promotions/spec/jobs/spree}/promotion_code_batch_job_spec.rb (100%) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 0157596d7d5..7a70952dbfa 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -310,7 +310,7 @@ Rails/ApplicationController: # This cop supports unsafe autocorrection (--autocorrect-all). Rails/ApplicationJob: Exclude: - - "core/app/jobs/spree/promotion_code_batch_job.rb" + - "legacy_promotions/app/jobs/spree/promotion_code_batch_job.rb" # Offense count: 1 # This cop supports unsafe autocorrection (--autocorrect-all). diff --git a/core/app/jobs/spree/promotion_code_batch_job.rb b/legacy_promotions/app/jobs/spree/promotion_code_batch_job.rb similarity index 100% rename from core/app/jobs/spree/promotion_code_batch_job.rb rename to legacy_promotions/app/jobs/spree/promotion_code_batch_job.rb diff --git a/core/spec/jobs/promotion_code_batch_job_spec.rb b/legacy_promotions/spec/jobs/spree/promotion_code_batch_job_spec.rb similarity index 100% rename from core/spec/jobs/promotion_code_batch_job_spec.rb rename to legacy_promotions/spec/jobs/spree/promotion_code_batch_job_spec.rb From ca574c9ea7c39ea42726e01f0d7c6c134ef58f1e Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 13 Jun 2024 15:58:40 +0200 Subject: [PATCH 033/834] SolidusAdmin: Components per Adjustment Source This adds a few components that just display adjustment sources. There are no visual changes. The improvement I was looking for is to de-couple the adjustments table from individual source types through dynamic component lookup: ``` component_name = adjustment.source&.class&.table_name&.singularize component_key = ["orders/show/adjustments/index/adjustment", component_name].compact.join("/") render component(component_key).new(adjustment) ``` This allows the `solidus_legacy_promotions` gem to ship with a component that knows how to render an adjustment source of type `Spree::PromotionAction` without hardcoding anything in `SolidusAdmin`. --- .../index/adjustment/component.html.erb | 12 +++ .../adjustments/index/adjustment/component.rb | 14 ++++ .../adjustment/spree_tax_rate/component.rb | 13 ++++ .../adjustment/spree_unit_cancel/component.rb | 4 + .../show/adjustments/index/component.rb | 26 +------ .../spec/features/orders/adjustments_spec.rb | 76 ++++++++++++++----- .../spree_promotion_action/component.rb | 13 ++++ .../orders/show/adjustments_spec.rb | 36 +++++++++ 8 files changed, 151 insertions(+), 43 deletions(-) create mode 100644 admin/app/components/solidus_admin/orders/show/adjustments/index/adjustment/component.html.erb create mode 100644 admin/app/components/solidus_admin/orders/show/adjustments/index/adjustment/component.rb create mode 100644 admin/app/components/solidus_admin/orders/show/adjustments/index/adjustment/spree_tax_rate/component.rb create mode 100644 admin/app/components/solidus_admin/orders/show/adjustments/index/adjustment/spree_unit_cancel/component.rb create mode 100644 legacy_promotions/lib/components/admin/solidus_admin/orders/show/adjustments/index/adjustment/spree_promotion_action/component.rb create mode 100644 legacy_promotions/spec/features/solidus_admin/orders/show/adjustments_spec.rb diff --git a/admin/app/components/solidus_admin/orders/show/adjustments/index/adjustment/component.html.erb b/admin/app/components/solidus_admin/orders/show/adjustments/index/adjustment/component.html.erb new file mode 100644 index 00000000000..16dcd17d1b9 --- /dev/null +++ b/admin/app/components/solidus_admin/orders/show/adjustments/index/adjustment/component.html.erb @@ -0,0 +1,12 @@ +
+
+
+ <%= adjustment.label %> +
+ <% if detail %> +
+ <%= detail %> +
+ <% end %> +
+
diff --git a/admin/app/components/solidus_admin/orders/show/adjustments/index/adjustment/component.rb b/admin/app/components/solidus_admin/orders/show/adjustments/index/adjustment/component.rb new file mode 100644 index 00000000000..f5a55798567 --- /dev/null +++ b/admin/app/components/solidus_admin/orders/show/adjustments/index/adjustment/component.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +class SolidusAdmin::Orders::Show::Adjustments::Index::Adjustment::Component < SolidusAdmin::BaseComponent + attr_reader :adjustment, :source, :model_name + + def initialize(adjustment) + @adjustment = adjustment + @source = adjustment.source + @model_name = source&.model_name&.human + end + + def detail + end +end diff --git a/admin/app/components/solidus_admin/orders/show/adjustments/index/adjustment/spree_tax_rate/component.rb b/admin/app/components/solidus_admin/orders/show/adjustments/index/adjustment/spree_tax_rate/component.rb new file mode 100644 index 00000000000..bd2b3a59c5a --- /dev/null +++ b/admin/app/components/solidus_admin/orders/show/adjustments/index/adjustment/spree_tax_rate/component.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class SolidusAdmin::Orders::Show::Adjustments::Index::Adjustment::SpreeTaxRate::Component < SolidusAdmin::Orders::Show::Adjustments::Index::Adjustment::Component + def detail + link_to("#{model_name}: #{zone_name}", spree.edit_admin_tax_rate_path(adjustment.source_id), class: "body-link") + end + + private + + def zone_name + source.zone&.name || t('spree.all_zones') + end +end diff --git a/admin/app/components/solidus_admin/orders/show/adjustments/index/adjustment/spree_unit_cancel/component.rb b/admin/app/components/solidus_admin/orders/show/adjustments/index/adjustment/spree_unit_cancel/component.rb new file mode 100644 index 00000000000..cf19bd49b58 --- /dev/null +++ b/admin/app/components/solidus_admin/orders/show/adjustments/index/adjustment/spree_unit_cancel/component.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +class SolidusAdmin::Orders::Show::Adjustments::Index::Adjustment::SpreeUnitCancel::Component < SolidusAdmin::Orders::Show::Adjustments::Index::Adjustment::Component +end diff --git a/admin/app/components/solidus_admin/orders/show/adjustments/index/component.rb b/admin/app/components/solidus_admin/orders/show/adjustments/index/component.rb index 286efe70d6a..dea11e9f369 100644 --- a/admin/app/components/solidus_admin/orders/show/adjustments/index/component.rb +++ b/admin/app/components/solidus_admin/orders/show/adjustments/index/component.rb @@ -83,9 +83,9 @@ def columns header: :source, col: { class: "w-56" }, data: ->(adjustment) { - tag.figure(safe_join([ - figcaption_for_source(adjustment), - ]), class: "flex items-center gap-2") + component_name = adjustment.source&.class&.table_name&.singularize + component_key = ["orders/show/adjustments/index/adjustment", component_name].compact.join("/") + render component(component_key).new(adjustment) } }, { @@ -150,26 +150,6 @@ def svg_data_uri(data) "data:image/svg+xml;base64,#{Base64.strict_encode64(data)}" end - def figcaption_for_source(adjustment) - return thumbnail_caption(adjustment.label, nil) unless adjustment.source_type - - # ["Spree::PromotionAction", nil, "Spree::UnitCancel", "Spree::TaxRate"] - record = adjustment.source - record_class = adjustment.source_type&.constantize - model_name = record_class.model_name.human - - case record || record_class - when Spree::TaxRate - detail = link_to("#{model_name}: #{record.zone&.name || t('spree.all_zones')}", spree.edit_admin_tax_rate_path(adjustment.source_id), class: "body-link") - when Spree::PromotionAction - detail = link_to("#{model_name}: #{record.promotion.name}", spree.edit_admin_promotion_path(adjustment.source_id), class: "body-link") - else - detail = "#{model_name}: #{record.display_amount}" if record.respond_to?(:display_amount) - end - - thumbnail_caption(adjustment.label, detail) - end - def figcaption_for_adjustable(adjustment) # ["Spree::LineItem", "Spree::Order", "Spree::Shipment"] record = adjustment.adjustable diff --git a/admin/spec/features/orders/adjustments_spec.rb b/admin/spec/features/orders/adjustments_spec.rb index 65251226733..c9219ef8a7a 100644 --- a/admin/spec/features/orders/adjustments_spec.rb +++ b/admin/spec/features/orders/adjustments_spec.rb @@ -3,29 +3,28 @@ require 'spec_helper' describe "Order", :js, type: :feature do - before { sign_in create(:admin_user, email: 'admin@example.com') } + let(:order) { create(:order, number: "R123456789") } - it "allows detaching a customer from an order" do + before do allow(SolidusAdmin::Config).to receive(:enable_alpha_features?) { true } + sign_in create(:admin_user, email: 'admin@example.com') + end - order = create(:order, number: "R123456789", user: create(:user)) - Spree::Adjustment.insert_all([ - { - order_id: order.id, - adjustable_id: order.id, - adjustable_type: "Spree::Order", - amount: 10, - label: "Test Adjustment", - eligible: true, - finalized: false, - created_at: Time.current, - updated_at: Time.current, - included: false, - source_type: "Spree::Order", - source_id: order.id, - promotion_code_id: nil, - }, - ]) + it "allows locking and unlocking adjustments" do + taxrate = create(:tax_rate) + Spree::Adjustment.create( + order: order, + adjustable: order, + amount: 10, + label: "Test Adjustment", + eligible: true, + finalized: false, + created_at: Time.current, + updated_at: Time.current, + included: false, + source: taxrate, + promotion_code_id: nil, + ) visit "/admin/orders/R123456789" click_on "Adjustments" @@ -49,4 +48,41 @@ expect(page).to be_axe_clean end + + it "can display an adjustment without a source" do + Spree::Adjustment.create( + order: order, + adjustable: order, + amount: 10, + label: "No Source Adjustment", + eligible: true, + finalized: false, + created_at: Time.current, + updated_at: Time.current, + included: false, + source: nil, + promotion_code_id: nil, + ) + visit "/admin/orders/R123456789" + + click_on "Adjustments" + expect(page).to have_content("No Source Adjustment") + expect(page).to be_axe_clean + end + + context "with a unit cancellation" do + let(:order) { create(:order_ready_to_ship, number: "R123456789") } + + before do + Spree::OrderCancellations.new(order).short_ship([order.inventory_units.first]) + end + + it "can display an adjustment with a unit cancellation" do + visit "/admin/orders/R123456789" + + click_on "Adjustments" + expect(page).to have_content("Cancellation - Short Ship") + expect(page).to be_axe_clean + end + end end diff --git a/legacy_promotions/lib/components/admin/solidus_admin/orders/show/adjustments/index/adjustment/spree_promotion_action/component.rb b/legacy_promotions/lib/components/admin/solidus_admin/orders/show/adjustments/index/adjustment/spree_promotion_action/component.rb new file mode 100644 index 00000000000..aec27a1435b --- /dev/null +++ b/legacy_promotions/lib/components/admin/solidus_admin/orders/show/adjustments/index/adjustment/spree_promotion_action/component.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class SolidusAdmin::Orders::Show::Adjustments::Index::Adjustment::SpreePromotionAction::Component < SolidusAdmin::Orders::Show::Adjustments::Index::Adjustment::Component + def detail + link_to("#{model_name}: #{promotion_name}", spree.edit_admin_promotion_path(adjustment.source_id), class: "body-link") + end + + private + + def promotion_name + source.promotion.name + end +end diff --git a/legacy_promotions/spec/features/solidus_admin/orders/show/adjustments_spec.rb b/legacy_promotions/spec/features/solidus_admin/orders/show/adjustments_spec.rb new file mode 100644 index 00000000000..6b27dc8852b --- /dev/null +++ b/legacy_promotions/spec/features/solidus_admin/orders/show/adjustments_spec.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe "Order", :js, type: :feature, solidus_admin: true do + let(:admin) { create(:admin_user) } + let(:order) { create(:order, number: "R123456789") } + + before do + allow(SolidusAdmin::Config).to receive(:enable_alpha_features?) { true } + sign_in admin + end + + context "with a promotion adjustment" do + let(:order) { create(:order_ready_to_ship, number: "R123456789") } + let(:promotion) { create(:promotion, :with_adjustable_action) } + + before do + Spree::Adjustment.create!( + order: order, + source: promotion.actions.first, + adjustable: order.line_items.first, + amount: 2, + label: "Promotion Adjustment" + ) + end + + it "can display the adjustment" do + visit "/admin/orders/R123456789" + + click_on "Adjustments" + expect(page).to have_content("Promotion Adjustment") + expect(page).to be_axe_clean + end + end +end From 79abe4d16ff1e9763364df5e8ef5ad2f02c43f4b Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 14 Jun 2024 10:14:04 +0200 Subject: [PATCH 034/834] Test against Ruby 3.3.2 Ruby 3.3.2 just came out, and there seems to be an issue with the now-included `net-pop` gem. Let's test against 3.3.2 for the time being. --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 26386f76164..a158820b122 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -380,4 +380,4 @@ workflows: matrix: { parameters: { rails: ['7.0'], ruby: ['3.2'], database: ['sqlite'], paperclip: [false] } } - test_solidus: name: *name - matrix: { parameters: { rails: ['7.1', 'main'], ruby: ['3.3'], database: ['sqlite'], paperclip: [false] } } + matrix: { parameters: { rails: ['7.1', 'main'], ruby: ['3.3.2'], database: ['sqlite'], paperclip: [false] } } From 52c8b7c41143c352b077e1ca4a063f676b90b0f9 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 14 Jun 2024 09:54:09 +0200 Subject: [PATCH 035/834] Mark flaky spec as flaky instead of sleep This marks the legacy promotions order index spec as flaky so that we get two retries if it fails. Better than just sending the process to sleep for a second. --- .../spec/features/solidus_admin/orders/index_spec.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/legacy_promotions/spec/features/solidus_admin/orders/index_spec.rb b/legacy_promotions/spec/features/solidus_admin/orders/index_spec.rb index 6753742fb57..2c7157050d1 100644 --- a/legacy_promotions/spec/features/solidus_admin/orders/index_spec.rb +++ b/legacy_promotions/spec/features/solidus_admin/orders/index_spec.rb @@ -9,11 +9,9 @@ before { sign_in create(:admin_user, email: "admin@example.com") } - it "lists products", :js do + it "lists products", :js, :flaky do visit "/admin/orders" - sleep 1 - click_button "Filter" within("div[role=search]") do From bbd3ec7638d3d11735b12fe358d81cf73492c9aa Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Mon, 19 Feb 2024 14:42:37 +0100 Subject: [PATCH 036/834] Move LegacyPromotions::Configuration into solidus_legacy_promotions --- .../lib/solidus_legacy_promotions/configuration.rb | 0 .../spec/lib/solidus_legacy_promotions/configuration_spec.rb | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {core => legacy_promotions}/lib/solidus_legacy_promotions/configuration.rb (100%) rename {core => legacy_promotions}/spec/lib/solidus_legacy_promotions/configuration_spec.rb (100%) diff --git a/core/lib/solidus_legacy_promotions/configuration.rb b/legacy_promotions/lib/solidus_legacy_promotions/configuration.rb similarity index 100% rename from core/lib/solidus_legacy_promotions/configuration.rb rename to legacy_promotions/lib/solidus_legacy_promotions/configuration.rb diff --git a/core/spec/lib/solidus_legacy_promotions/configuration_spec.rb b/legacy_promotions/spec/lib/solidus_legacy_promotions/configuration_spec.rb similarity index 100% rename from core/spec/lib/solidus_legacy_promotions/configuration_spec.rb rename to legacy_promotions/spec/lib/solidus_legacy_promotions/configuration_spec.rb From fa5d8d43df93139154d1834506944eb350f7e0f7 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 12 Jun 2024 10:16:57 +0200 Subject: [PATCH 037/834] Move Promotion Finder to legacy promotions gem --- {core => legacy_promotions}/app/models/spree/promotion_finder.rb | 0 .../spec/models/spree/promotion_finder_spec.rb | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {core => legacy_promotions}/app/models/spree/promotion_finder.rb (100%) rename {core => legacy_promotions}/spec/models/spree/promotion_finder_spec.rb (100%) diff --git a/core/app/models/spree/promotion_finder.rb b/legacy_promotions/app/models/spree/promotion_finder.rb similarity index 100% rename from core/app/models/spree/promotion_finder.rb rename to legacy_promotions/app/models/spree/promotion_finder.rb diff --git a/core/spec/models/spree/promotion_finder_spec.rb b/legacy_promotions/spec/models/spree/promotion_finder_spec.rb similarity index 100% rename from core/spec/models/spree/promotion_finder_spec.rb rename to legacy_promotions/spec/models/spree/promotion_finder_spec.rb From cda884aba429d102ec579a09a2f070ab22f7b7a8 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 12 Jun 2024 10:18:14 +0200 Subject: [PATCH 038/834] Move Promotion Advertiser to solidus_legacy_promotions This also fixes the description of the `NullPromotionAdvertiser`. --- core/spec/models/spree/null_promotion_advertiser_spec.rb | 2 +- .../app/models/spree/promotion_advertiser.rb | 0 .../spec/models/spree/promotion_advertiser_spec.rb | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename {core => legacy_promotions}/app/models/spree/promotion_advertiser.rb (100%) rename {core => legacy_promotions}/spec/models/spree/promotion_advertiser_spec.rb (100%) diff --git a/core/spec/models/spree/null_promotion_advertiser_spec.rb b/core/spec/models/spree/null_promotion_advertiser_spec.rb index 34878bb47b3..09ab3303d8e 100644 --- a/core/spec/models/spree/null_promotion_advertiser_spec.rb +++ b/core/spec/models/spree/null_promotion_advertiser_spec.rb @@ -2,7 +2,7 @@ require "rails_helper" -RSpec.describe Spree::PromotionAdvertiser, type: :model do +RSpec.describe Spree::NullPromotionAdvertiser, type: :model do describe ".for_product" do subject { described_class.for_product(product) } let(:product) { create(:product) } diff --git a/core/app/models/spree/promotion_advertiser.rb b/legacy_promotions/app/models/spree/promotion_advertiser.rb similarity index 100% rename from core/app/models/spree/promotion_advertiser.rb rename to legacy_promotions/app/models/spree/promotion_advertiser.rb diff --git a/core/spec/models/spree/promotion_advertiser_spec.rb b/legacy_promotions/spec/models/spree/promotion_advertiser_spec.rb similarity index 100% rename from core/spec/models/spree/promotion_advertiser_spec.rb rename to legacy_promotions/spec/models/spree/promotion_advertiser_spec.rb From 3654d4ee4cad2502875c7c3e4dbcdb6e7410998e Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 12 Jun 2024 10:19:47 +0200 Subject: [PATCH 039/834] Move promotion chooser to solidus_legacy_promotions This class does not have a spec. --- {core => legacy_promotions}/app/models/spree/promotion_chooser.rb | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {core => legacy_promotions}/app/models/spree/promotion_chooser.rb (100%) diff --git a/core/app/models/spree/promotion_chooser.rb b/legacy_promotions/app/models/spree/promotion_chooser.rb similarity index 100% rename from core/app/models/spree/promotion_chooser.rb rename to legacy_promotions/app/models/spree/promotion_chooser.rb From 37c0ef5f5a98417eadaa15c21b44130305cc9144 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 12 Jun 2024 11:04:11 +0200 Subject: [PATCH 040/834] Move legacy-promotion-specific order merger spec This spec relies on the entirety of the legacy promotion system to be present. The original spec is changed to reflect the intention, but with a manual line item adjustment instead of an automatic one. --- core/spec/models/spree/order_merger_spec.rb | 12 ++++--- .../spec/models/spree/order_merger_spec.rb | 36 +++++++++++++++++++ 2 files changed, 44 insertions(+), 4 deletions(-) create mode 100644 legacy_promotions/spec/models/spree/order_merger_spec.rb diff --git a/core/spec/models/spree/order_merger_spec.rb b/core/spec/models/spree/order_merger_spec.rb index 24f26822292..80841109232 100644 --- a/core/spec/models/spree/order_merger_spec.rb +++ b/core/spec/models/spree/order_merger_spec.rb @@ -142,11 +142,15 @@ module Spree expect(order_1.line_items.pluck(:variant_id)).to match_array([variant.id, variant_2.id]) end - context "with line item promotion applied to order 2" do - let!(:promotion) { create(:promotion, :with_line_item_adjustment, apply_automatically: true) } - + context "with line item adjustment on order 2" do before do - Spree::PromotionHandler::Cart.new(order_2).activate + order_2.line_items.first.adjustments.create!( + order: order_2, + adjustable: order_2.line_items.first, + label: "For loyal customers", + source: nil, + amount: -2 + ) expect(order_2.line_items.flat_map(&:adjustments)).not_to be_empty end diff --git a/legacy_promotions/spec/models/spree/order_merger_spec.rb b/legacy_promotions/spec/models/spree/order_merger_spec.rb new file mode 100644 index 00000000000..c7c32dd03b1 --- /dev/null +++ b/legacy_promotions/spec/models/spree/order_merger_spec.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require 'rails_helper' + +# Regression test for https://github.com/spree/spree/issues/2179 +RSpec.describe Spree::OrderMerger, type: :model do + let(:variant) { create(:variant) } + let!(:store) { create(:store, default: true) } + let(:order_1) { Spree::Order.create } + let(:order_2) { Spree::Order.create } + let(:user) { stub_model(Spree::LegacyUser, email: "solidus@example.com") } + let(:subject) { Spree::OrderMerger.new(order_1) } + + context "merging together two orders with different line items" do + let(:variant_2) { create(:variant) } + + before do + order_1.contents.add(variant, 1) + order_2.contents.add(variant_2, 1) + end + + context "with line item promotion applied to order 2" do + let!(:promotion) { create(:promotion, :with_line_item_adjustment, apply_automatically: true) } + + before do + Spree::PromotionHandler::Cart.new(order_2).activate + expect(order_2.line_items.flat_map(&:adjustments)).not_to be_empty + end + + it "does not carry a line item adjustments with the wrong order ID over" do + subject.merge!(order_2) + expect(order_1.line_items.flat_map(&:adjustments)).to be_empty + end + end + end +end From cdc71e2113441bc31c34ac503a86519255cc3ebe Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 12 Jun 2024 10:29:09 +0200 Subject: [PATCH 041/834] Move legacy promotion system handlers to legacy_promotions --- .rubocop_todo.yml | 4 ++-- .../app/models/spree/promotion_handler/cart.rb | 0 .../app/models/spree/promotion_handler/coupon.rb | 0 .../app/models/spree/promotion_handler/page.rb | 0 .../app/models/spree/promotion_handler/shipping.rb | 0 .../spec/models/spree/promotion_handler/cart_spec.rb | 0 .../spec/models/spree/promotion_handler/coupon_spec.rb | 0 .../spec/models/spree/promotion_handler/page_spec.rb | 0 .../spec/models/spree/promotion_handler/shipping_spec.rb | 0 9 files changed, 2 insertions(+), 2 deletions(-) rename {core => legacy_promotions}/app/models/spree/promotion_handler/cart.rb (100%) rename {core => legacy_promotions}/app/models/spree/promotion_handler/coupon.rb (100%) rename {core => legacy_promotions}/app/models/spree/promotion_handler/page.rb (100%) rename {core => legacy_promotions}/app/models/spree/promotion_handler/shipping.rb (100%) rename {core => legacy_promotions}/spec/models/spree/promotion_handler/cart_spec.rb (100%) rename {core => legacy_promotions}/spec/models/spree/promotion_handler/coupon_spec.rb (100%) rename {core => legacy_promotions}/spec/models/spree/promotion_handler/page_spec.rb (100%) rename {core => legacy_promotions}/spec/models/spree/promotion_handler/shipping_spec.rb (100%) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 7a70952dbfa..abc7610fca1 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -285,7 +285,7 @@ Naming/MethodParameterName: # Configuration parameters: SafeMultiline. Performance/DeletePrefix: Exclude: - - "core/app/models/spree/promotion_handler/page.rb" + - "legacy_promotions/app/models/spree/promotion_handler/page.rb" # Offense count: 3 # This cop supports safe autocorrection (--autocorrect). @@ -660,7 +660,7 @@ Style/RedundantAssignment: # This cop supports safe autocorrection (--autocorrect). Style/RedundantBegin: Exclude: - - "core/app/models/spree/promotion_handler/coupon.rb" + - "legacy_promotions/app/models/spree/promotion_handler/coupon.rb" - "core/app/models/spree/user_last_url_storer/rules/authentication_rule.rb" - "core/spec/models/spree/stock/simple_coordinator_spec.rb" diff --git a/core/app/models/spree/promotion_handler/cart.rb b/legacy_promotions/app/models/spree/promotion_handler/cart.rb similarity index 100% rename from core/app/models/spree/promotion_handler/cart.rb rename to legacy_promotions/app/models/spree/promotion_handler/cart.rb diff --git a/core/app/models/spree/promotion_handler/coupon.rb b/legacy_promotions/app/models/spree/promotion_handler/coupon.rb similarity index 100% rename from core/app/models/spree/promotion_handler/coupon.rb rename to legacy_promotions/app/models/spree/promotion_handler/coupon.rb diff --git a/core/app/models/spree/promotion_handler/page.rb b/legacy_promotions/app/models/spree/promotion_handler/page.rb similarity index 100% rename from core/app/models/spree/promotion_handler/page.rb rename to legacy_promotions/app/models/spree/promotion_handler/page.rb diff --git a/core/app/models/spree/promotion_handler/shipping.rb b/legacy_promotions/app/models/spree/promotion_handler/shipping.rb similarity index 100% rename from core/app/models/spree/promotion_handler/shipping.rb rename to legacy_promotions/app/models/spree/promotion_handler/shipping.rb diff --git a/core/spec/models/spree/promotion_handler/cart_spec.rb b/legacy_promotions/spec/models/spree/promotion_handler/cart_spec.rb similarity index 100% rename from core/spec/models/spree/promotion_handler/cart_spec.rb rename to legacy_promotions/spec/models/spree/promotion_handler/cart_spec.rb diff --git a/core/spec/models/spree/promotion_handler/coupon_spec.rb b/legacy_promotions/spec/models/spree/promotion_handler/coupon_spec.rb similarity index 100% rename from core/spec/models/spree/promotion_handler/coupon_spec.rb rename to legacy_promotions/spec/models/spree/promotion_handler/coupon_spec.rb diff --git a/core/spec/models/spree/promotion_handler/page_spec.rb b/legacy_promotions/spec/models/spree/promotion_handler/page_spec.rb similarity index 100% rename from core/spec/models/spree/promotion_handler/page_spec.rb rename to legacy_promotions/spec/models/spree/promotion_handler/page_spec.rb diff --git a/core/spec/models/spree/promotion_handler/shipping_spec.rb b/legacy_promotions/spec/models/spree/promotion_handler/shipping_spec.rb similarity index 100% rename from core/spec/models/spree/promotion_handler/shipping_spec.rb rename to legacy_promotions/spec/models/spree/promotion_handler/shipping_spec.rb From 2a1cb984a97c912e3452d429a88b582be46e9240 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 12 Jun 2024 10:41:51 +0200 Subject: [PATCH 042/834] Move OrderAdjustmentsRecalculator to solidus_legacy_promotions The spec has already been moved. --- .rubocop_todo.yml | 4 ++-- .../models/spree/promotion/order_adjustments_recalculator.rb | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename {core => legacy_promotions}/app/models/spree/promotion/order_adjustments_recalculator.rb (100%) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index abc7610fca1..81bd59ac808 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -89,7 +89,7 @@ Layout/EmptyLinesAroundAttributeAccessor: Exclude: - "core/app/models/spree/address/state_validator.rb" - "core/app/models/spree/order_updater.rb" - - "core/app/models/spree/promotion/order_adjustments_recalculator.rb" + - "legacy_promotions/app/models/spree/promotion/order_adjustments_recalculator.rb" - "core/app/models/spree/stock_quantities.rb" - "core/app/models/spree/variant.rb" - "core/lib/spree/app_configuration.rb" @@ -113,7 +113,7 @@ Layout/EmptyLinesAroundBlockBody: # SupportedStyles: empty_lines, empty_lines_except_namespace, empty_lines_special, no_empty_lines, beginning_only, ending_only Layout/EmptyLinesAroundClassBody: Exclude: - - "core/app/models/spree/promotion/order_adjustments_recalculator.rb" + - "legacy_promotions/app/models/spree/promotion/order_adjustments_recalculator.rb" # Offense count: 4 # This cop supports safe autocorrection (--autocorrect). diff --git a/core/app/models/spree/promotion/order_adjustments_recalculator.rb b/legacy_promotions/app/models/spree/promotion/order_adjustments_recalculator.rb similarity index 100% rename from core/app/models/spree/promotion/order_adjustments_recalculator.rb rename to legacy_promotions/app/models/spree/promotion/order_adjustments_recalculator.rb From d03262b0fa5fe2b5780e1e540977b85070c77889 Mon Sep 17 00:00:00 2001 From: Thomas von Deyen Date: Thu, 13 Jun 2024 10:55:43 +0200 Subject: [PATCH 043/834] Allow Psych 5 Ruby 3.3 has psych 5 as default gem and we get a Bundler error about psych 5 already been activated although solidus does not allow it. --- core/solidus_core.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/solidus_core.gemspec b/core/solidus_core.gemspec index 287d20fe36d..b6aff3cfc6c 100644 --- a/core/solidus_core.gemspec +++ b/core/solidus_core.gemspec @@ -42,7 +42,7 @@ Gem::Specification.new do |s| s.add_dependency 'mini_magick', '~> 4.10' s.add_dependency 'monetize', '~> 1.8' s.add_dependency 'kt-paperclip', ['>= 6.3', '< 8'] - s.add_dependency 'psych', ['>= 4.0.1', '< 5.0'] + s.add_dependency 'psych', ['>= 4.0.1', '< 6.0'] s.add_dependency 'ransack', '~> 4.0' s.add_dependency 'sprockets-rails', '!= 3.5.0' s.add_dependency 'state_machines-activerecord', '~> 0.6' From 72d52ab3cc1f1a3513a768085a9812bd36532e2e Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 18 Jun 2024 10:44:22 +0200 Subject: [PATCH 044/834] Move promotion translations to legacy_promotions --- .../config/locales/promotion_categories.en.yml | 0 .../config/locales/promotions.en.yml | 0 .../orders/index/component.yml | 17 +++++++++++++++++ 3 files changed, 17 insertions(+) rename {admin => legacy_promotions}/config/locales/promotion_categories.en.yml (100%) rename {admin => legacy_promotions}/config/locales/promotions.en.yml (100%) diff --git a/admin/config/locales/promotion_categories.en.yml b/legacy_promotions/config/locales/promotion_categories.en.yml similarity index 100% rename from admin/config/locales/promotion_categories.en.yml rename to legacy_promotions/config/locales/promotion_categories.en.yml diff --git a/admin/config/locales/promotions.en.yml b/legacy_promotions/config/locales/promotions.en.yml similarity index 100% rename from admin/config/locales/promotions.en.yml rename to legacy_promotions/config/locales/promotions.en.yml diff --git a/legacy_promotions/lib/components/admin/solidus_legacy_promotions/orders/index/component.yml b/legacy_promotions/lib/components/admin/solidus_legacy_promotions/orders/index/component.yml index 95af3f495d4..454b31bbdb7 100644 --- a/legacy_promotions/lib/components/admin/solidus_legacy_promotions/orders/index/component.yml +++ b/legacy_promotions/lib/components/admin/solidus_legacy_promotions/orders/index/component.yml @@ -1,3 +1,20 @@ en: + columns: + items: + one: 1 Item + other: '%{count} Items' filters: + status: Status + shipment_state: Shipment State + payment_state: Payment State + promotions: Promotions + date: + formats: + short: '%d %b %y' + scopes: + all_orders: All + canceled: Canceled + complete: Complete + returned: Returned + in_progress: In Progress promotions: Promotions From 99e525eeca2481fc56b4e3c97763d72183165c2d Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 18 Jun 2024 14:14:26 +0200 Subject: [PATCH 045/834] Require legacy promotion configuration in legacy_promotions This oversight breaks the specs of `solidus_support`. --- core/lib/spree/core.rb | 1 - legacy_promotions/lib/solidus_legacy_promotions.rb | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/core/lib/spree/core.rb b/core/lib/spree/core.rb index 0b9667109ba..e20dbb6f540 100644 --- a/core/lib/spree/core.rb +++ b/core/lib/spree/core.rb @@ -120,7 +120,6 @@ class GatewayError < RuntimeError; end require 'spree/core/role_configuration' require 'spree/core/state_machines' require 'spree/core/stock_configuration' -require 'solidus_legacy_promotions/configuration' require 'spree/core/null_promotion_configuration' require 'spree/core/validators/email' require 'spree/permission_sets' diff --git a/legacy_promotions/lib/solidus_legacy_promotions.rb b/legacy_promotions/lib/solidus_legacy_promotions.rb index 3c7c33c17f6..d6851e61ac6 100644 --- a/legacy_promotions/lib/solidus_legacy_promotions.rb +++ b/legacy_promotions/lib/solidus_legacy_promotions.rb @@ -24,4 +24,5 @@ module SolidusLegacyPromotions VERSION = Spree.solidus_version end +require "solidus_legacy_promotions/configuration" require "solidus_legacy_promotions/engine" From 824ba095c730bb8b98cb955071e414cd88c603a7 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 19 Jun 2024 14:05:58 +0200 Subject: [PATCH 046/834] Add failing spec: Apply shipping promos after state machine reload Notice I had to give the implementation method an argument, because RSpec stubs do not have the correct `arity`; and `state_machines` will send the event, resulting in an ArgumentError otherwise. --- .../models/spree/order_decorator.rb | 2 +- legacy_promotions/spec/models/spree/order_spec.rb | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/legacy_promotions/app/decorators/solidus_legacy_promotions/models/spree/order_decorator.rb b/legacy_promotions/app/decorators/solidus_legacy_promotions/models/spree/order_decorator.rb index f1dd58ced3c..873c43472e2 100644 --- a/legacy_promotions/app/decorators/solidus_legacy_promotions/models/spree/order_decorator.rb +++ b/legacy_promotions/app/decorators/solidus_legacy_promotions/models/spree/order_decorator.rb @@ -10,7 +10,7 @@ def self.prepended(base) end end - def apply_shipping_promotions + def apply_shipping_promotions(_event = nil) ::Spree::Config.promotions.shipping_promotion_handler_class.new(self).activate recalculate end diff --git a/legacy_promotions/spec/models/spree/order_spec.rb b/legacy_promotions/spec/models/spree/order_spec.rb index 75f22959c97..524261f6166 100644 --- a/legacy_promotions/spec/models/spree/order_spec.rb +++ b/legacy_promotions/spec/models/spree/order_spec.rb @@ -19,6 +19,20 @@ order.apply_shipping_promotions end + + context "after the order state machine is reloaded", :pending do + let(:order) { create(:order_with_line_items, state: :delivery) } + + before do + @old_checkout_flow = Spree::Order.checkout_flow + Spree::Order.checkout_flow(&@old_checkout_flow) + end + + it "calls apply_shipping_promotions " do + expect(order).to receive(:apply_shipping_promotions) + order.next! + end + end end context "empty!" do From dbcf66d5a4ad628aec02a8f3fd9b2cbc8658b7c1 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 19 Jun 2024 14:16:19 +0200 Subject: [PATCH 047/834] Patch Order State Machine rather than order Some extensions rely on the default state machine being fully reloadable, and as long as we use solidus_legacy_promotions, the before_transition method `:apply_shipping_promotions` must survive a reload of the state machine. Rather than patching Spree::Order and adding the transition here, we add it where all the other default transitions are added: In the class methods of the order state machine. --- .../lib/spree_order_state_machine_decorator.rb | 18 ++++++++++++++++++ .../models/spree/order_decorator.rb | 8 -------- .../spec/models/spree/order_spec.rb | 2 +- 3 files changed, 19 insertions(+), 9 deletions(-) create mode 100644 legacy_promotions/app/decorators/solidus_legacy_promotions/lib/spree_order_state_machine_decorator.rb diff --git a/legacy_promotions/app/decorators/solidus_legacy_promotions/lib/spree_order_state_machine_decorator.rb b/legacy_promotions/app/decorators/solidus_legacy_promotions/lib/spree_order_state_machine_decorator.rb new file mode 100644 index 00000000000..91548a9f539 --- /dev/null +++ b/legacy_promotions/app/decorators/solidus_legacy_promotions/lib/spree_order_state_machine_decorator.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require_dependency "spree/core/state_machines/order" + +module SolidusLegacyPromotions + module SpreeOrderStateMachineDecorator + def define_state_machine! + super + state_machine do + if states[:delivery] + before_transition from: :delivery, do: :apply_shipping_promotions + end + end + end + + Spree::Core::StateMachines::Order::ClassMethods.prepend self + end +end diff --git a/legacy_promotions/app/decorators/solidus_legacy_promotions/models/spree/order_decorator.rb b/legacy_promotions/app/decorators/solidus_legacy_promotions/models/spree/order_decorator.rb index 873c43472e2..a74d3e4a253 100644 --- a/legacy_promotions/app/decorators/solidus_legacy_promotions/models/spree/order_decorator.rb +++ b/legacy_promotions/app/decorators/solidus_legacy_promotions/models/spree/order_decorator.rb @@ -2,14 +2,6 @@ module SolidusLegacyPromotions module OrderDecorator - def self.prepended(base) - base.state_machine do - if states[:delivery] - before_transition from: :delivery, do: :apply_shipping_promotions - end - end - end - def apply_shipping_promotions(_event = nil) ::Spree::Config.promotions.shipping_promotion_handler_class.new(self).activate recalculate diff --git a/legacy_promotions/spec/models/spree/order_spec.rb b/legacy_promotions/spec/models/spree/order_spec.rb index 524261f6166..4cf80c5d3d6 100644 --- a/legacy_promotions/spec/models/spree/order_spec.rb +++ b/legacy_promotions/spec/models/spree/order_spec.rb @@ -20,7 +20,7 @@ order.apply_shipping_promotions end - context "after the order state machine is reloaded", :pending do + context "after the order state machine is reloaded" do let(:order) { create(:order_with_line_items, state: :delivery) } before do From 57ea514bc13b8bf12058c6cd8a89ef6cad91b89a Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 27 Feb 2024 11:07:28 +0100 Subject: [PATCH 048/834] Change default promotion source types to be empty `Spree::PromotionAction` is not in core any longer, and the null promo adjuster can not actually produce any adjustments. --- core/lib/spree/app_configuration.rb | 2 +- core/spec/lib/spree/app_configuration_spec.rb | 2 +- .../lib/solidus_legacy_promotions/engine.rb | 1 + .../spec/lib/spree/app_configuration_spec.rb | 13 +++++++++++++ 4 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 legacy_promotions/spec/lib/spree/app_configuration_spec.rb diff --git a/core/lib/spree/app_configuration.rb b/core/lib/spree/app_configuration.rb index 12ab2e22dfe..8b0223eeb59 100644 --- a/core/lib/spree/app_configuration.rb +++ b/core/lib/spree/app_configuration.rb @@ -552,7 +552,7 @@ def payment_canceller class_name_attribute :taxon_attachment_module, default: "Spree::Taxon::ActiveStorageAttachment" # Set of classes that can be promotion adjustment sources - add_class_set :adjustment_promotion_source_types, default: ["Spree::PromotionAction"] + add_class_set :adjustment_promotion_source_types, default: [] # Configures the absolute path that contains the Solidus engine # migrations. This will be checked at app boot to confirm that all Solidus diff --git a/core/spec/lib/spree/app_configuration_spec.rb b/core/spec/lib/spree/app_configuration_spec.rb index 119d45ebc7b..5eea3ea2b3b 100644 --- a/core/spec/lib/spree/app_configuration_spec.rb +++ b/core/spec/lib/spree/app_configuration_spec.rb @@ -214,7 +214,7 @@ class DummyClass; end; describe "#adjustment_promotion_source_types" do subject { described_class.new.adjustment_promotion_source_types } - it { is_expected.to contain_exactly(Spree::PromotionAction) } + it { is_expected.to be_empty } end it 'has a default admin VAT location with nil values by default' do diff --git a/legacy_promotions/lib/solidus_legacy_promotions/engine.rb b/legacy_promotions/lib/solidus_legacy_promotions/engine.rb index 48a90403573..0be296e75f1 100644 --- a/legacy_promotions/lib/solidus_legacy_promotions/engine.rb +++ b/legacy_promotions/lib/solidus_legacy_promotions/engine.rb @@ -76,6 +76,7 @@ class Engine < ::Rails::Engine initializer "solidus_legacy_promotions", after: "spree.load_config_initializers" do Spree::Config.order_contents_class = "Spree::OrderContents" Spree::Config.promotions = SolidusLegacyPromotions::Configuration.new + Spree::Config.adjustment_promotion_source_types << "Spree::PromotionAction" end end end diff --git a/legacy_promotions/spec/lib/spree/app_configuration_spec.rb b/legacy_promotions/spec/lib/spree/app_configuration_spec.rb new file mode 100644 index 00000000000..4efdd4c936c --- /dev/null +++ b/legacy_promotions/spec/lib/spree/app_configuration_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Spree::AppConfiguration do + let(:prefs) { Spree::Config } + + describe "#adjustment_promotion_source_types" do + subject { prefs.adjustment_promotion_source_types } + + it { is_expected.to contain_exactly(Spree::PromotionAction) } + end +end From 7aa59114ee3c62bf39373c74eb25dc96751d96b3 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 26 Jan 2024 16:22:24 +0100 Subject: [PATCH 049/834] Move promotion models --- {core => legacy_promotions}/app/models/spree/promotion.rb | 0 .../app/models/spree/promotion/actions/create_adjustment.rb | 0 .../app/models/spree/promotion/actions/create_item_adjustments.rb | 0 .../models/spree/promotion/actions/create_quantity_adjustments.rb | 0 .../app/models/spree/promotion/actions/free_shipping.rb | 0 .../app/models/spree/promotion/rules/first_order.rb | 0 .../models/spree/promotion/rules/first_repeat_purchase_since.rb | 0 .../app/models/spree/promotion/rules/item_total.rb | 0 .../app/models/spree/promotion/rules/minimum_quantity.rb | 0 .../app/models/spree/promotion/rules/nth_order.rb | 0 .../app/models/spree/promotion/rules/one_use_per_user.rb | 0 .../app/models/spree/promotion/rules/option_value.rb | 0 .../app/models/spree/promotion/rules/product.rb | 0 .../app/models/spree/promotion/rules/store.rb | 0 .../app/models/spree/promotion/rules/taxon.rb | 0 .../app/models/spree/promotion/rules/user.rb | 0 .../app/models/spree/promotion/rules/user_logged_in.rb | 0 .../app/models/spree/promotion/rules/user_role.rb | 0 {core => legacy_promotions}/app/models/spree/promotion_action.rb | 0 .../app/models/spree/promotion_category.rb | 0 {core => legacy_promotions}/app/models/spree/promotion_code.rb | 0 .../app/models/spree/promotion_code_batch.rb | 0 {core => legacy_promotions}/app/models/spree/promotion_rule.rb | 0 .../app/models/spree/promotion_rule_store.rb | 0 .../app/models/spree/promotion_rule_taxon.rb | 0 .../app/models/spree/promotion_rule_user.rb | 0 .../spec/models/spree/promotion/actions/create_adjustment_spec.rb | 0 .../spree/promotion/actions/create_item_adjustments_spec.rb | 0 .../spree/promotion/actions/create_quantity_adjustments_spec.rb | 0 .../spec/models/spree/promotion/actions/free_shipping_spec.rb | 0 .../spec/models/spree/promotion/rules/first_order_spec.rb | 0 .../spree/promotion/rules/first_repeat_purchase_since_spec.rb | 0 .../spec/models/spree/promotion/rules/item_total_spec.rb | 0 .../spec/models/spree/promotion/rules/minimum_quantity_spec.rb | 0 .../spec/models/spree/promotion/rules/nth_order_spec.rb | 0 .../spec/models/spree/promotion/rules/one_use_per_user_spec.rb | 0 .../spec/models/spree/promotion/rules/option_value_spec.rb | 0 .../spec/models/spree/promotion/rules/product_spec.rb | 0 .../spec/models/spree/promotion/rules/store_spec.rb | 0 .../spec/models/spree/promotion/rules/taxon_spec.rb | 0 .../spec/models/spree/promotion/rules/user_logged_in_spec.rb | 0 .../spec/models/spree/promotion/rules/user_role_spec.rb | 0 .../spec/models/spree/promotion/rules/user_spec.rb | 0 .../spec/models/spree/promotion_action_spec.rb | 0 .../spec/models/spree/promotion_category_spec.rb | 0 .../spec/models/spree/promotion_code_spec.rb | 0 .../spec/models/spree/promotion_rule_spec.rb | 0 {core => legacy_promotions}/spec/models/spree/promotion_spec.rb | 0 48 files changed, 0 insertions(+), 0 deletions(-) rename {core => legacy_promotions}/app/models/spree/promotion.rb (100%) rename {core => legacy_promotions}/app/models/spree/promotion/actions/create_adjustment.rb (100%) rename {core => legacy_promotions}/app/models/spree/promotion/actions/create_item_adjustments.rb (100%) rename {core => legacy_promotions}/app/models/spree/promotion/actions/create_quantity_adjustments.rb (100%) rename {core => legacy_promotions}/app/models/spree/promotion/actions/free_shipping.rb (100%) rename {core => legacy_promotions}/app/models/spree/promotion/rules/first_order.rb (100%) rename {core => legacy_promotions}/app/models/spree/promotion/rules/first_repeat_purchase_since.rb (100%) rename {core => legacy_promotions}/app/models/spree/promotion/rules/item_total.rb (100%) rename {core => legacy_promotions}/app/models/spree/promotion/rules/minimum_quantity.rb (100%) rename {core => legacy_promotions}/app/models/spree/promotion/rules/nth_order.rb (100%) rename {core => legacy_promotions}/app/models/spree/promotion/rules/one_use_per_user.rb (100%) rename {core => legacy_promotions}/app/models/spree/promotion/rules/option_value.rb (100%) rename {core => legacy_promotions}/app/models/spree/promotion/rules/product.rb (100%) rename {core => legacy_promotions}/app/models/spree/promotion/rules/store.rb (100%) rename {core => legacy_promotions}/app/models/spree/promotion/rules/taxon.rb (100%) rename {core => legacy_promotions}/app/models/spree/promotion/rules/user.rb (100%) rename {core => legacy_promotions}/app/models/spree/promotion/rules/user_logged_in.rb (100%) rename {core => legacy_promotions}/app/models/spree/promotion/rules/user_role.rb (100%) rename {core => legacy_promotions}/app/models/spree/promotion_action.rb (100%) rename {core => legacy_promotions}/app/models/spree/promotion_category.rb (100%) rename {core => legacy_promotions}/app/models/spree/promotion_code.rb (100%) rename {core => legacy_promotions}/app/models/spree/promotion_code_batch.rb (100%) rename {core => legacy_promotions}/app/models/spree/promotion_rule.rb (100%) rename {core => legacy_promotions}/app/models/spree/promotion_rule_store.rb (100%) rename {core => legacy_promotions}/app/models/spree/promotion_rule_taxon.rb (100%) rename {core => legacy_promotions}/app/models/spree/promotion_rule_user.rb (100%) rename {core => legacy_promotions}/spec/models/spree/promotion/actions/create_adjustment_spec.rb (100%) rename {core => legacy_promotions}/spec/models/spree/promotion/actions/create_item_adjustments_spec.rb (100%) rename {core => legacy_promotions}/spec/models/spree/promotion/actions/create_quantity_adjustments_spec.rb (100%) rename {core => legacy_promotions}/spec/models/spree/promotion/actions/free_shipping_spec.rb (100%) rename {core => legacy_promotions}/spec/models/spree/promotion/rules/first_order_spec.rb (100%) rename {core => legacy_promotions}/spec/models/spree/promotion/rules/first_repeat_purchase_since_spec.rb (100%) rename {core => legacy_promotions}/spec/models/spree/promotion/rules/item_total_spec.rb (100%) rename {core => legacy_promotions}/spec/models/spree/promotion/rules/minimum_quantity_spec.rb (100%) rename {core => legacy_promotions}/spec/models/spree/promotion/rules/nth_order_spec.rb (100%) rename {core => legacy_promotions}/spec/models/spree/promotion/rules/one_use_per_user_spec.rb (100%) rename {core => legacy_promotions}/spec/models/spree/promotion/rules/option_value_spec.rb (100%) rename {core => legacy_promotions}/spec/models/spree/promotion/rules/product_spec.rb (100%) rename {core => legacy_promotions}/spec/models/spree/promotion/rules/store_spec.rb (100%) rename {core => legacy_promotions}/spec/models/spree/promotion/rules/taxon_spec.rb (100%) rename {core => legacy_promotions}/spec/models/spree/promotion/rules/user_logged_in_spec.rb (100%) rename {core => legacy_promotions}/spec/models/spree/promotion/rules/user_role_spec.rb (100%) rename {core => legacy_promotions}/spec/models/spree/promotion/rules/user_spec.rb (100%) rename {core => legacy_promotions}/spec/models/spree/promotion_action_spec.rb (100%) rename {core => legacy_promotions}/spec/models/spree/promotion_category_spec.rb (100%) rename {core => legacy_promotions}/spec/models/spree/promotion_code_spec.rb (100%) rename {core => legacy_promotions}/spec/models/spree/promotion_rule_spec.rb (100%) rename {core => legacy_promotions}/spec/models/spree/promotion_spec.rb (100%) diff --git a/core/app/models/spree/promotion.rb b/legacy_promotions/app/models/spree/promotion.rb similarity index 100% rename from core/app/models/spree/promotion.rb rename to legacy_promotions/app/models/spree/promotion.rb diff --git a/core/app/models/spree/promotion/actions/create_adjustment.rb b/legacy_promotions/app/models/spree/promotion/actions/create_adjustment.rb similarity index 100% rename from core/app/models/spree/promotion/actions/create_adjustment.rb rename to legacy_promotions/app/models/spree/promotion/actions/create_adjustment.rb diff --git a/core/app/models/spree/promotion/actions/create_item_adjustments.rb b/legacy_promotions/app/models/spree/promotion/actions/create_item_adjustments.rb similarity index 100% rename from core/app/models/spree/promotion/actions/create_item_adjustments.rb rename to legacy_promotions/app/models/spree/promotion/actions/create_item_adjustments.rb diff --git a/core/app/models/spree/promotion/actions/create_quantity_adjustments.rb b/legacy_promotions/app/models/spree/promotion/actions/create_quantity_adjustments.rb similarity index 100% rename from core/app/models/spree/promotion/actions/create_quantity_adjustments.rb rename to legacy_promotions/app/models/spree/promotion/actions/create_quantity_adjustments.rb diff --git a/core/app/models/spree/promotion/actions/free_shipping.rb b/legacy_promotions/app/models/spree/promotion/actions/free_shipping.rb similarity index 100% rename from core/app/models/spree/promotion/actions/free_shipping.rb rename to legacy_promotions/app/models/spree/promotion/actions/free_shipping.rb diff --git a/core/app/models/spree/promotion/rules/first_order.rb b/legacy_promotions/app/models/spree/promotion/rules/first_order.rb similarity index 100% rename from core/app/models/spree/promotion/rules/first_order.rb rename to legacy_promotions/app/models/spree/promotion/rules/first_order.rb diff --git a/core/app/models/spree/promotion/rules/first_repeat_purchase_since.rb b/legacy_promotions/app/models/spree/promotion/rules/first_repeat_purchase_since.rb similarity index 100% rename from core/app/models/spree/promotion/rules/first_repeat_purchase_since.rb rename to legacy_promotions/app/models/spree/promotion/rules/first_repeat_purchase_since.rb diff --git a/core/app/models/spree/promotion/rules/item_total.rb b/legacy_promotions/app/models/spree/promotion/rules/item_total.rb similarity index 100% rename from core/app/models/spree/promotion/rules/item_total.rb rename to legacy_promotions/app/models/spree/promotion/rules/item_total.rb diff --git a/core/app/models/spree/promotion/rules/minimum_quantity.rb b/legacy_promotions/app/models/spree/promotion/rules/minimum_quantity.rb similarity index 100% rename from core/app/models/spree/promotion/rules/minimum_quantity.rb rename to legacy_promotions/app/models/spree/promotion/rules/minimum_quantity.rb diff --git a/core/app/models/spree/promotion/rules/nth_order.rb b/legacy_promotions/app/models/spree/promotion/rules/nth_order.rb similarity index 100% rename from core/app/models/spree/promotion/rules/nth_order.rb rename to legacy_promotions/app/models/spree/promotion/rules/nth_order.rb diff --git a/core/app/models/spree/promotion/rules/one_use_per_user.rb b/legacy_promotions/app/models/spree/promotion/rules/one_use_per_user.rb similarity index 100% rename from core/app/models/spree/promotion/rules/one_use_per_user.rb rename to legacy_promotions/app/models/spree/promotion/rules/one_use_per_user.rb diff --git a/core/app/models/spree/promotion/rules/option_value.rb b/legacy_promotions/app/models/spree/promotion/rules/option_value.rb similarity index 100% rename from core/app/models/spree/promotion/rules/option_value.rb rename to legacy_promotions/app/models/spree/promotion/rules/option_value.rb diff --git a/core/app/models/spree/promotion/rules/product.rb b/legacy_promotions/app/models/spree/promotion/rules/product.rb similarity index 100% rename from core/app/models/spree/promotion/rules/product.rb rename to legacy_promotions/app/models/spree/promotion/rules/product.rb diff --git a/core/app/models/spree/promotion/rules/store.rb b/legacy_promotions/app/models/spree/promotion/rules/store.rb similarity index 100% rename from core/app/models/spree/promotion/rules/store.rb rename to legacy_promotions/app/models/spree/promotion/rules/store.rb diff --git a/core/app/models/spree/promotion/rules/taxon.rb b/legacy_promotions/app/models/spree/promotion/rules/taxon.rb similarity index 100% rename from core/app/models/spree/promotion/rules/taxon.rb rename to legacy_promotions/app/models/spree/promotion/rules/taxon.rb diff --git a/core/app/models/spree/promotion/rules/user.rb b/legacy_promotions/app/models/spree/promotion/rules/user.rb similarity index 100% rename from core/app/models/spree/promotion/rules/user.rb rename to legacy_promotions/app/models/spree/promotion/rules/user.rb diff --git a/core/app/models/spree/promotion/rules/user_logged_in.rb b/legacy_promotions/app/models/spree/promotion/rules/user_logged_in.rb similarity index 100% rename from core/app/models/spree/promotion/rules/user_logged_in.rb rename to legacy_promotions/app/models/spree/promotion/rules/user_logged_in.rb diff --git a/core/app/models/spree/promotion/rules/user_role.rb b/legacy_promotions/app/models/spree/promotion/rules/user_role.rb similarity index 100% rename from core/app/models/spree/promotion/rules/user_role.rb rename to legacy_promotions/app/models/spree/promotion/rules/user_role.rb diff --git a/core/app/models/spree/promotion_action.rb b/legacy_promotions/app/models/spree/promotion_action.rb similarity index 100% rename from core/app/models/spree/promotion_action.rb rename to legacy_promotions/app/models/spree/promotion_action.rb diff --git a/core/app/models/spree/promotion_category.rb b/legacy_promotions/app/models/spree/promotion_category.rb similarity index 100% rename from core/app/models/spree/promotion_category.rb rename to legacy_promotions/app/models/spree/promotion_category.rb diff --git a/core/app/models/spree/promotion_code.rb b/legacy_promotions/app/models/spree/promotion_code.rb similarity index 100% rename from core/app/models/spree/promotion_code.rb rename to legacy_promotions/app/models/spree/promotion_code.rb diff --git a/core/app/models/spree/promotion_code_batch.rb b/legacy_promotions/app/models/spree/promotion_code_batch.rb similarity index 100% rename from core/app/models/spree/promotion_code_batch.rb rename to legacy_promotions/app/models/spree/promotion_code_batch.rb diff --git a/core/app/models/spree/promotion_rule.rb b/legacy_promotions/app/models/spree/promotion_rule.rb similarity index 100% rename from core/app/models/spree/promotion_rule.rb rename to legacy_promotions/app/models/spree/promotion_rule.rb diff --git a/core/app/models/spree/promotion_rule_store.rb b/legacy_promotions/app/models/spree/promotion_rule_store.rb similarity index 100% rename from core/app/models/spree/promotion_rule_store.rb rename to legacy_promotions/app/models/spree/promotion_rule_store.rb diff --git a/core/app/models/spree/promotion_rule_taxon.rb b/legacy_promotions/app/models/spree/promotion_rule_taxon.rb similarity index 100% rename from core/app/models/spree/promotion_rule_taxon.rb rename to legacy_promotions/app/models/spree/promotion_rule_taxon.rb diff --git a/core/app/models/spree/promotion_rule_user.rb b/legacy_promotions/app/models/spree/promotion_rule_user.rb similarity index 100% rename from core/app/models/spree/promotion_rule_user.rb rename to legacy_promotions/app/models/spree/promotion_rule_user.rb diff --git a/core/spec/models/spree/promotion/actions/create_adjustment_spec.rb b/legacy_promotions/spec/models/spree/promotion/actions/create_adjustment_spec.rb similarity index 100% rename from core/spec/models/spree/promotion/actions/create_adjustment_spec.rb rename to legacy_promotions/spec/models/spree/promotion/actions/create_adjustment_spec.rb diff --git a/core/spec/models/spree/promotion/actions/create_item_adjustments_spec.rb b/legacy_promotions/spec/models/spree/promotion/actions/create_item_adjustments_spec.rb similarity index 100% rename from core/spec/models/spree/promotion/actions/create_item_adjustments_spec.rb rename to legacy_promotions/spec/models/spree/promotion/actions/create_item_adjustments_spec.rb diff --git a/core/spec/models/spree/promotion/actions/create_quantity_adjustments_spec.rb b/legacy_promotions/spec/models/spree/promotion/actions/create_quantity_adjustments_spec.rb similarity index 100% rename from core/spec/models/spree/promotion/actions/create_quantity_adjustments_spec.rb rename to legacy_promotions/spec/models/spree/promotion/actions/create_quantity_adjustments_spec.rb diff --git a/core/spec/models/spree/promotion/actions/free_shipping_spec.rb b/legacy_promotions/spec/models/spree/promotion/actions/free_shipping_spec.rb similarity index 100% rename from core/spec/models/spree/promotion/actions/free_shipping_spec.rb rename to legacy_promotions/spec/models/spree/promotion/actions/free_shipping_spec.rb diff --git a/core/spec/models/spree/promotion/rules/first_order_spec.rb b/legacy_promotions/spec/models/spree/promotion/rules/first_order_spec.rb similarity index 100% rename from core/spec/models/spree/promotion/rules/first_order_spec.rb rename to legacy_promotions/spec/models/spree/promotion/rules/first_order_spec.rb diff --git a/core/spec/models/spree/promotion/rules/first_repeat_purchase_since_spec.rb b/legacy_promotions/spec/models/spree/promotion/rules/first_repeat_purchase_since_spec.rb similarity index 100% rename from core/spec/models/spree/promotion/rules/first_repeat_purchase_since_spec.rb rename to legacy_promotions/spec/models/spree/promotion/rules/first_repeat_purchase_since_spec.rb diff --git a/core/spec/models/spree/promotion/rules/item_total_spec.rb b/legacy_promotions/spec/models/spree/promotion/rules/item_total_spec.rb similarity index 100% rename from core/spec/models/spree/promotion/rules/item_total_spec.rb rename to legacy_promotions/spec/models/spree/promotion/rules/item_total_spec.rb diff --git a/core/spec/models/spree/promotion/rules/minimum_quantity_spec.rb b/legacy_promotions/spec/models/spree/promotion/rules/minimum_quantity_spec.rb similarity index 100% rename from core/spec/models/spree/promotion/rules/minimum_quantity_spec.rb rename to legacy_promotions/spec/models/spree/promotion/rules/minimum_quantity_spec.rb diff --git a/core/spec/models/spree/promotion/rules/nth_order_spec.rb b/legacy_promotions/spec/models/spree/promotion/rules/nth_order_spec.rb similarity index 100% rename from core/spec/models/spree/promotion/rules/nth_order_spec.rb rename to legacy_promotions/spec/models/spree/promotion/rules/nth_order_spec.rb diff --git a/core/spec/models/spree/promotion/rules/one_use_per_user_spec.rb b/legacy_promotions/spec/models/spree/promotion/rules/one_use_per_user_spec.rb similarity index 100% rename from core/spec/models/spree/promotion/rules/one_use_per_user_spec.rb rename to legacy_promotions/spec/models/spree/promotion/rules/one_use_per_user_spec.rb diff --git a/core/spec/models/spree/promotion/rules/option_value_spec.rb b/legacy_promotions/spec/models/spree/promotion/rules/option_value_spec.rb similarity index 100% rename from core/spec/models/spree/promotion/rules/option_value_spec.rb rename to legacy_promotions/spec/models/spree/promotion/rules/option_value_spec.rb diff --git a/core/spec/models/spree/promotion/rules/product_spec.rb b/legacy_promotions/spec/models/spree/promotion/rules/product_spec.rb similarity index 100% rename from core/spec/models/spree/promotion/rules/product_spec.rb rename to legacy_promotions/spec/models/spree/promotion/rules/product_spec.rb diff --git a/core/spec/models/spree/promotion/rules/store_spec.rb b/legacy_promotions/spec/models/spree/promotion/rules/store_spec.rb similarity index 100% rename from core/spec/models/spree/promotion/rules/store_spec.rb rename to legacy_promotions/spec/models/spree/promotion/rules/store_spec.rb diff --git a/core/spec/models/spree/promotion/rules/taxon_spec.rb b/legacy_promotions/spec/models/spree/promotion/rules/taxon_spec.rb similarity index 100% rename from core/spec/models/spree/promotion/rules/taxon_spec.rb rename to legacy_promotions/spec/models/spree/promotion/rules/taxon_spec.rb diff --git a/core/spec/models/spree/promotion/rules/user_logged_in_spec.rb b/legacy_promotions/spec/models/spree/promotion/rules/user_logged_in_spec.rb similarity index 100% rename from core/spec/models/spree/promotion/rules/user_logged_in_spec.rb rename to legacy_promotions/spec/models/spree/promotion/rules/user_logged_in_spec.rb diff --git a/core/spec/models/spree/promotion/rules/user_role_spec.rb b/legacy_promotions/spec/models/spree/promotion/rules/user_role_spec.rb similarity index 100% rename from core/spec/models/spree/promotion/rules/user_role_spec.rb rename to legacy_promotions/spec/models/spree/promotion/rules/user_role_spec.rb diff --git a/core/spec/models/spree/promotion/rules/user_spec.rb b/legacy_promotions/spec/models/spree/promotion/rules/user_spec.rb similarity index 100% rename from core/spec/models/spree/promotion/rules/user_spec.rb rename to legacy_promotions/spec/models/spree/promotion/rules/user_spec.rb diff --git a/core/spec/models/spree/promotion_action_spec.rb b/legacy_promotions/spec/models/spree/promotion_action_spec.rb similarity index 100% rename from core/spec/models/spree/promotion_action_spec.rb rename to legacy_promotions/spec/models/spree/promotion_action_spec.rb diff --git a/core/spec/models/spree/promotion_category_spec.rb b/legacy_promotions/spec/models/spree/promotion_category_spec.rb similarity index 100% rename from core/spec/models/spree/promotion_category_spec.rb rename to legacy_promotions/spec/models/spree/promotion_category_spec.rb diff --git a/core/spec/models/spree/promotion_code_spec.rb b/legacy_promotions/spec/models/spree/promotion_code_spec.rb similarity index 100% rename from core/spec/models/spree/promotion_code_spec.rb rename to legacy_promotions/spec/models/spree/promotion_code_spec.rb diff --git a/core/spec/models/spree/promotion_rule_spec.rb b/legacy_promotions/spec/models/spree/promotion_rule_spec.rb similarity index 100% rename from core/spec/models/spree/promotion_rule_spec.rb rename to legacy_promotions/spec/models/spree/promotion_rule_spec.rb diff --git a/core/spec/models/spree/promotion_spec.rb b/legacy_promotions/spec/models/spree/promotion_spec.rb similarity index 100% rename from core/spec/models/spree/promotion_spec.rb rename to legacy_promotions/spec/models/spree/promotion_spec.rb From eb6f7d8ea97f1c3171ab7f474d3cf589affd46fa Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 27 Feb 2024 12:23:05 +0100 Subject: [PATCH 050/834] Rubocop fixes --- .rubocop_todo.yml | 32 ++----------------- .../app/models/spree/promotion.rb | 2 +- .../order_adjustments_recalculator.rb | 2 +- .../spree/promotion/rules/first_order.rb | 6 ++-- .../spree/promotion/rules/user_logged_in.rb | 2 +- .../app/models/spree/promotion_code.rb | 2 +- .../models/spree/promotion_handler/coupon.rb | 6 ++-- .../models/spree/promotion_handler/page.rb | 2 +- .../promotion/rules/option_value_spec.rb | 2 +- 9 files changed, 12 insertions(+), 44 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 81bd59ac808..5503a94b75c 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -89,7 +89,6 @@ Layout/EmptyLinesAroundAttributeAccessor: Exclude: - "core/app/models/spree/address/state_validator.rb" - "core/app/models/spree/order_updater.rb" - - "legacy_promotions/app/models/spree/promotion/order_adjustments_recalculator.rb" - "core/app/models/spree/stock_quantities.rb" - "core/app/models/spree/variant.rb" - "core/lib/spree/app_configuration.rb" @@ -107,14 +106,6 @@ Layout/EmptyLinesAroundBlockBody: Exclude: - "core/spec/models/spree/order/checkout_spec.rb" -# Offense count: 1 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: empty_lines, empty_lines_except_namespace, empty_lines_special, no_empty_lines, beginning_only, ending_only -Layout/EmptyLinesAroundClassBody: - Exclude: - - "legacy_promotions/app/models/spree/promotion/order_adjustments_recalculator.rb" - # Offense count: 4 # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowForAlignment, AllowBeforeTrailingComments, ForceEqualSignAlignment. @@ -280,13 +271,6 @@ Lint/UnusedMethodArgument: Naming/MethodParameterName: Exclude: -# Offense count: 1 -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: SafeMultiline. -Performance/DeletePrefix: - Exclude: - - "legacy_promotions/app/models/spree/promotion_handler/page.rb" - # Offense count: 3 # This cop supports safe autocorrection (--autocorrect). Performance/RegexpMatch: @@ -343,7 +327,6 @@ Rails/Blank: - "core/app/models/spree/line_item.rb" - "core/app/models/spree/order.rb" - "core/app/models/spree/order_contents.rb" - - "core/app/models/spree/promotion/rules/user_logged_in.rb" - "core/app/models/spree/reimbursement_type/exchange.rb" - "core/app/models/spree/wallet_payment_source.rb" - "core/app/models/spree/zone.rb" @@ -431,12 +414,6 @@ Rails/OutputSafety: - "core/app/helpers/spree/products_helper.rb" - "core/lib/spree/money.rb" -# Offense count: 1 -# This cop supports unsafe autocorrection (--autocorrect-all). -Rails/Pick: - Exclude: - - "core/spec/models/spree/promotion/rules/option_value_spec.rb" - # Offense count: 1 # This cop supports safe autocorrection (--autocorrect). Rails/Presence: @@ -453,7 +430,6 @@ Rails/Present: - "api/app/controllers/spree/api/taxons_controller.rb" - "backend/app/helpers/spree/admin/stock_movements_helper.rb" - "core/app/models/concerns/spree/ordered_property_value_list.rb" - - "core/app/models/spree/promotion/rules/first_order.rb" - "core/app/models/spree/stock/availability_validator.rb" - "core/lib/spree/core/search/base.rb" - "core/spec/models/spree/stock/availability_validator_spec.rb" @@ -475,8 +451,8 @@ Rails/ReflectionClassName: Exclude: - "core/app/models/spree/credit_card.rb" - "core/app/models/spree/order.rb" - - "core/app/models/spree/promotion/rules/user.rb" - - "core/app/models/spree/promotion_rule_user.rb" + - "legacy_promotions/app/models/spree/promotion/rules/user.rb" + - "legacy_promotions/app/models/spree/promotion_rule_user.rb" - "core/app/models/spree/role_user.rb" - "core/app/models/spree/store_credit.rb" - "core/app/models/spree/user_address.rb" @@ -568,7 +544,6 @@ Style/CommentAnnotation: - "backend/app/controllers/spree/admin/products_controller.rb" - "backend/app/controllers/spree/admin/resource_controller.rb" - "core/app/models/spree/payment_method/store_credit.rb" - - "core/app/models/spree/promotion.rb" - "core/lib/spree/permission_sets/user_management.rb" - "core/lib/spree/testing_support/rake.rb" - "core/spec/models/spree/variant/scopes_spec.rb" @@ -660,7 +635,6 @@ Style/RedundantAssignment: # This cop supports safe autocorrection (--autocorrect). Style/RedundantBegin: Exclude: - - "legacy_promotions/app/models/spree/promotion_handler/coupon.rb" - "core/app/models/spree/user_last_url_storer/rules/authentication_rule.rb" - "core/spec/models/spree/stock/simple_coordinator_spec.rb" @@ -729,7 +703,6 @@ Style/RedundantReturn: Style/RedundantSelf: Exclude: - "core/app/models/spree/product.rb" - - "core/app/models/spree/promotion_code.rb" # Offense count: 1 # This cop supports unsafe autocorrection (--autocorrect-all). @@ -749,7 +722,6 @@ Style/SafeNavigation: # Configuration parameters: AllowModifier. Style/SoleNestedConditional: Exclude: - - "core/app/models/spree/promotion/rules/first_order.rb" - "core/lib/spree/core/controller_helpers/order.rb" # Offense count: 13 diff --git a/legacy_promotions/app/models/spree/promotion.rb b/legacy_promotions/app/models/spree/promotion.rb index d901fbebc0c..996ccd9365d 100644 --- a/legacy_promotions/app/models/spree/promotion.rb +++ b/legacy_promotions/app/models/spree/promotion.rb @@ -231,7 +231,7 @@ def remove_from(order) actions.each do |action| action.remove_from(order) end - # note: this destroys the join table entry, not the promotion itself + # NOTE: this destroys the join table entry, not the promotion itself order.promotions.destroy(self) order.order_promotions.reset order_promotions.reset diff --git a/legacy_promotions/app/models/spree/promotion/order_adjustments_recalculator.rb b/legacy_promotions/app/models/spree/promotion/order_adjustments_recalculator.rb index d8728bd597e..ca01e9c095a 100644 --- a/legacy_promotions/app/models/spree/promotion/order_adjustments_recalculator.rb +++ b/legacy_promotions/app/models/spree/promotion/order_adjustments_recalculator.rb @@ -2,7 +2,6 @@ module Spree class Promotion < Spree::Base - # This class encapsulates all the things the promotion system does to # an order. It is called from the `Spree::OrderUpdater` before taxes are # calculated, such that taxes always respect promotions. @@ -43,6 +42,7 @@ def call private attr_reader :order + delegate :line_items, :shipments, to: :order # Recalculate and persist the amount from this adjustment's source based on diff --git a/legacy_promotions/app/models/spree/promotion/rules/first_order.rb b/legacy_promotions/app/models/spree/promotion/rules/first_order.rb index a2a0ec7538f..fa1f7e2bee0 100644 --- a/legacy_promotions/app/models/spree/promotion/rules/first_order.rb +++ b/legacy_promotions/app/models/spree/promotion/rules/first_order.rb @@ -14,10 +14,8 @@ def eligible?(order, options = {}) @user = order.try(:user) || options[:user] @email = order.email - if user || email - if !completed_orders.blank? && completed_orders.first != order - eligibility_errors.add(:base, eligibility_error_message(:not_first_order), error_code: :not_first_order) - end + if (user || email) && (completed_orders.present? && completed_orders.first != order) + eligibility_errors.add(:base, eligibility_error_message(:not_first_order), error_code: :not_first_order) end eligibility_errors.empty? diff --git a/legacy_promotions/app/models/spree/promotion/rules/user_logged_in.rb b/legacy_promotions/app/models/spree/promotion/rules/user_logged_in.rb index 69341dad81c..21af3976e5f 100644 --- a/legacy_promotions/app/models/spree/promotion/rules/user_logged_in.rb +++ b/legacy_promotions/app/models/spree/promotion/rules/user_logged_in.rb @@ -9,7 +9,7 @@ def applicable?(promotable) end def eligible?(order, _options = {}) - unless order.user.present? + if order.user.blank? eligibility_errors.add(:base, eligibility_error_message(:no_user_specified), error_code: :no_user_specified) end eligibility_errors.empty? diff --git a/legacy_promotions/app/models/spree/promotion_code.rb b/legacy_promotions/app/models/spree/promotion_code.rb index bae81171f3d..fc1d9d6d666 100644 --- a/legacy_promotions/app/models/spree/promotion_code.rb +++ b/legacy_promotions/app/models/spree/promotion_code.rb @@ -33,7 +33,7 @@ def usage_count(excluded_orders: []) complete. where.not(spree_orders: { state: :canceled }). joins(:order_promotions). - where(spree_orders_promotions: { promotion_code_id: self.id }). + where(spree_orders_promotions: { promotion_code_id: id }). where.not(id: excluded_orders.map(&:id)). count end diff --git a/legacy_promotions/app/models/spree/promotion_handler/coupon.rb b/legacy_promotions/app/models/spree/promotion_handler/coupon.rb index 43d3d08e7c5..4e5ab2835f4 100644 --- a/legacy_promotions/app/models/spree/promotion_handler/coupon.rb +++ b/legacy_promotions/app/models/spree/promotion_handler/coupon.rb @@ -54,10 +54,8 @@ def set_error_code(status_code, options = {}) end def promotion - @promotion ||= begin - if promotion_code && promotion_code.promotion.active? - promotion_code.promotion - end + @promotion ||= if promotion_code && promotion_code.promotion.active? + promotion_code.promotion end end diff --git a/legacy_promotions/app/models/spree/promotion_handler/page.rb b/legacy_promotions/app/models/spree/promotion_handler/page.rb index e22450f212d..b96825c41da 100644 --- a/legacy_promotions/app/models/spree/promotion_handler/page.rb +++ b/legacy_promotions/app/models/spree/promotion_handler/page.rb @@ -7,7 +7,7 @@ class Page def initialize(order, path) @order = order - @path = path.gsub(/\A\//, '') + @path = path.delete_prefix('/') end def activate diff --git a/legacy_promotions/spec/models/spree/promotion/rules/option_value_spec.rb b/legacy_promotions/spec/models/spree/promotion/rules/option_value_spec.rb index 26eef3780db..706a44280e9 100644 --- a/legacy_promotions/spec/models/spree/promotion/rules/option_value_spec.rb +++ b/legacy_promotions/spec/models/spree/promotion/rules/option_value_spec.rb @@ -33,7 +33,7 @@ context "when there are any applicable line items" do before do rule.preferred_eligible_values = Hash[line_item.product.id => [ - line_item.variant.option_values.pluck(:id).first + line_item.variant.option_values.pick(:id) ]] end it { is_expected.to be true } From a0e19d9f29f36353e970440faf2d835277a31187 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 26 Jan 2024 17:52:32 +0100 Subject: [PATCH 051/834] Move promotion factories into solidus_legacy_promotions --- .../factories/order_promotion_factory.rb | 0 .../factories/promotion_category_factory.rb | 0 .../factories/promotion_code_factory.rb | 0 .../factories/promotion_factory.rb | 0 .../testing_support/factory_bot.rb | 28 +++++++++++++++++++ .../factories/order_promotion_factory_spec.rb | 0 .../promotion_category_factory_spec.rb | 0 .../factories/promotion_code_factory_spec.rb | 0 .../factories/promotion_factory_spec.rb | 0 legacy_promotions/spec/rails_helper.rb | 4 ++- 10 files changed, 31 insertions(+), 1 deletion(-) rename {core/lib/spree => legacy_promotions/lib/solidus_legacy_promotions}/testing_support/factories/order_promotion_factory.rb (100%) rename {core/lib/spree => legacy_promotions/lib/solidus_legacy_promotions}/testing_support/factories/promotion_category_factory.rb (100%) rename {core/lib/spree => legacy_promotions/lib/solidus_legacy_promotions}/testing_support/factories/promotion_code_factory.rb (100%) rename {core/lib/spree => legacy_promotions/lib/solidus_legacy_promotions}/testing_support/factories/promotion_factory.rb (100%) create mode 100644 legacy_promotions/lib/solidus_legacy_promotions/testing_support/factory_bot.rb rename {core/spec/lib/spree/core => legacy_promotions/spec/lib/solidus_legacy_promotions}/testing_support/factories/order_promotion_factory_spec.rb (100%) rename {core/spec/lib/spree/core => legacy_promotions/spec/lib/solidus_legacy_promotions}/testing_support/factories/promotion_category_factory_spec.rb (100%) rename {core/spec/lib/spree/core => legacy_promotions/spec/lib/solidus_legacy_promotions}/testing_support/factories/promotion_code_factory_spec.rb (100%) rename {core/spec/lib/spree/core => legacy_promotions/spec/lib/solidus_legacy_promotions}/testing_support/factories/promotion_factory_spec.rb (100%) diff --git a/core/lib/spree/testing_support/factories/order_promotion_factory.rb b/legacy_promotions/lib/solidus_legacy_promotions/testing_support/factories/order_promotion_factory.rb similarity index 100% rename from core/lib/spree/testing_support/factories/order_promotion_factory.rb rename to legacy_promotions/lib/solidus_legacy_promotions/testing_support/factories/order_promotion_factory.rb diff --git a/core/lib/spree/testing_support/factories/promotion_category_factory.rb b/legacy_promotions/lib/solidus_legacy_promotions/testing_support/factories/promotion_category_factory.rb similarity index 100% rename from core/lib/spree/testing_support/factories/promotion_category_factory.rb rename to legacy_promotions/lib/solidus_legacy_promotions/testing_support/factories/promotion_category_factory.rb diff --git a/core/lib/spree/testing_support/factories/promotion_code_factory.rb b/legacy_promotions/lib/solidus_legacy_promotions/testing_support/factories/promotion_code_factory.rb similarity index 100% rename from core/lib/spree/testing_support/factories/promotion_code_factory.rb rename to legacy_promotions/lib/solidus_legacy_promotions/testing_support/factories/promotion_code_factory.rb diff --git a/core/lib/spree/testing_support/factories/promotion_factory.rb b/legacy_promotions/lib/solidus_legacy_promotions/testing_support/factories/promotion_factory.rb similarity index 100% rename from core/lib/spree/testing_support/factories/promotion_factory.rb rename to legacy_promotions/lib/solidus_legacy_promotions/testing_support/factories/promotion_factory.rb diff --git a/legacy_promotions/lib/solidus_legacy_promotions/testing_support/factory_bot.rb b/legacy_promotions/lib/solidus_legacy_promotions/testing_support/factory_bot.rb new file mode 100644 index 00000000000..80386e46828 --- /dev/null +++ b/legacy_promotions/lib/solidus_legacy_promotions/testing_support/factory_bot.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require "factory_bot" +begin + require "factory_bot_rails" +rescue LoadError +end + +module SolidusLegacyPromotions + module TestingSupport + module FactoryBot + FACTORIES = Dir["#{::SolidusLegacyPromotions::Engine.root}/lib/solidus_legacy_promotions/testing_support/factories/**/*_factory.rb"].sort + + def self.definition_file_paths + @paths ||= FACTORIES.map { |path| path.sub(/.rb\z/, '') } + end + + def self.add_definitions! + ::FactoryBot.definition_file_paths.unshift(*definition_file_paths).uniq! + end + + def self.add_paths_and_load! + add_definitions! + ::FactoryBot.reload + end + end + end +end diff --git a/core/spec/lib/spree/core/testing_support/factories/order_promotion_factory_spec.rb b/legacy_promotions/spec/lib/solidus_legacy_promotions/testing_support/factories/order_promotion_factory_spec.rb similarity index 100% rename from core/spec/lib/spree/core/testing_support/factories/order_promotion_factory_spec.rb rename to legacy_promotions/spec/lib/solidus_legacy_promotions/testing_support/factories/order_promotion_factory_spec.rb diff --git a/core/spec/lib/spree/core/testing_support/factories/promotion_category_factory_spec.rb b/legacy_promotions/spec/lib/solidus_legacy_promotions/testing_support/factories/promotion_category_factory_spec.rb similarity index 100% rename from core/spec/lib/spree/core/testing_support/factories/promotion_category_factory_spec.rb rename to legacy_promotions/spec/lib/solidus_legacy_promotions/testing_support/factories/promotion_category_factory_spec.rb diff --git a/core/spec/lib/spree/core/testing_support/factories/promotion_code_factory_spec.rb b/legacy_promotions/spec/lib/solidus_legacy_promotions/testing_support/factories/promotion_code_factory_spec.rb similarity index 100% rename from core/spec/lib/spree/core/testing_support/factories/promotion_code_factory_spec.rb rename to legacy_promotions/spec/lib/solidus_legacy_promotions/testing_support/factories/promotion_code_factory_spec.rb diff --git a/core/spec/lib/spree/core/testing_support/factories/promotion_factory_spec.rb b/legacy_promotions/spec/lib/solidus_legacy_promotions/testing_support/factories/promotion_factory_spec.rb similarity index 100% rename from core/spec/lib/spree/core/testing_support/factories/promotion_factory_spec.rb rename to legacy_promotions/spec/lib/solidus_legacy_promotions/testing_support/factories/promotion_factory_spec.rb diff --git a/legacy_promotions/spec/rails_helper.rb b/legacy_promotions/spec/rails_helper.rb index a4691920530..1ea6aeef0ff 100644 --- a/legacy_promotions/spec/rails_helper.rb +++ b/legacy_promotions/spec/rails_helper.rb @@ -44,6 +44,7 @@ require 'spree/testing_support/authorization_helpers' require 'spree/testing_support/controller_requests' require "solidus_admin/testing_support/feature_helpers" +require 'solidus_legacy_promotions/testing_support/factory_bot' require 'cancan/matchers' require 'spree/testing_support/capybara_ext' @@ -51,7 +52,8 @@ ActiveJob::Base.queue_adapter = :test -Spree::TestingSupport::FactoryBot.add_paths_and_load! +Spree::TestingSupport::FactoryBot.add_definitions! +SolidusLegacyPromotions::TestingSupport::FactoryBot.add_paths_and_load! Capybara.register_driver :selenium_chrome_headless do |app| browser_options = ::Selenium::WebDriver::Chrome::Options.new From 6556b9ad23d97407bb81f09e931365d8f39eac64 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 26 Jan 2024 17:54:37 +0100 Subject: [PATCH 052/834] Move promotion-related migrations --- .rubocop_todo.yml | 2 +- .../20160101010000_solidus_one_four.rb | 103 ----------------- ...633_drop_unused_promo_action_line_items.rb | 13 --- ...d_promotion_order_promotion_foreign_key.rb | 10 -- ...60101010001_solidus_one_four_promotions.rb | 108 ++++++++++++++++++ ...02621_create_spree_promotion_code_batch.rb | 0 ...0202190713_create_promotion_rule_stores.rb | 0 ...join_characters_to_promotion_code_batch.rb | 0 ...84413_remove_code_from_spree_promotions.rb | 8 +- ...ions_with_any_policy_to_all_if_possible.rb | 0 ...emove_match_policy_from_spree_promotion.rb | 0 ...ove_unused_columns_from_promotion_rules.rb | 1 + ...084517_add_order_promotions_foreign_key.rb | 0 .../promotions_with_code_handlers.rb | 6 +- 14 files changed, 118 insertions(+), 133 deletions(-) delete mode 100644 core/db/migrate/20230325161633_drop_unused_promo_action_line_items.rb delete mode 100644 core/db/migrate/20231031175215_add_promotion_order_promotion_foreign_key.rb create mode 100644 legacy_promotions/db/migrate/20160101010001_solidus_one_four_promotions.rb rename {core => legacy_promotions}/db/migrate/20161017102621_create_spree_promotion_code_batch.rb (100%) rename {core => legacy_promotions}/db/migrate/20180202190713_create_promotion_rule_stores.rb (100%) rename {core => legacy_promotions}/db/migrate/20180328172631_add_join_characters_to_promotion_code_batch.rb (100%) rename {core => legacy_promotions}/db/migrate/20190106184413_remove_code_from_spree_promotions.rb (77%) rename {core => legacy_promotions}/db/migrate/20220317165036_set_promotions_with_any_policy_to_all_if_possible.rb (100%) rename {core => legacy_promotions}/db/migrate/20230322085416_remove_match_policy_from_spree_promotion.rb (100%) rename {core => legacy_promotions}/db/migrate/20230325132905_remove_unused_columns_from_promotion_rules.rb (99%) rename {core => legacy_promotions}/db/migrate/20231027084517_add_order_promotions_foreign_key.rb (100%) rename {core/lib/solidus => legacy_promotions/lib/solidus_legacy_promotions}/migrations/promotions_with_code_handlers.rb (95%) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 5503a94b75c..1ff5c6787e6 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -313,7 +313,7 @@ Rails/ApplicationRecord: - "core/db/migrate/20170412103617_transform_tax_rate_category_relation.rb" - "core/db/migrate/20180322142651_add_amount_remaining_to_store_credit_events.rb" - "core/db/migrate/20180710170104_create_spree_store_credit_reasons_table.rb" - - "core/db/migrate/20190106184413_remove_code_from_spree_promotions.rb" + - "legacy_promotions/db/migrate/20190106184413_remove_code_from_spree_promotions.rb" - "core/spec/lib/calculated_adjustments_spec.rb" - "core/spec/models/spree/validations/db_maximum_length_validator_spec.rb" - "core/spec/models/spree/wallet_payment_source_spec.rb" diff --git a/core/db/migrate/20160101010000_solidus_one_four.rb b/core/db/migrate/20160101010000_solidus_one_four.rb index 12fc5641274..a7a910c23fd 100644 --- a/core/db/migrate/20160101010000_solidus_one_four.rb +++ b/core/db/migrate/20160101010000_solidus_one_four.rb @@ -411,15 +411,6 @@ def up t.index ["product_id"], name: "index_spree_product_option_types_on_product_id" end - create_table "spree_product_promotion_rules", force: :cascade do |t| - t.integer "product_id" - t.integer "promotion_rule_id" - t.datetime "created_at", precision: 6 - t.datetime "updated_at", precision: 6 - t.index ["product_id"], name: "index_products_promotion_rules_on_product_id" - t.index ["promotion_rule_id"], name: "index_products_promotion_rules_on_promotion_rule_id" - end - create_table "spree_product_properties", force: :cascade do |t| t.string "value" t.integer "product_id" @@ -463,100 +454,6 @@ def up t.index ["taxon_id"], name: "index_spree_products_taxons_on_taxon_id" end - create_table "spree_promotion_action_line_items", force: :cascade do |t| - t.integer "promotion_action_id" - t.integer "variant_id" - t.integer "quantity", default: 1 - t.datetime "created_at", precision: 6 - t.datetime "updated_at", precision: 6 - t.index ["promotion_action_id"], name: "index_spree_promotion_action_line_items_on_promotion_action_id" - t.index ["variant_id"], name: "index_spree_promotion_action_line_items_on_variant_id" - end - - create_table "spree_promotion_actions", force: :cascade do |t| - t.integer "promotion_id" - t.integer "position" - t.string "type" - t.datetime "deleted_at" - t.text "preferences" - t.datetime "created_at", precision: 6 - t.datetime "updated_at", precision: 6 - t.index ["deleted_at"], name: "index_spree_promotion_actions_on_deleted_at" - t.index ["id", "type"], name: "index_spree_promotion_actions_on_id_and_type" - t.index ["promotion_id"], name: "index_spree_promotion_actions_on_promotion_id" - end - - create_table "spree_promotion_categories", force: :cascade do |t| - t.string "name" - t.datetime "created_at", precision: 6 - t.datetime "updated_at", precision: 6 - t.string "code" - end - - create_table "spree_promotion_codes", force: :cascade do |t| - t.integer "promotion_id", null: false - t.string "value", null: false - t.datetime "created_at", precision: 6 - t.datetime "updated_at", precision: 6 - t.index ["promotion_id"], name: "index_spree_promotion_codes_on_promotion_id" - t.index ["value"], name: "index_spree_promotion_codes_on_value", unique: true - end - - create_table "spree_promotion_rule_taxons", force: :cascade do |t| - t.integer "taxon_id" - t.integer "promotion_rule_id" - t.datetime "created_at", precision: 6 - t.datetime "updated_at", precision: 6 - t.index ["promotion_rule_id"], name: "index_spree_promotion_rule_taxons_on_promotion_rule_id" - t.index ["taxon_id"], name: "index_spree_promotion_rule_taxons_on_taxon_id" - end - - create_table "spree_promotion_rules", force: :cascade do |t| - t.integer "promotion_id" - t.integer "product_group_id" - t.string "type" - t.datetime "created_at", precision: 6 - t.datetime "updated_at", precision: 6 - t.string "code" - t.text "preferences" - t.index ["product_group_id"], name: "index_promotion_rules_on_product_group_id" - t.index ["promotion_id"], name: "index_spree_promotion_rules_on_promotion_id" - end - - create_table "spree_promotion_rules_users", force: :cascade do |t| - t.integer "user_id" - t.integer "promotion_rule_id" - t.datetime "created_at", precision: 6 - t.datetime "updated_at", precision: 6 - t.index ["promotion_rule_id"], name: "index_promotion_rules_users_on_promotion_rule_id" - t.index ["user_id"], name: "index_promotion_rules_users_on_user_id" - end - - create_table "spree_promotions", force: :cascade do |t| - t.string "description" - t.datetime "expires_at" - t.datetime "starts_at" - t.string "name" - t.string "type" - t.integer "usage_limit" - t.string "match_policy", default: "all" - t.string "code" - t.boolean "advertise", default: false - t.string "path" - t.datetime "created_at", precision: 6 - t.datetime "updated_at", precision: 6 - t.integer "promotion_category_id" - t.integer "per_code_usage_limit" - t.boolean "apply_automatically", default: false - t.index ["advertise"], name: "index_spree_promotions_on_advertise" - t.index ["apply_automatically"], name: "index_spree_promotions_on_apply_automatically" - t.index ["code"], name: "index_spree_promotions_on_code" - t.index ["expires_at"], name: "index_spree_promotions_on_expires_at" - t.index ["id", "type"], name: "index_spree_promotions_on_id_and_type" - t.index ["promotion_category_id"], name: "index_spree_promotions_on_promotion_category_id" - t.index ["starts_at"], name: "index_spree_promotions_on_starts_at" - end - create_table "spree_properties", force: :cascade do |t| t.string "name" t.string "presentation", null: false diff --git a/core/db/migrate/20230325161633_drop_unused_promo_action_line_items.rb b/core/db/migrate/20230325161633_drop_unused_promo_action_line_items.rb deleted file mode 100644 index d01c891d03b..00000000000 --- a/core/db/migrate/20230325161633_drop_unused_promo_action_line_items.rb +++ /dev/null @@ -1,13 +0,0 @@ -class DropUnusedPromoActionLineItems < ActiveRecord::Migration[5.2] - def change - drop_table :spree_promotion_action_line_items, force: :cascade do |t| - t.integer "promotion_action_id" - t.integer "variant_id" - t.integer "quantity", default: 1 - t.datetime "created_at", precision: 6 - t.datetime "updated_at", precision: 6 - t.index ["promotion_action_id"], name: "index_spree_promotion_action_line_items_on_promotion_action_id" - t.index ["variant_id"], name: "index_spree_promotion_action_line_items_on_variant_id" - end - end -end diff --git a/core/db/migrate/20231031175215_add_promotion_order_promotion_foreign_key.rb b/core/db/migrate/20231031175215_add_promotion_order_promotion_foreign_key.rb deleted file mode 100644 index cd5e1451758..00000000000 --- a/core/db/migrate/20231031175215_add_promotion_order_promotion_foreign_key.rb +++ /dev/null @@ -1,10 +0,0 @@ -class AddPromotionOrderPromotionForeignKey < ActiveRecord::Migration[7.0] - def up - Spree::OrderPromotion.left_joins(:promotion).where(spree_promotions: { id: nil }).delete_all - add_foreign_key :spree_orders_promotions, :spree_promotions, column: :promotion_id, on_delete: :cascade - end - - def down - remove_foreign_key :spree_orders_promotions, :spree_promotions - end -end diff --git a/legacy_promotions/db/migrate/20160101010001_solidus_one_four_promotions.rb b/legacy_promotions/db/migrate/20160101010001_solidus_one_four_promotions.rb new file mode 100644 index 00000000000..70f4ee6ca19 --- /dev/null +++ b/legacy_promotions/db/migrate/20160101010001_solidus_one_four_promotions.rb @@ -0,0 +1,108 @@ +# frozen_string_literal: true + +class SolidusOneFourPromotions < ActiveRecord::Migration[5.0] + def up + create_table "spree_orders_promotions", force: :cascade do |t| + t.integer "order_id" + t.integer "promotion_id" + t.integer "promotion_code_id" + t.datetime "created_at", precision: 6 + t.datetime "updated_at", precision: 6 + t.index ["order_id", "promotion_id"], name: "index_spree_orders_promotions_on_order_id_and_promotion_id" + t.index ["promotion_code_id"], name: "index_spree_orders_promotions_on_promotion_code_id" + end + + create_table "spree_product_promotion_rules", force: :cascade do |t| + t.integer "product_id" + t.integer "promotion_rule_id" + t.datetime "created_at", precision: 6 + t.datetime "updated_at", precision: 6 + t.index ["product_id"], name: "index_products_promotion_rules_on_product_id" + t.index ["promotion_rule_id"], name: "index_products_promotion_rules_on_promotion_rule_id" + end + + create_table "spree_promotion_actions", force: :cascade do |t| + t.integer "promotion_id" + t.integer "position" + t.string "type" + t.datetime "deleted_at" + t.text "preferences" + t.datetime "created_at", precision: 6 + t.datetime "updated_at", precision: 6 + t.index ["deleted_at"], name: "index_spree_promotion_actions_on_deleted_at" + t.index ["id", "type"], name: "index_spree_promotion_actions_on_id_and_type" + t.index ["promotion_id"], name: "index_spree_promotion_actions_on_promotion_id" + end + + create_table "spree_promotion_categories", force: :cascade do |t| + t.string "name" + t.datetime "created_at", precision: 6 + t.datetime "updated_at", precision: 6 + t.string "code" + end + + create_table "spree_promotion_codes", force: :cascade do |t| + t.integer "promotion_id", null: false + t.string "value", null: false + t.datetime "created_at", precision: 6 + t.datetime "updated_at", precision: 6 + t.index ["promotion_id"], name: "index_spree_promotion_codes_on_promotion_id" + t.index ["value"], name: "index_spree_promotion_codes_on_value", unique: true + end + + create_table "spree_promotion_rule_taxons", force: :cascade do |t| + t.integer "taxon_id" + t.integer "promotion_rule_id" + t.datetime "created_at", precision: 6 + t.datetime "updated_at", precision: 6 + t.index ["promotion_rule_id"], name: "index_spree_promotion_rule_taxons_on_promotion_rule_id" + t.index ["taxon_id"], name: "index_spree_promotion_rule_taxons_on_taxon_id" + end + + create_table "spree_promotion_rules", force: :cascade do |t| + t.integer "promotion_id" + t.integer "product_group_id" + t.string "type" + t.datetime "created_at", precision: 6 + t.datetime "updated_at", precision: 6 + t.string "code" + t.text "preferences" + t.index ["product_group_id"], name: "index_promotion_rules_on_product_group_id" + t.index ["promotion_id"], name: "index_spree_promotion_rules_on_promotion_id" + end + + create_table "spree_promotion_rules_users", force: :cascade do |t| + t.integer "user_id" + t.integer "promotion_rule_id" + t.datetime "created_at", precision: 6 + t.datetime "updated_at", precision: 6 + t.index ["promotion_rule_id"], name: "index_promotion_rules_users_on_promotion_rule_id" + t.index ["user_id"], name: "index_promotion_rules_users_on_user_id" + end + + create_table "spree_promotions", force: :cascade do |t| + t.string "description" + t.datetime "expires_at" + t.datetime "starts_at" + t.string "name" + t.string "type" + t.integer "usage_limit" + t.string "match_policy", default: "all" + t.string "code" + t.boolean "advertise", default: false + t.string "path" + t.datetime "created_at", precision: 6 + t.datetime "updated_at", precision: 6 + t.integer "promotion_category_id" + t.integer "per_code_usage_limit" + t.boolean "apply_automatically", default: false + t.index ["advertise"], name: "index_spree_promotions_on_advertise" + t.index ["apply_automatically"], name: "index_spree_promotions_on_apply_automatically" + t.index ["code"], name: "index_spree_promotions_on_code" + t.index ["expires_at"], name: "index_spree_promotions_on_expires_at" + t.index ["id", "type"], name: "index_spree_promotions_on_id_and_type" + t.index ["promotion_category_id"], name: "index_spree_promotions_on_promotion_category_id" + t.index ["starts_at"], name: "index_spree_promotions_on_starts_at" + end + end +end diff --git a/core/db/migrate/20161017102621_create_spree_promotion_code_batch.rb b/legacy_promotions/db/migrate/20161017102621_create_spree_promotion_code_batch.rb similarity index 100% rename from core/db/migrate/20161017102621_create_spree_promotion_code_batch.rb rename to legacy_promotions/db/migrate/20161017102621_create_spree_promotion_code_batch.rb diff --git a/core/db/migrate/20180202190713_create_promotion_rule_stores.rb b/legacy_promotions/db/migrate/20180202190713_create_promotion_rule_stores.rb similarity index 100% rename from core/db/migrate/20180202190713_create_promotion_rule_stores.rb rename to legacy_promotions/db/migrate/20180202190713_create_promotion_rule_stores.rb diff --git a/core/db/migrate/20180328172631_add_join_characters_to_promotion_code_batch.rb b/legacy_promotions/db/migrate/20180328172631_add_join_characters_to_promotion_code_batch.rb similarity index 100% rename from core/db/migrate/20180328172631_add_join_characters_to_promotion_code_batch.rb rename to legacy_promotions/db/migrate/20180328172631_add_join_characters_to_promotion_code_batch.rb diff --git a/core/db/migrate/20190106184413_remove_code_from_spree_promotions.rb b/legacy_promotions/db/migrate/20190106184413_remove_code_from_spree_promotions.rb similarity index 77% rename from core/db/migrate/20190106184413_remove_code_from_spree_promotions.rb rename to legacy_promotions/db/migrate/20190106184413_remove_code_from_spree_promotions.rb index f4a03afa534..02ec7227a36 100644 --- a/core/db/migrate/20190106184413_remove_code_from_spree_promotions.rb +++ b/legacy_promotions/db/migrate/20190106184413_remove_code_from_spree_promotions.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'solidus/migrations/promotions_with_code_handlers' +require 'solidus_legacy_promotions/migrations/promotions_with_code_handlers' class RemoveCodeFromSpreePromotions < ActiveRecord::Migration[5.1] class Promotion < ActiveRecord::Base @@ -35,8 +35,8 @@ def self.promotions_with_code_handler # comment/uncomment the one then better fits you needs or use a # custom class or callable object. # - Solidus::Migrations::PromotionWithCodeHandlers::RaiseException - # Solidus::Migrations::PromotionWithCodeHandlers::MoveToSpreePromotionCode - # Solidus::Migrations::PromotionWithCodeHandlers::DoNothing + SolidusLegacyPromotions::Migrations::PromotionWithCodeHandlers::RaiseException + # SolidusLegacyPromotions::Migrations::PromotionWithCodeHandlers::MoveToSpreePromotionCode + # SolidusLegacyPromotions::Migrations::PromotionWithCodeHandlers::DoNothing end end diff --git a/core/db/migrate/20220317165036_set_promotions_with_any_policy_to_all_if_possible.rb b/legacy_promotions/db/migrate/20220317165036_set_promotions_with_any_policy_to_all_if_possible.rb similarity index 100% rename from core/db/migrate/20220317165036_set_promotions_with_any_policy_to_all_if_possible.rb rename to legacy_promotions/db/migrate/20220317165036_set_promotions_with_any_policy_to_all_if_possible.rb diff --git a/core/db/migrate/20230322085416_remove_match_policy_from_spree_promotion.rb b/legacy_promotions/db/migrate/20230322085416_remove_match_policy_from_spree_promotion.rb similarity index 100% rename from core/db/migrate/20230322085416_remove_match_policy_from_spree_promotion.rb rename to legacy_promotions/db/migrate/20230322085416_remove_match_policy_from_spree_promotion.rb diff --git a/core/db/migrate/20230325132905_remove_unused_columns_from_promotion_rules.rb b/legacy_promotions/db/migrate/20230325132905_remove_unused_columns_from_promotion_rules.rb similarity index 99% rename from core/db/migrate/20230325132905_remove_unused_columns_from_promotion_rules.rb rename to legacy_promotions/db/migrate/20230325132905_remove_unused_columns_from_promotion_rules.rb index 53b62a646c7..9c9227d2c07 100644 --- a/core/db/migrate/20230325132905_remove_unused_columns_from_promotion_rules.rb +++ b/legacy_promotions/db/migrate/20230325132905_remove_unused_columns_from_promotion_rules.rb @@ -1,6 +1,7 @@ class RemoveUnusedColumnsFromPromotionRules < ActiveRecord::Migration[5.2] def change remove_column :spree_promotion_rules, :code, :string + remove_column :spree_promotion_rules, :product_group_id, :integer end end diff --git a/core/db/migrate/20231027084517_add_order_promotions_foreign_key.rb b/legacy_promotions/db/migrate/20231027084517_add_order_promotions_foreign_key.rb similarity index 100% rename from core/db/migrate/20231027084517_add_order_promotions_foreign_key.rb rename to legacy_promotions/db/migrate/20231027084517_add_order_promotions_foreign_key.rb diff --git a/core/lib/solidus/migrations/promotions_with_code_handlers.rb b/legacy_promotions/lib/solidus_legacy_promotions/migrations/promotions_with_code_handlers.rb similarity index 95% rename from core/lib/solidus/migrations/promotions_with_code_handlers.rb rename to legacy_promotions/lib/solidus_legacy_promotions/migrations/promotions_with_code_handlers.rb index c89030e81c9..ea8d492a46b 100644 --- a/core/lib/solidus/migrations/promotions_with_code_handlers.rb +++ b/legacy_promotions/lib/solidus_legacy_promotions/migrations/promotions_with_code_handlers.rb @@ -1,9 +1,11 @@ # frozen_string_literal: true -module Solidus +require 'spree/core' + +module SolidusLegacyPromotions module Migrations module PromotionWithCodeHandlers - class PromotionCode < Spree::Base + class PromotionCode < ::Spree::Base self.table_name = "spree_promotion_codes" end From 3dd49bd9cb406060ee7fad6e6046dafe4f3ddac0 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 28 Feb 2024 08:55:43 +0100 Subject: [PATCH 053/834] Legacy promotions: Conditionally run migrations Since these promotions will be installed into all stores, we need to make sure they don't do anything to the existing promotion system. For new stores, they must do the things though. --- ...60101010001_solidus_one_four_promotions.rb | 188 ++++++++++-------- ...02621_create_spree_promotion_code_batch.rb | 64 +++--- ...0202190713_create_promotion_rule_stores.rb | 10 +- ...join_characters_to_promotion_code_batch.rb | 14 +- ...84413_remove_code_from_spree_promotions.rb | 26 ++- ...emove_match_policy_from_spree_promotion.rb | 4 +- ...ove_unused_columns_from_promotion_rules.rb | 9 +- ...084517_add_order_promotions_foreign_key.rb | 8 +- 8 files changed, 185 insertions(+), 138 deletions(-) diff --git a/legacy_promotions/db/migrate/20160101010001_solidus_one_four_promotions.rb b/legacy_promotions/db/migrate/20160101010001_solidus_one_four_promotions.rb index 70f4ee6ca19..f3970d5d351 100644 --- a/legacy_promotions/db/migrate/20160101010001_solidus_one_four_promotions.rb +++ b/legacy_promotions/db/migrate/20160101010001_solidus_one_four_promotions.rb @@ -2,107 +2,125 @@ class SolidusOneFourPromotions < ActiveRecord::Migration[5.0] def up - create_table "spree_orders_promotions", force: :cascade do |t| - t.integer "order_id" - t.integer "promotion_id" - t.integer "promotion_code_id" - t.datetime "created_at", precision: 6 - t.datetime "updated_at", precision: 6 - t.index ["order_id", "promotion_id"], name: "index_spree_orders_promotions_on_order_id_and_promotion_id" - t.index ["promotion_code_id"], name: "index_spree_orders_promotions_on_promotion_code_id" + unless table_exists?(:spree_orders_promotions) + create_table "spree_orders_promotions", force: :cascade do |t| + t.integer "order_id" + t.integer "promotion_id" + t.integer "promotion_code_id" + t.datetime "created_at", precision: 6 + t.datetime "updated_at", precision: 6 + t.index ["order_id", "promotion_id"], name: "index_spree_orders_promotions_on_order_id_and_promotion_id" + t.index ["promotion_code_id"], name: "index_spree_orders_promotions_on_promotion_code_id" + end end - create_table "spree_product_promotion_rules", force: :cascade do |t| - t.integer "product_id" - t.integer "promotion_rule_id" - t.datetime "created_at", precision: 6 - t.datetime "updated_at", precision: 6 - t.index ["product_id"], name: "index_products_promotion_rules_on_product_id" - t.index ["promotion_rule_id"], name: "index_products_promotion_rules_on_promotion_rule_id" + unless table_exists?(:spree_product_promotion_rules) + create_table "spree_product_promotion_rules", if_not_exists: true, force: :cascade do |t| + t.integer "product_id" + t.integer "promotion_rule_id" + t.datetime "created_at", precision: 6 + t.datetime "updated_at", precision: 6 + t.index ["product_id"], name: "index_products_promotion_rules_on_product_id" + t.index ["promotion_rule_id"], name: "index_products_promotion_rules_on_promotion_rule_id" + end end - create_table "spree_promotion_actions", force: :cascade do |t| - t.integer "promotion_id" - t.integer "position" - t.string "type" - t.datetime "deleted_at" - t.text "preferences" - t.datetime "created_at", precision: 6 - t.datetime "updated_at", precision: 6 - t.index ["deleted_at"], name: "index_spree_promotion_actions_on_deleted_at" - t.index ["id", "type"], name: "index_spree_promotion_actions_on_id_and_type" - t.index ["promotion_id"], name: "index_spree_promotion_actions_on_promotion_id" + unless table_exists?(:spree_promotion_actions) + create_table "spree_promotion_actions", if_not_exists: true, force: :cascade do |t| + t.integer "promotion_id" + t.integer "position" + t.string "type" + t.datetime "deleted_at" + t.text "preferences" + t.datetime "created_at", precision: 6 + t.datetime "updated_at", precision: 6 + t.index ["deleted_at"], name: "index_spree_promotion_actions_on_deleted_at" + t.index ["id", "type"], name: "index_spree_promotion_actions_on_id_and_type" + t.index ["promotion_id"], name: "index_spree_promotion_actions_on_promotion_id" + end end - create_table "spree_promotion_categories", force: :cascade do |t| - t.string "name" - t.datetime "created_at", precision: 6 - t.datetime "updated_at", precision: 6 - t.string "code" + unless table_exists?(:spree_promotion_categories) + create_table "spree_promotion_categories", if_not_exists: true, force: :cascade do |t| + t.string "name" + t.datetime "created_at", precision: 6 + t.datetime "updated_at", precision: 6 + t.string "code" + end end - create_table "spree_promotion_codes", force: :cascade do |t| - t.integer "promotion_id", null: false - t.string "value", null: false - t.datetime "created_at", precision: 6 - t.datetime "updated_at", precision: 6 - t.index ["promotion_id"], name: "index_spree_promotion_codes_on_promotion_id" - t.index ["value"], name: "index_spree_promotion_codes_on_value", unique: true + unless table_exists?(:spree_promotion_codes) + create_table "spree_promotion_codes", if_not_exists: true, force: :cascade do |t| + t.integer "promotion_id", null: false + t.string "value", null: false + t.datetime "created_at", precision: 6 + t.datetime "updated_at", precision: 6 + t.index ["promotion_id"], name: "index_spree_promotion_codes_on_promotion_id" + t.index ["value"], name: "index_spree_promotion_codes_on_value", unique: true + end end - create_table "spree_promotion_rule_taxons", force: :cascade do |t| - t.integer "taxon_id" - t.integer "promotion_rule_id" - t.datetime "created_at", precision: 6 - t.datetime "updated_at", precision: 6 - t.index ["promotion_rule_id"], name: "index_spree_promotion_rule_taxons_on_promotion_rule_id" - t.index ["taxon_id"], name: "index_spree_promotion_rule_taxons_on_taxon_id" + unless table_exists?(:spree_promotion_rule_taxons) + create_table "spree_promotion_rule_taxons", if_not_exists: true, force: :cascade do |t| + t.integer "taxon_id" + t.integer "promotion_rule_id" + t.datetime "created_at", precision: 6 + t.datetime "updated_at", precision: 6 + t.index ["promotion_rule_id"], name: "index_spree_promotion_rule_taxons_on_promotion_rule_id" + t.index ["taxon_id"], name: "index_spree_promotion_rule_taxons_on_taxon_id" + end end - create_table "spree_promotion_rules", force: :cascade do |t| - t.integer "promotion_id" - t.integer "product_group_id" - t.string "type" - t.datetime "created_at", precision: 6 - t.datetime "updated_at", precision: 6 - t.string "code" - t.text "preferences" - t.index ["product_group_id"], name: "index_promotion_rules_on_product_group_id" - t.index ["promotion_id"], name: "index_spree_promotion_rules_on_promotion_id" + unless table_exists?(:spree_promotion_rules) + create_table "spree_promotion_rules", if_not_exists: true, force: :cascade do |t| + t.integer "promotion_id" + t.integer "product_group_id" + t.string "type" + t.datetime "created_at", precision: 6 + t.datetime "updated_at", precision: 6 + t.string "code" + t.text "preferences" + t.index ["product_group_id"], name: "index_promotion_rules_on_product_group_id" + t.index ["promotion_id"], name: "index_spree_promotion_rules_on_promotion_id" + end end - create_table "spree_promotion_rules_users", force: :cascade do |t| - t.integer "user_id" - t.integer "promotion_rule_id" - t.datetime "created_at", precision: 6 - t.datetime "updated_at", precision: 6 - t.index ["promotion_rule_id"], name: "index_promotion_rules_users_on_promotion_rule_id" - t.index ["user_id"], name: "index_promotion_rules_users_on_user_id" + unless table_exists?(:spree_promotion_rules_users) + create_table "spree_promotion_rules_users", if_not_exists: true, force: :cascade do |t| + t.integer "user_id" + t.integer "promotion_rule_id" + t.datetime "created_at", precision: 6 + t.datetime "updated_at", precision: 6 + t.index ["promotion_rule_id"], name: "index_promotion_rules_users_on_promotion_rule_id" + t.index ["user_id"], name: "index_promotion_rules_users_on_user_id" + end end - create_table "spree_promotions", force: :cascade do |t| - t.string "description" - t.datetime "expires_at" - t.datetime "starts_at" - t.string "name" - t.string "type" - t.integer "usage_limit" - t.string "match_policy", default: "all" - t.string "code" - t.boolean "advertise", default: false - t.string "path" - t.datetime "created_at", precision: 6 - t.datetime "updated_at", precision: 6 - t.integer "promotion_category_id" - t.integer "per_code_usage_limit" - t.boolean "apply_automatically", default: false - t.index ["advertise"], name: "index_spree_promotions_on_advertise" - t.index ["apply_automatically"], name: "index_spree_promotions_on_apply_automatically" - t.index ["code"], name: "index_spree_promotions_on_code" - t.index ["expires_at"], name: "index_spree_promotions_on_expires_at" - t.index ["id", "type"], name: "index_spree_promotions_on_id_and_type" - t.index ["promotion_category_id"], name: "index_spree_promotions_on_promotion_category_id" - t.index ["starts_at"], name: "index_spree_promotions_on_starts_at" + unless table_exists?(:spree_promotions) + create_table "spree_promotions", if_not_exists: true, force: :cascade do |t| + t.string "description" + t.datetime "expires_at" + t.datetime "starts_at" + t.string "name" + t.string "type" + t.integer "usage_limit" + t.string "match_policy", default: "all" + t.string "code" + t.boolean "advertise", default: false + t.string "path" + t.datetime "created_at", precision: 6 + t.datetime "updated_at", precision: 6 + t.integer "promotion_category_id" + t.integer "per_code_usage_limit" + t.boolean "apply_automatically", default: false + t.index ["advertise"], name: "index_spree_promotions_on_advertise" + t.index ["apply_automatically"], name: "index_spree_promotions_on_apply_automatically" + t.index ["code"], name: "index_spree_promotions_on_code" + t.index ["expires_at"], name: "index_spree_promotions_on_expires_at" + t.index ["id", "type"], name: "index_spree_promotions_on_id_and_type" + t.index ["promotion_category_id"], name: "index_spree_promotions_on_promotion_category_id" + t.index ["starts_at"], name: "index_spree_promotions_on_starts_at" + end end end end diff --git a/legacy_promotions/db/migrate/20161017102621_create_spree_promotion_code_batch.rb b/legacy_promotions/db/migrate/20161017102621_create_spree_promotion_code_batch.rb index 4f1cd918132..4fbdee62651 100644 --- a/legacy_promotions/db/migrate/20161017102621_create_spree_promotion_code_batch.rb +++ b/legacy_promotions/db/migrate/20161017102621_create_spree_promotion_code_batch.rb @@ -2,37 +2,47 @@ class CreateSpreePromotionCodeBatch < ActiveRecord::Migration[5.0] def change - create_table :spree_promotion_code_batches do |t| - t.references :promotion, null: false, index: true - t.string :base_code, null: false - t.integer :number_of_codes, null: false - t.string :email - t.string :error - t.string :state, default: "pending" - t.timestamps precision: 6 + unless table_exists?(:spree_promotion_code_batches) + create_table :spree_promotion_code_batches do |t| + t.references :promotion, null: false, index: true, type: :integer + t.string :base_code, null: false + t.integer :number_of_codes, null: false + t.string :email + t.string :error + t.string :state, default: "pending" + t.timestamps precision: 6 + end end - add_foreign_key( - :spree_promotion_code_batches, - :spree_promotions, - column: :promotion_id - ) + unless foreign_key_exists?(:spree_promotion_code_batches, :spree_promotions) + add_foreign_key( + :spree_promotion_code_batches, + :spree_promotions, + column: :promotion_id + ) + end - add_column( - :spree_promotion_codes, - :promotion_code_batch_id, - :integer - ) + unless column_exists?(:spree_promotion_codes, :promotion_code_batch_id) + add_column( + :spree_promotion_codes, + :promotion_code_batch_id, + :integer, + ) + end - add_foreign_key( - :spree_promotion_codes, - :spree_promotion_code_batches, - column: :promotion_code_batch_id - ) + unless foreign_key_exists?(:spree_promotion_codes, :spree_promotion_code_batches) + add_foreign_key( + :spree_promotion_codes, + :spree_promotion_code_batches, + column: :promotion_code_batch_id, + ) + end - add_index( - :spree_promotion_codes, - :promotion_code_batch_id - ) + unless index_exists?(:spree_promotion_codes, :promotion_code_batch_id) + add_index( + :spree_promotion_codes, + :promotion_code_batch_id, + ) + end end end diff --git a/legacy_promotions/db/migrate/20180202190713_create_promotion_rule_stores.rb b/legacy_promotions/db/migrate/20180202190713_create_promotion_rule_stores.rb index 62ee3fe06c1..3adb3e5cd68 100644 --- a/legacy_promotions/db/migrate/20180202190713_create_promotion_rule_stores.rb +++ b/legacy_promotions/db/migrate/20180202190713_create_promotion_rule_stores.rb @@ -2,11 +2,13 @@ class CreatePromotionRuleStores < ActiveRecord::Migration[5.1] def change - create_table :spree_promotion_rules_stores do |t| - t.references :store, null: false - t.references :promotion_rule, null: false + unless table_exists?(:spree_promotion_rules_stores) + create_table :spree_promotion_rules_stores do |t| + t.references :store, null: false + t.references :promotion_rule, null: false - t.timestamps + t.timestamps + end end end end diff --git a/legacy_promotions/db/migrate/20180328172631_add_join_characters_to_promotion_code_batch.rb b/legacy_promotions/db/migrate/20180328172631_add_join_characters_to_promotion_code_batch.rb index eb53564b988..5a38d16bdc4 100644 --- a/legacy_promotions/db/migrate/20180328172631_add_join_characters_to_promotion_code_batch.rb +++ b/legacy_promotions/db/migrate/20180328172631_add_join_characters_to_promotion_code_batch.rb @@ -2,10 +2,14 @@ class AddJoinCharactersToPromotionCodeBatch < ActiveRecord::Migration[5.1] def change - add_column(:spree_promotion_code_batches, - :join_characters, - :string, - null: false, - default: '_') + unless column_exists?(:spree_promotion_code_batches, :join_characters) + add_column( + :spree_promotion_code_batches, + :join_characters, + :string, + null: false, + default: "_" + ) + end end end diff --git a/legacy_promotions/db/migrate/20190106184413_remove_code_from_spree_promotions.rb b/legacy_promotions/db/migrate/20190106184413_remove_code_from_spree_promotions.rb index 02ec7227a36..76465b135df 100644 --- a/legacy_promotions/db/migrate/20190106184413_remove_code_from_spree_promotions.rb +++ b/legacy_promotions/db/migrate/20190106184413_remove_code_from_spree_promotions.rb @@ -9,22 +9,26 @@ class Promotion < ActiveRecord::Base end def up - promotions_with_code = Promotion.where.not(code: [nil, '']) + if column_exists?(:spree_promotions, :code) + promotions_with_code = Promotion.where.not(code: [nil, '']) - if promotions_with_code.any? - # You have some promotions with "code" field present! This is not good - # since we are going to remove that column. - # - self.class.promotions_with_code_handler.new(self, promotions_with_code).call - end + if promotions_with_code.any? + # You have some promotions with "code" field present! This is not good + # since we are going to remove that column. + # + self.class.promotions_with_code_handler.new(self, promotions_with_code).call + end - remove_index :spree_promotions, name: :index_spree_promotions_on_code - remove_column :spree_promotions, :code + remove_index :spree_promotions, name: :index_spree_promotions_on_code + remove_column :spree_promotions, :code + end end def down - add_column :spree_promotions, :code, :string - add_index :spree_promotions, :code, name: :index_spree_promotions_on_code + unless column_exists?(:spree_promotions, :code) + add_column :spree_promotions, :code, :string + add_index :spree_promotions, :code, name: :index_spree_promotions_on_code + end end def self.promotions_with_code_handler diff --git a/legacy_promotions/db/migrate/20230322085416_remove_match_policy_from_spree_promotion.rb b/legacy_promotions/db/migrate/20230322085416_remove_match_policy_from_spree_promotion.rb index 39cdc474a97..235076e7257 100644 --- a/legacy_promotions/db/migrate/20230322085416_remove_match_policy_from_spree_promotion.rb +++ b/legacy_promotions/db/migrate/20230322085416_remove_match_policy_from_spree_promotion.rb @@ -1,5 +1,7 @@ class RemoveMatchPolicyFromSpreePromotion < ActiveRecord::Migration[5.2] def change - remove_column :spree_promotions, :match_policy, :string, default: "all" + if column_exists?(:spree_promotions, :match_policy) + remove_column :spree_promotions, :match_policy, :string, default: "all" + end end end diff --git a/legacy_promotions/db/migrate/20230325132905_remove_unused_columns_from_promotion_rules.rb b/legacy_promotions/db/migrate/20230325132905_remove_unused_columns_from_promotion_rules.rb index 9c9227d2c07..9207ebddd70 100644 --- a/legacy_promotions/db/migrate/20230325132905_remove_unused_columns_from_promotion_rules.rb +++ b/legacy_promotions/db/migrate/20230325132905_remove_unused_columns_from_promotion_rules.rb @@ -1,7 +1,10 @@ class RemoveUnusedColumnsFromPromotionRules < ActiveRecord::Migration[5.2] def change - remove_column :spree_promotion_rules, :code, :string - - remove_column :spree_promotion_rules, :product_group_id, :integer + if column_exists?(:spree_promotion_rules, :code) + remove_column :spree_promotion_rules, :code, :string + end + if column_exists?(:spree_promotion_rules, :product_group_id) + remove_column :spree_promotion_rules, :product_group_id, :integer + end end end diff --git a/legacy_promotions/db/migrate/20231027084517_add_order_promotions_foreign_key.rb b/legacy_promotions/db/migrate/20231027084517_add_order_promotions_foreign_key.rb index 8660de79afc..7057a3bbb7a 100644 --- a/legacy_promotions/db/migrate/20231027084517_add_order_promotions_foreign_key.rb +++ b/legacy_promotions/db/migrate/20231027084517_add_order_promotions_foreign_key.rb @@ -1,10 +1,14 @@ class AddOrderPromotionsForeignKey < ActiveRecord::Migration[7.0] def up Spree::OrderPromotion.left_joins(:order).where(spree_orders: { id: nil }).delete_all - add_foreign_key :spree_orders_promotions, :spree_orders, column: :order_id, validate: false, on_delete: :cascade + unless foreign_key_exists?(:spree_orders_promotions, :spree_orders, column: :order_id) + add_foreign_key :spree_orders_promotions, :spree_orders, column: :order_id, validate: false, on_delete: :cascade + end end def down - remove_foreign_key :spree_orders_promotions, :spree_orders + if foreign_key_exists?(:spree_orders_promotions, :spree_orders, column: :order_id) + remove_foreign_key :spree_orders_promotions, :spree_orders_promotions + end end end From a8c99c968ed27b434352cea27fd22880b153404e Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 26 Jan 2024 18:29:38 +0100 Subject: [PATCH 054/834] Move promotion-specific code from product.rb to gem We need to purge references to Solidus' legacy promotion system from the core. --- core/app/models/spree/product.rb | 4 --- core/spec/models/spree/product_spec.rb | 10 ------ .../models/spree_product_decorator.rb | 16 +++++++++ .../spec/models/spree/product_spec.rb | 34 +++++++++++++++++++ 4 files changed, 50 insertions(+), 14 deletions(-) create mode 100644 legacy_promotions/app/decorators/solidus_legacy_promotions/models/spree_product_decorator.rb create mode 100644 legacy_promotions/spec/models/spree/product_spec.rb diff --git a/core/app/models/spree/product.rb b/core/app/models/spree/product.rb index fe13cc87546..107a8767597 100644 --- a/core/app/models/spree/product.rb +++ b/core/app/models/spree/product.rb @@ -16,7 +16,6 @@ class Product < Spree::Base self.product_option_types = [] self.product_properties = [] self.classifications.destroy_all - self.product_promotion_rules = [] end has_many :product_option_types, dependent: :destroy, inverse_of: :product @@ -31,9 +30,6 @@ class Product < Spree::Base has_many :classifications, dependent: :delete_all, inverse_of: :product has_many :taxons, through: :classifications, before_remove: :remove_taxon - has_many :product_promotion_rules, dependent: :destroy - has_many :promotion_rules, through: :product_promotion_rules - belongs_to :tax_category, class_name: 'Spree::TaxCategory', optional: true belongs_to :shipping_category, class_name: 'Spree::ShippingCategory', inverse_of: :products, optional: true diff --git a/core/spec/models/spree/product_spec.rb b/core/spec/models/spree/product_spec.rb index a664c0cc757..19f6752731c 100644 --- a/core/spec/models/spree/product_spec.rb +++ b/core/spec/models/spree/product_spec.rb @@ -416,16 +416,6 @@ class Extension < Spree::Base expect(product.properties).to be_empty end - it "removes from product promotion rules" do - promotion = create(:promotion) - rule = promotion.rules.create!(type: 'Spree::Promotion::Rules::Product', products: [product]) - - product.discard - - rule.reload - expect(rule.products).to be_empty - end - it "replaces the slug" do product.discard diff --git a/legacy_promotions/app/decorators/solidus_legacy_promotions/models/spree_product_decorator.rb b/legacy_promotions/app/decorators/solidus_legacy_promotions/models/spree_product_decorator.rb new file mode 100644 index 00000000000..73b52cb7aff --- /dev/null +++ b/legacy_promotions/app/decorators/solidus_legacy_promotions/models/spree_product_decorator.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module SolidusLegacyPromotions + module SpreeProductDecorator + def self.prepended(base) + base.has_many :product_promotion_rules, dependent: :destroy + base.has_many :promotion_rules, through: :product_promotion_rules + + base.after_discard do + self.product_promotion_rules = [] + end + end + + ::Spree::Product.prepend self + end +end diff --git a/legacy_promotions/spec/models/spree/product_spec.rb b/legacy_promotions/spec/models/spree/product_spec.rb new file mode 100644 index 00000000000..a2623be5fb3 --- /dev/null +++ b/legacy_promotions/spec/models/spree/product_spec.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe Spree::Product do + describe "#discard" do + let(:product) { create(:product, slug: "my-awesome-product") } + it "removes from product promotion rules" do + promotion = create(:promotion) + rule = promotion.rules.create!(type: "Spree::Promotion::Rules::Product", products: [product]) + + product.discard + + rule.reload + expect(rule.products).to be_empty + end + end + + # Regression test for https://github.com/spree/spree/issues/4416 + context "#possible_promotions" do + let(:product) { create(:product) } + let!(:promotion) { create(:promotion, :with_action, advertise: true, starts_at: 1.day.ago) } + let!(:rule) do + Spree::Promotion::Rules::Product.create( + promotion: promotion, + products: [product] + ) + end + + it "lists the promotion as a possible promotion" do + expect(product.possible_promotions).to include(promotion) + end + end +end From 3fd075a99172a0fd80ea01530ba4791836fb8067 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 26 Jan 2024 19:25:46 +0100 Subject: [PATCH 055/834] Move OrderPromotion class to legacy_promotions --- {core => legacy_promotions}/app/models/spree/order_promotion.rb | 0 .../spec/models/spree/order_promotion_spec.rb | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {core => legacy_promotions}/app/models/spree/order_promotion.rb (100%) rename {core => legacy_promotions}/spec/models/spree/order_promotion_spec.rb (100%) diff --git a/core/app/models/spree/order_promotion.rb b/legacy_promotions/app/models/spree/order_promotion.rb similarity index 100% rename from core/app/models/spree/order_promotion.rb rename to legacy_promotions/app/models/spree/order_promotion.rb diff --git a/core/spec/models/spree/order_promotion_spec.rb b/legacy_promotions/spec/models/spree/order_promotion_spec.rb similarity index 100% rename from core/spec/models/spree/order_promotion_spec.rb rename to legacy_promotions/spec/models/spree/order_promotion_spec.rb From 46eccefd7631f9b77811fcfa6ba63df3c9a790f2 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 26 Jan 2024 22:54:29 +0100 Subject: [PATCH 056/834] Move promotion calculators to legacy_promotions Except for the flat rate calculator, as that one is so generic it's often just used as a stand-in calculator. --- core/spec/lib/calculated_adjustments_spec.rb | 8 ++++---- .../lib/spree/core/environment/calculators_spec.rb | 12 ++++++------ .../models/spree/calculator/distributed_amount.rb | 0 .../spree/calculator/flat_percent_item_total.rb | 0 .../app/models/spree/calculator/flexi_rate.rb | 0 .../models/spree/calculator/percent_on_line_item.rb | 0 .../app/models/spree/calculator/tiered_flat_rate.rb | 0 .../app/models/spree/calculator/tiered_percent.rb | 0 .../spree/calculator/distributed_amount_spec.rb | 0 .../spree/calculator/flat_percent_item_total_spec.rb | 0 .../spec/models/spree/calculator/flexi_rate_spec.rb | 0 .../spree/calculator/percent_on_line_item_spec.rb | 0 .../models/spree/calculator/tiered_flat_rate_spec.rb | 0 .../models/spree/calculator/tiered_percent_spec.rb | 0 14 files changed, 10 insertions(+), 10 deletions(-) rename {core => legacy_promotions}/app/models/spree/calculator/distributed_amount.rb (100%) rename {core => legacy_promotions}/app/models/spree/calculator/flat_percent_item_total.rb (100%) rename {core => legacy_promotions}/app/models/spree/calculator/flexi_rate.rb (100%) rename {core => legacy_promotions}/app/models/spree/calculator/percent_on_line_item.rb (100%) rename {core => legacy_promotions}/app/models/spree/calculator/tiered_flat_rate.rb (100%) rename {core => legacy_promotions}/app/models/spree/calculator/tiered_percent.rb (100%) rename {core => legacy_promotions}/spec/models/spree/calculator/distributed_amount_spec.rb (100%) rename {core => legacy_promotions}/spec/models/spree/calculator/flat_percent_item_total_spec.rb (100%) rename {core => legacy_promotions}/spec/models/spree/calculator/flexi_rate_spec.rb (100%) rename {core => legacy_promotions}/spec/models/spree/calculator/percent_on_line_item_spec.rb (100%) rename {core => legacy_promotions}/spec/models/spree/calculator/tiered_flat_rate_spec.rb (100%) rename {core => legacy_promotions}/spec/models/spree/calculator/tiered_percent_spec.rb (100%) diff --git a/core/spec/lib/calculated_adjustments_spec.rb b/core/spec/lib/calculated_adjustments_spec.rb index 8d10940b639..6ba6c6b3c17 100644 --- a/core/spec/lib/calculated_adjustments_spec.rb +++ b/core/spec/lib/calculated_adjustments_spec.rb @@ -3,7 +3,7 @@ require 'rails_helper' RSpec.describe Spree::CalculatedAdjustments do - let(:calculator_class) { Spree::Calculator::FlatRate } + let(:calculator_class) { Spree::Calculator::Shipping::FlatRate } # RESOURCE FIXTURE before(:all) do @@ -120,13 +120,13 @@ class Calculable < ActiveRecord::Base end it "can update both calculator type and attributes" do - subject.update!(calculator_type: 'Spree::Calculator::FlexiRate', calculator_attributes: { + subject.update!(calculator_type: 'Spree::Calculator::Shipping::FlexiRate', calculator_attributes: { preferred_first_item: 123 }) - expect(subject.calculator.class).to eq(Spree::Calculator::FlexiRate) + expect(subject.calculator.class).to eq(Spree::Calculator::Shipping::FlexiRate) expect(subject.calculator.preferred_first_item).to eq(123) subject.reload - expect(subject.calculator.class).to eq(Spree::Calculator::FlexiRate) + expect(subject.calculator.class).to eq(Spree::Calculator::Shipping::FlexiRate) expect(subject.calculator.preferred_first_item).to eq(123) end end diff --git a/core/spec/lib/spree/core/environment/calculators_spec.rb b/core/spec/lib/spree/core/environment/calculators_spec.rb index 8d428bcaf01..86c66ef7b71 100644 --- a/core/spec/lib/spree/core/environment/calculators_spec.rb +++ b/core/spec/lib/spree/core/environment/calculators_spec.rb @@ -41,8 +41,8 @@ it "can be set to a new value" do previous_value = subject.promotion_actions_create_adjustments - subject.promotion_actions_create_adjustments = ["Spree::Calculator::FlatRate"] - expect(subject.promotion_actions_create_adjustments).to include(Spree::Calculator::FlatRate) + subject.promotion_actions_create_adjustments = ["Spree::Calculator::Shipping::FlatRate"] + expect(subject.promotion_actions_create_adjustments).to include(Spree::Calculator::Shipping::FlatRate) subject.promotion_actions_create_adjustments = previous_value end end @@ -54,8 +54,8 @@ it "can be set to a new value" do previous_value = subject.promotion_actions_create_item_adjustments - subject.promotion_actions_create_item_adjustments = ["Spree::Calculator::FlatRate"] - expect(subject.promotion_actions_create_item_adjustments).to include(Spree::Calculator::FlatRate) + subject.promotion_actions_create_item_adjustments = ["Spree::Calculator::Shipping::FlatRate"] + expect(subject.promotion_actions_create_item_adjustments).to include(Spree::Calculator::Shipping::FlatRate) subject.promotion_actions_create_item_adjustments = previous_value end end @@ -66,8 +66,8 @@ it "can be set to a new value" do previous_value = subject.promotion_actions_create_quantity_adjustments - subject.promotion_actions_create_quantity_adjustments = ["Spree::Calculator::FlatRate"] - expect(subject.promotion_actions_create_quantity_adjustments).to include(Spree::Calculator::FlatRate) + subject.promotion_actions_create_quantity_adjustments = ["Spree::Calculator::Shipping::FlatRate"] + expect(subject.promotion_actions_create_quantity_adjustments).to include(Spree::Calculator::Shipping::FlatRate) subject.promotion_actions_create_quantity_adjustments = previous_value end end diff --git a/core/app/models/spree/calculator/distributed_amount.rb b/legacy_promotions/app/models/spree/calculator/distributed_amount.rb similarity index 100% rename from core/app/models/spree/calculator/distributed_amount.rb rename to legacy_promotions/app/models/spree/calculator/distributed_amount.rb diff --git a/core/app/models/spree/calculator/flat_percent_item_total.rb b/legacy_promotions/app/models/spree/calculator/flat_percent_item_total.rb similarity index 100% rename from core/app/models/spree/calculator/flat_percent_item_total.rb rename to legacy_promotions/app/models/spree/calculator/flat_percent_item_total.rb diff --git a/core/app/models/spree/calculator/flexi_rate.rb b/legacy_promotions/app/models/spree/calculator/flexi_rate.rb similarity index 100% rename from core/app/models/spree/calculator/flexi_rate.rb rename to legacy_promotions/app/models/spree/calculator/flexi_rate.rb diff --git a/core/app/models/spree/calculator/percent_on_line_item.rb b/legacy_promotions/app/models/spree/calculator/percent_on_line_item.rb similarity index 100% rename from core/app/models/spree/calculator/percent_on_line_item.rb rename to legacy_promotions/app/models/spree/calculator/percent_on_line_item.rb diff --git a/core/app/models/spree/calculator/tiered_flat_rate.rb b/legacy_promotions/app/models/spree/calculator/tiered_flat_rate.rb similarity index 100% rename from core/app/models/spree/calculator/tiered_flat_rate.rb rename to legacy_promotions/app/models/spree/calculator/tiered_flat_rate.rb diff --git a/core/app/models/spree/calculator/tiered_percent.rb b/legacy_promotions/app/models/spree/calculator/tiered_percent.rb similarity index 100% rename from core/app/models/spree/calculator/tiered_percent.rb rename to legacy_promotions/app/models/spree/calculator/tiered_percent.rb diff --git a/core/spec/models/spree/calculator/distributed_amount_spec.rb b/legacy_promotions/spec/models/spree/calculator/distributed_amount_spec.rb similarity index 100% rename from core/spec/models/spree/calculator/distributed_amount_spec.rb rename to legacy_promotions/spec/models/spree/calculator/distributed_amount_spec.rb diff --git a/core/spec/models/spree/calculator/flat_percent_item_total_spec.rb b/legacy_promotions/spec/models/spree/calculator/flat_percent_item_total_spec.rb similarity index 100% rename from core/spec/models/spree/calculator/flat_percent_item_total_spec.rb rename to legacy_promotions/spec/models/spree/calculator/flat_percent_item_total_spec.rb diff --git a/core/spec/models/spree/calculator/flexi_rate_spec.rb b/legacy_promotions/spec/models/spree/calculator/flexi_rate_spec.rb similarity index 100% rename from core/spec/models/spree/calculator/flexi_rate_spec.rb rename to legacy_promotions/spec/models/spree/calculator/flexi_rate_spec.rb diff --git a/core/spec/models/spree/calculator/percent_on_line_item_spec.rb b/legacy_promotions/spec/models/spree/calculator/percent_on_line_item_spec.rb similarity index 100% rename from core/spec/models/spree/calculator/percent_on_line_item_spec.rb rename to legacy_promotions/spec/models/spree/calculator/percent_on_line_item_spec.rb diff --git a/core/spec/models/spree/calculator/tiered_flat_rate_spec.rb b/legacy_promotions/spec/models/spree/calculator/tiered_flat_rate_spec.rb similarity index 100% rename from core/spec/models/spree/calculator/tiered_flat_rate_spec.rb rename to legacy_promotions/spec/models/spree/calculator/tiered_flat_rate_spec.rb diff --git a/core/spec/models/spree/calculator/tiered_percent_spec.rb b/legacy_promotions/spec/models/spree/calculator/tiered_percent_spec.rb similarity index 100% rename from core/spec/models/spree/calculator/tiered_percent_spec.rb rename to legacy_promotions/spec/models/spree/calculator/tiered_percent_spec.rb From 2a4c53c5330aa02a4225d88318a2b320ed3e1017 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 26 Jan 2024 23:29:02 +0100 Subject: [PATCH 057/834] Move promotion permission sets to legacy_promotions In legacy_promotions, we'll just keep them in `app`, where they're tested, too. --- core/lib/spree/permission_sets.rb | 2 -- .../app/models}/spree/permission_sets/promotion_display.rb | 0 .../app/models}/spree/permission_sets/promotion_management.rb | 0 .../spec/models/spree/permission_sets/promotion_display_spec.rb | 0 .../models/spree/permission_sets/promotion_management_spec.rb | 0 5 files changed, 2 deletions(-) rename {core/lib => legacy_promotions/app/models}/spree/permission_sets/promotion_display.rb (100%) rename {core/lib => legacy_promotions/app/models}/spree/permission_sets/promotion_management.rb (100%) rename {core => legacy_promotions}/spec/models/spree/permission_sets/promotion_display_spec.rb (100%) rename {core => legacy_promotions}/spec/models/spree/permission_sets/promotion_management_spec.rb (100%) diff --git a/core/lib/spree/permission_sets.rb b/core/lib/spree/permission_sets.rb index 95786aaf079..8c4d80f7fb8 100644 --- a/core/lib/spree/permission_sets.rb +++ b/core/lib/spree/permission_sets.rb @@ -9,8 +9,6 @@ require 'spree/permission_sets/order_management' require 'spree/permission_sets/product_display' require 'spree/permission_sets/product_management' -require 'spree/permission_sets/promotion_display' -require 'spree/permission_sets/promotion_management' require 'spree/permission_sets/restricted_stock_display' require 'spree/permission_sets/restricted_stock_management' require 'spree/permission_sets/stock_display' diff --git a/core/lib/spree/permission_sets/promotion_display.rb b/legacy_promotions/app/models/spree/permission_sets/promotion_display.rb similarity index 100% rename from core/lib/spree/permission_sets/promotion_display.rb rename to legacy_promotions/app/models/spree/permission_sets/promotion_display.rb diff --git a/core/lib/spree/permission_sets/promotion_management.rb b/legacy_promotions/app/models/spree/permission_sets/promotion_management.rb similarity index 100% rename from core/lib/spree/permission_sets/promotion_management.rb rename to legacy_promotions/app/models/spree/permission_sets/promotion_management.rb diff --git a/core/spec/models/spree/permission_sets/promotion_display_spec.rb b/legacy_promotions/spec/models/spree/permission_sets/promotion_display_spec.rb similarity index 100% rename from core/spec/models/spree/permission_sets/promotion_display_spec.rb rename to legacy_promotions/spec/models/spree/permission_sets/promotion_display_spec.rb diff --git a/core/spec/models/spree/permission_sets/promotion_management_spec.rb b/legacy_promotions/spec/models/spree/permission_sets/promotion_management_spec.rb similarity index 100% rename from core/spec/models/spree/permission_sets/promotion_management_spec.rb rename to legacy_promotions/spec/models/spree/permission_sets/promotion_management_spec.rb From 5547c4dbec3bee4eba073ca8f0afa4c28cdfe7be Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Sat, 27 Jan 2024 13:05:14 +0100 Subject: [PATCH 058/834] Move completed order with promotion factory to legacy_promotions --- .../factories/order_factory.rb | 19 ------- .../factories/order_factory_spec.rb | 40 --------------- .../factories/order_factory.rb | 22 +++++++++ .../factories/order_factory_spec.rb | 49 +++++++++++++++++++ 4 files changed, 71 insertions(+), 59 deletions(-) create mode 100644 legacy_promotions/lib/solidus_legacy_promotions/testing_support/factories/order_factory.rb create mode 100644 legacy_promotions/spec/lib/solidus_legacy_promotions/testing_support/factories/order_factory_spec.rb diff --git a/core/lib/spree/testing_support/factories/order_factory.rb b/core/lib/spree/testing_support/factories/order_factory.rb index 47bc422ea99..d6e45999e26 100644 --- a/core/lib/spree/testing_support/factories/order_factory.rb +++ b/core/lib/spree/testing_support/factories/order_factory.rb @@ -54,25 +54,6 @@ order.recalculate end - factory :completed_order_with_promotion do - transient do - completed_at { Time.current } - promotion { nil } - end - - after(:create) do |order, evaluator| - promotion = evaluator.promotion || create(:promotion, code: "test") - promotion_code = promotion.codes.first || create(:promotion_code, promotion: promotion) - - promotion.activate(order: order, promotion_code: promotion_code) - order.order_promotions.create!(promotion: promotion, promotion_code: promotion_code) - - # Complete the order after the promotion has been activated - order.update_column(:completed_at, evaluator.completed_at) - order.update_column(:state, "complete") - end - end - factory :order_ready_to_complete do state { 'confirm' } payment_state { 'checkout' } diff --git a/core/spec/lib/spree/core/testing_support/factories/order_factory_spec.rb b/core/spec/lib/spree/core/testing_support/factories/order_factory_spec.rb index b0190bc7f45..3a85bdcd047 100644 --- a/core/spec/lib/spree/core/testing_support/factories/order_factory_spec.rb +++ b/core/spec/lib/spree/core/testing_support/factories/order_factory_spec.rb @@ -76,46 +76,6 @@ it_behaves_like 'shipping methods are assigned' end - describe 'completed order with promotion' do - let(:factory) { :completed_order_with_promotion } - - it_behaves_like 'a working factory' - it_behaves_like 'an order with line items factory', "complete", "on_hand" - it_behaves_like 'shipping methods are assigned' - it_behaves_like 'supplied completed_at is respected' - - it "has the expected attributes" do - order = create(factory) - aggregate_failures do - expect(order).to be_completed - expect(order).to be_complete - - expect(order.order_promotions.count).to eq(1) - order_promotion = order.order_promotions[0] - expect(order_promotion.promotion_code.promotion).to eq order_promotion.promotion - end - end - - context 'with a promotion with an action' do - let(:promotion) { create(:promotion, :with_line_item_adjustment) } - it "has the expected attributes" do - order = create(factory, promotion: promotion) - aggregate_failures do - expect(order).to be_completed - expect(order).to be_complete - - expect(order.line_items[0].adjustments.count).to eq 1 - adjustment = order.line_items[0].adjustments[0] - expect(adjustment).to have_attributes( - amount: -10, - eligible: true, - order_id: order.id - ) - end - end - end - end - describe 'order ready to complete' do let(:factory) { :order_ready_to_complete } diff --git a/legacy_promotions/lib/solidus_legacy_promotions/testing_support/factories/order_factory.rb b/legacy_promotions/lib/solidus_legacy_promotions/testing_support/factories/order_factory.rb new file mode 100644 index 00000000000..ce3eb69cb2d --- /dev/null +++ b/legacy_promotions/lib/solidus_legacy_promotions/testing_support/factories/order_factory.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :completed_order_with_promotion, parent: :order_with_line_items do + transient do + completed_at { Time.current } + promotion { nil } + end + + after(:create) do |order, evaluator| + promotion = evaluator.promotion || create(:promotion, code: "test") + promotion_code = promotion.codes.first || create(:promotion_code, promotion: promotion) + + promotion.activate(order: order, promotion_code: promotion_code) + order.order_promotions.create!(promotion: promotion, promotion_code: promotion_code) + + # Complete the order after the promotion has been activated + order.update_column(:completed_at, evaluator.completed_at) + order.update_column(:state, "complete") + end + end +end diff --git a/legacy_promotions/spec/lib/solidus_legacy_promotions/testing_support/factories/order_factory_spec.rb b/legacy_promotions/spec/lib/solidus_legacy_promotions/testing_support/factories/order_factory_spec.rb new file mode 100644 index 00000000000..37629e82bd9 --- /dev/null +++ b/legacy_promotions/spec/lib/solidus_legacy_promotions/testing_support/factories/order_factory_spec.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require 'rails_helper' +require "spree/testing_support/shared_examples/order_factory" +require 'spree/testing_support/shared_examples/working_factory' + +RSpec.describe 'order factory' do + let(:factory_class) { Spree::Order } + + describe 'completed order with promotion' do + let(:factory) { :completed_order_with_promotion } + + it_behaves_like 'a working factory' + it_behaves_like 'an order with line items factory', "complete", "on_hand" + it_behaves_like 'shipping methods are assigned' + it_behaves_like 'supplied completed_at is respected' + + it "has the expected attributes" do + order = create(factory) + aggregate_failures do + expect(order).to be_completed + expect(order).to be_complete + + expect(order.order_promotions.count).to eq(1) + order_promotion = order.order_promotions[0] + expect(order_promotion.promotion_code.promotion).to eq order_promotion.promotion + end + end + + context 'with a promotion with an action' do + let(:promotion) { create(:promotion, :with_line_item_adjustment) } + it "has the expected attributes" do + order = create(factory, promotion: promotion) + aggregate_failures do + expect(order).to be_completed + expect(order).to be_complete + + expect(order.line_items[0].adjustments.count).to eq 1 + adjustment = order.line_items[0].adjustments[0] + expect(adjustment).to have_attributes( + amount: -10, + eligible: true, + order_id: order.id + ) + end + end + end + end +end From 88253985c488ee742a013921f70e8b8f49abd3d1 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Sat, 27 Jan 2024 13:14:25 +0100 Subject: [PATCH 059/834] Move PercentOnLineItem calculator factory to legacy_promotions --- .../factories/calculator_factory.rb | 6 +---- .../factories/calculator_factory_spec.rb | 12 --------- .../factories/calculator_factory.rb | 7 +++++ .../factories/calculator_factory_spec.rb | 26 +++++++++++++++++++ 4 files changed, 34 insertions(+), 17 deletions(-) create mode 100644 legacy_promotions/lib/solidus_legacy_promotions/testing_support/factories/calculator_factory.rb create mode 100644 legacy_promotions/spec/lib/solidus_legacy_promotions/testing_support/factories/calculator_factory_spec.rb diff --git a/core/lib/spree/testing_support/factories/calculator_factory.rb b/core/lib/spree/testing_support/factories/calculator_factory.rb index 5f8d119c1c8..944a955aa67 100644 --- a/core/lib/spree/testing_support/factories/calculator_factory.rb +++ b/core/lib/spree/testing_support/factories/calculator_factory.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true FactoryBot.define do - factory :calculator, aliases: [:flat_rate_calculator], class: 'Spree::Calculator::FlatRate' do + factory :flat_rate_calculator, aliases: [:calculator], class: 'Spree::Calculator::FlatRate' do preferred_amount { 10.0 } end @@ -22,8 +22,4 @@ factory :shipping_no_amount_calculator, class: 'Spree::Calculator::Shipping::FlatRate' do preferred_amount { 0 } end - - factory :percent_on_item_calculator, class: 'Spree::Calculator::PercentOnLineItem' do - preferred_percent { 10 } - end end diff --git a/core/spec/lib/spree/core/testing_support/factories/calculator_factory_spec.rb b/core/spec/lib/spree/core/testing_support/factories/calculator_factory_spec.rb index 7efaa94dc24..d5b77ff3e24 100644 --- a/core/spec/lib/spree/core/testing_support/factories/calculator_factory_spec.rb +++ b/core/spec/lib/spree/core/testing_support/factories/calculator_factory_spec.rb @@ -12,12 +12,6 @@ it_behaves_like 'a working factory' end - describe 'no amount calculator' do - let(:factory) { :no_amount_calculator } - - it_behaves_like 'a working factory' - end - describe 'default_tax_calculator' do let(:factory) { :default_tax_calculator } @@ -35,10 +29,4 @@ it_behaves_like 'a working factory' end - - describe 'percent on item calculator' do - let(:factory) { :percent_on_item_calculator } - - it_behaves_like 'a working factory' - end end diff --git a/legacy_promotions/lib/solidus_legacy_promotions/testing_support/factories/calculator_factory.rb b/legacy_promotions/lib/solidus_legacy_promotions/testing_support/factories/calculator_factory.rb new file mode 100644 index 00000000000..02ab9920e46 --- /dev/null +++ b/legacy_promotions/lib/solidus_legacy_promotions/testing_support/factories/calculator_factory.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :percent_on_item_calculator, class: 'Spree::Calculator::PercentOnLineItem' do + preferred_percent { 10 } + end +end diff --git a/legacy_promotions/spec/lib/solidus_legacy_promotions/testing_support/factories/calculator_factory_spec.rb b/legacy_promotions/spec/lib/solidus_legacy_promotions/testing_support/factories/calculator_factory_spec.rb new file mode 100644 index 00000000000..e50abff2402 --- /dev/null +++ b/legacy_promotions/spec/lib/solidus_legacy_promotions/testing_support/factories/calculator_factory_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require 'rails_helper' +require 'spree/testing_support/shared_examples/working_factory' + +RSpec.describe 'calculator factory' do + let(:factory_class) { Spree::Calculator } + + describe 'flat_rate_calculator' do + let(:factory) { :flat_rate_calculator } + + it_behaves_like 'a working factory' + end + + describe 'no amount calculator' do + let(:factory) { :no_amount_calculator } + + it_behaves_like 'a working factory' + end + + describe 'percent on item calculator' do + let(:factory) { :percent_on_item_calculator } + + it_behaves_like 'a working factory' + end +end From 69b837feaea0ebebeea9894077983931b9bb1fa4 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Sat, 27 Jan 2024 13:33:41 +0100 Subject: [PATCH 060/834] Move promotion-specific code from Spree::Adjustment to legacy_promotions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We don't need #recalculate for anything in core, and we don´t need the association to promotion codes. --- core/app/models/spree/adjustment.rb | 55 ------ core/spec/models/spree/adjustment_spec.rb | 169 ---------------- .../models/spree_adjustment_decorator.rb | 65 +++++++ .../spec/models/spree/adjustment_spec.rb | 180 ++++++++++++++++++ 4 files changed, 245 insertions(+), 224 deletions(-) create mode 100644 legacy_promotions/app/decorators/solidus_legacy_promotions/models/spree_adjustment_decorator.rb create mode 100644 legacy_promotions/spec/models/spree/adjustment_spec.rb diff --git a/core/app/models/spree/adjustment.rb b/core/app/models/spree/adjustment.rb index 70520f8f3c2..60647a8281a 100644 --- a/core/app/models/spree/adjustment.rb +++ b/core/app/models/spree/adjustment.rb @@ -19,14 +19,12 @@ class Adjustment < Spree::Base belongs_to :adjustable, polymorphic: true, touch: true, optional: true belongs_to :source, polymorphic: true, optional: true belongs_to :order, class_name: 'Spree::Order', inverse_of: :all_adjustments, optional: true - belongs_to :promotion_code, class_name: 'Spree::PromotionCode', optional: true belongs_to :adjustment_reason, class_name: 'Spree::AdjustmentReason', inverse_of: :adjustments, optional: true validates :adjustable, presence: true validates :order, presence: true validates :label, presence: true validates :amount, numericality: true - validates :promotion_code, presence: true, if: :require_promotion_code? scope :not_finalized, -> { where(finalized: false) } scope :finalized, -> { where(finalized: true) } @@ -89,58 +87,5 @@ def tax? def cancellation? source_type == 'Spree::UnitCancel' end - - # Recalculate and persist the amount from this adjustment's source based on - # the adjustable ({Order}, {Shipment}, or {LineItem}) - # - # If the adjustment has no source (such as when created manually from the - # admin) or is closed, this is a noop. - # - # @return [BigDecimal] New amount of this adjustment - def recalculate - if finalized? && !tax? - return amount - end - - # If the adjustment has no source, do not attempt to re-calculate the - # amount. - # Some scenarios where this happens: - # - Adjustments that are manually created via the admin backend - # - PromotionAction adjustments where the PromotionAction was deleted - # after the order was completed. - if source.present? - self.amount = source.compute_amount(adjustable) - - if promotion? - self.eligible = calculate_eligibility - end - - # Persist only if changed - # This is only not a save! to avoid the extra queries to load the order - # (for validations) and to touch the adjustment. - update_columns(eligible: eligible, amount: amount, updated_at: Time.current) if changed? - end - amount - end - deprecate :recalculate, deprecator: Spree.deprecator - - # Calculates based on attached promotion (if this is a promotion - # adjustment) whether this promotion is still eligible. - # @api private - # @return [true,false] Whether this adjustment is eligible - def calculate_eligibility - if !finalized? && source && promotion? - source.promotion.eligible?(adjustable, promotion_code: promotion_code) - else - eligible? - end - end - deprecate :calculate_eligibility, deprecator: Spree.deprecator - - private - - def require_promotion_code? - promotion? && !source.promotion.apply_automatically && source.promotion.codes.any? - end end end diff --git a/core/spec/models/spree/adjustment_spec.rb b/core/spec/models/spree/adjustment_spec.rb index efbd9e48fdb..d83962f37e5 100644 --- a/core/spec/models/spree/adjustment_spec.rb +++ b/core/spec/models/spree/adjustment_spec.rb @@ -77,175 +77,6 @@ end end - context '#recalculate' do - subject { adjustment.recalculate } - let(:adjustment) do - line_item.adjustments.create!( - label: 'Adjustment', - order: order, - adjustable: order, - amount: 5, - finalized: finalized, - source: source, - ) - end - let(:order) { create(:order_with_line_items, line_items_price: 100) } - let(:line_item) { order.line_items.to_a.first } - - around do |example| - Spree.deprecator.silence do - example.run - end - end - - context "when adjustment is finalized" do - let(:finalized) { true } - - context 'with a promotion adjustment' do - let(:source) { promotion.actions.first! } - let(:promotion) { create(:promotion, :with_line_item_adjustment, adjustment_rate: 7) } - - it 'does not update the adjustment' do - expect { subject }.not_to change { adjustment.amount } - end - end - - context 'with a tax adjustment' do - let(:source) { mock_model(Spree::TaxRate, compute_amount: 10) } - - it 'updates the adjustment' do - expect { subject }.to change { adjustment.amount }.from(5).to(10) - end - end - - context 'with a sourceless adjustment' do - let(:source) { nil } - - it 'does nothing' do - expect { subject }.not_to change { adjustment.amount } - end - end - end - - context "when adjustment isn't finalized" do - let(:finalized) { false } - - context 'with a promotion adjustment' do - let(:source) { promotion.actions.first! } - let(:promotion) { create(:promotion, :with_line_item_adjustment, adjustment_rate: 7) } - - around do |example| - Spree.deprecator.silence do - example.run - end - end - - context 'when the promotion is eligible' do - it 'updates the adjustment' do - expect { subject }.to change { adjustment.amount }.from(5).to(-7) - end - - it 'sets the adjustment elgiible to true' do - subject - expect(adjustment.eligible).to eq(true) - end - end - - context 'when the promotion is not eligible' do - before do - promotion.update!(starts_at: 1.day.from_now) - end - - it 'zeros out the adjustment' do - expect { subject }.to change { adjustment.amount }.from(5).to(0) - end - - it 'sets the adjustment elgiible to false' do - subject - expect(adjustment.eligible).to eq(false) - end - end - end - - context 'with a tax adjustment' do - let(:source) { mock_model(Spree::TaxRate, compute_amount: 10) } - - it 'updates the adjustment' do - expect { subject }.to change { adjustment.amount }.from(5).to(10) - end - end - - context 'with a sourceless adjustment' do - let(:source) { nil } - - it 'does nothing' do - expect { subject }.to_not change { adjustment.amount } - end - end - end - end - - describe "promotion code presence error" do - subject do - adjustment.valid? - adjustment.errors[:promotion_code] - end - - context "when the adjustment is not a promotion adjustment" do - let(:adjustment) { build(:adjustment) } - - it { is_expected.to be_blank } - end - - context "when the adjustment is a promotion adjustment" do - let(:adjustment) { build(:adjustment, source: promotion.actions.first) } - let(:promotion) { create(:promotion, :with_order_adjustment) } - - context "when the promotion does not have a code" do - it { is_expected.to be_blank } - end - - context "when the promotion has a code" do - let!(:promotion_code) { create(:promotion_code, promotion: promotion) } - - it { is_expected.to include("can't be blank") } - end - end - - context "when the adjustment is a promotion that apply automatically adjustment" do - let(:adjustment) { build(:adjustment, source: promotion.actions.first) } - let(:promotion) { create(:promotion, :with_order_adjustment, apply_automatically: true) } - - context "when the promotion does not have a code" do - it { is_expected.to be_blank } - end - - context "when the promotion has a code" do - let!(:promotion_code) do - promotion.codes << build(:promotion_code, promotion: promotion) - end - - it { is_expected.to be_blank } - end - end - end - - describe "#calculate_eligibility" do - subject { adjustment.calculate_eligibility } - - around do |example| - Spree.deprecator.silence do - example.run - end - end - - context "when the adjustment is not a promotion adjustment" do - let(:adjustment) { build(:adjustment, eligible: true, source: nil) } - - it { is_expected.to eq true } - end - end - describe "#finalize" do let(:adjustable) { create(:order) } let(:adjustment) { build(:adjustment, finalized: false, adjustable: adjustable) } diff --git a/legacy_promotions/app/decorators/solidus_legacy_promotions/models/spree_adjustment_decorator.rb b/legacy_promotions/app/decorators/solidus_legacy_promotions/models/spree_adjustment_decorator.rb new file mode 100644 index 00000000000..d3068638f66 --- /dev/null +++ b/legacy_promotions/app/decorators/solidus_legacy_promotions/models/spree_adjustment_decorator.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +module SolidusLegacyPromotions + module SpreeAdjustmentDecorator + def self.prepended(base) + base.belongs_to :promotion_code, class_name: 'Spree::PromotionCode', optional: true + base.validates :promotion_code, presence: true, if: :require_promotion_code? + end + + # Recalculate and persist the amount from this adjustment's source based on + # the adjustable ({Order}, {Shipment}, or {LineItem}) + # + # If the adjustment has no source (such as when created manually from the + # admin) or is closed, this is a noop. + # + # @return [BigDecimal] New amount of this adjustment + def recalculate + if finalized? && !tax? + return amount + end + + # If the adjustment has no source, do not attempt to re-calculate the + # amount. + # Some scenarios where this happens: + # - Adjustments that are manually created via the admin backend + # - PromotionAction adjustments where the PromotionAction was deleted + # after the order was completed. + if source.present? + self.amount = source.compute_amount(adjustable) + + if promotion? + self.eligible = calculate_eligibility + end + + # Persist only if changed + # This is only not a save! to avoid the extra queries to load the order + # (for validations) and to touch the adjustment. + update_columns(eligible: eligible, amount: amount, updated_at: Time.current) if changed? + end + amount + end + deprecate :recalculate, deprecator: Spree.deprecator + + # Calculates based on attached promotion (if this is a promotion + # adjustment) whether this promotion is still eligible. + # @api private + # @return [true,false] Whether this adjustment is eligible + def calculate_eligibility + if !finalized? && source && promotion? + source.promotion.eligible?(adjustable, promotion_code: promotion_code) + else + eligible? + end + end + deprecate :calculate_eligibility, deprecator: ::Spree.deprecator + + private + + def require_promotion_code? + promotion? && !source.promotion.apply_automatically && source.promotion.codes.any? + end + + Spree::Adjustment.prepend self + end +end diff --git a/legacy_promotions/spec/models/spree/adjustment_spec.rb b/legacy_promotions/spec/models/spree/adjustment_spec.rb new file mode 100644 index 00000000000..48596f5f03d --- /dev/null +++ b/legacy_promotions/spec/models/spree/adjustment_spec.rb @@ -0,0 +1,180 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Spree::Adjustment, type: :model do + let!(:store) { create :store } + let(:order) { create :order } + let(:line_item) { create :line_item, order: order } + + let(:adjustment) { Spree::Adjustment.create!(label: 'Adjustment', adjustable: order, order: order, amount: 5) } + + context '#recalculate' do + subject { adjustment.recalculate } + let(:adjustment) do + line_item.adjustments.create!( + label: 'Adjustment', + order: order, + adjustable: order, + amount: 5, + finalized: finalized, + source: source, + ) + end + let(:order) { create(:order_with_line_items, line_items_price: 100) } + let(:line_item) { order.line_items.to_a.first } + + around do |example| + Spree.deprecator.silence do + example.run + end + end + + context "when adjustment is finalized" do + let(:finalized) { true } + + context 'with a promotion adjustment' do + let(:source) { promotion.actions.first! } + let(:promotion) { create(:promotion, :with_line_item_adjustment, adjustment_rate: 7) } + + it 'does not update the adjustment' do + expect { subject }.not_to change { adjustment.amount } + end + end + + context 'with a tax adjustment' do + let(:source) { mock_model(Spree::TaxRate, compute_amount: 10) } + + it 'updates the adjustment' do + expect { subject }.to change { adjustment.amount }.from(5).to(10) + end + end + + context 'with a sourceless adjustment' do + let(:source) { nil } + + it 'does nothing' do + expect { subject }.not_to change { adjustment.amount } + end + end + end + + context "when adjustment isn't finalized" do + let(:finalized) { false } + + context 'with a promotion adjustment' do + let(:source) { promotion.actions.first! } + let(:promotion) { create(:promotion, :with_line_item_adjustment, adjustment_rate: 7) } + + around do |example| + Spree.deprecator.silence do + example.run + end + end + + context 'when the promotion is eligible' do + it 'updates the adjustment' do + expect { subject }.to change { adjustment.amount }.from(5).to(-7) + end + + it 'sets the adjustment elgiible to true' do + subject + expect(adjustment.eligible).to eq(true) + end + end + + context 'when the promotion is not eligible' do + before do + promotion.update!(starts_at: 1.day.from_now) + end + + it 'zeros out the adjustment' do + expect { subject }.to change { adjustment.amount }.from(5).to(0) + end + + it 'sets the adjustment elgiible to false' do + subject + expect(adjustment.eligible).to eq(false) + end + end + end + + context 'with a tax adjustment' do + let(:source) { mock_model(Spree::TaxRate, compute_amount: 10) } + + it 'updates the adjustment' do + expect { subject }.to change { adjustment.amount }.from(5).to(10) + end + end + + context 'with a sourceless adjustment' do + let(:source) { nil } + + it 'does nothing' do + expect { subject }.to_not change { adjustment.amount } + end + end + end + end + + describe "promotion code presence error" do + subject do + adjustment.valid? + adjustment.errors[:promotion_code] + end + + context "when the adjustment is not a promotion adjustment" do + let(:adjustment) { build(:adjustment) } + + it { is_expected.to be_blank } + end + + context "when the adjustment is a promotion adjustment" do + let(:adjustment) { build(:adjustment, source: promotion.actions.first) } + let(:promotion) { create(:promotion, :with_order_adjustment) } + + context "when the promotion does not have a code" do + it { is_expected.to be_blank } + end + + context "when the promotion has a code" do + let!(:promotion_code) { create(:promotion_code, promotion: promotion) } + + it { is_expected.to include("can't be blank") } + end + end + + context "when the adjustment is a promotion that apply automatically adjustment" do + let(:adjustment) { build(:adjustment, source: promotion.actions.first) } + let(:promotion) { create(:promotion, :with_order_adjustment, apply_automatically: true) } + + context "when the promotion does not have a code" do + it { is_expected.to be_blank } + end + + context "when the promotion has a code" do + let!(:promotion_code) do + promotion.codes << build(:promotion_code, promotion: promotion) + end + + it { is_expected.to be_blank } + end + end + end + + describe "#calculate_eligibility" do + subject { adjustment.calculate_eligibility } + + around do |example| + Spree.deprecator.silence do + example.run + end + end + + context "when the adjustment is not a promotion adjustment" do + let(:adjustment) { build(:adjustment, eligible: true, source: nil) } + + it { is_expected.to eq true } + end + end +end From 81c8460026c8c4453c7c9428df6db09a84c0cc99 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Sat, 27 Jan 2024 13:47:25 +0100 Subject: [PATCH 061/834] Move spec for Shipment#total_before_tax to legacy_promotions I'm keeping the spec to test what the model does, but moving the extended spec with the actual promotion to solidus_legacy_promotions. --- core/spec/models/spree/shipment_spec.rb | 6 +-- .../spec/models/spree/shipment_spec.rb | 40 +++++++++++++++++++ 2 files changed, 42 insertions(+), 4 deletions(-) create mode 100644 legacy_promotions/spec/models/spree/shipment_spec.rb diff --git a/core/spec/models/spree/shipment_spec.rb b/core/spec/models/spree/shipment_spec.rb index dd09e487bc6..641b09c03f1 100644 --- a/core/spec/models/spree/shipment_spec.rb +++ b/core/spec/models/spree/shipment_spec.rb @@ -124,10 +124,8 @@ shipment.update!(cost: 10) end let!(:admin_adjustment) { create(:adjustment, adjustable: shipment, order: shipment.order, amount: -1, source: nil) } - let!(:promo_adjustment) { create(:adjustment, adjustable: shipment, order: shipment.order, amount: -2, source: promo_action) } - let!(:ineligible_promo_adjustment) { create(:adjustment, eligible: false, adjustable: shipment, order: shipment.order, amount: -4, source: promo_action) } - let(:promo_action) { promo.actions[0] } - let(:promo) { create(:promotion, :with_line_item_adjustment) } + let!(:other_adjustment) { create(:adjustment, adjustable: shipment, order: shipment.order, amount: -2, source: nil) } + let!(:ineligible_adjustment) { create(:adjustment, eligible: false, adjustable: shipment, order: shipment.order, amount: -4, source: nil) } it 'returns the amount minus any adjustments' do expect(shipment.total_before_tax).to eq(10 - 1 - 2) diff --git a/legacy_promotions/spec/models/spree/shipment_spec.rb b/legacy_promotions/spec/models/spree/shipment_spec.rb new file mode 100644 index 00000000000..e39ca1b85eb --- /dev/null +++ b/legacy_promotions/spec/models/spree/shipment_spec.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require 'rails_helper' +require 'benchmark' + +RSpec.describe Spree::Shipment, type: :model do + let(:order) { create(:order_ready_to_ship, line_items_count: 1) } + let(:shipping_method) { create(:shipping_method, name: "UPS") } + let(:stock_location) { create(:stock_location) } + let(:shipment) do + order.shipments.create!( + state: 'pending', + cost: 1, + inventory_units: order.inventory_units, + shipping_rates: [shipping_rate], + stock_location: stock_location + ) + end + let(:shipping_rate) do + Spree::ShippingRate.create!( + shipping_method: shipping_method, + selected: true + ) + end + + describe '#total_before_tax' do + before do + shipment.update!(cost: 10) + end + let!(:admin_adjustment) { create(:adjustment, adjustable: shipment, order: shipment.order, amount: -1, source: nil) } + let!(:promo_adjustment) { create(:adjustment, adjustable: shipment, order: shipment.order, amount: -2, source: promo_action) } + let!(:ineligible_promo_adjustment) { create(:adjustment, eligible: false, adjustable: shipment, order: shipment.order, amount: -4, source: promo_action) } + let(:promo_action) { promo.actions[0] } + let(:promo) { create(:promotion, :with_line_item_adjustment) } + + it 'returns the amount minus any adjustments' do + expect(shipment.total_before_tax).to eq(10 - 1 - 2) + end + end +end From c860e8dd615f94cc81854043af4b60458c31488c Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 27 Feb 2024 10:44:39 +0100 Subject: [PATCH 062/834] Move promotion-related associations to legacy_promotions --- core/app/models/spree/order.rb | 2 -- core/spec/models/spree/order_spec.rb | 14 -------------- .../models/spree/order_decorator.rb | 12 ------------ .../models/spree_order_decorator.rb | 17 +++++++++++++++++ 4 files changed, 17 insertions(+), 28 deletions(-) delete mode 100644 legacy_promotions/app/decorators/solidus_legacy_promotions/models/spree/order_decorator.rb create mode 100644 legacy_promotions/app/decorators/solidus_legacy_promotions/models/spree_order_decorator.rb diff --git a/core/app/models/spree/order.rb b/core/app/models/spree/order.rb index d33a6ab3358..eae408eb392 100644 --- a/core/app/models/spree/order.rb +++ b/core/app/models/spree/order.rb @@ -108,8 +108,6 @@ def states foreign_key: :order_id, dependent: :destroy, inverse_of: :order - has_many :order_promotions, class_name: 'Spree::OrderPromotion', dependent: :destroy - has_many :promotions, through: :order_promotions # Payments has_many :payments, dependent: :destroy, inverse_of: :order diff --git a/core/spec/models/spree/order_spec.rb b/core/spec/models/spree/order_spec.rb index f40aa63b405..c669fa98df9 100644 --- a/core/spec/models/spree/order_spec.rb +++ b/core/spec/models/spree/order_spec.rb @@ -1806,20 +1806,6 @@ def validate(line_item) end end - describe "order deletion" do - let(:order) { create(:order) } - let(:promotion) { create(:promotion) } - - subject { order.destroy } - before do - order.promotions << promotion - end - - it "deletes join table entries when deleting an order" do - expect { subject }.to change { Spree::OrderPromotion.count }.from(1).to(0) - end - end - describe ".find_by_param" do let(:order) { create(:order) } let(:param) { order.number } diff --git a/legacy_promotions/app/decorators/solidus_legacy_promotions/models/spree/order_decorator.rb b/legacy_promotions/app/decorators/solidus_legacy_promotions/models/spree/order_decorator.rb deleted file mode 100644 index a74d3e4a253..00000000000 --- a/legacy_promotions/app/decorators/solidus_legacy_promotions/models/spree/order_decorator.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -module SolidusLegacyPromotions - module OrderDecorator - def apply_shipping_promotions(_event = nil) - ::Spree::Config.promotions.shipping_promotion_handler_class.new(self).activate - recalculate - end - - ::Spree::Order.prepend(self) - end -end diff --git a/legacy_promotions/app/decorators/solidus_legacy_promotions/models/spree_order_decorator.rb b/legacy_promotions/app/decorators/solidus_legacy_promotions/models/spree_order_decorator.rb new file mode 100644 index 00000000000..4ba9536b01b --- /dev/null +++ b/legacy_promotions/app/decorators/solidus_legacy_promotions/models/spree_order_decorator.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module SolidusLegacyPromotions + module SpreeOrderDecorator + def self.prepended(base) + base.has_many :order_promotions, class_name: 'Spree::OrderPromotion', dependent: :destroy + base.has_many :promotions, through: :order_promotions + end + + def apply_shipping_promotions(_event = nil) + Spree::Config.promotions.shipping_promotion_handler_class.new(self).activate + recalculate + end + + Spree::Order.prepend(self) + end +end From a78e28d26d2ea426bb95e5dcb7398a43fa4a097e Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 19 Jun 2024 11:30:50 +0200 Subject: [PATCH 063/834] Automatically add legacy promotion factories This should help everyones test suite's to stay green. --- .../lib/solidus_legacy_promotions/engine.rb | 10 ++++++++++ legacy_promotions/spec/rails_helper.rb | 3 +-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/legacy_promotions/lib/solidus_legacy_promotions/engine.rb b/legacy_promotions/lib/solidus_legacy_promotions/engine.rb index 0be296e75f1..21fc47fb8c5 100644 --- a/legacy_promotions/lib/solidus_legacy_promotions/engine.rb +++ b/legacy_promotions/lib/solidus_legacy_promotions/engine.rb @@ -73,6 +73,16 @@ class Engine < ::Rails::Engine app.config.assets.precompile << "solidus_legacy_promotions/manifest.js" end + initializer "solidus_legacy_promotions.add_factories_to_core" do + if Rails.env.test? + require "spree/testing_support/factory_bot" + require "solidus_legacy_promotions/testing_support/factory_bot" + Spree::TestingSupport::FactoryBot.definition_file_paths.concat(SolidusLegacyPromotions::TestingSupport::FactoryBot.definition_file_paths) + end + rescue LoadError + # FactoryBot is not available, we don't need factories + end + initializer "solidus_legacy_promotions", after: "spree.load_config_initializers" do Spree::Config.order_contents_class = "Spree::OrderContents" Spree::Config.promotions = SolidusLegacyPromotions::Configuration.new diff --git a/legacy_promotions/spec/rails_helper.rb b/legacy_promotions/spec/rails_helper.rb index 1ea6aeef0ff..b2149922fc0 100644 --- a/legacy_promotions/spec/rails_helper.rb +++ b/legacy_promotions/spec/rails_helper.rb @@ -52,8 +52,7 @@ ActiveJob::Base.queue_adapter = :test -Spree::TestingSupport::FactoryBot.add_definitions! -SolidusLegacyPromotions::TestingSupport::FactoryBot.add_paths_and_load! +Spree::TestingSupport::FactoryBot.add_paths_and_load! Capybara.register_driver :selenium_chrome_headless do |app| browser_options = ::Selenium::WebDriver::Chrome::Options.new From e1b1a488fe94975c99bab8fd20c45723aa68c69d Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 19 Jun 2024 17:09:46 +0200 Subject: [PATCH 064/834] Support Tailwind CSS in core dummy app This allows running the core dummy app with the Tailwind required for setting sails towards the new frontend. --- .../testing_support/admin_assets.rb | 10 ++++ .../testing_support/dummy_app/rake_tasks.rb | 60 +++++++++++++++++++ legacy_promotions/Rakefile | 1 + legacy_promotions/spec/rails_helper.rb | 7 +-- 4 files changed, 72 insertions(+), 6 deletions(-) create mode 100644 admin/lib/solidus_admin/testing_support/admin_assets.rb create mode 100644 admin/lib/solidus_admin/testing_support/dummy_app/rake_tasks.rb diff --git a/admin/lib/solidus_admin/testing_support/admin_assets.rb b/admin/lib/solidus_admin/testing_support/admin_assets.rb new file mode 100644 index 00000000000..b9562ea4648 --- /dev/null +++ b/admin/lib/solidus_admin/testing_support/admin_assets.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +RSpec.configure do |config| + config.when_first_matching_example_defined(solidus_admin: true) do + config.before(:suite) do + system('bin/rails solidus_admin:tailwindcss:build') or abort 'Failed to build Tailwind CSS' + Rails.application.precompiled_assets + end + end +end diff --git a/admin/lib/solidus_admin/testing_support/dummy_app/rake_tasks.rb b/admin/lib/solidus_admin/testing_support/dummy_app/rake_tasks.rb new file mode 100644 index 00000000000..863c6330150 --- /dev/null +++ b/admin/lib/solidus_admin/testing_support/dummy_app/rake_tasks.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +namespace :solidus_admin do + namespace :tailwindcss do + desc "Build Tailwind CSS" + task build: :dummy_environment do + require "solidus_admin" + require "tailwindcss/commands" + + config_file = <<~JS + const adminRoot = "#{SolidusAdmin::Engine.root}" + const solidusAdmin = require(`${adminRoot}/config/tailwind.config.js`) + + module.exports = { + // Read how to use TailwindCSS presets: https://tailwindcss.com/docs/presets. + presets: [solidusAdmin], + + content: [ + // Include paths coming from SolidusAdmin. + ...solidusAdmin.content, + + // Include paths to your own components. + `${__dirname}/../../../../app/components/admin/**/*`, + `${__dirname}/../../../../lib/components/admin/**/*`, + ], + } + JS + FileUtils.mkdir_p(DummyApp::Application.root.join("config")) + File.write(DummyApp::Application.root.join("config/tailwind.config.js"), config_file) + FileUtils.mkdir_p(DummyApp::Application.root.join("app/assets/stylesheets/solidus_admin")) + FileUtils.cp( + SolidusAdmin::Engine.root.join("app/assets/stylesheets/solidus_admin/application.tailwind.css"), + DummyApp::Application.root.join("app/assets/stylesheets/solidus_admin/application.tailwind.css") + ) + + tailwindcss = Tailwindcss::Commands.executable + + tailwindcss_command = [ + tailwindcss, + "--config", DummyApp::Application.root.join("config/tailwind.config.js"), + "--input", DummyApp::Application.root.join("app/assets/stylesheets/solidus_admin/application.tailwind.css"), + "--output", DummyApp::Application.root.join("assets/builds/solidus_admin/tailwind.css") + ] + + sh tailwindcss_command.shelljoin + end + end +end + +# Attach Tailwind CSS build to other tasks. +%w[ + assets:precompile + test:prepare + spec:prepare + db:test:prepare +].each do |task_name| + next unless Rake::Task.task_defined?(task_name) + + Rake::Task[task_name].enhance(["solidus_admin:tailwindcss:build"]) +end diff --git a/legacy_promotions/Rakefile b/legacy_promotions/Rakefile index c4633164579..4b6e3ffb764 100644 --- a/legacy_promotions/Rakefile +++ b/legacy_promotions/Rakefile @@ -5,6 +5,7 @@ require 'rake' require 'rake/testtask' require 'rspec/core/rake_task' require 'spree/testing_support/dummy_app/rake_tasks' +require 'solidus_admin/testing_support/dummy_app/rake_tasks' require 'bundler/gem_tasks' RSpec::Core::RakeTask.new diff --git a/legacy_promotions/spec/rails_helper.rb b/legacy_promotions/spec/rails_helper.rb index a4691920530..8a2543db306 100644 --- a/legacy_promotions/spec/rails_helper.rb +++ b/legacy_promotions/spec/rails_helper.rb @@ -21,12 +21,7 @@ } mount Spree::Core::Engine, at: "/" end - -unless SolidusAdmin::Engine.root.join('app/assets/builds/solidus_admin/tailwind.css').exist? - Dir.chdir(SolidusAdmin::Engine.root) do - system 'bundle exec rake tailwindcss:build' or abort 'Failed to build Tailwind CSS' - end -end +require "solidus_admin/testing_support/admin_assets" require 'rails-controller-testing' require 'rspec/rails' From 1d2d04587aa9f0960644268dfa12d4f608171185 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 20 Jun 2024 11:38:29 +0200 Subject: [PATCH 065/834] Use new dummy app tailwind in admin gem --- admin/Rakefile | 1 + admin/spec/spec_helper.rb | 9 ++++----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/admin/Rakefile b/admin/Rakefile index 9217d0c961b..7364bc85419 100644 --- a/admin/Rakefile +++ b/admin/Rakefile @@ -5,6 +5,7 @@ require 'rake' require 'rake/testtask' require 'rspec/core/rake_task' require 'spree/testing_support/dummy_app/rake_tasks' +require 'solidus_admin/testing_support/dummy_app/rake_tasks' require 'bundler/gem_tasks' namespace :tailwindcss do diff --git a/admin/spec/spec_helper.rb b/admin/spec/spec_helper.rb index f9d8250cba5..70ecb0c11f7 100644 --- a/admin/spec/spec_helper.rb +++ b/admin/spec/spec_helper.rb @@ -29,11 +29,7 @@ mount Spree::Core::Engine, at: '/' end -unless SolidusAdmin::Engine.root.join('app/assets/builds/solidus_admin/tailwind.css').exist? - Dir.chdir(SolidusAdmin::Engine.root) do - system 'bundle exec rake tailwindcss:build' or abort 'Failed to build Tailwind CSS' - end -end +require "solidus_admin/testing_support/admin_assets" # RAILS require "rspec/rails" @@ -111,6 +107,9 @@ config.before do Rails.cache.clear end + config.define_derived_metadata(file_path: %r{spec/features}) do |metadata| + metadata[:solidus_admin] = true + end config.include FactoryBot::Syntax::Methods From be7aa6eed91406d873324a06a1d5c9c4e5037924 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 14 Jun 2024 09:00:09 +0200 Subject: [PATCH 066/834] Add UI component: Thumbnail with caption This is used in a number of other places. Also, this adds icons to the adjustment source components, which looks nice (and was probably intended). --- .../adjustments/index/adjustment/component.rb | 12 ++++++++++++ .../index/adjustment/spree_tax_rate/component.rb | 6 +++++- .../adjustment/spree_unit_cancel/component.rb | 3 +++ .../thumbnail_with_caption}/component.html.erb | 3 ++- .../ui/thumbnail_with_caption/component.rb | 15 +++++++++++++++ .../spree_promotion_action/component.rb | 4 ++++ 6 files changed, 41 insertions(+), 2 deletions(-) rename admin/app/components/solidus_admin/{orders/show/adjustments/index/adjustment => ui/thumbnail_with_caption}/component.html.erb (89%) create mode 100644 admin/app/components/solidus_admin/ui/thumbnail_with_caption/component.rb diff --git a/admin/app/components/solidus_admin/orders/show/adjustments/index/adjustment/component.rb b/admin/app/components/solidus_admin/orders/show/adjustments/index/adjustment/component.rb index f5a55798567..e6d90119c42 100644 --- a/admin/app/components/solidus_admin/orders/show/adjustments/index/adjustment/component.rb +++ b/admin/app/components/solidus_admin/orders/show/adjustments/index/adjustment/component.rb @@ -9,6 +9,18 @@ def initialize(adjustment) @model_name = source&.model_name&.human end + def call + render component("ui/thumbnail_with_caption").new(icon: icon, caption: caption, detail: detail) + end + + def caption + adjustment.label + end + def detail end + + def icon + "question-line" + end end diff --git a/admin/app/components/solidus_admin/orders/show/adjustments/index/adjustment/spree_tax_rate/component.rb b/admin/app/components/solidus_admin/orders/show/adjustments/index/adjustment/spree_tax_rate/component.rb index bd2b3a59c5a..1b46f41f99b 100644 --- a/admin/app/components/solidus_admin/orders/show/adjustments/index/adjustment/spree_tax_rate/component.rb +++ b/admin/app/components/solidus_admin/orders/show/adjustments/index/adjustment/spree_tax_rate/component.rb @@ -1,6 +1,10 @@ # frozen_string_literal: true class SolidusAdmin::Orders::Show::Adjustments::Index::Adjustment::SpreeTaxRate::Component < SolidusAdmin::Orders::Show::Adjustments::Index::Adjustment::Component + def icon + "percent-line" + end + def detail link_to("#{model_name}: #{zone_name}", spree.edit_admin_tax_rate_path(adjustment.source_id), class: "body-link") end @@ -8,6 +12,6 @@ def detail private def zone_name - source.zone&.name || t('spree.all_zones') + source.zone&.name || t("spree.all_zones") end end diff --git a/admin/app/components/solidus_admin/orders/show/adjustments/index/adjustment/spree_unit_cancel/component.rb b/admin/app/components/solidus_admin/orders/show/adjustments/index/adjustment/spree_unit_cancel/component.rb index cf19bd49b58..bfbf8905d2d 100644 --- a/admin/app/components/solidus_admin/orders/show/adjustments/index/adjustment/spree_unit_cancel/component.rb +++ b/admin/app/components/solidus_admin/orders/show/adjustments/index/adjustment/spree_unit_cancel/component.rb @@ -1,4 +1,7 @@ # frozen_string_literal: true class SolidusAdmin::Orders::Show::Adjustments::Index::Adjustment::SpreeUnitCancel::Component < SolidusAdmin::Orders::Show::Adjustments::Index::Adjustment::Component + def icon + "close-circle-line" + end end diff --git a/admin/app/components/solidus_admin/orders/show/adjustments/index/adjustment/component.html.erb b/admin/app/components/solidus_admin/ui/thumbnail_with_caption/component.html.erb similarity index 89% rename from admin/app/components/solidus_admin/orders/show/adjustments/index/adjustment/component.html.erb rename to admin/app/components/solidus_admin/ui/thumbnail_with_caption/component.html.erb index 16dcd17d1b9..37746c9d100 100644 --- a/admin/app/components/solidus_admin/orders/show/adjustments/index/adjustment/component.html.erb +++ b/admin/app/components/solidus_admin/ui/thumbnail_with_caption/component.html.erb @@ -1,7 +1,8 @@
+ <%= icon_thumbnail %>
- <%= adjustment.label %> + <%= caption %>
<% if detail %>
diff --git a/admin/app/components/solidus_admin/ui/thumbnail_with_caption/component.rb b/admin/app/components/solidus_admin/ui/thumbnail_with_caption/component.rb new file mode 100644 index 00000000000..575a57fc35b --- /dev/null +++ b/admin/app/components/solidus_admin/ui/thumbnail_with_caption/component.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class SolidusAdmin::UI::ThumbnailWithCaption::Component < SolidusAdmin::BaseComponent + attr_reader :icon, :caption, :detail + + def initialize(icon: "question-line", caption: "", detail: nil) + @icon = icon + @caption = caption + @detail = detail + end + + def icon_thumbnail + render component("ui/thumbnail").new(icon: icon) + end +end diff --git a/legacy_promotions/lib/components/admin/solidus_admin/orders/show/adjustments/index/adjustment/spree_promotion_action/component.rb b/legacy_promotions/lib/components/admin/solidus_admin/orders/show/adjustments/index/adjustment/spree_promotion_action/component.rb index aec27a1435b..66c4f818a0d 100644 --- a/legacy_promotions/lib/components/admin/solidus_admin/orders/show/adjustments/index/adjustment/spree_promotion_action/component.rb +++ b/legacy_promotions/lib/components/admin/solidus_admin/orders/show/adjustments/index/adjustment/spree_promotion_action/component.rb @@ -1,6 +1,10 @@ # frozen_string_literal: true class SolidusAdmin::Orders::Show::Adjustments::Index::Adjustment::SpreePromotionAction::Component < SolidusAdmin::Orders::Show::Adjustments::Index::Adjustment::Component + def icon + "megaphone-line" + end + def detail link_to("#{model_name}: #{promotion_name}", spree.edit_admin_promotion_path(adjustment.source_id), class: "body-link") end From 0ac5744c1219625e85e2c71f00c6d30fadc360e1 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 14 Jun 2024 09:04:42 +0200 Subject: [PATCH 067/834] Rename adjustment source namespace from adjustment to source It will otherwise get confusing when we add adjustables. --- .../index/adjustment/spree_unit_cancel/component.rb | 7 ------- .../orders/show/adjustments/index/component.rb | 2 +- .../adjustments/index/{adjustment => source}/component.rb | 2 +- .../{adjustment => source}/spree_tax_rate/component.rb | 2 +- .../index/source/spree_unit_cancel/component.rb | 7 +++++++ .../spree_promotion_action/component.rb | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) delete mode 100644 admin/app/components/solidus_admin/orders/show/adjustments/index/adjustment/spree_unit_cancel/component.rb rename admin/app/components/solidus_admin/orders/show/adjustments/index/{adjustment => source}/component.rb (80%) rename admin/app/components/solidus_admin/orders/show/adjustments/index/{adjustment => source}/spree_tax_rate/component.rb (64%) create mode 100644 admin/app/components/solidus_admin/orders/show/adjustments/index/source/spree_unit_cancel/component.rb rename legacy_promotions/lib/components/admin/solidus_admin/orders/show/adjustments/index/{adjustment => source}/spree_promotion_action/component.rb (62%) diff --git a/admin/app/components/solidus_admin/orders/show/adjustments/index/adjustment/spree_unit_cancel/component.rb b/admin/app/components/solidus_admin/orders/show/adjustments/index/adjustment/spree_unit_cancel/component.rb deleted file mode 100644 index bfbf8905d2d..00000000000 --- a/admin/app/components/solidus_admin/orders/show/adjustments/index/adjustment/spree_unit_cancel/component.rb +++ /dev/null @@ -1,7 +0,0 @@ -# frozen_string_literal: true - -class SolidusAdmin::Orders::Show::Adjustments::Index::Adjustment::SpreeUnitCancel::Component < SolidusAdmin::Orders::Show::Adjustments::Index::Adjustment::Component - def icon - "close-circle-line" - end -end diff --git a/admin/app/components/solidus_admin/orders/show/adjustments/index/component.rb b/admin/app/components/solidus_admin/orders/show/adjustments/index/component.rb index dea11e9f369..1fb6748cfb0 100644 --- a/admin/app/components/solidus_admin/orders/show/adjustments/index/component.rb +++ b/admin/app/components/solidus_admin/orders/show/adjustments/index/component.rb @@ -84,7 +84,7 @@ def columns col: { class: "w-56" }, data: ->(adjustment) { component_name = adjustment.source&.class&.table_name&.singularize - component_key = ["orders/show/adjustments/index/adjustment", component_name].compact.join("/") + component_key = ["orders/show/adjustments/index/source", component_name].compact.join("/") render component(component_key).new(adjustment) } }, diff --git a/admin/app/components/solidus_admin/orders/show/adjustments/index/adjustment/component.rb b/admin/app/components/solidus_admin/orders/show/adjustments/index/source/component.rb similarity index 80% rename from admin/app/components/solidus_admin/orders/show/adjustments/index/adjustment/component.rb rename to admin/app/components/solidus_admin/orders/show/adjustments/index/source/component.rb index e6d90119c42..4f1e34781c2 100644 --- a/admin/app/components/solidus_admin/orders/show/adjustments/index/adjustment/component.rb +++ b/admin/app/components/solidus_admin/orders/show/adjustments/index/source/component.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class SolidusAdmin::Orders::Show::Adjustments::Index::Adjustment::Component < SolidusAdmin::BaseComponent +class SolidusAdmin::Orders::Show::Adjustments::Index::Source::Component < SolidusAdmin::BaseComponent attr_reader :adjustment, :source, :model_name def initialize(adjustment) diff --git a/admin/app/components/solidus_admin/orders/show/adjustments/index/adjustment/spree_tax_rate/component.rb b/admin/app/components/solidus_admin/orders/show/adjustments/index/source/spree_tax_rate/component.rb similarity index 64% rename from admin/app/components/solidus_admin/orders/show/adjustments/index/adjustment/spree_tax_rate/component.rb rename to admin/app/components/solidus_admin/orders/show/adjustments/index/source/spree_tax_rate/component.rb index 1b46f41f99b..d3659ac3d56 100644 --- a/admin/app/components/solidus_admin/orders/show/adjustments/index/adjustment/spree_tax_rate/component.rb +++ b/admin/app/components/solidus_admin/orders/show/adjustments/index/source/spree_tax_rate/component.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class SolidusAdmin::Orders::Show::Adjustments::Index::Adjustment::SpreeTaxRate::Component < SolidusAdmin::Orders::Show::Adjustments::Index::Adjustment::Component +class SolidusAdmin::Orders::Show::Adjustments::Index::Source::SpreeTaxRate::Component < SolidusAdmin::Orders::Show::Adjustments::Index::Source::Component def icon "percent-line" end diff --git a/admin/app/components/solidus_admin/orders/show/adjustments/index/source/spree_unit_cancel/component.rb b/admin/app/components/solidus_admin/orders/show/adjustments/index/source/spree_unit_cancel/component.rb new file mode 100644 index 00000000000..dd405bf43ed --- /dev/null +++ b/admin/app/components/solidus_admin/orders/show/adjustments/index/source/spree_unit_cancel/component.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class SolidusAdmin::Orders::Show::Adjustments::Index::Source::SpreeUnitCancel::Component < SolidusAdmin::Orders::Show::Adjustments::Index::Source::Component + def icon + "close-circle-line" + end +end diff --git a/legacy_promotions/lib/components/admin/solidus_admin/orders/show/adjustments/index/adjustment/spree_promotion_action/component.rb b/legacy_promotions/lib/components/admin/solidus_admin/orders/show/adjustments/index/source/spree_promotion_action/component.rb similarity index 62% rename from legacy_promotions/lib/components/admin/solidus_admin/orders/show/adjustments/index/adjustment/spree_promotion_action/component.rb rename to legacy_promotions/lib/components/admin/solidus_admin/orders/show/adjustments/index/source/spree_promotion_action/component.rb index 66c4f818a0d..cb84016a112 100644 --- a/legacy_promotions/lib/components/admin/solidus_admin/orders/show/adjustments/index/adjustment/spree_promotion_action/component.rb +++ b/legacy_promotions/lib/components/admin/solidus_admin/orders/show/adjustments/index/source/spree_promotion_action/component.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class SolidusAdmin::Orders::Show::Adjustments::Index::Adjustment::SpreePromotionAction::Component < SolidusAdmin::Orders::Show::Adjustments::Index::Adjustment::Component +class SolidusAdmin::Orders::Show::Adjustments::Index::Source::SpreePromotionAction::Component < SolidusAdmin::Orders::Show::Adjustments::Index::Source::Component def icon "megaphone-line" end From 7f201b6c28d95f01bc4da99aa034c016afea80f3 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 14 Jun 2024 09:26:58 +0200 Subject: [PATCH 068/834] Use dynamic dispatch for adjustables as well This allows us to remove a bunch of untested (and untestable) code. Especially the `else` block in `figcaption_for_adjustable` is impossible to reach, as only orders, line items and shipments are adjustable. If people have their own adjustable objects, they can now just add a component. --- .../adjustments/index/adjustable/component.rb | 27 +++++++++ .../adjustable/spree_line_item/component.rb | 14 +++++ .../index/adjustable/spree_order/component.rb | 11 ++++ .../adjustable/spree_shipment/component.rb | 15 +++++ .../show/adjustments/index/component.rb | 60 +------------------ .../thumbnail_with_caption/component.html.erb | 6 +- 6 files changed, 75 insertions(+), 58 deletions(-) create mode 100644 admin/app/components/solidus_admin/orders/show/adjustments/index/adjustable/component.rb create mode 100644 admin/app/components/solidus_admin/orders/show/adjustments/index/adjustable/spree_line_item/component.rb create mode 100644 admin/app/components/solidus_admin/orders/show/adjustments/index/adjustable/spree_order/component.rb create mode 100644 admin/app/components/solidus_admin/orders/show/adjustments/index/adjustable/spree_shipment/component.rb diff --git a/admin/app/components/solidus_admin/orders/show/adjustments/index/adjustable/component.rb b/admin/app/components/solidus_admin/orders/show/adjustments/index/adjustable/component.rb new file mode 100644 index 00000000000..341e5f8931e --- /dev/null +++ b/admin/app/components/solidus_admin/orders/show/adjustments/index/adjustable/component.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +class SolidusAdmin::Orders::Show::Adjustments::Index::Adjustable::Component < SolidusAdmin::BaseComponent + attr_reader :adjustment, :adjustable, :model_name + + def initialize(adjustment) + @adjustment = adjustment + @adjustable = adjustment.adjustable + @model_name = adjustable&.model_name&.human + end + + def call + render component("ui/thumbnail_with_caption").new(caption: caption, detail: detail) do + thumbnail + end + end + + def thumbnail + render(component("ui/thumbnail").for(adjustment.adjustable, class: "basis-10")) + end + + def caption + end + + def detail + end +end diff --git a/admin/app/components/solidus_admin/orders/show/adjustments/index/adjustable/spree_line_item/component.rb b/admin/app/components/solidus_admin/orders/show/adjustments/index/adjustable/spree_line_item/component.rb new file mode 100644 index 00000000000..8f421f9a7b9 --- /dev/null +++ b/admin/app/components/solidus_admin/orders/show/adjustments/index/adjustable/spree_line_item/component.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +class SolidusAdmin::Orders::Show::Adjustments::Index::Adjustable::SpreeLineItem::Component < SolidusAdmin::Orders::Show::Adjustments::Index::Adjustable::Component + delegate :variant, to: :adjustable + + def caption + options_text = variant.options_text.presence + options_text || variant.sku + end + + def detail + link_to(variant.product.name, solidus_admin.product_path(variant.product), class: "body-link") + end +end diff --git a/admin/app/components/solidus_admin/orders/show/adjustments/index/adjustable/spree_order/component.rb b/admin/app/components/solidus_admin/orders/show/adjustments/index/adjustable/spree_order/component.rb new file mode 100644 index 00000000000..3fe8cfa7faf --- /dev/null +++ b/admin/app/components/solidus_admin/orders/show/adjustments/index/adjustable/spree_order/component.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class SolidusAdmin::Orders::Show::Adjustments::Index::Adjustable::SpreeOrder::Component < SolidusAdmin::Orders::Show::Adjustments::Index::Adjustable::Component + def caption + "#{Spree::Order.model_name.human} ##{adjustable.number}" + end + + def detail + adjustable.display_total + end +end diff --git a/admin/app/components/solidus_admin/orders/show/adjustments/index/adjustable/spree_shipment/component.rb b/admin/app/components/solidus_admin/orders/show/adjustments/index/adjustable/spree_shipment/component.rb new file mode 100644 index 00000000000..99515df64de --- /dev/null +++ b/admin/app/components/solidus_admin/orders/show/adjustments/index/adjustable/spree_shipment/component.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class SolidusAdmin::Orders::Show::Adjustments::Index::Adjustable::SpreeShipment::Component < SolidusAdmin::Orders::Show::Adjustments::Index::Adjustable::Component + def caption + "#{t('spree.shipment')} ##{adjustable.number}" + end + + def detail + link_to( + adjustable.shipping_method.name, + spree.edit_admin_shipping_method_path(adjustable.shipping_method), + class: "body-link" + ) + end +end diff --git a/admin/app/components/solidus_admin/orders/show/adjustments/index/component.rb b/admin/app/components/solidus_admin/orders/show/adjustments/index/component.rb index 1fb6748cfb0..1438d98948c 100644 --- a/admin/app/components/solidus_admin/orders/show/adjustments/index/component.rb +++ b/admin/app/components/solidus_admin/orders/show/adjustments/index/component.rb @@ -73,10 +73,9 @@ def columns header: :adjustable, col: { class: 'w-56' }, data: ->(adjustment) { - tag.figure(safe_join([ - render(component("ui/thumbnail").for(adjustment.adjustable, class: "basis-10")), - figcaption_for_adjustable(adjustment), - ]), class: "flex items-center gap-2") + component_name = adjustment.adjustable&.class&.table_name&.singularize + component_key = ["orders/show/adjustments/index/adjustable", component_name].compact.join("/") + render component(component_key).new(adjustment) } }, { @@ -141,57 +140,4 @@ def columns }, ] end - - def icon_thumbnail(name) - render component("ui/thumbnail").new(src: svg_data_uri(icon_tag(name))) - end - - def svg_data_uri(data) - "data:image/svg+xml;base64,#{Base64.strict_encode64(data)}" - end - - def figcaption_for_adjustable(adjustment) - # ["Spree::LineItem", "Spree::Order", "Spree::Shipment"] - record = adjustment.adjustable - record_class = adjustment.adjustable_type&.constantize - - case record || record_class - when Spree::LineItem - variant = record.variant - options_text = variant.options_text.presence - - description = options_text || variant.sku - detail = link_to(variant.product.name, solidus_admin.product_path(record.variant.product), class: "body-link") - when Spree::Order - description = "#{Spree::Order.model_name.human} ##{record.number}" - detail = record.display_total - when Spree::Shipment - description = "#{t('spree.shipment')} ##{record.number}" - detail = link_to(record.shipping_method.name, spree.edit_admin_shipping_method_path(record.shipping_method), class: "body-link") - when nil - # noop - else - name_method = [:display_name, :name, :number].find { record.respond_to? _1 } if record - price_method = [:display_amount, :display_total, :display_cost].find { record.respond_to? _1 } if record - - description = record_class.model_name.human - description = "#{description} - #{record.public_send(name_method)}" if name_method - - # attempt creating a link - url_options = [:admin, record, :edit, { only_path: true }] - url = begin; spree.url_for(url_options); rescue NoMethodError => e; logger.error(e.to_s); nil end - - description = link_to(description, url, class: "body-link") if url - detail = record.public_send(price_method) if price_method - end - - thumbnail_caption(description, detail) - end - - def thumbnail_caption(first_line, second_line) - tag.figcaption(safe_join([ - tag.div(first_line || NBSP, class: 'text-black body-small whitespace-nowrap text-ellipsis overflow-hidden'), - tag.div(second_line || NBSP, class: 'text-gray-500 body-small whitespace-nowrap text-ellipsis overflow-hidden') - ]), class: "flex flex-col gap-0 max-w-[15rem]") - end end diff --git a/admin/app/components/solidus_admin/ui/thumbnail_with_caption/component.html.erb b/admin/app/components/solidus_admin/ui/thumbnail_with_caption/component.html.erb index 37746c9d100..d8d6dbed29b 100644 --- a/admin/app/components/solidus_admin/ui/thumbnail_with_caption/component.html.erb +++ b/admin/app/components/solidus_admin/ui/thumbnail_with_caption/component.html.erb @@ -1,5 +1,9 @@
- <%= icon_thumbnail %> + <% if content? %> + <%= content %> + <% else %> + <%= icon_thumbnail %> + <% end %>
<%= caption %> From 0fae0c7d179f44344bbe9a648ea7fb22b11827a9 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 14 Jun 2024 10:33:34 +0200 Subject: [PATCH 069/834] DRY up dynamic component dispatch --- .../orders/show/adjustments/index/component.rb | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/admin/app/components/solidus_admin/orders/show/adjustments/index/component.rb b/admin/app/components/solidus_admin/orders/show/adjustments/index/component.rb index 1438d98948c..7a06b5abd72 100644 --- a/admin/app/components/solidus_admin/orders/show/adjustments/index/component.rb +++ b/admin/app/components/solidus_admin/orders/show/adjustments/index/component.rb @@ -73,18 +73,14 @@ def columns header: :adjustable, col: { class: 'w-56' }, data: ->(adjustment) { - component_name = adjustment.adjustable&.class&.table_name&.singularize - component_key = ["orders/show/adjustments/index/adjustable", component_name].compact.join("/") - render component(component_key).new(adjustment) + render_thumbnail_with_caption(adjustment, :adjustable) } }, { header: :source, col: { class: "w-56" }, data: ->(adjustment) { - component_name = adjustment.source&.class&.table_name&.singularize - component_key = ["orders/show/adjustments/index/source", component_name].compact.join("/") - render component(component_key).new(adjustment) + render_thumbnail_with_caption(adjustment, :source) } }, { @@ -140,4 +136,12 @@ def columns }, ] end + + private + + def render_thumbnail_with_caption(adjustment, role) + component_name = adjustment.send(role).class.base_class.name.delete("::").underscore if adjustment.send(role) + component_key = ["orders/show/adjustments/index/#{role}", component_name].compact.join("/") + render component(component_key).new(adjustment) + end end From ec157514d9622a03f8ac4fbfb64a9c115589270c Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 14 Jun 2024 11:57:12 +0200 Subject: [PATCH 070/834] Add spec coverage for shipment adjustments --- .../spec/features/orders/adjustments_spec.rb | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/admin/spec/features/orders/adjustments_spec.rb b/admin/spec/features/orders/adjustments_spec.rb index c9219ef8a7a..949a72d4d37 100644 --- a/admin/spec/features/orders/adjustments_spec.rb +++ b/admin/spec/features/orders/adjustments_spec.rb @@ -85,4 +85,25 @@ expect(page).to be_axe_clean end end + + context "with a shipment being adjusted" do + let(:order) { create(:order_with_line_items, number: "R123456789") } + + before do + order.shipments.first.adjustments.create!( + order: order, + label: "Manual shipping discount", + amount: -2, + source: nil + ) + end + + it "can display a shipment adjustment" do + visit "/admin/orders/R123456789" + + click_on "Adjustments" + expect(page).to have_content("Manual shipping discount") + expect(page).to be_axe_clean + end + end end From 6cfcdd76f066c1a28bd1e48193ef774c042568af Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 21 Jun 2024 12:02:11 +0200 Subject: [PATCH 071/834] Fix bin/rails in solidus_legacy_promotions This had some copy-pasta, and the requiring the engine requires requiring the gem first. --- legacy_promotions/bin/rails | 2 +- legacy_promotions/lib/solidus_legacy_promotions/engine.rb | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/legacy_promotions/bin/rails b/legacy_promotions/bin/rails index 35347a1f6a8..cddc7570f10 100755 --- a/legacy_promotions/bin/rails +++ b/legacy_promotions/bin/rails @@ -3,7 +3,7 @@ # installed from the root of your application. ENGINE_ROOT = File.expand_path('..', __dir__) -ENGINE_PATH = File.expand_path('../lib/spree/core/engine', __dir__) +ENGINE_PATH = File.expand_path('../lib/solidus_legacy_promotions/engine', __dir__) # Set up gems listed in the Gemfile. ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __dir__) diff --git a/legacy_promotions/lib/solidus_legacy_promotions/engine.rb b/legacy_promotions/lib/solidus_legacy_promotions/engine.rb index 21fc47fb8c5..e610740fd0a 100644 --- a/legacy_promotions/lib/solidus_legacy_promotions/engine.rb +++ b/legacy_promotions/lib/solidus_legacy_promotions/engine.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require 'solidus_legacy_promotions' + module SolidusLegacyPromotions class Engine < ::Rails::Engine include SolidusSupport::EngineExtensions From 10b555b13e15e8b81be0863ee877dd9876148a87 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Sat, 22 Jun 2024 10:27:39 +0200 Subject: [PATCH 072/834] Only validate adjustment promotion code for legacy promo adjustments SolidusFriendlyPromotions does not store promotion code IDs on adjustments, and it does not need to: The promotion code is stored on the `friendly_order_promotions` join table between promotion and order, and adjustments and promotion codes can be joined between that table and the adjustments' source. This fixes the build on solidus_friendly_promotions currently, because what that gem does it simply make `require_promotion_code` return `false` for adjustments created with the new promotion system. This change makes the validation self-contained for adjustments from the `legacy_promotions` gem. --- .../models/spree_adjustment_decorator.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/legacy_promotions/app/decorators/solidus_legacy_promotions/models/spree_adjustment_decorator.rb b/legacy_promotions/app/decorators/solidus_legacy_promotions/models/spree_adjustment_decorator.rb index d3068638f66..1ba2a643a30 100644 --- a/legacy_promotions/app/decorators/solidus_legacy_promotions/models/spree_adjustment_decorator.rb +++ b/legacy_promotions/app/decorators/solidus_legacy_promotions/models/spree_adjustment_decorator.rb @@ -56,8 +56,12 @@ def calculate_eligibility private + def legacy_promotion? + source_type == "Spree::PromotionAction" + end + def require_promotion_code? - promotion? && !source.promotion.apply_automatically && source.promotion.codes.any? + legacy_promotion? && !source.promotion.apply_automatically && source.promotion.codes.any? end Spree::Adjustment.prepend self From 57d30e7895e0d9158d936f7252fba80a1226e7f1 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Sat, 22 Jun 2024 10:31:19 +0200 Subject: [PATCH 073/834] Admin: Don't set promotion_code_id in specs It's always set to nil, so it doesn't matter for the specs in question, and we can just not specify it. --- admin/spec/features/orders/adjustments_spec.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/admin/spec/features/orders/adjustments_spec.rb b/admin/spec/features/orders/adjustments_spec.rb index 949a72d4d37..16006d5b942 100644 --- a/admin/spec/features/orders/adjustments_spec.rb +++ b/admin/spec/features/orders/adjustments_spec.rb @@ -22,8 +22,7 @@ created_at: Time.current, updated_at: Time.current, included: false, - source: taxrate, - promotion_code_id: nil, + source: taxrate ) visit "/admin/orders/R123456789" @@ -60,8 +59,7 @@ created_at: Time.current, updated_at: Time.current, included: false, - source: nil, - promotion_code_id: nil, + source: nil ) visit "/admin/orders/R123456789" From 07d39c09193f3de2a759fcf9f5815472046c0d6f Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Sat, 22 Jun 2024 10:32:36 +0200 Subject: [PATCH 074/834] API: Only send adjustment promo code ID when legacy promos are loaded If `solidus_legacy_promotions` is loaded, we can be sure that the column exists. --- api/lib/spree/api_configuration.rb | 2 +- api/openapi/solidus-api.oas.yml | 2 -- .../lib/solidus_legacy_promotions/engine.rb | 2 ++ legacy_promotions/spec/lib/spree/api_config_spec.rb | 11 +++++++++++ 4 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 legacy_promotions/spec/lib/spree/api_config_spec.rb diff --git a/api/lib/spree/api_configuration.rb b/api/lib/spree/api_configuration.rb index a71c363114a..856d235a3b7 100644 --- a/api/lib/spree/api_configuration.rb +++ b/api/lib/spree/api_configuration.rb @@ -72,7 +72,7 @@ class ApiConfiguration < Preferences::Configuration preference :adjustment_attributes, :array, default: [ :id, :source_type, :source_id, :adjustable_type, :adjustable_id, - :amount, :label, :promotion_code_id, + :amount, :label, :finalized, :eligible, :created_at, :updated_at ] diff --git a/api/openapi/solidus-api.oas.yml b/api/openapi/solidus-api.oas.yml index 70bc4bf7587..24ccad27ae2 100644 --- a/api/openapi/solidus-api.oas.yml +++ b/api/openapi/solidus-api.oas.yml @@ -6126,8 +6126,6 @@ components: type: integer label: type: string - promotion_code_id: - type: integer source_id: type: integer source_type: diff --git a/legacy_promotions/lib/solidus_legacy_promotions/engine.rb b/legacy_promotions/lib/solidus_legacy_promotions/engine.rb index e610740fd0a..9e1d4e8b220 100644 --- a/legacy_promotions/lib/solidus_legacy_promotions/engine.rb +++ b/legacy_promotions/lib/solidus_legacy_promotions/engine.rb @@ -89,6 +89,8 @@ class Engine < ::Rails::Engine Spree::Config.order_contents_class = "Spree::OrderContents" Spree::Config.promotions = SolidusLegacyPromotions::Configuration.new Spree::Config.adjustment_promotion_source_types << "Spree::PromotionAction" + + Spree::Api::Config.adjustment_attributes << :promotion_code_id end end end diff --git a/legacy_promotions/spec/lib/spree/api_config_spec.rb b/legacy_promotions/spec/lib/spree/api_config_spec.rb new file mode 100644 index 00000000000..8055572edfc --- /dev/null +++ b/legacy_promotions/spec/lib/spree/api_config_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe Spree::Api::Config do + describe "#adjustment_attributes" do + subject { described_class.adjustment_attributes } + + it { is_expected.to include(:promotion_code_id) } + end +end From 91ba272e1849764729195dca3a57fee6b6092e83 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Sat, 22 Jun 2024 10:34:45 +0200 Subject: [PATCH 075/834] Remove Spree::Adjustment#promotion_code_id from core This moves the column into solidus_legacy_promotions, which is the only place it's used at. --- core/db/migrate/20160101010000_solidus_one_four.rb | 2 -- ...00123_add_promotion_code_id_to_spree_adjustments.rb | 10 ++++++++++ legacy_promotions/spec/models/spree/adjustment_spec.rb | 1 + 3 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 legacy_promotions/db/migrate/20240621100123_add_promotion_code_id_to_spree_adjustments.rb diff --git a/core/db/migrate/20160101010000_solidus_one_four.rb b/core/db/migrate/20160101010000_solidus_one_four.rb index a7a910c23fd..99c69eaa325 100644 --- a/core/db/migrate/20160101010000_solidus_one_four.rb +++ b/core/db/migrate/20160101010000_solidus_one_four.rb @@ -90,14 +90,12 @@ def up t.datetime "updated_at", precision: 6 t.integer "order_id", null: false t.boolean "included", default: false - t.integer "promotion_code_id" t.integer "adjustment_reason_id" t.boolean "finalized", default: false, null: false t.index ["adjustable_id", "adjustable_type"], name: "index_spree_adjustments_on_adjustable_id_and_adjustable_type" t.index ["adjustable_id"], name: "index_adjustments_on_order_id" t.index ["eligible"], name: "index_spree_adjustments_on_eligible" t.index ["order_id"], name: "index_spree_adjustments_on_order_id" - t.index ["promotion_code_id"], name: "index_spree_adjustments_on_promotion_code_id" t.index ["source_id", "source_type"], name: "index_spree_adjustments_on_source_id_and_source_type" end diff --git a/legacy_promotions/db/migrate/20240621100123_add_promotion_code_id_to_spree_adjustments.rb b/legacy_promotions/db/migrate/20240621100123_add_promotion_code_id_to_spree_adjustments.rb new file mode 100644 index 00000000000..069cb9f5821 --- /dev/null +++ b/legacy_promotions/db/migrate/20240621100123_add_promotion_code_id_to_spree_adjustments.rb @@ -0,0 +1,10 @@ +class AddPromotionCodeIdToSpreeAdjustments < ActiveRecord::Migration[5.0] + def up + unless column_exists?(:spree_adjustments, :promotion_code_id) + add_column :spree_adjustments, :promotion_code_id, :integer + end + unless index_exists?(:spree_adjustments, :promotion_code_id) + add_index :spree_adjustments, :promotion_code_id, name: "index_spree_adjustments_on_promotion_code_id" + end + end +end diff --git a/legacy_promotions/spec/models/spree/adjustment_spec.rb b/legacy_promotions/spec/models/spree/adjustment_spec.rb index 48596f5f03d..eb2e1b5380b 100644 --- a/legacy_promotions/spec/models/spree/adjustment_spec.rb +++ b/legacy_promotions/spec/models/spree/adjustment_spec.rb @@ -9,6 +9,7 @@ let(:adjustment) { Spree::Adjustment.create!(label: 'Adjustment', adjustable: order, order: order, amount: 5) } + it { is_expected.to respond_to(:promotion_code) } context '#recalculate' do subject { adjustment.recalculate } let(:adjustment) do From 7fa5369ec4f84e4b226f36871956c4f50f55ff0d Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Mon, 24 Jun 2024 08:21:15 +0200 Subject: [PATCH 076/834] Remove spree_orders_promotions from core migration This table is created in legacy_promotions/db/migrate/20160101010001_solidus_one_four_promotions.rb, and that this is still in core is an oversight. --- core/db/migrate/20160101010000_solidus_one_four.rb | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/core/db/migrate/20160101010000_solidus_one_four.rb b/core/db/migrate/20160101010000_solidus_one_four.rb index a7a910c23fd..b1f178f626a 100644 --- a/core/db/migrate/20160101010000_solidus_one_four.rb +++ b/core/db/migrate/20160101010000_solidus_one_four.rb @@ -326,16 +326,6 @@ def up t.index ["user_id"], name: "index_spree_orders_on_user_id" end - create_table "spree_orders_promotions", force: :cascade do |t| - t.integer "order_id" - t.integer "promotion_id" - t.integer "promotion_code_id" - t.datetime "created_at", precision: 6 - t.datetime "updated_at", precision: 6 - t.index ["order_id", "promotion_id"], name: "index_spree_orders_promotions_on_order_id_and_promotion_id" - t.index ["promotion_code_id"], name: "index_spree_orders_promotions_on_promotion_code_id" - end - create_table "spree_payment_capture_events", force: :cascade do |t| t.decimal "amount", precision: 10, scale: 2, default: "0.0" t.integer "payment_id" From 2cadbb3aa0f6663bccaef8f28a61d6be23a54594 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Sat, 22 Jun 2024 13:36:39 +0200 Subject: [PATCH 077/834] DB: Move Spree::Adjustment#eligible to legacy_promotions --- core/db/migrate/20160101010000_solidus_one_four.rb | 2 -- ...4_move_adjustment_eligible_to_legacy_promotions.rb | 11 +++++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 legacy_promotions/db/migrate/20240622113334_move_adjustment_eligible_to_legacy_promotions.rb diff --git a/core/db/migrate/20160101010000_solidus_one_four.rb b/core/db/migrate/20160101010000_solidus_one_four.rb index ef584bf7f10..317e9c99d5c 100644 --- a/core/db/migrate/20160101010000_solidus_one_four.rb +++ b/core/db/migrate/20160101010000_solidus_one_four.rb @@ -85,7 +85,6 @@ def up t.integer "adjustable_id", null: false t.decimal "amount", precision: 10, scale: 2 t.string "label" - t.boolean "eligible", default: true t.datetime "created_at", precision: 6 t.datetime "updated_at", precision: 6 t.integer "order_id", null: false @@ -94,7 +93,6 @@ def up t.boolean "finalized", default: false, null: false t.index ["adjustable_id", "adjustable_type"], name: "index_spree_adjustments_on_adjustable_id_and_adjustable_type" t.index ["adjustable_id"], name: "index_adjustments_on_order_id" - t.index ["eligible"], name: "index_spree_adjustments_on_eligible" t.index ["order_id"], name: "index_spree_adjustments_on_order_id" t.index ["source_id", "source_type"], name: "index_spree_adjustments_on_source_id_and_source_type" end diff --git a/legacy_promotions/db/migrate/20240622113334_move_adjustment_eligible_to_legacy_promotions.rb b/legacy_promotions/db/migrate/20240622113334_move_adjustment_eligible_to_legacy_promotions.rb new file mode 100644 index 00000000000..d69646964ba --- /dev/null +++ b/legacy_promotions/db/migrate/20240622113334_move_adjustment_eligible_to_legacy_promotions.rb @@ -0,0 +1,11 @@ +class MoveAdjustmentEligibleToLegacyPromotions < ActiveRecord::Migration[7.0] + def up + unless column_exists?(:spree_adjustments, :eligible) + add_column(:spree_adjustments, :eligible, :boolean, default: true) + end + + unless index_exists?(:spree_adjustments, :eligible) + add_index :spree_adjustments, :eligible, name: "index_spree_adjustments_on_eligible" + end + end +end From f2e41b8eaacf9e31e260e939cd79c3ff9c5a6bb1 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Sat, 22 Jun 2024 13:56:44 +0200 Subject: [PATCH 078/834] Rely on ActiveRecord for default Adjustment#eligible value The ActiveRecord migration specifies that adjustments are eligible by default, so this is a no-op. --- core/lib/spree/testing_support/factories/adjustment_factory.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/core/lib/spree/testing_support/factories/adjustment_factory.rb b/core/lib/spree/testing_support/factories/adjustment_factory.rb index 810b7efe3c1..0d4b086dce7 100644 --- a/core/lib/spree/testing_support/factories/adjustment_factory.rb +++ b/core/lib/spree/testing_support/factories/adjustment_factory.rb @@ -7,7 +7,6 @@ amount { 100.0 } label { 'Shipping' } association(:source, factory: :tax_rate) - eligible { true } after(:build) do |adjustment| adjustments = adjustment.adjustable.adjustments From 9a1c52846679648be96c0e6bd69b838f3937c407 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Sat, 22 Jun 2024 13:59:44 +0200 Subject: [PATCH 079/834] Move implementations of eligible methods to legacy_promotions We're just keeping stubs in core for the time being, so we can properly deprecate once we've moved everything related to eligibility into solidus_legacy_promotions. --- core/app/models/spree/adjustment.rb | 10 ++++- core/spec/models/spree/adjustment_spec.rb | 6 +++ .../models/spree_adjustment_decorator.rb | 7 ++++ .../spec/models/spree/adjustment_spec.rb | 41 +++++++++++++++++++ 4 files changed, 63 insertions(+), 1 deletion(-) diff --git a/core/app/models/spree/adjustment.rb b/core/app/models/spree/adjustment.rb index 60647a8281a..f123962e04e 100644 --- a/core/app/models/spree/adjustment.rb +++ b/core/app/models/spree/adjustment.rb @@ -36,7 +36,10 @@ class Adjustment < Spree::Base end scope :price, -> { where(adjustable_type: 'Spree::LineItem') } scope :shipping, -> { where(adjustable_type: 'Spree::Shipment') } - scope :eligible, -> { where(eligible: true) } + scope :eligible, -> { all } + class << self + deprecate :eligible, deprecator: Spree.deprecator + end scope :charge, -> { where("#{quoted_table_name}.amount >= 0") } scope :credit, -> { where("#{quoted_table_name}.amount < 0") } scope :nonzero, -> { where("#{quoted_table_name}.amount != 0") } @@ -87,5 +90,10 @@ def tax? def cancellation? source_type == 'Spree::UnitCancel' end + + def eligible? + true + end + alias_method :eligible, :eligible? end end diff --git a/core/spec/models/spree/adjustment_spec.rb b/core/spec/models/spree/adjustment_spec.rb index d83962f37e5..3a2933900e1 100644 --- a/core/spec/models/spree/adjustment_spec.rb +++ b/core/spec/models/spree/adjustment_spec.rb @@ -18,6 +18,12 @@ end end + describe ".eligible", :silence_deprecations do + subject { described_class.eligible.to_sql } + + it { is_expected.to eq(Spree::Adjustment.all.to_sql) } + end + describe 'non_tax scope' do subject do Spree::Adjustment.non_tax.to_a diff --git a/legacy_promotions/app/decorators/solidus_legacy_promotions/models/spree_adjustment_decorator.rb b/legacy_promotions/app/decorators/solidus_legacy_promotions/models/spree_adjustment_decorator.rb index 1ba2a643a30..2d2b8a6b5ec 100644 --- a/legacy_promotions/app/decorators/solidus_legacy_promotions/models/spree_adjustment_decorator.rb +++ b/legacy_promotions/app/decorators/solidus_legacy_promotions/models/spree_adjustment_decorator.rb @@ -5,6 +5,8 @@ module SpreeAdjustmentDecorator def self.prepended(base) base.belongs_to :promotion_code, class_name: 'Spree::PromotionCode', optional: true base.validates :promotion_code, presence: true, if: :require_promotion_code? + + base.scope :eligible, -> { where(eligible: true) } end # Recalculate and persist the amount from this adjustment's source based on @@ -54,6 +56,11 @@ def calculate_eligibility end deprecate :calculate_eligibility, deprecator: ::Spree.deprecator + def eligible + self[:eligible] + end + alias_method :eligible?, :eligible + private def legacy_promotion? diff --git a/legacy_promotions/spec/models/spree/adjustment_spec.rb b/legacy_promotions/spec/models/spree/adjustment_spec.rb index eb2e1b5380b..d41f3f921ac 100644 --- a/legacy_promotions/spec/models/spree/adjustment_spec.rb +++ b/legacy_promotions/spec/models/spree/adjustment_spec.rb @@ -178,4 +178,45 @@ it { is_expected.to eq true } end end + + describe ".eligible scope" do + let!(:eligible_adjustment) { create(:adjustment, eligible: true) } + let!(:ineligible_adjustment) { create(:adjustment, eligible: false) } + + subject { described_class.eligible } + + it { is_expected.to contain_exactly(eligible_adjustment) } + end + + describe "#eligible" do + subject { adjustment.eligible } + + context "when the adjustment is eligible" do + let(:adjustment) { create(:adjustment, eligible: true) } + + it { is_expected.to be true } + end + + context "when the adjustment is not not eligible" do + let(:adjustment) { create(:adjustment, eligible: false) } + + it { is_expected.to be false } + end + end + + describe "#eligible?" do + subject { adjustment.eligible? } + + context "when the adjustment is eligible" do + let(:adjustment) { create(:adjustment, eligible: true) } + + it { is_expected.to be true } + end + + context "when the adjustment is not not eligible" do + let(:adjustment) { create(:adjustment, eligible: false) } + + it { is_expected.to be false } + end + end end From 7dc27444476ec5d911c1585e23a962204b0e13da Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Sat, 22 Jun 2024 14:04:44 +0200 Subject: [PATCH 080/834] Admin: Only filter adjustments by eligible if legacy promos present --- .../solidus_admin/adjustments_controller.rb | 16 ++++++++------- ..._admin_adjustments_controller_decorator.rb | 20 +++++++++++++++++++ 2 files changed, 29 insertions(+), 7 deletions(-) create mode 100644 legacy_promotions/app/decorators/solidus_legacy_promotions/controllers/solidus_admin/solidus_admin_adjustments_controller_decorator.rb diff --git a/admin/app/controllers/solidus_admin/adjustments_controller.rb b/admin/app/controllers/solidus_admin/adjustments_controller.rb index 6ad88acf2d8..350bc3dee5d 100644 --- a/admin/app/controllers/solidus_admin/adjustments_controller.rb +++ b/admin/app/controllers/solidus_admin/adjustments_controller.rb @@ -4,13 +4,7 @@ class SolidusAdmin::AdjustmentsController < SolidusAdmin::BaseController before_action :load_order def index - @adjustments = @order - .all_adjustments - .eligible - .order("adjustable_type ASC, created_at ASC") - .ransack(params[:q]) - .result - + load_adjustments set_page_and_extract_portion_from(@adjustments) respond_to do |format| @@ -49,6 +43,14 @@ def destroy private + def load_adjustments + @adjustments = @order + .all_adjustments + .order("adjustable_type ASC, created_at ASC") + .ransack(params[:q]) + .result + end + def load_order @order = Spree::Order.find_by!(number: params[:order_id]) end diff --git a/legacy_promotions/app/decorators/solidus_legacy_promotions/controllers/solidus_admin/solidus_admin_adjustments_controller_decorator.rb b/legacy_promotions/app/decorators/solidus_legacy_promotions/controllers/solidus_admin/solidus_admin_adjustments_controller_decorator.rb new file mode 100644 index 00000000000..5b489bfa34c --- /dev/null +++ b/legacy_promotions/app/decorators/solidus_legacy_promotions/controllers/solidus_admin/solidus_admin_adjustments_controller_decorator.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module SolidusLegacyPromotions + module SolidusAdminAdjustmentsControllerDecorator + private + + def load_adjustments + @adjustments = @order + .all_adjustments + .eligible + .order("adjustable_type ASC, created_at ASC") + .ransack(params[:q]) + .result + end + + if Object.const_defined?("SolidusAdmin::AdjustmentsController") + SolidusAdmin::AdjustmentsController.prepend self + end + end +end From 03bb3d8bec6d3515fae356adfabec8ac0c01261a Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Sat, 22 Jun 2024 14:05:37 +0200 Subject: [PATCH 081/834] Admin: Do not specify eligible default No need. --- admin/spec/features/orders/adjustments_spec.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/admin/spec/features/orders/adjustments_spec.rb b/admin/spec/features/orders/adjustments_spec.rb index 16006d5b942..6baeae09933 100644 --- a/admin/spec/features/orders/adjustments_spec.rb +++ b/admin/spec/features/orders/adjustments_spec.rb @@ -17,7 +17,6 @@ adjustable: order, amount: 10, label: "Test Adjustment", - eligible: true, finalized: false, created_at: Time.current, updated_at: Time.current, @@ -54,7 +53,6 @@ adjustable: order, amount: 10, label: "No Source Adjustment", - eligible: true, finalized: false, created_at: Time.current, updated_at: Time.current, From 3791f700f3435c50e79fdcec449082e1a5a3af7b Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Sat, 22 Jun 2024 14:06:27 +0200 Subject: [PATCH 082/834] API: Remove eligible from OAS spec --- api/openapi/solidus-api.oas.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/api/openapi/solidus-api.oas.yml b/api/openapi/solidus-api.oas.yml index 24ccad27ae2..138f1f7de60 100644 --- a/api/openapi/solidus-api.oas.yml +++ b/api/openapi/solidus-api.oas.yml @@ -6118,8 +6118,6 @@ components: type: string display_amount: type: string - eligible: - type: boolean finalized: type: boolean id: From f81b65d53130eec92e31638f015015a5607b98e9 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Sat, 22 Jun 2024 14:09:13 +0200 Subject: [PATCH 083/834] Only include Adjustment#eligible if legacy_promotions loaded Otherwise it's always true and doesn't really serve a purpose. --- api/lib/spree/api_configuration.rb | 2 +- legacy_promotions/lib/solidus_legacy_promotions/engine.rb | 1 + legacy_promotions/spec/lib/spree/api_config_spec.rb | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/api/lib/spree/api_configuration.rb b/api/lib/spree/api_configuration.rb index 856d235a3b7..2dce13e62d8 100644 --- a/api/lib/spree/api_configuration.rb +++ b/api/lib/spree/api_configuration.rb @@ -73,7 +73,7 @@ class ApiConfiguration < Preferences::Configuration preference :adjustment_attributes, :array, default: [ :id, :source_type, :source_id, :adjustable_type, :adjustable_id, :amount, :label, - :finalized, :eligible, :created_at, :updated_at + :finalized, :created_at, :updated_at ] preference :inventory_unit_attributes, :array, default: [ diff --git a/legacy_promotions/lib/solidus_legacy_promotions/engine.rb b/legacy_promotions/lib/solidus_legacy_promotions/engine.rb index 9e1d4e8b220..b1526f4eb42 100644 --- a/legacy_promotions/lib/solidus_legacy_promotions/engine.rb +++ b/legacy_promotions/lib/solidus_legacy_promotions/engine.rb @@ -91,6 +91,7 @@ class Engine < ::Rails::Engine Spree::Config.adjustment_promotion_source_types << "Spree::PromotionAction" Spree::Api::Config.adjustment_attributes << :promotion_code_id + Spree::Api::Config.adjustment_attributes << :eligible end end end diff --git a/legacy_promotions/spec/lib/spree/api_config_spec.rb b/legacy_promotions/spec/lib/spree/api_config_spec.rb index 8055572edfc..0c3432f97fe 100644 --- a/legacy_promotions/spec/lib/spree/api_config_spec.rb +++ b/legacy_promotions/spec/lib/spree/api_config_spec.rb @@ -7,5 +7,6 @@ subject { described_class.adjustment_attributes } it { is_expected.to include(:promotion_code_id) } + it { is_expected.to include(:eligible) } end end From d22d49434e1f881f318b85b5514a3a0ca747864b Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Sat, 22 Jun 2024 14:17:28 +0200 Subject: [PATCH 084/834] Do not filter adjustments by eligible in Cart Backbone View We overwrite the partial with a copy of the old code in solidus_legacy_promotions. --- .../views/order/details_adjustments.js | 3 +- .../views/order/details_adjustments.js | 43 +++++++++++++++++++ 2 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 legacy_promotions/app/assets/javascripts/spree/backend/views/order/details_adjustments.js diff --git a/backend/app/assets/javascripts/spree/backend/views/order/details_adjustments.js b/backend/app/assets/javascripts/spree/backend/views/order/details_adjustments.js index e25468a8471..c344368ca6e 100644 --- a/backend/app/assets/javascripts/spree/backend/views/order/details_adjustments.js +++ b/backend/app/assets/javascripts/spree/backend/views/order/details_adjustments.js @@ -9,8 +9,7 @@ Spree.Views.Order.DetailsAdjustments = Backbone.View.extend({ var collection = this.collection ? this.collection.chain() : _.chain([this.model]); collection .map(function(item) { - return (item.get("adjustments") || []) - .filter(function(adjustment) { return (adjustment.eligible === true); }); + return (item.get("adjustments") || []); }) .flatten(true) .each(function(adjustment){ diff --git a/legacy_promotions/app/assets/javascripts/spree/backend/views/order/details_adjustments.js b/legacy_promotions/app/assets/javascripts/spree/backend/views/order/details_adjustments.js new file mode 100644 index 00000000000..9db1b6c218a --- /dev/null +++ b/legacy_promotions/app/assets/javascripts/spree/backend/views/order/details_adjustments.js @@ -0,0 +1,43 @@ +Spree.Views.Order.DetailsAdjustments = Backbone.View.extend({ + initialize: function() { + this.listenTo(this.model, "change", this.render); + this.render() + }, + + adjustmentTotals: function() { + var totals = {}; + + var collection = this.collection ? this.collection.chain() : _.chain([this.model]); + collection + .map(function(item) { + return (item.get("adjustments") || []) + .filter(function(adjustment) { return (adjustment.eligible === true); }); + }) + .flatten(true) + .each(function(adjustment){ + var label = adjustment.label; + + /* Fixme: because this is done in JS, we only have floating point math */ + totals[label] = (totals[label] || 0); + totals[label] += Number(adjustment.amount); + }); + return totals; + }, + + render: function() { + var model = this.model; + var tbody = this.$('tbody'); + var adjustmentTotals = this.adjustmentTotals() + + tbody.empty(); + _.each(adjustmentTotals, function(amount, label) { + var html = HandlebarsTemplates["orders/details_adjustment_row"]({ + label: label, + amount: Spree.formatMoney(amount, model.get("currency")) + }); + tbody.append(html); + }); + + this.$el.toggleClass("hidden", _.isEmpty(adjustmentTotals)); + } +}) From 5266496db741f7c7f3bbbfc5b6345c26594d847e Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Sat, 22 Jun 2024 14:27:30 +0200 Subject: [PATCH 085/834] Move elibility strikethrough into solidus_legacy_promotions --- .../spree/backend/sections/_adjustments.scss | 3 --- .../admin/adjustments/_adjustment.html.erb | 1 - .../spree/backend/sections/_adjustments.scss | 3 +++ .../admin/adjustments/_adjustment.html.erb | 24 +++++++++++++++++++ 4 files changed, 27 insertions(+), 4 deletions(-) create mode 100644 legacy_promotions/app/assets/stylesheets/spree/backend/sections/_adjustments.scss create mode 100644 legacy_promotions/lib/views/backend/spree/admin/adjustments/_adjustment.html.erb diff --git a/backend/app/assets/stylesheets/spree/backend/sections/_adjustments.scss b/backend/app/assets/stylesheets/spree/backend/sections/_adjustments.scss index c7937f83298..e69de29bb2d 100644 --- a/backend/app/assets/stylesheets/spree/backend/sections/_adjustments.scss +++ b/backend/app/assets/stylesheets/spree/backend/sections/_adjustments.scss @@ -1,3 +0,0 @@ -.adjustment-ineligible { - text-decoration: line-through; -} diff --git a/backend/app/views/spree/admin/adjustments/_adjustment.html.erb b/backend/app/views/spree/admin/adjustments/_adjustment.html.erb index a2bbffb5f06..d0ac936fa0c 100644 --- a/backend/app/views/spree/admin/adjustments/_adjustment.html.erb +++ b/backend/app/views/spree/admin/adjustments/_adjustment.html.erb @@ -4,7 +4,6 @@ %> " > <%= display_adjustable(adjustment.adjustable) %> <%= adjustment.label %> diff --git a/legacy_promotions/app/assets/stylesheets/spree/backend/sections/_adjustments.scss b/legacy_promotions/app/assets/stylesheets/spree/backend/sections/_adjustments.scss new file mode 100644 index 00000000000..c7937f83298 --- /dev/null +++ b/legacy_promotions/app/assets/stylesheets/spree/backend/sections/_adjustments.scss @@ -0,0 +1,3 @@ +.adjustment-ineligible { + text-decoration: line-through; +} diff --git a/legacy_promotions/lib/views/backend/spree/admin/adjustments/_adjustment.html.erb b/legacy_promotions/lib/views/backend/spree/admin/adjustments/_adjustment.html.erb new file mode 100644 index 00000000000..a2bbffb5f06 --- /dev/null +++ b/legacy_promotions/lib/views/backend/spree/admin/adjustments/_adjustment.html.erb @@ -0,0 +1,24 @@ +<% + @edit_url = edit_admin_order_adjustment_path(@order, adjustment) + @delete_url = admin_order_adjustment_path(@order, adjustment) +%> +" + > + <%= display_adjustable(adjustment.adjustable) %> + <%= adjustment.label %> + <%= adjustment.display_amount.to_html %> + <%= adjustment_state(adjustment) %> + + <% unless adjustment.finalized? %> + <% if can?(:update, adjustment) %> + <%= link_to_edit adjustment, no_text: true %> + <% end %> + + <% if can?(:destroy, adjustment) %> + <%= link_to_delete adjustment, no_text: true %> + <% end %> + <% end %> + + From 6bfc2a4d1c0030168716a1b46273a393c0697dc6 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Sat, 22 Jun 2024 14:29:17 +0200 Subject: [PATCH 086/834] Move call to adjustment#eligible to legacy_promotions --- .../spree/admin/orders/_adjustments.html.erb | 2 +- .../spree/admin/orders/_adjustments.html.erb | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 legacy_promotions/lib/views/backend/spree/admin/orders/_adjustments.html.erb diff --git a/backend/app/views/spree/admin/orders/_adjustments.html.erb b/backend/app/views/spree/admin/orders/_adjustments.html.erb index 10e587d635a..c6959dba1bf 100644 --- a/backend/app/views/spree/admin/orders/_adjustments.html.erb +++ b/backend/app/views/spree/admin/orders/_adjustments.html.erb @@ -8,7 +8,7 @@ - <% adjustments.eligible.group_by(&:label).each do |label, adjustments| %> + <% adjustments.group_by(&:label).each do |label, adjustments| %> <%= label %>: <%= Spree::Money.new(adjustments.sum(&:amount), currency: adjustments.first.order.try(:currency)) %> diff --git a/legacy_promotions/lib/views/backend/spree/admin/orders/_adjustments.html.erb b/legacy_promotions/lib/views/backend/spree/admin/orders/_adjustments.html.erb new file mode 100644 index 00000000000..10e587d635a --- /dev/null +++ b/legacy_promotions/lib/views/backend/spree/admin/orders/_adjustments.html.erb @@ -0,0 +1,19 @@ +
+ <%= title %> + + + + + + + + + <% adjustments.eligible.group_by(&:label).each do |label, adjustments| %> + + + + + <% end %> + +
<%= Spree::Adjustment.human_attribute_name(:name) %><%= Spree::Adjustment.human_attribute_name(:amount) %>
<%= label %>:<%= Spree::Money.new(adjustments.sum(&:amount), currency: adjustments.first.order.try(:currency)) %>
+
From f05f6a877d1b200b174803296906c1673bed756f Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Sat, 22 Jun 2024 14:30:47 +0200 Subject: [PATCH 087/834] Move more calls to adjustment#eligible to legacy_promos --- .../admin/orders/_order_details.html.erb | 6 +- .../views/spree/admin/orders/confirm.html.erb | 6 +- .../admin/orders/_order_details.html.erb | 32 ++++++++++ .../spree/admin/orders/confirm.html.erb | 59 +++++++++++++++++++ 4 files changed, 97 insertions(+), 6 deletions(-) create mode 100644 legacy_promotions/lib/views/backend/spree/admin/orders/_order_details.html.erb create mode 100644 legacy_promotions/lib/views/backend/spree/admin/orders/confirm.html.erb diff --git a/backend/app/views/spree/admin/orders/_order_details.html.erb b/backend/app/views/spree/admin/orders/_order_details.html.erb index e60d336eb0f..89324738555 100644 --- a/backend/app/views/spree/admin/orders/_order_details.html.erb +++ b/backend/app/views/spree/admin/orders/_order_details.html.erb @@ -1,4 +1,4 @@ -
"> +
"> <%= render "spree/admin/orders/adjustments", { adjustments: order.line_item_adjustments, @@ -7,7 +7,7 @@ } %>
-
"> +
"> <%= render "spree/admin/orders/adjustments", { adjustments: order.shipment_adjustments, @@ -16,7 +16,7 @@ } %>
-
"> +
"> <%= render "spree/admin/orders/adjustments", { adjustments: order.adjustments, diff --git a/backend/app/views/spree/admin/orders/confirm.html.erb b/backend/app/views/spree/admin/orders/confirm.html.erb index 17e9c168421..2964e29af3d 100644 --- a/backend/app/views/spree/admin/orders/confirm.html.erb +++ b/backend/app/views/spree/admin/orders/confirm.html.erb @@ -18,7 +18,7 @@ order: @order, title: t('spree.line_item_adjustments'), } - ) if @order.line_item_adjustments.eligible.any? + ) if @order.line_item_adjustments.any? %> <%= @@ -29,7 +29,7 @@ order: @order, title: t('spree.shipment_adjustments'), } - ) if @order.shipment_adjustments.eligible.any? + ) if @order.shipment_adjustments.any? %> <%= @@ -40,7 +40,7 @@ order: @order, title: t('spree.order_adjustments'), } - ) if @order.adjustments.eligible.any? + ) if @order.adjustments.any? %> <% if @order.line_items.exists? %> diff --git a/legacy_promotions/lib/views/backend/spree/admin/orders/_order_details.html.erb b/legacy_promotions/lib/views/backend/spree/admin/orders/_order_details.html.erb new file mode 100644 index 00000000000..e60d336eb0f --- /dev/null +++ b/legacy_promotions/lib/views/backend/spree/admin/orders/_order_details.html.erb @@ -0,0 +1,32 @@ +
"> + <%= + render "spree/admin/orders/adjustments", { + adjustments: order.line_item_adjustments, + order: order, + title: t('spree.line_item_adjustments') + } + %> +
+
"> + <%= + render "spree/admin/orders/adjustments", { + adjustments: order.shipment_adjustments, + order: order, + title: t('spree.shipment_adjustments') + } + %> +
+
"> + <%= + render "spree/admin/orders/adjustments", { + adjustments: order.adjustments, + order: order, + title: t('spree.order_adjustments') + } + %> +
+ +
" id="order-total" data-hook="order_details_total"> + <%= t('spree.order_total') %> + <%= order.display_total %> +
diff --git a/legacy_promotions/lib/views/backend/spree/admin/orders/confirm.html.erb b/legacy_promotions/lib/views/backend/spree/admin/orders/confirm.html.erb new file mode 100644 index 00000000000..17e9c168421 --- /dev/null +++ b/legacy_promotions/lib/views/backend/spree/admin/orders/confirm.html.erb @@ -0,0 +1,59 @@ +<%= render partial: 'spree/admin/shared/order_tabs', locals: {current: 'Confirm'} %> + +<% admin_breadcrumb(t('spree.confirm_order')) %> + +<%= render partial: 'spree/admin/orders/confirm/customer_details', locals: {order: @order} %> + +<% if Spree::Order.checkout_step_names.include?(:delivery) %> + <%= render partial: 'spree/admin/orders/confirm/shipment', collection: @order.shipments, locals: {order: @order} %> +<% else %> + <%= render partial: 'spree/admin/orders/confirm/line_items', locals: {order: @order} %> +<% end %> + +<%= + render( + partial: 'spree/admin/orders/adjustments', + locals: { + adjustments: @order.line_item_adjustments, + order: @order, + title: t('spree.line_item_adjustments'), + } + ) if @order.line_item_adjustments.eligible.any? +%> + +<%= + render( + partial: 'spree/admin/orders/adjustments', + locals: { + adjustments: @order.shipment_adjustments, + order: @order, + title: t('spree.shipment_adjustments'), + } + ) if @order.shipment_adjustments.eligible.any? +%> + +<%= + render( + partial: 'spree/admin/orders/adjustments', + locals: { + adjustments: @order.adjustments, + order: @order, + title: t('spree.order_adjustments'), + } + ) if @order.adjustments.eligible.any? +%> + +<% if @order.line_items.exists? %> +
+ <%= t('spree.order_total') %> + <%= @order.display_total %> +
+<% end %> + +<%= render partial: 'spree/admin/orders/confirm/payments', locals: {payments: @order.payments} %> + +
+ <%= button_to [:complete, :admin, @order], { class: 'button', method: 'put' } do %> + <%= t('spree.complete_order') %> + <% end %> +
From 3d796c9f1f2a7ab212142edccb39c6f070ac016e Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Sat, 22 Jun 2024 14:35:38 +0200 Subject: [PATCH 088/834] Backend: Move order adjustments spec to legacy_promotions The deleted spec is a duplicate really. --- .../features/admin/orders/adjustments_spec.rb | 8 ---- .../admin/orders/order_details_spec.rb | 15 ------ .../backend/orders/adjustments_spec.rb | 46 +++++++++++++++++++ 3 files changed, 46 insertions(+), 23 deletions(-) create mode 100644 legacy_promotions/spec/features/backend/orders/adjustments_spec.rb diff --git a/backend/spec/features/admin/orders/adjustments_spec.rb b/backend/spec/features/admin/orders/adjustments_spec.rb index 57356ed9776..02ef8c740ec 100644 --- a/backend/spec/features/admin/orders/adjustments_spec.rb +++ b/backend/spec/features/admin/orders/adjustments_spec.rb @@ -22,7 +22,6 @@ let(:tax_category) { create(:tax_category) } let(:variant) { create(:variant, tax_category: tax_category) } - let!(:non_eligible_adjustment) { order.adjustments.create!(order: order, label: 'Non-Eligible', amount: 10, eligible: false) } let!(:adjustment) { order.adjustments.create!(order: order, label: 'Rebate', amount: 10) } before(:each) do @@ -41,13 +40,6 @@ expect(column_text(3)).to eq("$2.00") end end - - it "shows both eligible and non-eligible adjustments" do - expect(page).to have_content("Rebate") - expect(page).to have_content("Non-Eligible") - expect(find('tr', text: 'Rebate')[:class]).not_to eq('adjustment-ineligible') - expect(find('tr', text: 'Non-Eligible')[:class]).to eq('adjustment-ineligible') - end end context "admin creating a new adjustment" do diff --git a/backend/spec/features/admin/orders/order_details_spec.rb b/backend/spec/features/admin/orders/order_details_spec.rb index 459a4c5e94c..17430541eed 100644 --- a/backend/spec/features/admin/orders/order_details_spec.rb +++ b/backend/spec/features/admin/orders/order_details_spec.rb @@ -247,21 +247,6 @@ end end - context "with adjustments" do - let(:order) do - super().tap do |o| - o.adjustments.create!(order: order, label: 'Non-Eligible', amount: 10, eligible: false) - o.adjustments.create!(order: order, label: 'Rebate', amount: 10) - end - end - - it "shows only eligible adjustments" do - visit spree.cart_admin_order_path(order) - expect(page).to have_content("Rebate") - expect(page).not_to have_content("Non-Eligible") - end - end - context "variant doesn't track inventory" do let(:track_inventory) { false } let(:backorderable) { false } diff --git a/legacy_promotions/spec/features/backend/orders/adjustments_spec.rb b/legacy_promotions/spec/features/backend/orders/adjustments_spec.rb new file mode 100644 index 00000000000..77488f9becb --- /dev/null +++ b/legacy_promotions/spec/features/backend/orders/adjustments_spec.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe "Adjustments", type: :feature do + stub_authorization! + + context "when the order is completed" do + let!(:ship_address) { create(:address) } + let!(:tax_zone) { create(:global_zone) } # will include the above address + let!(:tax_rate) { create(:tax_rate, amount: 0.20, zone: tax_zone, tax_categories: [tax_category]) } + + let!(:order) do + create( + :completed_order_with_totals, + line_items_attributes: [{ price: 10, variant: variant }] * 5, + ship_address: ship_address + ) + end + let!(:line_item) { order.line_items[0] } + + let(:tax_category) { create(:tax_category) } + let(:variant) { create(:variant, tax_category: tax_category) } + + let!(:non_eligible_adjustment) { order.adjustments.create!(order: order, label: "Non-Eligible", amount: 10, eligible: false) } + let!(:adjustment) { order.adjustments.create!(order: order, label: "Rebate", amount: 10) } + + before(:each) do + order.recalculate + + visit spree.admin_path + click_link "Orders" + within_row(1) { click_icon :edit } + click_link "Adjustments" + end + + context "admin managing adjustments" do + it "shows both eligible and non-eligible adjustments" do + expect(page).to have_content("Rebate") + expect(page).to have_content("Non-Eligible") + expect(find("tr", text: "Rebate")[:class]).not_to eq("adjustment-ineligible") + expect(find("tr", text: "Non-Eligible")[:class]).to eq("adjustment-ineligible") + end + end + end +end From bf3ff8512ff37db838a8b93fb243f406f399861a Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Sat, 22 Jun 2024 14:37:38 +0200 Subject: [PATCH 089/834] Fix adjustment comment This makes reference to eligibility, and it uses outdated language for "finalized". --- core/app/models/spree/adjustment.rb | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/core/app/models/spree/adjustment.rb b/core/app/models/spree/adjustment.rb index f123962e04e..ca16e8fb494 100644 --- a/core/app/models/spree/adjustment.rb +++ b/core/app/models/spree/adjustment.rb @@ -4,17 +4,8 @@ module Spree # Adjustments represent a change to the +item_total+ of an Order. Each # adjustment has an +amount+ that can be either positive or negative. # - # Adjustments can be "opened" or "closed". Once an adjustment is closed, it + # Adjustments can be "unfinalized" or "finalized". Once an adjustment is finalized, it # will not be automatically updated. - # - # == Boolean attributes - # - # 1. *eligible?* - # - # This boolean attributes stores whether this adjustment is currently - # eligible for its order. Only eligible adjustments count towards the - # order's adjustment total. This allows an adjustment to be preserved if - # it becomes ineligible so it might be reinstated. class Adjustment < Spree::Base belongs_to :adjustable, polymorphic: true, touch: true, optional: true belongs_to :source, polymorphic: true, optional: true From d82cb3c1edd7e9c28ab617bd16102d0490a849d2 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Sat, 22 Jun 2024 14:42:29 +0200 Subject: [PATCH 090/834] Move eligibility check from line_item.rb to legacy_promotions The spec has already been moved over. --- core/app/models/spree/line_item.rb | 2 +- core/spec/models/spree/line_item_spec.rb | 1 - .../models/spree_line_item_decorator.rb | 11 +++++++++++ 3 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 legacy_promotions/app/decorators/solidus_legacy_promotions/models/spree_line_item_decorator.rb diff --git a/core/app/models/spree/line_item.rb b/core/app/models/spree/line_item.rb index e68783c9a57..8130c0ba8a9 100644 --- a/core/app/models/spree/line_item.rb +++ b/core/app/models/spree/line_item.rb @@ -62,7 +62,7 @@ def total # @return [BigDecimal] the amount of this item, taking into consideration # all non-tax adjustments. def total_before_tax - amount + adjustments.select { |value| !value.tax? && value.eligible? }.sum(&:amount) + amount + adjustments.reject(&:tax?).sum(&:amount) end # @return [BigDecimal] the amount of this line item before VAT tax diff --git a/core/spec/models/spree/line_item_spec.rb b/core/spec/models/spree/line_item_spec.rb index 67c0c41add3..e67dab00bc2 100644 --- a/core/spec/models/spree/line_item_spec.rb +++ b/core/spec/models/spree/line_item_spec.rb @@ -71,7 +71,6 @@ end let!(:admin_adjustment) { create(:adjustment, adjustable: line_item, order: line_item.order, amount: -1, source: nil) } let!(:other_adjustment) { create(:adjustment, adjustable: line_item, order: line_item.order, amount: -2, source: nil) } - let!(:ineligible_adjustment) { create(:adjustment, eligible: false, adjustable: line_item, order: line_item.order, amount: -4, source: nil) } it 'returns the amount minus any adjustments' do expect(line_item.total_before_tax).to eq(20 - 1 - 2) diff --git a/legacy_promotions/app/decorators/solidus_legacy_promotions/models/spree_line_item_decorator.rb b/legacy_promotions/app/decorators/solidus_legacy_promotions/models/spree_line_item_decorator.rb new file mode 100644 index 00000000000..f7d76204e41 --- /dev/null +++ b/legacy_promotions/app/decorators/solidus_legacy_promotions/models/spree_line_item_decorator.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module SolidusLegacyPromotions + module SpreeLineItemDecorator + def total_before_tax + amount + adjustments.select { |value| !value.tax? && value.eligible? }.sum(&:amount) + end + + Spree::LineItem.prepend self + end +end From f5b03f53c20dce6ccc6a960784d50625f7790c85 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Sat, 22 Jun 2024 14:49:14 +0200 Subject: [PATCH 091/834] Move order updater eligible functionality to legacy_promotions --- core/app/models/spree/order_updater.rb | 5 +-- .../models/spree_order_updater_decorator.rb | 40 +++++++++++++++++++ 2 files changed, 42 insertions(+), 3 deletions(-) create mode 100644 legacy_promotions/app/decorators/solidus_legacy_promotions/models/spree_order_updater_decorator.rb diff --git a/core/app/models/spree/order_updater.rb b/core/app/models/spree/order_updater.rb index b2994fd8ae7..2554c09b657 100644 --- a/core/app/models/spree/order_updater.rb +++ b/core/app/models/spree/order_updater.rb @@ -157,9 +157,9 @@ def update_adjustment_total recalculate_adjustments all_items = line_items + shipments - order_tax_adjustments = adjustments.select(&:eligible?).select(&:tax?) + order_tax_adjustments = adjustments.select(&:tax?) - order.adjustment_total = all_items.sum(&:adjustment_total) + adjustments.select(&:eligible?).sum(&:amount) + order.adjustment_total = all_items.sum(&:adjustment_total) + adjustments.sum(&:amount) order.included_tax_total = all_items.sum(&:included_tax_total) + order_tax_adjustments.select(&:included?).sum(&:amount) order.additional_tax_total = all_items.sum(&:additional_tax_total) + order_tax_adjustments.reject(&:included?).sum(&:amount) @@ -224,7 +224,6 @@ def update_item_totals # The cancellation_total isn't persisted anywhere but is included in # the adjustment_total item.adjustment_total = item.adjustments. - select(&:eligible?). reject(&:included?). sum(&:amount) diff --git a/legacy_promotions/app/decorators/solidus_legacy_promotions/models/spree_order_updater_decorator.rb b/legacy_promotions/app/decorators/solidus_legacy_promotions/models/spree_order_updater_decorator.rb new file mode 100644 index 00000000000..e8e18ee9cff --- /dev/null +++ b/legacy_promotions/app/decorators/solidus_legacy_promotions/models/spree_order_updater_decorator.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +module SolidusLegacyPromotions + module SpreeOrderUpdaterDecorator + def update_adjustment_total + recalculate_adjustments + + all_items = line_items + shipments + order_tax_adjustments = adjustments.select(&:eligible?).select(&:tax?) + + order.adjustment_total = all_items.sum(&:adjustment_total) + adjustments.select(&:eligible?).sum(&:amount) + order.included_tax_total = all_items.sum(&:included_tax_total) + order_tax_adjustments.select(&:included?).sum(&:amount) + order.additional_tax_total = all_items.sum(&:additional_tax_total) + order_tax_adjustments.reject(&:included?).sum(&:amount) + + update_order_total + end + + def update_item_totals + [*line_items, *shipments].each do |item| + # The cancellation_total isn't persisted anywhere but is included in + # the adjustment_total + item.adjustment_total = item.adjustments. + select(&:eligible?). + reject(&:included?). + sum(&:amount) + + if item.changed? + item.update_columns( + promo_total: item.promo_total, + included_tax_total: item.included_tax_total, + additional_tax_total: item.additional_tax_total, + adjustment_total: item.adjustment_total, + updated_at: Time.current, + ) + end + end + end + Spree::OrderUpdater.prepend self + end +end From 4ae2b43ebfc977c4d5056aa0ba1b555c307b5afe Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Sat, 22 Jun 2024 14:52:46 +0200 Subject: [PATCH 092/834] Do not respect eligible in core, but in legacy_promotions --- core/app/models/spree/order.rb | 2 +- core/app/models/spree/shipment.rb | 2 +- .../refunds/default_refund_amount_spec.rb | 1 - core/spec/models/spree/order_spec.rb | 8 ++++---- core/spec/models/spree/shipment_spec.rb | 1 - .../models/spree_order_decorator.rb | 4 ++++ .../models/spree_shipment_decorator.rb | 13 +++++++++++++ 7 files changed, 23 insertions(+), 8 deletions(-) create mode 100644 legacy_promotions/app/decorators/solidus_legacy_promotions/models/spree_shipment_decorator.rb diff --git a/core/app/models/spree/order.rb b/core/app/models/spree/order.rb index eae408eb392..01f79e25a14 100644 --- a/core/app/models/spree/order.rb +++ b/core/app/models/spree/order.rb @@ -227,7 +227,7 @@ def currency end def shipping_discount - shipment_adjustments.credit.eligible.sum(:amount) * - 1 + shipment_adjustments.credit.sum(:amount) * - 1 end def to_param diff --git a/core/app/models/spree/shipment.rb b/core/app/models/spree/shipment.rb index 336d76693d1..54381a87fbc 100644 --- a/core/app/models/spree/shipment.rb +++ b/core/app/models/spree/shipment.rb @@ -92,7 +92,7 @@ def total # @return [BigDecimal] the amount of this item, taking into consideration # all non-tax adjustments. def total_before_tax - amount + adjustments.select { |adjustment| !adjustment.tax? && adjustment.eligible? }.sum(&:amount) + amount + adjustments.reject(&:tax?).sum(&:amount) end # @return [BigDecimal] the amount of this shipment before VAT tax diff --git a/core/spec/models/spree/calculator/refunds/default_refund_amount_spec.rb b/core/spec/models/spree/calculator/refunds/default_refund_amount_spec.rb index 9a07fa5d40d..a871e382b7f 100644 --- a/core/spec/models/spree/calculator/refunds/default_refund_amount_spec.rb +++ b/core/spec/models/spree/calculator/refunds/default_refund_amount_spec.rb @@ -31,7 +31,6 @@ adjustable: order, order: order, amount: adjustment_amount, - eligible: true, label: 'Adjustment', source_type: 'Spree::Order' ) diff --git a/core/spec/models/spree/order_spec.rb b/core/spec/models/spree/order_spec.rb index c669fa98df9..cdf75f80306 100644 --- a/core/spec/models/spree/order_spec.rb +++ b/core/spec/models/spree/order_spec.rb @@ -1054,10 +1054,10 @@ def generate it "sums all of the line items totals before tax" do subject.line_items = [ Spree::LineItem.new(price: 10, quantity: 2, included_tax_total: 15.0).tap do |li| - li.adjustments.build(eligible: true, amount: -2) + li.adjustments.build(amount: -2) end, Spree::LineItem.new(price: 30, quantity: 1, included_tax_total: 16.0).tap do |li| - li.adjustments.build(eligible: true, amount: -3) + li.adjustments.build(amount: -3) end ] # (2*10)-2 + 30-3 = 18 + 27 = 14 @@ -1070,10 +1070,10 @@ def generate it "sums all of the line items totals before tax" do subject.shipments = [ Spree::Shipment.new(cost: 20, included_tax_total: 15.0).tap do |li| - li.adjustments.build(eligible: true, amount: -2) + li.adjustments.build(amount: -2) end, Spree::Shipment.new(cost: 30, included_tax_total: 16.0).tap do |li| - li.adjustments.build(eligible: true, amount: -3) + li.adjustments.build(amount: -3) end ] # 20-2 + 30-3 = 18 + 27 = 14 diff --git a/core/spec/models/spree/shipment_spec.rb b/core/spec/models/spree/shipment_spec.rb index 641b09c03f1..e1a5293a357 100644 --- a/core/spec/models/spree/shipment_spec.rb +++ b/core/spec/models/spree/shipment_spec.rb @@ -125,7 +125,6 @@ end let!(:admin_adjustment) { create(:adjustment, adjustable: shipment, order: shipment.order, amount: -1, source: nil) } let!(:other_adjustment) { create(:adjustment, adjustable: shipment, order: shipment.order, amount: -2, source: nil) } - let!(:ineligible_adjustment) { create(:adjustment, eligible: false, adjustable: shipment, order: shipment.order, amount: -4, source: nil) } it 'returns the amount minus any adjustments' do expect(shipment.total_before_tax).to eq(10 - 1 - 2) diff --git a/legacy_promotions/app/decorators/solidus_legacy_promotions/models/spree_order_decorator.rb b/legacy_promotions/app/decorators/solidus_legacy_promotions/models/spree_order_decorator.rb index 4ba9536b01b..a0a5680448d 100644 --- a/legacy_promotions/app/decorators/solidus_legacy_promotions/models/spree_order_decorator.rb +++ b/legacy_promotions/app/decorators/solidus_legacy_promotions/models/spree_order_decorator.rb @@ -12,6 +12,10 @@ def apply_shipping_promotions(_event = nil) recalculate end + def shipping_discount + shipment_adjustments.credit.eligible.sum(:amount) * - 1 + end + Spree::Order.prepend(self) end end diff --git a/legacy_promotions/app/decorators/solidus_legacy_promotions/models/spree_shipment_decorator.rb b/legacy_promotions/app/decorators/solidus_legacy_promotions/models/spree_shipment_decorator.rb new file mode 100644 index 00000000000..6d13ebfdf2b --- /dev/null +++ b/legacy_promotions/app/decorators/solidus_legacy_promotions/models/spree_shipment_decorator.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module SolidusLegacyPromotions + module SpreeShipmentDecorator + # @return [BigDecimal] the amount of this item, taking into consideration + # all non-tax eligible adjustments. + def total_before_tax + amount + adjustments.select { |adjustment| !adjustment.tax? && adjustment.eligible? }.sum(&:amount) + end + + Spree::Shipment.prepend self + end +end From b1f238fd24ebf7f8aec26d77bf73b1e6c0d9b8b2 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Sat, 22 Jun 2024 14:53:25 +0200 Subject: [PATCH 093/834] Do not specify eligible default in unit cancel --- core/app/models/spree/unit_cancel.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/core/app/models/spree/unit_cancel.rb b/core/app/models/spree/unit_cancel.rb index 480f3fe3547..54284c2c992 100644 --- a/core/app/models/spree/unit_cancel.rb +++ b/core/app/models/spree/unit_cancel.rb @@ -23,7 +23,6 @@ def adjust! amount: amount, order: inventory_unit.order, label: "#{I18n.t('spree.cancellation')} - #{reason}", - eligible: true, finalized: true ) From 03b3890fb35597a669775cff0272cfb65f183d0a Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Sat, 22 Jun 2024 14:56:15 +0200 Subject: [PATCH 094/834] Move eligible usage in order mailer views to legacy promos --- .../spree/order_mailer/cancel_email.html.erb | 2 +- .../spree/order_mailer/cancel_email.text.erb | 2 +- .../spree/order_mailer/confirm_email.html.erb | 10 +-- .../spree/order_mailer/confirm_email.text.erb | 10 +-- core/spec/mailers/order_mailer_spec.rb | 17 +--- .../spree/order_mailer/cancel_email.html.erb | 45 ++++++++++ .../spree/order_mailer/cancel_email.text.erb | 16 ++++ .../spree/order_mailer/confirm_email.html.erb | 84 +++++++++++++++++++ .../spree/order_mailer/confirm_email.text.erb | 38 +++++++++ .../spec/mailers/spree/order_mailer_spec.rb | 51 +++++++++++ 10 files changed, 250 insertions(+), 25 deletions(-) create mode 100644 legacy_promotions/app/views/spree/order_mailer/cancel_email.html.erb create mode 100644 legacy_promotions/app/views/spree/order_mailer/cancel_email.text.erb create mode 100644 legacy_promotions/app/views/spree/order_mailer/confirm_email.html.erb create mode 100644 legacy_promotions/app/views/spree/order_mailer/confirm_email.text.erb create mode 100644 legacy_promotions/spec/mailers/spree/order_mailer_spec.rb diff --git a/core/app/views/spree/order_mailer/cancel_email.html.erb b/core/app/views/spree/order_mailer/cancel_email.html.erb index 4978bbce22f..df6b41e7942 100644 --- a/core/app/views/spree/order_mailer/cancel_email.html.erb +++ b/core/app/views/spree/order_mailer/cancel_email.html.erb @@ -26,7 +26,7 @@ <%= t('.subtotal') %> <%= @order.display_item_total %> - <% @order.adjustments.eligible.each do |adjustment| %> + <% @order.adjustments.each do |adjustment| %> <%= sanitize(adjustment.label) %> diff --git a/core/app/views/spree/order_mailer/cancel_email.text.erb b/core/app/views/spree/order_mailer/cancel_email.text.erb index b27388c75db..3607dcd40ad 100644 --- a/core/app/views/spree/order_mailer/cancel_email.text.erb +++ b/core/app/views/spree/order_mailer/cancel_email.text.erb @@ -10,7 +10,7 @@ <% end %> ============================================================ <%= t('.subtotal') %> <%= @order.display_item_total %> -<% @order.adjustments.eligible.each do |adjustment| %> +<% @order.adjustments.each do |adjustment| %> <%= raw(adjustment.label) %> <%= adjustment.display_amount %> <% end %> <%= t('.total') %> <%= @order.display_total %> diff --git a/core/app/views/spree/order_mailer/confirm_email.html.erb b/core/app/views/spree/order_mailer/confirm_email.html.erb index ac05b46466d..feba49594ea 100644 --- a/core/app/views/spree/order_mailer/confirm_email.html.erb +++ b/core/app/views/spree/order_mailer/confirm_email.html.erb @@ -31,8 +31,8 @@ <% if @order.line_item_adjustments.exists? %> - <% if @order.all_adjustments.promotion.eligible.exists? %> - <% @order.all_adjustments.promotion.eligible.group_by(&:label).each do |label, adjustments| %> + <% if @order.all_adjustments.promotion.exists? %> + <% @order.all_adjustments.promotion.group_by(&:label).each do |label, adjustments| %> <%= t('spree.promotion') %> <%= label %>: @@ -48,8 +48,8 @@ <%= Spree::Money.new(shipments.sum(&:total_before_tax), currency: @order.currency) %> <% end %> - <% if @order.all_adjustments.eligible.tax.exists? %> - <% @order.all_adjustments.eligible.tax.group_by(&:label).each do |label, adjustments| %> + <% if @order.all_adjustments.tax.exists? %> + <% @order.all_adjustments.tax.group_by(&:label).each do |label, adjustments| %> <%= t('spree.tax') %> <%= label %>: @@ -57,7 +57,7 @@ <% end %> <% end %> - <% @order.adjustments.eligible.each do |adjustment| %> + <% @order.adjustments.each do |adjustment| %> <% next if (adjustment.source_type == 'Spree::TaxRate') and (adjustment.amount == 0) %> diff --git a/core/app/views/spree/order_mailer/confirm_email.text.erb b/core/app/views/spree/order_mailer/confirm_email.text.erb index 432247ccd8b..4d7cb24edb2 100644 --- a/core/app/views/spree/order_mailer/confirm_email.text.erb +++ b/core/app/views/spree/order_mailer/confirm_email.text.erb @@ -11,8 +11,8 @@ ============================================================ <%= t('.subtotal') %> <%= @order.display_item_total %> <% if @order.line_item_adjustments.exists? %> - <% if @order.all_adjustments.promotion.eligible.exists? %> - <% @order.all_adjustments.promotion.eligible.group_by(&:label).each do |label, adjustments| %> + <% if @order.all_adjustments.promotion.exists? %> + <% @order.all_adjustments.promotion.group_by(&:label).each do |label, adjustments| %> <%= t('spree.promotion') %>: <%= label %> <%= Spree::Money.new(adjustments.sum(&:amount), currency: @order.currency) %> <% end %> <% end %> @@ -22,13 +22,13 @@ <%= t('spree.shipping') %>: <%= name %> <%= Spree::Money.new(shipments.sum(&:total_before_tax), currency: @order.currency) %> <% end %> -<% if @order.all_adjustments.eligible.tax.exists? %> - <% @order.all_adjustments.eligible.tax.group_by(&:label).each do |label, adjustments| %> +<% if @order.all_adjustments.tax.exists? %> + <% @order.all_adjustments.tax.group_by(&:label).each do |label, adjustments| %> <%= t('spree.tax') %>: <%= label %> <%= Spree::Money.new(adjustments.sum(&:amount), currency: @order.currency) %> <% end %> <% end %> -<% @order.adjustments.eligible.each do |adjustment| %> +<% @order.adjustments.each do |adjustment| %> <% next if (adjustment.source_type == 'Spree::TaxRate') and (adjustment.amount == 0) %> <%= adjustment.label %> <%= adjustment.display_amount %> <% end %> diff --git a/core/spec/mailers/order_mailer_spec.rb b/core/spec/mailers/order_mailer_spec.rb index 0fa91fa6144..57deec8b676 100644 --- a/core/spec/mailers/order_mailer_spec.rb +++ b/core/spec/mailers/order_mailer_spec.rb @@ -31,22 +31,13 @@ expect(confirmation_email.body).not_to include(""") end - context "only shows eligible adjustments in emails" do + context "displays order adjustments in emails" do before do create( :adjustment, adjustable: order, order: order, - eligible: true, - label: 'Eligible Adjustment' - ) - - create( - :adjustment, - adjustable: order, - order: order, - eligible: false, - label: 'Ineligible Adjustment' + label: 'Order Adjustment' ) end @@ -54,11 +45,11 @@ let!(:cancel_email) { Spree::OrderMailer.cancel_email(order) } specify do - expect(confirmation_email.body).not_to include("Ineligible Adjustment") + expect(confirmation_email.parts.first.body).to include("Order Adjustment") end specify do - expect(cancel_email.body).not_to include("Ineligible Adjustment") + expect(cancel_email.parts.first.body).to include("Order Adjustment") end end diff --git a/legacy_promotions/app/views/spree/order_mailer/cancel_email.html.erb b/legacy_promotions/app/views/spree/order_mailer/cancel_email.html.erb new file mode 100644 index 00000000000..4978bbce22f --- /dev/null +++ b/legacy_promotions/app/views/spree/order_mailer/cancel_email.html.erb @@ -0,0 +1,45 @@ + + + + + +
+

+ <%= t('.dear_customer') %> +

+

+ <%= t('.instructions') %> +

+

+ <%= t('.order_summary_canceled') %> +

+ + <% @order.line_items.each do |item| %> + + + + + + <% end %> + + + + + + <% @order.adjustments.eligible.each do |adjustment| %> + + + + + + <% end %> + + + + + +
<%= item.variant.sku %> + <%= item.variant.product.name %> + <%= item.variant.options_text -%> + (<%=item.quantity%>) @ <%= item.single_money %> = <%= item.display_amount %>
<%= t('.subtotal') %><%= @order.display_item_total %>
<%= sanitize(adjustment.label) %><%= adjustment.display_amount %>
<%= t('.total') %><%= @order.display_total %>
+
diff --git a/legacy_promotions/app/views/spree/order_mailer/cancel_email.text.erb b/legacy_promotions/app/views/spree/order_mailer/cancel_email.text.erb new file mode 100644 index 00000000000..b27388c75db --- /dev/null +++ b/legacy_promotions/app/views/spree/order_mailer/cancel_email.text.erb @@ -0,0 +1,16 @@ +<%= t('.dear_customer') %> + +<%= t('.instructions') %> + +============================================================ +<%= t('.order_summary_canceled') %> +============================================================ +<% @order.line_items.each do |item| %> + <%= item.variant.sku %> <%= raw(item.variant.product.name) %> <%= raw(item.variant.options_text) -%> (<%=item.quantity%>) @ <%= item.single_money %> = <%= item.display_amount %> +<% end %> +============================================================ +<%= t('.subtotal') %> <%= @order.display_item_total %> +<% @order.adjustments.eligible.each do |adjustment| %> + <%= raw(adjustment.label) %> <%= adjustment.display_amount %> +<% end %> +<%= t('.total') %> <%= @order.display_total %> diff --git a/legacy_promotions/app/views/spree/order_mailer/confirm_email.html.erb b/legacy_promotions/app/views/spree/order_mailer/confirm_email.html.erb new file mode 100644 index 00000000000..ac05b46466d --- /dev/null +++ b/legacy_promotions/app/views/spree/order_mailer/confirm_email.html.erb @@ -0,0 +1,84 @@ + + + + + +
+

+ <%= t('.dear_customer') %> +

+

+ <%= t('.instructions') %> +

+

+ <%= t('.order_summary') %> +

+ + <% @order.line_items.each do |item| %> + + + + + + <% end %> + + + + + + <% if @order.line_item_adjustments.exists? %> + <% if @order.all_adjustments.promotion.eligible.exists? %> + <% @order.all_adjustments.promotion.eligible.group_by(&:label).each do |label, adjustments| %> + + + + + + <% end %> + <% end %> + <% end %> + <% @order.shipments.group_by { |s| s.selected_shipping_rate.try(:name) }.each do |name, shipments| %> + + + + + + <% end %> + <% if @order.all_adjustments.eligible.tax.exists? %> + <% @order.all_adjustments.eligible.tax.group_by(&:label).each do |label, adjustments| %> + + + + + + <% end %> + <% end %> + <% @order.adjustments.eligible.each do |adjustment| %> + <% next if (adjustment.source_type == 'Spree::TaxRate') and (adjustment.amount == 0) %> + + + + + + <% end %> + + + + + +
<%= item.variant.sku %> + <%= item.variant.product.name %> + <%= item.variant.options_text -%> + (<%=item.quantity%>) @ <%= item.single_money %> = <%= item.display_amount %>
+ <%= t('.subtotal') %> + + <%= @order.display_item_total %> +
<%= t('spree.promotion') %> <%= label %>:<%= Spree::Money.new(adjustments.sum(&:amount), currency: @order.currency) %>
<%= t('spree.shipping') %> <%= name %>:<%= Spree::Money.new(shipments.sum(&:total_before_tax), currency: @order.currency) %>
<%= t('spree.tax') %> <%= label %>:<%= Spree::Money.new(adjustments.sum(&:amount), currency: @order.currency) %>
<%= adjustment.label %>:<%= adjustment.display_amount %>
+ <%= t('.total') %> + + <%= @order.display_total %> +
+

+ <%= t('.thanks') %> +

+
diff --git a/legacy_promotions/app/views/spree/order_mailer/confirm_email.text.erb b/legacy_promotions/app/views/spree/order_mailer/confirm_email.text.erb new file mode 100644 index 00000000000..432247ccd8b --- /dev/null +++ b/legacy_promotions/app/views/spree/order_mailer/confirm_email.text.erb @@ -0,0 +1,38 @@ +<%= t('.dear_customer') %> + +<%= t('.instructions') %> + +============================================================ +<%= t('.order_summary') %> +============================================================ +<% @order.line_items.each do |item| %> + <%= item.variant.sku %> <%= raw(item.variant.product.name) %> <%= raw(item.variant.options_text) -%> (<%=item.quantity%>) @ <%= item.single_money %> = <%= item.display_amount %> +<% end %> +============================================================ +<%= t('.subtotal') %> <%= @order.display_item_total %> +<% if @order.line_item_adjustments.exists? %> + <% if @order.all_adjustments.promotion.eligible.exists? %> + <% @order.all_adjustments.promotion.eligible.group_by(&:label).each do |label, adjustments| %> +<%= t('spree.promotion') %>: <%= label %> <%= Spree::Money.new(adjustments.sum(&:amount), currency: @order.currency) %> + <% end %> + <% end %> +<% end %> + +<% @order.shipments.group_by { |s| s.selected_shipping_rate.try(:name) }.each do |name, shipments| %> +<%= t('spree.shipping') %>: <%= name %> <%= Spree::Money.new(shipments.sum(&:total_before_tax), currency: @order.currency) %> +<% end %> + +<% if @order.all_adjustments.eligible.tax.exists? %> + <% @order.all_adjustments.eligible.tax.group_by(&:label).each do |label, adjustments| %> +<%= t('spree.tax') %>: <%= label %> <%= Spree::Money.new(adjustments.sum(&:amount), currency: @order.currency) %> + <% end %> +<% end %> + +<% @order.adjustments.eligible.each do |adjustment| %> + <% next if (adjustment.source_type == 'Spree::TaxRate') and (adjustment.amount == 0) %> +<%= adjustment.label %> <%= adjustment.display_amount %> +<% end %> +============================================================ +<%= t('.total') %> <%= @order.display_total %> + +<%= t('.thanks') %> diff --git a/legacy_promotions/spec/mailers/spree/order_mailer_spec.rb b/legacy_promotions/spec/mailers/spree/order_mailer_spec.rb new file mode 100644 index 00000000000..8bf2ab1374a --- /dev/null +++ b/legacy_promotions/spec/mailers/spree/order_mailer_spec.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Spree::OrderMailer, type: :mailer do + let(:order) do + order = create(:order) + product = stub_model(Spree::Product, name: %{The "BEST" product}) + variant = stub_model(Spree::Variant, product: product) + price = stub_model(Spree::Price, variant: variant, amount: 5.00) + store = FactoryBot.build :store, mail_from_address: "store@example.com", bcc_email: "bcc@example.com" + line_item = stub_model(Spree::LineItem, variant: variant, order: order, quantity: 1, price: 4.99) + allow(variant).to receive_messages(default_price: price) + allow(order).to receive_messages(line_items: [line_item]) + allow(order).to receive(:store).and_return(store) + order + end + + context "only shows eligible adjustments in emails" do + before do + create( + :adjustment, + adjustable: order, + order: order, + eligible: true, + label: 'Eligible Adjustment' + ) + + create( + :adjustment, + adjustable: order, + order: order, + eligible: false, + label: 'Ineligible Adjustment' + ) + end + + let!(:confirmation_email) { Spree::OrderMailer.confirm_email(order) } + let!(:cancel_email) { Spree::OrderMailer.cancel_email(order) } + + specify do + expect(confirmation_email.parts.first.body).to include("Eligible Adjustment") + expect(confirmation_email.parts.first.body).not_to include("Ineligible Adjustment") + end + + specify do + expect(cancel_email.parts.first.body).to include("Eligible Adjustment") + expect(cancel_email.parts.first.body).not_to include("Ineligible Adjustment") + end + end +end From 4ded26569bf9fa77377b285c72d7dd49b4e4c5e6 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Mon, 24 Jun 2024 16:50:08 +0200 Subject: [PATCH 095/834] Remove usage of `if_not_exists` in Promo rollup migration This was another oversight, and its breaking builds of other gems. --- ...20160101010001_solidus_one_four_promotions.rb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/legacy_promotions/db/migrate/20160101010001_solidus_one_four_promotions.rb b/legacy_promotions/db/migrate/20160101010001_solidus_one_four_promotions.rb index f3970d5d351..645019b44b3 100644 --- a/legacy_promotions/db/migrate/20160101010001_solidus_one_four_promotions.rb +++ b/legacy_promotions/db/migrate/20160101010001_solidus_one_four_promotions.rb @@ -15,7 +15,7 @@ def up end unless table_exists?(:spree_product_promotion_rules) - create_table "spree_product_promotion_rules", if_not_exists: true, force: :cascade do |t| + create_table "spree_product_promotion_rules", force: :cascade do |t| t.integer "product_id" t.integer "promotion_rule_id" t.datetime "created_at", precision: 6 @@ -26,7 +26,7 @@ def up end unless table_exists?(:spree_promotion_actions) - create_table "spree_promotion_actions", if_not_exists: true, force: :cascade do |t| + create_table "spree_promotion_actions", force: :cascade do |t| t.integer "promotion_id" t.integer "position" t.string "type" @@ -41,7 +41,7 @@ def up end unless table_exists?(:spree_promotion_categories) - create_table "spree_promotion_categories", if_not_exists: true, force: :cascade do |t| + create_table "spree_promotion_categories", force: :cascade do |t| t.string "name" t.datetime "created_at", precision: 6 t.datetime "updated_at", precision: 6 @@ -50,7 +50,7 @@ def up end unless table_exists?(:spree_promotion_codes) - create_table "spree_promotion_codes", if_not_exists: true, force: :cascade do |t| + create_table "spree_promotion_codes", force: :cascade do |t| t.integer "promotion_id", null: false t.string "value", null: false t.datetime "created_at", precision: 6 @@ -61,7 +61,7 @@ def up end unless table_exists?(:spree_promotion_rule_taxons) - create_table "spree_promotion_rule_taxons", if_not_exists: true, force: :cascade do |t| + create_table "spree_promotion_rule_taxons", force: :cascade do |t| t.integer "taxon_id" t.integer "promotion_rule_id" t.datetime "created_at", precision: 6 @@ -72,7 +72,7 @@ def up end unless table_exists?(:spree_promotion_rules) - create_table "spree_promotion_rules", if_not_exists: true, force: :cascade do |t| + create_table "spree_promotion_rules", force: :cascade do |t| t.integer "promotion_id" t.integer "product_group_id" t.string "type" @@ -86,7 +86,7 @@ def up end unless table_exists?(:spree_promotion_rules_users) - create_table "spree_promotion_rules_users", if_not_exists: true, force: :cascade do |t| + create_table "spree_promotion_rules_users", force: :cascade do |t| t.integer "user_id" t.integer "promotion_rule_id" t.datetime "created_at", precision: 6 @@ -97,7 +97,7 @@ def up end unless table_exists?(:spree_promotions) - create_table "spree_promotions", if_not_exists: true, force: :cascade do |t| + create_table "spree_promotions", force: :cascade do |t| t.string "description" t.datetime "expires_at" t.datetime "starts_at" From 7176948ce526f3543b8fc6555f0a0ea843af34f8 Mon Sep 17 00:00:00 2001 From: Madeline Collier Date: Thu, 8 Aug 2024 12:33:42 +0200 Subject: [PATCH 096/834] Fix failing solidus_admin thumbnail spec Failure: /spec/components/solidus_admin/ui/thumbnail/component_spec.rb:6 Error: /home/circleci/solidus/admin/spec/components/previews/solidus_admin/ui/thumbnail/component_preview/overview.html.erb:53: numbered parameter is already used in /home/circleci/solidus/admin/spec/components/previews/solidus_admin/ui/thumbnail/component_preview/overview.html.erb:52: outer block here Failure/Error: <% image = Spree::Image.new.tap { _1.define_singleton_method(:attachment) { attachment } } %> By replaing the second instance of numbered params, we can avoid this error with no behavioural or functional changes to the code. Interestingly though, it was only failing on 2 out of our 8 spec runs, and it did so consistently. --- .../ui/thumbnail/component_preview/overview.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/admin/spec/components/previews/solidus_admin/ui/thumbnail/component_preview/overview.html.erb b/admin/spec/components/previews/solidus_admin/ui/thumbnail/component_preview/overview.html.erb index caa4fb043ee..9d9db7f2628 100644 --- a/admin/spec/components/previews/solidus_admin/ui/thumbnail/component_preview/overview.html.erb +++ b/admin/spec/components/previews/solidus_admin/ui/thumbnail/component_preview/overview.html.erb @@ -50,7 +50,7 @@
<% product = Spree::Product.new(name: "A good product") %> <% attachment = Object.new.tap { def _1.url(*) "https://placekitten.com/280/200"; end } %> - <% image = Spree::Image.new.tap { _1.define_singleton_method(:attachment) { attachment } } %> + <% image = Spree::Image.new.tap { |img| img.define_singleton_method(:attachment) { attachment } } %> <% [ Spree::UnitCancel.new, Spree::TaxRate.new, From 1664d10fa64b48d4be210e98d4a744bcd41a1f17 Mon Sep 17 00:00:00 2001 From: Madeline Collier Date: Wed, 7 Aug 2024 19:41:36 +0200 Subject: [PATCH 097/834] Lock solidus_core.gemspec to ransack '< 4.2' Solidus core's gemspec already required that ransack be '~> 4.0', but the latest version of ransack, v4.2.0, released July 10 2024, introduces a bug. The previous implementation was taking for granted that every predicate would respond to #value, which doesn't seem to be the case when the predicate is an instance of a Arel::SelectManager. This has already been flagged by @spaghetticode in his PR against ransack: https://github.com/activerecord-hackery/ransack/pull/1468 Since there has been little movement on this PR since January, we should lock to a version that works for us since currently many of our product specs are failing. (eg. spec/models/spree/product_spec.rb:659) We can remove this lock once the PR is merged and once the above test (and the others that are failing) are able to pass in ransack v4.2.0 or subsequent versions. --- core/solidus_core.gemspec | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/solidus_core.gemspec b/core/solidus_core.gemspec index b6aff3cfc6c..43ec266f676 100644 --- a/core/solidus_core.gemspec +++ b/core/solidus_core.gemspec @@ -43,7 +43,9 @@ Gem::Specification.new do |s| s.add_dependency 'monetize', '~> 1.8' s.add_dependency 'kt-paperclip', ['>= 6.3', '< 8'] s.add_dependency 'psych', ['>= 4.0.1', '< 6.0'] - s.add_dependency 'ransack', '~> 4.0' + # @note ransack 4.2 contains a bug which has not yet been addressed. + # @see https://github.com/activerecord-hackery/ransack/pull/1468 + s.add_dependency 'ransack', ['~> 4.0', '< 4.2'] s.add_dependency 'sprockets-rails', '!= 3.5.0' s.add_dependency 'state_machines-activerecord', '~> 0.6' s.add_dependency 'omnes', '~> 0.2.2' From b5b04938741e0c64e6296cdf1cffdceb0e2d6f1a Mon Sep 17 00:00:00 2001 From: Madeline Collier Date: Wed, 7 Aug 2024 16:03:35 +0200 Subject: [PATCH 098/834] Create adjustment reasons via new admin UI modal --- .../adjustment_reasons/index/component.rb | 14 +++++ .../adjustment_reasons/new/component.html.erb | 27 +++++++++ .../adjustment_reasons/new/component.rb | 12 ++++ .../adjustment_reasons/new/component.yml | 8 +++ .../adjustment_reasons_controller.rb | 55 ++++++++++++++++--- .../config/locales/adjustment_reasons.en.yml | 2 + admin/config/routes.rb | 2 +- .../spec/features/adjustment_reasons_spec.rb | 40 ++++++++++++++ 8 files changed, 152 insertions(+), 8 deletions(-) create mode 100644 admin/app/components/solidus_admin/adjustment_reasons/new/component.html.erb create mode 100644 admin/app/components/solidus_admin/adjustment_reasons/new/component.rb create mode 100644 admin/app/components/solidus_admin/adjustment_reasons/new/component.yml diff --git a/admin/app/components/solidus_admin/adjustment_reasons/index/component.rb b/admin/app/components/solidus_admin/adjustment_reasons/index/component.rb index e6d297e9904..8017a880cf1 100644 --- a/admin/app/components/solidus_admin/adjustment_reasons/index/component.rb +++ b/admin/app/components/solidus_admin/adjustment_reasons/index/component.rb @@ -13,6 +13,20 @@ def search_key :name_or_code_cont end + def page_actions + render component("ui/button").new( + tag: :a, + text: t('.add'), + href: solidus_admin.new_adjustment_reason_path, data: { turbo_frame: :new_adjustment_reason_modal }, + icon: "add-line", + class: "align-self-end w-full", + ) + end + + def turbo_frames + %w[new_adjustment_reason_modal] + end + def row_url(adjustment_reason) spree.edit_admin_adjustment_reason_path(adjustment_reason) end diff --git a/admin/app/components/solidus_admin/adjustment_reasons/new/component.html.erb b/admin/app/components/solidus_admin/adjustment_reasons/new/component.html.erb new file mode 100644 index 00000000000..1a3e58fda9b --- /dev/null +++ b/admin/app/components/solidus_admin/adjustment_reasons/new/component.html.erb @@ -0,0 +1,27 @@ +<%= turbo_frame_tag :new_adjustment_reason_modal do %> + <%= render component("ui/modal").new(title: t(".title")) do |modal| %> + <%= form_for @adjustment_reason, url: solidus_admin.adjustment_reasons_path, html: { id: form_id } do |f| %> +
+ <%= render component("ui/forms/field").text_field(f, :name, class: "required") %> + <%= render component("ui/forms/field").text_field(f, :code, class: "required") %> + +
+ <% modal.with_actions do %> +
+ <%= render component("ui/button").new(scheme: :secondary, text: t('.cancel')) %> +
+ <%= render component("ui/button").new(form: form_id, type: :submit, text: t('.submit')) %> + <% end %> + <% end %> + <% end %> +<% end %> + +<%= render component("adjustment_reasons/index").new(page: @page) %> diff --git a/admin/app/components/solidus_admin/adjustment_reasons/new/component.rb b/admin/app/components/solidus_admin/adjustment_reasons/new/component.rb new file mode 100644 index 00000000000..dddf9ddb8a0 --- /dev/null +++ b/admin/app/components/solidus_admin/adjustment_reasons/new/component.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class SolidusAdmin::AdjustmentReasons::New::Component < SolidusAdmin::BaseComponent + def initialize(page:, adjustment_reason:) + @page = page + @adjustment_reason = adjustment_reason + end + + def form_id + dom_id(@adjustment_reason, "#{stimulus_id}_new_adjustment_reason_form") + end +end diff --git a/admin/app/components/solidus_admin/adjustment_reasons/new/component.yml b/admin/app/components/solidus_admin/adjustment_reasons/new/component.yml new file mode 100644 index 00000000000..6b4b6f08c44 --- /dev/null +++ b/admin/app/components/solidus_admin/adjustment_reasons/new/component.yml @@ -0,0 +1,8 @@ +# Add your component translations here. +# Use the translation in the example in your template with `t(".hello")`. +en: + title: "New Adjustment Reason" + cancel: "Cancel" + submit: "Add Adjustment Reason" + hints: + active: "When checked, this adjustment reason will be available for selection when adding adjustments to orders." diff --git a/admin/app/controllers/solidus_admin/adjustment_reasons_controller.rb b/admin/app/controllers/solidus_admin/adjustment_reasons_controller.rb index 6957634863d..ff548569c12 100644 --- a/admin/app/controllers/solidus_admin/adjustment_reasons_controller.rb +++ b/admin/app/controllers/solidus_admin/adjustment_reasons_controller.rb @@ -5,18 +5,50 @@ class AdjustmentReasonsController < SolidusAdmin::BaseController include SolidusAdmin::ControllerHelpers::Search def index - adjustment_reasons = apply_search_to( - Spree::AdjustmentReason.order(id: :desc), - param: :q, - ) - - set_page_and_extract_portion_from(adjustment_reasons) + set_index_page respond_to do |format| format.html { render component('adjustment_reasons/index').new(page: @page) } end end + def new + @adjustment_reason = Spree::AdjustmentReason.new + + set_index_page + + respond_to do |format| + format.html { render component('adjustment_reasons/new').new(page: @page, adjustment_reason: @adjustment_reason) } + end + end + + def create + @adjustment_reason = Spree::AdjustmentReason.new(adjustment_reason_params) + + if @adjustment_reason.save + respond_to do |format| + flash[:notice] = t('.success') + + format.html do + redirect_to solidus_admin.adjustment_reasons_path, status: :see_other + end + + format.turbo_stream do + render turbo_stream: '' + end + end + else + set_index_page + + respond_to do |format| + format.html do + page_component = component('adjustment_reasons/new').new(page: @page, adjustment_reason: @adjustment_reason) + render page_component, status: :unprocessable_entity + end + end + end + end + def destroy @adjustment_reason = Spree::AdjustmentReason.find_by!(id: params[:id]) @@ -34,7 +66,16 @@ def load_adjustment_reason end def adjustment_reason_params - params.require(:adjustment_reason).permit(:adjustment_reason_id, permitted_adjustment_reason_attributes) + params.require(:adjustment_reason).permit(:name, :code, :active) + end + + def set_index_page + adjustment_reasons = apply_search_to( + Spree::AdjustmentReason.order(id: :desc), + param: :q, + ) + + set_page_and_extract_portion_from(adjustment_reasons) end end end diff --git a/admin/config/locales/adjustment_reasons.en.yml b/admin/config/locales/adjustment_reasons.en.yml index 90b05a067ce..e812cfa42e3 100644 --- a/admin/config/locales/adjustment_reasons.en.yml +++ b/admin/config/locales/adjustment_reasons.en.yml @@ -4,3 +4,5 @@ en: title: "Adjustment Reasons" destroy: success: "Adjustment Reasons were successfully removed." + create: + success: "Adjustment reason was successfully created." diff --git a/admin/config/routes.rb b/admin/config/routes.rb index e4cc29b6711..737fc602c2d 100644 --- a/admin/config/routes.rb +++ b/admin/config/routes.rb @@ -63,6 +63,6 @@ admin_resources :refund_reasons, only: [:index, :new, :create, :destroy] admin_resources :reimbursement_types, only: [:index] admin_resources :return_reasons, only: [:index, :destroy] - admin_resources :adjustment_reasons, only: [:index, :destroy] + admin_resources :adjustment_reasons, only: [:index, :new, :create, :destroy] admin_resources :store_credit_reasons, only: [:index, :destroy] end diff --git a/admin/spec/features/adjustment_reasons_spec.rb b/admin/spec/features/adjustment_reasons_spec.rb index 4f0dc85a3fb..9b8452d9693 100644 --- a/admin/spec/features/adjustment_reasons_spec.rb +++ b/admin/spec/features/adjustment_reasons_spec.rb @@ -19,4 +19,44 @@ expect(Spree::AdjustmentReason.count).to eq(0) expect(page).to be_axe_clean end + + context "when creating a new adjustment reason" do + let(:query) { "?page=1&q%5Bname_or_code_cont%5D=new" } + + before do + visit "/admin/adjustment_reasons#{query}" + click_on "Add new" + expect(page).to have_content("New Adjustment Reason") + expect(page).to be_axe_clean + end + + it "opens a modal" do + expect(page).to have_selector("dialog") + within("dialog") { click_on "Cancel" } + expect(page).not_to have_selector("dialog") + expect(page.current_url).to include(query) + end + + context "with valid data" do + it "successfully creates a new adjustment reason, keeping page and q params" do + fill_in "Name", with: "New Reason" + fill_in "Code", with: "1234" + + click_on "Add Adjustment Reason" + + expect(page).to have_content("Adjustment reason was successfully created.") + expect(Spree::AdjustmentReason.find_by(name: "New Reason")).to be_present + expect(page.current_url).to include(query) + end + end + + context "with invalid data" do + it "fails to create a new adjustment reason, keeping page and q params" do + click_on "Add Adjustment Reason" + + expect(page).to have_content("can't be blank").twice + expect(page.current_url).to include(query) + end + end + end end From fe185ac30c7c32d4eae11479e55794a5ef88e214 Mon Sep 17 00:00:00 2001 From: Madeline Collier Date: Thu, 8 Aug 2024 17:20:02 +0200 Subject: [PATCH 099/834] Fix bug with adjustment reasons checkbox The new adjustment reasons were always being created with `active: true` regardless of the checkbox state. This commit fixes that issue so that the checkbox state is respected and allows users to control the active state. --- .../solidus_admin/adjustment_reasons/new/component.html.erb | 1 + admin/spec/features/adjustment_reasons_spec.rb | 2 ++ 2 files changed, 3 insertions(+) diff --git a/admin/app/components/solidus_admin/adjustment_reasons/new/component.html.erb b/admin/app/components/solidus_admin/adjustment_reasons/new/component.html.erb index 1a3e58fda9b..f62d2576f18 100644 --- a/admin/app/components/solidus_admin/adjustment_reasons/new/component.html.erb +++ b/admin/app/components/solidus_admin/adjustment_reasons/new/component.html.erb @@ -5,6 +5,7 @@ <%= render component("ui/forms/field").text_field(f, :name, class: "required") %> <%= render component("ui/forms/field").text_field(f, :code, class: "required") %>
From 4f9903ba7c59aba0b24b2f067cb3f98c67b49f5c Mon Sep 17 00:00:00 2001 From: Nick Van Doorn Date: Thu, 8 Aug 2024 09:26:12 -0700 Subject: [PATCH 101/834] Add before action to handle option type params As reported in #5751, the new admin UI has a bug with the option type selectors: only one option type can be added. We narrowed this down to an issue handling option type params in the new admin products controller. We noticed legacy controller contains a before action to handle the incoming option type params, but the new one did not. Re-adding the before action solves the bug. This is required because the form input for option types contains comma separated values within a string: `` Co-authored-by: An Stewart Co-authored-by: Kendra Chateau --- .../controllers/solidus_admin/products_controller.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/admin/app/controllers/solidus_admin/products_controller.rb b/admin/app/controllers/solidus_admin/products_controller.rb index a085ef0d41c..094c2c6ab08 100644 --- a/admin/app/controllers/solidus_admin/products_controller.rb +++ b/admin/app/controllers/solidus_admin/products_controller.rb @@ -11,6 +11,8 @@ class ProductsController < SolidusAdmin::BaseController search_scope(:in_stock) { _1.where(id: Spree::Variant.in_stock.distinct.select(:product_id)) } search_scope(:out_of_stock) { _1.where.not(id: Spree::Variant.in_stock.distinct.select(:product_id)) } + before_action :split_params, only: [:update] + def index products = apply_search_to( Spree::Product.includes(:master, :variants), @@ -98,5 +100,14 @@ def activate flash[:notice] = t('.success') redirect_to products_path, status: :see_other end + + def split_params + if params[:product][:taxon_ids].present? + params[:product][:taxon_ids] = params[:product][:taxon_ids].split(',') + end + if params[:product][:option_type_ids].present? + params[:product][:option_type_ids] = params[:product][:option_type_ids].split(',') + end + end end end From 8091f38767b2131226797b9ff6743f96196c5682 Mon Sep 17 00:00:00 2001 From: Madeline Collier Date: Thu, 8 Aug 2024 17:16:26 +0200 Subject: [PATCH 102/834] Edit/Update adjustment reasons via new admin UI --- .../edit/component.html.erb | 27 +++++++++++++ .../adjustment_reasons/edit/component.rb | 12 ++++++ .../adjustment_reasons/edit/component.yml | 8 ++++ .../adjustment_reasons/index/component.rb | 7 +++- .../adjustment_reasons_controller.rb | 39 +++++++++++++++++++ .../config/locales/adjustment_reasons.en.yml | 2 + admin/config/routes.rb | 2 +- .../spec/features/adjustment_reasons_spec.rb | 32 +++++++++++++++ 8 files changed, 126 insertions(+), 3 deletions(-) create mode 100644 admin/app/components/solidus_admin/adjustment_reasons/edit/component.html.erb create mode 100644 admin/app/components/solidus_admin/adjustment_reasons/edit/component.rb create mode 100644 admin/app/components/solidus_admin/adjustment_reasons/edit/component.yml diff --git a/admin/app/components/solidus_admin/adjustment_reasons/edit/component.html.erb b/admin/app/components/solidus_admin/adjustment_reasons/edit/component.html.erb new file mode 100644 index 00000000000..2ffda1726c3 --- /dev/null +++ b/admin/app/components/solidus_admin/adjustment_reasons/edit/component.html.erb @@ -0,0 +1,27 @@ +<%= turbo_frame_tag :edit_adjustment_reason_modal do %> + <%= render component("ui/modal").new(title: t(".title")) do |modal| %> + <%= form_for @adjustment_reason, url: solidus_admin.adjustment_reason_path(@adjustment_reason), html: { id: form_id } do |f| %> +
+ <%= render component("ui/forms/field").text_field(f, :name, class: "required") %> + <%= render component("ui/forms/field").text_field(f, :code, class: "required") %> + +
+ <% modal.with_actions do %> +
+ <%= render component("ui/button").new(scheme: :secondary, text: t('.cancel')) %> +
+ <%= render component("ui/button").new(form: form_id, type: :submit, text: t('.submit')) %> + <% end %> + <% end %> + <% end %> +<% end %> +<%= render component("adjustment_reasons/index").new(page: @page) %> diff --git a/admin/app/components/solidus_admin/adjustment_reasons/edit/component.rb b/admin/app/components/solidus_admin/adjustment_reasons/edit/component.rb new file mode 100644 index 00000000000..e2005a5fb23 --- /dev/null +++ b/admin/app/components/solidus_admin/adjustment_reasons/edit/component.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class SolidusAdmin::AdjustmentReasons::Edit::Component < SolidusAdmin::BaseComponent + def initialize(page:, adjustment_reason:) + @page = page + @adjustment_reason = adjustment_reason + end + + def form_id + dom_id(@adjustment_reason, "#{stimulus_id}_edit_adjustment_reason_form") + end +end diff --git a/admin/app/components/solidus_admin/adjustment_reasons/edit/component.yml b/admin/app/components/solidus_admin/adjustment_reasons/edit/component.yml new file mode 100644 index 00000000000..8d4649384dd --- /dev/null +++ b/admin/app/components/solidus_admin/adjustment_reasons/edit/component.yml @@ -0,0 +1,8 @@ +# Add your component translations here. +# Use the translation in the example in your template with `t(".hello")`. +en: + title: "Edit Adjustment Reason" + cancel: "Cancel" + submit: "Update Adjustment Reason" + hints: + active: "When checked, this adjustment reason will be available for selection when adding adjustments to orders." diff --git a/admin/app/components/solidus_admin/adjustment_reasons/index/component.rb b/admin/app/components/solidus_admin/adjustment_reasons/index/component.rb index 8017a880cf1..647d97ecc82 100644 --- a/admin/app/components/solidus_admin/adjustment_reasons/index/component.rb +++ b/admin/app/components/solidus_admin/adjustment_reasons/index/component.rb @@ -24,11 +24,14 @@ def page_actions end def turbo_frames - %w[new_adjustment_reason_modal] + %w[ + new_adjustment_reason_modal + edit_adjustment_reason_modal + ] end def row_url(adjustment_reason) - spree.edit_admin_adjustment_reason_path(adjustment_reason) + spree.edit_admin_adjustment_reason_path(adjustment_reason, _turbo_frame: :edit_adjustment_reason_modal) end def batch_actions diff --git a/admin/app/controllers/solidus_admin/adjustment_reasons_controller.rb b/admin/app/controllers/solidus_admin/adjustment_reasons_controller.rb index ff548569c12..1211530bb9d 100644 --- a/admin/app/controllers/solidus_admin/adjustment_reasons_controller.rb +++ b/admin/app/controllers/solidus_admin/adjustment_reasons_controller.rb @@ -4,6 +4,8 @@ module SolidusAdmin class AdjustmentReasonsController < SolidusAdmin::BaseController include SolidusAdmin::ControllerHelpers::Search + before_action :find_adjustment_reason, only: %i[edit update] + def index set_index_page @@ -49,6 +51,39 @@ def create end end + def edit + set_index_page + + respond_to do |format| + format.html { render component('adjustment_reasons/edit').new(page: @page, adjustment_reason: @adjustment_reason) } + end + end + + def update + if @adjustment_reason.update(adjustment_reason_params) + respond_to do |format| + flash[:notice] = t('.success') + + format.html do + redirect_to solidus_admin.adjustment_reasons_path, status: :see_other + end + + format.turbo_stream do + render turbo_stream: '' + end + end + else + set_index_page + + respond_to do |format| + format.html do + page_component = component('adjustment_reasons/edit').new(page: @page, adjustment_reason: @adjustment_reason) + render page_component, status: :unprocessable_entity + end + end + end + end + def destroy @adjustment_reason = Spree::AdjustmentReason.find_by!(id: params[:id]) @@ -65,6 +100,10 @@ def load_adjustment_reason authorize! action_name, @adjustment_reason end + def find_adjustment_reason + @adjustment_reason = Spree::AdjustmentReason.find(params[:id]) + end + def adjustment_reason_params params.require(:adjustment_reason).permit(:name, :code, :active) end diff --git a/admin/config/locales/adjustment_reasons.en.yml b/admin/config/locales/adjustment_reasons.en.yml index e812cfa42e3..33da17b7cbf 100644 --- a/admin/config/locales/adjustment_reasons.en.yml +++ b/admin/config/locales/adjustment_reasons.en.yml @@ -6,3 +6,5 @@ en: success: "Adjustment Reasons were successfully removed." create: success: "Adjustment reason was successfully created." + update: + success: "Adjustment reason was successfully updated." diff --git a/admin/config/routes.rb b/admin/config/routes.rb index 737fc602c2d..97e70acb51b 100644 --- a/admin/config/routes.rb +++ b/admin/config/routes.rb @@ -63,6 +63,6 @@ admin_resources :refund_reasons, only: [:index, :new, :create, :destroy] admin_resources :reimbursement_types, only: [:index] admin_resources :return_reasons, only: [:index, :destroy] - admin_resources :adjustment_reasons, only: [:index, :new, :create, :destroy] + admin_resources :adjustment_reasons, except: [:show] admin_resources :store_credit_reasons, only: [:index, :destroy] end diff --git a/admin/spec/features/adjustment_reasons_spec.rb b/admin/spec/features/adjustment_reasons_spec.rb index dea217dcf20..9a205dfdd6f 100644 --- a/admin/spec/features/adjustment_reasons_spec.rb +++ b/admin/spec/features/adjustment_reasons_spec.rb @@ -61,4 +61,36 @@ end end end + + context "when editing an existing adjustment reason" do + let(:query) { "?page=1&q%5Bname_or_code_cont%5D=reason" } + + before do + Spree::AdjustmentReason.create(name: "Good Reason", code: 5999) + visit "/admin/adjustment_reasons#{query}" + find_row("Good Reason").click + expect(page).to have_content("Edit Adjustment Reason") + expect(page).to be_axe_clean + end + + it "opens a modal" do + expect(page).to have_selector("dialog") + within("dialog") { click_on "Cancel" } + expect(page).not_to have_selector("dialog") + expect(page.current_url).to include(query) + end + + it "successfully updates the existing adjustment reason" do + fill_in "Name", with: "Better Reason" + page.uncheck "adjustment_reason[active]" + + click_on "Update Adjustment Reason" + expect(page).to have_content("Adjustment reason was successfully updated.") + expect(page).to have_content("Better Reason") + expect(page).not_to have_content("Good Reason") + expect(Spree::AdjustmentReason.find_by(name: "Better Reason")).to be_present + expect(Spree::AdjustmentReason.find_by(name: "Better Reason").active).to be_falsey + expect(page.current_url).to include(query) + end + end end From 364aec2a79ec1a4fa0fcb42cc844e9f30f9f72d2 Mon Sep 17 00:00:00 2001 From: Madeline Collier Date: Thu, 8 Aug 2024 17:17:43 +0200 Subject: [PATCH 103/834] Unify flash messaging casing in new admin UI This should consistently use either uppercase or lowercase. Since the others use lowercase, I went with that. --- admin/config/locales/adjustment_reasons.en.yml | 2 +- admin/spec/features/adjustment_reasons_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/admin/config/locales/adjustment_reasons.en.yml b/admin/config/locales/adjustment_reasons.en.yml index 33da17b7cbf..15d5d69a9af 100644 --- a/admin/config/locales/adjustment_reasons.en.yml +++ b/admin/config/locales/adjustment_reasons.en.yml @@ -3,7 +3,7 @@ en: adjustment_reasons: title: "Adjustment Reasons" destroy: - success: "Adjustment Reasons were successfully removed." + success: "Adjustment reasons were successfully removed." create: success: "Adjustment reason was successfully created." update: diff --git a/admin/spec/features/adjustment_reasons_spec.rb b/admin/spec/features/adjustment_reasons_spec.rb index 9a205dfdd6f..8af9a711ea0 100644 --- a/admin/spec/features/adjustment_reasons_spec.rb +++ b/admin/spec/features/adjustment_reasons_spec.rb @@ -14,7 +14,7 @@ select_row("Default-adjustment-reason") click_on "Delete" - expect(page).to have_content("Adjustment Reasons were successfully removed.") + expect(page).to have_content("Adjustment reasons were successfully removed.") expect(page).not_to have_content("Default-adjustment-reason") expect(Spree::AdjustmentReason.count).to eq(0) expect(page).to be_axe_clean From 0d6017d66a319d989cdfa92115b1578623c8b3f9 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 8 Aug 2024 12:10:59 +0200 Subject: [PATCH 104/834] Use new extension point in order updater spec This spec still used one of the old extension points for the promotion system, and #5813 exposed that. --- core/spec/models/spree/order_updater_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/spec/models/spree/order_updater_spec.rb b/core/spec/models/spree/order_updater_spec.rb index 0bf623256d4..c6b80031935 100644 --- a/core/spec/models/spree/order_updater_spec.rb +++ b/core/spec/models/spree/order_updater_spec.rb @@ -62,7 +62,7 @@ module Spree describe 'promotion recalculation' do it "calls the Promotion Adjustments Recalculator" do adjuster = double(:call) - expect(Spree::Config.promotion_adjuster_class).to receive(:new).and_return(adjuster) + expect(Spree::Config.promotions.order_adjuster_class).to receive(:new).and_return(adjuster) expect(adjuster).to receive(:call) order.recalculate end From 9398e80c5a019649e7fee977c308cde6b029205c Mon Sep 17 00:00:00 2001 From: Madeline Collier Date: Thu, 8 Aug 2024 19:16:45 +0200 Subject: [PATCH 105/834] Edit/Update shipping categories via new admin UI --- .../edit/component.html.erb | 16 ++++++++ .../shipping_categories/edit/component.rb | 12 ++++++ .../shipping_categories/edit/component.yml | 6 +++ .../shipping_categories/index/component.rb | 7 +++- .../shipping_categories_controller.rb | 39 +++++++++++++++++++ .../config/locales/shipping_categories.en.yml | 2 + admin/config/routes.rb | 2 +- .../spec/features/shipping_categories_spec.rb | 30 ++++++++++++++ 8 files changed, 111 insertions(+), 3 deletions(-) create mode 100644 admin/app/components/solidus_admin/shipping_categories/edit/component.html.erb create mode 100644 admin/app/components/solidus_admin/shipping_categories/edit/component.rb create mode 100644 admin/app/components/solidus_admin/shipping_categories/edit/component.yml diff --git a/admin/app/components/solidus_admin/shipping_categories/edit/component.html.erb b/admin/app/components/solidus_admin/shipping_categories/edit/component.html.erb new file mode 100644 index 00000000000..88185925b39 --- /dev/null +++ b/admin/app/components/solidus_admin/shipping_categories/edit/component.html.erb @@ -0,0 +1,16 @@ +<%= turbo_frame_tag :edit_shipping_category_modal do %> + <%= render component("ui/modal").new(title: t(".title")) do |modal| %> + <%= form_for @shipping_category, url: solidus_admin.shipping_category_path(@shipping_category), html: { id: form_id } do |f| %> +
+ <%= render component("ui/forms/field").text_field(f, :name) %> +
+ <% modal.with_actions do %> +
+ <%= render component("ui/button").new(scheme: :secondary, text: t('.cancel')) %> +
+ <%= render component("ui/button").new(form: form_id, type: :submit, text: t('.submit')) %> + <% end %> + <% end %> + <% end %> +<% end %> +<%= render component("shipping_categories/index").new(page: @page) %> diff --git a/admin/app/components/solidus_admin/shipping_categories/edit/component.rb b/admin/app/components/solidus_admin/shipping_categories/edit/component.rb new file mode 100644 index 00000000000..6f2496e4af1 --- /dev/null +++ b/admin/app/components/solidus_admin/shipping_categories/edit/component.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class SolidusAdmin::ShippingCategories::Edit::Component < SolidusAdmin::ShippingCategories::Index::Component + def initialize(page:, shipping_category:) + @page = page + @shipping_category = shipping_category + end + + def form_id + dom_id(@shipping_category, "#{stimulus_id}_edit_shipping_category_form") + end +end diff --git a/admin/app/components/solidus_admin/shipping_categories/edit/component.yml b/admin/app/components/solidus_admin/shipping_categories/edit/component.yml new file mode 100644 index 00000000000..fe349faaeb6 --- /dev/null +++ b/admin/app/components/solidus_admin/shipping_categories/edit/component.yml @@ -0,0 +1,6 @@ +# Add your component translations here. +# Use the translation in the example in your template with `t(".hello")`. +en: + title: "Edit Shipping Category" + cancel: "Cancel" + submit: "Update Shipping Category" diff --git a/admin/app/components/solidus_admin/shipping_categories/index/component.rb b/admin/app/components/solidus_admin/shipping_categories/index/component.rb index d15fee49d9c..4253dcec01c 100644 --- a/admin/app/components/solidus_admin/shipping_categories/index/component.rb +++ b/admin/app/components/solidus_admin/shipping_categories/index/component.rb @@ -26,11 +26,14 @@ def page_actions end def turbo_frames - %w[new_shipping_category_modal] + %w[ + new_shipping_category_modal + edit_shipping_category_modal + ] end def row_url(shipping_category) - spree.edit_admin_shipping_category_path(shipping_category) + spree.edit_admin_shipping_category_path(shipping_category, _turbo_frame: :edit_shipping_category_modal) end def search_key diff --git a/admin/app/controllers/solidus_admin/shipping_categories_controller.rb b/admin/app/controllers/solidus_admin/shipping_categories_controller.rb index a27bf31313e..19fbe6687eb 100644 --- a/admin/app/controllers/solidus_admin/shipping_categories_controller.rb +++ b/admin/app/controllers/solidus_admin/shipping_categories_controller.rb @@ -4,6 +4,8 @@ module SolidusAdmin class ShippingCategoriesController < SolidusAdmin::BaseController include SolidusAdmin::ControllerHelpers::Search + before_action :find_shipping_category, only: %i[edit update] + def new @shipping_category = Spree::ShippingCategory.new @@ -51,6 +53,39 @@ def index end end + def edit + set_index_page + + respond_to do |format| + format.html { render component('shipping_categories/edit').new(page: @page, shipping_category: @shipping_category) } + end + end + + def update + if @shipping_category.update(shipping_category_params) + respond_to do |format| + flash[:notice] = t('.success') + + format.html do + redirect_to solidus_admin.shipping_categories_path, status: :see_other + end + + format.turbo_stream do + render turbo_stream: '' + end + end + else + set_index_page + + respond_to do |format| + format.html do + page_component = component('shipping_categories/edit').new(page: @page, shipping_category: @shipping_category) + render page_component, status: :unprocessable_entity + end + end + end + end + def destroy @shipping_category = Spree::ShippingCategory.find_by!(id: params[:id]) @@ -67,6 +102,10 @@ def load_shipping_category authorize! action_name, @shipping_category end + def find_shipping_category + @shipping_category = Spree::ShippingCategory.find(params[:id]) + end + def shipping_category_params params.require(:shipping_category).permit(:name) end diff --git a/admin/config/locales/shipping_categories.en.yml b/admin/config/locales/shipping_categories.en.yml index 7bcc3dd54e1..d266a4ad4ac 100644 --- a/admin/config/locales/shipping_categories.en.yml +++ b/admin/config/locales/shipping_categories.en.yml @@ -6,3 +6,5 @@ en: success: "Shipping categories were successfully removed." create: success: "Shipping category was successfully created." + update: + success: "Shipping category was successfully updated." diff --git a/admin/config/routes.rb b/admin/config/routes.rb index 737fc602c2d..be1105893e1 100644 --- a/admin/config/routes.rb +++ b/admin/config/routes.rb @@ -56,7 +56,7 @@ admin_resources :payment_methods, only: [:index, :destroy], sortable: true admin_resources :stock_items, only: [:index, :edit, :update] admin_resources :shipping_methods, only: [:index, :destroy] - admin_resources :shipping_categories, only: [:new, :index, :create, :destroy] + admin_resources :shipping_categories, except: [:show] admin_resources :stock_locations, only: [:index, :destroy] admin_resources :stores, only: [:index, :destroy] admin_resources :zones, only: [:index, :destroy] diff --git a/admin/spec/features/shipping_categories_spec.rb b/admin/spec/features/shipping_categories_spec.rb index 56b374c55bb..674ce76a00d 100644 --- a/admin/spec/features/shipping_categories_spec.rb +++ b/admin/spec/features/shipping_categories_spec.rb @@ -58,4 +58,34 @@ end end end + + context "when editing an existing shipping category" do + let(:query) { "?page=1&q%5Bname_or_description_cont%5D=mail" } + + before do + Spree::ShippingCategory.create(name: "Letter Mail") + visit "/admin/shipping_categories#{query}" + find_row("Letter Mail").click + expect(page).to have_content("Edit Shipping Category") + expect(page).to be_axe_clean + end + + it "opens a modal" do + expect(page).to have_selector("dialog") + within("dialog") { click_on "Cancel" } + expect(page).not_to have_selector("dialog") + expect(page.current_url).to include(query) + end + + it "successfully updates the existing shipping category" do + fill_in "Name", with: "Air Mail" + + click_on "Update Shipping Category" + expect(page).to have_content("Shipping category was successfully updated.") + expect(page).to have_content("Air Mail") + expect(page).not_to have_content("Letter Mail") + expect(Spree::ShippingCategory.find_by(name: "Air Mail")).to be_present + expect(page.current_url).to include(query) + end + end end From 72cf9781c6749eb16699e354184e365dcd0e8ccd Mon Sep 17 00:00:00 2001 From: Madeline Collier Date: Mon, 12 Aug 2024 15:10:54 +0200 Subject: [PATCH 106/834] Edit/Update refund reasons via new admin UI --- .../refund_reasons/edit/component.html.erb | 27 +++++++++++++ .../refund_reasons/edit/component.rb | 12 ++++++ .../refund_reasons/edit/component.yml | 8 ++++ .../refund_reasons/index/component.rb | 7 +++- .../refund_reasons_controller.rb | 39 +++++++++++++++++++ admin/config/locales/refund_reasons.en.yml | 2 + admin/config/routes.rb | 2 +- admin/spec/features/refund_reasons_spec.rb | 30 ++++++++++++++ 8 files changed, 124 insertions(+), 3 deletions(-) create mode 100644 admin/app/components/solidus_admin/refund_reasons/edit/component.html.erb create mode 100644 admin/app/components/solidus_admin/refund_reasons/edit/component.rb create mode 100644 admin/app/components/solidus_admin/refund_reasons/edit/component.yml diff --git a/admin/app/components/solidus_admin/refund_reasons/edit/component.html.erb b/admin/app/components/solidus_admin/refund_reasons/edit/component.html.erb new file mode 100644 index 00000000000..4fab63b28f3 --- /dev/null +++ b/admin/app/components/solidus_admin/refund_reasons/edit/component.html.erb @@ -0,0 +1,27 @@ +<%= turbo_frame_tag :edit_refund_reason_modal do %> + <%= render component("ui/modal").new(title: t(".title")) do |modal| %> + <%= form_for @refund_reason, url: solidus_admin.refund_reason_path(@refund_reason), html: { id: form_id } do |f| %> +
+ <%= render component("ui/forms/field").text_field(f, :name, class: "required") %> + <%= render component("ui/forms/field").text_field(f, :code, class: "required") %> + +
+ <% modal.with_actions do %> +
+ <%= render component("ui/button").new(scheme: :secondary, text: t('.cancel')) %> +
+ <%= render component("ui/button").new(form: form_id, type: :submit, text: t('.submit')) %> + <% end %> + <% end %> + <% end %> +<% end %> +<%= render component("refund_reasons/index").new(page: @page) %> diff --git a/admin/app/components/solidus_admin/refund_reasons/edit/component.rb b/admin/app/components/solidus_admin/refund_reasons/edit/component.rb new file mode 100644 index 00000000000..285847dc84d --- /dev/null +++ b/admin/app/components/solidus_admin/refund_reasons/edit/component.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class SolidusAdmin::RefundReasons::Edit::Component < SolidusAdmin::BaseComponent + def initialize(page:, refund_reason:) + @page = page + @refund_reason = refund_reason + end + + def form_id + dom_id(@refund_reason, "#{stimulus_id}_edit_refund_reason_form") + end +end diff --git a/admin/app/components/solidus_admin/refund_reasons/edit/component.yml b/admin/app/components/solidus_admin/refund_reasons/edit/component.yml new file mode 100644 index 00000000000..c604c1a6280 --- /dev/null +++ b/admin/app/components/solidus_admin/refund_reasons/edit/component.yml @@ -0,0 +1,8 @@ +# Add your component translations here. +# Use the translation in the example in your template with `t(".hello")`. +en: + title: "Edit Refund Reason" + cancel: "Cancel" + submit: "Update Refund Reason" + hints: + active: "When checked, this adjustment reason will be available for selection when adding adjustments to orders." diff --git a/admin/app/components/solidus_admin/refund_reasons/index/component.rb b/admin/app/components/solidus_admin/refund_reasons/index/component.rb index 6cbcfecada9..0cc0bf4f443 100644 --- a/admin/app/components/solidus_admin/refund_reasons/index/component.rb +++ b/admin/app/components/solidus_admin/refund_reasons/index/component.rb @@ -14,11 +14,14 @@ def search_key end def row_url(refund_reason) - spree.edit_admin_refund_reason_path(refund_reason) + spree.edit_admin_refund_reason_path(refund_reason, _turbo_frame: :edit_refund_reason_modal) end def turbo_frames - %w[new_refund_reason_modal] + %w[ + new_refund_reason_modal + edit_refund_reason_modal + ] end def page_actions diff --git a/admin/app/controllers/solidus_admin/refund_reasons_controller.rb b/admin/app/controllers/solidus_admin/refund_reasons_controller.rb index f88cee5f06a..c0e6ff4c6c2 100644 --- a/admin/app/controllers/solidus_admin/refund_reasons_controller.rb +++ b/admin/app/controllers/solidus_admin/refund_reasons_controller.rb @@ -4,6 +4,8 @@ module SolidusAdmin class RefundReasonsController < SolidusAdmin::BaseController include SolidusAdmin::ControllerHelpers::Search + before_action :find_refund_reason, only: %i[edit update] + def index set_index_page @@ -49,6 +51,39 @@ def create end end + def edit + set_index_page + + respond_to do |format| + format.html { render component('refund_reasons/edit').new(page: @page, refund_reason: @refund_reason) } + end + end + + def update + if @refund_reason.update(refund_reason_params) + respond_to do |format| + flash[:notice] = t('.success') + + format.html do + redirect_to solidus_admin.refund_reasons_path, status: :see_other + end + + format.turbo_stream do + render turbo_stream: '' + end + end + else + set_index_page + + respond_to do |format| + format.html do + page_component = component('refund_reasons/edit').new(page: @page, refund_reason: @refund_reason) + render page_component, status: :unprocessable_entity + end + end + end + end + def destroy @refund_reason = Spree::RefundReason.find_by!(id: params[:id]) @@ -65,6 +100,10 @@ def load_refund_reason authorize! action_name, @refund_reason end + def find_refund_reason + @refund_reason = Spree::RefundReason.find(params[:id]) + end + def refund_reason_params params.require(:refund_reason).permit(:name, :code, :active) end diff --git a/admin/config/locales/refund_reasons.en.yml b/admin/config/locales/refund_reasons.en.yml index 5113e88772a..97a50264e49 100644 --- a/admin/config/locales/refund_reasons.en.yml +++ b/admin/config/locales/refund_reasons.en.yml @@ -6,3 +6,5 @@ en: success: "Refund Reasons were successfully removed." create: success: "Refund reason was successfully created." + update: + success: "Refund reason was successfully updated." diff --git a/admin/config/routes.rb b/admin/config/routes.rb index aaf6e8b36ef..aaf962d8a6c 100644 --- a/admin/config/routes.rb +++ b/admin/config/routes.rb @@ -60,7 +60,7 @@ admin_resources :stock_locations, only: [:index, :destroy] admin_resources :stores, only: [:index, :destroy] admin_resources :zones, only: [:index, :destroy] - admin_resources :refund_reasons, only: [:index, :new, :create, :destroy] + admin_resources :refund_reasons, except: [:show] admin_resources :reimbursement_types, only: [:index] admin_resources :return_reasons, only: [:index, :destroy] admin_resources :adjustment_reasons, except: [:show] diff --git a/admin/spec/features/refund_reasons_spec.rb b/admin/spec/features/refund_reasons_spec.rb index 1cc9307e331..a88099418aa 100644 --- a/admin/spec/features/refund_reasons_spec.rb +++ b/admin/spec/features/refund_reasons_spec.rb @@ -58,4 +58,34 @@ end end end + + context "when editing an existing refund reason" do + let(:query) { "?page=1&q%5Bname_or_description_cont%5D=Ret" } + + before do + Spree::RefundReason.create(name: "Return process") + visit "/admin/refund_reasons#{query}" + find_row("Return process").click + expect(page).to have_content("Edit Refund Reason") + expect(page).to be_axe_clean + end + + it "opens a modal" do + expect(page).to have_selector("dialog") + within("dialog") { click_on "Cancel" } + expect(page).not_to have_selector("dialog") + expect(page.current_url).to include(query) + end + + it "successfully updates the existing refund reason" do + fill_in "Name", with: "Customer complaint" + + click_on "Update Refund Reason" + expect(page).to have_content("Refund reason was successfully updated.") + expect(page).to have_content("Customer complaint") + expect(page).not_to have_content("Return process") + expect(Spree::RefundReason.find_by(name: "Customer complaint")).to be_present + expect(page.current_url).to include(query) + end + end end From edb553358fd54dd3d0814ec95de0d7bcd10feffa Mon Sep 17 00:00:00 2001 From: Madeline Collier Date: Mon, 12 Aug 2024 16:38:46 +0200 Subject: [PATCH 107/834] Create store credit reasons via new admin UI modal --- .../store_credit_reasons/index/component.rb | 10 +++- .../new/component.html.erb | 27 +++++++++ .../store_credit_reasons/new/component.rb | 12 ++++ .../store_credit_reasons/new/component.yml | 8 +++ .../store_credit_reasons_controller.rb | 55 ++++++++++++++++--- .../locales/store_credit_reasons.en.yml | 2 + admin/config/routes.rb | 2 +- .../features/store_credit_reasons_spec.rb | 39 +++++++++++++ 8 files changed, 145 insertions(+), 10 deletions(-) create mode 100644 admin/app/components/solidus_admin/store_credit_reasons/new/component.html.erb create mode 100644 admin/app/components/solidus_admin/store_credit_reasons/new/component.rb create mode 100644 admin/app/components/solidus_admin/store_credit_reasons/new/component.yml diff --git a/admin/app/components/solidus_admin/store_credit_reasons/index/component.rb b/admin/app/components/solidus_admin/store_credit_reasons/index/component.rb index 3199d8aac0b..0daaefc463f 100644 --- a/admin/app/components/solidus_admin/store_credit_reasons/index/component.rb +++ b/admin/app/components/solidus_admin/store_credit_reasons/index/component.rb @@ -5,16 +5,22 @@ def model_class Spree::StoreCreditReason end - def actions + def page_actions render component("ui/button").new( tag: :a, text: t('.add'), - href: spree.new_admin_store_credit_reason_path, + href: solidus_admin.new_store_credit_reason_path, data: { turbo_frame: :new_store_credit_reason_modal }, icon: "add-line", class: "align-self-end w-full", ) end + def turbo_frames + %w[ + new_store_credit_reason_modal + ] + end + def row_url(store_credit_reason) spree.edit_admin_store_credit_reason_path(store_credit_reason) end diff --git a/admin/app/components/solidus_admin/store_credit_reasons/new/component.html.erb b/admin/app/components/solidus_admin/store_credit_reasons/new/component.html.erb new file mode 100644 index 00000000000..1d8f9d8d22d --- /dev/null +++ b/admin/app/components/solidus_admin/store_credit_reasons/new/component.html.erb @@ -0,0 +1,27 @@ +<%= turbo_frame_tag :new_store_credit_reason_modal do %> + <%= render component("ui/modal").new(title: t(".title")) do |modal| %> + <%= form_for @store_credit_reason, url: solidus_admin.store_credit_reasons_path, html: { id: form_id } do |f| %> +
+ <%= render component("ui/forms/field").text_field(f, :name, class: "required") %> + +
+ <% modal.with_actions do %> +
+ <%= render component("ui/button").new(scheme: :secondary, text: t('.cancel')) %> +
+ <%= render component("ui/button").new(form: form_id, type: :submit, text: t('.submit')) %> + <% end %> + <% end %> + <% end %> +<% end %> + +<%= render component("store_credit_reasons/index").new(page: @page) %> diff --git a/admin/app/components/solidus_admin/store_credit_reasons/new/component.rb b/admin/app/components/solidus_admin/store_credit_reasons/new/component.rb new file mode 100644 index 00000000000..9fbeebbe21a --- /dev/null +++ b/admin/app/components/solidus_admin/store_credit_reasons/new/component.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class SolidusAdmin::StoreCreditReasons::New::Component < SolidusAdmin::BaseComponent + def initialize(page:, store_credit_reason:) + @page = page + @store_credit_reason = store_credit_reason + end + + def form_id + dom_id(@store_credit_reason, "#{stimulus_id}_new_store_credit_reason_form") + end +end diff --git a/admin/app/components/solidus_admin/store_credit_reasons/new/component.yml b/admin/app/components/solidus_admin/store_credit_reasons/new/component.yml new file mode 100644 index 00000000000..9b63a8b85f1 --- /dev/null +++ b/admin/app/components/solidus_admin/store_credit_reasons/new/component.yml @@ -0,0 +1,8 @@ +# Add your component translations here. +# Use the translation in the example in your template with `t(".hello")`. +en: + title: "New Store Credit Reason" + cancel: "Cancel" + submit: "Add Store Credit Reason" + hints: + active: "When checked, this adjustment reason will be available for selection when adding adjustments to orders." diff --git a/admin/app/controllers/solidus_admin/store_credit_reasons_controller.rb b/admin/app/controllers/solidus_admin/store_credit_reasons_controller.rb index db2f088ab9d..ae44bb32fb8 100644 --- a/admin/app/controllers/solidus_admin/store_credit_reasons_controller.rb +++ b/admin/app/controllers/solidus_admin/store_credit_reasons_controller.rb @@ -5,18 +5,50 @@ class StoreCreditReasonsController < SolidusAdmin::BaseController include SolidusAdmin::ControllerHelpers::Search def index - store_credit_reasons = apply_search_to( - Spree::StoreCreditReason.unscoped.order(id: :desc), - param: :q, - ) - - set_page_and_extract_portion_from(store_credit_reasons) + set_index_page respond_to do |format| format.html { render component('store_credit_reasons/index').new(page: @page) } end end + def new + @store_credit_reason = Spree::StoreCreditReason.new + + set_index_page + + respond_to do |format| + format.html { render component('store_credit_reasons/new').new(page: @page, store_credit_reason: @store_credit_reason) } + end + end + + def create + @store_credit_reason = Spree::StoreCreditReason.new(store_credit_reason_params) + + if @store_credit_reason.save + respond_to do |format| + flash[:notice] = t('.success') + + format.html do + redirect_to solidus_admin.store_credit_reasons_path, status: :see_other + end + + format.turbo_stream do + render turbo_stream: '' + end + end + else + set_index_page + + respond_to do |format| + format.html do + page_component = component('store_credit_reasons/new').new(page: @page, store_credit_reason: @store_credit_reason) + render page_component, status: :unprocessable_entity + end + end + end + end + def destroy @store_credit_reason = Spree::StoreCreditReason.find_by!(id: params[:id]) @@ -34,7 +66,16 @@ def load_store_credit_reason end def store_credit_reason_params - params.require(:store_credit_reason).permit(:store_credit_reason_id, permitted_store_credit_reason_attributes) + params.require(:store_credit_reason).permit(:name, :active) + end + + def set_index_page + store_credit_reasons = apply_search_to( + Spree::StoreCreditReason.unscoped.order(id: :desc), + param: :q, + ) + + set_page_and_extract_portion_from(store_credit_reasons) end end end diff --git a/admin/config/locales/store_credit_reasons.en.yml b/admin/config/locales/store_credit_reasons.en.yml index 7d4812eacc4..2f70e4a2aaa 100644 --- a/admin/config/locales/store_credit_reasons.en.yml +++ b/admin/config/locales/store_credit_reasons.en.yml @@ -4,3 +4,5 @@ en: title: "Store Credit Reasons" destroy: success: "Store Credit Reasons were successfully removed." + create: + success: "Store Credit Reason was successfully created." diff --git a/admin/config/routes.rb b/admin/config/routes.rb index aaf962d8a6c..49d9702509c 100644 --- a/admin/config/routes.rb +++ b/admin/config/routes.rb @@ -64,5 +64,5 @@ admin_resources :reimbursement_types, only: [:index] admin_resources :return_reasons, only: [:index, :destroy] admin_resources :adjustment_reasons, except: [:show] - admin_resources :store_credit_reasons, only: [:index, :destroy] + admin_resources :store_credit_reasons, only: [:index, :new, :create, :destroy] end diff --git a/admin/spec/features/store_credit_reasons_spec.rb b/admin/spec/features/store_credit_reasons_spec.rb index 5a8ffff0e20..52e9cfa64aa 100644 --- a/admin/spec/features/store_credit_reasons_spec.rb +++ b/admin/spec/features/store_credit_reasons_spec.rb @@ -19,4 +19,43 @@ expect(Spree::StoreCreditReason.count).to eq(0) expect(page).to be_axe_clean end + + context "when creating a new store credit reason" do + let(:query) { "?page=1&q%5Bname_cont%5D=new" } + + before do + visit "/admin/store_credit_reasons#{query}" + click_on "Add new" + expect(page).to have_content("New Store Credit Reason") + expect(page).to be_axe_clean + end + + it "opens a modal" do + expect(page).to have_selector("dialog") + within("dialog") { click_on "Cancel" } + expect(page).not_to have_selector("dialog") + expect(page.current_url).to include(query) + end + + context "with valid data" do + it "successfully creates a new store credit reason, keeping page and q params" do + fill_in "Name", with: "New Reason" + + click_on "Add Store Credit Reason" + + expect(page).to have_content("Store Credit Reason was successfully created.") + expect(Spree::StoreCreditReason.find_by(name: "New Reason")).to be_present + expect(page.current_url).to include(query) + end + end + + context "with invalid data" do + it "fails to create a new store credit reason, keeping page and q params" do + click_on "Add Store Credit Reason" + + expect(page).to have_content("can't be blank") + expect(page.current_url).to include(query) + end + end + end end From c3c5b0f48e9efa5eea6a3e48facf5a09ef490422 Mon Sep 17 00:00:00 2001 From: benjamin wil Date: Thu, 25 Jul 2024 16:02:54 -0700 Subject: [PATCH 108/834] Test `Spree.deprecator` functionality These tests were written to aid in debugging an issue: solidusio/solidus#5766 Here we expose an issue using a pending test, with the ENV that allows overriding of the default deprecation behavior, which gets lost during the initialization of the `DummyApp`. Co-authored-by: Chris Todorov --- core/spec/lib/spree/deprecator_spec.rb | 49 ++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 core/spec/lib/spree/deprecator_spec.rb diff --git a/core/spec/lib/spree/deprecator_spec.rb b/core/spec/lib/spree/deprecator_spec.rb new file mode 100644 index 00000000000..381e90bcbc2 --- /dev/null +++ b/core/spec/lib/spree/deprecator_spec.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe "Spree.deprecator" do + Dummy = Class.new do + def deprecated_method + "foo" + end + deprecate :deprecated_method, deprecator: Spree.deprecator + end + + context "by default" do + it "returns a valid deprecator" do + expect(Spree.deprecator).to have_attributes( + deprecation_horizon: "5.0", + gem_name: "Solidus" + ) + end + + it "does not raise an error unless overridden by environment" do + if ENV["SOLIDUS_RAISE_DEPRECATIONS"] + pending "fix for deprecation behaviour override" + expect { Dummy.new.deprecated_method }.to raise_error(ActiveSupport::DeprecationException) + else + expect { Dummy.new.deprecated_method }.not_to raise_error + end + end + end + + context "when the behavior has been changed to :raise" do + around do |example| + behavior_name = ActiveSupport::Deprecation::DEFAULT_BEHAVIORS.detect { |_behavior_name, behavior_proc| + behavior_proc == Spree.deprecator.behavior.first + }.first + + Spree.deprecator.behavior = :raise + + example.run + + Spree.deprecator.behavior = behavior_name + end + + it "raises an error when a deprecated method is called" do + expect { Dummy.new.deprecated_method } + .to raise_error(ActiveSupport::DeprecationException) + end + end +end From 586c5196f0cb9539a7168718bbdcb56a1a5b83e4 Mon Sep 17 00:00:00 2001 From: Chris Todorov Date: Wed, 7 Aug 2024 14:41:41 -0700 Subject: [PATCH 109/834] Resolve load order issue with deprecator behaviour This change fixes issue solidusio/solidus#5766 which seems to be caused by the deprecator behaviour being lost when the dummy app is initialized. By moving the code that sets the behaviour to happen after the initialization of the dummy app we can avoid this issue. Co-authored-by: Benjamin Willems --- core/lib/spree/testing_support/dummy_app.rb | 11 ++++++----- core/spec/lib/spree/deprecator_spec.rb | 1 - 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/core/lib/spree/testing_support/dummy_app.rb b/core/lib/spree/testing_support/dummy_app.rb index 39a246fb416..f9e9ed64f8b 100644 --- a/core/lib/spree/testing_support/dummy_app.rb +++ b/core/lib/spree/testing_support/dummy_app.rb @@ -41,6 +41,12 @@ def self.setup(gem_root:, lib_name:, auto_migrate: true) DummyApp::Application.config.root = root DummyApp::Application.initialize! unless DummyApp::Application.initialized? + # Raise on deprecation warnings. + # NOTE: This needs to happen after the application is initialized. + if ENV['SOLIDUS_RAISE_DEPRECATIONS'].present? + Spree.deprecator.behavior = :raise + end + if auto_migrate DummyApp::Migrations.auto_migrate end @@ -151,8 +157,3 @@ class Application < ::Rails::Application config.taxon_attachment_module = 'Spree::Taxon::PaperclipAttachment' end end - -# Raise on deprecation warnings -if ENV['SOLIDUS_RAISE_DEPRECATIONS'].present? - Spree.deprecator.behavior = :raise -end diff --git a/core/spec/lib/spree/deprecator_spec.rb b/core/spec/lib/spree/deprecator_spec.rb index 381e90bcbc2..1007b074f4c 100644 --- a/core/spec/lib/spree/deprecator_spec.rb +++ b/core/spec/lib/spree/deprecator_spec.rb @@ -20,7 +20,6 @@ def deprecated_method it "does not raise an error unless overridden by environment" do if ENV["SOLIDUS_RAISE_DEPRECATIONS"] - pending "fix for deprecation behaviour override" expect { Dummy.new.deprecated_method }.to raise_error(ActiveSupport::DeprecationException) else expect { Dummy.new.deprecated_method }.not_to raise_error From 92f30d83bc39c5809c7b4dff511ae7222db1c65f Mon Sep 17 00:00:00 2001 From: Madeline Collier Date: Tue, 13 Aug 2024 15:48:16 +0200 Subject: [PATCH 110/834] Edit/Update store credit reasons via new admin UI --- .../edit/component.html.erb | 26 +++++++++++++ .../store_credit_reasons/edit/component.rb | 12 ++++++ .../store_credit_reasons/edit/component.yml | 8 ++++ .../store_credit_reasons/index/component.rb | 3 +- .../store_credit_reasons/new/component.yml | 2 +- .../store_credit_reasons_controller.rb | 39 +++++++++++++++++++ .../locales/store_credit_reasons.en.yml | 6 ++- admin/config/routes.rb | 2 +- .../features/store_credit_reasons_spec.rb | 36 +++++++++++++++-- 9 files changed, 126 insertions(+), 8 deletions(-) create mode 100644 admin/app/components/solidus_admin/store_credit_reasons/edit/component.html.erb create mode 100644 admin/app/components/solidus_admin/store_credit_reasons/edit/component.rb create mode 100644 admin/app/components/solidus_admin/store_credit_reasons/edit/component.yml diff --git a/admin/app/components/solidus_admin/store_credit_reasons/edit/component.html.erb b/admin/app/components/solidus_admin/store_credit_reasons/edit/component.html.erb new file mode 100644 index 00000000000..d6e791c121f --- /dev/null +++ b/admin/app/components/solidus_admin/store_credit_reasons/edit/component.html.erb @@ -0,0 +1,26 @@ +<%= turbo_frame_tag :edit_store_credit_reason_modal do %> + <%= render component("ui/modal").new(title: t(".title")) do |modal| %> + <%= form_for @store_credit_reason, url: solidus_admin.store_credit_reason_path(@store_credit_reason), html: { id: form_id } do |f| %> +
+ <%= render component("ui/forms/field").text_field(f, :name, class: "required") %> + +
+ <% modal.with_actions do %> +
+ <%= render component("ui/button").new(scheme: :secondary, text: t('.cancel')) %> +
+ <%= render component("ui/button").new(form: form_id, type: :submit, text: t('.submit')) %> + <% end %> + <% end %> + <% end %> +<% end %> +<%= render component("store_credit_reasons/index").new(page: @page) %> diff --git a/admin/app/components/solidus_admin/store_credit_reasons/edit/component.rb b/admin/app/components/solidus_admin/store_credit_reasons/edit/component.rb new file mode 100644 index 00000000000..8a079ff9e96 --- /dev/null +++ b/admin/app/components/solidus_admin/store_credit_reasons/edit/component.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class SolidusAdmin::StoreCreditReasons::Edit::Component < SolidusAdmin::BaseComponent + def initialize(page:, store_credit_reason:) + @page = page + @store_credit_reason = store_credit_reason + end + + def form_id + dom_id(@store_credit_reason, "#{stimulus_id}_edit_store_credit_reason_form") + end +end diff --git a/admin/app/components/solidus_admin/store_credit_reasons/edit/component.yml b/admin/app/components/solidus_admin/store_credit_reasons/edit/component.yml new file mode 100644 index 00000000000..c3cb570c710 --- /dev/null +++ b/admin/app/components/solidus_admin/store_credit_reasons/edit/component.yml @@ -0,0 +1,8 @@ +# Add your component translations here. +# Use the translation in the example in your template with `t(".hello")`. +en: + title: "Edit Store Credit Reason" + cancel: "Cancel" + submit: "Update Store Credit Reason" + hints: + active: "When checked, this store credit reason will be available for selection when adding store credit to orders." diff --git a/admin/app/components/solidus_admin/store_credit_reasons/index/component.rb b/admin/app/components/solidus_admin/store_credit_reasons/index/component.rb index 0daaefc463f..744045ba2c6 100644 --- a/admin/app/components/solidus_admin/store_credit_reasons/index/component.rb +++ b/admin/app/components/solidus_admin/store_credit_reasons/index/component.rb @@ -18,11 +18,12 @@ def page_actions def turbo_frames %w[ new_store_credit_reason_modal + edit_store_credit_reason_modal ] end def row_url(store_credit_reason) - spree.edit_admin_store_credit_reason_path(store_credit_reason) + spree.edit_admin_store_credit_reason_path(store_credit_reason, _turbo_frame: :edit_store_credit_reason_modal) end def search_url diff --git a/admin/app/components/solidus_admin/store_credit_reasons/new/component.yml b/admin/app/components/solidus_admin/store_credit_reasons/new/component.yml index 9b63a8b85f1..b24663d7edc 100644 --- a/admin/app/components/solidus_admin/store_credit_reasons/new/component.yml +++ b/admin/app/components/solidus_admin/store_credit_reasons/new/component.yml @@ -5,4 +5,4 @@ en: cancel: "Cancel" submit: "Add Store Credit Reason" hints: - active: "When checked, this adjustment reason will be available for selection when adding adjustments to orders." + active: "When checked, this store credit reason will be available for selection when adding store credit to orders." diff --git a/admin/app/controllers/solidus_admin/store_credit_reasons_controller.rb b/admin/app/controllers/solidus_admin/store_credit_reasons_controller.rb index ae44bb32fb8..399b35781d7 100644 --- a/admin/app/controllers/solidus_admin/store_credit_reasons_controller.rb +++ b/admin/app/controllers/solidus_admin/store_credit_reasons_controller.rb @@ -4,6 +4,8 @@ module SolidusAdmin class StoreCreditReasonsController < SolidusAdmin::BaseController include SolidusAdmin::ControllerHelpers::Search + before_action :find_store_credit_reason, only: %i[edit update] + def index set_index_page @@ -49,6 +51,39 @@ def create end end + def edit + set_index_page + + respond_to do |format| + format.html { render component('store_credit_reasons/edit').new(page: @page, store_credit_reason: @store_credit_reason) } + end + end + + def update + if @store_credit_reason.update(store_credit_reason_params) + respond_to do |format| + flash[:notice] = t('.success') + + format.html do + redirect_to solidus_admin.store_credit_reasons_path, status: :see_other + end + + format.turbo_stream do + render turbo_stream: '' + end + end + else + set_index_page + + respond_to do |format| + format.html do + page_component = component('store_credit_reasons/edit').new(page: @page, store_credit_reason: @store_credit_reason) + render page_component, status: :unprocessable_entity + end + end + end + end + def destroy @store_credit_reason = Spree::StoreCreditReason.find_by!(id: params[:id]) @@ -65,6 +100,10 @@ def load_store_credit_reason authorize! action_name, @store_credit_reason end + def find_store_credit_reason + @store_credit_reason = Spree::StoreCreditReason.find(params[:id]) + end + def store_credit_reason_params params.require(:store_credit_reason).permit(:name, :active) end diff --git a/admin/config/locales/store_credit_reasons.en.yml b/admin/config/locales/store_credit_reasons.en.yml index 2f70e4a2aaa..2d70b99d077 100644 --- a/admin/config/locales/store_credit_reasons.en.yml +++ b/admin/config/locales/store_credit_reasons.en.yml @@ -3,6 +3,8 @@ en: store_credit_reasons: title: "Store Credit Reasons" destroy: - success: "Store Credit Reasons were successfully removed." + success: "Store credit reasons were successfully removed." create: - success: "Store Credit Reason was successfully created." + success: "Store credit reason was successfully created." + update: + success: "Store credit reason was successfully updated." diff --git a/admin/config/routes.rb b/admin/config/routes.rb index 49d9702509c..f82f324a81b 100644 --- a/admin/config/routes.rb +++ b/admin/config/routes.rb @@ -64,5 +64,5 @@ admin_resources :reimbursement_types, only: [:index] admin_resources :return_reasons, only: [:index, :destroy] admin_resources :adjustment_reasons, except: [:show] - admin_resources :store_credit_reasons, only: [:index, :new, :create, :destroy] + admin_resources :store_credit_reasons, except: [:show] end diff --git a/admin/spec/features/store_credit_reasons_spec.rb b/admin/spec/features/store_credit_reasons_spec.rb index 52e9cfa64aa..b4b2c46aff4 100644 --- a/admin/spec/features/store_credit_reasons_spec.rb +++ b/admin/spec/features/store_credit_reasons_spec.rb @@ -5,7 +5,7 @@ describe "Store Credit Reasons", :js, type: :feature do before { sign_in create(:admin_user, email: 'admin@example.com') } - it "lists Store Credit Reasons and allows deleting them" do + it "lists store credit reasons and allows deleting them" do create(:store_credit_reason, name: "Default-store-credit-reason") visit "/admin/store_credit_reasons" @@ -14,7 +14,7 @@ select_row("Default-store-credit-reason") click_on "Delete" - expect(page).to have_content("Store Credit Reasons were successfully removed.") + expect(page).to have_content("Store credit reasons were successfully removed.") expect(page).not_to have_content("Default-store-credit-reason") expect(Spree::StoreCreditReason.count).to eq(0) expect(page).to be_axe_clean @@ -43,7 +43,7 @@ click_on "Add Store Credit Reason" - expect(page).to have_content("Store Credit Reason was successfully created.") + expect(page).to have_content("Store credit reason was successfully created.") expect(Spree::StoreCreditReason.find_by(name: "New Reason")).to be_present expect(page.current_url).to include(query) end @@ -58,4 +58,34 @@ end end end + + context "when editing an existing store credit reason" do + let(:query) { "?page=1&q%5Bname_cont%5D=customer" } + + before do + Spree::StoreCreditReason.create(name: "New Customer Reward") + visit "/admin/store_credit_reasons#{query}" + find_row("New Customer Reward").click + expect(page).to have_content("Edit Store Credit Reason") + expect(page).to be_axe_clean + end + + it "opens a modal" do + expect(page).to have_selector("dialog") + within("dialog") { click_on "Cancel" } + expect(page).not_to have_selector("dialog") + expect(page.current_url).to include(query) + end + + it "successfully updates the existing store credit reason" do + fill_in "Name", with: "Customer complaint" + + click_on "Update Store Credit Reason" + expect(page).to have_content("Store credit reason was successfully updated.") + expect(page).to have_content("Customer complaint") + expect(page).not_to have_content("New Customer Reward") + expect(Spree::StoreCreditReason.find_by(name: "Customer complaint")).to be_present + expect(page.current_url).to include(query) + end + end end From 24a9259be3c66cdecfc0b69ad46ddade0eee4eb9 Mon Sep 17 00:00:00 2001 From: Madeline Collier Date: Tue, 13 Aug 2024 14:56:40 +0200 Subject: [PATCH 111/834] Appease codecov with new admin request specs Our coverage percentage has been dropping by a few percentage points on the last couple PRs due to our feature tests only really testing the happy paths of the admin and not really covering every possible controller redirect and format-specific response. Adidng these specs should win us back those coverage points. --- .../store_credit_reasons_spec.rb | 133 ++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 admin/spec/requests/solidus_admin/store_credit_reasons_spec.rb diff --git a/admin/spec/requests/solidus_admin/store_credit_reasons_spec.rb b/admin/spec/requests/solidus_admin/store_credit_reasons_spec.rb new file mode 100644 index 00000000000..4044c13f713 --- /dev/null +++ b/admin/spec/requests/solidus_admin/store_credit_reasons_spec.rb @@ -0,0 +1,133 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe "SolidusAdmin::StoreCreditReasonsController", type: :request do + let(:admin_user) { create(:admin_user) } + let(:store_credit_reason) { create(:store_credit_reason) } + + before do + allow_any_instance_of(SolidusAdmin::BaseController).to receive(:spree_current_user).and_return(admin_user) + end + + describe "GET /index" do + it "renders the index template with a 200 OK status" do + get solidus_admin.store_credit_reasons_path + expect(response).to have_http_status(:ok) + end + end + + describe "GET /new" do + it "renders the new template with a 200 OK status" do + get solidus_admin.new_store_credit_reason_path + expect(response).to have_http_status(:ok) + end + end + + describe "POST /create" do + context "with valid parameters" do + let(:valid_attributes) { { name: "Customer Loyalty", active: true } } + + it "creates a new StoreCreditReason" do + expect { + post solidus_admin.store_credit_reasons_path, params: { store_credit_reason: valid_attributes } + }.to change(Spree::StoreCreditReason, :count).by(1) + end + + it "redirects to the index page with a 303 See Other status" do + post solidus_admin.store_credit_reasons_path, params: { store_credit_reason: valid_attributes } + expect(response).to redirect_to(solidus_admin.store_credit_reasons_path) + expect(response).to have_http_status(:see_other) + end + + it "displays a success flash message" do + post solidus_admin.store_credit_reasons_path, params: { store_credit_reason: valid_attributes } + follow_redirect! + expect(response.body).to include("Store credit reason was successfully created.") + end + end + + context "with invalid parameters" do + let(:invalid_attributes) { { name: "", active: true } } + + it "does not create a new StoreCreditReason" do + expect { + post solidus_admin.store_credit_reasons_path, params: { store_credit_reason: invalid_attributes } + }.not_to change(Spree::StoreCreditReason, :count) + end + + it "renders the new template with unprocessable_entity status" do + post solidus_admin.store_credit_reasons_path, params: { store_credit_reason: invalid_attributes } + expect(response).to have_http_status(:unprocessable_entity) + end + end + end + + describe "GET /edit" do + it "renders the edit template with a 200 OK status" do + get solidus_admin.edit_store_credit_reason_path(store_credit_reason) + expect(response).to have_http_status(:ok) + end + end + + describe "PATCH /update" do + context "with valid parameters" do + let(:valid_attributes) { { name: "Updated Reason", active: false } } + + it "updates the store credit reason" do + patch solidus_admin.store_credit_reason_path(store_credit_reason), params: { store_credit_reason: valid_attributes } + store_credit_reason.reload + expect(store_credit_reason.name).to eq("Updated Reason") + expect(store_credit_reason.active).to be(false) + end + + it "redirects to the index page with a 303 See Other status" do + patch solidus_admin.store_credit_reason_path(store_credit_reason), params: { store_credit_reason: valid_attributes } + expect(response).to redirect_to(solidus_admin.store_credit_reasons_path) + expect(response).to have_http_status(:see_other) + end + + it "displays a success flash message" do + patch solidus_admin.store_credit_reason_path(store_credit_reason), params: { store_credit_reason: valid_attributes } + follow_redirect! + expect(response.body).to include("Store credit reason was successfully updated.") + end + end + + context "with invalid parameters" do + let(:invalid_attributes) { { name: "", active: false } } + + it "does not update the store credit reason" do + original_name = store_credit_reason.name + patch solidus_admin.store_credit_reason_path(store_credit_reason), params: { store_credit_reason: invalid_attributes } + store_credit_reason.reload + expect(store_credit_reason.name).to eq(original_name) + end + + it "renders the edit template with unprocessable_entity status" do + patch solidus_admin.store_credit_reason_path(store_credit_reason), params: { store_credit_reason: invalid_attributes } + expect(response).to have_http_status(:unprocessable_entity) + end + end + end + + describe "DELETE /destroy" do + it "deletes the store credit reason and redirects to the index page with a 303 See Other status" do + # This ensures the store_credit_reason exists prior to deletion. + store_credit_reason + + expect { + delete solidus_admin.store_credit_reason_path(store_credit_reason) + }.to change(Spree::StoreCreditReason, :count).by(-1) + + expect(response).to redirect_to(solidus_admin.store_credit_reasons_path) + expect(response).to have_http_status(:see_other) + end + + it "displays a success flash message after deletion" do + delete solidus_admin.store_credit_reason_path(store_credit_reason) + follow_redirect! + expect(response.body).to include("Store credit reasons were successfully removed.") + end + end +end From f24382912eb4dd987111ce98c8e1733f2f00ec38 Mon Sep 17 00:00:00 2001 From: Madeline Collier Date: Tue, 13 Aug 2024 17:08:16 +0200 Subject: [PATCH 112/834] Remove unused `load_refund_reason` from controller The general consensus is that this pattern persists across the new admin controllers purely by virtue of a copy/paste carry-over as it looks like it's not being used anywhere. --- .../controllers/solidus_admin/refund_reasons_controller.rb | 5 ----- 1 file changed, 5 deletions(-) diff --git a/admin/app/controllers/solidus_admin/refund_reasons_controller.rb b/admin/app/controllers/solidus_admin/refund_reasons_controller.rb index c0e6ff4c6c2..be3a2c1a3cc 100644 --- a/admin/app/controllers/solidus_admin/refund_reasons_controller.rb +++ b/admin/app/controllers/solidus_admin/refund_reasons_controller.rb @@ -95,11 +95,6 @@ def destroy private - def load_refund_reason - @refund_reason = Spree::RefundReason.find_by!(id: params[:id]) - authorize! action_name, @refund_reason - end - def find_refund_reason @refund_reason = Spree::RefundReason.find(params[:id]) end From 479011f010b40e17ede958b18fc121fef3ede4a4 Mon Sep 17 00:00:00 2001 From: Madeline Collier Date: Tue, 13 Aug 2024 17:10:40 +0200 Subject: [PATCH 113/834] Ensure copy is consistent for admin refund reasons The copy had a few inconsistencies and copy/paste errors. --- .../components/solidus_admin/refund_reasons/edit/component.yml | 2 +- admin/config/locales/refund_reasons.en.yml | 2 +- admin/spec/features/refund_reasons_spec.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/admin/app/components/solidus_admin/refund_reasons/edit/component.yml b/admin/app/components/solidus_admin/refund_reasons/edit/component.yml index c604c1a6280..045e9033cbe 100644 --- a/admin/app/components/solidus_admin/refund_reasons/edit/component.yml +++ b/admin/app/components/solidus_admin/refund_reasons/edit/component.yml @@ -5,4 +5,4 @@ en: cancel: "Cancel" submit: "Update Refund Reason" hints: - active: "When checked, this adjustment reason will be available for selection when adding adjustments to orders." + active: "When checked, this refund reason will be available for selection when adding refunds to orders." diff --git a/admin/config/locales/refund_reasons.en.yml b/admin/config/locales/refund_reasons.en.yml index 97a50264e49..a748998bb9f 100644 --- a/admin/config/locales/refund_reasons.en.yml +++ b/admin/config/locales/refund_reasons.en.yml @@ -3,7 +3,7 @@ en: refund_reasons: title: "Refund Reasons" destroy: - success: "Refund Reasons were successfully removed." + success: "Refund reasons were successfully removed." create: success: "Refund reason was successfully created." update: diff --git a/admin/spec/features/refund_reasons_spec.rb b/admin/spec/features/refund_reasons_spec.rb index a88099418aa..ae5a52663eb 100644 --- a/admin/spec/features/refund_reasons_spec.rb +++ b/admin/spec/features/refund_reasons_spec.rb @@ -14,7 +14,7 @@ select_row("Default-refund-reason") click_on "Delete" - expect(page).to have_content("Refund Reasons were successfully removed.") + expect(page).to have_content("Refund reasons were successfully removed.") expect(page).not_to have_content("Default-refund-reason") expect(Spree::RefundReason.count).to eq(0) expect(page).to be_axe_clean From 1e1de9ca9985ce3a1558834600eb3eefab8eaccd Mon Sep 17 00:00:00 2001 From: Madeline Collier Date: Tue, 13 Aug 2024 17:15:33 +0200 Subject: [PATCH 114/834] Fix attribute reference The human-readable output is the same regardless of which model we are referencing here, (always "Active") but since this is the RefundReason component, let's be consistent and reference that here. --- .../solidus_admin/refund_reasons/new/component.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/admin/app/components/solidus_admin/refund_reasons/new/component.html.erb b/admin/app/components/solidus_admin/refund_reasons/new/component.html.erb index ed82c770caf..ef1a44999d2 100644 --- a/admin/app/components/solidus_admin/refund_reasons/new/component.html.erb +++ b/admin/app/components/solidus_admin/refund_reasons/new/component.html.erb @@ -10,7 +10,7 @@ value: "1", checked: f.object.active ) %> - <%= Spree::TaxCategory.human_attribute_name :active %> + <%= Spree::RefundReason.human_attribute_name :active %> <%= render component("ui/toggletip").new(text: t(".hints.active")) %>
From cb3303871b8547d2132c8b591c69f16a8a68b6f5 Mon Sep 17 00:00:00 2001 From: Madeline Collier Date: Tue, 13 Aug 2024 17:22:36 +0200 Subject: [PATCH 115/834] Add request spec for new admin refund reasons --- .../solidus_admin/refund_reasons_spec.rb | 134 ++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 admin/spec/requests/solidus_admin/refund_reasons_spec.rb diff --git a/admin/spec/requests/solidus_admin/refund_reasons_spec.rb b/admin/spec/requests/solidus_admin/refund_reasons_spec.rb new file mode 100644 index 00000000000..0c1a8979840 --- /dev/null +++ b/admin/spec/requests/solidus_admin/refund_reasons_spec.rb @@ -0,0 +1,134 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe "SolidusAdmin::RefundReasonsController", type: :request do + let(:admin_user) { create(:admin_user) } + let(:refund_reason) { create(:refund_reason) } + + before do + allow_any_instance_of(SolidusAdmin::BaseController).to receive(:spree_current_user).and_return(admin_user) + end + + describe "GET /index" do + it "renders the index template with a 200 OK status" do + get solidus_admin.refund_reasons_path + expect(response).to have_http_status(:ok) + end + end + + describe "GET /new" do + it "renders the new template with a 200 OK status" do + get solidus_admin.new_refund_reason_path + expect(response).to have_http_status(:ok) + end + end + + describe "POST /create" do + context "with valid parameters" do + let(:valid_attributes) { { name: "Refund for Defective Item", code: "DEFECT", active: true } } + + it "creates a new RefundReason" do + expect { + post solidus_admin.refund_reasons_path, params: { refund_reason: valid_attributes } + }.to change(Spree::RefundReason, :count).by(1) + end + + it "redirects to the index page with a 303 See Other status" do + post solidus_admin.refund_reasons_path, params: { refund_reason: valid_attributes } + expect(response).to redirect_to(solidus_admin.refund_reasons_path) + expect(response).to have_http_status(:see_other) + end + + it "displays a success flash message" do + post solidus_admin.refund_reasons_path, params: { refund_reason: valid_attributes } + follow_redirect! + expect(response.body).to include("Refund reason was successfully created.") + end + end + + context "with invalid parameters" do + let(:invalid_attributes) { { name: "", code: "", active: true } } + + it "does not create a new RefundReason" do + expect { + post solidus_admin.refund_reasons_path, params: { refund_reason: invalid_attributes } + }.not_to change(Spree::RefundReason, :count) + end + + it "renders the new template with unprocessable_entity status" do + post solidus_admin.refund_reasons_path, params: { refund_reason: invalid_attributes } + expect(response).to have_http_status(:unprocessable_entity) + end + end + end + + describe "GET /edit" do + it "renders the edit template with a 200 OK status" do + get solidus_admin.edit_refund_reason_path(refund_reason) + expect(response).to have_http_status(:ok) + end + end + + describe "PATCH /update" do + context "with valid parameters" do + let(:valid_attributes) { { name: "Updated Refund Reason", code: "UPD", active: false } } + + it "updates the refund reason" do + patch solidus_admin.refund_reason_path(refund_reason), params: { refund_reason: valid_attributes } + refund_reason.reload + expect(refund_reason.name).to eq("Updated Refund Reason") + expect(refund_reason.code).to eq("UPD") + expect(refund_reason.active).to be(false) + end + + it "redirects to the index page with a 303 See Other status" do + patch solidus_admin.refund_reason_path(refund_reason), params: { refund_reason: valid_attributes } + expect(response).to redirect_to(solidus_admin.refund_reasons_path) + expect(response).to have_http_status(:see_other) + end + + it "displays a success flash message" do + patch solidus_admin.refund_reason_path(refund_reason), params: { refund_reason: valid_attributes } + follow_redirect! + expect(response.body).to include("Refund reason was successfully updated.") + end + end + + context "with invalid parameters" do + let(:invalid_attributes) { { name: "", code: "UPD", active: false } } + + it "does not update the refund reason" do + original_name = refund_reason.name + patch solidus_admin.refund_reason_path(refund_reason), params: { refund_reason: invalid_attributes } + refund_reason.reload + expect(refund_reason.name).to eq(original_name) + end + + it "renders the edit template with unprocessable_entity status" do + patch solidus_admin.refund_reason_path(refund_reason), params: { refund_reason: invalid_attributes } + expect(response).to have_http_status(:unprocessable_entity) + end + end + end + + describe "DELETE /destroy" do + it "deletes the refund reason and redirects to the index page with a 303 See Other status" do + # This ensures the refund_reason exists prior to deletion. + refund_reason + + expect { + delete solidus_admin.refund_reason_path(refund_reason) + }.to change(Spree::RefundReason, :count).by(-1) + + expect(response).to redirect_to(solidus_admin.refund_reasons_path) + expect(response).to have_http_status(:see_other) + end + + it "displays a success flash message after deletion" do + delete solidus_admin.refund_reason_path(refund_reason) + follow_redirect! + expect(response.body).to include("Refund reasons were successfully removed.") + end + end +end From d31e1e67f6564b442c80894b97a754103c27d639 Mon Sep 17 00:00:00 2001 From: Madeline Collier Date: Wed, 14 Aug 2024 16:00:06 +0200 Subject: [PATCH 116/834] Remove unused load method in new admin controllers These controllers were based off an example controller in which the `load_[resource_name]` method was needed. However in these cases it looks like this method is not being used, so we should be safe to remove these unused methods from these controllers. --- .../solidus_admin/adjustment_reasons_controller.rb | 5 ----- .../solidus_admin/reimbursement_types_controller.rb | 5 ----- .../controllers/solidus_admin/return_reasons_controller.rb | 5 ----- .../solidus_admin/shipping_categories_controller.rb | 5 ----- .../controllers/solidus_admin/shipping_methods_controller.rb | 5 ----- .../controllers/solidus_admin/stock_locations_controller.rb | 5 ----- .../solidus_admin/store_credit_reasons_controller.rb | 5 ----- admin/app/controllers/solidus_admin/stores_controller.rb | 5 ----- .../controllers/solidus_admin/tax_categories_controller.rb | 5 ----- admin/app/controllers/solidus_admin/tax_rates_controller.rb | 5 ----- admin/app/controllers/solidus_admin/users_controller.rb | 5 ----- admin/app/controllers/solidus_admin/zones_controller.rb | 5 ----- 12 files changed, 60 deletions(-) diff --git a/admin/app/controllers/solidus_admin/adjustment_reasons_controller.rb b/admin/app/controllers/solidus_admin/adjustment_reasons_controller.rb index 1211530bb9d..43b59a9fac9 100644 --- a/admin/app/controllers/solidus_admin/adjustment_reasons_controller.rb +++ b/admin/app/controllers/solidus_admin/adjustment_reasons_controller.rb @@ -95,11 +95,6 @@ def destroy private - def load_adjustment_reason - @adjustment_reason = Spree::AdjustmentReason.find_by!(id: params[:id]) - authorize! action_name, @adjustment_reason - end - def find_adjustment_reason @adjustment_reason = Spree::AdjustmentReason.find(params[:id]) end diff --git a/admin/app/controllers/solidus_admin/reimbursement_types_controller.rb b/admin/app/controllers/solidus_admin/reimbursement_types_controller.rb index 67eba73f73e..530578386ee 100644 --- a/admin/app/controllers/solidus_admin/reimbursement_types_controller.rb +++ b/admin/app/controllers/solidus_admin/reimbursement_types_controller.rb @@ -19,11 +19,6 @@ def index private - def load_reimbursement_type - @reimbursement_type = Spree::ReimbursementType.find_by!(id: params[:id]) - authorize! action_name, @reimbursement_type - end - def reimbursement_type_params params.require(:reimbursement_type).permit(:reimbursement_type_id, permitted_reimbursement_type_attributes) end diff --git a/admin/app/controllers/solidus_admin/return_reasons_controller.rb b/admin/app/controllers/solidus_admin/return_reasons_controller.rb index ad3a7883218..4887f236794 100644 --- a/admin/app/controllers/solidus_admin/return_reasons_controller.rb +++ b/admin/app/controllers/solidus_admin/return_reasons_controller.rb @@ -28,11 +28,6 @@ def destroy private - def load_return_reason - @return_reason = Spree::ReturnReason.find_by!(id: params[:id]) - authorize! action_name, @return_reason - end - def return_reason_params params.require(:return_reason).permit(:return_reason_id, permitted_return_reason_attributes) end diff --git a/admin/app/controllers/solidus_admin/shipping_categories_controller.rb b/admin/app/controllers/solidus_admin/shipping_categories_controller.rb index 19fbe6687eb..c87b635ce06 100644 --- a/admin/app/controllers/solidus_admin/shipping_categories_controller.rb +++ b/admin/app/controllers/solidus_admin/shipping_categories_controller.rb @@ -97,11 +97,6 @@ def destroy private - def load_shipping_category - @shipping_category = Spree::ShippingCategory.find_by!(id: params[:id]) - authorize! action_name, @shipping_category - end - def find_shipping_category @shipping_category = Spree::ShippingCategory.find(params[:id]) end diff --git a/admin/app/controllers/solidus_admin/shipping_methods_controller.rb b/admin/app/controllers/solidus_admin/shipping_methods_controller.rb index dcf18294bbd..6b079943e29 100644 --- a/admin/app/controllers/solidus_admin/shipping_methods_controller.rb +++ b/admin/app/controllers/solidus_admin/shipping_methods_controller.rb @@ -28,11 +28,6 @@ def destroy private - def load_shipping_method - @shipping_method = Spree::ShippingMethod.find_by!(number: params[:id]) - authorize! action_name, @shipping_method - end - def shipping_method_params params.require(:shipping_method).permit(:shipping_method_id, permitted_shipping_method_attributes) end diff --git a/admin/app/controllers/solidus_admin/stock_locations_controller.rb b/admin/app/controllers/solidus_admin/stock_locations_controller.rb index 364bfb12fc3..445c4ec25b0 100644 --- a/admin/app/controllers/solidus_admin/stock_locations_controller.rb +++ b/admin/app/controllers/solidus_admin/stock_locations_controller.rb @@ -28,11 +28,6 @@ def destroy private - def load_stock_location - @stock_location = Spree::StockLocation.find_by!(number: params[:id]) - authorize! action_name, @stock_location - end - def stock_location_params params.require(:stock_location).permit(:stock_location_id, permitted_stock_location_attributes) end diff --git a/admin/app/controllers/solidus_admin/store_credit_reasons_controller.rb b/admin/app/controllers/solidus_admin/store_credit_reasons_controller.rb index 399b35781d7..08e3292fbdb 100644 --- a/admin/app/controllers/solidus_admin/store_credit_reasons_controller.rb +++ b/admin/app/controllers/solidus_admin/store_credit_reasons_controller.rb @@ -95,11 +95,6 @@ def destroy private - def load_store_credit_reason - @store_credit_reason = Spree::StoreCreditReason.find_by!(id: params[:id]) - authorize! action_name, @store_credit_reason - end - def find_store_credit_reason @store_credit_reason = Spree::StoreCreditReason.find(params[:id]) end diff --git a/admin/app/controllers/solidus_admin/stores_controller.rb b/admin/app/controllers/solidus_admin/stores_controller.rb index ef60bcf8298..c653163d5c9 100644 --- a/admin/app/controllers/solidus_admin/stores_controller.rb +++ b/admin/app/controllers/solidus_admin/stores_controller.rb @@ -28,11 +28,6 @@ def destroy private - def load_store - @store = Spree::Store.find_by!(number: params[:id]) - authorize! action_name, @store - end - def store_params params.require(:store).permit(:store_id, permitted_store_attributes) end diff --git a/admin/app/controllers/solidus_admin/tax_categories_controller.rb b/admin/app/controllers/solidus_admin/tax_categories_controller.rb index 5bea5239211..5ffa67828f5 100644 --- a/admin/app/controllers/solidus_admin/tax_categories_controller.rb +++ b/admin/app/controllers/solidus_admin/tax_categories_controller.rb @@ -99,11 +99,6 @@ def destroy private - def load_tax_category - @tax_category = Spree::TaxCategory.find_by!(number: params[:id]) - authorize! action_name, @tax_category - end - def find_tax_category @tax_category = Spree::TaxCategory.find(params[:id]) end diff --git a/admin/app/controllers/solidus_admin/tax_rates_controller.rb b/admin/app/controllers/solidus_admin/tax_rates_controller.rb index 3a313fd9826..75c2f239eb4 100644 --- a/admin/app/controllers/solidus_admin/tax_rates_controller.rb +++ b/admin/app/controllers/solidus_admin/tax_rates_controller.rb @@ -28,11 +28,6 @@ def destroy private - def load_tax_rate - @tax_rate = Spree::TaxRate.find_by!(number: params[:id]) - authorize! action_name, @tax_rate - end - def tax_rate_params params.require(:tax_rate).permit(:tax_rate_id, permitted_tax_rate_attributes) end diff --git a/admin/app/controllers/solidus_admin/users_controller.rb b/admin/app/controllers/solidus_admin/users_controller.rb index 90518339c07..b8de0af3d0e 100644 --- a/admin/app/controllers/solidus_admin/users_controller.rb +++ b/admin/app/controllers/solidus_admin/users_controller.rb @@ -34,11 +34,6 @@ def destroy private - def load_user - @user = Spree.user_class.find_by!(number: params[:id]) - authorize! action_name, @user - end - def user_params params.require(:user).permit(:user_id, permitted_user_attributes) end diff --git a/admin/app/controllers/solidus_admin/zones_controller.rb b/admin/app/controllers/solidus_admin/zones_controller.rb index 6dd3a8b3f48..11d64cd3a3a 100644 --- a/admin/app/controllers/solidus_admin/zones_controller.rb +++ b/admin/app/controllers/solidus_admin/zones_controller.rb @@ -28,11 +28,6 @@ def destroy private - def load_zone - @zone = Spree::Zone.find_by!(number: params[:id]) - authorize! action_name, @zone - end - def zone_params params.require(:zone).permit(:zone_id, permitted_zone_attributes) end From 8dc4c1f73807c9392891c2865f5da14816a43b22 Mon Sep 17 00:00:00 2001 From: Madeline Collier Date: Wed, 14 Aug 2024 16:02:56 +0200 Subject: [PATCH 117/834] Add more request spec coverage While the bulk of our coverage comes from feature tests, the Codecov percentage was sufferring as smaller portions of our controllers were going untested without these types of specs. Adding them fixes our decreases in our Codecov percentage and adds peace of mind. --- .../solidus_admin/adjustment_reasons_spec.rb | 134 ++++++++++++++++++ .../solidus_admin/shipping_categories_spec.rb | 132 +++++++++++++++++ 2 files changed, 266 insertions(+) create mode 100644 admin/spec/requests/solidus_admin/adjustment_reasons_spec.rb create mode 100644 admin/spec/requests/solidus_admin/shipping_categories_spec.rb diff --git a/admin/spec/requests/solidus_admin/adjustment_reasons_spec.rb b/admin/spec/requests/solidus_admin/adjustment_reasons_spec.rb new file mode 100644 index 00000000000..1f4a8bd7ccb --- /dev/null +++ b/admin/spec/requests/solidus_admin/adjustment_reasons_spec.rb @@ -0,0 +1,134 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe "SolidusAdmin::AdjustmentReasonsController", type: :request do + let(:admin_user) { create(:admin_user) } + let(:adjustment_reason) { create(:adjustment_reason) } + + before do + allow_any_instance_of(SolidusAdmin::BaseController).to receive(:spree_current_user).and_return(admin_user) + end + + describe "GET /index" do + it "renders the index template with a 200 OK status" do + get solidus_admin.adjustment_reasons_path + expect(response).to have_http_status(:ok) + end + end + + describe "GET /new" do + it "renders the new template with a 200 OK status" do + get solidus_admin.new_adjustment_reason_path + expect(response).to have_http_status(:ok) + end + end + + describe "POST /create" do + context "with valid parameters" do + let(:valid_attributes) { { name: "Price Adjustment", code: "PRICE_ADJUST", active: true } } + + it "creates a new AdjustmentReason" do + expect { + post solidus_admin.adjustment_reasons_path, params: { adjustment_reason: valid_attributes } + }.to change(Spree::AdjustmentReason, :count).by(1) + end + + it "redirects to the index page with a 303 See Other status" do + post solidus_admin.adjustment_reasons_path, params: { adjustment_reason: valid_attributes } + expect(response).to redirect_to(solidus_admin.adjustment_reasons_path) + expect(response).to have_http_status(:see_other) + end + + it "displays a success flash message" do + post solidus_admin.adjustment_reasons_path, params: { adjustment_reason: valid_attributes } + follow_redirect! + expect(response.body).to include("Adjustment reason was successfully created.") + end + end + + context "with invalid parameters" do + let(:invalid_attributes) { { name: "", code: "", active: true } } + + it "does not create a new AdjustmentReason" do + expect { + post solidus_admin.adjustment_reasons_path, params: { adjustment_reason: invalid_attributes } + }.not_to change(Spree::AdjustmentReason, :count) + end + + it "renders the new template with unprocessable_entity status" do + post solidus_admin.adjustment_reasons_path, params: { adjustment_reason: invalid_attributes } + expect(response).to have_http_status(:unprocessable_entity) + end + end + end + + describe "GET /edit" do + it "renders the edit template with a 200 OK status" do + get solidus_admin.edit_adjustment_reason_path(adjustment_reason) + expect(response).to have_http_status(:ok) + end + end + + describe "PATCH /update" do + context "with valid parameters" do + let(:valid_attributes) { { name: "Updated Adjustment Reason", code: "UPD_ADJ", active: false } } + + it "updates the adjustment reason" do + patch solidus_admin.adjustment_reason_path(adjustment_reason), params: { adjustment_reason: valid_attributes } + adjustment_reason.reload + expect(adjustment_reason.name).to eq("Updated Adjustment Reason") + expect(adjustment_reason.code).to eq("UPD_ADJ") + expect(adjustment_reason.active).to be(false) + end + + it "redirects to the index page with a 303 See Other status" do + patch solidus_admin.adjustment_reason_path(adjustment_reason), params: { adjustment_reason: valid_attributes } + expect(response).to redirect_to(solidus_admin.adjustment_reasons_path) + expect(response).to have_http_status(:see_other) + end + + it "displays a success flash message" do + patch solidus_admin.adjustment_reason_path(adjustment_reason), params: { adjustment_reason: valid_attributes } + follow_redirect! + expect(response.body).to include("Adjustment reason was successfully updated.") + end + end + + context "with invalid parameters" do + let(:invalid_attributes) { { name: "", code: "UPD_ADJ", active: false } } + + it "does not update the adjustment reason" do + original_name = adjustment_reason.name + patch solidus_admin.adjustment_reason_path(adjustment_reason), params: { adjustment_reason: invalid_attributes } + adjustment_reason.reload + expect(adjustment_reason.name).to eq(original_name) + end + + it "renders the edit template with unprocessable_entity status" do + patch solidus_admin.adjustment_reason_path(adjustment_reason), params: { adjustment_reason: invalid_attributes } + expect(response).to have_http_status(:unprocessable_entity) + end + end + end + + describe "DELETE /destroy" do + it "deletes the adjustment reason and redirects to the index page with a 303 See Other status" do + # This ensures the adjustment_reason exists prior to deletion. + adjustment_reason + + expect { + delete solidus_admin.adjustment_reason_path(adjustment_reason) + }.to change(Spree::AdjustmentReason, :count).by(-1) + + expect(response).to redirect_to(solidus_admin.adjustment_reasons_path) + expect(response).to have_http_status(:see_other) + end + + it "displays a success flash message after deletion" do + delete solidus_admin.adjustment_reason_path(adjustment_reason) + follow_redirect! + expect(response.body).to include("Adjustment reasons were successfully removed.") + end + end +end diff --git a/admin/spec/requests/solidus_admin/shipping_categories_spec.rb b/admin/spec/requests/solidus_admin/shipping_categories_spec.rb new file mode 100644 index 00000000000..ebe973cb004 --- /dev/null +++ b/admin/spec/requests/solidus_admin/shipping_categories_spec.rb @@ -0,0 +1,132 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe "SolidusAdmin::ShippingCategoriesController", type: :request do + let(:admin_user) { create(:admin_user) } + let(:shipping_category) { create(:shipping_category) } + + before do + allow_any_instance_of(SolidusAdmin::BaseController).to receive(:spree_current_user).and_return(admin_user) + end + + describe "GET /index" do + it "renders the index template with a 200 OK status" do + get solidus_admin.shipping_categories_path + expect(response).to have_http_status(:ok) + end + end + + describe "GET /new" do + it "renders the new template with a 200 OK status" do + get solidus_admin.new_shipping_category_path + expect(response).to have_http_status(:ok) + end + end + + describe "POST /create" do + context "with valid parameters" do + let(:valid_attributes) { { name: "Express" } } + + it "creates a new ShippingCategory" do + expect { + post solidus_admin.shipping_categories_path, params: { shipping_category: valid_attributes } + }.to change(Spree::ShippingCategory, :count).by(1) + end + + it "redirects to the index page with a 303 See Other status" do + post solidus_admin.shipping_categories_path, params: { shipping_category: valid_attributes } + expect(response).to redirect_to(solidus_admin.shipping_categories_path) + expect(response).to have_http_status(:see_other) + end + + it "displays a success flash message" do + post solidus_admin.shipping_categories_path, params: { shipping_category: valid_attributes } + follow_redirect! + expect(response.body).to include("Shipping category was successfully created.") + end + end + + context "with invalid parameters" do + let(:invalid_attributes) { { name: "" } } + + it "does not create a new ShippingCategory" do + expect { + post solidus_admin.shipping_categories_path, params: { shipping_category: invalid_attributes } + }.not_to change(Spree::ShippingCategory, :count) + end + + it "renders the new template with unprocessable_entity status" do + post solidus_admin.shipping_categories_path, params: { shipping_category: invalid_attributes } + expect(response).to have_http_status(:unprocessable_entity) + end + end + end + + describe "GET /edit" do + it "renders the edit template with a 200 OK status" do + get solidus_admin.edit_shipping_category_path(shipping_category) + expect(response).to have_http_status(:ok) + end + end + + describe "PATCH /update" do + context "with valid parameters" do + let(:valid_attributes) { { name: "Updated Shipping Category" } } + + it "updates the shipping category" do + patch solidus_admin.shipping_category_path(shipping_category), params: { shipping_category: valid_attributes } + shipping_category.reload + expect(shipping_category.name).to eq("Updated Shipping Category") + end + + it "redirects to the index page with a 303 See Other status" do + patch solidus_admin.shipping_category_path(shipping_category), params: { shipping_category: valid_attributes } + expect(response).to redirect_to(solidus_admin.shipping_categories_path) + expect(response).to have_http_status(:see_other) + end + + it "displays a success flash message" do + patch solidus_admin.shipping_category_path(shipping_category), params: { shipping_category: valid_attributes } + follow_redirect! + expect(response.body).to include("Shipping category was successfully updated.") + end + end + + context "with invalid parameters" do + let(:invalid_attributes) { { name: "" } } + + it "does not update the shipping category" do + original_name = shipping_category.name + patch solidus_admin.shipping_category_path(shipping_category), params: { shipping_category: invalid_attributes } + shipping_category.reload + expect(shipping_category.name).to eq(original_name) + end + + it "renders the edit template with unprocessable_entity status" do + patch solidus_admin.shipping_category_path(shipping_category), params: { shipping_category: invalid_attributes } + expect(response).to have_http_status(:unprocessable_entity) + end + end + end + + describe "DELETE /destroy" do + it "deletes the shipping category and redirects to the index page with a 303 See Other status" do + # Ensure the shipping_category exists before deletion + shipping_category + + expect { + delete solidus_admin.shipping_category_path(shipping_category) + }.to change(Spree::ShippingCategory, :count).by(-1) + + expect(response).to redirect_to(solidus_admin.shipping_categories_path) + expect(response).to have_http_status(:see_other) + end + + it "displays a success flash message after deletion" do + delete solidus_admin.shipping_category_path(shipping_category) + follow_redirect! + expect(response.body).to include("Shipping categories were successfully removed.") + end + end +end From 00869ecf08576f57c46d9e2b87fa963ee7d99f08 Mon Sep 17 00:00:00 2001 From: Madeline Collier Date: Fri, 16 Aug 2024 12:02:44 +0200 Subject: [PATCH 118/834] Create refund reasons via new admin UI modal Co-authored-by: Chris Todorov Co-authored-by: Kendra Riga --- .../return_reasons/index/component.rb | 17 ++++ .../return_reasons/new/component.html.erb | 27 ++++++ .../return_reasons/new/component.rb | 12 +++ .../return_reasons/new/component.yml | 8 ++ .../return_reasons_controller.rb | 60 +++++++++++-- admin/config/locales/return_reasons.en.yml | 4 +- admin/config/routes.rb | 2 +- admin/spec/features/return_reasons_spec.rb | 43 +++++++++- .../solidus_admin/return_reasons_spec.rb | 84 +++++++++++++++++++ 9 files changed, 247 insertions(+), 10 deletions(-) create mode 100644 admin/app/components/solidus_admin/return_reasons/new/component.html.erb create mode 100644 admin/app/components/solidus_admin/return_reasons/new/component.rb create mode 100644 admin/app/components/solidus_admin/return_reasons/new/component.yml create mode 100644 admin/spec/requests/solidus_admin/return_reasons_spec.rb diff --git a/admin/app/components/solidus_admin/return_reasons/index/component.rb b/admin/app/components/solidus_admin/return_reasons/index/component.rb index 75ba35f2b95..ec6c118a7d8 100644 --- a/admin/app/components/solidus_admin/return_reasons/index/component.rb +++ b/admin/app/components/solidus_admin/return_reasons/index/component.rb @@ -17,6 +17,23 @@ def row_url(return_reason) spree.edit_admin_return_reason_path(return_reason) end + def turbo_frames + %w[ + new_return_reason_modal + ] + end + + def page_actions + render component("ui/button").new( + tag: :a, + text: t('.add'), + href: solidus_admin.new_return_reason_path, + data: { turbo_frame: :new_return_reason_modal }, + icon: "add-line", + class: "align-self-end w-full", + ) + end + def batch_actions [ { diff --git a/admin/app/components/solidus_admin/return_reasons/new/component.html.erb b/admin/app/components/solidus_admin/return_reasons/new/component.html.erb new file mode 100644 index 00000000000..7a55ebbcacd --- /dev/null +++ b/admin/app/components/solidus_admin/return_reasons/new/component.html.erb @@ -0,0 +1,27 @@ +<%= turbo_frame_tag :new_return_reason_modal do %> + <%= render component("ui/modal").new(title: t(".title")) do |modal| %> + <%= form_for @return_reason, url: solidus_admin.return_reasons_path, html: { id: form_id } do |f| %> +
+ <%= render component("ui/forms/field").text_field(f, :name, class: "required") %> + +
+ <% modal.with_actions do %> +
+ <%= render component("ui/button").new(scheme: :secondary, text: t('.cancel')) %> +
+ <%= render component("ui/button").new(form: form_id, type: :submit, text: t('.submit')) %> + <% end %> + <% end %> + <% end %> +<% end %> + +<%= render component("return_reasons/index").new(page: @page) %> diff --git a/admin/app/components/solidus_admin/return_reasons/new/component.rb b/admin/app/components/solidus_admin/return_reasons/new/component.rb new file mode 100644 index 00000000000..7efe494f634 --- /dev/null +++ b/admin/app/components/solidus_admin/return_reasons/new/component.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class SolidusAdmin::ReturnReasons::New::Component < SolidusAdmin::BaseComponent + def initialize(page:, return_reason:) + @page = page + @return_reason = return_reason + end + + def form_id + dom_id(@return_reason, "#{stimulus_id}_new_return_reason_form") + end +end diff --git a/admin/app/components/solidus_admin/return_reasons/new/component.yml b/admin/app/components/solidus_admin/return_reasons/new/component.yml new file mode 100644 index 00000000000..bbed4e31820 --- /dev/null +++ b/admin/app/components/solidus_admin/return_reasons/new/component.yml @@ -0,0 +1,8 @@ +# Add your component translations here. +# Use the translation in the example in your template with `t(".hello")`. +en: + title: "New Return Reason" + cancel: "Cancel" + submit: "Add Return Reason" + hints: + active: "When checked, this return reason will be available for selection when returning orders." diff --git a/admin/app/controllers/solidus_admin/return_reasons_controller.rb b/admin/app/controllers/solidus_admin/return_reasons_controller.rb index 4887f236794..4cd70759bdd 100644 --- a/admin/app/controllers/solidus_admin/return_reasons_controller.rb +++ b/admin/app/controllers/solidus_admin/return_reasons_controller.rb @@ -4,19 +4,52 @@ module SolidusAdmin class ReturnReasonsController < SolidusAdmin::BaseController include SolidusAdmin::ControllerHelpers::Search - def index - return_reasons = apply_search_to( - Spree::ReturnReason.unscoped.order(id: :desc), - param: :q, - ) - set_page_and_extract_portion_from(return_reasons) + def index + set_index_page respond_to do |format| format.html { render component('return_reasons/index').new(page: @page) } end end + def new + @return_reason = Spree::ReturnReason.new + + set_index_page + + respond_to do |format| + format.html { render component('return_reasons/new').new(page: @page, return_reason: @return_reason) } + end + end + + def create + @return_reason = Spree::ReturnReason.new(return_reason_params) + + if @return_reason.save + respond_to do |format| + flash[:notice] = t('.success') + + format.html do + redirect_to solidus_admin.return_reasons_path, status: :see_other + end + + format.turbo_stream do + render turbo_stream: '' + end + end + else + set_index_page + + respond_to do |format| + format.html do + page_component = component('return_reasons/new').new(page: @page, return_reason: @return_reason) + render page_component, status: :unprocessable_entity + end + end + end + end + def destroy @return_reason = Spree::ReturnReason.find_by!(id: params[:id]) @@ -28,8 +61,21 @@ def destroy private + def find_return_reason + @return_reason = Spree::ReturnReason.find(params[:id]) + end + + def set_index_page + return_reasons = apply_search_to( + Spree::ReturnReason.unscoped.order(id: :desc), + param: :q, + ) + + set_page_and_extract_portion_from(return_reasons) + end + def return_reason_params - params.require(:return_reason).permit(:return_reason_id, permitted_return_reason_attributes) + params.require(:return_reason).permit(:name, :active) end end end diff --git a/admin/config/locales/return_reasons.en.yml b/admin/config/locales/return_reasons.en.yml index 248dc9769d7..caab2675d5c 100644 --- a/admin/config/locales/return_reasons.en.yml +++ b/admin/config/locales/return_reasons.en.yml @@ -3,4 +3,6 @@ en: return_reasons: title: "Return Reasons" destroy: - success: "Return Reasons were successfully removed." + success: "Return reasons were successfully removed." + create: + success: "Return reason was successfully created." diff --git a/admin/config/routes.rb b/admin/config/routes.rb index f82f324a81b..43e31293e5f 100644 --- a/admin/config/routes.rb +++ b/admin/config/routes.rb @@ -62,7 +62,7 @@ admin_resources :zones, only: [:index, :destroy] admin_resources :refund_reasons, except: [:show] admin_resources :reimbursement_types, only: [:index] - admin_resources :return_reasons, only: [:index, :destroy] + admin_resources :return_reasons, only: [:index, :destroy, :new, :create] admin_resources :adjustment_reasons, except: [:show] admin_resources :store_credit_reasons, except: [:show] end diff --git a/admin/spec/features/return_reasons_spec.rb b/admin/spec/features/return_reasons_spec.rb index 3e929cd1dd4..34708f7f93c 100644 --- a/admin/spec/features/return_reasons_spec.rb +++ b/admin/spec/features/return_reasons_spec.rb @@ -14,9 +14,50 @@ select_row("Default-return-reason") click_on "Delete" - expect(page).to have_content("Return Reasons were successfully removed.") + expect(page).to have_content("Return reasons were successfully removed.") expect(page).not_to have_content("Default-return-reason") expect(Spree::ReturnReason.count).to eq(0) expect(page).to be_axe_clean end + + context "when creating a new return reason" do + let(:query) { "?page=1&q%5Bname_cont%5D=new" } + + before do + visit "/admin/return_reasons#{query}" + click_on "Add new" + expect(page).to have_content("New Return Reason") + expect(page).to be_axe_clean + end + + it "opens a modal" do + expect(page).to have_selector("dialog") + within("dialog") { click_on "Cancel" } + expect(page).not_to have_selector("dialog") + expect(page.current_url).to include(query) + end + + context "with valid data" do + it "successfully creates a new return reason, keeping page and q params" do + fill_in "Name", with: "New Reason" + page.uncheck "return_reason[active]" + + click_on "Add Return Reason" + + expect(page).to have_content("Return reason was successfully created.") + expect(Spree::ReturnReason.find_by(name: "New Reason")).to be_present + expect(Spree::ReturnReason.find_by(name: "New Reason").active).to be_falsey + expect(page.current_url).to include(query) + end + end + + context "with invalid data" do + it "fails to create a new return reason, keeping page and q params" do + click_on "Add Return Reason" + + expect(page).to have_content("can't be blank") + expect(page.current_url).to include(query) + end + end + end end diff --git a/admin/spec/requests/solidus_admin/return_reasons_spec.rb b/admin/spec/requests/solidus_admin/return_reasons_spec.rb new file mode 100644 index 00000000000..e7a95646cf9 --- /dev/null +++ b/admin/spec/requests/solidus_admin/return_reasons_spec.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe "SolidusAdmin::ReturnReasonsController", type: :request do + let(:admin_user) { create(:admin_user) } + let(:return_reason) { create(:return_reason) } + + before do + allow_any_instance_of(SolidusAdmin::BaseController).to receive(:spree_current_user).and_return(admin_user) + end + + describe "GET /index" do + it "renders the index template with a 200 OK status" do + get solidus_admin.return_reasons_path + expect(response).to have_http_status(:ok) + end + end + + describe "GET /new" do + it "renders the new template with a 200 OK status" do + get solidus_admin.new_return_reason_path + expect(response).to have_http_status(:ok) + end + end + + describe "POST /create" do + context "with valid parameters" do + let(:valid_attributes) { { name: "Damaged item", active: true } } + + it "creates a new ReturnReason" do + expect { + post solidus_admin.return_reasons_path, params: { return_reason: valid_attributes } + }.to change(Spree::ReturnReason, :count).by(1) + end + + it "redirects to the index page with a 303 See Other status" do + post solidus_admin.return_reasons_path, params: { return_reason: valid_attributes } + expect(response).to redirect_to(solidus_admin.return_reasons_path) + expect(response).to have_http_status(:see_other) + end + + it "displays a success flash message" do + post solidus_admin.return_reasons_path, params: { return_reason: valid_attributes } + follow_redirect! + expect(response.body).to include("Return reason was successfully created.") + end + end + + context "with invalid parameters" do + let(:invalid_attributes) { { name: "" } } + + it "does not create a new ReturnReason" do + expect { + post solidus_admin.return_reasons_path, params: { return_reason: invalid_attributes } + }.not_to change(Spree::ReturnReason, :count) + end + + it "renders the new template with unprocessable_entity status" do + post solidus_admin.return_reasons_path, params: { return_reason: invalid_attributes } + expect(response).to have_http_status(:unprocessable_entity) + end + end + end + + describe "DELETE /destroy" do + let!(:return_reason_to_delete) { create(:return_reason) } + + it "deletes the return reason and redirects to the index page with a 303 See Other status" do + expect { + delete solidus_admin.return_reason_path(return_reason_to_delete) + }.to change(Spree::ReturnReason, :count).by(-1) + + expect(response).to redirect_to(solidus_admin.return_reasons_path) + expect(response).to have_http_status(:see_other) + end + + it "displays a success flash message after deletion" do + delete solidus_admin.return_reason_path(return_reason_to_delete) + follow_redirect! + expect(response.body).to include("Return reasons were successfully removed.") + end + end +end From 10eb257b90fbe6107455872295be7f5c6e5a86ee Mon Sep 17 00:00:00 2001 From: Madeline Collier Date: Fri, 16 Aug 2024 12:07:53 +0200 Subject: [PATCH 119/834] Edit/Update return reasons via new admin UI --- .../return_reasons/edit/component.html.erb | 26 ++++++++++ .../return_reasons/edit/component.rb | 12 +++++ .../return_reasons/edit/component.yml | 8 ++++ .../return_reasons/index/component.rb | 3 +- .../return_reasons_controller.rb | 34 +++++++++++++ admin/config/locales/return_reasons.en.yml | 2 + admin/config/routes.rb | 2 +- admin/spec/features/return_reasons_spec.rb | 32 +++++++++++++ .../solidus_admin/return_reasons_spec.rb | 48 +++++++++++++++++++ 9 files changed, 165 insertions(+), 2 deletions(-) create mode 100644 admin/app/components/solidus_admin/return_reasons/edit/component.html.erb create mode 100644 admin/app/components/solidus_admin/return_reasons/edit/component.rb create mode 100644 admin/app/components/solidus_admin/return_reasons/edit/component.yml diff --git a/admin/app/components/solidus_admin/return_reasons/edit/component.html.erb b/admin/app/components/solidus_admin/return_reasons/edit/component.html.erb new file mode 100644 index 00000000000..8782aa23c43 --- /dev/null +++ b/admin/app/components/solidus_admin/return_reasons/edit/component.html.erb @@ -0,0 +1,26 @@ +<%= turbo_frame_tag :edit_return_reason_modal do %> + <%= render component("ui/modal").new(title: t(".title")) do |modal| %> + <%= form_for @return_reason, url: solidus_admin.return_reason_path(@return_reason), html: { id: form_id } do |f| %> +
+ <%= render component("ui/forms/field").text_field(f, :name, class: "required") %> + +
+ <% modal.with_actions do %> +
+ <%= render component("ui/button").new(scheme: :secondary, text: t('.cancel')) %> +
+ <%= render component("ui/button").new(form: form_id, type: :submit, text: t('.submit')) %> + <% end %> + <% end %> + <% end %> +<% end %> +<%= render component("return_reasons/index").new(page: @page) %> diff --git a/admin/app/components/solidus_admin/return_reasons/edit/component.rb b/admin/app/components/solidus_admin/return_reasons/edit/component.rb new file mode 100644 index 00000000000..2ce5ed7f0bf --- /dev/null +++ b/admin/app/components/solidus_admin/return_reasons/edit/component.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class SolidusAdmin::ReturnReasons::Edit::Component < SolidusAdmin::BaseComponent + def initialize(page:, return_reason:) + @page = page + @return_reason = return_reason + end + + def form_id + dom_id(@return_reason, "#{stimulus_id}_edit_return_reason_form") + end +end diff --git a/admin/app/components/solidus_admin/return_reasons/edit/component.yml b/admin/app/components/solidus_admin/return_reasons/edit/component.yml new file mode 100644 index 00000000000..03806d9bf30 --- /dev/null +++ b/admin/app/components/solidus_admin/return_reasons/edit/component.yml @@ -0,0 +1,8 @@ +# Add your component translations here. +# Use the translation in the example in your template with `t(".hello")`. +en: + title: "Edit Return Reason" + cancel: "Cancel" + submit: "Update Return Reason" + hints: + active: "When checked, this return reason will be available for selection when returning orders." diff --git a/admin/app/components/solidus_admin/return_reasons/index/component.rb b/admin/app/components/solidus_admin/return_reasons/index/component.rb index ec6c118a7d8..175d9575828 100644 --- a/admin/app/components/solidus_admin/return_reasons/index/component.rb +++ b/admin/app/components/solidus_admin/return_reasons/index/component.rb @@ -14,12 +14,13 @@ def search_key end def row_url(return_reason) - spree.edit_admin_return_reason_path(return_reason) + spree.edit_admin_return_reason_path(return_reason, _turbo_frame: :edit_return_reason_modal) end def turbo_frames %w[ new_return_reason_modal + edit_return_reason_modal ] end diff --git a/admin/app/controllers/solidus_admin/return_reasons_controller.rb b/admin/app/controllers/solidus_admin/return_reasons_controller.rb index 4cd70759bdd..ef47465ecd3 100644 --- a/admin/app/controllers/solidus_admin/return_reasons_controller.rb +++ b/admin/app/controllers/solidus_admin/return_reasons_controller.rb @@ -4,6 +4,7 @@ module SolidusAdmin class ReturnReasonsController < SolidusAdmin::BaseController include SolidusAdmin::ControllerHelpers::Search + before_action :find_return_reason, only: %i[edit update] def index set_index_page @@ -50,6 +51,39 @@ def create end end + def edit + set_index_page + + respond_to do |format| + format.html { render component('return_reasons/edit').new(page: @page, return_reason: @return_reason) } + end + end + + def update + if @return_reason.update(return_reason_params) + respond_to do |format| + flash[:notice] = t('.success') + + format.html do + redirect_to solidus_admin.return_reasons_path, status: :see_other + end + + format.turbo_stream do + render turbo_stream: '' + end + end + else + set_index_page + + respond_to do |format| + format.html do + page_component = component('return_reasons/edit').new(page: @page, return_reason: @return_reason) + render page_component, status: :unprocessable_entity + end + end + end + end + def destroy @return_reason = Spree::ReturnReason.find_by!(id: params[:id]) diff --git a/admin/config/locales/return_reasons.en.yml b/admin/config/locales/return_reasons.en.yml index caab2675d5c..7987e6f786a 100644 --- a/admin/config/locales/return_reasons.en.yml +++ b/admin/config/locales/return_reasons.en.yml @@ -6,3 +6,5 @@ en: success: "Return reasons were successfully removed." create: success: "Return reason was successfully created." + update: + success: "Return reason was successfully updated." diff --git a/admin/config/routes.rb b/admin/config/routes.rb index 43e31293e5f..7ad5ef34c89 100644 --- a/admin/config/routes.rb +++ b/admin/config/routes.rb @@ -62,7 +62,7 @@ admin_resources :zones, only: [:index, :destroy] admin_resources :refund_reasons, except: [:show] admin_resources :reimbursement_types, only: [:index] - admin_resources :return_reasons, only: [:index, :destroy, :new, :create] + admin_resources :return_reasons, except: [:show] admin_resources :adjustment_reasons, except: [:show] admin_resources :store_credit_reasons, except: [:show] end diff --git a/admin/spec/features/return_reasons_spec.rb b/admin/spec/features/return_reasons_spec.rb index 34708f7f93c..5932101bf41 100644 --- a/admin/spec/features/return_reasons_spec.rb +++ b/admin/spec/features/return_reasons_spec.rb @@ -60,4 +60,36 @@ end end end + + context "when editing an existing return reason" do + let(:query) { "?page=1&q%5Bname_cont%5D=reason" } + + before do + Spree::ReturnReason.create(name: "Good Reason") + visit "/admin/return_reasons#{query}" + find_row("Good Reason").click + expect(page).to have_content("Edit Return Reason") + expect(page).to be_axe_clean + end + + it "opens a modal" do + expect(page).to have_selector("dialog") + within("dialog") { click_on "Cancel" } + expect(page).not_to have_selector("dialog") + expect(page.current_url).to include(query) + end + + it "successfully updates the existing return reason" do + fill_in "Name", with: "Better Reason" + page.uncheck "return_reason[active]" + + click_on "Update Return Reason" + expect(page).to have_content("Return reason was successfully updated.") + expect(page).to have_content("Better Reason") + expect(page).not_to have_content("Good Reason") + expect(Spree::ReturnReason.find_by(name: "Better Reason")).to be_present + expect(Spree::ReturnReason.find_by(name: "Better Reason").active).to be_falsey + expect(page.current_url).to include(query) + end + end end diff --git a/admin/spec/requests/solidus_admin/return_reasons_spec.rb b/admin/spec/requests/solidus_admin/return_reasons_spec.rb index e7a95646cf9..179715484ba 100644 --- a/admin/spec/requests/solidus_admin/return_reasons_spec.rb +++ b/admin/spec/requests/solidus_admin/return_reasons_spec.rb @@ -24,6 +24,54 @@ end end + describe "GET /edit" do + it "renders the edit template with a 200 OK status" do + get solidus_admin.edit_return_reason_path(return_reason) + expect(response).to have_http_status(:ok) + end + end + + describe "PATCH /update" do + context "with valid parameters" do + let(:valid_attributes) { { name: "Updated Return Reason", active: false } } + + it "updates the return reason" do + patch solidus_admin.return_reason_path(return_reason), params: { return_reason: valid_attributes } + return_reason.reload + expect(return_reason.name).to eq("Updated Return Reason") + expect(return_reason.active).to be(false) + end + + it "redirects to the index page with a 303 See Other status" do + patch solidus_admin.return_reason_path(return_reason), params: { return_reason: valid_attributes } + expect(response).to redirect_to(solidus_admin.return_reasons_path) + expect(response).to have_http_status(:see_other) + end + + it "displays a success flash message" do + patch solidus_admin.return_reason_path(return_reason), params: { return_reason: valid_attributes } + follow_redirect! + expect(response.body).to include("Return reason was successfully updated.") + end + end + + context "with invalid parameters" do + let(:invalid_attributes) { { name: "", active: false } } + + it "does not update the return reason" do + original_name = return_reason.name + patch solidus_admin.return_reason_path(return_reason), params: { return_reason: invalid_attributes } + return_reason.reload + expect(return_reason.name).to eq(original_name) + end + + it "renders the edit template with unprocessable_entity status" do + patch solidus_admin.return_reason_path(return_reason), params: { return_reason: invalid_attributes } + expect(response).to have_http_status(:unprocessable_entity) + end + end + end + describe "POST /create" do context "with valid parameters" do let(:valid_attributes) { { name: "Damaged item", active: true } } From 4cc04fb525dbe5aba65ca301fd4f825b89e3500a Mon Sep 17 00:00:00 2001 From: Daniele Palombo <387690+DanielePalombo@users.noreply.github.com> Date: Wed, 13 Sep 2023 08:57:09 +0200 Subject: [PATCH 120/834] Rescue PromotionCode creation Before this fix, when the Spree::PromotionCode creation failed, the exception was raised and the job fails with error. This commit rescues the creation exception and remove the failed code from the codes list. --- .../spree/promotion_code/batch_builder.rb | 6 ++++-- .../promotion_code/batch_builder_spec.rb | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/legacy_promotions/app/models/spree/promotion_code/batch_builder.rb b/legacy_promotions/app/models/spree/promotion_code/batch_builder.rb index 60eb7873bba..0d0700d9551 100644 --- a/legacy_promotions/app/models/spree/promotion_code/batch_builder.rb +++ b/legacy_promotions/app/models/spree/promotion_code/batch_builder.rb @@ -40,13 +40,15 @@ def generate_random_codes new_codes = Array.new(max_codes_to_generate) { generate_random_code }.uniq codes_for_current_batch = get_unique_codes(new_codes) - codes_for_current_batch.each do |value| + codes_for_current_batch = codes_for_current_batch.map do |value| Spree::PromotionCode.create!( value: value, promotion: promotion, promotion_code_batch: promotion_code_batch ) - end + rescue ActiveRecord::RecordInvalid + nil + end.compact created_codes += codes_for_current_batch.size end end diff --git a/legacy_promotions/spec/models/spree/promotion_code/batch_builder_spec.rb b/legacy_promotions/spec/models/spree/promotion_code/batch_builder_spec.rb index 67e513ba11b..e056d62893a 100644 --- a/legacy_promotions/spec/models/spree/promotion_code/batch_builder_spec.rb +++ b/legacy_promotions/spec/models/spree/promotion_code/batch_builder_spec.rb @@ -72,6 +72,25 @@ subject.build_promotion_codes expect(promotion.codes.size).to eq(number_of_codes) end + + context "when promotion_code creation returns an error" do + before do + @raise_exception = true + allow(Spree::PromotionCode).to receive(:create!) do + if @raise_exception + @raise_exception = false + raise(ActiveRecord::RecordInvalid) + else + create(:promotion_code, promotion: promotion) + end + end + end + + it "creates the correct number of codes anyway" do + subject.build_promotion_codes + expect(promotion.codes.size).to eq(number_of_codes) + end + end end end From 6f6dc4e95d7900273f37ac4ddd5d2da16ff6d51b Mon Sep 17 00:00:00 2001 From: Daniele Palombo <387690+DanielePalombo@users.noreply.github.com> Date: Wed, 13 Sep 2023 08:57:52 +0200 Subject: [PATCH 121/834] Add the ability to restart PromotionCode Batch --- .../app/models/spree/promotion_code/batch_builder.rb | 3 ++- .../models/spree/promotion_code/batch_builder_spec.rb | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/legacy_promotions/app/models/spree/promotion_code/batch_builder.rb b/legacy_promotions/app/models/spree/promotion_code/batch_builder.rb index 0d0700d9551..a6f321fa910 100644 --- a/legacy_promotions/app/models/spree/promotion_code/batch_builder.rb +++ b/legacy_promotions/app/models/spree/promotion_code/batch_builder.rb @@ -31,7 +31,8 @@ def build_promotion_codes private def generate_random_codes - created_codes = 0 + created_codes = promotion_code_batch.promotion_codes.count + batch_size = @options[:batch_size] while created_codes < number_of_codes diff --git a/legacy_promotions/spec/models/spree/promotion_code/batch_builder_spec.rb b/legacy_promotions/spec/models/spree/promotion_code/batch_builder_spec.rb index e056d62893a..7b52fffe859 100644 --- a/legacy_promotions/spec/models/spree/promotion_code/batch_builder_spec.rb +++ b/legacy_promotions/spec/models/spree/promotion_code/batch_builder_spec.rb @@ -91,6 +91,16 @@ expect(promotion.codes.size).to eq(number_of_codes) end end + + context "when same promotion_codes are already present" do + before do + create_list(:promotion_code, 11, promotion: promotion, promotion_code_batch: promotion_code_batch) + end + + it "creates only the missing promotion_codes" do + expect { subject.build_promotion_codes }.to change { promotion.codes.size }.by(39) + end + end end end From d7e3688a9350504dc8ee14d8b0d1c654c62b806c Mon Sep 17 00:00:00 2001 From: Kendra Riga Date: Fri, 16 Aug 2024 13:45:10 -0700 Subject: [PATCH 122/834] Don't use instance variable in spec --- .../spec/models/spree/promotion_code/batch_builder_spec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/legacy_promotions/spec/models/spree/promotion_code/batch_builder_spec.rb b/legacy_promotions/spec/models/spree/promotion_code/batch_builder_spec.rb index 7b52fffe859..d9f0c06b53d 100644 --- a/legacy_promotions/spec/models/spree/promotion_code/batch_builder_spec.rb +++ b/legacy_promotions/spec/models/spree/promotion_code/batch_builder_spec.rb @@ -75,10 +75,10 @@ context "when promotion_code creation returns an error" do before do - @raise_exception = true + raise_exception = true allow(Spree::PromotionCode).to receive(:create!) do - if @raise_exception - @raise_exception = false + if raise_exception + raise_exception = false raise(ActiveRecord::RecordInvalid) else create(:promotion_code, promotion: promotion) From 211110a1fe685e599d29bd51cf8c8902207cf771 Mon Sep 17 00:00:00 2001 From: Kendra Riga Date: Fri, 16 Aug 2024 13:49:31 -0700 Subject: [PATCH 123/834] Fix performance regression Collecting results of large code counts can exceed available memory. This was initially proposed on another PR that we are basing this one on: https://github.com/solidusio/solidus/pull/5383 Co-authored-by: Jared Norman Co-authored-by: Nick Co-authored-by: Benjamin Co-authored-by: Sofia Co-authored-by: Senem Soy Co-authored-by: Andrew Stewart Co-authored-by: Chris --- .../app/models/spree/promotion_code/batch_builder.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/legacy_promotions/app/models/spree/promotion_code/batch_builder.rb b/legacy_promotions/app/models/spree/promotion_code/batch_builder.rb index a6f321fa910..422690d32e9 100644 --- a/legacy_promotions/app/models/spree/promotion_code/batch_builder.rb +++ b/legacy_promotions/app/models/spree/promotion_code/batch_builder.rb @@ -41,15 +41,15 @@ def generate_random_codes new_codes = Array.new(max_codes_to_generate) { generate_random_code }.uniq codes_for_current_batch = get_unique_codes(new_codes) - codes_for_current_batch = codes_for_current_batch.map do |value| + codes_for_current_batch.filter! do |value| Spree::PromotionCode.create!( value: value, promotion: promotion, promotion_code_batch: promotion_code_batch ) rescue ActiveRecord::RecordInvalid - nil - end.compact + false + end created_codes += codes_for_current_batch.size end end From eb6f7342be31317fcf7221f3a06c5014768f679d Mon Sep 17 00:00:00 2001 From: Madeline Collier Date: Wed, 14 Aug 2024 19:38:31 +0200 Subject: [PATCH 124/834] Add nesting for "Users and Roles" admin component Now instead of a top level Users component, the main landing page is "Users and Roles" (with the users page being the pre-selected tab, so the only visual change is a new page header and a new tab component to swap between "Users" and "Roles". --- .../solidus_admin/users/index/component.rb | 2 +- .../users_and_roles/component.rb | 19 +++++++++++++++++++ .../users_and_roles/component.yml | 2 ++ admin/spec/features/users_spec.rb | 1 + 4 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 admin/app/components/solidus_admin/users_and_roles/component.rb create mode 100644 admin/app/components/solidus_admin/users_and_roles/component.yml diff --git a/admin/app/components/solidus_admin/users/index/component.rb b/admin/app/components/solidus_admin/users/index/component.rb index 5b555b0912e..46b20878508 100644 --- a/admin/app/components/solidus_admin/users/index/component.rb +++ b/admin/app/components/solidus_admin/users/index/component.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class SolidusAdmin::Users::Index::Component < SolidusAdmin::UI::Pages::Index::Component +class SolidusAdmin::Users::Index::Component < SolidusAdmin::UsersAndRoles::Component def model_class Spree.user_class end diff --git a/admin/app/components/solidus_admin/users_and_roles/component.rb b/admin/app/components/solidus_admin/users_and_roles/component.rb new file mode 100644 index 00000000000..dbcf6bd7882 --- /dev/null +++ b/admin/app/components/solidus_admin/users_and_roles/component.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +class SolidusAdmin::UsersAndRoles::Component < SolidusAdmin::UI::Pages::Index::Component + def title + page_header_title safe_join([ + tag.div(t(".title")), + ]) + end + + def tabs + [ + { + text: Spree.user_class.model_name.human(count: 2), + href: solidus_admin.users_path, + current: model_class == Spree.user_class, + }, + ] + end +end diff --git a/admin/app/components/solidus_admin/users_and_roles/component.yml b/admin/app/components/solidus_admin/users_and_roles/component.yml new file mode 100644 index 00000000000..5fa0dabe81b --- /dev/null +++ b/admin/app/components/solidus_admin/users_and_roles/component.yml @@ -0,0 +1,2 @@ +en: + title: "Users and Roles" diff --git a/admin/spec/features/users_spec.rb b/admin/spec/features/users_spec.rb index ceef0b54d75..2149c1a20b7 100644 --- a/admin/spec/features/users_spec.rb +++ b/admin/spec/features/users_spec.rb @@ -11,6 +11,7 @@ create(:user, :with_orders, email: "customer-with-order@example.com") visit "/admin/users" + expect(page).to have_content("Users and Roles") expect(page).to have_content("customer@example.com") expect(page).not_to have_content("admin-2@example.com") click_on "Admins" From 1fbe6d553d825c9ed571729c9991773559b4e680 Mon Sep 17 00:00:00 2001 From: Madeline Collier Date: Wed, 14 Aug 2024 19:40:58 +0200 Subject: [PATCH 125/834] Add Role component to the new admin This includes the index, the new/create logic, and the singular or bulk deletion logic as well as the associated components and specs. --- .../solidus_admin/roles/index/component.rb | 65 ++++++++++++++ .../solidus_admin/roles/index/component.yml | 6 ++ .../roles/new/component.html.erb | 17 ++++ .../solidus_admin/roles/new/component.rb | 12 +++ .../solidus_admin/roles/new/component.yml | 6 ++ .../users_and_roles/component.rb | 5 ++ .../solidus_admin/roles_controller.rb | 79 +++++++++++++++++ admin/config/locales/roles.en.yml | 8 ++ admin/config/routes.rb | 1 + admin/spec/features/roles_spec.rb | 77 +++++++++++++++++ .../spec/requests/solidus_admin/roles_spec.rb | 85 +++++++++++++++++++ 11 files changed, 361 insertions(+) create mode 100644 admin/app/components/solidus_admin/roles/index/component.rb create mode 100644 admin/app/components/solidus_admin/roles/index/component.yml create mode 100644 admin/app/components/solidus_admin/roles/new/component.html.erb create mode 100644 admin/app/components/solidus_admin/roles/new/component.rb create mode 100644 admin/app/components/solidus_admin/roles/new/component.yml create mode 100644 admin/app/controllers/solidus_admin/roles_controller.rb create mode 100644 admin/config/locales/roles.en.yml create mode 100644 admin/spec/features/roles_spec.rb create mode 100644 admin/spec/requests/solidus_admin/roles_spec.rb diff --git a/admin/app/components/solidus_admin/roles/index/component.rb b/admin/app/components/solidus_admin/roles/index/component.rb new file mode 100644 index 00000000000..6d2063e0fc0 --- /dev/null +++ b/admin/app/components/solidus_admin/roles/index/component.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +class SolidusAdmin::Roles::Index::Component < SolidusAdmin::UsersAndRoles::Component + def model_class + Spree::Role + end + + def search_key + :name_cont + end + + def search_url + solidus_admin.roles_path + end + + def row_url(role) + solidus_admin.roles_path(role) + end + + def page_actions + render component("ui/button").new( + tag: :a, + text: t('.add'), + href: solidus_admin.new_role_path, data: { turbo_frame: :new_role_modal }, + icon: "add-line", + ) + end + + def turbo_frames + %w[ + new_role_modal + ] + end + + def batch_actions + [ + { + label: t('.batch_actions.delete'), + action: solidus_admin.roles_path, + method: :delete, + icon: 'delete-bin-7-line', + }, + ] + end + + def scopes + [ + { name: :all, label: t('.scopes.all'), default: true }, + { name: :admin, label: t('.scopes.admin') }, + ] + end + + def filters + [] + end + + def columns + [ + { + header: :role, + data: :name, + } + ] + end +end diff --git a/admin/app/components/solidus_admin/roles/index/component.yml b/admin/app/components/solidus_admin/roles/index/component.yml new file mode 100644 index 00000000000..3934c0825e9 --- /dev/null +++ b/admin/app/components/solidus_admin/roles/index/component.yml @@ -0,0 +1,6 @@ +en: + batch_actions: + delete: 'Delete' + scopes: + admin: Admin + all: All diff --git a/admin/app/components/solidus_admin/roles/new/component.html.erb b/admin/app/components/solidus_admin/roles/new/component.html.erb new file mode 100644 index 00000000000..dcd5d78d38c --- /dev/null +++ b/admin/app/components/solidus_admin/roles/new/component.html.erb @@ -0,0 +1,17 @@ +<%= turbo_frame_tag :new_role_modal do %> + <%= render component("ui/modal").new(title: t(".title")) do |modal| %> + <%= form_for @role, url: solidus_admin.roles_path, html: { id: form_id } do |f| %> +
+ <%= render component("ui/forms/field").text_field(f, :name, class: "required") %> +
+ <% modal.with_actions do %> +
+ <%= render component("ui/button").new(scheme: :secondary, text: t('.cancel')) %> +
+ <%= render component("ui/button").new(form: form_id, type: :submit, text: t('.submit')) %> + <% end %> + <% end %> + <% end %> +<% end %> + +<%= render component("roles/index").new(page: @page) %> diff --git a/admin/app/components/solidus_admin/roles/new/component.rb b/admin/app/components/solidus_admin/roles/new/component.rb new file mode 100644 index 00000000000..8c849014bf4 --- /dev/null +++ b/admin/app/components/solidus_admin/roles/new/component.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class SolidusAdmin::Roles::New::Component < SolidusAdmin::BaseComponent + def initialize(page:, role:) + @page = page + @role = role + end + + def form_id + dom_id(@role, "#{stimulus_id}_new_role_form") + end +end diff --git a/admin/app/components/solidus_admin/roles/new/component.yml b/admin/app/components/solidus_admin/roles/new/component.yml new file mode 100644 index 00000000000..6c318631b74 --- /dev/null +++ b/admin/app/components/solidus_admin/roles/new/component.yml @@ -0,0 +1,6 @@ +# Add your component translations here. +# Use the translation in the example in your template with `t(".hello")`. +en: + title: "New Role" + cancel: "Cancel" + submit: "Add Role" diff --git a/admin/app/components/solidus_admin/users_and_roles/component.rb b/admin/app/components/solidus_admin/users_and_roles/component.rb index dbcf6bd7882..450aa352d0c 100644 --- a/admin/app/components/solidus_admin/users_and_roles/component.rb +++ b/admin/app/components/solidus_admin/users_and_roles/component.rb @@ -14,6 +14,11 @@ def tabs href: solidus_admin.users_path, current: model_class == Spree.user_class, }, + { + text: Spree::Role.model_name.human(count: 2), + href: solidus_admin.roles_path, + current: model_class == Spree::Role, + }, ] end end diff --git a/admin/app/controllers/solidus_admin/roles_controller.rb b/admin/app/controllers/solidus_admin/roles_controller.rb new file mode 100644 index 00000000000..f01cf30cb27 --- /dev/null +++ b/admin/app/controllers/solidus_admin/roles_controller.rb @@ -0,0 +1,79 @@ +# frozen_string_literal: true + +module SolidusAdmin + class RolesController < SolidusAdmin::BaseController + include SolidusAdmin::ControllerHelpers::Search + + search_scope(:all) + search_scope(:admin) { _1.where(name: "admin") } + + def index + set_index_page + + respond_to do |format| + format.html { render component('roles/index').new(page: @page) } + end + end + + def new + @role = Spree::Role.new + + set_index_page + + respond_to do |format| + format.html { render component('roles/new').new(page: @page, role: @role) } + end + end + + def create + @role = Spree::Role.new(role_params) + + if @role.save + respond_to do |format| + flash[:notice] = t('.success') + + format.html do + redirect_to solidus_admin.roles_path, status: :see_other + end + + format.turbo_stream do + render turbo_stream: '' + end + end + else + set_index_page + + respond_to do |format| + format.html do + page_component = component('roles/new').new(page: @page, role: @role) + render page_component, status: :unprocessable_entity + end + end + end + end + + def destroy + @roles = Spree::Role.where(id: params[:id]) + + Spree::Role.transaction { @roles.destroy_all } + + flash[:notice] = t('.success') + redirect_back_or_to solidus_admin.roles_path, status: :see_other + end + + private + + def set_index_page + roles = apply_search_to( + Spree::Role.unscoped.order(id: :desc), + param: :q, + ) + + set_page_and_extract_portion_from(roles) + end + + def role_params + params.require(:role).permit(:role_id, :name, :description, :type) + end + end +end diff --git a/admin/config/locales/roles.en.yml b/admin/config/locales/roles.en.yml new file mode 100644 index 00000000000..3017e99ac94 --- /dev/null +++ b/admin/config/locales/roles.en.yml @@ -0,0 +1,8 @@ +en: + solidus_admin: + roles: + title: "Roles" + destroy: + success: "Roles were successfully removed." + create: + success: "Role was successfully created." diff --git a/admin/config/routes.rb b/admin/config/routes.rb index 7ad5ef34c89..e2c6e716a05 100644 --- a/admin/config/routes.rb +++ b/admin/config/routes.rb @@ -63,6 +63,7 @@ admin_resources :refund_reasons, except: [:show] admin_resources :reimbursement_types, only: [:index] admin_resources :return_reasons, except: [:show] + admin_resources :roles, only: [:index, :new, :create, :destroy] admin_resources :adjustment_reasons, except: [:show] admin_resources :store_credit_reasons, except: [:show] end diff --git a/admin/spec/features/roles_spec.rb b/admin/spec/features/roles_spec.rb new file mode 100644 index 00000000000..a05e4f88c47 --- /dev/null +++ b/admin/spec/features/roles_spec.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe "Roles", :js, type: :feature do + before { sign_in create(:admin_user, email: 'admin@example.com') } + + it "lists roles and allows deleting them" do + create(:role, name: "Customer Role" ) + Spree::Role.find_or_create_by(name: 'admin') + + visit "/admin/roles" + expect(page).to have_content("Users and Roles") + expect(page).to have_content("Customer Role") + expect(page).to have_content("admin") + click_on "Admin" + expect(page).to have_content("admin") + expect(page).not_to have_content("Customer Role") + click_on "All" + expect(page).to have_content("Customer Role") + expect(page).to have_content("admin") + + expect(page).to be_axe_clean + + select_row("Customer Role") + click_on "Delete" + expect(page).to have_content("Roles were successfully removed.") + expect(page).not_to have_content("Customer Role") + expect(Spree::Role.count).to eq(1) + end + + context "when creating a role" do + let(:query) { "?page=1&q%5Bname_cont%5D=new" } + + before do + visit "/admin/roles#{query}" + click_on "Add new" + expect(page).to have_content("New Role") + expect(page).to be_axe_clean + end + + it "opens a modal" do + expect(page).to have_selector("dialog") + within("dialog") { click_on "Cancel" } + expect(page).not_to have_selector("dialog") + expect(page.current_url).to include(query) + end + + context "with valid data" do + it "successfully creates a new role, keeping page and q params" do + fill_in "Name", with: "Purchaser" + + click_on "Add Role" + + expect(page).to have_content("Role was successfully created.") + expect(Spree::Role.find_by(name: "Purchaser")).to be_present + expect(page.current_url).to include(query) + end + end + + context "with invalid data" do + # @note: The only validation that Roles currently have is that names must + # be unique (but they can still be blank). + before do + create(:role, name: "Customer Role" ) + end + + it "fails to create a new role, keeping page and q params" do + fill_in "Name", with: "Customer Role" + click_on "Add Role" + + expect(page).to have_content("has already been taken") + expect(page.current_url).to include(query) + end + end + end +end diff --git a/admin/spec/requests/solidus_admin/roles_spec.rb b/admin/spec/requests/solidus_admin/roles_spec.rb new file mode 100644 index 00000000000..fea41daabf6 --- /dev/null +++ b/admin/spec/requests/solidus_admin/roles_spec.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe "SolidusAdmin::RolesController", type: :request do + let(:admin_user) { create(:admin_user) } + let(:role) { create(:role) } + + before do + allow_any_instance_of(SolidusAdmin::BaseController).to receive(:spree_current_user).and_return(admin_user) + Spree::Role.find_or_create_by(name: 'admin') + end + + describe "GET /index" do + it "renders the index template with a 200 OK status" do + get solidus_admin.roles_path + expect(response).to have_http_status(:ok) + end + end + + describe "GET /new" do + it "renders the new template with a 200 OK status" do + get solidus_admin.new_role_path + expect(response).to have_http_status(:ok) + end + end + + describe "POST /create" do + context "with valid parameters" do + let(:valid_attributes) { { name: "Customer" } } + + it "creates a new Role" do + expect { + post solidus_admin.roles_path, params: { role: valid_attributes } + }.to change(Spree::Role, :count).by(1) + end + + it "redirects to the index page with a 303 See Other status" do + post solidus_admin.roles_path, params: { role: valid_attributes } + expect(response).to redirect_to(solidus_admin.roles_path) + expect(response).to have_http_status(:see_other) + end + + it "displays a success flash message" do + post solidus_admin.roles_path, params: { role: valid_attributes } + follow_redirect! + expect(response.body).to include("Role was successfully created.") + end + end + + context "with invalid parameters" do + let(:invalid_attributes) { { name: "admin" } } + + it "does not create a new Role" do + expect { + post solidus_admin.roles_path, params: { role: invalid_attributes } + }.not_to change(Spree::Role, :count) + end + + it "renders the new template with unprocessable_entity status" do + post solidus_admin.roles_path, params: { role: invalid_attributes } + expect(response).to have_http_status(:unprocessable_entity) + end + end + end + + describe "DELETE /destroy" do + let!(:role_to_delete) { create(:role) } + + it "deletes the role and redirects to the index page with a 303 See Other status" do + expect { + delete solidus_admin.role_path(role_to_delete) + }.to change(Spree::Role, :count).by(-1) + + expect(response).to redirect_to(solidus_admin.roles_path) + expect(response).to have_http_status(:see_other) + end + + it "displays a success flash message after deletion" do + delete solidus_admin.role_path(role_to_delete) + follow_redirect! + expect(response.body).to include("Roles were successfully removed.") + end + end +end From 70f35bd85d64c4a86dea04316f119e671167252e Mon Sep 17 00:00:00 2001 From: ugifractal Date: Tue, 20 Aug 2024 06:27:30 +0700 Subject: [PATCH 126/834] Limit supported rails version to before 7.2 --- Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 2e52e9b84d2..75d1b5bd42c 100644 --- a/Gemfile +++ b/Gemfile @@ -8,7 +8,7 @@ gemspec require: false if /(stable|main)/.match? ENV['RAILS_VERSION'] gem 'rails', github: 'rails', require: false, branch: ENV['RAILS_VERSION'] else - gem 'rails', ENV['RAILS_VERSION'] || '~> 7.1.0', require: false + gem 'rails', ENV['RAILS_VERSION'] || '< 7.2', require: false end # rubocop:enable Bundler/DuplicatedGem From 4f6112510737be32fbe3a92098a43ab22a159d96 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Mon, 26 Aug 2024 16:07:28 +0200 Subject: [PATCH 127/834] Remove inaccessible details/summary element The variant search on the order#show page utilized a `details`/`summary` HTML element in order to get open/close functionality. However, Axe flags this as inaccessible, because the `summary` element does not have a label. This removes the `summary` element in favor of a simple div, and changes the controller to rely on a `hidden` class instead. --- .../solidus_admin/ui/forms/search/component.html.erb | 5 ++--- .../components/solidus_admin/ui/forms/search/component.js | 6 +++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/admin/app/components/solidus_admin/ui/forms/search/component.html.erb b/admin/app/components/solidus_admin/ui/forms/search/component.html.erb index cb1ab64e292..25a4b3bce5c 100644 --- a/admin/app/components/solidus_admin/ui/forms/search/component.html.erb +++ b/admin/app/components/solidus_admin/ui/forms/search/component.html.erb @@ -23,8 +23,7 @@ ) %>
-
- +
-
+
diff --git a/admin/app/components/solidus_admin/ui/forms/search/component.js b/admin/app/components/solidus_admin/ui/forms/search/component.js index d37938d1d7a..7810d350547 100644 --- a/admin/app/components/solidus_admin/ui/forms/search/component.js +++ b/admin/app/components/solidus_admin/ui/forms/search/component.js @@ -95,7 +95,7 @@ export default class extends Controller { } if (this.openResults && resultsHtml && this.query) { - if (!this.resultsTarget.parentNode.open) this.selectedIndex = 0 + if (this.resultsTarget.parentNode.classList.contains("hidden")) this.selectedIndex = 0 for (const result of this.resultTargets) { if (result === this.selectedResult) { @@ -108,9 +108,9 @@ export default class extends Controller { result.removeAttribute("aria-selected") } } - this.resultsTarget.parentNode.open = true + this.resultsTarget.parentNode.classList.remove("hidden") } else { - this.resultsTarget.parentNode.open = false + this.resultsTarget.parentNode.classList.add("hidden") } } } From 76ae9f6823943826950391be9863dcaa98b5fdb1 Mon Sep 17 00:00:00 2001 From: Madeline Collier Date: Thu, 15 Aug 2024 12:47:30 +0200 Subject: [PATCH 128/834] Edit/Update roles via new admin UI --- .../roles/edit/component.html.erb | 16 +++++++ .../solidus_admin/roles/edit/component.rb | 12 +++++ .../solidus_admin/roles/edit/component.yml | 6 +++ .../solidus_admin/roles/index/component.rb | 3 +- .../solidus_admin/roles_controller.rb | 39 +++++++++++++++ admin/config/locales/roles.en.yml | 2 + admin/config/routes.rb | 2 +- admin/spec/features/roles_spec.rb | 30 ++++++++++++ .../spec/requests/solidus_admin/roles_spec.rb | 47 +++++++++++++++++++ 9 files changed, 155 insertions(+), 2 deletions(-) create mode 100644 admin/app/components/solidus_admin/roles/edit/component.html.erb create mode 100644 admin/app/components/solidus_admin/roles/edit/component.rb create mode 100644 admin/app/components/solidus_admin/roles/edit/component.yml diff --git a/admin/app/components/solidus_admin/roles/edit/component.html.erb b/admin/app/components/solidus_admin/roles/edit/component.html.erb new file mode 100644 index 00000000000..541df92c402 --- /dev/null +++ b/admin/app/components/solidus_admin/roles/edit/component.html.erb @@ -0,0 +1,16 @@ +<%= turbo_frame_tag :edit_role_modal do %> + <%= render component("ui/modal").new(title: t(".title")) do |modal| %> + <%= form_for @role, url: solidus_admin.role_path(@role), html: { id: form_id } do |f| %> +
+ <%= render component("ui/forms/field").text_field(f, :name, class: "required") %> +
+ <% modal.with_actions do %> +
+ <%= render component("ui/button").new(scheme: :secondary, text: t('.cancel')) %> +
+ <%= render component("ui/button").new(form: form_id, type: :submit, text: t('.submit')) %> + <% end %> + <% end %> + <% end %> +<% end %> +<%= render component("roles/index").new(page: @page) %> diff --git a/admin/app/components/solidus_admin/roles/edit/component.rb b/admin/app/components/solidus_admin/roles/edit/component.rb new file mode 100644 index 00000000000..3d2e06dc2fe --- /dev/null +++ b/admin/app/components/solidus_admin/roles/edit/component.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class SolidusAdmin::Roles::Edit::Component < SolidusAdmin::BaseComponent + def initialize(page:, role:) + @page = page + @role = role + end + + def form_id + dom_id(@role, "#{stimulus_id}_edit_role_form") + end +end diff --git a/admin/app/components/solidus_admin/roles/edit/component.yml b/admin/app/components/solidus_admin/roles/edit/component.yml new file mode 100644 index 00000000000..22945513daa --- /dev/null +++ b/admin/app/components/solidus_admin/roles/edit/component.yml @@ -0,0 +1,6 @@ +# Add your component translations here. +# Use the translation in the example in your template with `t(".hello")`. +en: + title: "Edit Role" + cancel: "Cancel" + submit: "Update Role" diff --git a/admin/app/components/solidus_admin/roles/index/component.rb b/admin/app/components/solidus_admin/roles/index/component.rb index 6d2063e0fc0..63ac5c15923 100644 --- a/admin/app/components/solidus_admin/roles/index/component.rb +++ b/admin/app/components/solidus_admin/roles/index/component.rb @@ -14,7 +14,7 @@ def search_url end def row_url(role) - solidus_admin.roles_path(role) + solidus_admin.edit_role_path(role, _turbo_frame: :edit_role_modal) end def page_actions @@ -29,6 +29,7 @@ def page_actions def turbo_frames %w[ new_role_modal + edit_role_modal ] end diff --git a/admin/app/controllers/solidus_admin/roles_controller.rb b/admin/app/controllers/solidus_admin/roles_controller.rb index f01cf30cb27..1d601ce7c6a 100644 --- a/admin/app/controllers/solidus_admin/roles_controller.rb +++ b/admin/app/controllers/solidus_admin/roles_controller.rb @@ -4,6 +4,8 @@ module SolidusAdmin class RolesController < SolidusAdmin::BaseController include SolidusAdmin::ControllerHelpers::Search + before_action :find_role, only: %i[edit update] + search_scope(:all) search_scope(:admin) { _1.where(name: "admin") } @@ -52,6 +54,39 @@ def create end end + def edit + set_index_page + + respond_to do |format| + format.html { render component('roles/edit').new(page: @page, role: @role) } + end + end + + def update + if @role.update(role_params) + respond_to do |format| + flash[:notice] = t('.success') + + format.html do + redirect_to solidus_admin.roles_path, status: :see_other + end + + format.turbo_stream do + render turbo_stream: '' + end + end + else + set_index_page + + respond_to do |format| + format.html do + page_component = component('roles/edit').new(page: @page, role: @role) + render page_component, status: :unprocessable_entity + end + end + end + end + def destroy @roles = Spree::Role.where(id: params[:id]) @@ -63,6 +98,10 @@ def destroy private + def find_role + @role = Spree::Role.find(params[:id]) + end + def set_index_page roles = apply_search_to( Spree::Role.unscoped.order(id: :desc), diff --git a/admin/config/locales/roles.en.yml b/admin/config/locales/roles.en.yml index 3017e99ac94..abf69365133 100644 --- a/admin/config/locales/roles.en.yml +++ b/admin/config/locales/roles.en.yml @@ -6,3 +6,5 @@ en: success: "Roles were successfully removed." create: success: "Role was successfully created." + update: + success: "Role was successfully updated." diff --git a/admin/config/routes.rb b/admin/config/routes.rb index e2c6e716a05..61f5a729799 100644 --- a/admin/config/routes.rb +++ b/admin/config/routes.rb @@ -63,7 +63,7 @@ admin_resources :refund_reasons, except: [:show] admin_resources :reimbursement_types, only: [:index] admin_resources :return_reasons, except: [:show] - admin_resources :roles, only: [:index, :new, :create, :destroy] + admin_resources :roles, except: [:show] admin_resources :adjustment_reasons, except: [:show] admin_resources :store_credit_reasons, except: [:show] end diff --git a/admin/spec/features/roles_spec.rb b/admin/spec/features/roles_spec.rb index a05e4f88c47..2b08dab450b 100644 --- a/admin/spec/features/roles_spec.rb +++ b/admin/spec/features/roles_spec.rb @@ -74,4 +74,34 @@ end end end + + context "when editing an existing role" do + let(:query) { "?page=1&q%5Bname_cont%5D=er" } + + before do + Spree::Role.create(name: "Reviewer") + visit "/admin/roles#{query}" + find_row("Reviewer").click + expect(page).to have_content("Edit Role") + expect(page).to be_axe_clean + end + + it "opens a modal" do + expect(page).to have_selector("dialog") + within("dialog") { click_on "Cancel" } + expect(page).not_to have_selector("dialog") + expect(page.current_url).to include(query) + end + + it "successfully updates the existing role" do + fill_in "Name", with: "Publisher" + + click_on "Update Role" + expect(page).to have_content("Role was successfully updated.") + expect(page).to have_content("Publisher") + expect(page).not_to have_content("Reviewer") + expect(Spree::Role.find_by(name: "Publisher")).to be_present + expect(page.current_url).to include(query) + end + end end diff --git a/admin/spec/requests/solidus_admin/roles_spec.rb b/admin/spec/requests/solidus_admin/roles_spec.rb index fea41daabf6..95e57c6cb63 100644 --- a/admin/spec/requests/solidus_admin/roles_spec.rb +++ b/admin/spec/requests/solidus_admin/roles_spec.rb @@ -64,6 +64,53 @@ end end + describe "GET /edit" do + it "renders the edit template with a 200 OK status" do + get solidus_admin.edit_role_path(role) + expect(response).to have_http_status(:ok) + end + end + + describe "PATCH /update" do + context "with valid parameters" do + let(:valid_attributes) { { name: "Publisher" } } + + it "updates the role" do + patch solidus_admin.role_path(role), params: { role: valid_attributes } + role.reload + expect(role.name).to eq("Publisher") + end + + it "redirects to the index page with a 303 See Other status" do + patch solidus_admin.role_path(role), params: { role: valid_attributes } + expect(response).to redirect_to(solidus_admin.roles_path) + expect(response).to have_http_status(:see_other) + end + + it "displays a success flash message" do + patch solidus_admin.role_path(role), params: { role: valid_attributes } + follow_redirect! + expect(response.body).to include("Role was successfully updated.") + end + end + + context "with invalid parameters" do + let(:invalid_attributes) { { name: "admin" } } + + it "does not update the role" do + original_name = role.name + patch solidus_admin.role_path(role), params: { role: invalid_attributes } + role.reload + expect(role.name).to eq(original_name) + end + + it "renders the edit template with unprocessable_entity status" do + patch solidus_admin.role_path(role), params: { role: invalid_attributes } + expect(response).to have_http_status(:unprocessable_entity) + end + end + end + describe "DELETE /destroy" do let!(:role_to_delete) { create(:role) } From 5288851c3d157024a142d77d50d46f72224914ce Mon Sep 17 00:00:00 2001 From: Madeline Collier Date: Thu, 29 Aug 2024 11:41:36 +0200 Subject: [PATCH 129/834] Standardize admin controller setup methods Since `rails g scaffold` outputs the following boilerplate: def set_post @post = Post.find(params[:id]) end We should adhere to the Rails conventions and stick to `set_` instead of `find_` in our admin controllers. --- .../solidus_admin/adjustment_reasons_controller.rb | 4 ++-- .../controllers/solidus_admin/refund_reasons_controller.rb | 4 ++-- .../controllers/solidus_admin/return_reasons_controller.rb | 4 ++-- admin/app/controllers/solidus_admin/roles_controller.rb | 4 ++-- .../solidus_admin/shipping_categories_controller.rb | 4 ++-- .../solidus_admin/store_credit_reasons_controller.rb | 4 ++-- .../controllers/solidus_admin/tax_categories_controller.rb | 4 ++-- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/admin/app/controllers/solidus_admin/adjustment_reasons_controller.rb b/admin/app/controllers/solidus_admin/adjustment_reasons_controller.rb index 43b59a9fac9..64793387604 100644 --- a/admin/app/controllers/solidus_admin/adjustment_reasons_controller.rb +++ b/admin/app/controllers/solidus_admin/adjustment_reasons_controller.rb @@ -4,7 +4,7 @@ module SolidusAdmin class AdjustmentReasonsController < SolidusAdmin::BaseController include SolidusAdmin::ControllerHelpers::Search - before_action :find_adjustment_reason, only: %i[edit update] + before_action :set_adjustment_reason, only: %i[edit update] def index set_index_page @@ -95,7 +95,7 @@ def destroy private - def find_adjustment_reason + def set_adjustment_reason @adjustment_reason = Spree::AdjustmentReason.find(params[:id]) end diff --git a/admin/app/controllers/solidus_admin/refund_reasons_controller.rb b/admin/app/controllers/solidus_admin/refund_reasons_controller.rb index be3a2c1a3cc..92e8c5b2195 100644 --- a/admin/app/controllers/solidus_admin/refund_reasons_controller.rb +++ b/admin/app/controllers/solidus_admin/refund_reasons_controller.rb @@ -4,7 +4,7 @@ module SolidusAdmin class RefundReasonsController < SolidusAdmin::BaseController include SolidusAdmin::ControllerHelpers::Search - before_action :find_refund_reason, only: %i[edit update] + before_action :set_refund_reason, only: %i[edit update] def index set_index_page @@ -95,7 +95,7 @@ def destroy private - def find_refund_reason + def set_refund_reason @refund_reason = Spree::RefundReason.find(params[:id]) end diff --git a/admin/app/controllers/solidus_admin/return_reasons_controller.rb b/admin/app/controllers/solidus_admin/return_reasons_controller.rb index ef47465ecd3..892603806ca 100644 --- a/admin/app/controllers/solidus_admin/return_reasons_controller.rb +++ b/admin/app/controllers/solidus_admin/return_reasons_controller.rb @@ -4,7 +4,7 @@ module SolidusAdmin class ReturnReasonsController < SolidusAdmin::BaseController include SolidusAdmin::ControllerHelpers::Search - before_action :find_return_reason, only: %i[edit update] + before_action :set_return_reason, only: %i[edit update] def index set_index_page @@ -95,7 +95,7 @@ def destroy private - def find_return_reason + def set_return_reason @return_reason = Spree::ReturnReason.find(params[:id]) end diff --git a/admin/app/controllers/solidus_admin/roles_controller.rb b/admin/app/controllers/solidus_admin/roles_controller.rb index 1d601ce7c6a..cba1b7bd8ef 100644 --- a/admin/app/controllers/solidus_admin/roles_controller.rb +++ b/admin/app/controllers/solidus_admin/roles_controller.rb @@ -4,7 +4,7 @@ module SolidusAdmin class RolesController < SolidusAdmin::BaseController include SolidusAdmin::ControllerHelpers::Search - before_action :find_role, only: %i[edit update] + before_action :set_role, only: %i[edit update] search_scope(:all) search_scope(:admin) { _1.where(name: "admin") } @@ -98,7 +98,7 @@ def destroy private - def find_role + def set_role @role = Spree::Role.find(params[:id]) end diff --git a/admin/app/controllers/solidus_admin/shipping_categories_controller.rb b/admin/app/controllers/solidus_admin/shipping_categories_controller.rb index c87b635ce06..a7a58686da7 100644 --- a/admin/app/controllers/solidus_admin/shipping_categories_controller.rb +++ b/admin/app/controllers/solidus_admin/shipping_categories_controller.rb @@ -4,7 +4,7 @@ module SolidusAdmin class ShippingCategoriesController < SolidusAdmin::BaseController include SolidusAdmin::ControllerHelpers::Search - before_action :find_shipping_category, only: %i[edit update] + before_action :set_shipping_category, only: %i[edit update] def new @shipping_category = Spree::ShippingCategory.new @@ -97,7 +97,7 @@ def destroy private - def find_shipping_category + def set_shipping_category @shipping_category = Spree::ShippingCategory.find(params[:id]) end diff --git a/admin/app/controllers/solidus_admin/store_credit_reasons_controller.rb b/admin/app/controllers/solidus_admin/store_credit_reasons_controller.rb index 08e3292fbdb..98bbd0615bf 100644 --- a/admin/app/controllers/solidus_admin/store_credit_reasons_controller.rb +++ b/admin/app/controllers/solidus_admin/store_credit_reasons_controller.rb @@ -4,7 +4,7 @@ module SolidusAdmin class StoreCreditReasonsController < SolidusAdmin::BaseController include SolidusAdmin::ControllerHelpers::Search - before_action :find_store_credit_reason, only: %i[edit update] + before_action :set_store_credit_reason, only: %i[edit update] def index set_index_page @@ -95,7 +95,7 @@ def destroy private - def find_store_credit_reason + def set_store_credit_reason @store_credit_reason = Spree::StoreCreditReason.find(params[:id]) end diff --git a/admin/app/controllers/solidus_admin/tax_categories_controller.rb b/admin/app/controllers/solidus_admin/tax_categories_controller.rb index 5ffa67828f5..96a7d96a750 100644 --- a/admin/app/controllers/solidus_admin/tax_categories_controller.rb +++ b/admin/app/controllers/solidus_admin/tax_categories_controller.rb @@ -4,7 +4,7 @@ module SolidusAdmin class TaxCategoriesController < SolidusAdmin::BaseController include SolidusAdmin::ControllerHelpers::Search - before_action :find_tax_category, only: %i[edit update] + before_action :set_tax_category, only: %i[edit update] def new @tax_category = Spree::TaxCategory.new @@ -99,7 +99,7 @@ def destroy private - def find_tax_category + def set_tax_category @tax_category = Spree::TaxCategory.find(params[:id]) end From 16d75d0acb1807657eca8e3bf2a3af367bc42afc Mon Sep 17 00:00:00 2001 From: Thomas von Deyen Date: Mon, 29 Jul 2024 15:42:20 +0200 Subject: [PATCH 130/834] Format payment source --- core/app/models/spree/payment_source.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/app/models/spree/payment_source.rb b/core/app/models/spree/payment_source.rb index 14133f6f7d7..1251f43a67a 100644 --- a/core/app/models/spree/payment_source.rb +++ b/core/app/models/spree/payment_source.rb @@ -7,7 +7,10 @@ class PaymentSource < Spree::Base belongs_to :payment_method, optional: true has_many :payments, as: :source - has_many :wallet_payment_sources, class_name: 'Spree::WalletPaymentSource', as: :payment_source, inverse_of: :payment_source + has_many :wallet_payment_sources, + class_name: 'Spree::WalletPaymentSource', + as: :payment_source, + inverse_of: :payment_source attr_accessor :imported From 4ca261c152a67190d71018ca6c17a0c57166b9dd Mon Sep 17 00:00:00 2001 From: Thomas von Deyen Date: Tue, 27 Aug 2024 14:05:45 +0200 Subject: [PATCH 131/834] Destroy wallet payment source when source gets deleted A wallet payment source without an actual payment source does not make any sense. --- core/app/models/spree/payment_source.rb | 3 ++- core/spec/support/concerns/payment_source.rb | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/core/app/models/spree/payment_source.rb b/core/app/models/spree/payment_source.rb index 1251f43a67a..0832fc5de76 100644 --- a/core/app/models/spree/payment_source.rb +++ b/core/app/models/spree/payment_source.rb @@ -10,7 +10,8 @@ class PaymentSource < Spree::Base has_many :wallet_payment_sources, class_name: 'Spree::WalletPaymentSource', as: :payment_source, - inverse_of: :payment_source + inverse_of: :payment_source, + dependent: :destroy attr_accessor :imported diff --git a/core/spec/support/concerns/payment_source.rb b/core/spec/support/concerns/payment_source.rb index 10ef52b9cc2..c64c3ea93e8 100644 --- a/core/spec/support/concerns/payment_source.rb +++ b/core/spec/support/concerns/payment_source.rb @@ -13,6 +13,24 @@ it { is_expected.to respond_to(:user) } end + describe "#wallet_payment_sources" do + let(:user) { create(:user) } + + let!(:wallet_payment_source) do + # There are nasty validations that do not matter for this test + payment_source.save(validate: false) + Spree::WalletPaymentSource.new(user: user, payment_source: payment_source).tap do |wps| + wps.save(validate: false) + end + end + + context "when the payment source gets destroyed" do + it "destroys the wallet payment source" do + expect { payment_source.destroy }.to change { Spree::WalletPaymentSource.count }.by(-1) + end + end + end + describe "#can_capture?" do it "should be true if payment is pending" do payment = mock_model(Spree::Payment, pending?: true, created_at: Time.current) From 33e32651dde41378437191854466bc554b4748e0 Mon Sep 17 00:00:00 2001 From: Madeline Collier Date: Wed, 21 Aug 2024 19:28:05 +0200 Subject: [PATCH 132/834] Make Spree::Role names required It doesn't make a lot of sense to allow blank on this considering it is the only defining feature of a Role currently. Co-authored-by: benjamin wil --- core/app/models/spree/role.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/app/models/spree/role.rb b/core/app/models/spree/role.rb index 3e2d7d1a234..6d140ed6ef0 100644 --- a/core/app/models/spree/role.rb +++ b/core/app/models/spree/role.rb @@ -5,7 +5,7 @@ class Role < Spree::Base has_many :role_users, class_name: "Spree::RoleUser", dependent: :destroy has_many :users, through: :role_users - validates_uniqueness_of :name, case_sensitive: true + validates :name, presence: true, uniqueness: { case_sensitive: true, allow_blank: true } def admin? name == "admin" From 8c04a39f70bc3e7a1db6d415cf048a6f05acd57d Mon Sep 17 00:00:00 2001 From: Madeline Collier Date: Wed, 21 Aug 2024 19:44:37 +0200 Subject: [PATCH 133/834] Add solidus_user_roles migrations to core These migrations originally come from `solidus_user_roles`. We intend to migrate some of the functionality from that extension into `core`, and this is the first step. With the table check, these migrations should be safe to (re)run for any store which might already have `solidus_user_roles` installed. --- ...0240821173254_create_spree_permission_sets_in_core.rb | 9 +++++++++ ...40821173341_create_spree_roles_permissions_in_core.rb | 9 +++++++++ 2 files changed, 18 insertions(+) create mode 100644 core/db/migrate/20240821173254_create_spree_permission_sets_in_core.rb create mode 100644 core/db/migrate/20240821173341_create_spree_roles_permissions_in_core.rb diff --git a/core/db/migrate/20240821173254_create_spree_permission_sets_in_core.rb b/core/db/migrate/20240821173254_create_spree_permission_sets_in_core.rb new file mode 100644 index 00000000000..bf827a11fa8 --- /dev/null +++ b/core/db/migrate/20240821173254_create_spree_permission_sets_in_core.rb @@ -0,0 +1,9 @@ +class CreateSpreePermissionSetsInCore < ActiveRecord::Migration[7.0] + def change + create_table :spree_permission_sets, if_not_exists: true do |t| + t.string :name + t.string :set + t.timestamps null: false + end + end +end diff --git a/core/db/migrate/20240821173341_create_spree_roles_permissions_in_core.rb b/core/db/migrate/20240821173341_create_spree_roles_permissions_in_core.rb new file mode 100644 index 00000000000..d4bac14e445 --- /dev/null +++ b/core/db/migrate/20240821173341_create_spree_roles_permissions_in_core.rb @@ -0,0 +1,9 @@ +class CreateSpreeRolesPermissionsInCore < ActiveRecord::Migration[7.0] + def change + create_table :spree_role_permissions, if_not_exists: true do |t| + t.references :role + t.references :permission_set + t.timestamps null: false + end + end +end From 1aa18eee9e85f91c7debbb2d5d55d5b7124724fb Mon Sep 17 00:00:00 2001 From: Madeline Collier Date: Wed, 21 Aug 2024 19:51:40 +0200 Subject: [PATCH 134/834] Add description to spree_roles table --- .../migrate/20240821173641_add_description_to_spree_roles.rb | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 core/db/migrate/20240821173641_add_description_to_spree_roles.rb diff --git a/core/db/migrate/20240821173641_add_description_to_spree_roles.rb b/core/db/migrate/20240821173641_add_description_to_spree_roles.rb new file mode 100644 index 00000000000..3578008ebf1 --- /dev/null +++ b/core/db/migrate/20240821173641_add_description_to_spree_roles.rb @@ -0,0 +1,5 @@ +class AddDescriptionToSpreeRoles < ActiveRecord::Migration[7.0] + def change + add_column :spree_roles, :description, :text + end +end From f7398438a80af4a9914cb1936257260cbce5bcf3 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Mon, 2 Sep 2024 14:39:42 +0200 Subject: [PATCH 135/834] Decorate Default Return Refund Amount Calculator in LegacyPromotions This calculator uses the `eligible` API from `Spree::Adjustment`, which in a system with the new promotions system will not be necessary. This hasn't been caught before because deprecation warnings did not raise until https://github.com/solidusio/solidus/pull/5813 was merged. --- .../calculator/returns/default_refund_amount.rb | 2 +- ...lator_returns_default_refund_amount_decorator.rb | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 legacy_promotions/app/decorators/solidus_legacy_promotions/models/spree_calculator_returns_default_refund_amount_decorator.rb diff --git a/core/app/models/spree/calculator/returns/default_refund_amount.rb b/core/app/models/spree/calculator/returns/default_refund_amount.rb index b92da1fd105..fc3833560fc 100644 --- a/core/app/models/spree/calculator/returns/default_refund_amount.rb +++ b/core/app/models/spree/calculator/returns/default_refund_amount.rb @@ -14,7 +14,7 @@ def compute(return_item) private def weighted_order_adjustment_amount(inventory_unit) - inventory_unit.order.adjustments.eligible.non_tax.sum(:amount) * percentage_of_order_total(inventory_unit) + inventory_unit.order.adjustments.non_tax.sum(:amount) * percentage_of_order_total(inventory_unit) end def weighted_line_item_amount(inventory_unit) diff --git a/legacy_promotions/app/decorators/solidus_legacy_promotions/models/spree_calculator_returns_default_refund_amount_decorator.rb b/legacy_promotions/app/decorators/solidus_legacy_promotions/models/spree_calculator_returns_default_refund_amount_decorator.rb new file mode 100644 index 00000000000..0a1726a9c6c --- /dev/null +++ b/legacy_promotions/app/decorators/solidus_legacy_promotions/models/spree_calculator_returns_default_refund_amount_decorator.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module SolidusLegacyPromotions + module SpreeCalculatorReturnsDefaultRefundAmountDecorator + private + + def weighted_order_adjustment_amount(inventory_unit) + inventory_unit.order.adjustments.eligible.non_tax.sum(:amount) * percentage_of_order_total(inventory_unit) + end + + Spree::Calculator::Returns::DefaultRefundAmount.prepend self + end +end From d45a722fbfea9e3b75cbb82fb67eef7aa50f745c Mon Sep 17 00:00:00 2001 From: DemoYeti <164791169+DemoYeti@users.noreply.github.com> Date: Mon, 2 Sep 2024 12:50:21 -0400 Subject: [PATCH 136/834] Add missing `the` to README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2736aa2d6b2..a1aeb80640c 100644 --- a/README.md +++ b/README.md @@ -226,7 +226,7 @@ docker-compose build --build-arg RUBY_VERSION=3.0 app docker-compose up -d ``` -The rails version can be customized at runtime through `RAILS_VERSION` environment variable: +The rails version can be customized at runtime through the `RAILS_VERSION` environment variable: ```bash RAILS_VERSION='~> 5.0' docker-compose up -d From 78c4ad1056a1c1ed4a606189a20d3ca9176cf63a Mon Sep 17 00:00:00 2001 From: Madeline Collier Date: Fri, 30 Aug 2024 15:37:37 +0200 Subject: [PATCH 137/834] Remove :type from permitted role params This can be re-added whenever we get around to adding a migration to add it to the spree_roles table. For now all we need is the :name & :description. --- admin/app/controllers/solidus_admin/roles_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/admin/app/controllers/solidus_admin/roles_controller.rb b/admin/app/controllers/solidus_admin/roles_controller.rb index cba1b7bd8ef..0fa1c16044b 100644 --- a/admin/app/controllers/solidus_admin/roles_controller.rb +++ b/admin/app/controllers/solidus_admin/roles_controller.rb @@ -112,7 +112,7 @@ def set_index_page end def role_params - params.require(:role).permit(:role_id, :name, :description, :type) + params.require(:role).permit(:role_id, :name, :description) end end end From abd82d8ee6ab578283f975b29f2fa6cf9b8531de Mon Sep 17 00:00:00 2001 From: Madeline Collier Date: Fri, 30 Aug 2024 15:41:26 +0200 Subject: [PATCH 138/834] Normalize quotes in admin translation files --- .../app/components/solidus_admin/roles/index/component.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/admin/app/components/solidus_admin/roles/index/component.yml b/admin/app/components/solidus_admin/roles/index/component.yml index 3934c0825e9..5a9eabd3f0d 100644 --- a/admin/app/components/solidus_admin/roles/index/component.yml +++ b/admin/app/components/solidus_admin/roles/index/component.yml @@ -1,6 +1,6 @@ en: batch_actions: - delete: 'Delete' + delete: "Delete" scopes: - admin: Admin - all: All + admin: "Admin" + all: "All" From 275321ba29d55cf07e6066340b43122679964c0f Mon Sep 17 00:00:00 2001 From: Madeline Collier Date: Fri, 30 Aug 2024 15:41:50 +0200 Subject: [PATCH 139/834] Update Spree::Role admin UI with descriptions In a separate PR, descriptions were added to the spree_roles table, and validations were added so that names are now required to be present & unique. The admin specs now reflect that reality and the UI has been updated to display descriptions. --- .../roles/edit/component.html.erb | 1 + .../solidus_admin/roles/index/component.rb | 4 +++ .../roles/new/component.html.erb | 1 + admin/spec/features/roles_spec.rb | 30 +++++++++++++------ .../spec/requests/solidus_admin/roles_spec.rb | 8 ++--- 5 files changed, 31 insertions(+), 13 deletions(-) diff --git a/admin/app/components/solidus_admin/roles/edit/component.html.erb b/admin/app/components/solidus_admin/roles/edit/component.html.erb index 541df92c402..1d0ead5bcfa 100644 --- a/admin/app/components/solidus_admin/roles/edit/component.html.erb +++ b/admin/app/components/solidus_admin/roles/edit/component.html.erb @@ -3,6 +3,7 @@ <%= form_for @role, url: solidus_admin.role_path(@role), html: { id: form_id } do |f| %>
<%= render component("ui/forms/field").text_field(f, :name, class: "required") %> + <%= render component("ui/forms/field").text_field(f, :description) %>
<% modal.with_actions do %>
diff --git a/admin/app/components/solidus_admin/roles/index/component.rb b/admin/app/components/solidus_admin/roles/index/component.rb index 63ac5c15923..c5c4e92e297 100644 --- a/admin/app/components/solidus_admin/roles/index/component.rb +++ b/admin/app/components/solidus_admin/roles/index/component.rb @@ -60,6 +60,10 @@ def columns { header: :role, data: :name, + }, + { + header: :description, + data: :description, } ] end diff --git a/admin/app/components/solidus_admin/roles/new/component.html.erb b/admin/app/components/solidus_admin/roles/new/component.html.erb index dcd5d78d38c..3d49f4f987a 100644 --- a/admin/app/components/solidus_admin/roles/new/component.html.erb +++ b/admin/app/components/solidus_admin/roles/new/component.html.erb @@ -3,6 +3,7 @@ <%= form_for @role, url: solidus_admin.roles_path, html: { id: form_id } do |f| %>
<%= render component("ui/forms/field").text_field(f, :name, class: "required") %> + <%= render component("ui/forms/field").text_field(f, :description) %>
<% modal.with_actions do %> diff --git a/admin/spec/features/roles_spec.rb b/admin/spec/features/roles_spec.rb index 2b08dab450b..c2843d84816 100644 --- a/admin/spec/features/roles_spec.rb +++ b/admin/spec/features/roles_spec.rb @@ -49,6 +49,7 @@ context "with valid data" do it "successfully creates a new role, keeping page and q params" do fill_in "Name", with: "Purchaser" + fill_in "Description", with: "A person who buys stuff" click_on "Add Role" @@ -59,18 +60,27 @@ end context "with invalid data" do - # @note: The only validation that Roles currently have is that names must - # be unique (but they can still be blank). - before do - create(:role, name: "Customer Role" ) + context "with a non-unique name" do + before do + create(:role, name: "Customer Role" ) + end + + it "fails to create a new role, keeping page and q params" do + fill_in "Name", with: "Customer Role" + click_on "Add Role" + + expect(page).to have_content("has already been taken") + expect(page.current_url).to include(query) + end end - it "fails to create a new role, keeping page and q params" do - fill_in "Name", with: "Customer Role" - click_on "Add Role" + context "with no name" do + it "fails to create a new role, keeping page and q params" do + click_on "Add Role" - expect(page).to have_content("has already been taken") - expect(page.current_url).to include(query) + expect(page).to have_content("can't be blank") + expect(page.current_url).to include(query) + end end end end @@ -95,10 +105,12 @@ it "successfully updates the existing role" do fill_in "Name", with: "Publisher" + fill_in "Description", with: "A person who publishes stuff" click_on "Update Role" expect(page).to have_content("Role was successfully updated.") expect(page).to have_content("Publisher") + expect(page).to have_content("A person who publishes stuff") expect(page).not_to have_content("Reviewer") expect(Spree::Role.find_by(name: "Publisher")).to be_present expect(page.current_url).to include(query) diff --git a/admin/spec/requests/solidus_admin/roles_spec.rb b/admin/spec/requests/solidus_admin/roles_spec.rb index 95e57c6cb63..43b96560334 100644 --- a/admin/spec/requests/solidus_admin/roles_spec.rb +++ b/admin/spec/requests/solidus_admin/roles_spec.rb @@ -27,7 +27,7 @@ describe "POST /create" do context "with valid parameters" do - let(:valid_attributes) { { name: "Customer" } } + let(:valid_attributes) { { name: "Customer", description: "A person who buys stuff" } } it "creates a new Role" do expect { @@ -49,7 +49,7 @@ end context "with invalid parameters" do - let(:invalid_attributes) { { name: "admin" } } + let(:invalid_attributes) { { name: "" } } it "does not create a new Role" do expect { @@ -73,7 +73,7 @@ describe "PATCH /update" do context "with valid parameters" do - let(:valid_attributes) { { name: "Publisher" } } + let(:valid_attributes) { { name: "Publisher", description: "A person who publishes stuff" } } it "updates the role" do patch solidus_admin.role_path(role), params: { role: valid_attributes } @@ -95,7 +95,7 @@ end context "with invalid parameters" do - let(:invalid_attributes) { { name: "admin" } } + let(:invalid_attributes) { { name: "" } } it "does not update the role" do original_name = role.name From ba1081b1f97bad8a8e92a89fb696660675fe2066 Mon Sep 17 00:00:00 2001 From: Madeline Collier Date: Fri, 6 Sep 2024 15:53:26 +0200 Subject: [PATCH 140/834] Add PermissionSet, RolePermission models This is building off of the new migrations in https://github.com/solidusio/solidus/pull/5833 These originate from `solidus_user_roles`. We intend to migrate some of the functionality from that extension into `core`, and this is the (second) step. Co-authored-by: benjamin wil --- core/app/models/spree/permission_set.rb | 11 +++++++++++ core/app/models/spree/role.rb | 2 ++ core/app/models/spree/role_permission.rb | 8 ++++++++ 3 files changed, 21 insertions(+) create mode 100644 core/app/models/spree/permission_set.rb create mode 100644 core/app/models/spree/role_permission.rb diff --git a/core/app/models/spree/permission_set.rb b/core/app/models/spree/permission_set.rb new file mode 100644 index 00000000000..10682e266ec --- /dev/null +++ b/core/app/models/spree/permission_set.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Spree + class PermissionSet < Spree::Base + has_many :role_permissions + has_many :roles, through: :role_permissions + validates :name, :set, presence: true + scope :display_permissions, -> { where('name LIKE ?', '%Display') } + scope :management_permissions, -> { where('name LIKE ?', '%Management') } + end +end diff --git a/core/app/models/spree/role.rb b/core/app/models/spree/role.rb index 6d140ed6ef0..2e42435a1c1 100644 --- a/core/app/models/spree/role.rb +++ b/core/app/models/spree/role.rb @@ -4,6 +4,8 @@ module Spree class Role < Spree::Base has_many :role_users, class_name: "Spree::RoleUser", dependent: :destroy has_many :users, through: :role_users + has_many :role_permissions, dependent: :destroy + has_many :permission_sets, through: :role_permissions validates :name, presence: true, uniqueness: { case_sensitive: true, allow_blank: true } diff --git a/core/app/models/spree/role_permission.rb b/core/app/models/spree/role_permission.rb new file mode 100644 index 00000000000..a194df5b2f0 --- /dev/null +++ b/core/app/models/spree/role_permission.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +module Spree + class RolePermission < Spree::Base + belongs_to :role + belongs_to :permission_set + end +end From 80a900a2ba3d0f901758235327a86b5d3e5b1a76 Mon Sep 17 00:00:00 2001 From: Madeline Collier Date: Fri, 6 Sep 2024 15:54:58 +0200 Subject: [PATCH 141/834] Add privilege & category to PermissionSets Later commits proved the need for an easier method of identifying and categorizing permission sets more cleanly on the front-end, which is why we added the category & privilege columns. Each subclass (eg. `Spree::PermissionSets::ConfigurationDisplay`) has its own privilege and category, which is then used to build the instance (Spree::PermissionSet) with the correct identifying features. --- core/app/models/spree/permission_set.rb | 6 +-- core/db/default/spree/permission_sets.rb | 10 +++++ ...e_and_category_to_spree_permission_sets.rb | 6 +++ core/db/seeds.rb | 1 + core/lib/spree/permission_sets/base.rb | 14 +++++- .../permission_sets/configuration_display.rb | 10 +++++ .../configuration_management.rb | 10 +++++ .../permission_sets/dashboard_display.rb | 10 +++++ .../spree/permission_sets/default_customer.rb | 10 +++++ .../spree/permission_sets/order_display.rb | 10 +++++ .../spree/permission_sets/order_management.rb | 10 +++++ .../spree/permission_sets/product_display.rb | 10 +++++ .../permission_sets/product_management.rb | 10 +++++ .../restricted_stock_display.rb | 10 +++++ .../restricted_stock_management.rb | 10 +++++ .../spree/permission_sets/stock_display.rb | 10 +++++ .../spree/permission_sets/stock_management.rb | 10 +++++ core/lib/spree/permission_sets/super_user.rb | 10 +++++ .../lib/spree/permission_sets/user_display.rb | 10 +++++ .../spree/permission_sets/user_management.rb | 10 +++++ .../models/spree/permission_sets/base_spec.rb | 45 +++++++++++++++++++ .../permission_sets/configuration_display.rb | 12 +++++ .../configuration_management_spec.rb | 12 +++++ .../permission_sets/dashboard_display_spec.rb | 12 +++++ .../permission_sets/default_customer_spec.rb | 18 ++++++++ .../permission_sets/order_display_spec.rb | 12 +++++ .../permission_sets/order_management_spec.rb | 12 +++++ .../permission_sets/product_display_spec.rb | 12 +++++ .../product_management_spec.rb | 12 +++++ .../restricted_stock_display_spec.rb | 12 +++++ .../restricted_stock_management_spec.rb | 12 +++++ .../permission_sets/stock_display_spec.rb | 12 +++++ .../permission_sets/stock_management_spec.rb | 12 +++++ .../spree/permission_sets/super_user_spec.rb | 22 +++++++++ .../permission_sets/user_display_spec.rb | 12 +++++ .../permission_sets/user_management_spec.rb | 12 +++++ 36 files changed, 424 insertions(+), 4 deletions(-) create mode 100644 core/db/default/spree/permission_sets.rb create mode 100644 core/db/migrate/20240904152041_add_privilege_and_category_to_spree_permission_sets.rb create mode 100644 core/spec/models/spree/permission_sets/default_customer_spec.rb create mode 100644 core/spec/models/spree/permission_sets/super_user_spec.rb diff --git a/core/app/models/spree/permission_set.rb b/core/app/models/spree/permission_set.rb index 10682e266ec..10b091c1d47 100644 --- a/core/app/models/spree/permission_set.rb +++ b/core/app/models/spree/permission_set.rb @@ -4,8 +4,8 @@ module Spree class PermissionSet < Spree::Base has_many :role_permissions has_many :roles, through: :role_permissions - validates :name, :set, presence: true - scope :display_permissions, -> { where('name LIKE ?', '%Display') } - scope :management_permissions, -> { where('name LIKE ?', '%Management') } + validates :name, :set, :privilege, :category, presence: true + scope :display_permissions, -> { where(privilege: "display") } + scope :management_permissions, -> { where(privilege: "management") } end end diff --git a/core/db/default/spree/permission_sets.rb b/core/db/default/spree/permission_sets.rb new file mode 100644 index 00000000000..de195d3302c --- /dev/null +++ b/core/db/default/spree/permission_sets.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +Spree::PermissionSets::Base.subclasses.each do |permission| + Spree::PermissionSet.create!( + name: permission.name.demodulize, + set: permission.name, + privilege: permission.privilege, + category: permission.category + ) +end diff --git a/core/db/migrate/20240904152041_add_privilege_and_category_to_spree_permission_sets.rb b/core/db/migrate/20240904152041_add_privilege_and_category_to_spree_permission_sets.rb new file mode 100644 index 00000000000..74676fb4253 --- /dev/null +++ b/core/db/migrate/20240904152041_add_privilege_and_category_to_spree_permission_sets.rb @@ -0,0 +1,6 @@ +class AddPrivilegeAndCategoryToSpreePermissionSets < ActiveRecord::Migration[7.0] + def change + add_column :spree_permission_sets, :privilege, :string + add_column :spree_permission_sets, :category, :string + end +end diff --git a/core/db/seeds.rb b/core/db/seeds.rb index e3d0b75d327..3196cf016b2 100644 --- a/core/db/seeds.rb +++ b/core/db/seeds.rb @@ -13,6 +13,7 @@ zones refund_reasons roles + permission_sets shipping_categories ).each do |seed| shell.say_status :seed, seed diff --git a/core/lib/spree/permission_sets/base.rb b/core/lib/spree/permission_sets/base.rb index ba03880bb1d..586c21442e4 100644 --- a/core/lib/spree/permission_sets/base.rb +++ b/core/lib/spree/permission_sets/base.rb @@ -18,11 +18,23 @@ def initialize(ability) end # Activate permissions on the ability. Put your can and cannot statements here. - # Must be overriden by subclasses + # Must be overridden by subclasses def activate! raise NotImplementedError.new end + # Provide the permission set privilege in the form of a :symbol. + # Must be overridden by subclasses. + def self.privilege + raise NotImplementedError, "Subclass #{name} must define a privilege using `self.privilege :symbol`" + end + + # Provide the permission set category in the form of a :symbol. + # Must be overridden by subclasses. + def self.category + raise NotImplementedError, "Subclass #{name} must define a category using `self.category :symbol`" + end + private attr_reader :ability diff --git a/core/lib/spree/permission_sets/configuration_display.rb b/core/lib/spree/permission_sets/configuration_display.rb index 0b1b5bd897d..a6b2e46cabc 100644 --- a/core/lib/spree/permission_sets/configuration_display.rb +++ b/core/lib/spree/permission_sets/configuration_display.rb @@ -22,6 +22,16 @@ module PermissionSets # - Reimbursement types # - Return reasons class ConfigurationDisplay < PermissionSets::Base + class << self + def privilege + :display + end + + def category + :configuration + end + end + def activate! can [:read, :admin], Spree::TaxCategory can [:read, :admin], Spree::TaxRate diff --git a/core/lib/spree/permission_sets/configuration_management.rb b/core/lib/spree/permission_sets/configuration_management.rb index f305d8878c7..56d0946d7e4 100644 --- a/core/lib/spree/permission_sets/configuration_management.rb +++ b/core/lib/spree/permission_sets/configuration_management.rb @@ -21,6 +21,16 @@ module PermissionSets # - Reimbursement types # - Return reasons class ConfigurationManagement < PermissionSets::Base + class << self + def privilege + :management + end + + def category + :configuration + end + end + def activate! can :manage, Spree::TaxCategory can :manage, Spree::TaxRate diff --git a/core/lib/spree/permission_sets/dashboard_display.rb b/core/lib/spree/permission_sets/dashboard_display.rb index ce8c66815b1..ce268973306 100644 --- a/core/lib/spree/permission_sets/dashboard_display.rb +++ b/core/lib/spree/permission_sets/dashboard_display.rb @@ -8,6 +8,16 @@ module PermissionSets # which may or not contain sensitive information depending on # customizations. class DashboardDisplay < PermissionSets::Base + class << self + def privilege + :other + end + + def category + :dashboard_display + end + end + def activate! can [:admin, :home], :dashboards end diff --git a/core/lib/spree/permission_sets/default_customer.rb b/core/lib/spree/permission_sets/default_customer.rb index be50238536c..4de18ce3420 100644 --- a/core/lib/spree/permission_sets/default_customer.rb +++ b/core/lib/spree/permission_sets/default_customer.rb @@ -32,6 +32,16 @@ module PermissionSets # with the same user, as well as reading and updating the user record and # their associated cards. class DefaultCustomer < PermissionSets::Base + class << self + def privilege + :other + end + + def category + :default_customer + end + end + def activate! can :read, Country can :read, OptionType diff --git a/core/lib/spree/permission_sets/order_display.rb b/core/lib/spree/permission_sets/order_display.rb index 471c6ca5239..b72e939df0f 100644 --- a/core/lib/spree/permission_sets/order_display.rb +++ b/core/lib/spree/permission_sets/order_display.rb @@ -22,6 +22,16 @@ module PermissionSets # However, it does not allow any modifications to be made to any of these # resources. class OrderDisplay < PermissionSets::Base + class << self + def privilege + :display + end + + def category + :order + end + end + def activate! can [:read, :admin, :edit, :cart], Spree::Order can [:read, :admin], Spree::Payment diff --git a/core/lib/spree/permission_sets/order_management.rb b/core/lib/spree/permission_sets/order_management.rb index bd5638ec6e9..9b29cf7743d 100644 --- a/core/lib/spree/permission_sets/order_management.rb +++ b/core/lib/spree/permission_sets/order_management.rb @@ -21,6 +21,16 @@ module PermissionSets # # It also allows reading reimbursement types, but not modifying them. class OrderManagement < PermissionSets::Base + class << self + def privilege + :management + end + + def category + :order + end + end + def activate! can :read, Spree::ReimbursementType can :manage, Spree::Order diff --git a/core/lib/spree/permission_sets/product_display.rb b/core/lib/spree/permission_sets/product_display.rb index f733fbe5817..d6d281bca60 100644 --- a/core/lib/spree/permission_sets/product_display.rb +++ b/core/lib/spree/permission_sets/product_display.rb @@ -17,6 +17,16 @@ module PermissionSets # - Taxonomies # - Taxons class ProductDisplay < PermissionSets::Base + class << self + def privilege + :display + end + + def category + :product + end + end + def activate! can [:read, :admin, :edit], Spree::Product can [:read, :admin], Spree::Image diff --git a/core/lib/spree/permission_sets/product_management.rb b/core/lib/spree/permission_sets/product_management.rb index 27eabf5c747..c919eef3811 100644 --- a/core/lib/spree/permission_sets/product_management.rb +++ b/core/lib/spree/permission_sets/product_management.rb @@ -19,6 +19,16 @@ module PermissionSets # - Classifications # - Prices class ProductManagement < PermissionSets::Base + class << self + def privilege + :management + end + + def category + :product + end + end + def activate! can :manage, Spree::Classification can :manage, Spree::Image diff --git a/core/lib/spree/permission_sets/restricted_stock_display.rb b/core/lib/spree/permission_sets/restricted_stock_display.rb index 5a36e4f4e60..9bb2137f12c 100644 --- a/core/lib/spree/permission_sets/restricted_stock_display.rb +++ b/core/lib/spree/permission_sets/restricted_stock_display.rb @@ -8,6 +8,16 @@ module PermissionSets # locations, both of them limited to locations they have access to. # Permissions are also granted for the admin panel for items. class RestrictedStockDisplay < PermissionSets::Base + class << self + def privilege + :display + end + + def category + :restricted_stock + end + end + def activate! can [:read, :admin], Spree::StockItem, stock_location_id: location_ids can :read, Spree::StockLocation, id: location_ids diff --git a/core/lib/spree/permission_sets/restricted_stock_management.rb b/core/lib/spree/permission_sets/restricted_stock_management.rb index 9f2a33489b2..358f5bf9ab4 100644 --- a/core/lib/spree/permission_sets/restricted_stock_management.rb +++ b/core/lib/spree/permission_sets/restricted_stock_management.rb @@ -8,6 +8,16 @@ module PermissionSets # access to their locations. Those locations are also readable by the # corresponding ability. class RestrictedStockManagement < PermissionSets::Base + class << self + def privilege + :management + end + + def category + :restricted_stock + end + end + def activate! can :manage, Spree::StockItem, stock_location_id: location_ids can :read, Spree::StockLocation, id: location_ids diff --git a/core/lib/spree/permission_sets/stock_display.rb b/core/lib/spree/permission_sets/stock_display.rb index 27f1288eb9a..a7b1ae0a104 100644 --- a/core/lib/spree/permission_sets/stock_display.rb +++ b/core/lib/spree/permission_sets/stock_display.rb @@ -7,6 +7,16 @@ module PermissionSets # This permission set allows users to view information about stock items # (also from the admin panel) and stock locations. class StockDisplay < PermissionSets::Base + class << self + def privilege + :display + end + + def category + :stock + end + end + def activate! can [:read, :admin], Spree::StockItem can :read, Spree::StockLocation diff --git a/core/lib/spree/permission_sets/stock_management.rb b/core/lib/spree/permission_sets/stock_management.rb index 982b2a1a11b..c869a82d5e5 100644 --- a/core/lib/spree/permission_sets/stock_management.rb +++ b/core/lib/spree/permission_sets/stock_management.rb @@ -7,6 +7,16 @@ module PermissionSets # This permission set grants full control over all stock items and read # access to locations. class StockManagement < PermissionSets::Base + class << self + def privilege + :management + end + + def category + :stock + end + end + def activate! can :manage, Spree::StockItem can :read, Spree::StockLocation diff --git a/core/lib/spree/permission_sets/super_user.rb b/core/lib/spree/permission_sets/super_user.rb index f40e6947254..d2f1ff7e1bb 100644 --- a/core/lib/spree/permission_sets/super_user.rb +++ b/core/lib/spree/permission_sets/super_user.rb @@ -8,6 +8,16 @@ module PermissionSets # # It grants permission to perform any read or write action on any resource. class SuperUser < PermissionSets::Base + class << self + def privilege + :other + end + + def category + :super_user + end + end + def activate! can :manage, :all end diff --git a/core/lib/spree/permission_sets/user_display.rb b/core/lib/spree/permission_sets/user_display.rb index 0ff154ee2af..793b827207e 100644 --- a/core/lib/spree/permission_sets/user_display.rb +++ b/core/lib/spree/permission_sets/user_display.rb @@ -7,6 +7,16 @@ module PermissionSets # This permission set allows users to view all related information about # users, roles and store credits, also from the admin panel. class UserDisplay < PermissionSets::Base + class << self + def privilege + :display + end + + def category + :user + end + end + def activate! can [:read, :admin, :edit, :addresses, :orders, :items], Spree.user_class can [:read, :admin], Spree::StoreCredit diff --git a/core/lib/spree/permission_sets/user_management.rb b/core/lib/spree/permission_sets/user_management.rb index 4e241c467cc..2633204069d 100644 --- a/core/lib/spree/permission_sets/user_management.rb +++ b/core/lib/spree/permission_sets/user_management.rb @@ -12,6 +12,16 @@ module PermissionSets # - Roles # - API keys class UserManagement < PermissionSets::Base + class << self + def privilege + :management + end + + def category + :user + end + end + def activate! can [:admin, :read, :create, :update, :save_in_address_book, :remove_from_address_book, :addresses, :orders, :items], Spree.user_class diff --git a/core/spec/models/spree/permission_sets/base_spec.rb b/core/spec/models/spree/permission_sets/base_spec.rb index e949cfb102b..87ce6ad7581 100644 --- a/core/spec/models/spree/permission_sets/base_spec.rb +++ b/core/spec/models/spree/permission_sets/base_spec.rb @@ -11,4 +11,49 @@ expect{ subject }.to raise_error(NotImplementedError) end end + + context "when the subclass does not define a privilege" do + # Define a subclass that does not override privilege + before do + stub_const('Spree::PermissionSets::ExampleSubclass', Class.new(Spree::PermissionSets::Base)) + end + + it "raises a NotImplementedError" do + expect { Spree::PermissionSets::ExampleSubclass.privilege }.to raise_error(NotImplementedError, /must define a privilege/) + end + end + + context "when the subclass does not define a category" do + # Define a subclass that does not override category + before do + stub_const('Spree::PermissionSets::ExampleSubclass', Class.new(Spree::PermissionSets::Base)) + end + + it "raises a NotImplementedError" do + expect { Spree::PermissionSets::ExampleSubclass.category }.to raise_error(NotImplementedError, /must define a category/) + end + end + + context "when the subclass defines privilege and category correctly" do + # Define a subclass that correctly overrides privilege and category + before do + stub_const('Spree::PermissionSets::ValidSubclass', Class.new(Spree::PermissionSets::Base) do + def self.privilege + :valid_privilege + end + + def self.category + :valid_category + end + end) + end + + it "returns the correct privilege" do + expect(Spree::PermissionSets::ValidSubclass.privilege).to eq(:valid_privilege) + end + + it "returns the correct category" do + expect(Spree::PermissionSets::ValidSubclass.category).to eq(:valid_category) + end + end end diff --git a/core/spec/models/spree/permission_sets/configuration_display.rb b/core/spec/models/spree/permission_sets/configuration_display.rb index 0489d1f8825..7b2b8fef1d9 100644 --- a/core/spec/models/spree/permission_sets/configuration_display.rb +++ b/core/spec/models/spree/permission_sets/configuration_display.rb @@ -73,4 +73,16 @@ it { is_expected.not_to be_able_to(:admin, Spree::ReimbursementType) } it { is_expected.not_to be_able_to(:admin, Spree::ReturnReason) } end + + describe ".privilege" do + it "returns the correct privilege symbol" do + expect(described_class.privilege).to eq(:display) + end + end + + describe ".category" do + it "returns the correct category symbol" do + expect(described_class.category).to eq(:configuration) + end + end end diff --git a/core/spec/models/spree/permission_sets/configuration_management_spec.rb b/core/spec/models/spree/permission_sets/configuration_management_spec.rb index fdb26475b1c..43445d94273 100644 --- a/core/spec/models/spree/permission_sets/configuration_management_spec.rb +++ b/core/spec/models/spree/permission_sets/configuration_management_spec.rb @@ -45,4 +45,16 @@ it { is_expected.not_to be_able_to(:manage, Spree::ReimbursementType) } it { is_expected.not_to be_able_to(:manage, Spree::ReturnReason) } end + + describe ".privilege" do + it "returns the correct privilege symbol" do + expect(described_class.privilege).to eq(:management) + end + end + + describe ".category" do + it "returns the correct category symbol" do + expect(described_class.category).to eq(:configuration) + end + end end diff --git a/core/spec/models/spree/permission_sets/dashboard_display_spec.rb b/core/spec/models/spree/permission_sets/dashboard_display_spec.rb index e60279eec6d..b6a76cad080 100644 --- a/core/spec/models/spree/permission_sets/dashboard_display_spec.rb +++ b/core/spec/models/spree/permission_sets/dashboard_display_spec.rb @@ -21,4 +21,16 @@ it { is_expected.not_to be_able_to(:admin, :dashboards) } it { is_expected.not_to be_able_to(:home, :dashboards) } end + + describe ".privilege" do + it "returns the correct privilege symbol" do + expect(described_class.privilege).to eq(:other) + end + end + + describe ".category" do + it "returns the correct category symbol" do + expect(described_class.category).to eq(:dashboard_display) + end + end end diff --git a/core/spec/models/spree/permission_sets/default_customer_spec.rb b/core/spec/models/spree/permission_sets/default_customer_spec.rb new file mode 100644 index 00000000000..7d9cf4a5480 --- /dev/null +++ b/core/spec/models/spree/permission_sets/default_customer_spec.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require 'rails_helper' +require 'spree/testing_support/dummy_ability' + +RSpec.describe Spree::PermissionSets::DefaultCustomer do + describe ".privilege" do + it "returns the correct privilege symbol" do + expect(described_class.privilege).to eq(:other) + end + end + + describe ".category" do + it "returns the correct category symbol" do + expect(described_class.category).to eq(:default_customer) + end + end +end diff --git a/core/spec/models/spree/permission_sets/order_display_spec.rb b/core/spec/models/spree/permission_sets/order_display_spec.rb index c6b799ae055..f057afef6b6 100644 --- a/core/spec/models/spree/permission_sets/order_display_spec.rb +++ b/core/spec/models/spree/permission_sets/order_display_spec.rb @@ -54,4 +54,16 @@ it { is_expected.not_to be_able_to(:read, Spree::ReturnItem) } it { is_expected.not_to be_able_to(:read, Spree::Refund) } end + + describe ".privilege" do + it "returns the correct privilege symbol" do + expect(described_class.privilege).to eq(:display) + end + end + + describe ".category" do + it "returns the correct category symbol" do + expect(described_class.category).to eq(:order) + end + end end diff --git a/core/spec/models/spree/permission_sets/order_management_spec.rb b/core/spec/models/spree/permission_sets/order_management_spec.rb index 11a9bcf4cdb..7f984cfb525 100644 --- a/core/spec/models/spree/permission_sets/order_management_spec.rb +++ b/core/spec/models/spree/permission_sets/order_management_spec.rb @@ -41,4 +41,16 @@ it { is_expected.not_to be_able_to(:manage, Spree::ReturnItem) } it { is_expected.not_to be_able_to(:manage, Spree::Refund) } end + + describe ".privilege" do + it "returns the correct privilege symbol" do + expect(described_class.privilege).to eq(:management) + end + end + + describe ".category" do + it "returns the correct category symbol" do + expect(described_class.category).to eq(:order) + end + end end diff --git a/core/spec/models/spree/permission_sets/product_display_spec.rb b/core/spec/models/spree/permission_sets/product_display_spec.rb index 45080f8e176..7c3c7dae894 100644 --- a/core/spec/models/spree/permission_sets/product_display_spec.rb +++ b/core/spec/models/spree/permission_sets/product_display_spec.rb @@ -55,4 +55,16 @@ it { is_expected.not_to be_able_to(:admin, Spree::Taxon) } it { is_expected.not_to be_able_to(:edit, Spree::Product) } end + + describe ".privilege" do + it "returns the correct privilege symbol" do + expect(described_class.privilege).to eq(:display) + end + end + + describe ".category" do + it "returns the correct category symbol" do + expect(described_class.category).to eq(:product) + end + end end diff --git a/core/spec/models/spree/permission_sets/product_management_spec.rb b/core/spec/models/spree/permission_sets/product_management_spec.rb index 9c533c04fe2..a867b2683df 100644 --- a/core/spec/models/spree/permission_sets/product_management_spec.rb +++ b/core/spec/models/spree/permission_sets/product_management_spec.rb @@ -37,4 +37,16 @@ it { is_expected.not_to be_able_to(:manage, Spree::Taxon) } it { is_expected.not_to be_able_to(:manage, Spree::Classification) } end + + describe ".privilege" do + it "returns the correct privilege symbol" do + expect(described_class.privilege).to eq(:management) + end + end + + describe ".category" do + it "returns the correct category symbol" do + expect(described_class.category).to eq(:product) + end + end end diff --git a/core/spec/models/spree/permission_sets/restricted_stock_display_spec.rb b/core/spec/models/spree/permission_sets/restricted_stock_display_spec.rb index 27da28f35fc..c84cb4da7ac 100644 --- a/core/spec/models/spree/permission_sets/restricted_stock_display_spec.rb +++ b/core/spec/models/spree/permission_sets/restricted_stock_display_spec.rb @@ -39,4 +39,16 @@ it { is_expected.to_not be_able_to(:read, item1) } it { is_expected.to_not be_able_to(:read, item2) } end + + describe ".privilege" do + it "returns the correct privilege symbol" do + expect(described_class.privilege).to eq(:display) + end + end + + describe ".category" do + it "returns the correct category symbol" do + expect(described_class.category).to eq(:restricted_stock) + end + end end diff --git a/core/spec/models/spree/permission_sets/restricted_stock_management_spec.rb b/core/spec/models/spree/permission_sets/restricted_stock_management_spec.rb index 779fd3f5016..469fee07381 100644 --- a/core/spec/models/spree/permission_sets/restricted_stock_management_spec.rb +++ b/core/spec/models/spree/permission_sets/restricted_stock_management_spec.rb @@ -39,4 +39,16 @@ it { is_expected.to_not be_able_to(:manage, item1) } it { is_expected.to_not be_able_to(:manage, item2) } end + + describe ".privilege" do + it "returns the correct privilege symbol" do + expect(described_class.privilege).to eq(:management) + end + end + + describe ".category" do + it "returns the correct category symbol" do + expect(described_class.category).to eq(:restricted_stock) + end + end end diff --git a/core/spec/models/spree/permission_sets/stock_display_spec.rb b/core/spec/models/spree/permission_sets/stock_display_spec.rb index e5515b3f121..6ff71b15eb6 100644 --- a/core/spec/models/spree/permission_sets/stock_display_spec.rb +++ b/core/spec/models/spree/permission_sets/stock_display_spec.rb @@ -23,4 +23,16 @@ it { is_expected.not_to be_able_to(:admin, Spree::StockItem) } it { is_expected.not_to be_able_to(:read, Spree::StockLocation) } end + + describe ".privilege" do + it "returns the correct privilege symbol" do + expect(described_class.privilege).to eq(:display) + end + end + + describe ".category" do + it "returns the correct category symbol" do + expect(described_class.category).to eq(:stock) + end + end end diff --git a/core/spec/models/spree/permission_sets/stock_management_spec.rb b/core/spec/models/spree/permission_sets/stock_management_spec.rb index 97eec759977..cd2e99823f8 100644 --- a/core/spec/models/spree/permission_sets/stock_management_spec.rb +++ b/core/spec/models/spree/permission_sets/stock_management_spec.rb @@ -21,4 +21,16 @@ it { is_expected.not_to be_able_to(:manage, Spree::StockItem) } it { is_expected.not_to be_able_to(:show, Spree::StockLocation) } end + + describe ".privilege" do + it "returns the correct privilege symbol" do + expect(described_class.privilege).to eq(:management) + end + end + + describe ".category" do + it "returns the correct category symbol" do + expect(described_class.category).to eq(:stock) + end + end end diff --git a/core/spec/models/spree/permission_sets/super_user_spec.rb b/core/spec/models/spree/permission_sets/super_user_spec.rb new file mode 100644 index 00000000000..59a4eb90487 --- /dev/null +++ b/core/spec/models/spree/permission_sets/super_user_spec.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require 'rails_helper' +require 'spree/testing_support/dummy_ability' + +RSpec.describe Spree::PermissionSets::SuperUser do + let(:ability) { DummyAbility.new } + + subject { described_class.new(ability) } + + describe ".privilege" do + it "returns the correct privilege symbol" do + expect(described_class.privilege).to eq(:other) + end + end + + describe ".category" do + it "returns the correct category symbol" do + expect(described_class.category).to eq(:super_user) + end + end +end diff --git a/core/spec/models/spree/permission_sets/user_display_spec.rb b/core/spec/models/spree/permission_sets/user_display_spec.rb index 3adc806e4b5..af678043f30 100644 --- a/core/spec/models/spree/permission_sets/user_display_spec.rb +++ b/core/spec/models/spree/permission_sets/user_display_spec.rb @@ -37,4 +37,16 @@ it { is_expected.not_to be_able_to(:admin, Spree::StoreCredit) } it { is_expected.not_to be_able_to(:read, Spree::Role) } end + + describe ".privilege" do + it "returns the correct privilege symbol" do + expect(described_class.privilege).to eq(:display) + end + end + + describe ".category" do + it "returns the correct category symbol" do + expect(described_class.category).to eq(:user) + end + end end diff --git a/core/spec/models/spree/permission_sets/user_management_spec.rb b/core/spec/models/spree/permission_sets/user_management_spec.rb index d148b8c4ed5..8e14cec78aa 100644 --- a/core/spec/models/spree/permission_sets/user_management_spec.rb +++ b/core/spec/models/spree/permission_sets/user_management_spec.rb @@ -56,4 +56,16 @@ it { is_expected.not_to be_able_to(:manage, Spree::StoreCredit) } it { is_expected.not_to be_able_to(:read, Spree::Role) } end + + describe ".privilege" do + it "returns the correct privilege symbol" do + expect(described_class.privilege).to eq(:management) + end + end + + describe ".category" do + it "returns the correct category symbol" do + expect(described_class.category).to eq(:user) + end + end end From 63d1c2e9591cfb5e29a28d0707b8ae8f41e68da2 Mon Sep 17 00:00:00 2001 From: Madeline Collier Date: Thu, 5 Sep 2024 13:09:25 +0200 Subject: [PATCH 142/834] Add new CheckboxRow admin component While I am not sure how frequently this will be re-used, the fact that it comes up 14 times across the roles/new and roles/edit pages makes this extraction worthwhile. I also tried out a partial and a helper method previously but both approaches were messy and poorly architected. This should serve us much better in the long run and be easier to maintain. --- .../ui/checkbox_row/component.html.erb | 29 +++++++++++++++++++ .../ui/checkbox_row/component.rb | 11 +++++++ .../ui/checkbox_row/component_preview.rb | 10 +++++++ .../component_preview/overview.html.erb | 15 ++++++++++ .../ui/checkbox_row/component_spec.rb | 9 ++++++ 5 files changed, 74 insertions(+) create mode 100644 admin/app/components/solidus_admin/ui/checkbox_row/component.html.erb create mode 100644 admin/app/components/solidus_admin/ui/checkbox_row/component.rb create mode 100644 admin/spec/components/previews/solidus_admin/ui/checkbox_row/component_preview.rb create mode 100644 admin/spec/components/previews/solidus_admin/ui/checkbox_row/component_preview/overview.html.erb create mode 100644 admin/spec/components/solidus_admin/ui/checkbox_row/component_spec.rb diff --git a/admin/app/components/solidus_admin/ui/checkbox_row/component.html.erb b/admin/app/components/solidus_admin/ui/checkbox_row/component.html.erb new file mode 100644 index 00000000000..eea1b4c2ae1 --- /dev/null +++ b/admin/app/components/solidus_admin/ui/checkbox_row/component.html.erb @@ -0,0 +1,29 @@ +<% if @layout == :default && @options.present? %> + + <%= @row_title %> + + <% @options.each do |option| %> + + <% end %> + + +<% end %> + +<% if @layout == :subsection && @options.present? %> +
+
<%= @row_title %>
+
+ <% @options.each do |option| %> +
+ +
+ <% end %> +
+
+<% end %> diff --git a/admin/app/components/solidus_admin/ui/checkbox_row/component.rb b/admin/app/components/solidus_admin/ui/checkbox_row/component.rb new file mode 100644 index 00000000000..86908d09ca9 --- /dev/null +++ b/admin/app/components/solidus_admin/ui/checkbox_row/component.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class SolidusAdmin::UI::CheckboxRow::Component < SolidusAdmin::BaseComponent + def initialize(options:, row_title:, form:, method:, layout: :default) + @options = options + @row_title = row_title + @form = form + @method = method + @layout = layout + end +end diff --git a/admin/spec/components/previews/solidus_admin/ui/checkbox_row/component_preview.rb b/admin/spec/components/previews/solidus_admin/ui/checkbox_row/component_preview.rb new file mode 100644 index 00000000000..c8befd652ca --- /dev/null +++ b/admin/spec/components/previews/solidus_admin/ui/checkbox_row/component_preview.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +# @component "ui/checkbox_row" +class SolidusAdmin::UI::CheckboxRow::ComponentPreview < ViewComponent::Preview + include SolidusAdmin::Preview + + def overview + render_with_template + end +end diff --git a/admin/spec/components/previews/solidus_admin/ui/checkbox_row/component_preview/overview.html.erb b/admin/spec/components/previews/solidus_admin/ui/checkbox_row/component_preview/overview.html.erb new file mode 100644 index 00000000000..62d52f74e53 --- /dev/null +++ b/admin/spec/components/previews/solidus_admin/ui/checkbox_row/component_preview/overview.html.erb @@ -0,0 +1,15 @@ +
+
+ Default Layout +
+ + <%= render current_component.new(options: [{ label: "true", id: 1 }, { label: "false", id: 0 }], row_title: "I have flossed recently", form: ActionView::Helpers::FormBuilder.new(:object, Object.new, self, {}), method: "attribute", layout: :default) %> +
+ +
+
+ Subsection Layout +
+ + <%= render current_component.new(options: [{ label: "Pizza", id: 1 }, { label: "Pasta", id: 0 }, { label: "Soup", id: 3 }], row_title: "Favourite foods", form: ActionView::Helpers::FormBuilder.new(:object, Object.new, self, {}), method: "attribute", layout: :subsection) %> +
diff --git a/admin/spec/components/solidus_admin/ui/checkbox_row/component_spec.rb b/admin/spec/components/solidus_admin/ui/checkbox_row/component_spec.rb new file mode 100644 index 00000000000..ba8b2039860 --- /dev/null +++ b/admin/spec/components/solidus_admin/ui/checkbox_row/component_spec.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe SolidusAdmin::UI::CheckboxRow::Component, type: :component do + it "renders the overview preview" do + render_preview(:overview) + end +end From dec39cf76769aaea5565344e950fae8ab80da5e5 Mon Sep 17 00:00:00 2001 From: Madeline Collier Date: Mon, 2 Sep 2024 18:33:30 +0200 Subject: [PATCH 143/834] Add permission set management to roles admin Building off of the previous commit as well as the migrations that were added to core to pull in some of the key features of solidus_user_roles we can now allow admins to assign permission sets when creating a role. --- .../roles/new/component.html.erb | 19 +++- .../solidus_admin/roles/new/component.rb | 8 ++ .../solidus_admin/roles/new/component.yml | 13 +++ .../solidus_admin/roles_controller.rb | 2 +- .../solidus_admin/permission_sets_helper.rb | 32 +++++++ admin/spec/features/roles_spec.rb | 31 +++++- .../permission_sets_helper_spec.rb | 96 +++++++++++++++++++ 7 files changed, 197 insertions(+), 4 deletions(-) create mode 100644 admin/app/helpers/solidus_admin/permission_sets_helper.rb create mode 100644 admin/spec/helpers/solidus_admin/permission_sets_helper_spec.rb diff --git a/admin/app/components/solidus_admin/roles/new/component.html.erb b/admin/app/components/solidus_admin/roles/new/component.html.erb index 3d49f4f987a..92d44cd2d76 100644 --- a/admin/app/components/solidus_admin/roles/new/component.html.erb +++ b/admin/app/components/solidus_admin/roles/new/component.html.erb @@ -2,9 +2,24 @@ <%= render component("ui/modal").new(title: t(".title")) do |modal| %> <%= form_for @role, url: solidus_admin.roles_path, html: { id: form_id } do |f| %>
- <%= render component("ui/forms/field").text_field(f, :name, class: "required") %> - <%= render component("ui/forms/field").text_field(f, :description) %> + <%= render component("ui/forms/field").text_field(f, :name, class: "required", placeholder: t(".name_placeholder")) %> + <%= render component("ui/forms/field").text_field(f, :description, placeholder: t(".description_placeholder")) %>
+ + <% if permission_set_options.present? %> +

+ <%= t(".choose_permissions") %> +

+ <% end %> + + <%= render component("ui/checkbox_row").new(options: permission_set_options[:order], row_title: t('.orders'), form: f, method: :permission_set_ids) %> + <%= render component("ui/checkbox_row").new(options: permission_set_options[:product], row_title: t('.products'), form: f, method: :permission_set_ids) %> + <%= render component("ui/checkbox_row").new(options: permission_set_options[:stock], row_title: t('.stock'), form: f, method: :permission_set_ids) %> + <%= render component("ui/checkbox_row").new(options: permission_set_options[:user], row_title: t('.customers'), form: f, method: :permission_set_ids) %> + <%= render component("ui/checkbox_row").new(options: permission_set_options[:restricted_stock], row_title: t('.restricted_stock'), form: f, method: :permission_set_ids) %> + <%= render component("ui/checkbox_row").new(options: permission_set_options[:configuration], row_title: t('.settings'), form: f, method: :permission_set_ids) %> + <%= render component("ui/checkbox_row").new(options: permission_set_options[:other], row_title: t('.other'), form: f, method: :permission_set_ids, layout: :subsection) %> + <% modal.with_actions do %> <%= render component("ui/button").new(scheme: :secondary, text: t('.cancel')) %> diff --git a/admin/app/components/solidus_admin/roles/new/component.rb b/admin/app/components/solidus_admin/roles/new/component.rb index 8c849014bf4..3619452722e 100644 --- a/admin/app/components/solidus_admin/roles/new/component.rb +++ b/admin/app/components/solidus_admin/roles/new/component.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class SolidusAdmin::Roles::New::Component < SolidusAdmin::BaseComponent + include SolidusAdmin::PermissionSetsHelper + def initialize(page:, role:) @page = page @role = role @@ -9,4 +11,10 @@ def initialize(page:, role:) def form_id dom_id(@role, "#{stimulus_id}_new_role_form") end + + private + + def permission_set_options + @permission_set_options ||= organize_permissions(permission_sets: Spree::PermissionSet.all, view_label: t(".view"), edit_label: t(".edit")) + end end diff --git a/admin/app/components/solidus_admin/roles/new/component.yml b/admin/app/components/solidus_admin/roles/new/component.yml index 6c318631b74..897ca991648 100644 --- a/admin/app/components/solidus_admin/roles/new/component.yml +++ b/admin/app/components/solidus_admin/roles/new/component.yml @@ -4,3 +4,16 @@ en: title: "New Role" cancel: "Cancel" submit: "Add Role" + edit: "Edit" + view: "View" + name_placeholder: "Enter a clear role name" + description_placeholder: "Enter a brief description" + choose_permissions: "Choose permissions" + orders: "Orders" + products: "Products" + stock: "Stock" + restricted_stock: "Restricted Stock" + customers: "Customers" + promotions: "Promotions" + settings: "Settings" + other: "Other permissions" diff --git a/admin/app/controllers/solidus_admin/roles_controller.rb b/admin/app/controllers/solidus_admin/roles_controller.rb index 0fa1c16044b..fea6e2f5b0f 100644 --- a/admin/app/controllers/solidus_admin/roles_controller.rb +++ b/admin/app/controllers/solidus_admin/roles_controller.rb @@ -112,7 +112,7 @@ def set_index_page end def role_params - params.require(:role).permit(:role_id, :name, :description) + params.require(:role).permit(:role_id, :name, :description, permission_set_ids: []) end end end diff --git a/admin/app/helpers/solidus_admin/permission_sets_helper.rb b/admin/app/helpers/solidus_admin/permission_sets_helper.rb new file mode 100644 index 00000000000..78567bbf0ca --- /dev/null +++ b/admin/app/helpers/solidus_admin/permission_sets_helper.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module SolidusAdmin + module PermissionSetsHelper + # @param permission_sets [Array] an array of + # PermissionSet objects to be organized into categories based on their + # names. + # @param view_label [String] A string of your choice associated with "View" + # or "Display" level permissions. Used when rendering the checkbox. + # @param edit_label [String] A string of your choice associated with "Edit" + # or "Management" level permissions. Used when rendering the checkbox. + def organize_permissions(permission_sets:, view_label:, edit_label:) + return {} if permission_sets.blank? + + permission_sets.each_with_object({}) do |permission, grouped_permissions| + group_key = permission.category.to_sym + + case permission.privilege + when "display" + grouped_permissions[group_key] ||= [] + grouped_permissions[group_key] << { label: view_label, id: permission.id } + when "management" + grouped_permissions[group_key] ||= [] + grouped_permissions[group_key] << { label: edit_label, id: permission.id } + else + grouped_permissions[:other] ||= [] + grouped_permissions[:other] << { label: permission.name, id: permission.id } + end + end + end + end +end diff --git a/admin/spec/features/roles_spec.rb b/admin/spec/features/roles_spec.rb index c2843d84816..5fd88c399ed 100644 --- a/admin/spec/features/roles_spec.rb +++ b/admin/spec/features/roles_spec.rb @@ -3,7 +3,26 @@ require 'spec_helper' describe "Roles", :js, type: :feature do - before { sign_in create(:admin_user, email: 'admin@example.com') } + before do + sign_in create(:admin_user, email: 'admin@example.com') + end + + let!(:settings_edit_permission) { + Spree::PermissionSet.find_or_create_by!( + name: "ConfigurationManagement", + set: "Spree::PermissionSets::ConfigurationManagement", + privilege: "management", + category: "configuration" + ) + } + let!(:settings_view_permission) { + Spree::PermissionSet.find_or_create_by!( + name: "ConfigurationDisplay", + set: "Spree::PermissionSets::ConfigurationDisplay", + privilege: "display", + category: "configuration" + ) + } it "lists roles and allows deleting them" do create(:role, name: "Customer Role" ) @@ -51,10 +70,20 @@ fill_in "Name", with: "Purchaser" fill_in "Description", with: "A person who buys stuff" + within("form.new_role") do + expect(page).to have_content("Choose permissions") + expect(page).to have_content("Settings") + expect(page).to have_content("Edit") + expect(page).to have_content("View") + find('label', text: 'View').find('input[type=checkbox]').click + end + click_on "Add Role" expect(page).to have_content("Role was successfully created.") expect(Spree::Role.find_by(name: "Purchaser")).to be_present + expect(Spree::Role.find_by(name: "Purchaser").permission_set_ids) + .to contain_exactly(settings_view_permission.id) expect(page.current_url).to include(query) end end diff --git a/admin/spec/helpers/solidus_admin/permission_sets_helper_spec.rb b/admin/spec/helpers/solidus_admin/permission_sets_helper_spec.rb new file mode 100644 index 00000000000..235e0df669f --- /dev/null +++ b/admin/spec/helpers/solidus_admin/permission_sets_helper_spec.rb @@ -0,0 +1,96 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe SolidusAdmin::PermissionSetsHelper, :helper do + describe "#organize_permissions" do + let(:view_label) { "View" } + let(:edit_label) { "Edit" } + + let(:display_permission) do + Spree::PermissionSet.create!( + category: "sample_privilege", + privilege: "display", + name: "SampleDisplay", + set: "Spree::PermissionSet::SampleDisplay" + ) + end + + let(:management_permission) do + Spree::PermissionSet.create!( + category: "sample_privilege", + privilege: "management", + name: "SampleManagement", + set: "Spree::PermissionSet::SampleManagement" + ) + end + + let(:other_permission) do + Spree::PermissionSet.create!( + category: "sample_privilege", + privilege: "other", + name: "SampleOther", + set: "Spree::PermissionSet::SampleOther" + ) + end + + let(:permission_sets) { [display_permission, management_permission, other_permission] } + + context "when permission_sets are provided" do + it "organizes permissions into the correct categories with labels and IDs" do + result = helper.organize_permissions( + permission_sets: permission_sets, + view_label: view_label, + edit_label: edit_label + ) + + expect(result[:sample_privilege]).to contain_exactly( + hash_including(label: view_label, id: display_permission.id), + hash_including(label: edit_label, id: management_permission.id) + ) + + expect(result[:other]).to contain_exactly( + hash_including(label: "SampleOther", id: other_permission.id) + ) + end + + it "creates a hash with keys for each privilege and other" do + result = helper.organize_permissions( + permission_sets: permission_sets, + view_label: view_label, + edit_label: edit_label + ) + + expect(result.keys).to include(:sample_privilege, :other) + end + end + + context "when permission_sets are empty" do + let(:permission_sets) { [] } + + it "returns an empty hash" do + result = helper.organize_permissions( + permission_sets: permission_sets, + view_label: view_label, + edit_label: edit_label + ) + + expect(result).to eq({}) + end + end + + context "when permission_sets are nil" do + let(:permission_sets) { nil } + + it "returns an empty hash" do + result = helper.organize_permissions( + permission_sets: permission_sets, + view_label: view_label, + edit_label: edit_label + ) + + expect(result).to eq({}) + end + end + end +end From 0567e80277bcc70e5729e6a9f0bdcfb00783c634 Mon Sep 17 00:00:00 2001 From: Madeline Collier Date: Mon, 2 Sep 2024 18:35:51 +0200 Subject: [PATCH 144/834] Add permission set management when editing roles The role edit modal now also has the functionality to allow admins to assign permission sets. --- .../roles/edit/component.html.erb | 16 ++++++++++++++++ .../solidus_admin/roles/edit/component.rb | 8 ++++++++ .../solidus_admin/roles/edit/component.yml | 13 +++++++++++++ admin/spec/features/roles_spec.rb | 18 +++++++++++++++++- 4 files changed, 54 insertions(+), 1 deletion(-) diff --git a/admin/app/components/solidus_admin/roles/edit/component.html.erb b/admin/app/components/solidus_admin/roles/edit/component.html.erb index 1d0ead5bcfa..9e6b674eb48 100644 --- a/admin/app/components/solidus_admin/roles/edit/component.html.erb +++ b/admin/app/components/solidus_admin/roles/edit/component.html.erb @@ -5,6 +5,21 @@ <%= render component("ui/forms/field").text_field(f, :name, class: "required") %> <%= render component("ui/forms/field").text_field(f, :description) %>
+ + <% if permission_set_options.present? %> +

+ <%= t(".choose_permissions") %> +

+ <% end %> + + <%= render component("ui/checkbox_row").new(options: permission_set_options[:order], row_title: t('.orders'), form: f, method: :permission_set_ids) %> + <%= render component("ui/checkbox_row").new(options: permission_set_options[:product], row_title: t('.products'), form: f, method: :permission_set_ids) %> + <%= render component("ui/checkbox_row").new(options: permission_set_options[:stock], row_title: t('.stock'), form: f, method: :permission_set_ids) %> + <%= render component("ui/checkbox_row").new(options: permission_set_options[:user], row_title: t('.customers'), form: f, method: :permission_set_ids) %> + <%= render component("ui/checkbox_row").new(options: permission_set_options[:restricted_stock], row_title: t('.restricted_stock'), form: f, method: :permission_set_ids) %> + <%= render component("ui/checkbox_row").new(options: permission_set_options[:configuration], row_title: t('.settings'), form: f, method: :permission_set_ids) %> + <%= render component("ui/checkbox_row").new(options: permission_set_options[:other], row_title: t('.other'), form: f, method: :permission_set_ids, layout: :subsection) %> + <% modal.with_actions do %> <%= render component("ui/button").new(scheme: :secondary, text: t('.cancel')) %> @@ -14,4 +29,5 @@ <% end %> <% end %> <% end %> + <%= render component("roles/index").new(page: @page) %> diff --git a/admin/app/components/solidus_admin/roles/edit/component.rb b/admin/app/components/solidus_admin/roles/edit/component.rb index 3d2e06dc2fe..67b1f47659f 100644 --- a/admin/app/components/solidus_admin/roles/edit/component.rb +++ b/admin/app/components/solidus_admin/roles/edit/component.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class SolidusAdmin::Roles::Edit::Component < SolidusAdmin::BaseComponent + include SolidusAdmin::PermissionSetsHelper + def initialize(page:, role:) @page = page @role = role @@ -9,4 +11,10 @@ def initialize(page:, role:) def form_id dom_id(@role, "#{stimulus_id}_edit_role_form") end + + private + + def permission_set_options + @permission_set_options ||= organize_permissions(permission_sets: Spree::PermissionSet.all, view_label: t(".view"), edit_label: t(".edit")) + end end diff --git a/admin/app/components/solidus_admin/roles/edit/component.yml b/admin/app/components/solidus_admin/roles/edit/component.yml index 22945513daa..4686593111c 100644 --- a/admin/app/components/solidus_admin/roles/edit/component.yml +++ b/admin/app/components/solidus_admin/roles/edit/component.yml @@ -4,3 +4,16 @@ en: title: "Edit Role" cancel: "Cancel" submit: "Update Role" + edit: "Edit" + view: "View" + name_placeholder: "Enter a clear role name" + description_placeholder: "Enter a brief description" + choose_permissions: "Choose permissions" + orders: "Orders" + products: "Products" + stock: "Stock" + restricted_stock: "Restricted Stock" + customers: "Customers" + promotions: "Promotions" + settings: "Settings" + other: "Other permissions" diff --git a/admin/spec/features/roles_spec.rb b/admin/spec/features/roles_spec.rb index 5fd88c399ed..0132e2ed205 100644 --- a/admin/spec/features/roles_spec.rb +++ b/admin/spec/features/roles_spec.rb @@ -118,11 +118,13 @@ let(:query) { "?page=1&q%5Bname_cont%5D=er" } before do - Spree::Role.create(name: "Reviewer") + Spree::Role.create(name: "Reviewer", permission_sets: [settings_edit_permission]) visit "/admin/roles#{query}" find_row("Reviewer").click expect(page).to have_content("Edit Role") expect(page).to be_axe_clean + expect(Spree::Role.find_by(name: "Reviewer").permission_set_ids) + .to contain_exactly(settings_edit_permission.id) end it "opens a modal" do @@ -136,12 +138,26 @@ fill_in "Name", with: "Publisher" fill_in "Description", with: "A person who publishes stuff" + within("form.edit_role") do + expect(page).to have_content("Choose permissions") + expect(page).to have_content("Settings") + expect(page).to have_content("Edit") + expect(page).to have_content("View") + expect(find('label', text: 'Edit').find('input[type=checkbox]').checked?).to eq(true) + find('label', text: 'Edit').find('input[type=checkbox]').uncheck + find('label', text: 'View').find('input[type=checkbox]').check + end + click_on "Update Role" expect(page).to have_content("Role was successfully updated.") expect(page).to have_content("Publisher") expect(page).to have_content("A person who publishes stuff") expect(page).not_to have_content("Reviewer") expect(Spree::Role.find_by(name: "Publisher")).to be_present + expect(Spree::Role.find_by(name: "Publisher").permission_set_ids) + .to contain_exactly( + settings_view_permission.id, + ) expect(page.current_url).to include(query) end end From 32393252aea0a2efb3c4c1ebc123f72bb8ad8b70 Mon Sep 17 00:00:00 2001 From: Madeline Collier Date: Thu, 5 Sep 2024 14:21:19 +0200 Subject: [PATCH 145/834] Appease Codecov with new model specs I would not normally write specs to ensure associations, as that seems like it's not my responsibility to test, but Codecov seems to think otherwise and would like a spec for `Spree::RolePermission`, a model with nothing on it except some `belongs_to`s. I also added specs for the other models that were flagged. The association specs are a bit uglier than they could be if we were using the `shoulda_matchers` and their `is_expected.to belong_to` but it seems like it's not worth it to install it just to beautify 3 specs! --- core/spec/models/spree/permission_set_spec.rb | 65 ++++++++++++++++ .../spec/models/spree/role_permission_spec.rb | 17 ++++ core/spec/models/spree/role_spec.rb | 78 +++++++++++++++++++ 3 files changed, 160 insertions(+) create mode 100644 core/spec/models/spree/permission_set_spec.rb create mode 100644 core/spec/models/spree/role_permission_spec.rb create mode 100644 core/spec/models/spree/role_spec.rb diff --git a/core/spec/models/spree/permission_set_spec.rb b/core/spec/models/spree/permission_set_spec.rb new file mode 100644 index 00000000000..6a29d2f6136 --- /dev/null +++ b/core/spec/models/spree/permission_set_spec.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe Spree::PermissionSet, type: :model do + describe "associations" do + it "has many role_permissions" do + association = described_class.reflect_on_association(:role_permissions) + expect(association.macro).to eq(:has_many) + end + + it "has many roles through role_permissions" do + association = described_class.reflect_on_association(:roles) + expect(association.macro).to eq(:has_many) + expect(association.options[:through]).to eq(:role_permissions) + end + end + + describe "validations" do + it "is invalid without a name" do + permission_set = described_class.new(set: "Spree::PermissionSet::OrderDisplay", category: "order", privilege: "display") + expect(permission_set).not_to be_valid + expect(permission_set.errors[:name]).to include("can't be blank") + end + + it "is invalid without a set" do + permission_set = described_class.new(name: "OrderDisplay", category: "order", privilege: "display") + expect(permission_set).not_to be_valid + expect(permission_set.errors[:set]).to include("can't be blank") + end + + it "is invalid without a privilege" do + permission_set = described_class.new(name: "OrderDisplay", set: "Spree::PermissionSet::OrderDisplay", category: "display") + expect(permission_set).not_to be_valid + expect(permission_set.errors[:privilege]).to include("can't be blank") + end + + it "is invalid without a category" do + permission_set = described_class.new(name: "OrderDisplay", set: "Spree::PermissionSet::OrderDisplay", privilege: "order") + expect(permission_set).not_to be_valid + expect(permission_set.errors[:category]).to include("can't be blank") + end + end + + describe "scopes" do + let!(:display_permission) { described_class.create(name: "OrderDisplay", set: "Spree::PermissionSet::OrderDisplay", category: "order", privilege: "display") } + let!(:management_permission) { described_class.create(name: "OrderManagement", set: "Spree::PermissionSet::OrderManagement", category: "order", privilege: "management") } + let!(:other_permission) { described_class.create(name: "Shipping", set: "Spree::PermissionSet::Shipping", category: "shipping", privilege: "other") } + + it 'returns permission sets with privilege: display for display_permissions scope' do + expect(Spree::PermissionSet.display_permissions).to include(display_permission) + expect(Spree::PermissionSet.display_permissions).not_to include(management_permission, other_permission) + end + + it 'returns permission sets with privilege: management for management_permissions scope' do + expect(Spree::PermissionSet.management_permissions).to include(management_permission) + expect(Spree::PermissionSet.management_permissions).not_to include(display_permission, other_permission) + end + + it 'returns permission sets with privilege: other for other_permissions scope' do + expect(Spree::PermissionSet.management_permissions).to include(management_permission) + expect(Spree::PermissionSet.management_permissions).not_to include(display_permission, other_permission) + end + end +end diff --git a/core/spec/models/spree/role_permission_spec.rb b/core/spec/models/spree/role_permission_spec.rb new file mode 100644 index 00000000000..5f6665b7600 --- /dev/null +++ b/core/spec/models/spree/role_permission_spec.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe Spree::RolePermission, type: :model do + describe "associations" do + it "belongs to a role" do + association = described_class.reflect_on_association(:role) + expect(association.macro).to eq(:belongs_to) + end + + it "belongs to a permission_set" do + association = described_class.reflect_on_association(:permission_set) + expect(association.macro).to eq(:belongs_to) + end + end +end diff --git a/core/spec/models/spree/role_spec.rb b/core/spec/models/spree/role_spec.rb new file mode 100644 index 00000000000..7d44706c019 --- /dev/null +++ b/core/spec/models/spree/role_spec.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe Spree::Role, type: :model do + describe "associations" do + it "has many role_users" do + association = described_class.reflect_on_association(:role_users) + expect(association.macro).to eq(:has_many) + expect(association.options[:dependent]).to eq(:destroy) + end + + it "has many users through role_users" do + association = described_class.reflect_on_association(:users) + expect(association.macro).to eq(:has_many) + expect(association.options[:through]).to eq(:role_users) + end + + it "has many role_permissions" do + association = described_class.reflect_on_association(:role_permissions) + expect(association.macro).to eq(:has_many) + expect(association.options[:dependent]).to eq(:destroy) + end + + it "has many permission_sets through role_permissions" do + association = described_class.reflect_on_association(:permission_sets) + expect(association.macro).to eq(:has_many) + expect(association.options[:through]).to eq(:role_permissions) + end + end + + describe "validations" do + it "is invalid without a name" do + role = described_class.new + expect(role).not_to be_valid + expect(role.errors[:name]).to include("can't be blank") + end + + it "validates uniqueness of name" do + described_class.create!(name: "admin") + duplicate_role = described_class.new(name: "admin") + expect(duplicate_role).not_to be_valid + expect(duplicate_role.errors[:name]).to include("has already been taken") + end + end + + describe "#admin?" do + it "returns true if the role name is 'admin'" do + role = described_class.new(name: "admin") + expect(role.admin?).to be true + end + + it "returns false if the role name is not 'admin'" do + role = described_class.new(name: "user") + expect(role.admin?).to be false + end + end + + describe "#destroy" do + let(:role) { create(:role) } + let(:display_permission) { Spree::PermissionSet.create!(name: "OrderDisplay", set: "Spree::PermissionSet::OrderDisplay", category: "order", privilege: "display") } + + before do + role.permission_sets << display_permission + role.save + end + + it "destroys all associated role permissions" do + role_permission = role.role_permissions.first + + role.destroy + aggregate_failures do + expect{ described_class.find(role.id) }.to raise_error(ActiveRecord::RecordNotFound) + expect{ Spree::RolePermission.find(role_permission.id) }.to raise_error(ActiveRecord::RecordNotFound) + end + end + end +end From 022e27409684d84378ddc78c89bfd5183597a3a1 Mon Sep 17 00:00:00 2001 From: Madeline Collier Date: Mon, 9 Sep 2024 14:48:18 +0200 Subject: [PATCH 146/834] Change default scope in Users admin I feel pretty strongly that the default scope should be "all" instead of "customers". That way, when presented with a long list the first thought is to filter it, instead of the first thought being "why is this user missing" and then realizing that it's because it's filtering by users with the "customer" role only. --- .../components/solidus_admin/users/index/component.rb | 4 ++-- admin/app/controllers/solidus_admin/users_controller.rb | 4 ++-- admin/spec/features/users_spec.rb | 9 +++++---- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/admin/app/components/solidus_admin/users/index/component.rb b/admin/app/components/solidus_admin/users/index/component.rb index 46b20878508..be25eb9bf38 100644 --- a/admin/app/components/solidus_admin/users/index/component.rb +++ b/admin/app/components/solidus_admin/users/index/component.rb @@ -39,11 +39,11 @@ def batch_actions def scopes [ - { name: :customers, label: t('.scopes.customers'), default: true }, + { name: :all, label: t('.scopes.all'), default: true }, + { name: :customers, label: t('.scopes.customers') }, { name: :admin, label: t('.scopes.admin') }, { name: :with_orders, label: t('.scopes.with_orders') }, { name: :without_orders, label: t('.scopes.without_orders') }, - { name: :all, label: t('.scopes.all') }, ] end diff --git a/admin/app/controllers/solidus_admin/users_controller.rb b/admin/app/controllers/solidus_admin/users_controller.rb index b8de0af3d0e..7f674b4e09e 100644 --- a/admin/app/controllers/solidus_admin/users_controller.rb +++ b/admin/app/controllers/solidus_admin/users_controller.rb @@ -4,11 +4,11 @@ module SolidusAdmin class UsersController < SolidusAdmin::BaseController include SolidusAdmin::ControllerHelpers::Search - search_scope(:customers, default: true) { _1.left_outer_joins(:role_users).where(role_users: { id: nil }) } + search_scope(:all, default: true) + search_scope(:customers) { _1.left_outer_joins(:role_users).where(role_users: { id: nil }) } search_scope(:admin) { _1.joins(:role_users).distinct } search_scope(:with_orders) { _1.joins(:orders).distinct } search_scope(:without_orders) { _1.left_outer_joins(:orders).where(orders: { id: nil }) } - search_scope(:all) def index users = apply_search_to( diff --git a/admin/spec/features/users_spec.rb b/admin/spec/features/users_spec.rb index 2149c1a20b7..6268025e8de 100644 --- a/admin/spec/features/users_spec.rb +++ b/admin/spec/features/users_spec.rb @@ -11,6 +11,10 @@ create(:user, :with_orders, email: "customer-with-order@example.com") visit "/admin/users" + expect(page).to have_content("customer@example.com") + expect(page).to have_content("admin-2@example.com") + expect(page).to have_content("customer-with-order@example.com") + click_on "Customers" expect(page).to have_content("Users and Roles") expect(page).to have_content("customer@example.com") expect(page).not_to have_content("admin-2@example.com") @@ -19,13 +23,10 @@ expect(page).not_to have_content("customer@example.com") click_on "With Orders" expect(page).to have_content("customer-with-order@example.com") - click_on "All" - expect(page).to have_content("customer@example.com") - expect(page).to have_content("admin-2@example.com") - expect(page).to have_content("customer-with-order@example.com") expect(page).to be_axe_clean + click_on "All" select_row("customer@example.com") click_on "Delete" expect(page).to have_content("Users were successfully removed.") From 3bb092ec02b5d6f8de728c34a4c09d193fd21786 Mon Sep 17 00:00:00 2001 From: Madeline Collier Date: Mon, 9 Sep 2024 16:59:33 +0200 Subject: [PATCH 147/834] Display last_sign_in_at instead of created_at The new users admin interface now shows the last sign in date instead of the date the user was created. It will range between "Never", "Less than a minute ago", "3 days ago", "2 months ago" etc. It uses the built-in logic provided by `ActionView::Helpers::DateHelper#time_ago_in_words`. The specs are a bit tricky and stubby due to the need to keep them general and agnostic of whatever is being used for the `Spree.user_class`. --- .../solidus_admin/users/index/component.rb | 19 +++++++++++++-- .../solidus_admin/users/index/component.yml | 4 ++++ admin/spec/features/users_spec.rb | 23 +++++++++++++++++-- core/config/locales/en.yml | 1 + 4 files changed, 43 insertions(+), 4 deletions(-) diff --git a/admin/app/components/solidus_admin/users/index/component.rb b/admin/app/components/solidus_admin/users/index/component.rb index be25eb9bf38..81fb38444ff 100644 --- a/admin/app/components/solidus_admin/users/index/component.rb +++ b/admin/app/components/solidus_admin/users/index/component.rb @@ -88,9 +88,24 @@ def columns data: -> { _1.display_lifetime_value.to_html }, }, { - header: :created_at, - data: ->(user) { l(user.created_at.to_date, format: :long) }, + header: :last_active, + data: ->(user) { last_login(user) }, }, ] end + + private + + # @todo add logic to display "Invitation sent" when the user has not yet + # accepted the invitation and filled out account details. To be implemented + # in conjunction with the invitation logic. + def last_login(user) + return t('.last_login.never') if user.try(:last_sign_in_at).blank? + + t( + '.last_login.login_time_ago', + # @note The second `.try` is only here for the specs to work. + last_login_time: time_ago_in_words(user.try(:last_sign_in_at)) + ).capitalize + end end diff --git a/admin/app/components/solidus_admin/users/index/component.yml b/admin/app/components/solidus_admin/users/index/component.yml index ba63d2d41f6..789fbb6a3cf 100644 --- a/admin/app/components/solidus_admin/users/index/component.yml +++ b/admin/app/components/solidus_admin/users/index/component.yml @@ -13,3 +13,7 @@ en: status: active: Active inactive: Inactive + last_login: + login_time_ago: "%{last_login_time} ago" + never: Never + invitation_sent: Invitation sent diff --git a/admin/spec/features/users_spec.rb b/admin/spec/features/users_spec.rb index 6268025e8de..f0e88296031 100644 --- a/admin/spec/features/users_spec.rb +++ b/admin/spec/features/users_spec.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" describe "Users", :js, type: :feature do - before { sign_in create(:admin_user, email: 'admin@example.com') } + before { sign_in create(:admin_user, email: "admin@example.com") } it "lists users and allows deleting them" do create(:user, email: "customer@example.com") @@ -11,6 +11,9 @@ create(:user, :with_orders, email: "customer-with-order@example.com") visit "/admin/users" + expect(page).to have_content("Last active") + expect(page).to have_content("Never") + expect(page).to have_content("customer@example.com") expect(page).to have_content("admin-2@example.com") expect(page).to have_content("customer-with-order@example.com") @@ -33,4 +36,20 @@ expect(page).not_to have_content("customer@example.com") expect(Spree.user_class.count).to eq(3) end + + context "when a user has recently signed in" do + let(:sign_in_date) { DateTime.now } + + before do + allow_any_instance_of(Spree.user_class).to receive(:try).with(:email).and_call_original + allow_any_instance_of(Spree.user_class).to receive(:try).with(:last_sign_in_at).and_return(sign_in_date) + end + + it "lists the last time they were active" do + visit "/admin/users" + expect(page).to have_content("Last active") + expect(page).to have_content("Less than a minute ago") + expect(page).not_to have_content("Never") + end + end end diff --git a/core/config/locales/en.yml b/core/config/locales/en.yml index 1bca2c7fd11..d77fd1e12dd 100644 --- a/core/config/locales/en.yml +++ b/core/config/locales/en.yml @@ -410,6 +410,7 @@ en: analytics_id: Analytics ID spree/user: email: Email + last_active: Last active lifetime_value: Total spent password: Password password_confirmation: Password Confirmation From d3b9db317bfd432508dd90cf6779c8bbfc5463ba Mon Sep 17 00:00:00 2001 From: Thomas von Deyen Date: Thu, 12 Sep 2024 10:15:05 +0200 Subject: [PATCH 148/834] Replace custom product ransacker with association We introduced a custom Ransacker to allow filtering producs by their variants option values in the new admin interface. This ransacker is broken in latest Ransack v4.2 and overly complicated. By introducing a option_values association to products, that does the same join over variants_including_master and their distinct option_values we can use normal ransack search. Quoting the Ransack documentation: > "Ransackers, like scopes, are not a cure-all. Many use cases can be better solved with a standard Ransack search on a dedicated database search field, which is faster, index-able, and scales better than converting/ransacking data on the fly." --- .../solidus_admin/products/index/component.rb | 2 +- core/app/models/spree/product.rb | 24 ++++--------------- core/spec/models/spree/product_spec.rb | 12 +++++----- 3 files changed, 11 insertions(+), 27 deletions(-) diff --git a/admin/app/components/solidus_admin/products/index/component.rb b/admin/app/components/solidus_admin/products/index/component.rb index 029b1c2be02..6a015ebcbd2 100644 --- a/admin/app/components/solidus_admin/products/index/component.rb +++ b/admin/app/components/solidus_admin/products/index/component.rb @@ -68,7 +68,7 @@ def filters { label: option_type.presentation, combinator: 'or', - attribute: "variants_option_values", + attribute: "option_values_id", predicate: "in", options: option_type.option_values.pluck(:name, :id), } diff --git a/core/app/models/spree/product.rb b/core/app/models/spree/product.rb index 107a8767597..f06df96cd2e 100644 --- a/core/app/models/spree/product.rb +++ b/core/app/models/spree/product.rb @@ -57,6 +57,8 @@ class Product < Spree::Base has_many :line_items, through: :variants_including_master has_many :orders, through: :line_items + has_many :option_values, -> { distinct }, through: :variants_including_master + scope :sort_by_master_default_price_amount_asc, -> { with_default_price.order('spree_prices.amount ASC') } @@ -127,26 +129,8 @@ def find_or_build_master alias :options :product_option_types - # The :variants_option_values ransacker filters Spree::Product based on - # variant option values ids. - # - # Usage: - # Spree::Product.ransack( - # variants_option_values_in: [option_value_id1, option_value_id2] - # ).result - ransacker :variants_option_values, formatter: proc { |v| - if OptionValue.exists?(v) - joins(variants_including_master: :option_values) - .where(spree_option_values: { id: v }) - .distinct - .select(:id).arel - end - } do |parent| - parent.table[:id] - end - - self.allowed_ransackable_associations = %w[stores variants_including_master master variants] - self.allowed_ransackable_attributes = %w[name slug variants_option_values] + self.allowed_ransackable_associations = %w[stores variants_including_master master variants option_values] + self.allowed_ransackable_attributes = %w[name slug] self.allowed_ransackable_scopes = %i[available with_discarded with_all_variant_sku_cont with_kept_variant_sku_cont] # @return [Boolean] true if there are any variants diff --git a/core/spec/models/spree/product_spec.rb b/core/spec/models/spree/product_spec.rb index 19f6752731c..561ba07c4df 100644 --- a/core/spec/models/spree/product_spec.rb +++ b/core/spec/models/spree/product_spec.rb @@ -627,13 +627,13 @@ class Extension < Spree::Base end end - context "ransacker :variants_option_values" do + describe "ransack :option_values_id_in" do it "filters products based on option values of their variants" do product_1 = create(:product) option_value_1 = create(:option_value) create(:variant, product: product_1, option_values: [option_value_1]) - result = Spree::Product.ransack(variants_option_values_in: [option_value_1.id]).result + result = Spree::Product.ransack(option_values_id_in: [option_value_1.id]).result expect(result).to contain_exactly(product_1) end @@ -644,13 +644,13 @@ class Extension < Spree::Base create(:variant, product: product_1, option_values: [option_value_1]) create(:variant, product: product_2, option_values: [option_value_1]) - result = Spree::Product.ransack(variants_option_values_in: [option_value_1.id]).result + result = Spree::Product.ransack(option_values_id_in: [option_value_1.id]).result expect(result).to contain_exactly(product_1, product_2) end it "returns no products if there is no match" do non_existing_option_value_id = 99999 - result = Spree::Product.ransack(variants_option_values_in: [non_existing_option_value_id]).result + result = Spree::Product.ransack(option_values_id_in: [non_existing_option_value_id]).result expect(result).to be_empty end @@ -662,7 +662,7 @@ class Extension < Spree::Base create(:variant, product: product_1, option_values: [option_value_1]) create(:variant, product: product_2, option_values: [option_value_2]) - result = Spree::Product.ransack(variants_option_values_in: [option_value_1.id, option_value_2.id]).result + result = Spree::Product.ransack(option_values_id_in: [option_value_1.id, option_value_2.id]).result expect(result).to contain_exactly(product_1, product_2) end @@ -674,7 +674,7 @@ class Extension < Spree::Base create(:variant, product: product_1, option_values: [option_value_1]) create(:variant, product: product_2, option_values: [option_value_2]) - result = Spree::Product.ransack(variants_option_values_in: [option_value_1.id]).result + result = Spree::Product.ransack(option_values_id_in: [option_value_1.id]).result expect(result).not_to include(product_2) end end From d730d95246e8e989a583c6ec880b7057ea6ccbfc Mon Sep 17 00:00:00 2001 From: Thomas von Deyen Date: Thu, 12 Sep 2024 10:15:51 +0200 Subject: [PATCH 149/834] Allow Ransack 4.2 again Now that we removed the broken ransacker in previous commit, we can allow Ransack v4.2 - with Rails 7.2 support - again. --- core/solidus_core.gemspec | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/core/solidus_core.gemspec b/core/solidus_core.gemspec index 43ec266f676..073b3ead54b 100644 --- a/core/solidus_core.gemspec +++ b/core/solidus_core.gemspec @@ -43,9 +43,7 @@ Gem::Specification.new do |s| s.add_dependency 'monetize', '~> 1.8' s.add_dependency 'kt-paperclip', ['>= 6.3', '< 8'] s.add_dependency 'psych', ['>= 4.0.1', '< 6.0'] - # @note ransack 4.2 contains a bug which has not yet been addressed. - # @see https://github.com/activerecord-hackery/ransack/pull/1468 - s.add_dependency 'ransack', ['~> 4.0', '< 4.2'] + s.add_dependency 'ransack', ['~> 4.0', '< 5'] s.add_dependency 'sprockets-rails', '!= 3.5.0' s.add_dependency 'state_machines-activerecord', '~> 0.6' s.add_dependency 'omnes', '~> 0.2.2' From ae695346578aeab830410bcc843ccc3af8dc4ab4 Mon Sep 17 00:00:00 2001 From: Thomas von Deyen Date: Thu, 12 Sep 2024 10:32:00 +0200 Subject: [PATCH 150/834] Remove Ransack patch We do not use the custom ransacker this was necessary for --- core/lib/spree/core.rb | 2 -- core/lib/spree/ransack_4_1_patch.rb | 16 ---------------- 2 files changed, 18 deletions(-) delete mode 100644 core/lib/spree/ransack_4_1_patch.rb diff --git a/core/lib/spree/core.rb b/core/lib/spree/core.rb index e20dbb6f540..c9ca87e1198 100644 --- a/core/lib/spree/core.rb +++ b/core/lib/spree/core.rb @@ -22,8 +22,6 @@ require 'ransack' require 'state_machines-activerecord' -require_relative './ransack_4_1_patch' - # This is required because ActiveModel::Validations#invalid? conflicts with the # invalid state of a Payment. In the future this should be removed. StateMachines::Machine.ignore_method_conflicts = true diff --git a/core/lib/spree/ransack_4_1_patch.rb b/core/lib/spree/ransack_4_1_patch.rb deleted file mode 100644 index f531de330d8..00000000000 --- a/core/lib/spree/ransack_4_1_patch.rb +++ /dev/null @@ -1,16 +0,0 @@ -# frozen_string_literal: true - -require "ransack/version" - -return unless Ransack::VERSION.start_with?("4.1.") - -module RansackNodeConditionPatch - private - - # Waiting for https://github.com/activerecord-hackery/ransack/pull/1468 - def casted_array?(predicate) - predicate.is_a?(Arel::Nodes::Casted) && predicate.value.is_a?(Array) - end - - Ransack::Nodes::Condition.prepend(self) -end From c4fd1dfee5c1e44c2b2f8a5dcc076281996c960d Mon Sep 17 00:00:00 2001 From: Monica Giambitto Date: Tue, 3 Oct 2023 17:00:28 +0200 Subject: [PATCH 151/834] Call empty only on incomplete orders Code fails with a 500 on admin because of the order shipping status. The change triggers @order.empty! only when the order is incomplete. Co-authored-by: An Stewart Co-authored-by: Kendra Chateau --- .../spree/api/orders_controller.rb | 9 ++- .../backend/views/cart/empty_cart_button.js | 2 +- .../admin/orders/order_details_spec.rb | 57 +++++++++---------- 3 files changed, 34 insertions(+), 34 deletions(-) diff --git a/api/app/controllers/spree/api/orders_controller.rb b/api/app/controllers/spree/api/orders_controller.rb index 732c94a2228..3f8507086da 100644 --- a/api/app/controllers/spree/api/orders_controller.rb +++ b/api/app/controllers/spree/api/orders_controller.rb @@ -46,8 +46,13 @@ def create def empty authorize! :update, @order, order_token - @order.empty! - respond_with(@order, default_template: :show) + + if @order.complete? + invalid_resource!(@order) + else + @order.empty! + respond_with(@order, default_template: :show) + end end def index diff --git a/backend/app/assets/javascripts/spree/backend/views/cart/empty_cart_button.js b/backend/app/assets/javascripts/spree/backend/views/cart/empty_cart_button.js index b6a30198415..f3104de531c 100644 --- a/backend/app/assets/javascripts/spree/backend/views/cart/empty_cart_button.js +++ b/backend/app/assets/javascripts/spree/backend/views/cart/empty_cart_button.js @@ -24,6 +24,6 @@ Spree.Views.Cart.EmptyCartButton = Backbone.View.extend({ render: function() { var isNew = function (item) { return item.isNew() }; - this.$el.prop("disabled", !this.collection.length || this.collection.some(isNew)); + this.$el.prop("disabled", !this.collection.length || this.collection.some(isNew) || this.model.get("completed_at")); } }); diff --git a/backend/spec/features/admin/orders/order_details_spec.rb b/backend/spec/features/admin/orders/order_details_spec.rb index 17430541eed..09b8f7ba44a 100644 --- a/backend/spec/features/admin/orders/order_details_spec.rb +++ b/backend/spec/features/admin/orders/order_details_spec.rb @@ -137,39 +137,11 @@ expect(page).to have_field('quantity') end - it "can remove all items with empty cart" do + it "cannot remove all items with a completed order" do + # Wait for the cart contents to be loaded. expect(page).to have_content("spree t-shirt") - within("#item_total") do - expect(page).to have_content("$40.00") - end - - within("#order_total") do - expect(page).to have_content("$40.00") - end - - within("#order-total", text: 'Order Total') do - expect(page).to have_content("$40.00") - end - - accept_confirm "Are you sure you want to delete this record?" do - click_on 'Empty Cart' - end - - expect(page).not_to have_content("spree t-shirt") - - # Should have a new item row - expect(page).to have_field('quantity') - - within("#item_total") do - expect(page).to have_content("$0.00") - end - - within("#order_total") do - expect(page).to have_content("$0.00") - end - - expect(page).to have_css('#order-total', visible: false) + expect(page).to have_button("Empty Cart", disabled: true) end # Regression test for https://github.com/spree/spree/issues/3862 @@ -384,6 +356,29 @@ expect(page).not_to have_selector('.fa-arrows-h') expect(page).not_to have_selector('.fa-trash') end + + context 'order has shipped' do + it 'disables empty cart' do + order = create(:order_ready_to_ship) + + visit spree.cart_admin_order_path(order) + order.fulfill! + + ## simulate shipping order in another tab + shipment = order.shipments.first + order.shipping.ship( + inventory_units: shipment.inventory_units, + stock_location: shipment.stock_location, + address: order.ship_address, + shipping_method: shipment.shipping_method + ) + + # Make an assertion here about page content to give the JS time + # to load before the next expectation. + expect(page).to have_content("Cart") + expect(page).to have_button("Empty Cart", disabled: true) + end + end end end From f7cea4be66815829e0152f5f3545253f6542f3f9 Mon Sep 17 00:00:00 2001 From: Madeline Collier Date: Mon, 23 Sep 2024 13:20:00 +0200 Subject: [PATCH 152/834] Fix failing API promotions specs Three of these examples were failing with the following error: NameError: uninitialized constant Spree::Api::OpenStruct OpenStruct.new( ^^^^^^^^^^ Requiring ostruct and ensuring the correct scope is used when referencing OpenStruct fixes the errors. --- api/spec/requests/spree/api/promotions_spec.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/api/spec/requests/spree/api/promotions_spec.rb b/api/spec/requests/spree/api/promotions_spec.rb index 7ab350e779e..b6b16f0a921 100644 --- a/api/spec/requests/spree/api/promotions_spec.rb +++ b/api/spec/requests/spree/api/promotions_spec.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'spec_helper' +require 'ostruct' module Spree::Api describe 'Promotions', type: :request do @@ -25,7 +26,7 @@ module Spree::Api end let(:found_promotion) do - OpenStruct.new( + ::OpenStruct.new( id: 1, name: 'Test Promotion', description: 'Promotion for testing purposes', From bf4ab8720bfd4811064a251a375fa338945a3512 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 25 Sep 2024 15:43:29 +0200 Subject: [PATCH 153/834] Stop pinning Chrome We initially did this because of some Bug in Chrome, but now the download of the very specific version here doesn't work any longer. Let's see what happens if we don't pin. --- .circleci/config.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index a158820b122..0dc19b64d42 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -59,9 +59,7 @@ commands: setup: steps: - checkout - - browser-tools/install-chrome: - chrome-version: "118.0.5993.70" - replace-existing: true + - browser-tools/install-chrome - run: name: Check chrome version command: | From b2f8e4f1894d7a40931077b76427c155f52879a8 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 25 Sep 2024 14:09:03 +0200 Subject: [PATCH 154/834] Spree::Variant.in_stock: Only show distinct variants Without the `.distinct` added to this scope, it returns the same variant multiple times if there is stock in multiple stock locations. --- core/app/models/spree/variant.rb | 2 +- core/spec/models/spree/variant_spec.rb | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/core/app/models/spree/variant.rb b/core/app/models/spree/variant.rb index dec68365450..1c20b3f7932 100644 --- a/core/app/models/spree/variant.rb +++ b/core/app/models/spree/variant.rb @@ -94,7 +94,7 @@ def self.in_stock(stock_locations = nil) if stock_locations.present? in_stock_variants = in_stock_variants.where(spree_stock_items: { stock_location_id: stock_locations.map(&:id) }) end - in_stock_variants + in_stock_variants.distinct end # Returns a scope of Variants which are suppliable. This includes: diff --git a/core/spec/models/spree/variant_spec.rb b/core/spec/models/spree/variant_spec.rb index def0b21ff3c..ee8a3f58290 100644 --- a/core/spec/models/spree/variant_spec.rb +++ b/core/spec/models/spree/variant_spec.rb @@ -885,6 +885,18 @@ it "returns all in stock variants" do expect(subject).to eq [in_stock_variant] end + + context "with stock in several locations" do + let!(:other_stock_location) { create(:stock_location, propagate_all_variants: true) } + + before do + Spree::StockItem.where(variant: in_stock_variant).update_all(count_on_hand: 10) + end + + it "returns just one variant" do + expect(subject).to eq([in_stock_variant]) + end + end end context "inventory levels globally not tracked" do From dce4abb16d455859443ffc0c574a33e4684c26af Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 25 Sep 2024 14:58:23 +0200 Subject: [PATCH 155/834] Variant Autocomplete: Allow passing select2 options This change allows users of the variant autocomplete to customize the behavior of select2, rather that just discarding all the keys that are not `searchParameters`. We're using this for a custom controller in which we want to be able to filter by variant, and be able to clear the selection. So in that controller, we can do: ```js const inputElement = document.getElementById('variantInput') inputElement.variantAutocomplete({ allowClear: true }) ``` --- .../spree/backend/variant_autocomplete.js | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/backend/app/assets/javascripts/spree/backend/variant_autocomplete.js b/backend/app/assets/javascripts/spree/backend/variant_autocomplete.js index d6133c6edfd..7b22805d36f 100644 --- a/backend/app/assets/javascripts/spree/backend/variant_autocomplete.js +++ b/backend/app/assets/javascripts/spree/backend/variant_autocomplete.js @@ -10,17 +10,18 @@ /** * Make the element a select2 dropdown used for finding Variants. By default, the search term will be * passed to the defined Spree::Config.variant_search_class by the controller with its defined scope. - * @param {Object|undefined|null} options Options + * @param {Object|undefined|null} options Options to be passed to select2. If null, the default options will be used. * @param {Function|undefined} options.searchParameters Returns a hash object for params to merge on the select2 ajax request * Accepts an argument of the select2 search term. To use Ransack, define * variant_search_term as a falsy value, and q as the Ransack query. Note, * you need to ensure that the attributes are allowed to be Ransacked. */ - $.fn.variantAutocomplete = function(options) { + $.fn.variantAutocomplete = function(options = {}) { // Default options - options = options || {} - - this.select2({ + const searchParameters = options.searchParameters || null + delete options.searchParameters + const select2options = options + const defaultOptions = { placeholder: Spree.translations.variant_placeholder, minimumInputLength: 3, initSelection: function(element, callback) { @@ -39,7 +40,7 @@ } }, data: function(term, page) { - const extraParameters = options["searchParameters"] ? options["searchParameters"](term) : {} + const extraParameters = searchParameters ? searchParameters(term) : {} return { variant_search_term: term, @@ -64,7 +65,9 @@ } else { return Select2.util.escapeMarkup(variant.name); } - } - }); + }, + } + + this.select2(Object.assign({}, defaultOptions, select2options)); }; })(); From ac8ee597335968072b9c42d9f1bf5f8e9ba7e957 Mon Sep 17 00:00:00 2001 From: Madeline Collier Date: Wed, 18 Sep 2024 21:22:23 +0200 Subject: [PATCH 156/834] Add new users admin edit page This migrates the `users/:id/edit` page to the new admin. It still relies on the old backend admin controller for the `#update` action as well as the other top level user tabs such as address, order history, and so on. Co-authored-by: benjamin wil --- .../users/edit/api_access/component.html.erb | 49 +++++++++++++++ .../users/edit/api_access/component.js | 9 +++ .../users/edit/api_access/component.rb | 7 +++ .../users/edit/api_access/component.yml | 10 +++ .../users/edit/component.html.erb | 62 ++++++++++++++++++ .../solidus_admin/users/edit/component.rb | 63 +++++++++++++++++++ .../solidus_admin/users/edit/component.yml | 16 +++++ .../solidus_admin/users/index/component.rb | 5 +- .../solidus_admin/users_controller.rb | 12 ++++ admin/config/routes.rb | 2 +- admin/spec/features/users_spec.rb | 59 ++++++++++++++++- .../spec/requests/solidus_admin/users_spec.rb | 59 +++++++++++++++++ core/config/locales/en.yml | 1 + 13 files changed, 350 insertions(+), 4 deletions(-) create mode 100644 admin/app/components/solidus_admin/users/edit/api_access/component.html.erb create mode 100644 admin/app/components/solidus_admin/users/edit/api_access/component.js create mode 100644 admin/app/components/solidus_admin/users/edit/api_access/component.rb create mode 100644 admin/app/components/solidus_admin/users/edit/api_access/component.yml create mode 100644 admin/app/components/solidus_admin/users/edit/component.html.erb create mode 100644 admin/app/components/solidus_admin/users/edit/component.rb create mode 100644 admin/app/components/solidus_admin/users/edit/component.yml create mode 100644 admin/spec/requests/solidus_admin/users_spec.rb diff --git a/admin/app/components/solidus_admin/users/edit/api_access/component.html.erb b/admin/app/components/solidus_admin/users/edit/api_access/component.html.erb new file mode 100644 index 00000000000..96d4e00ed9a --- /dev/null +++ b/admin/app/components/solidus_admin/users/edit/api_access/component.html.erb @@ -0,0 +1,49 @@ +<%= render component('ui/panel').new(title: t('.api_access')) do %> +
+ <% if @user.spree_api_key.present? %> +
+

<%= t('.key') %>

+ <% if @user == helpers.current_solidus_admin_user %> + <%= @user.spree_api_key %> + <% else %> + (<%= t('spree.hidden') %>) + <% end %> +
+ +
+ <%= form_with url: spree.admin_user_api_key_path(@user), method: :delete, local: true, html: { class: 'clear_api_key inline-flex' } do %> + <%= render component("ui/button").new( + text: t('.clear_key'), + scheme: :secondary, + type: :submit, + "data-action": "click->#{stimulus_id}#confirm", + "data-#{stimulus_id}-message-param": t(".confirm_clear_key"), + ) %> + <% end %> + + <%= form_with url: spree.admin_user_api_key_path(@user), method: :post, local: true, html: { class: 'regen_api_key inline-flex' } do %> + <%= render component("ui/button").new( + text: t('.regenerate_key'), + scheme: :secondary, + type: :submit, + "data-action": "click->#{stimulus_id}#confirm", + "data-#{stimulus_id}-message-param": t(".confirm_regenerate_key"), + ) %> + <% end %> +
+ + <% else %> +
<%= t('.no_key') %>
+
+
+ <%= form_with url: spree.admin_user_api_key_path(@user), method: :post, local: true, html: { class: 'generate_api_key inline-flex' } do %> + <%= render component("ui/button").new( + text: t('.generate_key'), + type: :submit, + ) %> + <% end %> +
+
+ <% end %> +
+<% end %> diff --git a/admin/app/components/solidus_admin/users/edit/api_access/component.js b/admin/app/components/solidus_admin/users/edit/api_access/component.js new file mode 100644 index 00000000000..910294c5462 --- /dev/null +++ b/admin/app/components/solidus_admin/users/edit/api_access/component.js @@ -0,0 +1,9 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + confirm(event) { + if (!confirm(event.params.message)) { + event.preventDefault() + } + } +} diff --git a/admin/app/components/solidus_admin/users/edit/api_access/component.rb b/admin/app/components/solidus_admin/users/edit/api_access/component.rb new file mode 100644 index 00000000000..be6df3729b8 --- /dev/null +++ b/admin/app/components/solidus_admin/users/edit/api_access/component.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class SolidusAdmin::Users::Edit::ApiAccess::Component < SolidusAdmin::BaseComponent + def initialize(user:) + @user = user + end +end diff --git a/admin/app/components/solidus_admin/users/edit/api_access/component.yml b/admin/app/components/solidus_admin/users/edit/api_access/component.yml new file mode 100644 index 00000000000..e865ba47c30 --- /dev/null +++ b/admin/app/components/solidus_admin/users/edit/api_access/component.yml @@ -0,0 +1,10 @@ +en: + api_access: API Access + no_key: No key + key: "Key" + generate_key: Generate API key + clear_key: Clear key + regenerate_key: Regenerate key + hidden: Hidden + confirm_clear_key: Are you sure you want to clear this user's API key? It will invalidate the existing key. + confirm_regenerate_key: Are you sure you want to regenerate this user's API key? It will invalidate the existing key. diff --git a/admin/app/components/solidus_admin/users/edit/component.html.erb b/admin/app/components/solidus_admin/users/edit/component.html.erb new file mode 100644 index 00000000000..b9b8cbe4488 --- /dev/null +++ b/admin/app/components/solidus_admin/users/edit/component.html.erb @@ -0,0 +1,62 @@ +<%= page do %> + <%= page_header do %> + <%= page_header_back(solidus_admin.users_path) %> + <%= page_header_title(t(".title", email: @user.email)) %> + + <% # @todo: I am not sure how we want to handle Cancan stuff in the new admin. %> + <% # if can?(:admin, Spree::Order) && can?(:create, Spree::Order) %> + <%= page_header_actions do %> + <%= render component("ui/button").new(tag: :a, text: t(".create_order_for_user"), href: spree.new_admin_order_path(user_id: @user.id)) %> + <% end %> + <% # end %> + <% end %> + + <%= page_header do %> + <% tabs.each do |tab| %> + <%= render(component("ui/button").new(tag: :a, scheme: :ghost, text: tab[:text], 'aria-current': tab[:current], href: tab[:href])) %> + <% end %> + <% end %> + + <%= page_with_sidebar do %> + <%= page_with_sidebar_main do %> + + <%= render component('ui/panel').new(title: Spree.user_class.model_name.human) do %> + <%= form_for @user, url: solidus_admin.user_path(@user), html: { id: form_id, autocomplete: "off" } do |f| %> +
+ <%= render component("ui/forms/field").text_field(f, :email) %> +
+
+ <%= render component("ui/forms/field").text_field(f, :password) %> +
+
+ <%= render component("ui/forms/field").text_field(f, :password_confirmation) %> +
+
+ <%= render component("ui/checkbox_row").new(options: role_options, row_title: "Roles", form: f, method: "spree_role_ids", layout: :subsection) %> +
+
+ <%= render component("ui/button").new(tag: :button, text: t(".update"), form: form_id) %> + <%= render component("ui/button").new(tag: :a, text: t(".cancel"), href: solidus_admin.user_path(@user), scheme: :secondary) %> +
+ <% end %> + <% end %> + + <%= render component("users/edit/api_access").new(user: @user) %> + + <% end %> + + <%= page_with_sidebar_aside do %> + <%= render component("ui/panel").new(title: t("spree.lifetime_stats")) do %> + <%= render component("ui/details_list").new( + items: [ + { label: t("spree.total_sales"), value: @user.display_lifetime_value.to_html }, + { label: t("spree.order_count"), value: @user.order_count.to_i }, + { label: t("spree.average_order_value"), value: @user.display_average_order_value.to_html }, + { label: t("spree.member_since"), value: @user.created_at.to_date }, + { label: t(".last_active"), value: last_login(@user) }, + ] + ) %> + <% end %> + <% end %> + <% end %> +<% end %> diff --git a/admin/app/components/solidus_admin/users/edit/component.rb b/admin/app/components/solidus_admin/users/edit/component.rb new file mode 100644 index 00000000000..78f1b8a82eb --- /dev/null +++ b/admin/app/components/solidus_admin/users/edit/component.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +class SolidusAdmin::Users::Edit::Component < SolidusAdmin::BaseComponent + include SolidusAdmin::Layout::PageHelpers + + def initialize(user:) + @user = user + end + + def form_id + @form_id ||= "#{stimulus_id}--form-#{@user.id}" + end + + def tabs + [ + { + text: t('.account'), + href: solidus_admin.users_path, + current: action_name == "show", + }, + { + text: t('.addresses'), + href: spree.addresses_admin_user_path(@user), + # @todo: update this "current" logic once folded into new admin + current: action_name != "show", + }, + { + text: t('.order_history'), + href: spree.orders_admin_user_path(@user), + # @todo: update this "current" logic once folded into new admin + current: action_name != "show", + }, + { + text: t('.items'), + href: spree.items_admin_user_path(@user), + # @todo: update this "current" logic once folded into new admin + current: action_name != "show", + }, + { + text: t('.store_credit'), + href: spree.admin_user_store_credits_path(@user), + # @todo: update this "current" logic once folded into new admin + current: action_name != "show", + }, + ] + end + + def last_login(user) + return t('.last_login.never') if user.try(:last_sign_in_at).blank? + + t( + '.last_login.login_time_ago', + # @note The second `.try` is only here for the specs to work. + last_login_time: time_ago_in_words(user.try(:last_sign_in_at)) + ).capitalize + end + + def role_options + Spree::Role.all.map do |role| + { label: role.name, id: role.id } + end + end +end diff --git a/admin/app/components/solidus_admin/users/edit/component.yml b/admin/app/components/solidus_admin/users/edit/component.yml new file mode 100644 index 00000000000..01b3cf230ac --- /dev/null +++ b/admin/app/components/solidus_admin/users/edit/component.yml @@ -0,0 +1,16 @@ +en: + title: "Users / %{email}" + account: Account + addresses: Addresses + order_history: Order History + items: Items + store_credit: Store Credit + last_active: Last Active + last_login: + login_time_ago: "%{last_login_time} ago" + never: Never + invitation_sent: Invitation sent + create_order_for_user: Create order for this user + update: Update + cancel: Cancel + back: Back diff --git a/admin/app/components/solidus_admin/users/index/component.rb b/admin/app/components/solidus_admin/users/index/component.rb index 81fb38444ff..0b911061d90 100644 --- a/admin/app/components/solidus_admin/users/index/component.rb +++ b/admin/app/components/solidus_admin/users/index/component.rb @@ -14,7 +14,7 @@ def search_url end def row_url(user) - spree.admin_user_path(user) + solidus_admin.edit_user_path(user) end def page_actions @@ -104,7 +104,8 @@ def last_login(user) t( '.last_login.login_time_ago', - # @note The second `.try` is only here for the specs to work. + # @note The second `.try` is here for the specs and for setups that use a + # custom User class which may not have this attribute. last_login_time: time_ago_in_words(user.try(:last_sign_in_at)) ).capitalize end diff --git a/admin/app/controllers/solidus_admin/users_controller.rb b/admin/app/controllers/solidus_admin/users_controller.rb index 7f674b4e09e..5a679a4d2e1 100644 --- a/admin/app/controllers/solidus_admin/users_controller.rb +++ b/admin/app/controllers/solidus_admin/users_controller.rb @@ -23,6 +23,14 @@ def index end end + def edit + set_user + + respond_to do |format| + format.html { render component('users/edit').new(user: @user) } + end + end + def destroy @users = Spree.user_class.where(id: params[:id]) @@ -34,6 +42,10 @@ def destroy private + def set_user + @user = Spree.user_class.find(params[:id]) + end + def user_params params.require(:user).permit(:user_id, permitted_user_attributes) end diff --git a/admin/config/routes.rb b/admin/config/routes.rb index 61f5a729799..afbb4be37c5 100644 --- a/admin/config/routes.rb +++ b/admin/config/routes.rb @@ -45,7 +45,7 @@ end end - admin_resources :users, only: [:index, :destroy] + admin_resources :users, only: [:index, :edit, :destroy] admin_resources :promotions, only: [:index, :destroy] admin_resources :properties, only: [:index, :destroy] admin_resources :option_types, only: [:index, :destroy], sortable: true diff --git a/admin/spec/features/users_spec.rb b/admin/spec/features/users_spec.rb index f0e88296031..36cd9eba915 100644 --- a/admin/spec/features/users_spec.rb +++ b/admin/spec/features/users_spec.rb @@ -3,7 +3,11 @@ require "spec_helper" describe "Users", :js, type: :feature do - before { sign_in create(:admin_user, email: "admin@example.com") } + let(:admin) { create(:admin_user, email: "admin@example.com") } + + before do + sign_in admin + end it "lists users and allows deleting them" do create(:user, email: "customer@example.com") @@ -52,4 +56,57 @@ expect(page).not_to have_content("Never") end end + + context "when editing an existing user" do + before do + # This is needed for the actions which are still powered by the backend + # and not the new admin. (#update, etc.) + stub_authorization!(admin) + + create(:user, email: "customer@example.com") + visit "/admin/users" + find_row("customer@example.com").click + end + + it "shows the edit page" do + expect(page).to have_content("Users / customer@example.com") + expect(page).to have_content("Lifetime Stats") + expect(page).to have_content("Roles") + expect(find("label", text: /admin/i).find("input[type=checkbox]").checked?).to eq(false) + end + + it "allows editing of the existing user" do + # API key interactions + expect(page).to have_content("No key") + click_on "Generate API key" + expect(page).to have_content("Key generated") + expect(page).to have_content("(hidden)") + + click_on "Regenerate key" + expect(page).to have_content("Key generated") + expect(page).to have_content("(hidden)") + + click_on "Clear key" + expect(page).to have_content("Key cleared") + expect(page).to have_content("No key") + + # Update user + within("form.edit_user") do + fill_in "Email", with: "dogtown@example.com" + find("label", text: /admin/i).find("input[type=checkbox]").check + click_on "Update" + end + + expect(page).to have_content("Users / dogtown@example.com") + expect(find("label", text: /admin/i).find("input[type=checkbox]").checked?).to eq(true) + + # Cancel out of editing + within("form.edit_user") do + fill_in "Email", with: "newemail@example.com" + click_on "Cancel" + end + + expect(page).not_to have_content("newemail@example.com") + end + end end diff --git a/admin/spec/requests/solidus_admin/users_spec.rb b/admin/spec/requests/solidus_admin/users_spec.rb new file mode 100644 index 00000000000..59df8ac3c6c --- /dev/null +++ b/admin/spec/requests/solidus_admin/users_spec.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe "SolidusAdmin::UsersController", type: :request do + let(:admin_user) { create(:admin_user) } + let(:user) { create(:user) } + + before do + allow_any_instance_of(SolidusAdmin::BaseController).to receive(:spree_current_user).and_return(admin_user) + end + + describe "GET /index" do + it "renders the index template with a 200 OK status" do + get solidus_admin.users_path + expect(response).to have_http_status(:ok) + end + end + + describe "GET /edit" do + it "renders the edit template with a 200 OK status" do + get solidus_admin.edit_user_path(user) + expect(response).to have_http_status(:ok) + end + end + + describe "DELETE /destroy" do + it "deletes the user and redirects to the index page with a 303 See Other status" do + # Ensure the user exists prior to deletion + user + + expect { + delete solidus_admin.user_path(user) + }.to change(Spree.user_class, :count).by(-1) + + expect(response).to redirect_to(solidus_admin.users_path) + expect(response).to have_http_status(:see_other) + end + + it "displays a success flash message after deletion" do + delete solidus_admin.user_path(user) + follow_redirect! + expect(response.body).to include("Users were successfully removed.") + end + end + + describe "search functionality" do + before do + create(:user, email: "test@example.com") + create(:user, email: "another@example.com") + end + + it "filters users based on search parameters" do + get solidus_admin.users_path, params: { q: { email_cont: "test" } } + expect(response.body).to include("test@example.com") + expect(response.body).not_to include("another@example.com") + end + end +end diff --git a/core/config/locales/en.yml b/core/config/locales/en.yml index d77fd1e12dd..5968fab8ad5 100644 --- a/core/config/locales/en.yml +++ b/core/config/locales/en.yml @@ -1855,6 +1855,7 @@ en: order_approved: Order approved order_canceled: Order canceled order_completed: Order completed + order_count: Order Count order_details: Order Details order_email_resent: Order Email Resent order_information: Order Information From 22e8e803c6adddb547382ab3c296e8ec3c4b6036 Mon Sep 17 00:00:00 2001 From: Alessandro Lepore Date: Thu, 3 Oct 2024 12:30:15 +0200 Subject: [PATCH 157/834] Format date with #to_fs The #to_s(format) method was deprecated and removed from Rails 7.1. This is causing `ActionView::Template::Error wrong number of arguments (given 1, expected 0)` when a tax rate with an expiration date is displayed. --- backend/app/views/spree/admin/tax_rates/index.html.erb | 2 +- backend/spec/features/admin/configuration/tax_rates_spec.rb | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/app/views/spree/admin/tax_rates/index.html.erb b/backend/app/views/spree/admin/tax_rates/index.html.erb index 84c679e5685..be2b20094be 100644 --- a/backend/app/views/spree/admin/tax_rates/index.html.erb +++ b/backend/app/views/spree/admin/tax_rates/index.html.erb @@ -81,7 +81,7 @@ <%= tax_rate.amount %> <%= tax_rate.included_in_price? ? t('spree.say_yes') : t('spree.say_no') %> <%= tax_rate.show_rate_in_label? ? t('spree.say_yes') : t('spree.say_no') %> - <%= tax_rate.expires_at.to_date.to_s(:short_date) if tax_rate.expires_at %> + <%= tax_rate.expires_at.to_date.to_fs(:short_date) if tax_rate.expires_at %> <%= tax_rate.calculator && tax_rate.calculator.class.model_name.human %> <% if can?(:update, tax_rate) %> diff --git a/backend/spec/features/admin/configuration/tax_rates_spec.rb b/backend/spec/features/admin/configuration/tax_rates_spec.rb index 465728f4ede..713f6b7ca9d 100644 --- a/backend/spec/features/admin/configuration/tax_rates_spec.rb +++ b/backend/spec/features/admin/configuration/tax_rates_spec.rb @@ -25,6 +25,7 @@ click_link "Tax Rates" click_link "New Tax Rate" fill_in "Rate", with: "0.05" + fill_in "Expiration Date", with: "2050-01-01" click_button "Create" expect(page).to have_content("Tax Rate has been successfully created!") end From 3cb9d799bab12ab78d30b741e09c2224399f8038 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 9 Oct 2024 08:44:01 +0200 Subject: [PATCH 158/834] Performance: Remove includes from Spree::Variant#options_text While it seems like a good idea to include the option types in this instance method on Spree::Variant, it's actually not. This method is likely used in a view context, where the controller will have preloaded the option types and option values already - running `option_values.includes(:option_type)` here will then generate a query even though things are preloaded already. --- core/app/models/spree/variant.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/app/models/spree/variant.rb b/core/app/models/spree/variant.rb index 1c20b3f7932..cf7852ecb7c 100644 --- a/core/app/models/spree/variant.rb +++ b/core/app/models/spree/variant.rb @@ -194,7 +194,7 @@ def is_backorderable? # # @return [String] a sentence-ified string of option values. def options_text - values = option_values.includes(:option_type).sort_by do |option_value| + values = option_values.sort_by do |option_value| option_value.option_type.position end From 53f4b28aae40e2fdd22ba5eea4ab3ce42fba67ba Mon Sep 17 00:00:00 2001 From: Madeline Collier Date: Mon, 7 Oct 2024 14:51:59 +0200 Subject: [PATCH 159/834] Add new users admin addresses page This migrates the `users/:id/addresses` page to the new admin. Instead of squashing everything into one controller action with an `if request.put?` wrapper (like the legacy backend controller did), I chose to break this new admin into two actions separated by their methods and responsibilities. The old admin page did not have anything in the way of validations or error messages, it would simply fail to update the address and leave it as is if the user provided invalid values. (While also showing a message saying that the update was successful?) This page improves upon that flow, adding validations to the address forms when their values are invalid and preventing the update, but since we are submitting an update against the user and not the address directly, we still need to do some extra work to run the validations and generate errors for the form (rather than silently failing to update like the legacy back-end controller used to do). --- .../users/addresses/component.html.erb | 56 +++++++++++++++ .../users/addresses/component.rb | 72 +++++++++++++++++++ .../users/addresses/component.yml | 18 +++++ .../solidus_admin/users/edit/component.rb | 15 ++-- .../solidus_admin/users_controller.rb | 49 ++++++++++++- admin/config/locales/users.en.yml | 6 ++ admin/config/routes.rb | 8 ++- admin/spec/features/users_spec.rb | 52 ++++++++++++++ .../spec/requests/solidus_admin/users_spec.rb | 61 +++++++++++++++- 9 files changed, 324 insertions(+), 13 deletions(-) create mode 100644 admin/app/components/solidus_admin/users/addresses/component.html.erb create mode 100644 admin/app/components/solidus_admin/users/addresses/component.rb create mode 100644 admin/app/components/solidus_admin/users/addresses/component.yml diff --git a/admin/app/components/solidus_admin/users/addresses/component.html.erb b/admin/app/components/solidus_admin/users/addresses/component.html.erb new file mode 100644 index 00000000000..b2d41f37e3c --- /dev/null +++ b/admin/app/components/solidus_admin/users/addresses/component.html.erb @@ -0,0 +1,56 @@ +<%= page do %> + <%= page_header do %> + <%= page_header_back(solidus_admin.users_path) %> + <%= page_header_title(t(".title", email: @user.email)) %> + + <%= page_header_actions do %> + <%= render component("ui/button").new(tag: :a, text: t(".create_order_for_user"), href: spree.new_admin_order_path(user_id: @user.id)) %> + <% end %> + <% end %> + + <%= page_header do %> + <% tabs.each do |tab| %> + <%= render(component("ui/button").new(tag: :a, scheme: :ghost, text: tab[:text], 'aria-current': tab[:current], href: tab[:href])) %> + <% end %> + <% end %> + + <%= page_with_sidebar do %> + <%= page_with_sidebar_main do %> + <%= render component('ui/panel').new(title: t(".billing_address")) do %> + <%= form_for @user, url: solidus_admin.update_addresses_user_path(@user), method: :put, html: { id: "#{form_id}_billing", autocomplete: "off", class: "bill_address" } do |f| %> + + <%= render component('ui/forms/address').new(address: @bill_address, name: "user[bill_address_attributes]") %> +
+ <%= render component("ui/button").new(tag: :button, text: t(".update"), form: "#{form_id}_billing") %> + <%= render component("ui/button").new(tag: :a, text: t(".cancel"), href: solidus_admin.addresses_user_path(@user), scheme: :secondary) %> +
+ <% end %> + <% end %> + + <%= render component('ui/panel').new(title: t(".shipping_address")) do %> + <%= form_for @user, url: solidus_admin.update_addresses_user_path(@user), method: :put, html: { id: "#{form_id}_shipping", autocomplete: "off", class: "ship_address" } do |f| %> + + <%= render component('ui/forms/address').new(address: @ship_address, name: "user[ship_address_attributes]") %> +
+ <%= render component("ui/button").new(tag: :button, text: t(".update"), form: "#{form_id}_shipping") %> + <%= render component("ui/button").new(tag: :a, text: t(".cancel"), href: solidus_admin.addresses_user_path(@user), scheme: :secondary) %> +
+ <% end %> + <% end %> + <% end %> + + <%= page_with_sidebar_aside do %> + <%= render component("ui/panel").new(title: t("spree.lifetime_stats")) do %> + <%= render component("ui/details_list").new( + items: [ + { label: t("spree.total_sales"), value: @user.display_lifetime_value.to_html }, + { label: t("spree.order_count"), value: @user.order_count.to_i }, + { label: t("spree.average_order_value"), value: @user.display_average_order_value.to_html }, + { label: t("spree.member_since"), value: @user.created_at.to_date }, + { label: t(".last_active"), value: last_login(@user) }, + ] + ) %> + <% end %> + <% end %> + <% end %> +<% end %> diff --git a/admin/app/components/solidus_admin/users/addresses/component.rb b/admin/app/components/solidus_admin/users/addresses/component.rb new file mode 100644 index 00000000000..bc7665b3cc6 --- /dev/null +++ b/admin/app/components/solidus_admin/users/addresses/component.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +class SolidusAdmin::Users::Addresses::Component < SolidusAdmin::BaseComponent + include SolidusAdmin::Layout::PageHelpers + + def initialize(user:, address: nil, type: nil) + @user = user + @bill_address = bill_address(address, type) + @ship_address = ship_address(address, type) + end + + def form_id + @form_id ||= "#{stimulus_id}--form-#{@user.id}" + end + + def tabs + [ + { + text: t('.account'), + href: solidus_admin.user_path(@user), + current: false, + }, + { + text: t('.addresses'), + href: solidus_admin.addresses_user_path(@user), + current: true, + }, + { + text: t('.order_history'), + href: spree.orders_admin_user_path(@user), + current: false, + }, + { + text: t('.items'), + href: spree.items_admin_user_path(@user), + current: false, + }, + { + text: t('.store_credit'), + href: spree.admin_user_store_credits_path(@user), + current: false, + }, + ] + end + + def last_login(user) + return t('.last_login.never') if user.try(:last_sign_in_at).blank? + + t( + '.last_login.login_time_ago', + # @note The second `.try` is here for the specs and for setups that use a + # custom User class which may not have this attribute. + last_login_time: time_ago_in_words(user.try(:last_sign_in_at)) + ).capitalize + end + + def bill_address(address, type) + if address.present? && type == "bill" + address + else + @user.bill_address || Spree::Address.build_default + end + end + + def ship_address(address, type) + if address.present? && type == "ship" + address + else + @user.ship_address || Spree::Address.build_default + end + end +end diff --git a/admin/app/components/solidus_admin/users/addresses/component.yml b/admin/app/components/solidus_admin/users/addresses/component.yml new file mode 100644 index 00000000000..6872c1d5cd5 --- /dev/null +++ b/admin/app/components/solidus_admin/users/addresses/component.yml @@ -0,0 +1,18 @@ +en: + title: "Users / %{email} / Addresses" + account: Account + addresses: Addresses + order_history: Order History + items: Items + store_credit: Store Credit + last_active: Last Active + last_login: + login_time_ago: "%{last_login_time} ago" + never: Never + invitation_sent: Invitation sent + create_order_for_user: Create order for this user + update: Update + cancel: Cancel + back: Back + billing_address: Billing Address + shipping_address: Shipping Address diff --git a/admin/app/components/solidus_admin/users/edit/component.rb b/admin/app/components/solidus_admin/users/edit/component.rb index 78f1b8a82eb..1263af87cfd 100644 --- a/admin/app/components/solidus_admin/users/edit/component.rb +++ b/admin/app/components/solidus_admin/users/edit/component.rb @@ -15,32 +15,31 @@ def tabs [ { text: t('.account'), - href: solidus_admin.users_path, - current: action_name == "show", + href: solidus_admin.user_path(@user), + current: action_name == "edit", }, { text: t('.addresses'), - href: spree.addresses_admin_user_path(@user), - # @todo: update this "current" logic once folded into new admin - current: action_name != "show", + href: solidus_admin.addresses_user_path(@user), + current: action_name == "addresses", }, { text: t('.order_history'), href: spree.orders_admin_user_path(@user), # @todo: update this "current" logic once folded into new admin - current: action_name != "show", + current: action_name != "edit", }, { text: t('.items'), href: spree.items_admin_user_path(@user), # @todo: update this "current" logic once folded into new admin - current: action_name != "show", + current: action_name != "edit", }, { text: t('.store_credit'), href: spree.admin_user_store_credits_path(@user), # @todo: update this "current" logic once folded into new admin - current: action_name != "show", + current: action_name != "edit", }, ] end diff --git a/admin/app/controllers/solidus_admin/users_controller.rb b/admin/app/controllers/solidus_admin/users_controller.rb index 5a679a4d2e1..59ac12d44c2 100644 --- a/admin/app/controllers/solidus_admin/users_controller.rb +++ b/admin/app/controllers/solidus_admin/users_controller.rb @@ -3,6 +3,9 @@ module SolidusAdmin class UsersController < SolidusAdmin::BaseController include SolidusAdmin::ControllerHelpers::Search + include Spree::Core::ControllerHelpers::StrongParameters + + before_action :set_user, only: [:edit, :addresses, :update_addresses] search_scope(:all, default: true) search_scope(:customers) { _1.left_outer_joins(:role_users).where(role_users: { id: nil }) } @@ -23,9 +26,31 @@ def index end end - def edit - set_user + def addresses + respond_to do |format| + format.turbo_stream { render turbo_stream: '' } + format.html { render component('users/addresses').new(user: @user) } + end + end + + def update_addresses + set_address_from_params + if @address.valid? && @user.update(user_params) + flash[:success] = t(".#{@type}.success") + + respond_to do |format| + format.turbo_stream { render turbo_stream: '' } + format.html { render component('users/addresses').new(user: @user) } + end + else + respond_to do |format| + format.html { render component('users/addresses').new(user: @user, address: @address, type: @type), status: :unprocessable_entity } + end + end + end + + def edit respond_to do |format| format.html { render component('users/edit').new(user: @user) } end @@ -47,7 +72,25 @@ def set_user end def user_params - params.require(:user).permit(:user_id, permitted_user_attributes) + params.require(:user).permit( + :user_id, + permitted_user_attributes, + bill_address_attributes: permitted_address_attributes, + ship_address_attributes: permitted_address_attributes + ) + end + + # @note This method is used to generate validation errors on the address. + # Since the update is being performed via the @user, and not directly on + # the @address, we sadly don't seem to get these errors automatically. + def set_address_from_params + if user_params.key?(:bill_address_attributes) + @address = Spree::Address.new(user_params[:bill_address_attributes]) + @type = "bill" + elsif user_params.key?(:ship_address_attributes) + @address = Spree::Address.new(user_params[:ship_address_attributes]) + @type = "ship" + end end def authorization_subject diff --git a/admin/config/locales/users.en.yml b/admin/config/locales/users.en.yml index c15e9fb0faa..5fda686929c 100644 --- a/admin/config/locales/users.en.yml +++ b/admin/config/locales/users.en.yml @@ -4,3 +4,9 @@ en: title: "Users" destroy: success: "Users were successfully removed." + update_addresses: + bill: + success: "Billing Address has been successfully updated." + ship: + success: "Shipping Address has been successfully updated." + error: "Address could not be updated." diff --git a/admin/config/routes.rb b/admin/config/routes.rb index afbb4be37c5..ac4693d32d5 100644 --- a/admin/config/routes.rb +++ b/admin/config/routes.rb @@ -45,7 +45,13 @@ end end - admin_resources :users, only: [:index, :edit, :destroy] + admin_resources :users, only: [:index, :edit, :destroy] do + member do + get :addresses + put :update_addresses + end + end + admin_resources :promotions, only: [:index, :destroy] admin_resources :properties, only: [:index, :destroy] admin_resources :option_types, only: [:index, :destroy], sortable: true diff --git a/admin/spec/features/users_spec.rb b/admin/spec/features/users_spec.rb index 36cd9eba915..5884e3bcc25 100644 --- a/admin/spec/features/users_spec.rb +++ b/admin/spec/features/users_spec.rb @@ -109,4 +109,56 @@ expect(page).not_to have_content("newemail@example.com") end end + + context "when editing a user's addresses" do + before do + create(:user_with_addresses, email: "customer@example.com") + visit "/admin/users" + find_row("customer@example.com").click + click_on "Addresses" + end + + it "shows the address page" do + expect(page).to have_content("Users / customer@example.com / Addresses") + expect(page).to have_content("Lifetime Stats") + expect(page).to have_content("Billing Address") + expect(page).to be_axe_clean + end + + it "allows editing of the existing address" do + # Invalid submission + within("form.ship_address") do + fill_in "Name", with: "" + fill_in "Street Address", with: "" + click_on "Update" + end + expect(page).to have_content("can't be blank").twice + + # Valid submission + within("form.bill_address") do + fill_in "Name", with: "Galadriel" + click_on "Update" + end + expect(page).to have_content("Billing Address has been successfully updated.") + + # Valid submission + within("form.ship_address") do + fill_in "Name", with: "Elrond" + click_on "Update" + end + expect(page).to have_content("Shipping Address has been successfully updated.") + + # Cancel submission + within("form.bill_address") do + fill_in "Name", with: "Smeagol" + click_on "Cancel" + end + expect(page).to have_content("Users / customer@example.com / Addresses") + expect(page).not_to have_content("Smeagol") + + # The address forms weirdly only have values rather than actual text on the page. + expect(page).to have_field("user[bill_address_attributes][name]", with: "Galadriel") + expect(page).to have_field("user[ship_address_attributes][name]", with: "Elrond") + end + end end diff --git a/admin/spec/requests/solidus_admin/users_spec.rb b/admin/spec/requests/solidus_admin/users_spec.rb index 59df8ac3c6c..36847fd639f 100644 --- a/admin/spec/requests/solidus_admin/users_spec.rb +++ b/admin/spec/requests/solidus_admin/users_spec.rb @@ -4,7 +4,42 @@ RSpec.describe "SolidusAdmin::UsersController", type: :request do let(:admin_user) { create(:admin_user) } - let(:user) { create(:user) } + let(:user) { create(:user_with_addresses) } + let(:address) { create(:address) } + + let(:valid_address_params) do + { + user: { + bill_address_attributes: { + name: address.name, + address1: address.address1, + address2: address.address2, + city: address.city, + zipcode: address.zipcode, + state_id: address.state_id, + country_id: address.country_id, + phone: address.phone + } + } + } + end + + # Invalid due to missing "name" field. + let(:invalid_address_params) do + { + user: { + bill_address_attributes: { + address1: address.address1, + address2: address.address2, + city: address.city, + zipcode: address.zipcode, + state_id: address.state_id, + country_id: address.country_id, + phone: address.phone + } + } + } + end before do allow_any_instance_of(SolidusAdmin::BaseController).to receive(:spree_current_user).and_return(admin_user) @@ -44,6 +79,30 @@ end end + describe "GET /addresses" do + it "renders the addresses template with a 200 OK status" do + get solidus_admin.addresses_user_path(user) + expect(response).to have_http_status(:ok) + end + end + + describe "PUT /update_addresses" do + context "with valid address parameters" do + it "updates the user's address and redirects with a success message" do + put solidus_admin.update_addresses_user_path(user), params: valid_address_params + expect(response).to have_http_status(:ok) + expect(response.body).to include("Address has been successfully updated.") + end + end + + context "with invalid address parameters" do + it "does not update the user's address and renders the addresses component with errors" do + put solidus_admin.update_addresses_user_path(user), params: invalid_address_params + expect(response).to have_http_status(:unprocessable_entity) + end + end + end + describe "search functionality" do before do create(:user, email: "test@example.com") From d04fca27980b9f39bad8e2be1a9f4ee58db61d75 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 9 Oct 2024 08:23:57 +0200 Subject: [PATCH 160/834] Introduce concept of template and non_template variants All products have variants, but not all variants can actually be sold. When a product has option types, the sellable variants are those that are assigned to option values, whereas the "master" variant is only there as a template for other variants to be created. I'm using the language "template variant" because "master variant" because it more closely matches what the `is_master` flag on the Spree::Variant actually does. --- core/app/models/spree/variant.rb | 5 +++++ core/spec/models/spree/variant_spec.rb | 22 ++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/core/app/models/spree/variant.rb b/core/app/models/spree/variant.rb index 1c20b3f7932..b1de1756a60 100644 --- a/core/app/models/spree/variant.rb +++ b/core/app/models/spree/variant.rb @@ -80,6 +80,11 @@ class Variant < Spree::Base after_destroy :destroy_option_values_variants + scope :template_variants, -> do + left_joins(product: { option_types: :option_values }).where(is_master: true).where.not(spree_option_values: { id: nil }).reorder(nil).distinct + end + scope :non_template_variants, -> { where.not(id: template_variants) } + # Returns variants that are in stock. When stock locations are provided as # a parameter, the scope is limited to variants that are in stock in the # provided stock locations. diff --git a/core/spec/models/spree/variant_spec.rb b/core/spec/models/spree/variant_spec.rb index ee8a3f58290..b24e8b413f6 100644 --- a/core/spec/models/spree/variant_spec.rb +++ b/core/spec/models/spree/variant_spec.rb @@ -7,6 +7,28 @@ let!(:variant) { create(:variant) } + describe '.non_template_variants' do + let(:option_type) { create(:option_type, option_values: [option_value]) } + let(:option_value) { build(:option_value) } + let(:product) { create(:product, option_types: [option_type]) } + let!(:variant) { create(:variant, product: product) } + + subject { described_class.non_template_variants } + + it { is_expected.to contain_exactly(variant) } + end + + describe '.template_variants' do + let(:option_type) { create(:option_type, option_values: [option_value]) } + let(:option_value) { build(:option_value) } + let(:product) { create(:product, option_types: [option_type]) } + let!(:variant) { create(:variant, product: product) } + + subject { described_class.template_variants } + + it { is_expected.to contain_exactly(product.master) } + end + describe 'delegates' do let(:product) { build(:product) } let(:variant) { build(:variant, product: product) } From 6b4d76dfdce276303523c02fcee261d179e38eee Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 9 Oct 2024 08:30:59 +0200 Subject: [PATCH 161/834] Variant search: Exclude template variants Currently, one can add template variants to one's cart, but that should not be possible. This commit makes sure that the variant search won't return template variants. --- core/lib/spree/core/search/variant.rb | 4 ++-- core/spec/lib/search/variant_spec.rb | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/core/lib/spree/core/search/variant.rb b/core/lib/spree/core/search/variant.rb index dc964a15aca..ca9fa77259d 100644 --- a/core/lib/spree/core/search/variant.rb +++ b/core/lib/spree/core/search/variant.rb @@ -22,7 +22,7 @@ class Variant def initialize(query_string, scope: Spree::Variant.all) @query_string = query_string - @scope = scope + @scope = scope.non_template_variants end # Searches the variants table using the ransack 'search_terms' defined on the class. @@ -39,7 +39,7 @@ def results @scope.ransack(search_term_params(word)).result.pluck(:id) end - Spree::Variant.where(id: matches.inject(:&)) + @scope.where(id: matches.inject(:&)) end private diff --git a/core/spec/lib/search/variant_spec.rb b/core/spec/lib/search/variant_spec.rb index 8d210e1d81c..bb633420774 100644 --- a/core/spec/lib/search/variant_spec.rb +++ b/core/spec/lib/search/variant_spec.rb @@ -27,6 +27,16 @@ def refute_found(query_string, variant) it { refute_found("bca", variant) } end + context "with a template variant" do + let!(:option_type) { create(:option_type, option_values: [option_value]) } + let(:option_value) { build(:option_value) } + let(:product) { create(:product, option_types: [option_type], sku: "TEMPLATE") } + let(:variant) { create(:variant, product: product, sku: "NOT_TEMPLATE") } + + it { refute_found("TEMPLATE", product.master) } + it { assert_found("NOT_TEMPLATE", variant) } + end + context "by product" do it { assert_found("My Special Product", variant) } it { assert_found("My Spec", variant) } From eacfa052f09220f11a282e082bdcfe62e33a7c65 Mon Sep 17 00:00:00 2001 From: Nick Van Doorn Date: Wed, 9 Oct 2024 14:48:18 -0700 Subject: [PATCH 162/834] Increase shared memory to 512MB Prior to this change, we were seeing some crashes running feature specs after exceeding the 256MB shared memory limit. Co-authored-by: An Stewart --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 4712f3444a7..5f40034d5c9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -18,7 +18,7 @@ services: - postgres:/var/lib/postgresql/data:cached app: - shm_size: '256mb' + shm_size: '512mb' build: context: .dockerdev dockerfile: Dockerfile From 403721a412ce18ec9b3feaecbf4c033e079c5811 Mon Sep 17 00:00:00 2001 From: Nick Van Doorn Date: Wed, 9 Oct 2024 14:51:25 -0700 Subject: [PATCH 163/834] Update default Rails version in Docker config There is an incompatibility between our test suite and Rails versions `< 7.1.0`, so we provide `7.1.0` as the new default version. Co-authored-by: An Stewart --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 5f40034d5c9..0ad28fedc22 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -34,7 +34,7 @@ services: CAPYBARA_DRIVER: selenium_chrome_headless_docker_friendly DB_USERNAME: root DB_PASSWORD: password - RAILS_VERSION: ${RAILS_VERSION:-~> 7.0.2} + RAILS_VERSION: ${RAILS_VERSION:-~> 7.1.0} DB_ALL: "1" DB_MYSQL_HOST: mysql DB_POSTGRES_HOST: postgres From 75970042615a2f39eeefc60231c3b4412103918f Mon Sep 17 00:00:00 2001 From: Chris Todorov Date: Thu, 10 Oct 2024 14:14:50 -0700 Subject: [PATCH 164/834] Add filtering by store to orders index component In the legacy admin the order search supported a filter by store. This behaviour is not in the new admin yet, so we're introducing it with this change. Co-authored-by: Harmony Evangelina Co-authored-by: Tom Van Manen --- .../solidus_admin/orders/index/component.rb | 7 +++++ .../solidus_admin/orders/index/component.yml | 1 + admin/spec/features/orders/index_spec.rb | 29 ++++++++++++++++++- 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/admin/app/components/solidus_admin/orders/index/component.rb b/admin/app/components/solidus_admin/orders/index/component.rb index 92e37feadc6..3970e122e35 100644 --- a/admin/app/components/solidus_admin/orders/index/component.rb +++ b/admin/app/components/solidus_admin/orders/index/component.rb @@ -77,6 +77,13 @@ def filters option ] end + }, + { + label: t('.filters.store'), + combinator: 'or', + attribute: "store_id", + predicate: "eq", + options: Spree::Store.all.pluck(:name, :id) } ] end diff --git a/admin/app/components/solidus_admin/orders/index/component.yml b/admin/app/components/solidus_admin/orders/index/component.yml index e40bbe916f3..bbc0c79ff70 100644 --- a/admin/app/components/solidus_admin/orders/index/component.yml +++ b/admin/app/components/solidus_admin/orders/index/component.yml @@ -5,6 +5,7 @@ en: other: '%{count} Items' filters: status: Status + store: Store shipment_state: Shipment State payment_state: Payment State promotions: Promotions diff --git a/admin/spec/features/orders/index_spec.rb b/admin/spec/features/orders/index_spec.rb index ddc8f83f5ff..474edfe9439 100644 --- a/admin/spec/features/orders/index_spec.rb +++ b/admin/spec/features/orders/index_spec.rb @@ -5,7 +5,7 @@ describe "Orders", type: :feature do before { sign_in create(:admin_user, email: 'admin@example.com') } - it "lists products", :js do + it "lists orders", :js do create(:order, number: "R123456789", total: 19.99) visit "/admin/orders" @@ -15,4 +15,31 @@ expect(page).to have_content("$19.99") expect(page).to be_axe_clean end + + context "with multiple stores", :js do + let!(:order_in_default_store) { create :order } + let(:another_store) { create :store, name: "Another Store" } + let!(:order_in_another_store) { create :order, store: another_store } + + it "can filter orders by store" do + visit solidus_admin.orders_path + + click_on "In Progress" + + expect(page).to have_content(order_in_default_store.number) + expect(page).to have_content(order_in_another_store.number) + + click_on "Filter" + + within("div[role='search']") do + find('details', text: "Store").click + expect(page).to have_content("Another Store") + + find('label', text: "Another Store").click + end + + expect(page).to have_content(order_in_another_store.number) + expect(page).to_not have_content(order_in_default_store.number) + end + end end From 75aac689f52c3e987cf1390fc422c28c2ca610f5 Mon Sep 17 00:00:00 2001 From: Madeline Collier Date: Thu, 10 Oct 2024 13:11:53 +0200 Subject: [PATCH 165/834] Add new users admin order history page This migrates the `users/:id/orders` page from the legacy soldius_backend to the new solidus_admin. --- .../users/addresses/component.rb | 2 +- .../solidus_admin/users/edit/component.rb | 5 +- .../users/orders/component.html.erb | 52 +++++++ .../solidus_admin/users/orders/component.rb | 145 ++++++++++++++++++ .../solidus_admin/users/orders/component.yml | 16 ++ .../solidus_admin/users_controller.rb | 16 +- admin/config/routes.rb | 1 + admin/spec/features/users_spec.rb | 45 ++++++ .../spec/requests/solidus_admin/users_spec.rb | 10 ++ 9 files changed, 287 insertions(+), 5 deletions(-) create mode 100644 admin/app/components/solidus_admin/users/orders/component.html.erb create mode 100644 admin/app/components/solidus_admin/users/orders/component.rb create mode 100644 admin/app/components/solidus_admin/users/orders/component.yml diff --git a/admin/app/components/solidus_admin/users/addresses/component.rb b/admin/app/components/solidus_admin/users/addresses/component.rb index bc7665b3cc6..6116312b866 100644 --- a/admin/app/components/solidus_admin/users/addresses/component.rb +++ b/admin/app/components/solidus_admin/users/addresses/component.rb @@ -27,7 +27,7 @@ def tabs }, { text: t('.order_history'), - href: spree.orders_admin_user_path(@user), + href: solidus_admin.orders_user_path(@user), current: false, }, { diff --git a/admin/app/components/solidus_admin/users/edit/component.rb b/admin/app/components/solidus_admin/users/edit/component.rb index 1263af87cfd..19ff9adbc8e 100644 --- a/admin/app/components/solidus_admin/users/edit/component.rb +++ b/admin/app/components/solidus_admin/users/edit/component.rb @@ -25,9 +25,8 @@ def tabs }, { text: t('.order_history'), - href: spree.orders_admin_user_path(@user), - # @todo: update this "current" logic once folded into new admin - current: action_name != "edit", + href: solidus_admin.orders_user_path(@user), + current: action_name == "orders", }, { text: t('.items'), diff --git a/admin/app/components/solidus_admin/users/orders/component.html.erb b/admin/app/components/solidus_admin/users/orders/component.html.erb new file mode 100644 index 00000000000..a386d089b84 --- /dev/null +++ b/admin/app/components/solidus_admin/users/orders/component.html.erb @@ -0,0 +1,52 @@ +<%= page do %> + <%= page_header do %> + <%= page_header_back(solidus_admin.users_path) %> + <%= page_header_title(t(".title", email: @user.email)) %> + + <%= page_header_actions do %> + <%= render component("ui/button").new(tag: :a, text: t(".create_order_for_user"), href: spree.new_admin_order_path(user_id: @user.id)) %> + <% end %> + <% end %> + + <%= page_header do %> + <% tabs.each do |tab| %> + <%= render(component("ui/button").new(tag: :a, scheme: :ghost, text: tab[:text], 'aria-current': tab[:current], href: tab[:href])) %> + <% end %> + <% end %> + + <%= page_with_sidebar do %> + <%= page_with_sidebar_main do %> + <%= render component('ui/panel').new(title: t(".order_history")) do %> + <% if @orders.present? %> + <%= render component('ui/table').new( + id: stimulus_id, + data: { + class: model_class, + rows: rows, + fade: -> { row_fade(_1) }, + columns: columns, + url: -> { row_url(_1) }, + }, + )%> + <% else %> + <%= t(".no_orders_found") %> + <%= render component("ui/button").new(tag: :a, text: t(".create_one"), href: spree.new_admin_order_path(user_id: @user.id)) %> + <% end %> + <% end %> + <% end %> + + <%= page_with_sidebar_aside do %> + <%= render component("ui/panel").new(title: t("spree.lifetime_stats")) do %> + <%= render component("ui/details_list").new( + items: [ + { label: t("spree.total_sales"), value: @user.display_lifetime_value.to_html }, + { label: t("spree.order_count"), value: @user.order_count.to_i }, + { label: t("spree.average_order_value"), value: @user.display_average_order_value.to_html }, + { label: t("spree.member_since"), value: @user.created_at.to_date }, + { label: t(".last_active"), value: last_login(@user) }, + ] + ) %> + <% end %> + <% end %> + <% end %> +<% end %> diff --git a/admin/app/components/solidus_admin/users/orders/component.rb b/admin/app/components/solidus_admin/users/orders/component.rb new file mode 100644 index 00000000000..ee57928111b --- /dev/null +++ b/admin/app/components/solidus_admin/users/orders/component.rb @@ -0,0 +1,145 @@ +# frozen_string_literal: true + +class SolidusAdmin::Users::Orders::Component < SolidusAdmin::BaseComponent + include SolidusAdmin::Layout::PageHelpers + + def initialize(user:, orders:) + @user = user + @orders = orders + end + + def form_id + @form_id ||= "#{stimulus_id}--form-#{@user.id}" + end + + def tabs + [ + { + text: t('.account'), + href: solidus_admin.user_path(@user), + current: false, + }, + { + text: t('.addresses'), + href: solidus_admin.addresses_user_path(@user), + current: false, + }, + { + text: t('.order_history'), + href: solidus_admin.orders_user_path(@user), + current: true, + }, + { + text: t('.items'), + href: spree.items_admin_user_path(@user), + current: false, + }, + { + text: t('.store_credit'), + href: spree.admin_user_store_credits_path(@user), + current: false, + }, + ] + end + + def last_login(user) + return t('.last_login.never') if user.try(:last_sign_in_at).blank? + + t( + '.last_login.login_time_ago', + # @note The second `.try` is only here for the specs to work. + last_login_time: time_ago_in_words(user.try(:last_sign_in_at)) + ).capitalize + end + + def model_class + Spree::Order + end + + def row_url(order) + spree.edit_admin_order_path(order) + end + + def rows + @orders + end + + def row_fade(_order) + false + end + + def columns + [ + number_column, + state_column, + date_column, + payment_column, + shipment_column, + total_column, + ] + end + + def number_column + { + header: :order, + data: ->(order) do + if !row_fade(order) + content_tag :div, order.number, class: 'font-semibold' + else + content_tag :div, order.number + end + end + } + end + + def state_column + { + header: :state, + data: ->(order) do + color = { + 'complete' => :green, + 'returned' => :red, + 'canceled' => :blue, + 'cart' => :graphite_light, + }[order.state] || :yellow + component('ui/badge').new(name: order.state.humanize, color: color) + end + } + end + + def date_column + { + header: :date, + data: ->(order) do + content_tag :div, l(order.created_at, format: :short) + end + } + end + + def total_column + { + header: :total, + data: ->(order) do + content_tag :div, number_to_currency(order.total) + end + } + end + + def payment_column + { + header: :payment, + data: ->(order) do + component('ui/badge').new(name: order.payment_state.humanize, color: order.paid? ? :green : :yellow) if order.payment_state? + end + } + end + + def shipment_column + { + header: :shipment, + data: ->(order) do + component('ui/badge').new(name: order.shipment_state.humanize, color: order.shipped? ? :green : :yellow) if order.shipment_state? + end + } + end +end diff --git a/admin/app/components/solidus_admin/users/orders/component.yml b/admin/app/components/solidus_admin/users/orders/component.yml new file mode 100644 index 00000000000..6c4b6f1368a --- /dev/null +++ b/admin/app/components/solidus_admin/users/orders/component.yml @@ -0,0 +1,16 @@ +en: + title: "Users / %{email} / Order History" + account: Account + addresses: Addresses + order_history: Order History + items: Items + store_credit: Store Credit + last_active: Last Active + last_login: + login_time_ago: "%{last_login_time} ago" + never: Never + invitation_sent: Invitation sent + create_order_for_user: Create order for this user + no_orders_found: No Orders found. + create_one: Create One + back: Back diff --git a/admin/app/controllers/solidus_admin/users_controller.rb b/admin/app/controllers/solidus_admin/users_controller.rb index 59ac12d44c2..8b1a7858598 100644 --- a/admin/app/controllers/solidus_admin/users_controller.rb +++ b/admin/app/controllers/solidus_admin/users_controller.rb @@ -5,7 +5,7 @@ class UsersController < SolidusAdmin::BaseController include SolidusAdmin::ControllerHelpers::Search include Spree::Core::ControllerHelpers::StrongParameters - before_action :set_user, only: [:edit, :addresses, :update_addresses] + before_action :set_user, only: [:edit, :addresses, :update_addresses, :orders] search_scope(:all, default: true) search_scope(:customers) { _1.left_outer_joins(:role_users).where(role_users: { id: nil }) } @@ -50,6 +50,14 @@ def update_addresses end end + def orders + set_orders + + respond_to do |format| + format.html { render component('users/orders').new(user: @user, orders: @orders) } + end + end + def edit respond_to do |format| format.html { render component('users/edit').new(user: @user) } @@ -93,6 +101,12 @@ def set_address_from_params end end + def set_orders + params[:q] ||= {} + @search = Spree::Order.reverse_chronological.ransack(params[:q].merge(user_id_eq: @user.id)) + @orders = @search.result.page(params[:page]).per(Spree::Config[:admin_products_per_page]) + end + def authorization_subject Spree.user_class end diff --git a/admin/config/routes.rb b/admin/config/routes.rb index ac4693d32d5..d33b23d60cc 100644 --- a/admin/config/routes.rb +++ b/admin/config/routes.rb @@ -49,6 +49,7 @@ member do get :addresses put :update_addresses + get :orders end end diff --git a/admin/spec/features/users_spec.rb b/admin/spec/features/users_spec.rb index 5884e3bcc25..0a4f450c984 100644 --- a/admin/spec/features/users_spec.rb +++ b/admin/spec/features/users_spec.rb @@ -161,4 +161,49 @@ expect(page).to have_field("user[ship_address_attributes][name]", with: "Elrond") end end + + context "when viewing a user's order history" do + context "when a user has no orders" do + before do + create(:user, email: "customer@example.com") + visit "/admin/users" + find_row("customer@example.com").click + click_on "Order History" + end + + it "shows the order history page" do + expect(page).to have_content("Users / customer@example.com / Order History") + expect(page).to have_content("Lifetime Stats") + expect(page).to have_content("Order History") + expect(page).to be_axe_clean + end + + it "shows the appropriate content" do + expect(page).to have_content("No Orders found.") + end + end + + context "when a user has ordered before" do + before do + create(:user, :with_orders, email: "loyal_customer@example.com") + visit "/admin/users" + find_row("loyal_customer@example.com").click + click_on "Order History" + end + + it "shows the order history page" do + expect(page).to have_content("Users / loyal_customer@example.com / Order History") + expect(page).to have_content("Lifetime Stats") + expect(page).to have_content("Order History") + expect(page).to be_axe_clean + end + + it "shows the order history" do + expect(page).to have_content(/R\d+/) # Matches on any order number. + expect(page).to have_content("Shipment") + expect(page).to have_content("Payment") + expect(page).not_to have_content("No Orders found.") + end + end + end end diff --git a/admin/spec/requests/solidus_admin/users_spec.rb b/admin/spec/requests/solidus_admin/users_spec.rb index 36847fd639f..882342189b7 100644 --- a/admin/spec/requests/solidus_admin/users_spec.rb +++ b/admin/spec/requests/solidus_admin/users_spec.rb @@ -59,6 +59,16 @@ end end + describe "GET /orders" do + let!(:order) { create(:order, user: user) } + + it "renders the orders template and displays the user's orders" do + get solidus_admin.orders_user_path(user) + expect(response).to have_http_status(:ok) + expect(response.body).to include(order.number) + end + end + describe "DELETE /destroy" do it "deletes the user and redirects to the index page with a 303 See Other status" do # Ensure the user exists prior to deletion From d46369f7d7f9246665fcc6830654c6e56d0a78fb Mon Sep 17 00:00:00 2001 From: Madeline Collier Date: Fri, 11 Oct 2024 17:02:58 +0200 Subject: [PATCH 166/834] Handle states_required? in admin address component Previously, this component would just disable the state_id field when the selected country did not require states, but this meant that the user would never be able to self-fill the state_name field, which is something that some stores can require with the `Spree::Config[:address_requires_state]` preference. The component now takes this into account and will swap between a state_id dropdown select field, or a blank state_name text input depending on the country the user has selected. This brings this new admin component up to par with the previous backend address forms. --- .../ui/forms/address/component.html.erb | 36 +++++++++---- .../ui/forms/address/component.js | 51 ++++++++++++++----- 2 files changed, 65 insertions(+), 22 deletions(-) diff --git a/admin/app/components/solidus_admin/ui/forms/address/component.html.erb b/admin/app/components/solidus_admin/ui/forms/address/component.html.erb index 3567418ffb1..0dd61697425 100644 --- a/admin/app/components/solidus_admin/ui/forms/address/component.html.erb +++ b/admin/app/components/solidus_admin/ui/forms/address/component.html.erb @@ -21,15 +21,33 @@ "data-action": "change->#{stimulus_id}#loadStates" ) %> - <%= render component("ui/forms/field").select( - @name, - :state_id, - state_options, - object: @address, - value: @address.try(:state_id), - disabled: @address.country&.states&.empty?, - "data-#{stimulus_id}-target": "state" - ) %> + <%= content_tag(:div, + data: { "#{stimulus_id}-target": "stateNameWrapper" }, + class: (@address.country&.states&.empty? ? "flex flex-col gap-2 w-full" : "hidden flex flex-col gap-2 w-full") + ) do %> + <%= render component("ui/forms/field").text_field( + @name, + :state_name, + object: @address, + value: @address.try(:state_name), + "data-#{stimulus_id}-target": "stateName" + ) %> + <% end %> + > + + <%= content_tag(:div, + data: { "#{stimulus_id}-target": "stateWrapper" }, + class: (@address.country&.states&.empty? ? "hidden flex flex-col gap-2 w-full" : "flex flex-col gap-2 w-full") + ) do %> + <%= render component("ui/forms/field").select( + @name, + :state_id, + state_options, + object: @address, + value: @address.try(:state_id), + "data-#{stimulus_id}-target": "state" + ) %> + <% end %> <%= render component("ui/forms/field").text_field(@name, :phone, object: @address) %>
diff --git a/admin/app/components/solidus_admin/ui/forms/address/component.js b/admin/app/components/solidus_admin/ui/forms/address/component.js index 1b255ee39a4..c39c7cd161b 100644 --- a/admin/app/components/solidus_admin/ui/forms/address/component.js +++ b/admin/app/components/solidus_admin/ui/forms/address/component.js @@ -1,7 +1,7 @@ import { Controller } from '@hotwired/stimulus' export default class extends Controller { - static targets = ["country", "state"] + static targets = ["country", "state", "stateName", "stateWrapper", "stateNameWrapper"] loadStates() { const countryId = this.countryTarget.value @@ -13,22 +13,47 @@ export default class extends Controller { }) } - updateStateOptions(data) { + updateStateOptions(states) { + if (states.length === 0) { + // Show state name text input if no states to choose from. + this.toggleStateFields(false) + } else { + // Show state select dropdown. + this.toggleStateFields(true) + this.populateStateSelect(states) + } + } + + toggleStateFields(showSelect) { + const stateWrapper = this.stateWrapperTarget + const stateNameWrapper = this.stateNameWrapperTarget const stateSelect = this.stateTarget + const stateName = this.stateNameTarget - stateSelect.innerHTML = "" - if (data.length === 0) { - stateSelect.disabled = true - } else { + if (showSelect) { + // Show state select dropdown. stateSelect.disabled = false - - data.forEach((state) => { - const option = document.createElement("option") - option.value = state.id - option.innerText = state.name - stateSelect.appendChild(option) - }) + stateName.value = "" + stateWrapper.classList.remove('hidden') + stateNameWrapper.classList.add('hidden') + } else { + // Show state name text input if no states to choose from. + stateSelect.disabled = true + stateWrapper.classList.add("hidden") + stateNameWrapper.classList.remove("hidden") } } + + populateStateSelect(states) { + const stateSelect = this.stateTarget + stateSelect.innerHTML = "" + + states.forEach((state) => { + const option = document.createElement("option") + option.value = state.id + option.innerText = state.name + stateSelect.appendChild(option) + }) + } } From c50cba4fe187f10ddda489ba45812be7b096cab0 Mon Sep 17 00:00:00 2001 From: Madeline Collier Date: Mon, 14 Oct 2024 16:26:58 +0200 Subject: [PATCH 167/834] Extract user stats into shared component This DRYs things up a bit so that all pages of the users admin can reuse the same helper, translations, and component. Since the order index does not use this stats component, but does make use of the last_login logic, that was broken out into a separate helper for ease of use. --- .../users/addresses/component.html.erb | 12 +------ .../users/addresses/component.rb | 11 ------- .../users/addresses/component.yml | 4 --- .../users/edit/component.html.erb | 12 +------ .../solidus_admin/users/edit/component.rb | 10 ------ .../solidus_admin/users/edit/component.yml | 4 --- .../solidus_admin/users/index/component.rb | 18 ++--------- .../solidus_admin/users/index/component.yml | 4 --- .../users/orders/component.html.erb | 12 +------ .../solidus_admin/users/orders/component.rb | 10 ------ .../solidus_admin/users/orders/component.yml | 4 --- .../users/stats/component.html.erb | 11 +++++++ .../solidus_admin/users/stats/component.rb | 9 ++++++ .../solidus_admin/users/stats/component.yml | 2 ++ .../solidus_admin/last_login_helper.rb | 16 ++++++++++ admin/config/locales/users.en.yml | 4 +++ .../solidus_admin/last_login_helper_spec.rb | 31 +++++++++++++++++++ 17 files changed, 78 insertions(+), 96 deletions(-) create mode 100644 admin/app/components/solidus_admin/users/stats/component.html.erb create mode 100644 admin/app/components/solidus_admin/users/stats/component.rb create mode 100644 admin/app/components/solidus_admin/users/stats/component.yml create mode 100644 admin/app/helpers/solidus_admin/last_login_helper.rb create mode 100644 admin/spec/helpers/solidus_admin/last_login_helper_spec.rb diff --git a/admin/app/components/solidus_admin/users/addresses/component.html.erb b/admin/app/components/solidus_admin/users/addresses/component.html.erb index b2d41f37e3c..267295d8ca5 100644 --- a/admin/app/components/solidus_admin/users/addresses/component.html.erb +++ b/admin/app/components/solidus_admin/users/addresses/component.html.erb @@ -40,17 +40,7 @@ <% end %> <%= page_with_sidebar_aside do %> - <%= render component("ui/panel").new(title: t("spree.lifetime_stats")) do %> - <%= render component("ui/details_list").new( - items: [ - { label: t("spree.total_sales"), value: @user.display_lifetime_value.to_html }, - { label: t("spree.order_count"), value: @user.order_count.to_i }, - { label: t("spree.average_order_value"), value: @user.display_average_order_value.to_html }, - { label: t("spree.member_since"), value: @user.created_at.to_date }, - { label: t(".last_active"), value: last_login(@user) }, - ] - ) %> - <% end %> + <%= render component("users/stats").new(user: @user) %> <% end %> <% end %> <% end %> diff --git a/admin/app/components/solidus_admin/users/addresses/component.rb b/admin/app/components/solidus_admin/users/addresses/component.rb index 6116312b866..0432aefba80 100644 --- a/admin/app/components/solidus_admin/users/addresses/component.rb +++ b/admin/app/components/solidus_admin/users/addresses/component.rb @@ -43,17 +43,6 @@ def tabs ] end - def last_login(user) - return t('.last_login.never') if user.try(:last_sign_in_at).blank? - - t( - '.last_login.login_time_ago', - # @note The second `.try` is here for the specs and for setups that use a - # custom User class which may not have this attribute. - last_login_time: time_ago_in_words(user.try(:last_sign_in_at)) - ).capitalize - end - def bill_address(address, type) if address.present? && type == "bill" address diff --git a/admin/app/components/solidus_admin/users/addresses/component.yml b/admin/app/components/solidus_admin/users/addresses/component.yml index 6872c1d5cd5..dfeaebe49a2 100644 --- a/admin/app/components/solidus_admin/users/addresses/component.yml +++ b/admin/app/components/solidus_admin/users/addresses/component.yml @@ -6,10 +6,6 @@ en: items: Items store_credit: Store Credit last_active: Last Active - last_login: - login_time_ago: "%{last_login_time} ago" - never: Never - invitation_sent: Invitation sent create_order_for_user: Create order for this user update: Update cancel: Cancel diff --git a/admin/app/components/solidus_admin/users/edit/component.html.erb b/admin/app/components/solidus_admin/users/edit/component.html.erb index b9b8cbe4488..1f7036c314a 100644 --- a/admin/app/components/solidus_admin/users/edit/component.html.erb +++ b/admin/app/components/solidus_admin/users/edit/component.html.erb @@ -46,17 +46,7 @@ <% end %> <%= page_with_sidebar_aside do %> - <%= render component("ui/panel").new(title: t("spree.lifetime_stats")) do %> - <%= render component("ui/details_list").new( - items: [ - { label: t("spree.total_sales"), value: @user.display_lifetime_value.to_html }, - { label: t("spree.order_count"), value: @user.order_count.to_i }, - { label: t("spree.average_order_value"), value: @user.display_average_order_value.to_html }, - { label: t("spree.member_since"), value: @user.created_at.to_date }, - { label: t(".last_active"), value: last_login(@user) }, - ] - ) %> - <% end %> + <%= render component("users/stats").new(user: @user) %> <% end %> <% end %> <% end %> diff --git a/admin/app/components/solidus_admin/users/edit/component.rb b/admin/app/components/solidus_admin/users/edit/component.rb index 19ff9adbc8e..738764c5f4a 100644 --- a/admin/app/components/solidus_admin/users/edit/component.rb +++ b/admin/app/components/solidus_admin/users/edit/component.rb @@ -43,16 +43,6 @@ def tabs ] end - def last_login(user) - return t('.last_login.never') if user.try(:last_sign_in_at).blank? - - t( - '.last_login.login_time_ago', - # @note The second `.try` is only here for the specs to work. - last_login_time: time_ago_in_words(user.try(:last_sign_in_at)) - ).capitalize - end - def role_options Spree::Role.all.map do |role| { label: role.name, id: role.id } diff --git a/admin/app/components/solidus_admin/users/edit/component.yml b/admin/app/components/solidus_admin/users/edit/component.yml index 01b3cf230ac..4aa3d1056f1 100644 --- a/admin/app/components/solidus_admin/users/edit/component.yml +++ b/admin/app/components/solidus_admin/users/edit/component.yml @@ -6,10 +6,6 @@ en: items: Items store_credit: Store Credit last_active: Last Active - last_login: - login_time_ago: "%{last_login_time} ago" - never: Never - invitation_sent: Invitation sent create_order_for_user: Create order for this user update: Update cancel: Cancel diff --git a/admin/app/components/solidus_admin/users/index/component.rb b/admin/app/components/solidus_admin/users/index/component.rb index 0b911061d90..2d6db499bc9 100644 --- a/admin/app/components/solidus_admin/users/index/component.rb +++ b/admin/app/components/solidus_admin/users/index/component.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class SolidusAdmin::Users::Index::Component < SolidusAdmin::UsersAndRoles::Component + include SolidusAdmin::LastLoginHelper + def model_class Spree.user_class end @@ -93,20 +95,4 @@ def columns }, ] end - - private - - # @todo add logic to display "Invitation sent" when the user has not yet - # accepted the invitation and filled out account details. To be implemented - # in conjunction with the invitation logic. - def last_login(user) - return t('.last_login.never') if user.try(:last_sign_in_at).blank? - - t( - '.last_login.login_time_ago', - # @note The second `.try` is here for the specs and for setups that use a - # custom User class which may not have this attribute. - last_login_time: time_ago_in_words(user.try(:last_sign_in_at)) - ).capitalize - end end diff --git a/admin/app/components/solidus_admin/users/index/component.yml b/admin/app/components/solidus_admin/users/index/component.yml index 789fbb6a3cf..ba63d2d41f6 100644 --- a/admin/app/components/solidus_admin/users/index/component.yml +++ b/admin/app/components/solidus_admin/users/index/component.yml @@ -13,7 +13,3 @@ en: status: active: Active inactive: Inactive - last_login: - login_time_ago: "%{last_login_time} ago" - never: Never - invitation_sent: Invitation sent diff --git a/admin/app/components/solidus_admin/users/orders/component.html.erb b/admin/app/components/solidus_admin/users/orders/component.html.erb index a386d089b84..a157f46f8b5 100644 --- a/admin/app/components/solidus_admin/users/orders/component.html.erb +++ b/admin/app/components/solidus_admin/users/orders/component.html.erb @@ -36,17 +36,7 @@ <% end %> <%= page_with_sidebar_aside do %> - <%= render component("ui/panel").new(title: t("spree.lifetime_stats")) do %> - <%= render component("ui/details_list").new( - items: [ - { label: t("spree.total_sales"), value: @user.display_lifetime_value.to_html }, - { label: t("spree.order_count"), value: @user.order_count.to_i }, - { label: t("spree.average_order_value"), value: @user.display_average_order_value.to_html }, - { label: t("spree.member_since"), value: @user.created_at.to_date }, - { label: t(".last_active"), value: last_login(@user) }, - ] - ) %> - <% end %> + <%= render component("users/stats").new(user: @user) %> <% end %> <% end %> <% end %> diff --git a/admin/app/components/solidus_admin/users/orders/component.rb b/admin/app/components/solidus_admin/users/orders/component.rb index ee57928111b..917156abfca 100644 --- a/admin/app/components/solidus_admin/users/orders/component.rb +++ b/admin/app/components/solidus_admin/users/orders/component.rb @@ -42,16 +42,6 @@ def tabs ] end - def last_login(user) - return t('.last_login.never') if user.try(:last_sign_in_at).blank? - - t( - '.last_login.login_time_ago', - # @note The second `.try` is only here for the specs to work. - last_login_time: time_ago_in_words(user.try(:last_sign_in_at)) - ).capitalize - end - def model_class Spree::Order end diff --git a/admin/app/components/solidus_admin/users/orders/component.yml b/admin/app/components/solidus_admin/users/orders/component.yml index 6c4b6f1368a..98c4724fe23 100644 --- a/admin/app/components/solidus_admin/users/orders/component.yml +++ b/admin/app/components/solidus_admin/users/orders/component.yml @@ -6,10 +6,6 @@ en: items: Items store_credit: Store Credit last_active: Last Active - last_login: - login_time_ago: "%{last_login_time} ago" - never: Never - invitation_sent: Invitation sent create_order_for_user: Create order for this user no_orders_found: No Orders found. create_one: Create One diff --git a/admin/app/components/solidus_admin/users/stats/component.html.erb b/admin/app/components/solidus_admin/users/stats/component.html.erb new file mode 100644 index 00000000000..888473b400b --- /dev/null +++ b/admin/app/components/solidus_admin/users/stats/component.html.erb @@ -0,0 +1,11 @@ +<%= render component("ui/panel").new(title: t("spree.lifetime_stats")) do %> + <%= render component("ui/details_list").new( + items: [ + { label: t("spree.total_sales"), value: @user.display_lifetime_value.to_html }, + { label: t("spree.order_count"), value: @user.order_count.to_i }, + { label: t("spree.average_order_value"), value: @user.display_average_order_value.to_html }, + { label: t("spree.member_since"), value: @user.created_at.to_date }, + { label: t(".last_active"), value: last_login(@user) }, + ] + ) %> +<% end %> diff --git a/admin/app/components/solidus_admin/users/stats/component.rb b/admin/app/components/solidus_admin/users/stats/component.rb new file mode 100644 index 00000000000..e93aef7b03e --- /dev/null +++ b/admin/app/components/solidus_admin/users/stats/component.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class SolidusAdmin::Users::Stats::Component < SolidusAdmin::BaseComponent + include SolidusAdmin::LastLoginHelper + + def initialize(user:) + @user = user + end +end diff --git a/admin/app/components/solidus_admin/users/stats/component.yml b/admin/app/components/solidus_admin/users/stats/component.yml new file mode 100644 index 00000000000..f01f52d773e --- /dev/null +++ b/admin/app/components/solidus_admin/users/stats/component.yml @@ -0,0 +1,2 @@ +en: + last_active: Last Active diff --git a/admin/app/helpers/solidus_admin/last_login_helper.rb b/admin/app/helpers/solidus_admin/last_login_helper.rb new file mode 100644 index 00000000000..95f49b00c00 --- /dev/null +++ b/admin/app/helpers/solidus_admin/last_login_helper.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module SolidusAdmin + module LastLoginHelper + def last_login(user) + return t('solidus_admin.users.last_login.never') if user.try(:last_sign_in_at).blank? + + t( + 'solidus_admin.users.last_login.login_time_ago', + # @note The second `.try` is here for the specs and for setups that use a + # custom User class which may not have this attribute. + last_login_time: time_ago_in_words(user.try(:last_sign_in_at)) + ).capitalize + end + end +end diff --git a/admin/config/locales/users.en.yml b/admin/config/locales/users.en.yml index 5fda686929c..8a08a99a8aa 100644 --- a/admin/config/locales/users.en.yml +++ b/admin/config/locales/users.en.yml @@ -10,3 +10,7 @@ en: ship: success: "Shipping Address has been successfully updated." error: "Address could not be updated." + last_login: + never: "Never" + login_time_ago: "%{last_login_time} ago" + invitation_sent: "Invitation sent" diff --git a/admin/spec/helpers/solidus_admin/last_login_helper_spec.rb b/admin/spec/helpers/solidus_admin/last_login_helper_spec.rb new file mode 100644 index 00000000000..9b2099f6973 --- /dev/null +++ b/admin/spec/helpers/solidus_admin/last_login_helper_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe SolidusAdmin::LastLoginHelper, type: :helper do + describe "#last_login" do + let(:user) { double("User") } + + context "when user has never logged in" do + it "returns 'Never'" do + allow(user).to receive(:last_sign_in_at).and_return(nil) + + expect(helper.last_login(user)).to eq("Never") + end + end + + context "when user has logged in before" do + it "returns the time ago since the last login, capitalized" do + last_sign_in_time = 2.days.ago + allow(user).to receive(:last_sign_in_at).and_return(last_sign_in_time) + + expect(helper) + .to receive(:time_ago_in_words) + .with(last_sign_in_time) + .and_return("2 days") + + expect(helper.last_login(user)).to eq("2 days ago") + end + end + end +end From 214ce64045a2ed7dd5e023e8cb785c953678d80f Mon Sep 17 00:00:00 2001 From: Madeline Collier Date: Tue, 15 Oct 2024 14:38:36 +0200 Subject: [PATCH 168/834] Add new users admin items page This migrates the `users/:id/items` page from the legacy soldius_backend to the new solidus_admin. --- .../users/items/component.html.erb | 41 +++++ .../solidus_admin/users/items/component.rb | 174 ++++++++++++++++++ .../solidus_admin/users/items/component.yml | 16 ++ .../solidus_admin/users_controller.rb | 17 +- admin/config/routes.rb | 1 + admin/spec/features/users_spec.rb | 46 +++++ .../spec/requests/solidus_admin/users_spec.rb | 10 + 7 files changed, 304 insertions(+), 1 deletion(-) create mode 100644 admin/app/components/solidus_admin/users/items/component.html.erb create mode 100644 admin/app/components/solidus_admin/users/items/component.rb create mode 100644 admin/app/components/solidus_admin/users/items/component.yml diff --git a/admin/app/components/solidus_admin/users/items/component.html.erb b/admin/app/components/solidus_admin/users/items/component.html.erb new file mode 100644 index 00000000000..82dd555cf86 --- /dev/null +++ b/admin/app/components/solidus_admin/users/items/component.html.erb @@ -0,0 +1,41 @@ +<%= page do %> + <%= page_header do %> + <%= page_header_back(solidus_admin.users_path) %> + <%= page_header_title(t(".title", email: @user.email)) %> + + <%= page_header_actions do %> + <%= render component("ui/button").new(tag: :a, text: t(".create_order_for_user"), href: spree.new_admin_order_path(user_id: @user.id)) %> + <% end %> + <% end %> + + <%= page_header do %> + <% tabs.each do |tab| %> + <%= render(component("ui/button").new(tag: :a, scheme: :ghost, text: tab[:text], 'aria-current': tab[:current], href: tab[:href])) %> + <% end %> + <% end %> + + <%= page_with_sidebar do %> + <%= page_with_sidebar_main do %> + <%= render component('ui/panel').new(title: t(".items_purchased")) do %> + <% if @items.present? %> + <%= render component('ui/table').new( + id: stimulus_id, + data: { + class: model_class, + rows: rows, + columns: columns, + url: -> { row_url(_1.order) }, + }, + )%> + <% else %> + <%= t(".no_orders_found") %> + <%= render component("ui/button").new(tag: :a, text: t(".create_one"), href: spree.new_admin_order_path(user_id: @user.id)) %> + <% end %> + <% end %> + <% end %> + + <%= page_with_sidebar_aside do %> + <%= render component("users/stats").new(user: @user) %> + <% end %> + <% end %> +<% end %> diff --git a/admin/app/components/solidus_admin/users/items/component.rb b/admin/app/components/solidus_admin/users/items/component.rb new file mode 100644 index 00000000000..5a901240a74 --- /dev/null +++ b/admin/app/components/solidus_admin/users/items/component.rb @@ -0,0 +1,174 @@ +# frozen_string_literal: true + +class SolidusAdmin::Users::Items::Component < SolidusAdmin::BaseComponent + include SolidusAdmin::Layout::PageHelpers + + def initialize(user:, items:) + @user = user + @items = items + end + + def form_id + @form_id ||= "#{stimulus_id}--form-#{@user.id}" + end + + def tabs + [ + { + text: t('.account'), + href: solidus_admin.user_path(@user), + current: false, + }, + { + text: t('.addresses'), + href: solidus_admin.addresses_user_path(@user), + current: false, + }, + { + text: t('.order_history'), + href: solidus_admin.orders_user_path(@user), + current: false, + }, + { + text: t('.items'), + href: solidus_admin.items_user_path(@user), + current: true, + }, + { + text: t('.store_credit'), + href: spree.admin_user_store_credits_path(@user), + current: false, + }, + ] + end + + def model_class + Spree::LineItem + end + + def row_url(order) + spree.edit_admin_order_path(order) + end + + def rows + @items + end + + def columns + [ + date_column, + image_column, + description_column, + price_column, + quantity_column, + total_column, + state_column, + number_column, + ] + end + + def date_column + { + col: { class: "w-[8%]" }, + header: :date, + data: ->(item) do + content_tag :div, l(item.order.created_at, format: :short), class: "text-sm" + end + } + end + + def image_column + { + col: { class: "w-[8%]" }, + header: tag.span('aria-label': Spree::Image.model_name.human, role: 'text'), + data: ->(item) do + image = item.variant.gallery.images.first || item.variant.product.gallery.images.first or return + + render( + component('ui/thumbnail').new( + src: image.url(:small), + alt: item.product.name + ) + ) + end + } + end + + def description_column + { + col: { class: "w-[24%]" }, + header: t(".description_column_header"), + data: ->(item) { item_name_with_variant_and_sku(item) } + } + end + + def price_column + { + col: { class: "w-[10%]" }, + header: :price, + data: ->(item) do + content_tag :div, item.single_money.to_html + end + } + end + + def quantity_column + { + col: { class: "w-[7%]" }, + header: :qty, + data: ->(item) do + content_tag :div, item.quantity + end + } + end + + def total_column + { + col: { class: "w-[10%]" }, + header: t(".total_column_header"), + data: ->(item) do + content_tag :div, item.money.to_html + end + } + end + + def state_column + { + col: { class: "w-[15%]" }, + header: :state, + data: ->(item) do + color = { + 'complete' => :green, + 'returned' => :red, + 'canceled' => :blue, + 'cart' => :graphite_light, + }[item.order.state] || :yellow + component('ui/badge').new(name: item.order.state.humanize, color: color) + end + } + end + + def number_column + { + col: { class: "w-[18%]" }, + header: t(".number_column_header"), + data: ->(item) do + content_tag :div, item.order.number, class: "font-semibold text-sm" + end + } + end + + private + + def item_name_with_variant_and_sku(item) + content = [] + content << item.product.name + content << "(#{item.variant.options_text})" if item.variant.option_values.any? + content << "#{t('spree.sku')}: #{item.variant.sku}" if item.variant.sku.present? + + # The `.html_safe` is required for the description to display as desired. + # rubocop:disable Rails/OutputSafety + safe_join([content_tag(:div, content.join("
").html_safe, class: "text-sm")]) + # rubocop:enable Rails/OutputSafety + end +end diff --git a/admin/app/components/solidus_admin/users/items/component.yml b/admin/app/components/solidus_admin/users/items/component.yml new file mode 100644 index 00000000000..2659e722c06 --- /dev/null +++ b/admin/app/components/solidus_admin/users/items/component.yml @@ -0,0 +1,16 @@ +en: + title: "Users / %{email} / Items Purchased" + account: Account + addresses: Addresses + order_history: Order History + items: Items + store_credit: Store Credit + last_active: Last Active + create_order_for_user: Create order for this user + items_purchased: Items Purchased + no_orders_found: No Orders found. + create_one: Create One + back: Back + number_column_header: "Order #" + description_column_header: "Description" + total_column_header: "Total" diff --git a/admin/app/controllers/solidus_admin/users_controller.rb b/admin/app/controllers/solidus_admin/users_controller.rb index 8b1a7858598..7e7d181a1bd 100644 --- a/admin/app/controllers/solidus_admin/users_controller.rb +++ b/admin/app/controllers/solidus_admin/users_controller.rb @@ -5,7 +5,7 @@ class UsersController < SolidusAdmin::BaseController include SolidusAdmin::ControllerHelpers::Search include Spree::Core::ControllerHelpers::StrongParameters - before_action :set_user, only: [:edit, :addresses, :update_addresses, :orders] + before_action :set_user, only: [:edit, :addresses, :update_addresses, :orders, :items] search_scope(:all, default: true) search_scope(:customers) { _1.left_outer_joins(:role_users).where(role_users: { id: nil }) } @@ -58,6 +58,14 @@ def orders end end + def items + set_items + + respond_to do |format| + format.html { render component('users/items').new(user: @user, items: @items) } + end + end + def edit respond_to do |format| format.html { render component('users/edit').new(user: @user) } @@ -107,6 +115,13 @@ def set_orders @orders = @search.result.page(params[:page]).per(Spree::Config[:admin_products_per_page]) end + def set_items + params[:q] ||= {} + @search = Spree::Order.reverse_chronological.includes(line_items: { variant: [:product, { option_values: :option_type }] }).ransack(params[:q].merge(user_id_eq: @user.id)) + @orders = @search.result.page(params[:page]).per(Spree::Config[:admin_products_per_page]) + @items = @orders&.map(&:line_items)&.flatten + end + def authorization_subject Spree.user_class end diff --git a/admin/config/routes.rb b/admin/config/routes.rb index d33b23d60cc..862ee70212c 100644 --- a/admin/config/routes.rb +++ b/admin/config/routes.rb @@ -50,6 +50,7 @@ get :addresses put :update_addresses get :orders + get :items end end diff --git a/admin/spec/features/users_spec.rb b/admin/spec/features/users_spec.rb index 0a4f450c984..b6ac4e097ab 100644 --- a/admin/spec/features/users_spec.rb +++ b/admin/spec/features/users_spec.rb @@ -206,4 +206,50 @@ end end end + + context "when viewing a user's purchased items" do + context "when a user has no purchased items" do + before do + create(:user, email: "customer@example.com") + visit "/admin/users" + find_row("customer@example.com").click + click_on "Items" + end + + it "shows the purchased items page" do + expect(page).to have_content("Users / customer@example.com / Items Purchased") + expect(page).to have_content("Lifetime Stats") + expect(page).to have_content("Items Purchased") + expect(page).to be_axe_clean + end + + it "shows the appropriate content" do + expect(page).to have_content("No Orders found.") + end + end + + context "when a user has ordered before" do + before do + create(:order_with_line_items, user: create(:user, email: "loyal_customer@example.com")) + visit "/admin/users" + find_row("loyal_customer@example.com").click + click_on "Items" + end + + it "shows the purchased items page" do + expect(page).to have_content("Users / loyal_customer@example.com / Items Purchased") + expect(page).to have_content("Lifetime Stats") + expect(page).to have_content("Items Purchased") + expect(page).to be_axe_clean + end + + it "lists the purchased items" do + expect(page).to have_content(/R\d+/) # Matches on any order number. + expect(page).to have_content("Description") + expect(page).to have_content("Qty") + expect(page).to have_content("State") + expect(page).not_to have_content("No Orders found.") + end + end + end end diff --git a/admin/spec/requests/solidus_admin/users_spec.rb b/admin/spec/requests/solidus_admin/users_spec.rb index 882342189b7..c2ca7c88e2b 100644 --- a/admin/spec/requests/solidus_admin/users_spec.rb +++ b/admin/spec/requests/solidus_admin/users_spec.rb @@ -69,6 +69,16 @@ end end + describe "GET /items" do + let!(:order) { create(:order_with_line_items, user: user) } + + it "renders the items template and displays the user's purchased items" do + get solidus_admin.items_user_path(user) + expect(response).to have_http_status(:ok) + expect(response.body).to include(order.number) + end + end + describe "DELETE /destroy" do it "deletes the user and redirects to the index page with a 303 See Other status" do # Ensure the user exists prior to deletion From c26cb64527641eeaf987d5df434dbd2ccfc5429d Mon Sep 17 00:00:00 2001 From: Madeline Collier Date: Tue, 15 Oct 2024 15:33:07 +0200 Subject: [PATCH 169/834] Remove unnecessary form_id methods in components These two components didn't require form_ids as they had no forms. Purely informational display pages, only `get` requests, no forms in sight. --- admin/app/components/solidus_admin/users/items/component.rb | 4 ---- admin/app/components/solidus_admin/users/orders/component.rb | 4 ---- 2 files changed, 8 deletions(-) diff --git a/admin/app/components/solidus_admin/users/items/component.rb b/admin/app/components/solidus_admin/users/items/component.rb index 5a901240a74..f86628d6c0d 100644 --- a/admin/app/components/solidus_admin/users/items/component.rb +++ b/admin/app/components/solidus_admin/users/items/component.rb @@ -8,10 +8,6 @@ def initialize(user:, items:) @items = items end - def form_id - @form_id ||= "#{stimulus_id}--form-#{@user.id}" - end - def tabs [ { diff --git a/admin/app/components/solidus_admin/users/orders/component.rb b/admin/app/components/solidus_admin/users/orders/component.rb index 917156abfca..d2b80caf14d 100644 --- a/admin/app/components/solidus_admin/users/orders/component.rb +++ b/admin/app/components/solidus_admin/users/orders/component.rb @@ -8,10 +8,6 @@ def initialize(user:, orders:) @orders = orders end - def form_id - @form_id ||= "#{stimulus_id}--form-#{@user.id}" - end - def tabs [ { From 91757615f953d1521b6d6bc8ec5fc1e7a90fa553 Mon Sep 17 00:00:00 2001 From: Thomas von Deyen Date: Wed, 16 Oct 2024 20:28:40 +0200 Subject: [PATCH 170/834] fix: bin/sandbox command Use `bundle exec` to invoke the rails installer, otherwise it will fail with ``` /Users//.rubies/ruby-3.3.5/lib/ruby/3.3.0/rubygems.rb:259:in `find_spec_for_exe': can't find gem rails (= 7.2.1.1) with executable rails (Gem::GemNotFoundException) from /Users//.rubies/ruby-3.3.5/lib/ruby/3.3.0/rubygems.rb:278:in `activate_bin_path' from /Users//.gem/ruby/3.3.5/bin/rails:25:in `
' ``` although it is installed. --- bin/sandbox | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/sandbox b/bin/sandbox index 11b0a084594..f342e720dcd 100755 --- a/bin/sandbox +++ b/bin/sandbox @@ -42,7 +42,7 @@ rm -rf ./sandbox echo "~~~> Creating a pristine Rails app" rails_version=`bundle exec ruby -e'require "rails"; puts Rails.version'` -rails _${rails_version}_ new sandbox \ +bundle exec rails _${rails_version}_ new sandbox \ --database="$RAILSDB" \ --skip-git \ --skip-keeps \ From 4840ccd59047bb84dd3658b340c3951f9ec32fe2 Mon Sep 17 00:00:00 2001 From: Thomas von Deyen Date: Wed, 16 Oct 2024 20:46:06 +0200 Subject: [PATCH 171/834] Fix installer test: Show wget output --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 0dc19b64d42..398088204e4 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -203,7 +203,7 @@ commands: cd <> unset RAILS_ENV # avoid doing everything on the test environment bin/rails server -p 3000 & - wget --quiet --output-document - --tries=30 --retry-connrefused "http://localhost:3000<>" | grep "<>" + wget --output-document - --tries=30 --retry-connrefused "http://localhost:3000<>" | grep "<>" echo "Exited with $?" kill $(cat "tmp/pids/server.pid") From d7623ea60e672c4882d73d95e42638213e9f8320 Mon Sep 17 00:00:00 2001 From: Thomas von Deyen Date: Wed, 16 Oct 2024 21:02:56 +0200 Subject: [PATCH 172/834] log wget output --- .circleci/config.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 398088204e4..bcc5d89928a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -203,7 +203,9 @@ commands: cd <> unset RAILS_ENV # avoid doing everything on the test environment bin/rails server -p 3000 & - wget --output-document - --tries=30 --retry-connrefused "http://localhost:3000<>" | grep "<>" + wget --no-verbose --output-document output.html --tries=30 --retry-connrefused "http://localhost:3000<>" + cat output.html + grep "<>" output.html echo "Exited with $?" kill $(cat "tmp/pids/server.pid") From 8a6f2cc9e9415c2d7e7cfd35798b036ca318fdbe Mon Sep 17 00:00:00 2001 From: Chris Todorov Date: Wed, 16 Oct 2024 14:37:24 -0700 Subject: [PATCH 173/834] Update Tailwind executable call for v3.0 `solidus_starter_frontend` provides templates including the `tailwindcss-rails` gem without any version constraints. So when 3.0 was released this week, it broke CI runs for the Solidus repository. It appears that the only breaking change is related to the `.executable` call. As of `tailwindcss-rails` `>= 3.0` the command for the executable was extracted to a separate `tailwindcss-ruby` gem, which causes an error in the rake tasks we generate for the `solidus_admin`. The error this fixes is: NoMethodError: undefined method `executable' for Tailwindcss::Commands:Module Co-authored-by: benjamin wil --- Gemfile | 2 +- admin/lib/solidus_admin/install_tailwindcss.rb | 2 +- admin/lib/solidus_admin/testing_support/dummy_app/rake_tasks.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 75d1b5bd42c..dc9b4aaf08e 100644 --- a/Gemfile +++ b/Gemfile @@ -51,7 +51,7 @@ end group :admin do gem 'solidus_admin', path: 'admin', require: false - gem 'tailwindcss-rails', '~> 2.0', require: false + gem 'tailwindcss-rails', '~> 3.0', require: false gem 'axe-core-rspec', '~> 4.8', require: false gem 'axe-core-capybara', '~> 4.8', require: false end diff --git a/admin/lib/solidus_admin/install_tailwindcss.rb b/admin/lib/solidus_admin/install_tailwindcss.rb index 1b882a068ed..5bd0137743f 100644 --- a/admin/lib/solidus_admin/install_tailwindcss.rb +++ b/admin/lib/solidus_admin/install_tailwindcss.rb @@ -39,7 +39,7 @@ namespace :solidus_admin do namespace :tailwindcss do root = Rails.root - tailwindcss = Tailwindcss::Commands.executable + tailwindcss = Tailwindcss::Ruby.executable tailwindcss_command = [ tailwindcss, diff --git a/admin/lib/solidus_admin/testing_support/dummy_app/rake_tasks.rb b/admin/lib/solidus_admin/testing_support/dummy_app/rake_tasks.rb index 863c6330150..1b145467899 100644 --- a/admin/lib/solidus_admin/testing_support/dummy_app/rake_tasks.rb +++ b/admin/lib/solidus_admin/testing_support/dummy_app/rake_tasks.rb @@ -33,7 +33,7 @@ DummyApp::Application.root.join("app/assets/stylesheets/solidus_admin/application.tailwind.css") ) - tailwindcss = Tailwindcss::Commands.executable + tailwindcss = Tailwindcss::Ruby.executable tailwindcss_command = [ tailwindcss, From 6a2a98139437ec4e3aa1fc4b18c49441346c71a2 Mon Sep 17 00:00:00 2001 From: Thomas von Deyen Date: Fri, 18 Oct 2024 07:49:09 +0200 Subject: [PATCH 174/834] Release solidus_admin/v0.3.0 --- admin/lib/solidus_admin/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/admin/lib/solidus_admin/version.rb b/admin/lib/solidus_admin/version.rb index 9f54bb4f216..22d21d7a234 100644 --- a/admin/lib/solidus_admin/version.rb +++ b/admin/lib/solidus_admin/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module SolidusAdmin - VERSION = "0.2.0" + VERSION = "0.3.0" end From 53c77ebf8e018f7577a16f513e73ece5a05248df Mon Sep 17 00:00:00 2001 From: Thomas von Deyen Date: Fri, 18 Oct 2024 08:03:08 +0200 Subject: [PATCH 175/834] Revert "log wget output" This reverts commit d7623ea60e672c4882d73d95e42638213e9f8320. --- .circleci/config.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index bcc5d89928a..398088204e4 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -203,9 +203,7 @@ commands: cd <> unset RAILS_ENV # avoid doing everything on the test environment bin/rails server -p 3000 & - wget --no-verbose --output-document output.html --tries=30 --retry-connrefused "http://localhost:3000<>" - cat output.html - grep "<>" output.html + wget --output-document - --tries=30 --retry-connrefused "http://localhost:3000<>" | grep "<>" echo "Exited with $?" kill $(cat "tmp/pids/server.pid") From 449222cc4e7482e87df25fde835499053b2a7c43 Mon Sep 17 00:00:00 2001 From: Thomas von Deyen Date: Fri, 18 Oct 2024 08:03:19 +0200 Subject: [PATCH 176/834] Revert "Fix installer test: Show wget output" This reverts commit 4840ccd59047bb84dd3658b340c3951f9ec32fe2. --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 398088204e4..0dc19b64d42 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -203,7 +203,7 @@ commands: cd <> unset RAILS_ENV # avoid doing everything on the test environment bin/rails server -p 3000 & - wget --output-document - --tries=30 --retry-connrefused "http://localhost:3000<>" | grep "<>" + wget --quiet --output-document - --tries=30 --retry-connrefused "http://localhost:3000<>" | grep "<>" echo "Exited with $?" kill $(cat "tmp/pids/server.pid") From 41cb5d3e4b9fd1142108d91f207ee3adf27ef8e4 Mon Sep 17 00:00:00 2001 From: Thomas von Deyen Date: Fri, 18 Oct 2024 14:45:16 +0200 Subject: [PATCH 177/834] Release solidus_admin/v0.3.1 --- admin/lib/solidus_admin/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/admin/lib/solidus_admin/version.rb b/admin/lib/solidus_admin/version.rb index 22d21d7a234..838637885d0 100644 --- a/admin/lib/solidus_admin/version.rb +++ b/admin/lib/solidus_admin/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module SolidusAdmin - VERSION = "0.3.0" + VERSION = "0.3.1" end From 9db13eb6124e69308528af0e15544129f45634af Mon Sep 17 00:00:00 2001 From: Thomas von Deyen Date: Fri, 18 Oct 2024 08:39:06 +0200 Subject: [PATCH 178/834] fix(solidus_admin): Run bundle install after adding lookbook The following mount task will fail with a bundler error if we do not also install the lookbook gem. Just adding it to the gemgroup won't run bundle install for us. --- admin/lib/generators/solidus_admin/install/install_generator.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/admin/lib/generators/solidus_admin/install/install_generator.rb b/admin/lib/generators/solidus_admin/install/install_generator.rb index 305ef8eac2e..a59d9338dcf 100644 --- a/admin/lib/generators/solidus_admin/install/install_generator.rb +++ b/admin/lib/generators/solidus_admin/install/install_generator.rb @@ -38,6 +38,8 @@ def install_lookbook gem "actioncable" end + execute_command :bundle, :install + route "mount Lookbook::Engine, at: '#{solidus_mount_point}lookbook' if Rails.env.development?" end From ffb9ef4788890a914b724e7f6b21308e0ab43c25 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 25 Oct 2024 13:55:27 +0200 Subject: [PATCH 179/834] Fix test suite by running Puma's `master` branch The current release is incompatible with Capybara when also using Rackup 1.0.1, causing all feature specs to fail. The fix has been merged into puma's `master` branch, so we can temporarily run that in CI. --- Gemfile | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index dc9b4aaf08e..9b125f062a1 100644 --- a/Gemfile +++ b/Gemfile @@ -21,6 +21,7 @@ gem 'pg', '~> 1.0', require: false if dbs.match?(/all|postgres/) gem 'fast_sqlite', require: false if dbs.match?(/all|sqlite/) gem 'sqlite3', '~> 1.4', require: false if dbs.match?(/all|sqlite/) + gem 'database_cleaner', '~> 2.0', require: false gem 'rspec-activemodel-mocks', '~> 1.1', require: false gem 'rspec-rails', '~> 6.0.3', require: false @@ -30,7 +31,12 @@ gem 'simplecov-cobertura', require: false gem 'rack', '< 3', require: false gem 'rake', require: false, groups: [:lint, :release] gem 'rails-controller-testing', require: false -gem 'puma', '< 7', require: false + +# Temporarily use the master branch of puma until the next release +# The current release, 6.4.3, has bug that causes Capybara to crash. +# See https://github.com/puma/puma/pull/3532 +gem 'puma', github: 'puma/puma', branch: 'master', require: false + gem 'i18n-tasks', '~> 0.9', require: false gem 'rspec_junit_formatter', require: false gem 'yard', require: false From 593ec3c0757fd2587528c17e880d8d818025e488 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 26 May 2023 10:41:08 +0200 Subject: [PATCH 180/834] Initial commit --- friendly_promotions/.circleci/config.yml | 53 +++++++++++++ friendly_promotions/.gem_release.yml | 5 ++ friendly_promotions/.github/stale.yml | 1 + .../.github_changelog_generator | 2 + friendly_promotions/.gitignore | 21 +++++ friendly_promotions/.rspec | 2 + friendly_promotions/.rubocop.yml | 5 ++ friendly_promotions/CHANGELOG.md | 1 + friendly_promotions/Gemfile | 43 ++++++++++ friendly_promotions/LICENSE | 26 +++++++ friendly_promotions/README.md | 73 +++++++++++++++++ friendly_promotions/Rakefile | 6 ++ .../backend/solidus_friendly_promotions.js | 2 + .../frontend/solidus_friendly_promotions.js | 2 + .../backend/solidus_friendly_promotions.css | 4 + .../frontend/solidus_friendly_promotions.css | 4 + friendly_promotions/bin/console | 17 ++++ friendly_promotions/bin/rails | 7 ++ friendly_promotions/bin/rails-engine | 13 ++++ friendly_promotions/bin/rails-sandbox | 16 ++++ friendly_promotions/bin/rake | 7 ++ friendly_promotions/bin/sandbox | 78 +++++++++++++++++++ friendly_promotions/bin/setup | 8 ++ friendly_promotions/config/locales/en.yml | 5 ++ friendly_promotions/config/routes.rb | 5 ++ .../install/install_generator.rb | 41 ++++++++++ .../install/templates/initializer.rb | 6 ++ .../lib/solidus_friendly_promotions.rb | 5 ++ .../configuration.rb | 21 +++++ .../lib/solidus_friendly_promotions/engine.rb | 19 +++++ .../testing_support/factories.rb | 4 + .../solidus_friendly_promotions/version.rb | 5 ++ .../solidus_friendly_promotions.gemspec | 36 +++++++++ friendly_promotions/spec/spec_helper.rb | 32 ++++++++ 34 files changed, 575 insertions(+) create mode 100644 friendly_promotions/.circleci/config.yml create mode 100644 friendly_promotions/.gem_release.yml create mode 100644 friendly_promotions/.github/stale.yml create mode 100644 friendly_promotions/.github_changelog_generator create mode 100644 friendly_promotions/.gitignore create mode 100644 friendly_promotions/.rspec create mode 100644 friendly_promotions/.rubocop.yml create mode 100644 friendly_promotions/CHANGELOG.md create mode 100644 friendly_promotions/Gemfile create mode 100644 friendly_promotions/LICENSE create mode 100644 friendly_promotions/README.md create mode 100644 friendly_promotions/Rakefile create mode 100644 friendly_promotions/app/assets/javascripts/spree/backend/solidus_friendly_promotions.js create mode 100644 friendly_promotions/app/assets/javascripts/spree/frontend/solidus_friendly_promotions.js create mode 100644 friendly_promotions/app/assets/stylesheets/spree/backend/solidus_friendly_promotions.css create mode 100644 friendly_promotions/app/assets/stylesheets/spree/frontend/solidus_friendly_promotions.css create mode 100755 friendly_promotions/bin/console create mode 100755 friendly_promotions/bin/rails create mode 100755 friendly_promotions/bin/rails-engine create mode 100755 friendly_promotions/bin/rails-sandbox create mode 100755 friendly_promotions/bin/rake create mode 100755 friendly_promotions/bin/sandbox create mode 100755 friendly_promotions/bin/setup create mode 100644 friendly_promotions/config/locales/en.yml create mode 100644 friendly_promotions/config/routes.rb create mode 100644 friendly_promotions/lib/generators/solidus_friendly_promotions/install/install_generator.rb create mode 100644 friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb create mode 100644 friendly_promotions/lib/solidus_friendly_promotions.rb create mode 100644 friendly_promotions/lib/solidus_friendly_promotions/configuration.rb create mode 100644 friendly_promotions/lib/solidus_friendly_promotions/engine.rb create mode 100644 friendly_promotions/lib/solidus_friendly_promotions/testing_support/factories.rb create mode 100644 friendly_promotions/lib/solidus_friendly_promotions/version.rb create mode 100644 friendly_promotions/solidus_friendly_promotions.gemspec create mode 100644 friendly_promotions/spec/spec_helper.rb diff --git a/friendly_promotions/.circleci/config.yml b/friendly_promotions/.circleci/config.yml new file mode 100644 index 00000000000..28d70ebd500 --- /dev/null +++ b/friendly_promotions/.circleci/config.yml @@ -0,0 +1,53 @@ +version: 2.1 + +orbs: + # Required for feature specs. + browser-tools: circleci/browser-tools@1.1 + + # Always take the latest version of the orb, this allows us to + # run specs against Solidus supported versions only without the need + # to change this configuration every time a Solidus version is released + # or goes EOL. + solidusio_extensions: solidusio/extensions@volatile + +jobs: + run-specs-with-sqlite: + executor: solidusio_extensions/sqlite + steps: + - browser-tools/install-chrome + - solidusio_extensions/run-tests + run-specs-with-postgres: + executor: solidusio_extensions/postgres + steps: + - browser-tools/install-chrome + - solidusio_extensions/run-tests + run-specs-with-mysql: + executor: solidusio_extensions/mysql + steps: + - browser-tools/install-chrome + - solidusio_extensions/run-tests + lint-code: + executor: solidusio_extensions/sqlite-memory + steps: + - solidusio_extensions/lint-code + +workflows: + "Run specs on supported Solidus versions": + jobs: + - run-specs-with-sqlite + - run-specs-with-postgres + - run-specs-with-mysql + - lint-code + + "Weekly run specs against master": + triggers: + - schedule: + cron: "0 0 * * 4" # every Thursday + filters: + branches: + only: + - master + jobs: + - run-specs-with-sqlite + - run-specs-with-postgres + - run-specs-with-mysql diff --git a/friendly_promotions/.gem_release.yml b/friendly_promotions/.gem_release.yml new file mode 100644 index 00000000000..8c2591b0dba --- /dev/null +++ b/friendly_promotions/.gem_release.yml @@ -0,0 +1,5 @@ +bump: + recurse: false + file: 'lib/solidus_friendly_promotions/version.rb' + message: Bump SolidusFriendlyPromotions to %{version} + tag: true diff --git a/friendly_promotions/.github/stale.yml b/friendly_promotions/.github/stale.yml new file mode 100644 index 00000000000..0d0b1c994dd --- /dev/null +++ b/friendly_promotions/.github/stale.yml @@ -0,0 +1 @@ +_extends: .github diff --git a/friendly_promotions/.github_changelog_generator b/friendly_promotions/.github_changelog_generator new file mode 100644 index 00000000000..eac0962107b --- /dev/null +++ b/friendly_promotions/.github_changelog_generator @@ -0,0 +1,2 @@ +issues=false +exclude-labels=infrastructure diff --git a/friendly_promotions/.gitignore b/friendly_promotions/.gitignore new file mode 100644 index 00000000000..1ba20966542 --- /dev/null +++ b/friendly_promotions/.gitignore @@ -0,0 +1,21 @@ +*.gem +\#* +*~ +.#* +.DS_Store +.idea +.project +.sass-cache +coverage +Gemfile.lock +Gemfile-local +tmp +nbproject +pkg +*.swp +spec/dummy +spec/examples.txt +/sandbox +.rvmrc +.ruby-version +.ruby-gemset diff --git a/friendly_promotions/.rspec b/friendly_promotions/.rspec new file mode 100644 index 00000000000..83e16f80447 --- /dev/null +++ b/friendly_promotions/.rspec @@ -0,0 +1,2 @@ +--color +--require spec_helper diff --git a/friendly_promotions/.rubocop.yml b/friendly_promotions/.rubocop.yml new file mode 100644 index 00000000000..b075a8f6826 --- /dev/null +++ b/friendly_promotions/.rubocop.yml @@ -0,0 +1,5 @@ +require: + - solidus_dev_support/rubocop + +AllCops: + NewCops: disable diff --git a/friendly_promotions/CHANGELOG.md b/friendly_promotions/CHANGELOG.md new file mode 100644 index 00000000000..825c32f0d03 --- /dev/null +++ b/friendly_promotions/CHANGELOG.md @@ -0,0 +1 @@ +# Changelog diff --git a/friendly_promotions/Gemfile b/friendly_promotions/Gemfile new file mode 100644 index 00000000000..2f2a55c68e6 --- /dev/null +++ b/friendly_promotions/Gemfile @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +source 'https://rubygems.org' +git_source(:github) { |repo| "https://github.com/#{repo}.git" } + +branch = ENV.fetch('SOLIDUS_BRANCH', 'master') +gem 'solidus', github: 'solidusio/solidus', branch: branch + +# The solidus_frontend gem has been pulled out since v3.2 +gem 'solidus_frontend', github: 'solidusio/solidus_frontend' if branch == 'master' +gem 'solidus_frontend' if branch >= 'v3.2' # rubocop:disable Bundler/DuplicatedGem + +# Needed to help Bundler figure out how to resolve dependencies, +# otherwise it takes forever to resolve them. +# See https://github.com/bundler/bundler/issues/6677 +gem 'rails', '>0.a' + + +# Provides basic authentication functionality for testing parts of your engine +gem 'solidus_auth_devise' + +case ENV.fetch('DB', nil) +when 'mysql' + gem 'mysql2' +when 'postgresql' + gem 'pg' +else + gem 'sqlite3' +end + +# While we still support Ruby < 3 we need to workaround a limitation in +# the 'async' gem that relies on the latest ruby, since RubyGems doesn't +# resolve gems based on the required ruby version. +gem 'async', '< 3' if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('3') + +gemspec + +# Use a local Gemfile to include development dependencies that might not be +# relevant for the project or for other contributors, e.g. pry-byebug. +# +# We use `send` instead of calling `eval_gemfile` to work around an issue with +# how Dependabot parses projects: https://github.com/dependabot/dependabot-core/issues/1658. +send(:eval_gemfile, 'Gemfile-local') if File.exist? 'Gemfile-local' diff --git a/friendly_promotions/LICENSE b/friendly_promotions/LICENSE new file mode 100644 index 00000000000..375c0dd192b --- /dev/null +++ b/friendly_promotions/LICENSE @@ -0,0 +1,26 @@ +Copyright (c) 2023 Martin Meyerhoff +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name Solidus nor the names of its contributors may be used to + endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/friendly_promotions/README.md b/friendly_promotions/README.md new file mode 100644 index 00000000000..950e71818ea --- /dev/null +++ b/friendly_promotions/README.md @@ -0,0 +1,73 @@ +# Solidus Friendly Promotions + +[![CircleCI](https://circleci.com/gh/solidusio-contrib/solidus_friendly_promotions.svg?style=shield)](https://circleci.com/gh/solidusio-contrib/solidus_friendly_promotions) +[![codecov](https://codecov.io/gh/solidusio-contrib/solidus_friendly_promotions/branch/master/graph/badge.svg)](https://codecov.io/gh/solidusio-contrib/solidus_friendly_promotions) + + + +## Installation + +Add solidus_friendly_promotions to your Gemfile: + +```ruby +gem 'solidus_friendly_promotions' +``` + +Bundle your dependencies and run the installation generator: + +```shell +bin/rails generate solidus_friendly_promotions:install +``` + +## Usage + + + +## Development + +### Testing the extension + +First bundle your dependencies, then run `bin/rake`. `bin/rake` will default to building the dummy +app if it does not exist, then it will run specs. The dummy app can be regenerated by using +`bin/rake extension:test_app`. + +```shell +bin/rake +``` + +To run [Rubocop](https://github.com/bbatsov/rubocop) static code analysis run + +```shell +bundle exec rubocop +``` + +When testing your application's integration with this extension you may use its factories. +You can load Solidus core factories along with this extension's factories using this statement: + +```ruby +SolidusDevSupport::TestingSupport::Factories.load_for(SolidusFriendlyPromotions::Engine) +``` + +### Running the sandbox + +To run this extension in a sandboxed Solidus application, you can run `bin/sandbox`. The path for +the sandbox app is `./sandbox` and `bin/rails` will forward any Rails commands to +`sandbox/bin/rails`. + +Here's an example: + +``` +$ bin/rails server +=> Booting Puma +=> Rails 6.0.2.1 application starting in development +* Listening on tcp://127.0.0.1:3000 +Use Ctrl-C to stop +``` + +### Releasing new versions + +Please refer to the [dedicated page](https://github.com/solidusio/solidus/wiki/How-to-release-extensions) in the Solidus wiki. + +## License + +Copyright (c) 2023 Martin Meyerhoff, released under the New BSD License. diff --git a/friendly_promotions/Rakefile b/friendly_promotions/Rakefile new file mode 100644 index 00000000000..c08aa46816c --- /dev/null +++ b/friendly_promotions/Rakefile @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +require 'solidus_dev_support/rake_tasks' +SolidusDevSupport::RakeTasks.install + +task default: 'extension:specs' diff --git a/friendly_promotions/app/assets/javascripts/spree/backend/solidus_friendly_promotions.js b/friendly_promotions/app/assets/javascripts/spree/backend/solidus_friendly_promotions.js new file mode 100644 index 00000000000..8aa3b014409 --- /dev/null +++ b/friendly_promotions/app/assets/javascripts/spree/backend/solidus_friendly_promotions.js @@ -0,0 +1,2 @@ +// Placeholder manifest file. +// the installer will append this file to the app vendored assets here: vendor/assets/javascripts/spree/backend/all.js' \ No newline at end of file diff --git a/friendly_promotions/app/assets/javascripts/spree/frontend/solidus_friendly_promotions.js b/friendly_promotions/app/assets/javascripts/spree/frontend/solidus_friendly_promotions.js new file mode 100644 index 00000000000..a79f2e948dd --- /dev/null +++ b/friendly_promotions/app/assets/javascripts/spree/frontend/solidus_friendly_promotions.js @@ -0,0 +1,2 @@ +// Placeholder manifest file. +// the installer will append this file to the app vendored assets here: vendor/assets/javascripts/spree/frontend/all.js' \ No newline at end of file diff --git a/friendly_promotions/app/assets/stylesheets/spree/backend/solidus_friendly_promotions.css b/friendly_promotions/app/assets/stylesheets/spree/backend/solidus_friendly_promotions.css new file mode 100644 index 00000000000..e3c236629e3 --- /dev/null +++ b/friendly_promotions/app/assets/stylesheets/spree/backend/solidus_friendly_promotions.css @@ -0,0 +1,4 @@ +/* +Placeholder manifest file. +the installer will append this file to the app vendored assets here: 'vendor/assets/stylesheets/spree/backend/all.css' +*/ diff --git a/friendly_promotions/app/assets/stylesheets/spree/frontend/solidus_friendly_promotions.css b/friendly_promotions/app/assets/stylesheets/spree/frontend/solidus_friendly_promotions.css new file mode 100644 index 00000000000..da236237c52 --- /dev/null +++ b/friendly_promotions/app/assets/stylesheets/spree/frontend/solidus_friendly_promotions.css @@ -0,0 +1,4 @@ +/* +Placeholder manifest file. +the installer will append this file to the app vendored assets here: 'vendor/assets/stylesheets/spree/frontend/all.css' +*/ diff --git a/friendly_promotions/bin/console b/friendly_promotions/bin/console new file mode 100755 index 00000000000..5170bfc2daa --- /dev/null +++ b/friendly_promotions/bin/console @@ -0,0 +1,17 @@ +#!/usr/bin/env ruby + +# frozen_string_literal: true + +require "bundler/setup" +require "solidus_friendly_promotions" + +# You can add fixtures and/or initialization code here to make experimenting +# with your gem easier. You can also use a different console, if you like. +$LOAD_PATH.unshift(*Dir["#{__dir__}/../app/*"]) + +# (If you use this, don't forget to add pry to your Gemfile!) +# require "pry" +# Pry.start + +require "irb" +IRB.start(__FILE__) diff --git a/friendly_promotions/bin/rails b/friendly_promotions/bin/rails new file mode 100755 index 00000000000..6dbbbc36e77 --- /dev/null +++ b/friendly_promotions/bin/rails @@ -0,0 +1,7 @@ +#!/usr/bin/env ruby + +if %w[g generate].include? ARGV.first + exec "#{__dir__}/rails-engine", *ARGV +else + exec "#{__dir__}/rails-sandbox", *ARGV +end diff --git a/friendly_promotions/bin/rails-engine b/friendly_promotions/bin/rails-engine new file mode 100755 index 00000000000..00cd82aeace --- /dev/null +++ b/friendly_promotions/bin/rails-engine @@ -0,0 +1,13 @@ +#!/usr/bin/env ruby +# This command will automatically be run when you run "rails" with Rails gems +# installed from the root of your application. + +ENGINE_ROOT = File.expand_path('..', __dir__) +ENGINE_PATH = File.expand_path('../lib/solidus_friendly_promotions/engine', __dir__) + +# Set up gems listed in the Gemfile. +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) +require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) + +require 'rails/all' +require 'rails/engine/commands' diff --git a/friendly_promotions/bin/rails-sandbox b/friendly_promotions/bin/rails-sandbox new file mode 100755 index 00000000000..ad2df04d0e7 --- /dev/null +++ b/friendly_promotions/bin/rails-sandbox @@ -0,0 +1,16 @@ +#!/usr/bin/env ruby + +app_root = 'sandbox' + +unless File.exist? "#{app_root}/bin/rails" + warn 'Creating the sandbox app...' + Dir.chdir "#{__dir__}/.." do + system "#{__dir__}/sandbox" or begin + warn 'Automatic creation of the sandbox app failed' + exit 1 + end + end +end + +Dir.chdir app_root +exec 'bin/rails', *ARGV diff --git a/friendly_promotions/bin/rake b/friendly_promotions/bin/rake new file mode 100755 index 00000000000..1e6eacd34e8 --- /dev/null +++ b/friendly_promotions/bin/rake @@ -0,0 +1,7 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("rake", "rake") diff --git a/friendly_promotions/bin/sandbox b/friendly_promotions/bin/sandbox new file mode 100755 index 00000000000..bf577b65c23 --- /dev/null +++ b/friendly_promotions/bin/sandbox @@ -0,0 +1,78 @@ +#!/usr/bin/env bash + +set -e +test -z "${DEBUG+empty_string}" || set -x + +test "$DB" = "sqlite" && export DB="sqlite3" + +if [ -z "$SOLIDUS_BRANCH" ] +then + echo "~~> Use 'export SOLIDUS_BRANCH=[master|v3.2|...]' to control the Solidus branch" + SOLIDUS_BRANCH="master" +fi +echo "~~> Using branch $SOLIDUS_BRANCH of solidus" + +if [ -z "$SOLIDUS_FRONTEND" ] +then + echo "~~> Use 'export SOLIDUS_FRONTEND=[solidus_frontend|solidus_starter_frontend]' to control the Solidus frontend" + SOLIDUS_FRONTEND="solidus_frontend" +fi +echo "~~> Using branch $SOLIDUS_FRONTEND as the solidus frontend" + +extension_name="solidus_friendly_promotions" + +# Stay away from the bundler env of the containing extension. +function unbundled { + ruby -rbundler -e'b = proc {system *ARGV}; Bundler.respond_to?(:with_unbundled_env) ? Bundler.with_unbundled_env(&b) : Bundler.with_clean_env(&b)' -- $@ +} + +rm -rf ./sandbox +unbundled bundle exec rails new sandbox \ + --database="${DB:-sqlite3}" \ + --skip-bundle \ + --skip-git \ + --skip-keeps \ + --skip-rc \ + --skip-spring \ + --skip-test \ + --skip-javascript + +if [ ! -d "sandbox" ]; then + echo 'sandbox rails application failed' + exit 1 +fi + +cd ./sandbox +cat <> Gemfile +gem 'solidus', github: 'solidusio/solidus', branch: '$SOLIDUS_BRANCH' +gem 'rails-i18n' +gem 'solidus_i18n' + +gem '$extension_name', path: '..' + +group :test, :development do + platforms :mri do + gem 'pry-byebug' + end +end +RUBY + +unbundled bundle install --gemfile Gemfile + +unbundled bundle exec rake db:drop db:create + +unbundled bundle exec rails generate solidus:install \ + --auto-accept \ + --user_class=Spree::User \ + --enforce_available_locales=true \ + --with-authentication=true \ + --payment-method=none \ + --frontend=${SOLIDUS_FRONTEND} \ + $@ + +unbundled bundle exec rails generate solidus:auth:install --auto-run-migrations +unbundled bundle exec rails generate ${extension_name}:install --auto-run-migrations + +echo +echo "🚀 Sandbox app successfully created for $extension_name!" +echo "🧪 This app is intended for test purposes." diff --git a/friendly_promotions/bin/setup b/friendly_promotions/bin/setup new file mode 100755 index 00000000000..67d919320aa --- /dev/null +++ b/friendly_promotions/bin/setup @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -euo pipefail +IFS=$'\n\t' +set -vx + +gem install bundler --conservative +bundle update +bin/rake clobber diff --git a/friendly_promotions/config/locales/en.yml b/friendly_promotions/config/locales/en.yml new file mode 100644 index 00000000000..f341cf450bc --- /dev/null +++ b/friendly_promotions/config/locales/en.yml @@ -0,0 +1,5 @@ +# Sample localization file for English. Add more files in this directory for other locales. +# See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. + +en: + hello: Hello world diff --git a/friendly_promotions/config/routes.rb b/friendly_promotions/config/routes.rb new file mode 100644 index 00000000000..59d02443fbd --- /dev/null +++ b/friendly_promotions/config/routes.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +Spree::Core::Engine.routes.draw do + # Add your extension routes here +end diff --git a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/install_generator.rb b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/install_generator.rb new file mode 100644 index 00000000000..31ac0a7c3d7 --- /dev/null +++ b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/install_generator.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module Generators + class InstallGenerator < Rails::Generators::Base + class_option :auto_run_migrations, type: :boolean, default: false + source_root File.expand_path('templates', __dir__) + + def self.exit_on_failure? + true + end + + def copy_initializer + template 'initializer.rb', 'config/initializers/solidus_friendly_promotions.rb' + end + + def add_javascripts + append_file 'vendor/assets/javascripts/spree/frontend/all.js', "//= require spree/frontend/solidus_friendly_promotions\n" + append_file 'vendor/assets/javascripts/spree/backend/all.js', "//= require spree/backend/solidus_friendly_promotions\n" + end + + def add_stylesheets + inject_into_file 'vendor/assets/stylesheets/spree/frontend/all.css', " *= require spree/frontend/solidus_friendly_promotions\n", before: %r{\*/}, verbose: true # rubocop:disable Layout/LineLength + inject_into_file 'vendor/assets/stylesheets/spree/backend/all.css', " *= require spree/backend/solidus_friendly_promotions\n", before: %r{\*/}, verbose: true # rubocop:disable Layout/LineLength + end + + def add_migrations + run 'bin/rails railties:install:migrations FROM=solidus_friendly_promotions' + end + + def run_migrations + run_migrations = options[:auto_run_migrations] || ['', 'y', 'Y'].include?(ask('Would you like to run the migrations now? [Y/n]')) # rubocop:disable Layout/LineLength + if run_migrations + run 'bin/rails db:migrate' + else + puts 'Skipping bin/rails db:migrate, don\'t forget to run it!' # rubocop:disable Rails/Output + end + end + end + end +end diff --git a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb new file mode 100644 index 00000000000..79e8c8e6990 --- /dev/null +++ b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +SolidusFriendlyPromotions.configure do |config| + # TODO: Remember to change this with the actual preferences you have implemented! + # config.sample_preference = 'sample_value' +end diff --git a/friendly_promotions/lib/solidus_friendly_promotions.rb b/friendly_promotions/lib/solidus_friendly_promotions.rb new file mode 100644 index 00000000000..5ae3dc6fcc7 --- /dev/null +++ b/friendly_promotions/lib/solidus_friendly_promotions.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +require 'solidus_friendly_promotions/configuration' +require 'solidus_friendly_promotions/version' +require 'solidus_friendly_promotions/engine' diff --git a/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb b/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb new file mode 100644 index 00000000000..d00757a289a --- /dev/null +++ b/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + class Configuration + # Define here the settings for this extension, e.g.: + # + # attr_accessor :my_setting + end + + class << self + def configuration + @configuration ||= Configuration.new + end + + alias config configuration + + def configure + yield configuration + end + end +end diff --git a/friendly_promotions/lib/solidus_friendly_promotions/engine.rb b/friendly_promotions/lib/solidus_friendly_promotions/engine.rb new file mode 100644 index 00000000000..1cf24dc3e2c --- /dev/null +++ b/friendly_promotions/lib/solidus_friendly_promotions/engine.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require 'solidus_core' +require 'solidus_support' + +module SolidusFriendlyPromotions + class Engine < Rails::Engine + include SolidusSupport::EngineExtensions + + isolate_namespace ::Spree + + engine_name 'solidus_friendly_promotions' + + # use rspec for tests + config.generators do |g| + g.test_framework :rspec + end + end +end diff --git a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/factories.rb b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/factories.rb new file mode 100644 index 00000000000..745a01e4c27 --- /dev/null +++ b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/factories.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +FactoryBot.define do +end diff --git a/friendly_promotions/lib/solidus_friendly_promotions/version.rb b/friendly_promotions/lib/solidus_friendly_promotions/version.rb new file mode 100644 index 00000000000..eb53a9e9496 --- /dev/null +++ b/friendly_promotions/lib/solidus_friendly_promotions/version.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + VERSION = '0.0.1' +end diff --git a/friendly_promotions/solidus_friendly_promotions.gemspec b/friendly_promotions/solidus_friendly_promotions.gemspec new file mode 100644 index 00000000000..b26ed10cbb0 --- /dev/null +++ b/friendly_promotions/solidus_friendly_promotions.gemspec @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require_relative 'lib/solidus_friendly_promotions/version' + +Gem::Specification.new do |spec| + spec.name = 'solidus_friendly_promotions' + spec.version = SolidusFriendlyPromotions::VERSION + spec.authors = ['Martin Meyerhoff'] + spec.email = 'mamhoff@gmail.com' + + spec.summary = 'A replacement for Solidus\' promotion system' + spec.description = 'Experimental replacement for the promotion system in Solidus' + spec.homepage = 'https://github.com/solidusio-contrib/solidus_friendly_promotions#readme' + spec.license = 'BSD-3-Clause' + + spec.metadata['homepage_uri'] = spec.homepage + spec.metadata['source_code_uri'] = 'https://github.com/solidusio-contrib/solidus_friendly_promotions' + spec.metadata['changelog_uri'] = 'https://github.com/solidusio-contrib/solidus_friendly_promotions/blob/master/CHANGELOG.md' + + spec.required_ruby_version = Gem::Requirement.new('>= 2.5', '< 4') + + # Specify which files should be added to the gem when it is released. + # The `git ls-files -z` loads the files in the RubyGem that have been added into git. + files = Dir.chdir(__dir__) { `git ls-files -z`.split("\x0") } + + spec.files = files.grep_v(%r{^(test|spec|features)/}) + spec.test_files = files.grep(%r{^(test|spec|features)/}) + spec.bindir = "exe" + spec.executables = files.grep(%r{^exe/}) { |f| File.basename(f) } + spec.require_paths = ["lib"] + + spec.add_dependency 'solidus_core', ['>= 3.0.0', '< 5'] + spec.add_dependency 'solidus_support', '~> 0.5' + + spec.add_development_dependency 'solidus_dev_support', '~> 2.6' +end diff --git a/friendly_promotions/spec/spec_helper.rb b/friendly_promotions/spec/spec_helper.rb new file mode 100644 index 00000000000..62c1bab47b0 --- /dev/null +++ b/friendly_promotions/spec/spec_helper.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +# Configure Rails Environment +ENV['RAILS_ENV'] = 'test' + +# Run Coverage report +require 'solidus_dev_support/rspec/coverage' + +# Create the dummy app if it's still missing. +dummy_env = "#{__dir__}/dummy/config/environment.rb" +system 'bin/rake extension:test_app' unless File.exist? dummy_env +require dummy_env + +# Requires factories and other useful helpers defined in spree_core. +require 'solidus_dev_support/rspec/feature_helper' + +# Requires supporting ruby files with custom matchers and macros, etc, +# in spec/support/ and its subdirectories. +Dir["#{__dir__}/support/**/*.rb"].sort.each { |f| require f } + +# Requires factories defined in Solidus core and this extension. +# See: lib/solidus_friendly_promotions/testing_support/factories.rb +SolidusDevSupport::TestingSupport::Factories.load_for(SolidusFriendlyPromotions::Engine) + +RSpec.configure do |config| + config.infer_spec_type_from_file_location! + config.use_transactional_fixtures = false + + if Spree.solidus_gem_version < Gem::Version.new('2.11') + config.extend Spree::TestingSupport::AuthorizationHelpers::Request, type: :system + end +end From a7ed882f3252a34c26ceaeb6d09e48797561353e Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 26 May 2023 10:47:09 +0200 Subject: [PATCH 181/834] Add integration spec This integration spec comes from the experimental PR https://github.com/solidusio/solidus/pull/4296. That PR will very probably not be merged, so I'm moving it here as a vantage point. --- .../spec/models/promotion/integration_spec.rb | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 friendly_promotions/spec/models/promotion/integration_spec.rb diff --git a/friendly_promotions/spec/models/promotion/integration_spec.rb b/friendly_promotions/spec/models/promotion/integration_spec.rb new file mode 100644 index 00000000000..555e3992668 --- /dev/null +++ b/friendly_promotions/spec/models/promotion/integration_spec.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe "Promotion System" do + context "A promotion that creates line item adjustments" do + let(:shirt) { create(:product) } + let(:pants) { create(:product) } + let(:promotion) { create(:promotion, name: "20% off Shirts", apply_automatically: true) } + let(:order) { create(:order) } + + before do + promotion.rules << rule + promotion.actions << action + order.contents.add(shirt.master, 1) + order.contents.add(pants.master, 1) + end + + context "with an order-level rule" do + let(:rule) { Spree::Promotion::Rules::Product.new(products: [shirt]) } + + context "with an order level action" do + let(:calculator) { Spree::Calculator::FlatPercentItemTotal.new(preferred_flat_percent: 20) } + let(:action) { Spree::Promotion::Actions::CreateAdjustment.new(calculator: calculator) } + + it "creates one order-level adjustment" do + expect(order.adjustments.length).to eq(1) + expect(order.total).to eq(31.98) + expect(order.item_total).to eq(39.98) + # This is wrong! But order level adjustments can't work any other way + expect(order.item_total_before_tax).to eq(39.98) + expect(order.line_items.flat_map(&:adjustments)).to be_empty + end + end + + context "with an line item level action" do + let(:calculator) { Spree::Calculator::PercentOnLineItem.new(preferred_percent: 20) } + let(:action) { Spree::Promotion::Actions::CreateItemAdjustments.new(calculator: calculator) } + + it "creates one order-level adjustment" do + pending + expect(order.adjustments).to be_empty + expect(order.total).to eq(31.98) + expect(order.item_total).to eq(39.98) + expect(order.item_total_before_tax).to eq(31.98) + expect(order.line_items.flat_map(&:adjustments).length).to eq(2) + end + end + end + + context "with a line-item level rule" do + let(:rule) { Spree::Promotion::Rules::LineItemProduct.new(products: [shirt]) } + + context "with an order level action" do + let(:calculator) { Spree::Calculator::FlatPercentItemTotal.new(preferred_flat_percent: 20) } + let(:action) { Spree::Promotion::Actions::CreateAdjustment.new(calculator: calculator) } + + it "creates one order-level adjustment" do + pending + # Whoops - this works because line item level rules don't affect order-level actions :( + expect(order.adjustments.length).to eq(1) + expect(order.total).to eq(31.98) + expect(order.item_total).to eq(39.98) + # This is wrong! But order level adjustments can't work any other way + expect(order.item_total_before_tax).to eq(39.98) + expect(order.line_items.flat_map(&:adjustments)).to be_empty + end + end + + context "with an line item level action" do + let(:calculator) { Spree::Calculator::PercentOnLineItem.new(preferred_percent: 20) } + let(:action) { Spree::Promotion::Actions::CreateItemAdjustments.new(calculator: calculator) } + + it "creates one line item level adjustment" do + pending + expect(order.adjustments).to be_empty + expect(order.total).to eq(35.98) + expect(order.item_total).to eq(39.98) + expect(order.item_total_before_tax).to eq(35.98) + expect(order.line_items.flat_map(&:adjustments).length).to eq(1) + end + end + end + end +end From ff8f57cec1b708dfa71267e7fc597ccf8518f129 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 26 May 2023 11:53:31 +0200 Subject: [PATCH 182/834] Use Solidus main branch No weird errors! Much good! --- friendly_promotions/Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/friendly_promotions/Gemfile b/friendly_promotions/Gemfile index 2f2a55c68e6..0a229872832 100644 --- a/friendly_promotions/Gemfile +++ b/friendly_promotions/Gemfile @@ -3,7 +3,7 @@ source 'https://rubygems.org' git_source(:github) { |repo| "https://github.com/#{repo}.git" } -branch = ENV.fetch('SOLIDUS_BRANCH', 'master') +branch = ENV.fetch('SOLIDUS_BRANCH', 'main') gem 'solidus', github: 'solidusio/solidus', branch: branch # The solidus_frontend gem has been pulled out since v3.2 From 419be4b35a9b6b3a03ebb699bce503b5f5d46be4 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 31 May 2023 12:45:59 +0200 Subject: [PATCH 183/834] Add RSpec ActiveModel Mocks These are needed to import the promotion rule specs. --- friendly_promotions/solidus_friendly_promotions.gemspec | 1 + friendly_promotions/spec/spec_helper.rb | 3 +++ 2 files changed, 4 insertions(+) diff --git a/friendly_promotions/solidus_friendly_promotions.gemspec b/friendly_promotions/solidus_friendly_promotions.gemspec index b26ed10cbb0..05cb6b99c9a 100644 --- a/friendly_promotions/solidus_friendly_promotions.gemspec +++ b/friendly_promotions/solidus_friendly_promotions.gemspec @@ -33,4 +33,5 @@ Gem::Specification.new do |spec| spec.add_dependency 'solidus_support', '~> 0.5' spec.add_development_dependency 'solidus_dev_support', '~> 2.6' + spec.add_development_dependency 'rspec-activemodel-mocks', '~> 1.0' end diff --git a/friendly_promotions/spec/spec_helper.rb b/friendly_promotions/spec/spec_helper.rb index 62c1bab47b0..1cb2698e589 100644 --- a/friendly_promotions/spec/spec_helper.rb +++ b/friendly_promotions/spec/spec_helper.rb @@ -14,6 +14,9 @@ # Requires factories and other useful helpers defined in spree_core. require 'solidus_dev_support/rspec/feature_helper' +# Explicitly load activemodel mocks +require 'rspec-activemodel-mocks' + # Requires supporting ruby files with custom matchers and macros, etc, # in spec/support/ and its subdirectories. Dir["#{__dir__}/support/**/*.rb"].sort.each { |f| require f } From 06e73ce07e196294dc0bea44411d01dd4ea83a67 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 31 May 2023 12:50:56 +0200 Subject: [PATCH 184/834] Import Solidus Promotion Rules Since `Spree::Promotion` is an STI class, we can redefine all Solidus promotion rules in another place and not touch any of the core Solidus things. --- .../rules/first_order.rb | 36 +++++ .../rules/first_repeat_purchase_since.rb | 34 +++++ .../rules/item_total.rb | 95 ++++++++++++ .../rules/line_item_option_value.rb | 39 +++++ .../rules/line_item_product.rb | 46 ++++++ .../rules/line_item_taxon.rb | 52 +++++++ .../rules/nth_order.rb | 44 ++++++ .../rules/one_use_per_user.rb | 23 +++ .../rules/option_value.rb | 28 ++++ .../rules/product.rb | 73 +++++++++ .../rules/store.rb | 24 +++ .../rules/taxon.rb | 73 +++++++++ .../solidus_friendly_promotions/rules/user.rb | 32 ++++ .../rules/user_logged_in.rb | 18 +++ .../rules/user_role.rb | 43 ++++++ .../spec/models/promotion/integration_spec.rb | 6 +- .../rules/first_order_spec.rb | 87 +++++++++++ .../rules/first_repeat_purchase_since_spec.rb | 71 +++++++++ .../rules/item_total_spec.rb | 128 ++++++++++++++++ .../rules/line_item_product_spec.rb | 47 ++++++ .../rules/line_item_taxon_spec.rb | 92 +++++++++++ .../rules/nth_order_spec.rb | 72 +++++++++ .../rules/one_use_per_user_spec.rb | 54 +++++++ .../rules/option_value_spec.rb | 48 ++++++ .../rules/product_spec.rb | 127 +++++++++++++++ .../rules/store_spec.rb | 35 +++++ .../rules/taxon_spec.rb | 144 ++++++++++++++++++ .../rules/user_logged_in_spec.rb | 33 ++++ .../rules/user_role_spec.rb | 88 +++++++++++ .../rules/user_spec.rb | 39 +++++ 30 files changed, 1727 insertions(+), 4 deletions(-) create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/rules/first_order.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_option_value.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/rules/option_value.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/rules/user_role.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_order_spec.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_repeat_purchase_since_spec.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/rules/item_total_spec.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/rules/line_item_product_spec.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/rules/line_item_taxon_spec.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/rules/nth_order_spec.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/rules/one_use_per_user_spec.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/rules/option_value_spec.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/rules/product_spec.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/rules/store_spec.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/rules/taxon_spec.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_logged_in_spec.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_role_spec.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_spec.rb diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_order.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_order.rb new file mode 100644 index 00000000000..5b92a0cf9a4 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_order.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module Rules + class FirstOrder < ::Spree::PromotionRule + attr_reader :user, :email + + def applicable?(promotable) + promotable.is_a?(Spree::Order) + end + + def eligible?(order, options = {}) + @user = order.try(:user) || options[:user] + @email = order.email + + if user || email + if !completed_orders.blank? && completed_orders.first != order + eligibility_errors.add(:base, eligibility_error_message(:not_first_order), error_code: :not_first_order) + end + end + + eligibility_errors.empty? + end + + private + + def completed_orders + user ? user.orders.complete : orders_by_email + end + + def orders_by_email + Spree::Order.where(email: email).complete + end + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb new file mode 100644 index 00000000000..eab1e40fa42 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module Rules + class FirstRepeatPurchaseSince < ::Spree::PromotionRule + preference :days_ago, :integer, default: 365 + validates :preferred_days_ago, numericality: { only_integer: true, greater_than: 0 } + + # This promotion is applicable to orders only. + def applicable?(promotable) + promotable.is_a?(Spree::Order) + end + + # This is never eligible if the order does not have a user, and that user does not have any previous completed orders. + # + # This is eligible if the user's most recently completed order is more than the preferred days ago + # @param order [Spree::Order] + def eligible?(order, _options = {}) + return false unless order.user + + last_order = last_completed_order(order.user) + return false unless last_order + + last_order.completed_at < preferred_days_ago.days.ago + end + + private + + def last_completed_order(user) + user.orders.complete.order(:completed_at).last + end + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb new file mode 100644 index 00000000000..153134ee604 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb @@ -0,0 +1,95 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module Rules + # A rule to apply to an order greater than (or greater than or equal to) + # a specific amount + # + # To add extra operators please override `self.operators_map` or any other helper method. + # To customize the error message you can also override `ineligible_message`. + class ItemTotal < ::Spree::PromotionRule + include ActiveSupport::Deprecation::DeprecatedConstantAccessor + + preference :amount, :decimal, default: 100.00 + preference :currency, :string, default: ->{ Spree::Config[:currency] } + preference :operator, :string, default: 'gt' + + # The list of allowed operators names mapped to their symbols. + def self.operators_map + { + gte: :>=, + gt: :>, + } + end + + def self.operator_options + operators_map.map do |name, _method| + [I18n.t(name, scope: 'spree.item_total_rule.operators'), name] + end + end + + # @deprecated + OPERATORS = operators_map.keys.map(&:to_s) + deprecate_constant( + :OPERATORS, + :operators_map, + message: "OPERATORS is deprecated! Use `operators_map.keys.map(&:to_s)` instead.", + deprecator: Spree::Deprecation, + ) + + def applicable?(promotable) + promotable.is_a?(Spree::Order) + end + + def eligible?(order, _options = {}) + return false unless order.currency == preferred_currency + + unless total_for_order(order).send(operator, threshold) + eligibility_errors.add(:base, ineligible_message, error_code: ineligible_error_code) + end + + eligibility_errors.empty? + end + + private + + def operator + self.class.operators_map.fetch( + preferred_operator.to_sym, + preferred_operator_default, + ) + end + + def total_for_order(order) + order.item_total + end + + def threshold + BigDecimal(preferred_amount.to_s) + end + + def formatted_amount + Spree::Money.new(preferred_amount, currency: preferred_currency).to_s + end + + def ineligible_message + case preferred_operator.to_s + when 'gte' + eligibility_error_message(:item_total_less_than, amount: formatted_amount) + when 'gt' + eligibility_error_message(:item_total_less_than_or_equal, amount: formatted_amount) + else + eligibility_error_message(:item_total_doesnt_match_with_operator, amount: formatted_amount, operator: preferred_operator) + end + end + + def ineligible_error_code + if preferred_operator == 'gte' + :item_total_less_than + else + :item_total_less_than_or_equal + end + end + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_option_value.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_option_value.rb new file mode 100644 index 00000000000..ceff257c286 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_option_value.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module Rules + class LineItemOptionValue < ::Spree::PromotionRule + preference :eligible_values, :hash + + def applicable?(promotable) + promotable.is_a?(Spree::LineItem) + end + + def eligible?(line_item, _options = {}) + pid = line_item.product.id + ovids = line_item.variant.option_values.pluck(:id) + + product_ids.include?(pid) && (value_ids(pid) & ovids).present? + end + + def preferred_eligible_values + values = preferences[:eligible_values] || {} + Hash[values.keys.map(&:to_i).zip( + values.values.map do |value| + (value.is_a?(Array) ? value : value.split(",")).map(&:to_i) + end + )] + end + + private + + def product_ids + preferred_eligible_values.keys + end + + def value_ids(product_id) + preferred_eligible_values[product_id] + end + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb new file mode 100644 index 00000000000..d930e6f5681 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module Rules + # A rule to apply a promotion only to line items with or without a chosen product + class LineItemProduct < Spree::PromotionRule + MATCH_POLICIES = %w(include exclude) + + has_many :product_promotion_rules, + dependent: :destroy, + foreign_key: :promotion_rule_id, + class_name: "Spree::ProductPromotionRule" + has_many :products, + class_name: "Spree::Product", + through: :product_promotion_rules + + preference :match_policy, :string, default: MATCH_POLICIES.first + + def applicable?(promotable) + promotable.is_a?(Spree::LineItem) + end + + def eligible?(line_item, _options = {}) + if inverse? + !product_ids.include?(line_item.variant.product_id) + else + product_ids.include?(line_item.variant.product_id) + end + end + + def product_ids_string + product_ids.join(",") + end + + def product_ids_string=(product_ids) + self.product_ids = product_ids.to_s.split(",").map(&:strip) + end + + private + + def inverse? + preferred_match_policy == "exclude" + end + end + end + end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb new file mode 100644 index 00000000000..ae6683c6f4b --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module Rules + class LineItemTaxon < ::Spree::PromotionRule + has_many :promotion_rule_taxons, class_name: 'Spree::PromotionRuleTaxon', foreign_key: :promotion_rule_id, + dependent: :destroy + has_many :taxons, through: :promotion_rule_taxons, class_name: 'Spree::Taxon' + + MATCH_POLICIES = %w(include exclude) + + validates_inclusion_of :preferred_match_policy, in: MATCH_POLICIES + + preference :match_policy, :string, default: MATCH_POLICIES.first + def applicable?(promotable) + promotable.is_a?(Spree::LineItem) + end + + def eligible?(line_item, _options = {}) + found = Spree::Classification.where( + product_id: line_item.variant.product_id, + taxon_id: rule_taxon_ids_with_children + ).exists? + + case preferred_match_policy + when 'include' + found + when 'exclude' + !found + else + raise "unexpected match policy: #{preferred_match_policy.inspect}" + end + end + + def taxon_ids_string + taxons.pluck(:id).join(',') + end + + def taxon_ids_string=(taxon_ids) + taxon_ids = taxon_ids.to_s.split(',').map(&:strip) + self.taxons = Spree::Taxon.find(taxon_ids) + end + + private + + # ids of taxons rules and taxons rules children + def rule_taxon_ids_with_children + taxons.flat_map { |taxon| taxon.self_and_descendants.ids }.uniq + end + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb new file mode 100644 index 00000000000..3c8d94ca5cf --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + + module Rules + class NthOrder < ::Spree::PromotionRule + preference :nth_order, :integer, default: 2 + # It does not make sense to have this apply to the first order using preferred_nth_order == 1 + # Instead we could use the first_order rule + validates :preferred_nth_order, numericality: { only_integer: true, greater_than: 1 } + + # This promotion is applicable to orders only. + def applicable?(promotable) + promotable.is_a?(Spree::Order) + end + + # This is never eligible if the order does not have a user, and that user does not have any previous completed orders. + # + # Use the first order rule if you want a promotion to be applied to the first order for a user. + # @param order [Spree::Order] + def eligible?(order, _options = {}) + return false unless order.user + + nth_order?(order) + end + + private + + def completed_order_count(order) + order. + user. + orders. + complete. + where(Spree::Order.arel_table[:completed_at].lt(order.completed_at || Time.current)). + count + end + + def nth_order?(order) + count = completed_order_count(order) + 1 + count == preferred_nth_order + end + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb new file mode 100644 index 00000000000..2b174a8c9c7 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module Rules + class OneUsePerUser < ::Spree::PromotionRule + def applicable?(promotable) + promotable.is_a?(Spree::Order) + end + + def eligible?(order, _options = {}) + if order.user.present? + if promotion.used_by?(order.user, [order]) + eligibility_errors.add(:base, eligibility_error_message(:limit_once_per_user), error_code: :limit_once_per_user) + end + else + eligibility_errors.add(:base, eligibility_error_message(:no_user_specified), error_code: :no_user_specified) + end + + eligibility_errors.empty? + end + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/option_value.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/option_value.rb new file mode 100644 index 00000000000..26e1cffba9a --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/option_value.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module Rules + class OptionValue < ::Spree::PromotionRule + preference :eligible_values, :hash + + def applicable?(promotable) + promotable.is_a?(Spree::Order) + end + + def eligible?(order, _options = {}) + order.line_items.any? do |item| + LineItemOptionValue.new(preferred_eligible_values: preferred_eligible_values).eligible?(item) + end + end + + def preferred_eligible_values + values = preferences[:eligible_values] || {} + Hash[values.keys.map(&:to_i).zip( + values.values.map do |value| + (value.is_a?(Array) ? value : value.split(",")).map(&:to_i) + end + )] + end + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb new file mode 100644 index 00000000000..f6b0ce019da --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module Rules + # A rule to limit a promotion based on products in the order. Can + # require all or any of the products to be present. Valid products + # either come from assigned product group or are assingned directly to + # the rule. + class Product < ::Spree::PromotionRule + has_many :product_promotion_rules, dependent: :destroy, foreign_key: :promotion_rule_id, + class_name: 'Spree::ProductPromotionRule' + has_many :products, class_name: 'Spree::Product', through: :product_promotion_rules + + def preload_relations + [:products] + end + + MATCH_POLICIES = %w(any all none) + + validates_inclusion_of :preferred_match_policy, in: MATCH_POLICIES + + preference :match_policy, :string, default: MATCH_POLICIES.first + + # scope/association that is used to test eligibility + def eligible_products + products + end + + def applicable?(promotable) + promotable.is_a?(Spree::Order) + end + + def eligible?(order, _options = {}) + return true if eligible_products.empty? + + case preferred_match_policy + when "all" + unless eligible_products.all? { |product| order_products(order).include?(product) } + eligibility_errors.add(:base, eligibility_error_message(:missing_product), error_code: :missing_product) + end + when "any" + unless order_products(order).any? { |product| eligible_products.include?(product) } + eligibility_errors.add(:base, eligibility_error_message(:no_applicable_products), + error_code: :no_applicable_products) + end + when "none" + unless order_products(order).none? { |product| eligible_products.include?(product) } + eligibility_errors.add(:base, eligibility_error_message(:has_excluded_product), + error_code: :has_excluded_product) + end + else + raise "unexpected match policy: #{preferred_match_policy.inspect}" + end + + eligibility_errors.empty? + end + + def product_ids_string + product_ids.join(',') + end + + def product_ids_string=(product_ids) + self.product_ids = product_ids.to_s.split(',').map(&:strip) + end + + private + + def order_products(order) + order.line_items.map(&:variant).map(&:product) + end + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb new file mode 100644 index 00000000000..a0fe76bf694 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module Rules + class Store < ::Spree::PromotionRule + has_many :promotion_rule_stores, class_name: "Spree::PromotionRuleStore", + foreign_key: :promotion_rule_id, + dependent: :destroy + has_many :stores, through: :promotion_rule_stores, class_name: "Spree::Store" + + def preload_relations + [:stores] + end + + def applicable?(promotable) + promotable.is_a?(Spree::Order) + end + + def eligible?(order, _options = {}) + stores.none? || stores.include?(order.store) + end + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb new file mode 100644 index 00000000000..6ab8658ad58 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module Rules + class Taxon < ::Spree::PromotionRule + has_many :promotion_rule_taxons, class_name: 'Spree::PromotionRuleTaxon', foreign_key: :promotion_rule_id, + dependent: :destroy + has_many :taxons, through: :promotion_rule_taxons, class_name: 'Spree::Taxon' + + def preload_relations + [:taxons] + end + + MATCH_POLICIES = %w(any all none) + + validates_inclusion_of :preferred_match_policy, in: MATCH_POLICIES + + preference :match_policy, :string, default: MATCH_POLICIES.first + def applicable?(promotable) + promotable.is_a?(Spree::Order) + end + + def eligible?(order, _options = {}) + order_taxons = taxons_in_order(order) + + case preferred_match_policy + when 'all' + matches_all = taxons.all? do |rule_taxon| + order_taxons.where(id: rule_taxon.self_and_descendants.ids).exists? + end + + unless matches_all + eligibility_errors.add(:base, eligibility_error_message(:missing_taxon), error_code: :missing_taxon) + end + when 'any' + unless order_taxons.where(id: rule_taxon_ids_with_children).exists? + eligibility_errors.add(:base, eligibility_error_message(:no_matching_taxons), error_code: :no_matching_taxons) + end + when 'none' + if order_taxons.where(id: rule_taxon_ids_with_children).exists? + eligibility_errors.add(:base, eligibility_error_message(:has_excluded_taxon), error_code: :has_excluded_taxon) + end + else + raise "unexpected match policy: #{preferred_match_policy.inspect}" + end + + eligibility_errors.empty? + end + + def taxon_ids_string + taxons.pluck(:id).join(',') + end + + def taxon_ids_string=(taxon_ids) + taxon_ids = taxon_ids.to_s.split(',').map(&:strip) + self.taxons = Spree::Taxon.find(taxon_ids) + end + + private + + # All taxons in an order + def taxons_in_order(order) + Spree::Taxon.joins(products: { variants_including_master: :line_items }) + .where(spree_line_items: { order_id: order.id }).distinct + end + + # ids of taxons rules and taxons rules children + def rule_taxon_ids_with_children + taxons.flat_map { |taxon| taxon.self_and_descendants.ids }.uniq + end + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb new file mode 100644 index 00000000000..1a0f08ad861 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module Rules + class User < ::Spree::PromotionRule + has_many :promotion_rule_users, class_name: 'Spree::PromotionRuleUser', + foreign_key: :promotion_rule_id, + dependent: :destroy + has_many :users, through: :promotion_rule_users, class_name: Spree::UserClassHandle.new + + def preload_relations + [:users] + end + + def applicable?(promotable) + promotable.is_a?(Spree::Order) + end + + def eligible?(order, _options = {}) + users.include?(order.user) + end + + def user_ids_string + user_ids.join(',') + end + + def user_ids_string=(user_ids) + self.user_ids = user_ids.to_s.split(',').map(&:strip) + end + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb new file mode 100644 index 00000000000..1f7f02afd3b --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module Rules + class UserLoggedIn < ::Spree::PromotionRule + def applicable?(promotable) + promotable.is_a?(Spree::Order) + end + + def eligible?(order, _options = {}) + unless order.user.present? + eligibility_errors.add(:base, eligibility_error_message(:no_user_specified), error_code: :no_user_specified) + end + eligibility_errors.empty? + end + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_role.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_role.rb new file mode 100644 index 00000000000..94fe8a65adf --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_role.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module Rules + class UserRole < ::Spree::PromotionRule + preference :role_ids, :array, default: [] + + MATCH_POLICIES = %w(any all) + preference :match_policy, default: MATCH_POLICIES.first + + def applicable?(promotable) + promotable.is_a?(Spree::Order) + end + + def eligible?(order, _options = {}) + return false unless order.user + if all_match_policy? + match_all_roles?(order) + else + match_any_roles?(order) + end + end + + private + + def all_match_policy? + preferred_match_policy == 'all' && preferred_role_ids.present? + end + + def user_roles(order) + order.user.spree_roles.where(id: preferred_role_ids) + end + + def match_all_roles?(order) + user_roles(order).count == preferred_role_ids.count + end + + def match_any_roles?(order) + user_roles(order).exists? + end + end + end +end diff --git a/friendly_promotions/spec/models/promotion/integration_spec.rb b/friendly_promotions/spec/models/promotion/integration_spec.rb index 555e3992668..4d065132bd0 100644 --- a/friendly_promotions/spec/models/promotion/integration_spec.rb +++ b/friendly_promotions/spec/models/promotion/integration_spec.rb @@ -17,7 +17,7 @@ end context "with an order-level rule" do - let(:rule) { Spree::Promotion::Rules::Product.new(products: [shirt]) } + let(:rule) { SolidusFriendlyPromotions::Rules::Product.new(products: [shirt]) } context "with an order level action" do let(:calculator) { Spree::Calculator::FlatPercentItemTotal.new(preferred_flat_percent: 20) } @@ -38,7 +38,6 @@ let(:action) { Spree::Promotion::Actions::CreateItemAdjustments.new(calculator: calculator) } it "creates one order-level adjustment" do - pending expect(order.adjustments).to be_empty expect(order.total).to eq(31.98) expect(order.item_total).to eq(39.98) @@ -49,14 +48,13 @@ end context "with a line-item level rule" do - let(:rule) { Spree::Promotion::Rules::LineItemProduct.new(products: [shirt]) } + let(:rule) { SolidusFriendlyPromotions::Rules::LineItemProduct.new(products: [shirt]) } context "with an order level action" do let(:calculator) { Spree::Calculator::FlatPercentItemTotal.new(preferred_flat_percent: 20) } let(:action) { Spree::Promotion::Actions::CreateAdjustment.new(calculator: calculator) } it "creates one order-level adjustment" do - pending # Whoops - this works because line item level rules don't affect order-level actions :( expect(order.adjustments.length).to eq(1) expect(order.total).to eq(31.98) diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_order_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_order_spec.rb new file mode 100644 index 00000000000..0a958413e3c --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_order_spec.rb @@ -0,0 +1,87 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SolidusFriendlyPromotions::Rules::FirstOrder, type: :model do + let(:rule) { SolidusFriendlyPromotions::Rules::FirstOrder.new } + let(:order) { mock_model(Spree::Order, user: nil, email: nil) } + let(:user) { mock_model(Spree::LegacyUser) } + + context "without a user or email" do + it { expect(rule).to be_eligible(order) } + it "does not set an error message" do + rule.eligible?(order) + expect(rule.eligibility_errors.full_messages.first). + to be_nil + end + end + + context "first order" do + context "for a signed user" do + context "with no completed orders" do + before(:each) do + allow(user).to receive_message_chain(:orders, complete: []) + end + + specify do + allow(order).to receive_messages(user: user) + expect(rule).to be_eligible(order) + end + + it "should be eligible when user passed in payload data" do + expect(rule).to be_eligible(order, user: user) + end + end + + context "with completed orders" do + before(:each) do + allow(order).to receive_messages(user: user) + end + + it "should be eligible when checked against first completed order" do + allow(user).to receive_message_chain(:orders, complete: [order]) + expect(rule).to be_eligible(order) + end + + context "with another order" do + before { allow(user).to receive_message_chain(:orders, complete: [mock_model(Spree::Order)]) } + it { expect(rule).not_to be_eligible(order) } + it "sets an error message" do + rule.eligible?(order) + expect(rule.eligibility_errors.full_messages.first). + to eq "This coupon code can only be applied to your first order." + end + it "sets an error code" do + rule.eligible?(order) + expect(rule.eligibility_errors.details[:base].first[:error_code]). + to eq :not_first_order + end + end + end + end + + context "for a guest user" do + let(:email) { 'user@solidus.io' } + before { allow(order).to receive_messages email: 'user@solidus.io' } + + context "with no other orders" do + it { expect(rule).to be_eligible(order) } + end + + context "with another order" do + before { allow(rule).to receive_messages(orders_by_email: [mock_model(Spree::Order)]) } + it { expect(rule).not_to be_eligible(order) } + it "sets an error message" do + rule.eligible?(order) + expect(rule.eligibility_errors.full_messages.first). + to eq "This coupon code can only be applied to your first order." + end + it "sets an error code" do + rule.eligible?(order) + expect(rule.eligibility_errors.details[:base].first[:error_code]). + to eq :not_first_order + end + end + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_repeat_purchase_since_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_repeat_purchase_since_spec.rb new file mode 100644 index 00000000000..a827a300f75 --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_repeat_purchase_since_spec.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SolidusFriendlyPromotions::Rules::FirstRepeatPurchaseSince do + describe "#applicable?" do + subject { described_class.new.applicable?(promotable) } + + context "when the promotable is an order" do + let(:promotable) { Spree::Order.new } + + it { is_expected.to be true } + end + + context "when the promotable is not a order" do + let(:promotable) { Spree::LineItem.new } + + it { is_expected.to be false } + end + end + + describe "eligible?" do + let(:instance) { described_class.new } + subject { instance.eligible?(order) } + + before do + instance.preferred_days_ago = 365 + end + + context "when the order does not have a user" do + let(:order) { Spree::Order.new } + + it { is_expected.to be false } + end + + context "when the order has a user" do + let(:order) { create :order } + let(:user) { order.user } + + context "when the user has completed orders" do + let(:order_completion_date_1) { 1.day.ago } + let(:order_completion_date_2) { 1.day.ago } + before do + old_order_1 = create :completed_order_with_totals, user: user + old_order_1.update(completed_at: order_completion_date_1) + + old_order_2 = create :completed_order_with_totals, user: user + old_order_2.update(completed_at: order_completion_date_2) + end + + context "the last completed order was greater than the preferred days ago" do + let(:order_completion_date_1) { 14.months.ago } + let(:order_completion_date_2) { 13.months.ago } + + it { is_expected.to be true } + end + + context "the last completed order was less than the preferred days ago" do + let(:order_completion_date_1) { 14.months.ago } + let(:order_completion_date_2) { 11.months.ago } + + it { is_expected.to be false } + end + end + + context "when the user has no completed orders " do + it { is_expected.to be false } + end + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/item_total_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/item_total_spec.rb new file mode 100644 index 00000000000..f1c6444f98f --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/item_total_spec.rb @@ -0,0 +1,128 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SolidusFriendlyPromotions::Rules::ItemTotal, type: :model do + let(:rule) do + SolidusFriendlyPromotions::Rules::ItemTotal.new( + preferred_amount: preferred_amount, + preferred_operator: preferred_operator + ) + end + let(:order) { double(:order, item_total: item_total, currency: order_currency) } + let(:preferred_amount) { 50 } + let(:order_currency) { 'USD' } + + context "preferred operator set to gt" do + let(:preferred_operator) { 'gt' } + + context "item total is greater than preferred amount" do + let(:item_total) { 51 } + + it "should be eligible when item total is greater than preferred amount" do + expect(rule).to be_eligible(order) + end + + context "when the order is a different currency" do + let(:order_currency) { "CAD" } + + it "is not eligible" do + expect(rule).not_to be_eligible(order) + end + end + end + + context "when item total is equal to preferred amount" do + let(:item_total) { 50 } + + it "is not eligible" do + expect(rule).not_to be_eligible(order) + end + + it "set an error message" do + rule.eligible?(order) + expect(rule.eligibility_errors.full_messages.first). + to eq "This coupon code can't be applied to orders less than or equal to $50.00." + end + it "sets an error code" do + rule.eligible?(order) + expect(rule.eligibility_errors.details[:base].first[:error_code]). + to eq :item_total_less_than_or_equal + end + end + + context "when item total is lower than preferred amount" do + let(:item_total) { 49 } + + it "is not eligible" do + expect(rule).not_to be_eligible(order) + end + + it "set an error message" do + rule.eligible?(order) + expect(rule.eligibility_errors.full_messages.first). + to eq "This coupon code can't be applied to orders less than or equal to $50.00." + end + it "sets an error code" do + rule.eligible?(order) + expect(rule.eligibility_errors.details[:base].first[:error_code]). + to eq :item_total_less_than_or_equal + end + end + end + + context "preferred operator set to gte" do + let(:preferred_operator) { 'gte' } + + context "total is greater than preferred amount" do + let(:item_total) { 51 } + + it "should be eligible when item total is greater than preferred amount" do + expect(rule).to be_eligible(order) + end + + context "when the order is a different currency" do + let(:order_currency) { "CAD" } + + it "is not eligible" do + expect(rule).not_to be_eligible(order) + end + end + end + + context "item total is equal to preferred amount" do + let(:item_total) { 50 } + + it "should be eligible" do + expect(rule).to be_eligible(order) + end + + context "when the order is a different currency" do + let(:order_currency) { "CAD" } + + it "is not eligible" do + expect(rule).not_to be_eligible(order) + end + end + end + + context "when item total is lower than preferred amount" do + let(:item_total) { 49 } + + it "is not eligible" do + expect(rule).not_to be_eligible(order) + end + + it "set an error message" do + rule.eligible?(order) + expect(rule.eligibility_errors.full_messages.first). + to eq "This coupon code can't be applied to orders less than $50.00." + end + it "sets an error code" do + rule.eligible?(order) + expect(rule.eligibility_errors.details[:base].first[:error_code]). + to eq :item_total_less_than + end + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/line_item_product_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/line_item_product_spec.rb new file mode 100644 index 00000000000..ee5d634c574 --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/line_item_product_spec.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe SolidusFriendlyPromotions::Rules::LineItemProduct, type: :model do + let(:rule) { described_class.new(rule_options) } + let(:rule_options) { {} } + + context "#eligible?(line_item)" do + let(:rule_line_item) { Spree::LineItem.new(product: rule_product) } + let(:other_line_item) { Spree::LineItem.new(product: other_product) } + + let(:rule_options) { super().merge(products: [rule_product]) } + let(:rule_product) { mock_model(Spree::Product) } + let(:other_product) { mock_model(Spree::Product) } + + it "should be eligible if there are no products" do + expect(rule).to be_eligible(rule_line_item) + end + + subject { rule.eligible?(line_item, {}) } + + context "for product in rule" do + let(:line_item) { rule_line_item } + it { is_expected.to be_truthy } + end + + context "for product not in rule" do + let(:line_item) { other_line_item } + it { is_expected.to be_falsey } + end + + context "if match policy is inverse" do + let(:rule_options) { super().merge(preferred_match_policy: "exclude") } + + context "for product in rule" do + let(:line_item) { rule_line_item } + it { is_expected.to be_falsey } + end + + context "for product not in rule" do + let(:line_item) { other_line_item } + it { is_expected.to be_truthy } + end + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/line_item_taxon_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/line_item_taxon_spec.rb new file mode 100644 index 00000000000..de67486218c --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/line_item_taxon_spec.rb @@ -0,0 +1,92 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SolidusFriendlyPromotions::Rules::LineItemTaxon, type: :model do + let(:taxon) { create :taxon, name: 'first' } + let(:taxon2) { create :taxon, name: 'second' } + let(:order) { create :order_with_line_items } + let(:product) { order.products.first } + + let(:rule) do + described_class.create!(promotion: create(:promotion)) + end + + describe '#eligible?' do + let(:line_item) { order.line_items.first! } + let(:order) { create :order_with_line_items } + let(:taxon) { create :taxon, name: 'first' } + + context 'with an invalid match policy' do + before do + rule.preferred_match_policy = 'invalid' + rule.save!(validate: false) + line_item.product.taxons << taxon + rule.taxons << taxon + end + + it 'raises' do + expect { + rule.eligible?(line_item) + }.to raise_error('unexpected match policy: "invalid"') + end + end + + context 'when a product has a taxon of a taxon rule' do + before do + product.taxons << taxon + rule.taxons << taxon + rule.save! + end + + it 'is eligible' do + expect(rule).to be_eligible(line_item) + end + end + + context 'when a product has a taxon child of a taxon rule' do + before do + taxon.children << taxon2 + product.taxons << taxon2 + rule.taxons << taxon + rule.save! + end + + it 'is eligible' do + expect(rule).to be_eligible(line_item) + end + + context "with 'exclude' match policy" do + before do + rule.update(preferred_match_policy: :exclude) + end + + it "is not eligible" do + expect(rule).not_to be_eligible(line_item) + end + end + end + + context 'when a product does not have taxon or child taxon of a taxon rule' do + before do + product.taxons << taxon2 + rule.taxons << taxon + rule.save! + end + + it 'is not eligible' do + expect(rule).not_to be_eligible(line_item) + end + + context "with 'exclude' match policy" do + before do + rule.update(preferred_match_policy: :exclude) + end + + it "is not eligible" do + expect(rule).to be_eligible(line_item) + end + end + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/nth_order_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/nth_order_spec.rb new file mode 100644 index 00000000000..483a685038d --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/nth_order_spec.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SolidusFriendlyPromotions::Rules::NthOrder do + describe "#applicable?" do + subject { described_class.new.applicable?(promotable) } + + context "when the promotable is an order" do + let(:promotable) { Spree::Order.new } + + it { is_expected.to be true } + end + + context "when the promotable is not a order" do + let(:promotable) { "not an order" } + + it { is_expected.to be false } + end + end + + describe "eligible?" do + let(:instance) { described_class.new } + subject { instance.eligible?(order) } + + before do + instance.preferred_nth_order = 2 + end + + context "when the order does not have a user" do + let(:order) { Spree::Order.new } + + it { is_expected.to be false } + end + + context "when the order has a user" do + let(:order) { create :order } + let(:user) { order.user } + + context "when the user has completed orders" do + before do + old_order = create :completed_order_with_totals, user: user + old_order.update(completed_at: 1.day.ago) + end + + context "when this order will be the 'nth' order" do + it { is_expected.to be true } + end + + context "when this order is completed and is still the 'nth' order" do + before do + order.update(completed_at: Time.current) + end + + it { is_expected.to be true } + end + + context "when this order will not be the 'nth' order" do + before do + instance.preferred_nth_order = 100 + end + + it { is_expected.to be false } + end + end + + context "when the user has no completed orders " do + it { is_expected.to be false } + end + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/one_use_per_user_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/one_use_per_user_spec.rb new file mode 100644 index 00000000000..35ef09ce1a0 --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/one_use_per_user_spec.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SolidusFriendlyPromotions::Rules::OneUsePerUser, type: :model do + let(:rule) { described_class.new } + + describe '#eligible?(order)' do + subject { rule.eligible?(order) } + let(:order) { double Spree::Order, user: user } + let(:user) { double Spree::LegacyUser } + let(:promotion) { stub_model Spree::Promotion, used_by?: used_by } + let(:used_by) { false } + + before { rule.promotion = promotion } + + context 'when the order is assigned to a user' do + context 'when the user has used this promotion before' do + let(:used_by) { true } + + it { is_expected.to be false } + it "sets an error message" do + subject + expect(rule.eligibility_errors.full_messages.first). + to eq "This coupon code can only be used once per user." + end + it "sets an error code" do + rule.eligible?(order) + expect(rule.eligibility_errors.details[:base].first[:error_code]). + to eq :limit_once_per_user + end + end + + context 'when the user has not used this promotion before' do + it { is_expected.to be true } + end + end + + context 'when the order is not assigned to a user' do + let(:user) { nil } + it { is_expected.to be false } + it "sets an error message" do + subject + expect(rule.eligibility_errors.full_messages.first). + to eq "You need to login before applying this coupon code." + end + it "sets an error code" do + rule.eligible?(order) + expect(rule.eligibility_errors.details[:base].first[:error_code]). + to eq :no_user_specified + end + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/option_value_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/option_value_spec.rb new file mode 100644 index 00000000000..fdf4e3441fb --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/option_value_spec.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SolidusFriendlyPromotions::Rules::OptionValue do + let(:rule) { SolidusFriendlyPromotions::Rules::OptionValue.new } + + describe "#preferred_eligible_values" do + subject { rule.preferred_eligible_values } + it "assigns a nicely formatted hash" do + rule.preferred_eligible_values = Hash["5" => "1,2", "6" => "1"] + expect(subject).to eq Hash[5 => [1, 2], 6 => [1]] + end + end + + describe "#applicable?" do + subject { rule.applicable?(promotable) } + context "when promotable is an order" do + let(:promotable) { Spree::Order.new } + it { is_expected.to be true } + end + context "when promotable is not an order" do + let(:promotable) { Spree::LineItem.new } + it { is_expected.to be false } + end + end + + describe "#eligible?" do + let(:variant) { create :variant } + let(:line_item) { create :line_item, variant: variant } + let(:promotable) { line_item.order } + subject { rule.eligible?(promotable) } + context "when there are any applicable line items" do + before do + rule.preferred_eligible_values = Hash[line_item.product.id => [ + line_item.variant.option_values.pluck(:id).first + ]] + end + it { is_expected.to be true } + end + context "when there are no applicable line items" do + before do + rule.preferred_eligible_values = Hash[99 => [99]] + end + it { is_expected.to be false } + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/product_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/product_spec.rb new file mode 100644 index 00000000000..2a2e30b53c6 --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/product_spec.rb @@ -0,0 +1,127 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SolidusFriendlyPromotions::Rules::Product, type: :model do + let(:rule) { SolidusFriendlyPromotions::Rules::Product.new(rule_options) } + let(:rule_options) { {} } + + context "#eligible?(order)" do + let(:order) { Spree::Order.new } + + it "should be eligible if there are no products" do + allow(rule).to receive_messages(eligible_products: []) + expect(rule).to be_eligible(order) + end + + before do + 3.times { |i| instance_variable_set("@product#{i}", mock_model(Spree::Product)) } + end + + context "with 'any' match policy" do + let(:rule_options) { super().merge(preferred_match_policy: "any") } + + it "should be eligible if any of the products is in eligible products" do + allow(rule).to receive_messages(order_products: [@product1, @product2]) + allow(rule).to receive_messages(eligible_products: [@product2, @product3]) + expect(rule).to be_eligible(order) + end + + context "when none of the products are eligible products" do + before do + allow(rule).to receive_messages(order_products: [@product1]) + allow(rule).to receive_messages(eligible_products: [@product2, @product3]) + end + it { expect(rule).not_to be_eligible(order) } + it "sets an error message" do + rule.eligible?(order) + expect(rule.eligibility_errors.full_messages.first). + to eq "You need to add an applicable product before applying this coupon code." + end + it "sets an error code" do + rule.eligible?(order) + expect(rule.eligibility_errors.details[:base].first[:error_code]). + to eq :no_applicable_products + end + end + end + + context "with 'all' match policy" do + let(:rule_options) { super().merge(preferred_match_policy: "all") } + + it "should be eligible if all of the eligible products are ordered" do + allow(rule).to receive_messages(order_products: [@product3, @product2, @product1]) + allow(rule).to receive_messages(eligible_products: [@product2, @product3]) + expect(rule).to be_eligible(order) + end + + context "when any of the eligible products is not ordered" do + before do + allow(rule).to receive_messages(order_products: [@product1, @product2]) + allow(rule).to receive_messages(eligible_products: [@product1, @product2, @product3]) + end + it { expect(rule).not_to be_eligible(order) } + it "sets an error message" do + rule.eligible?(order) + expect(rule.eligibility_errors.full_messages.first). + to eq "This coupon code can't be applied because you don't have all of the necessary products in your cart." + end + it "sets an error code" do + rule.eligible?(order) + expect(rule.eligibility_errors.details[:base].first[:error_code]). + to eq :missing_product + end + end + end + + context "with 'none' match policy" do + let(:rule_options) { super().merge(preferred_match_policy: "none") } + + it "should be eligible if none of the order's products are in eligible products" do + allow(rule).to receive_messages(order_products: [@product1]) + allow(rule).to receive_messages(eligible_products: [@product2, @product3]) + expect(rule).to be_eligible(order) + end + + context "when any of the order's products are in eligible products" do + before do + allow(rule).to receive_messages(order_products: [@product1, @product2]) + allow(rule).to receive_messages(eligible_products: [@product2, @product3]) + end + it { expect(rule).not_to be_eligible(order) } + it "sets an error message" do + rule.eligible?(order) + expect(rule.eligibility_errors.full_messages.first). + to eq "Your cart contains a product that prevents this coupon code from being applied." + end + it "sets an error code" do + rule.eligible?(order) + expect(rule.eligibility_errors.details[:base].first[:error_code]). + to eq :has_excluded_product + end + end + end + + context "with an invalid match policy" do + let(:rule) do + SolidusFriendlyPromotions::Rules::Product.create!( + promotion: create(:promotion), + product_promotion_rules: [ + Spree::ProductPromotionRule.new(product: product), + ], + ).tap do |rule| + rule.preferred_match_policy = 'invalid' + rule.save!(validate: false) + end + end + let(:product) { order.line_items.first!.product } + let(:order) { create(:order_with_line_items, line_items_count: 1) } + + it 'raises' do + expect { + rule.eligible?(order) + }.to raise_error('unexpected match policy: "invalid"') + end + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/store_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/store_spec.rb new file mode 100644 index 00000000000..f7e3b558bc9 --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/store_spec.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe SolidusFriendlyPromotions::Rules::Store, type: :model do + let(:rule) { described_class.new } + + context "#eligible?(order)" do + let(:order) { Spree::Order.new } + + it "is eligible if no stores are provided" do + expect(rule).to be_eligible(order) + end + + it "is eligible if stores include the order's store" do + default_store = Spree::Store.new(name: "Default") + other_store = Spree::Store.new(name: "Other") + + rule.stores = [default_store, other_store] + order.store = default_store + + expect(rule).to be_eligible(order) + end + + it "is not eligible if order is placed in a different store" do + default_store = Spree::Store.new(name: "Default") + other_store = Spree::Store.new(name: "Other") + + rule.stores = [other_store] + order.store = default_store + + expect(rule).not_to be_eligible(order) + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/taxon_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/taxon_spec.rb new file mode 100644 index 00000000000..a75e8728b64 --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/taxon_spec.rb @@ -0,0 +1,144 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SolidusFriendlyPromotions::Rules::Taxon, type: :model do + let(:taxon) { create :taxon, name: 'first' } + let(:taxon2) { create :taxon, name: 'second' } + let(:order) { create :order_with_line_items } + let(:product) { order.products.first } + + let(:rule) do + SolidusFriendlyPromotions::Rules::Taxon.create!(promotion: create(:promotion)) + end + + context '#eligible?(order)' do + context 'with any match policy' do + before do + rule.update!(preferred_match_policy: 'any') + end + + it 'is eligible if order does have any prefered taxon' do + product.taxons << taxon + rule.taxons << taxon + expect(rule).to be_eligible(order) + end + + context "when order does not have any prefered taxon" do + before { rule.taxons << taxon2 } + it { expect(rule).not_to be_eligible(order) } + it "sets an error message" do + rule.eligible?(order) + expect(rule.eligibility_errors.full_messages.first). + to eq "You need to add a product from an applicable category before applying this coupon code." + end + it "sets an error code" do + rule.eligible?(order) + expect(rule.eligibility_errors.details[:base].first[:error_code]). + to eq :no_matching_taxons + end + end + + context 'when a product has a taxon child of a taxon rule' do + before do + taxon.children << taxon2 + product.taxons << taxon2 + rule.taxons << taxon + end + + it{ expect(rule).to be_eligible(order) } + end + end + + context 'with all match policy' do + before do + rule.update!(preferred_match_policy: 'all') + end + + it 'is eligible order has all prefered taxons' do + product.taxons << taxon2 + order.products.last.taxons << taxon + + rule.taxons = [taxon, taxon2] + + expect(rule).to be_eligible(order) + end + + context "when order does not have all prefered taxons" do + before { rule.taxons << taxon } + it { expect(rule).not_to be_eligible(order) } + it "sets an error message" do + rule.eligible?(order) + expect(rule.eligibility_errors.full_messages.first). + to eq "You need to add a product from all applicable categories before applying this coupon code." + end + it "sets an error code" do + rule.eligible?(order) + expect(rule.eligibility_errors.details[:base].first[:error_code]). + to eq :missing_taxon + end + end + + context 'when a product has a taxon child of a taxon rule' do + let(:taxon3) { create :taxon } + + before do + taxon.children << taxon2 + taxon.save! + taxon.reload + + product.taxons = [taxon2, taxon3] + rule.taxons = [taxon, taxon3] + end + + it { expect(rule).to be_eligible(order) } + end + end + + context 'with none match policy' do + before do + rule.preferred_match_policy = 'none' + end + + context "none of the order's products are in listed taxon" do + before { rule.taxons << taxon2 } + it { expect(rule).to be_eligible(order) } + end + + context "one of the order's products is in a listed taxon" do + before do + order.products.first.taxons << taxon + rule.taxons << taxon + end + it "should not be eligible" do + expect(rule).not_to be_eligible(order) + end + it "sets an error message" do + rule.eligible?(order) + expect(rule.eligibility_errors.full_messages.first). + to eq "Your cart contains a product from an excluded category that prevents this coupon code from being applied." + end + it "sets an error code" do + rule.eligible?(order) + expect(rule.eligibility_errors.details[:base].first[:error_code]). + to eq :has_excluded_taxon + end + end + end + + context 'with an invalid match policy' do + before do + order.products.first.taxons << taxon + rule.taxons << taxon + rule.preferred_match_policy = 'invalid' + rule.save!(validate: false) + end + + it 'raises' do + expect { + rule.eligible?(order) + }.to raise_error('unexpected match policy: "invalid"') + end + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_logged_in_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_logged_in_spec.rb new file mode 100644 index 00000000000..7c6191ef504 --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_logged_in_spec.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SolidusFriendlyPromotions::Rules::UserLoggedIn, type: :model do + let(:rule) { SolidusFriendlyPromotions::Rules::UserLoggedIn.new } + + context "#eligible?(order)" do + let(:order) { Spree::Order.new } + + it "should be eligible if order has an associated user" do + user = double('User') + allow(order).to receive_messages(user: user) + + expect(rule).to be_eligible(order) + end + + context "when user is not logged in" do + before { allow(order).to receive_messages(user: nil) } # better to be explicit here + it { expect(rule).not_to be_eligible(order) } + it "sets an error message" do + rule.eligible?(order) + expect(rule.eligibility_errors.full_messages.first). + to eq "You need to login before applying this coupon code." + end + it "sets an error code" do + rule.eligible?(order) + expect(rule.eligibility_errors.details[:base].first[:error_code]). + to eq :no_user_specified + end + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_role_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_role_spec.rb new file mode 100644 index 00000000000..beae7821091 --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_role_spec.rb @@ -0,0 +1,88 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SolidusFriendlyPromotions::Rules::UserRole, type: :model do + let(:rule) { described_class.new(preferred_role_ids: roles_for_rule.map(&:id)) } + let(:user) { create(:user, spree_roles: roles_for_user) } + let(:roles_for_rule) { [] } + let(:roles_for_user) { [] } + + subject { rule } + + shared_examples 'eligibility' do + context 'no roles on rule' do + let(:roles_for_user) { [create(:role)] } + it 'should not be eligible' do + expect(rule).to_not be_eligible(order) + end + end + + context 'no roles on user' do + let(:roles_for_rule) { [create(:role)] } + it 'should not be eligible' do + expect(rule).to_not be_eligible(order) + end + end + + context 'mismatched roles' do + let(:roles_for_user) { [create(:role)] } + let(:roles_for_rule) { [create(:role)] } + it 'should not be eligible' do + expect(rule).to_not be_eligible(order) + end + end + + context 'matching all roles' do + let(:roles_for_user) { [create(:role), create(:role)] } + let(:roles_for_rule) { roles_for_user } + it 'should be eligible' do + expect(rule).to be_eligible(order) + end + end + end + + context '#eligible?(order)' do + context 'order with no user' do + let(:order) { Spree::Order.new } + + it 'should not be eligible' do + expect(rule).to_not be_eligible(order) + end + end + + context 'order with user' do + let(:order) { Spree::Order.new(user: user) } + + context 'with any match policy' do + before { rule.preferred_match_policy = 'any' } + + include_examples 'eligibility' + + context 'one shared role' do + let(:shared_role) { create(:role) } + let(:roles_for_user) { [create(:role), shared_role] } + let(:roles_for_rule) { [create(:role), shared_role] } + it 'should be eligible' do + expect(rule).to be_eligible(order) + end + end + end + + context 'with all match policy' do + before { rule.preferred_match_policy = 'all' } + + include_examples 'eligibility' + + context 'one shared role' do + let(:shared_role) { create(:role) } + let(:roles_for_user) { [create(:role), shared_role] } + let(:roles_for_rule) { [create(:role), shared_role] } + it 'should not be eligible' do + expect(rule).to_not be_eligible(order) + end + end + end + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_spec.rb new file mode 100644 index 00000000000..c1df8349180 --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_spec.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SolidusFriendlyPromotions::Rules::User, type: :model do + let(:rule) { SolidusFriendlyPromotions::Rules::User.new } + + context "#eligible?(order)" do + let(:order) { Spree::Order.new } + + it "should not be eligible if users are not provided" do + expect(rule).not_to be_eligible(order) + end + + it "should be eligible if users include user placing the order" do + user = mock_model(Spree::LegacyUser) + users = [user, mock_model(Spree::LegacyUser)] + allow(rule).to receive_messages(users: users) + allow(order).to receive_messages(user: user) + + expect(rule).to be_eligible(order) + end + + it "should not be eligible if user placing the order is not listed" do + allow(order).to receive_messages(user: mock_model(Spree::LegacyUser)) + users = [mock_model(Spree::LegacyUser), mock_model(Spree::LegacyUser)] + allow(rule).to receive_messages(users: users) + + expect(rule).not_to be_eligible(order) + end + + # Regression test for https://github.com/spree/spree/issues/3885 + it "can assign to user_ids" do + user1 = Spree::LegacyUser.create!(email: "test1@example.com") + user2 = Spree::LegacyUser.create!(email: "test2@example.com") + rule.user_ids = "#{user1.id}, #{user2.id}" + end + end +end From 4f172c192dac6fe0bd3faedc27bddb3293870d1a Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 31 May 2023 12:56:01 +0200 Subject: [PATCH 185/834] Standard Ruby --- friendly_promotions/Gemfile | 31 +++-- friendly_promotions/Rakefile | 4 +- .../rules/first_repeat_purchase_since.rb | 2 +- .../rules/item_total.rb | 18 +-- .../rules/line_item_option_value.rb | 4 +- .../rules/line_item_product.rb | 66 +++++------ .../rules/line_item_taxon.rb | 74 ++++++------ .../rules/nth_order.rb | 65 ++++++----- .../rules/one_use_per_user.rb | 28 ++--- .../rules/option_value.rb | 36 +++--- .../rules/product.rb | 106 +++++++++--------- .../rules/store.rb | 30 ++--- .../rules/taxon.rb | 106 +++++++++--------- .../solidus_friendly_promotions/rules/user.rb | 42 +++---- .../rules/user_logged_in.rb | 20 ++-- .../rules/user_role.rb | 56 ++++----- .../install/install_generator.rb | 20 ++-- .../lib/solidus_friendly_promotions.rb | 6 +- .../configuration.rb | 2 +- .../lib/solidus_friendly_promotions/engine.rb | 6 +- .../solidus_friendly_promotions/version.rb | 2 +- .../solidus_friendly_promotions.gemspec | 33 +++--- .../rules/first_order_spec.rb | 26 ++--- .../rules/first_repeat_purchase_since_spec.rb | 2 +- .../rules/item_total_spec.rb | 32 +++--- .../rules/line_item_taxon_spec.rb | 30 ++--- .../rules/nth_order_spec.rb | 2 +- .../rules/one_use_per_user_spec.rb | 28 ++--- .../rules/option_value_spec.rb | 12 +- .../rules/product_spec.rb | 34 +++--- .../rules/taxon_spec.rb | 62 +++++----- .../rules/user_logged_in_spec.rb | 12 +- .../rules/user_role_spec.rb | 48 ++++---- .../rules/user_spec.rb | 2 +- friendly_promotions/spec/spec_helper.rb | 12 +- 35 files changed, 528 insertions(+), 531 deletions(-) diff --git a/friendly_promotions/Gemfile b/friendly_promotions/Gemfile index 0a229872832..5b90455db84 100644 --- a/friendly_promotions/Gemfile +++ b/friendly_promotions/Gemfile @@ -1,37 +1,36 @@ # frozen_string_literal: true -source 'https://rubygems.org' +source "https://rubygems.org" git_source(:github) { |repo| "https://github.com/#{repo}.git" } -branch = ENV.fetch('SOLIDUS_BRANCH', 'main') -gem 'solidus', github: 'solidusio/solidus', branch: branch +branch = ENV.fetch("SOLIDUS_BRANCH", "main") +gem "solidus", github: "solidusio/solidus", branch: branch # The solidus_frontend gem has been pulled out since v3.2 -gem 'solidus_frontend', github: 'solidusio/solidus_frontend' if branch == 'master' -gem 'solidus_frontend' if branch >= 'v3.2' # rubocop:disable Bundler/DuplicatedGem +gem "solidus_frontend", github: "solidusio/solidus_frontend" if branch == "master" +gem "solidus_frontend" if branch >= "v3.2" # rubocop:disable Bundler/DuplicatedGem # Needed to help Bundler figure out how to resolve dependencies, # otherwise it takes forever to resolve them. # See https://github.com/bundler/bundler/issues/6677 -gem 'rails', '>0.a' - +gem "rails", ">0.a" # Provides basic authentication functionality for testing parts of your engine -gem 'solidus_auth_devise' +gem "solidus_auth_devise" -case ENV.fetch('DB', nil) -when 'mysql' - gem 'mysql2' -when 'postgresql' - gem 'pg' +case ENV.fetch("DB", nil) +when "mysql" + gem "mysql2" +when "postgresql" + gem "pg" else - gem 'sqlite3' + gem "sqlite3" end # While we still support Ruby < 3 we need to workaround a limitation in # the 'async' gem that relies on the latest ruby, since RubyGems doesn't # resolve gems based on the required ruby version. -gem 'async', '< 3' if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('3') +gem "async", "< 3" if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("3") gemspec @@ -40,4 +39,4 @@ gemspec # # We use `send` instead of calling `eval_gemfile` to work around an issue with # how Dependabot parses projects: https://github.com/dependabot/dependabot-core/issues/1658. -send(:eval_gemfile, 'Gemfile-local') if File.exist? 'Gemfile-local' +send(:eval_gemfile, "Gemfile-local") if File.exist? "Gemfile-local" diff --git a/friendly_promotions/Rakefile b/friendly_promotions/Rakefile index c08aa46816c..50635fc88d1 100644 --- a/friendly_promotions/Rakefile +++ b/friendly_promotions/Rakefile @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'solidus_dev_support/rake_tasks' +require "solidus_dev_support/rake_tasks" SolidusDevSupport::RakeTasks.install -task default: 'extension:specs' +task default: "extension:specs" diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb index eab1e40fa42..409b2f1ea99 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb @@ -4,7 +4,7 @@ module SolidusFriendlyPromotions module Rules class FirstRepeatPurchaseSince < ::Spree::PromotionRule preference :days_ago, :integer, default: 365 - validates :preferred_days_ago, numericality: { only_integer: true, greater_than: 0 } + validates :preferred_days_ago, numericality: {only_integer: true, greater_than: 0} # This promotion is applicable to orders only. def applicable?(promotable) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb index 153134ee604..b80c3d45357 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb @@ -11,20 +11,20 @@ class ItemTotal < ::Spree::PromotionRule include ActiveSupport::Deprecation::DeprecatedConstantAccessor preference :amount, :decimal, default: 100.00 - preference :currency, :string, default: ->{ Spree::Config[:currency] } - preference :operator, :string, default: 'gt' + preference :currency, :string, default: -> { Spree::Config[:currency] } + preference :operator, :string, default: "gt" # The list of allowed operators names mapped to their symbols. def self.operators_map { gte: :>=, - gt: :>, + gt: :> } end def self.operator_options operators_map.map do |name, _method| - [I18n.t(name, scope: 'spree.item_total_rule.operators'), name] + [I18n.t(name, scope: "spree.item_total_rule.operators"), name] end end @@ -34,7 +34,7 @@ def self.operator_options :OPERATORS, :operators_map, message: "OPERATORS is deprecated! Use `operators_map.keys.map(&:to_s)` instead.", - deprecator: Spree::Deprecation, + deprecator: Spree::Deprecation ) def applicable?(promotable) @@ -56,7 +56,7 @@ def eligible?(order, _options = {}) def operator self.class.operators_map.fetch( preferred_operator.to_sym, - preferred_operator_default, + preferred_operator_default ) end @@ -74,9 +74,9 @@ def formatted_amount def ineligible_message case preferred_operator.to_s - when 'gte' + when "gte" eligibility_error_message(:item_total_less_than, amount: formatted_amount) - when 'gt' + when "gt" eligibility_error_message(:item_total_less_than_or_equal, amount: formatted_amount) else eligibility_error_message(:item_total_doesnt_match_with_operator, amount: formatted_amount, operator: preferred_operator) @@ -84,7 +84,7 @@ def ineligible_message end def ineligible_error_code - if preferred_operator == 'gte' + if preferred_operator == "gte" :item_total_less_than else :item_total_less_than_or_equal diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_option_value.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_option_value.rb index ceff257c286..27a2eb46ba3 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_option_value.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_option_value.rb @@ -18,11 +18,11 @@ def eligible?(line_item, _options = {}) def preferred_eligible_values values = preferences[:eligible_values] || {} - Hash[values.keys.map(&:to_i).zip( + values.keys.map(&:to_i).zip( values.values.map do |value| (value.is_a?(Array) ? value : value.split(",")).map(&:to_i) end - )] + ).to_h end private diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb index d930e6f5681..ad969ce628c 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb @@ -2,45 +2,45 @@ module SolidusFriendlyPromotions module Rules - # A rule to apply a promotion only to line items with or without a chosen product - class LineItemProduct < Spree::PromotionRule - MATCH_POLICIES = %w(include exclude) - - has_many :product_promotion_rules, - dependent: :destroy, - foreign_key: :promotion_rule_id, - class_name: "Spree::ProductPromotionRule" - has_many :products, - class_name: "Spree::Product", - through: :product_promotion_rules - - preference :match_policy, :string, default: MATCH_POLICIES.first - - def applicable?(promotable) - promotable.is_a?(Spree::LineItem) - end + # A rule to apply a promotion only to line items with or without a chosen product + class LineItemProduct < Spree::PromotionRule + MATCH_POLICIES = %w[include exclude] + + has_many :product_promotion_rules, + dependent: :destroy, + foreign_key: :promotion_rule_id, + class_name: "Spree::ProductPromotionRule" + has_many :products, + class_name: "Spree::Product", + through: :product_promotion_rules + + preference :match_policy, :string, default: MATCH_POLICIES.first + + def applicable?(promotable) + promotable.is_a?(Spree::LineItem) + end - def eligible?(line_item, _options = {}) - if inverse? - !product_ids.include?(line_item.variant.product_id) - else - product_ids.include?(line_item.variant.product_id) - end + def eligible?(line_item, _options = {}) + if inverse? + !product_ids.include?(line_item.variant.product_id) + else + product_ids.include?(line_item.variant.product_id) end + end - def product_ids_string - product_ids.join(",") - end + def product_ids_string + product_ids.join(",") + end - def product_ids_string=(product_ids) - self.product_ids = product_ids.to_s.split(",").map(&:strip) - end + def product_ids_string=(product_ids) + self.product_ids = product_ids.to_s.split(",").map(&:strip) + end - private + private - def inverse? - preferred_match_policy == "exclude" - end + def inverse? + preferred_match_policy == "exclude" end end end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb index ae6683c6f4b..45caa7fc53f 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb @@ -1,52 +1,52 @@ # frozen_string_literal: true module SolidusFriendlyPromotions - module Rules - class LineItemTaxon < ::Spree::PromotionRule - has_many :promotion_rule_taxons, class_name: 'Spree::PromotionRuleTaxon', foreign_key: :promotion_rule_id, - dependent: :destroy - has_many :taxons, through: :promotion_rule_taxons, class_name: 'Spree::Taxon' + module Rules + class LineItemTaxon < ::Spree::PromotionRule + has_many :promotion_rule_taxons, class_name: "Spree::PromotionRuleTaxon", foreign_key: :promotion_rule_id, + dependent: :destroy + has_many :taxons, through: :promotion_rule_taxons, class_name: "Spree::Taxon" - MATCH_POLICIES = %w(include exclude) + MATCH_POLICIES = %w[include exclude] - validates_inclusion_of :preferred_match_policy, in: MATCH_POLICIES + validates_inclusion_of :preferred_match_policy, in: MATCH_POLICIES - preference :match_policy, :string, default: MATCH_POLICIES.first - def applicable?(promotable) - promotable.is_a?(Spree::LineItem) - end + preference :match_policy, :string, default: MATCH_POLICIES.first + def applicable?(promotable) + promotable.is_a?(Spree::LineItem) + end - def eligible?(line_item, _options = {}) - found = Spree::Classification.where( - product_id: line_item.variant.product_id, - taxon_id: rule_taxon_ids_with_children - ).exists? - - case preferred_match_policy - when 'include' - found - when 'exclude' - !found - else - raise "unexpected match policy: #{preferred_match_policy.inspect}" - end + def eligible?(line_item, _options = {}) + found = Spree::Classification.where( + product_id: line_item.variant.product_id, + taxon_id: rule_taxon_ids_with_children + ).exists? + + case preferred_match_policy + when "include" + found + when "exclude" + !found + else + raise "unexpected match policy: #{preferred_match_policy.inspect}" end + end - def taxon_ids_string - taxons.pluck(:id).join(',') - end + def taxon_ids_string + taxons.pluck(:id).join(",") + end - def taxon_ids_string=(taxon_ids) - taxon_ids = taxon_ids.to_s.split(',').map(&:strip) - self.taxons = Spree::Taxon.find(taxon_ids) - end + def taxon_ids_string=(taxon_ids) + taxon_ids = taxon_ids.to_s.split(",").map(&:strip) + self.taxons = Spree::Taxon.find(taxon_ids) + end - private + private - # ids of taxons rules and taxons rules children - def rule_taxon_ids_with_children - taxons.flat_map { |taxon| taxon.self_and_descendants.ids }.uniq - end + # ids of taxons rules and taxons rules children + def rule_taxon_ids_with_children + taxons.flat_map { |taxon| taxon.self_and_descendants.ids }.uniq end end + end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb index 3c8d94ca5cf..a8ceb652431 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb @@ -1,44 +1,43 @@ # frozen_string_literal: true module SolidusFriendlyPromotions + module Rules + class NthOrder < ::Spree::PromotionRule + preference :nth_order, :integer, default: 2 + # It does not make sense to have this apply to the first order using preferred_nth_order == 1 + # Instead we could use the first_order rule + validates :preferred_nth_order, numericality: {only_integer: true, greater_than: 1} + + # This promotion is applicable to orders only. + def applicable?(promotable) + promotable.is_a?(Spree::Order) + end - module Rules - class NthOrder < ::Spree::PromotionRule - preference :nth_order, :integer, default: 2 - # It does not make sense to have this apply to the first order using preferred_nth_order == 1 - # Instead we could use the first_order rule - validates :preferred_nth_order, numericality: { only_integer: true, greater_than: 1 } - - # This promotion is applicable to orders only. - def applicable?(promotable) - promotable.is_a?(Spree::Order) - end - - # This is never eligible if the order does not have a user, and that user does not have any previous completed orders. - # - # Use the first order rule if you want a promotion to be applied to the first order for a user. - # @param order [Spree::Order] - def eligible?(order, _options = {}) - return false unless order.user + # This is never eligible if the order does not have a user, and that user does not have any previous completed orders. + # + # Use the first order rule if you want a promotion to be applied to the first order for a user. + # @param order [Spree::Order] + def eligible?(order, _options = {}) + return false unless order.user - nth_order?(order) - end + nth_order?(order) + end - private + private - def completed_order_count(order) - order. - user. - orders. - complete. - where(Spree::Order.arel_table[:completed_at].lt(order.completed_at || Time.current)). - count - end + def completed_order_count(order) + order + .user + .orders + .complete + .where(Spree::Order.arel_table[:completed_at].lt(order.completed_at || Time.current)) + .count + end - def nth_order?(order) - count = completed_order_count(order) + 1 - count == preferred_nth_order - end + def nth_order?(order) + count = completed_order_count(order) + 1 + count == preferred_nth_order end + end end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb index 2b174a8c9c7..8cbc905d02a 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb @@ -1,23 +1,23 @@ # frozen_string_literal: true module SolidusFriendlyPromotions - module Rules - class OneUsePerUser < ::Spree::PromotionRule - def applicable?(promotable) - promotable.is_a?(Spree::Order) - end + module Rules + class OneUsePerUser < ::Spree::PromotionRule + def applicable?(promotable) + promotable.is_a?(Spree::Order) + end - def eligible?(order, _options = {}) - if order.user.present? - if promotion.used_by?(order.user, [order]) - eligibility_errors.add(:base, eligibility_error_message(:limit_once_per_user), error_code: :limit_once_per_user) - end - else - eligibility_errors.add(:base, eligibility_error_message(:no_user_specified), error_code: :no_user_specified) + def eligible?(order, _options = {}) + if order.user.present? + if promotion.used_by?(order.user, [order]) + eligibility_errors.add(:base, eligibility_error_message(:limit_once_per_user), error_code: :limit_once_per_user) end - - eligibility_errors.empty? + else + eligibility_errors.add(:base, eligibility_error_message(:no_user_specified), error_code: :no_user_specified) end + + eligibility_errors.empty? end end + end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/option_value.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/option_value.rb index 26e1cffba9a..84f79875b90 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/option_value.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/option_value.rb @@ -1,28 +1,28 @@ # frozen_string_literal: true module SolidusFriendlyPromotions - module Rules - class OptionValue < ::Spree::PromotionRule - preference :eligible_values, :hash + module Rules + class OptionValue < ::Spree::PromotionRule + preference :eligible_values, :hash - def applicable?(promotable) - promotable.is_a?(Spree::Order) - end + def applicable?(promotable) + promotable.is_a?(Spree::Order) + end - def eligible?(order, _options = {}) - order.line_items.any? do |item| - LineItemOptionValue.new(preferred_eligible_values: preferred_eligible_values).eligible?(item) - end + def eligible?(order, _options = {}) + order.line_items.any? do |item| + LineItemOptionValue.new(preferred_eligible_values: preferred_eligible_values).eligible?(item) end + end - def preferred_eligible_values - values = preferences[:eligible_values] || {} - Hash[values.keys.map(&:to_i).zip( - values.values.map do |value| - (value.is_a?(Array) ? value : value.split(",")).map(&:to_i) - end - )] - end + def preferred_eligible_values + values = preferences[:eligible_values] || {} + values.keys.map(&:to_i).zip( + values.values.map do |value| + (value.is_a?(Array) ? value : value.split(",")).map(&:to_i) + end + ).to_h end end + end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb index f6b0ce019da..e257adc952a 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb @@ -1,73 +1,73 @@ # frozen_string_literal: true module SolidusFriendlyPromotions - module Rules - # A rule to limit a promotion based on products in the order. Can - # require all or any of the products to be present. Valid products - # either come from assigned product group or are assingned directly to - # the rule. - class Product < ::Spree::PromotionRule - has_many :product_promotion_rules, dependent: :destroy, foreign_key: :promotion_rule_id, - class_name: 'Spree::ProductPromotionRule' - has_many :products, class_name: 'Spree::Product', through: :product_promotion_rules + module Rules + # A rule to limit a promotion based on products in the order. Can + # require all or any of the products to be present. Valid products + # either come from assigned product group or are assingned directly to + # the rule. + class Product < ::Spree::PromotionRule + has_many :product_promotion_rules, dependent: :destroy, foreign_key: :promotion_rule_id, + class_name: "Spree::ProductPromotionRule" + has_many :products, class_name: "Spree::Product", through: :product_promotion_rules - def preload_relations - [:products] - end + def preload_relations + [:products] + end - MATCH_POLICIES = %w(any all none) + MATCH_POLICIES = %w[any all none] - validates_inclusion_of :preferred_match_policy, in: MATCH_POLICIES + validates_inclusion_of :preferred_match_policy, in: MATCH_POLICIES - preference :match_policy, :string, default: MATCH_POLICIES.first + preference :match_policy, :string, default: MATCH_POLICIES.first - # scope/association that is used to test eligibility - def eligible_products - products - end + # scope/association that is used to test eligibility + def eligible_products + products + end - def applicable?(promotable) - promotable.is_a?(Spree::Order) - end + def applicable?(promotable) + promotable.is_a?(Spree::Order) + end - def eligible?(order, _options = {}) - return true if eligible_products.empty? + def eligible?(order, _options = {}) + return true if eligible_products.empty? - case preferred_match_policy - when "all" - unless eligible_products.all? { |product| order_products(order).include?(product) } - eligibility_errors.add(:base, eligibility_error_message(:missing_product), error_code: :missing_product) - end - when "any" - unless order_products(order).any? { |product| eligible_products.include?(product) } - eligibility_errors.add(:base, eligibility_error_message(:no_applicable_products), - error_code: :no_applicable_products) - end - when "none" - unless order_products(order).none? { |product| eligible_products.include?(product) } - eligibility_errors.add(:base, eligibility_error_message(:has_excluded_product), - error_code: :has_excluded_product) - end - else - raise "unexpected match policy: #{preferred_match_policy.inspect}" + case preferred_match_policy + when "all" + unless eligible_products.all? { |product| order_products(order).include?(product) } + eligibility_errors.add(:base, eligibility_error_message(:missing_product), error_code: :missing_product) end - - eligibility_errors.empty? + when "any" + unless order_products(order).any? { |product| eligible_products.include?(product) } + eligibility_errors.add(:base, eligibility_error_message(:no_applicable_products), + error_code: :no_applicable_products) + end + when "none" + unless order_products(order).none? { |product| eligible_products.include?(product) } + eligibility_errors.add(:base, eligibility_error_message(:has_excluded_product), + error_code: :has_excluded_product) + end + else + raise "unexpected match policy: #{preferred_match_policy.inspect}" end - def product_ids_string - product_ids.join(',') - end + eligibility_errors.empty? + end - def product_ids_string=(product_ids) - self.product_ids = product_ids.to_s.split(',').map(&:strip) - end + def product_ids_string + product_ids.join(",") + end + + def product_ids_string=(product_ids) + self.product_ids = product_ids.to_s.split(",").map(&:strip) + end - private + private - def order_products(order) - order.line_items.map(&:variant).map(&:product) - end + def order_products(order) + order.line_items.map(&:variant).map(&:product) end + end end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb index a0fe76bf694..680600542ff 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb @@ -1,24 +1,24 @@ # frozen_string_literal: true module SolidusFriendlyPromotions - module Rules - class Store < ::Spree::PromotionRule - has_many :promotion_rule_stores, class_name: "Spree::PromotionRuleStore", - foreign_key: :promotion_rule_id, - dependent: :destroy - has_many :stores, through: :promotion_rule_stores, class_name: "Spree::Store" + module Rules + class Store < ::Spree::PromotionRule + has_many :promotion_rule_stores, class_name: "Spree::PromotionRuleStore", + foreign_key: :promotion_rule_id, + dependent: :destroy + has_many :stores, through: :promotion_rule_stores, class_name: "Spree::Store" - def preload_relations - [:stores] - end + def preload_relations + [:stores] + end - def applicable?(promotable) - promotable.is_a?(Spree::Order) - end + def applicable?(promotable) + promotable.is_a?(Spree::Order) + end - def eligible?(order, _options = {}) - stores.none? || stores.include?(order.store) - end + def eligible?(order, _options = {}) + stores.none? || stores.include?(order.store) end end + end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb index 6ab8658ad58..6b67fea88cf 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb @@ -1,73 +1,73 @@ # frozen_string_literal: true module SolidusFriendlyPromotions - module Rules - class Taxon < ::Spree::PromotionRule - has_many :promotion_rule_taxons, class_name: 'Spree::PromotionRuleTaxon', foreign_key: :promotion_rule_id, - dependent: :destroy - has_many :taxons, through: :promotion_rule_taxons, class_name: 'Spree::Taxon' + module Rules + class Taxon < ::Spree::PromotionRule + has_many :promotion_rule_taxons, class_name: "Spree::PromotionRuleTaxon", foreign_key: :promotion_rule_id, + dependent: :destroy + has_many :taxons, through: :promotion_rule_taxons, class_name: "Spree::Taxon" - def preload_relations - [:taxons] - end + def preload_relations + [:taxons] + end - MATCH_POLICIES = %w(any all none) + MATCH_POLICIES = %w[any all none] - validates_inclusion_of :preferred_match_policy, in: MATCH_POLICIES + validates_inclusion_of :preferred_match_policy, in: MATCH_POLICIES - preference :match_policy, :string, default: MATCH_POLICIES.first - def applicable?(promotable) - promotable.is_a?(Spree::Order) - end - - def eligible?(order, _options = {}) - order_taxons = taxons_in_order(order) + preference :match_policy, :string, default: MATCH_POLICIES.first + def applicable?(promotable) + promotable.is_a?(Spree::Order) + end - case preferred_match_policy - when 'all' - matches_all = taxons.all? do |rule_taxon| - order_taxons.where(id: rule_taxon.self_and_descendants.ids).exists? - end + def eligible?(order, _options = {}) + order_taxons = taxons_in_order(order) - unless matches_all - eligibility_errors.add(:base, eligibility_error_message(:missing_taxon), error_code: :missing_taxon) - end - when 'any' - unless order_taxons.where(id: rule_taxon_ids_with_children).exists? - eligibility_errors.add(:base, eligibility_error_message(:no_matching_taxons), error_code: :no_matching_taxons) - end - when 'none' - if order_taxons.where(id: rule_taxon_ids_with_children).exists? - eligibility_errors.add(:base, eligibility_error_message(:has_excluded_taxon), error_code: :has_excluded_taxon) - end - else - raise "unexpected match policy: #{preferred_match_policy.inspect}" + case preferred_match_policy + when "all" + matches_all = taxons.all? do |rule_taxon| + order_taxons.where(id: rule_taxon.self_and_descendants.ids).exists? end - eligibility_errors.empty? + unless matches_all + eligibility_errors.add(:base, eligibility_error_message(:missing_taxon), error_code: :missing_taxon) + end + when "any" + unless order_taxons.where(id: rule_taxon_ids_with_children).exists? + eligibility_errors.add(:base, eligibility_error_message(:no_matching_taxons), error_code: :no_matching_taxons) + end + when "none" + if order_taxons.where(id: rule_taxon_ids_with_children).exists? + eligibility_errors.add(:base, eligibility_error_message(:has_excluded_taxon), error_code: :has_excluded_taxon) + end + else + raise "unexpected match policy: #{preferred_match_policy.inspect}" end - def taxon_ids_string - taxons.pluck(:id).join(',') - end + eligibility_errors.empty? + end - def taxon_ids_string=(taxon_ids) - taxon_ids = taxon_ids.to_s.split(',').map(&:strip) - self.taxons = Spree::Taxon.find(taxon_ids) - end + def taxon_ids_string + taxons.pluck(:id).join(",") + end + + def taxon_ids_string=(taxon_ids) + taxon_ids = taxon_ids.to_s.split(",").map(&:strip) + self.taxons = Spree::Taxon.find(taxon_ids) + end - private + private - # All taxons in an order - def taxons_in_order(order) - Spree::Taxon.joins(products: { variants_including_master: :line_items }) - .where(spree_line_items: { order_id: order.id }).distinct - end + # All taxons in an order + def taxons_in_order(order) + Spree::Taxon.joins(products: {variants_including_master: :line_items}) + .where(spree_line_items: {order_id: order.id}).distinct + end - # ids of taxons rules and taxons rules children - def rule_taxon_ids_with_children - taxons.flat_map { |taxon| taxon.self_and_descendants.ids }.uniq - end + # ids of taxons rules and taxons rules children + def rule_taxon_ids_with_children + taxons.flat_map { |taxon| taxon.self_and_descendants.ids }.uniq end end + end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb index 1a0f08ad861..7eee578248b 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb @@ -1,32 +1,32 @@ # frozen_string_literal: true module SolidusFriendlyPromotions - module Rules - class User < ::Spree::PromotionRule - has_many :promotion_rule_users, class_name: 'Spree::PromotionRuleUser', - foreign_key: :promotion_rule_id, - dependent: :destroy - has_many :users, through: :promotion_rule_users, class_name: Spree::UserClassHandle.new + module Rules + class User < ::Spree::PromotionRule + has_many :promotion_rule_users, class_name: "Spree::PromotionRuleUser", + foreign_key: :promotion_rule_id, + dependent: :destroy + has_many :users, through: :promotion_rule_users, class_name: Spree::UserClassHandle.new - def preload_relations - [:users] - end + def preload_relations + [:users] + end - def applicable?(promotable) - promotable.is_a?(Spree::Order) - end + def applicable?(promotable) + promotable.is_a?(Spree::Order) + end - def eligible?(order, _options = {}) - users.include?(order.user) - end + def eligible?(order, _options = {}) + users.include?(order.user) + end - def user_ids_string - user_ids.join(',') - end + def user_ids_string + user_ids.join(",") + end - def user_ids_string=(user_ids) - self.user_ids = user_ids.to_s.split(',').map(&:strip) - end + def user_ids_string=(user_ids) + self.user_ids = user_ids.to_s.split(",").map(&:strip) end end + end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb index 1f7f02afd3b..bec0ae0b101 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb @@ -1,18 +1,18 @@ # frozen_string_literal: true module SolidusFriendlyPromotions - module Rules - class UserLoggedIn < ::Spree::PromotionRule - def applicable?(promotable) - promotable.is_a?(Spree::Order) - end + module Rules + class UserLoggedIn < ::Spree::PromotionRule + def applicable?(promotable) + promotable.is_a?(Spree::Order) + end - def eligible?(order, _options = {}) - unless order.user.present? - eligibility_errors.add(:base, eligibility_error_message(:no_user_specified), error_code: :no_user_specified) - end - eligibility_errors.empty? + def eligible?(order, _options = {}) + unless order.user.present? + eligibility_errors.add(:base, eligibility_error_message(:no_user_specified), error_code: :no_user_specified) end + eligibility_errors.empty? end end + end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_role.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_role.rb index 94fe8a65adf..42c1bb07f57 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_role.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_role.rb @@ -1,43 +1,43 @@ # frozen_string_literal: true module SolidusFriendlyPromotions - module Rules - class UserRole < ::Spree::PromotionRule - preference :role_ids, :array, default: [] + module Rules + class UserRole < ::Spree::PromotionRule + preference :role_ids, :array, default: [] - MATCH_POLICIES = %w(any all) - preference :match_policy, default: MATCH_POLICIES.first + MATCH_POLICIES = %w[any all] + preference :match_policy, default: MATCH_POLICIES.first - def applicable?(promotable) - promotable.is_a?(Spree::Order) - end + def applicable?(promotable) + promotable.is_a?(Spree::Order) + end - def eligible?(order, _options = {}) - return false unless order.user - if all_match_policy? - match_all_roles?(order) - else - match_any_roles?(order) - end + def eligible?(order, _options = {}) + return false unless order.user + if all_match_policy? + match_all_roles?(order) + else + match_any_roles?(order) end + end - private + private - def all_match_policy? - preferred_match_policy == 'all' && preferred_role_ids.present? - end + def all_match_policy? + preferred_match_policy == "all" && preferred_role_ids.present? + end - def user_roles(order) - order.user.spree_roles.where(id: preferred_role_ids) - end + def user_roles(order) + order.user.spree_roles.where(id: preferred_role_ids) + end - def match_all_roles?(order) - user_roles(order).count == preferred_role_ids.count - end + def match_all_roles?(order) + user_roles(order).count == preferred_role_ids.count + end - def match_any_roles?(order) - user_roles(order).exists? - end + def match_any_roles?(order) + user_roles(order).exists? end end + end end diff --git a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/install_generator.rb b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/install_generator.rb index 31ac0a7c3d7..cf402d0bd68 100644 --- a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/install_generator.rb +++ b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/install_generator.rb @@ -4,36 +4,36 @@ module SolidusFriendlyPromotions module Generators class InstallGenerator < Rails::Generators::Base class_option :auto_run_migrations, type: :boolean, default: false - source_root File.expand_path('templates', __dir__) + source_root File.expand_path("templates", __dir__) def self.exit_on_failure? true end def copy_initializer - template 'initializer.rb', 'config/initializers/solidus_friendly_promotions.rb' + template "initializer.rb", "config/initializers/solidus_friendly_promotions.rb" end def add_javascripts - append_file 'vendor/assets/javascripts/spree/frontend/all.js', "//= require spree/frontend/solidus_friendly_promotions\n" - append_file 'vendor/assets/javascripts/spree/backend/all.js', "//= require spree/backend/solidus_friendly_promotions\n" + append_file "vendor/assets/javascripts/spree/frontend/all.js", "//= require spree/frontend/solidus_friendly_promotions\n" + append_file "vendor/assets/javascripts/spree/backend/all.js", "//= require spree/backend/solidus_friendly_promotions\n" end def add_stylesheets - inject_into_file 'vendor/assets/stylesheets/spree/frontend/all.css', " *= require spree/frontend/solidus_friendly_promotions\n", before: %r{\*/}, verbose: true # rubocop:disable Layout/LineLength - inject_into_file 'vendor/assets/stylesheets/spree/backend/all.css', " *= require spree/backend/solidus_friendly_promotions\n", before: %r{\*/}, verbose: true # rubocop:disable Layout/LineLength + inject_into_file "vendor/assets/stylesheets/spree/frontend/all.css", " *= require spree/frontend/solidus_friendly_promotions\n", before: %r{\*/}, verbose: true # rubocop:disable Layout/LineLength + inject_into_file "vendor/assets/stylesheets/spree/backend/all.css", " *= require spree/backend/solidus_friendly_promotions\n", before: %r{\*/}, verbose: true # rubocop:disable Layout/LineLength end def add_migrations - run 'bin/rails railties:install:migrations FROM=solidus_friendly_promotions' + run "bin/rails railties:install:migrations FROM=solidus_friendly_promotions" end def run_migrations - run_migrations = options[:auto_run_migrations] || ['', 'y', 'Y'].include?(ask('Would you like to run the migrations now? [Y/n]')) # rubocop:disable Layout/LineLength + run_migrations = options[:auto_run_migrations] || ["", "y", "Y"].include?(ask("Would you like to run the migrations now? [Y/n]")) # rubocop:disable Layout/LineLength if run_migrations - run 'bin/rails db:migrate' + run "bin/rails db:migrate" else - puts 'Skipping bin/rails db:migrate, don\'t forget to run it!' # rubocop:disable Rails/Output + puts "Skipping bin/rails db:migrate, don't forget to run it!" # rubocop:disable Rails/Output end end end diff --git a/friendly_promotions/lib/solidus_friendly_promotions.rb b/friendly_promotions/lib/solidus_friendly_promotions.rb index 5ae3dc6fcc7..bf6950e2e15 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'solidus_friendly_promotions/configuration' -require 'solidus_friendly_promotions/version' -require 'solidus_friendly_promotions/engine' +require "solidus_friendly_promotions/configuration" +require "solidus_friendly_promotions/version" +require "solidus_friendly_promotions/engine" diff --git a/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb b/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb index d00757a289a..bb9d78a8c19 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb @@ -12,7 +12,7 @@ def configuration @configuration ||= Configuration.new end - alias config configuration + alias_method :config, :configuration def configure yield configuration diff --git a/friendly_promotions/lib/solidus_friendly_promotions/engine.rb b/friendly_promotions/lib/solidus_friendly_promotions/engine.rb index 1cf24dc3e2c..47a740fd133 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions/engine.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions/engine.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require 'solidus_core' -require 'solidus_support' +require "solidus_core" +require "solidus_support" module SolidusFriendlyPromotions class Engine < Rails::Engine @@ -9,7 +9,7 @@ class Engine < Rails::Engine isolate_namespace ::Spree - engine_name 'solidus_friendly_promotions' + engine_name "solidus_friendly_promotions" # use rspec for tests config.generators do |g| diff --git a/friendly_promotions/lib/solidus_friendly_promotions/version.rb b/friendly_promotions/lib/solidus_friendly_promotions/version.rb index eb53a9e9496..ab1978d5a44 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions/version.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module SolidusFriendlyPromotions - VERSION = '0.0.1' + VERSION = "0.0.1" end diff --git a/friendly_promotions/solidus_friendly_promotions.gemspec b/friendly_promotions/solidus_friendly_promotions.gemspec index 05cb6b99c9a..5180cdf27a8 100644 --- a/friendly_promotions/solidus_friendly_promotions.gemspec +++ b/friendly_promotions/solidus_friendly_promotions.gemspec @@ -1,37 +1,36 @@ # frozen_string_literal: true -require_relative 'lib/solidus_friendly_promotions/version' +require_relative "lib/solidus_friendly_promotions/version" Gem::Specification.new do |spec| - spec.name = 'solidus_friendly_promotions' + spec.name = "solidus_friendly_promotions" spec.version = SolidusFriendlyPromotions::VERSION - spec.authors = ['Martin Meyerhoff'] - spec.email = 'mamhoff@gmail.com' + spec.authors = ["Martin Meyerhoff"] + spec.email = "mamhoff@gmail.com" - spec.summary = 'A replacement for Solidus\' promotion system' - spec.description = 'Experimental replacement for the promotion system in Solidus' - spec.homepage = 'https://github.com/solidusio-contrib/solidus_friendly_promotions#readme' - spec.license = 'BSD-3-Clause' + spec.summary = "A replacement for Solidus' promotion system" + spec.description = "Experimental replacement for the promotion system in Solidus" + spec.homepage = "https://github.com/solidusio-contrib/solidus_friendly_promotions#readme" + spec.license = "BSD-3-Clause" - spec.metadata['homepage_uri'] = spec.homepage - spec.metadata['source_code_uri'] = 'https://github.com/solidusio-contrib/solidus_friendly_promotions' - spec.metadata['changelog_uri'] = 'https://github.com/solidusio-contrib/solidus_friendly_promotions/blob/master/CHANGELOG.md' + spec.metadata["homepage_uri"] = spec.homepage + spec.metadata["source_code_uri"] = "https://github.com/solidusio-contrib/solidus_friendly_promotions" + spec.metadata["changelog_uri"] = "https://github.com/solidusio-contrib/solidus_friendly_promotions/blob/master/CHANGELOG.md" - spec.required_ruby_version = Gem::Requirement.new('>= 2.5', '< 4') + spec.required_ruby_version = Gem::Requirement.new(">= 2.5", "< 4") # Specify which files should be added to the gem when it is released. # The `git ls-files -z` loads the files in the RubyGem that have been added into git. files = Dir.chdir(__dir__) { `git ls-files -z`.split("\x0") } spec.files = files.grep_v(%r{^(test|spec|features)/}) - spec.test_files = files.grep(%r{^(test|spec|features)/}) spec.bindir = "exe" spec.executables = files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] - spec.add_dependency 'solidus_core', ['>= 3.0.0', '< 5'] - spec.add_dependency 'solidus_support', '~> 0.5' + spec.add_dependency "solidus_core", [">= 3.0.0", "< 5"] + spec.add_dependency "solidus_support", "~> 0.5" - spec.add_development_dependency 'solidus_dev_support', '~> 2.6' - spec.add_development_dependency 'rspec-activemodel-mocks', '~> 1.0' + spec.add_development_dependency "solidus_dev_support", "~> 2.6" + spec.add_development_dependency "rspec-activemodel-mocks", "~> 1.0" end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_order_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_order_spec.rb index 0a958413e3c..74e95b91a54 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_order_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_order_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" RSpec.describe SolidusFriendlyPromotions::Rules::FirstOrder, type: :model do let(:rule) { SolidusFriendlyPromotions::Rules::FirstOrder.new } @@ -11,8 +11,8 @@ it { expect(rule).to be_eligible(order) } it "does not set an error message" do rule.eligible?(order) - expect(rule.eligibility_errors.full_messages.first). - to be_nil + expect(rule.eligibility_errors.full_messages.first) + .to be_nil end end @@ -48,21 +48,21 @@ it { expect(rule).not_to be_eligible(order) } it "sets an error message" do rule.eligible?(order) - expect(rule.eligibility_errors.full_messages.first). - to eq "This coupon code can only be applied to your first order." + expect(rule.eligibility_errors.full_messages.first) + .to eq "This coupon code can only be applied to your first order." end it "sets an error code" do rule.eligible?(order) - expect(rule.eligibility_errors.details[:base].first[:error_code]). - to eq :not_first_order + expect(rule.eligibility_errors.details[:base].first[:error_code]) + .to eq :not_first_order end end end end context "for a guest user" do - let(:email) { 'user@solidus.io' } - before { allow(order).to receive_messages email: 'user@solidus.io' } + let(:email) { "user@solidus.io" } + before { allow(order).to receive_messages email: "user@solidus.io" } context "with no other orders" do it { expect(rule).to be_eligible(order) } @@ -73,13 +73,13 @@ it { expect(rule).not_to be_eligible(order) } it "sets an error message" do rule.eligible?(order) - expect(rule.eligibility_errors.full_messages.first). - to eq "This coupon code can only be applied to your first order." + expect(rule.eligibility_errors.full_messages.first) + .to eq "This coupon code can only be applied to your first order." end it "sets an error code" do rule.eligible?(order) - expect(rule.eligibility_errors.details[:base].first[:error_code]). - to eq :not_first_order + expect(rule.eligibility_errors.details[:base].first[:error_code]) + .to eq :not_first_order end end end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_repeat_purchase_since_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_repeat_purchase_since_spec.rb index a827a300f75..33dd9487c7f 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_repeat_purchase_since_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_repeat_purchase_since_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" RSpec.describe SolidusFriendlyPromotions::Rules::FirstRepeatPurchaseSince do describe "#applicable?" do diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/item_total_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/item_total_spec.rb index f1c6444f98f..4c396723103 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/item_total_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/item_total_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" RSpec.describe SolidusFriendlyPromotions::Rules::ItemTotal, type: :model do let(:rule) do @@ -11,10 +11,10 @@ end let(:order) { double(:order, item_total: item_total, currency: order_currency) } let(:preferred_amount) { 50 } - let(:order_currency) { 'USD' } + let(:order_currency) { "USD" } context "preferred operator set to gt" do - let(:preferred_operator) { 'gt' } + let(:preferred_operator) { "gt" } context "item total is greater than preferred amount" do let(:item_total) { 51 } @@ -41,13 +41,13 @@ it "set an error message" do rule.eligible?(order) - expect(rule.eligibility_errors.full_messages.first). - to eq "This coupon code can't be applied to orders less than or equal to $50.00." + expect(rule.eligibility_errors.full_messages.first) + .to eq "This coupon code can't be applied to orders less than or equal to $50.00." end it "sets an error code" do rule.eligible?(order) - expect(rule.eligibility_errors.details[:base].first[:error_code]). - to eq :item_total_less_than_or_equal + expect(rule.eligibility_errors.details[:base].first[:error_code]) + .to eq :item_total_less_than_or_equal end end @@ -60,19 +60,19 @@ it "set an error message" do rule.eligible?(order) - expect(rule.eligibility_errors.full_messages.first). - to eq "This coupon code can't be applied to orders less than or equal to $50.00." + expect(rule.eligibility_errors.full_messages.first) + .to eq "This coupon code can't be applied to orders less than or equal to $50.00." end it "sets an error code" do rule.eligible?(order) - expect(rule.eligibility_errors.details[:base].first[:error_code]). - to eq :item_total_less_than_or_equal + expect(rule.eligibility_errors.details[:base].first[:error_code]) + .to eq :item_total_less_than_or_equal end end end context "preferred operator set to gte" do - let(:preferred_operator) { 'gte' } + let(:preferred_operator) { "gte" } context "total is greater than preferred amount" do let(:item_total) { 51 } @@ -115,13 +115,13 @@ it "set an error message" do rule.eligible?(order) - expect(rule.eligibility_errors.full_messages.first). - to eq "This coupon code can't be applied to orders less than $50.00." + expect(rule.eligibility_errors.full_messages.first) + .to eq "This coupon code can't be applied to orders less than $50.00." end it "sets an error code" do rule.eligible?(order) - expect(rule.eligibility_errors.details[:base].first[:error_code]). - to eq :item_total_less_than + expect(rule.eligibility_errors.details[:base].first[:error_code]) + .to eq :item_total_less_than end end end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/line_item_taxon_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/line_item_taxon_spec.rb index de67486218c..c65fe5e792a 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/line_item_taxon_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/line_item_taxon_spec.rb @@ -1,50 +1,50 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" RSpec.describe SolidusFriendlyPromotions::Rules::LineItemTaxon, type: :model do - let(:taxon) { create :taxon, name: 'first' } - let(:taxon2) { create :taxon, name: 'second' } - let(:order) { create :order_with_line_items } + let(:taxon) { create :taxon, name: "first" } + let(:taxon2) { create :taxon, name: "second" } + let(:order) { create :order_with_line_items } let(:product) { order.products.first } let(:rule) do described_class.create!(promotion: create(:promotion)) end - describe '#eligible?' do + describe "#eligible?" do let(:line_item) { order.line_items.first! } let(:order) { create :order_with_line_items } - let(:taxon) { create :taxon, name: 'first' } + let(:taxon) { create :taxon, name: "first" } - context 'with an invalid match policy' do + context "with an invalid match policy" do before do - rule.preferred_match_policy = 'invalid' + rule.preferred_match_policy = "invalid" rule.save!(validate: false) line_item.product.taxons << taxon rule.taxons << taxon end - it 'raises' do + it "raises" do expect { rule.eligible?(line_item) }.to raise_error('unexpected match policy: "invalid"') end end - context 'when a product has a taxon of a taxon rule' do + context "when a product has a taxon of a taxon rule" do before do product.taxons << taxon rule.taxons << taxon rule.save! end - it 'is eligible' do + it "is eligible" do expect(rule).to be_eligible(line_item) end end - context 'when a product has a taxon child of a taxon rule' do + context "when a product has a taxon child of a taxon rule" do before do taxon.children << taxon2 product.taxons << taxon2 @@ -52,7 +52,7 @@ rule.save! end - it 'is eligible' do + it "is eligible" do expect(rule).to be_eligible(line_item) end @@ -67,14 +67,14 @@ end end - context 'when a product does not have taxon or child taxon of a taxon rule' do + context "when a product does not have taxon or child taxon of a taxon rule" do before do product.taxons << taxon2 rule.taxons << taxon rule.save! end - it 'is not eligible' do + it "is not eligible" do expect(rule).not_to be_eligible(line_item) end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/nth_order_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/nth_order_spec.rb index 483a685038d..e478a3630cc 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/nth_order_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/nth_order_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" RSpec.describe SolidusFriendlyPromotions::Rules::NthOrder do describe "#applicable?" do diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/one_use_per_user_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/one_use_per_user_spec.rb index 35ef09ce1a0..d95968fff81 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/one_use_per_user_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/one_use_per_user_spec.rb @@ -1,11 +1,11 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" RSpec.describe SolidusFriendlyPromotions::Rules::OneUsePerUser, type: :model do let(:rule) { described_class.new } - describe '#eligible?(order)' do + describe "#eligible?(order)" do subject { rule.eligible?(order) } let(:order) { double Spree::Order, user: user } let(:user) { double Spree::LegacyUser } @@ -14,40 +14,40 @@ before { rule.promotion = promotion } - context 'when the order is assigned to a user' do - context 'when the user has used this promotion before' do + context "when the order is assigned to a user" do + context "when the user has used this promotion before" do let(:used_by) { true } it { is_expected.to be false } it "sets an error message" do subject - expect(rule.eligibility_errors.full_messages.first). - to eq "This coupon code can only be used once per user." + expect(rule.eligibility_errors.full_messages.first) + .to eq "This coupon code can only be used once per user." end it "sets an error code" do rule.eligible?(order) - expect(rule.eligibility_errors.details[:base].first[:error_code]). - to eq :limit_once_per_user + expect(rule.eligibility_errors.details[:base].first[:error_code]) + .to eq :limit_once_per_user end end - context 'when the user has not used this promotion before' do + context "when the user has not used this promotion before" do it { is_expected.to be true } end end - context 'when the order is not assigned to a user' do + context "when the order is not assigned to a user" do let(:user) { nil } it { is_expected.to be false } it "sets an error message" do subject - expect(rule.eligibility_errors.full_messages.first). - to eq "You need to login before applying this coupon code." + expect(rule.eligibility_errors.full_messages.first) + .to eq "You need to login before applying this coupon code." end it "sets an error code" do rule.eligible?(order) - expect(rule.eligibility_errors.details[:base].first[:error_code]). - to eq :no_user_specified + expect(rule.eligibility_errors.details[:base].first[:error_code]) + .to eq :no_user_specified end end end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/option_value_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/option_value_spec.rb index fdf4e3441fb..913944f2bee 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/option_value_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/option_value_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" RSpec.describe SolidusFriendlyPromotions::Rules::OptionValue do let(:rule) { SolidusFriendlyPromotions::Rules::OptionValue.new } @@ -8,8 +8,8 @@ describe "#preferred_eligible_values" do subject { rule.preferred_eligible_values } it "assigns a nicely formatted hash" do - rule.preferred_eligible_values = Hash["5" => "1,2", "6" => "1"] - expect(subject).to eq Hash[5 => [1, 2], 6 => [1]] + rule.preferred_eligible_values= {"5" => "1,2", "6" => "1"} + expect(subject).to eq({5 => [1, 2], 6 => [1]}) end end @@ -32,15 +32,15 @@ subject { rule.eligible?(promotable) } context "when there are any applicable line items" do before do - rule.preferred_eligible_values = Hash[line_item.product.id => [ + rule.preferred_eligible_values= {line_item.product.id => [ line_item.variant.option_values.pluck(:id).first - ]] + ]} end it { is_expected.to be true } end context "when there are no applicable line items" do before do - rule.preferred_eligible_values = Hash[99 => [99]] + rule.preferred_eligible_values= {99 => [99]} end it { is_expected.to be false } end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/product_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/product_spec.rb index 2a2e30b53c6..192f20531df 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/product_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/product_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" RSpec.describe SolidusFriendlyPromotions::Rules::Product, type: :model do let(:rule) { SolidusFriendlyPromotions::Rules::Product.new(rule_options) } @@ -35,13 +35,13 @@ it { expect(rule).not_to be_eligible(order) } it "sets an error message" do rule.eligible?(order) - expect(rule.eligibility_errors.full_messages.first). - to eq "You need to add an applicable product before applying this coupon code." + expect(rule.eligibility_errors.full_messages.first) + .to eq "You need to add an applicable product before applying this coupon code." end it "sets an error code" do rule.eligible?(order) - expect(rule.eligibility_errors.details[:base].first[:error_code]). - to eq :no_applicable_products + expect(rule.eligibility_errors.details[:base].first[:error_code]) + .to eq :no_applicable_products end end end @@ -63,13 +63,13 @@ it { expect(rule).not_to be_eligible(order) } it "sets an error message" do rule.eligible?(order) - expect(rule.eligibility_errors.full_messages.first). - to eq "This coupon code can't be applied because you don't have all of the necessary products in your cart." + expect(rule.eligibility_errors.full_messages.first) + .to eq "This coupon code can't be applied because you don't have all of the necessary products in your cart." end it "sets an error code" do rule.eligible?(order) - expect(rule.eligibility_errors.details[:base].first[:error_code]). - to eq :missing_product + expect(rule.eligibility_errors.details[:base].first[:error_code]) + .to eq :missing_product end end end @@ -91,13 +91,13 @@ it { expect(rule).not_to be_eligible(order) } it "sets an error message" do rule.eligible?(order) - expect(rule.eligibility_errors.full_messages.first). - to eq "Your cart contains a product that prevents this coupon code from being applied." + expect(rule.eligibility_errors.full_messages.first) + .to eq "Your cart contains a product that prevents this coupon code from being applied." end it "sets an error code" do rule.eligible?(order) - expect(rule.eligibility_errors.details[:base].first[:error_code]). - to eq :has_excluded_product + expect(rule.eligibility_errors.details[:base].first[:error_code]) + .to eq :has_excluded_product end end end @@ -107,17 +107,17 @@ SolidusFriendlyPromotions::Rules::Product.create!( promotion: create(:promotion), product_promotion_rules: [ - Spree::ProductPromotionRule.new(product: product), - ], + Spree::ProductPromotionRule.new(product: product) + ] ).tap do |rule| - rule.preferred_match_policy = 'invalid' + rule.preferred_match_policy = "invalid" rule.save!(validate: false) end end let(:product) { order.line_items.first!.product } let(:order) { create(:order_with_line_items, line_items_count: 1) } - it 'raises' do + it "raises" do expect { rule.eligible?(order) }.to raise_error('unexpected match policy: "invalid"') diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/taxon_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/taxon_spec.rb index a75e8728b64..0e24cec66cd 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/taxon_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/taxon_spec.rb @@ -1,24 +1,24 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" RSpec.describe SolidusFriendlyPromotions::Rules::Taxon, type: :model do - let(:taxon) { create :taxon, name: 'first' } - let(:taxon2) { create :taxon, name: 'second' } - let(:order) { create :order_with_line_items } + let(:taxon) { create :taxon, name: "first" } + let(:taxon2) { create :taxon, name: "second" } + let(:order) { create :order_with_line_items } let(:product) { order.products.first } let(:rule) do SolidusFriendlyPromotions::Rules::Taxon.create!(promotion: create(:promotion)) end - context '#eligible?(order)' do - context 'with any match policy' do + context "#eligible?(order)" do + context "with any match policy" do before do - rule.update!(preferred_match_policy: 'any') + rule.update!(preferred_match_policy: "any") end - it 'is eligible if order does have any prefered taxon' do + it "is eligible if order does have any prefered taxon" do product.taxons << taxon rule.taxons << taxon expect(rule).to be_eligible(order) @@ -29,33 +29,33 @@ it { expect(rule).not_to be_eligible(order) } it "sets an error message" do rule.eligible?(order) - expect(rule.eligibility_errors.full_messages.first). - to eq "You need to add a product from an applicable category before applying this coupon code." + expect(rule.eligibility_errors.full_messages.first) + .to eq "You need to add a product from an applicable category before applying this coupon code." end it "sets an error code" do rule.eligible?(order) - expect(rule.eligibility_errors.details[:base].first[:error_code]). - to eq :no_matching_taxons + expect(rule.eligibility_errors.details[:base].first[:error_code]) + .to eq :no_matching_taxons end end - context 'when a product has a taxon child of a taxon rule' do + context "when a product has a taxon child of a taxon rule" do before do taxon.children << taxon2 product.taxons << taxon2 rule.taxons << taxon end - it{ expect(rule).to be_eligible(order) } + it { expect(rule).to be_eligible(order) } end end - context 'with all match policy' do + context "with all match policy" do before do - rule.update!(preferred_match_policy: 'all') + rule.update!(preferred_match_policy: "all") end - it 'is eligible order has all prefered taxons' do + it "is eligible order has all prefered taxons" do product.taxons << taxon2 order.products.last.taxons << taxon @@ -69,17 +69,17 @@ it { expect(rule).not_to be_eligible(order) } it "sets an error message" do rule.eligible?(order) - expect(rule.eligibility_errors.full_messages.first). - to eq "You need to add a product from all applicable categories before applying this coupon code." + expect(rule.eligibility_errors.full_messages.first) + .to eq "You need to add a product from all applicable categories before applying this coupon code." end it "sets an error code" do rule.eligible?(order) - expect(rule.eligibility_errors.details[:base].first[:error_code]). - to eq :missing_taxon + expect(rule.eligibility_errors.details[:base].first[:error_code]) + .to eq :missing_taxon end end - context 'when a product has a taxon child of a taxon rule' do + context "when a product has a taxon child of a taxon rule" do let(:taxon3) { create :taxon } before do @@ -95,9 +95,9 @@ end end - context 'with none match policy' do + context "with none match policy" do before do - rule.preferred_match_policy = 'none' + rule.preferred_match_policy = "none" end context "none of the order's products are in listed taxon" do @@ -115,26 +115,26 @@ end it "sets an error message" do rule.eligible?(order) - expect(rule.eligibility_errors.full_messages.first). - to eq "Your cart contains a product from an excluded category that prevents this coupon code from being applied." + expect(rule.eligibility_errors.full_messages.first) + .to eq "Your cart contains a product from an excluded category that prevents this coupon code from being applied." end it "sets an error code" do rule.eligible?(order) - expect(rule.eligibility_errors.details[:base].first[:error_code]). - to eq :has_excluded_taxon + expect(rule.eligibility_errors.details[:base].first[:error_code]) + .to eq :has_excluded_taxon end end end - context 'with an invalid match policy' do + context "with an invalid match policy" do before do order.products.first.taxons << taxon rule.taxons << taxon - rule.preferred_match_policy = 'invalid' + rule.preferred_match_policy = "invalid" rule.save!(validate: false) end - it 'raises' do + it "raises" do expect { rule.eligible?(order) }.to raise_error('unexpected match policy: "invalid"') diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_logged_in_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_logged_in_spec.rb index 7c6191ef504..4147c5d4bd0 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_logged_in_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_logged_in_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" RSpec.describe SolidusFriendlyPromotions::Rules::UserLoggedIn, type: :model do let(:rule) { SolidusFriendlyPromotions::Rules::UserLoggedIn.new } @@ -9,7 +9,7 @@ let(:order) { Spree::Order.new } it "should be eligible if order has an associated user" do - user = double('User') + user = double("User") allow(order).to receive_messages(user: user) expect(rule).to be_eligible(order) @@ -20,13 +20,13 @@ it { expect(rule).not_to be_eligible(order) } it "sets an error message" do rule.eligible?(order) - expect(rule.eligibility_errors.full_messages.first). - to eq "You need to login before applying this coupon code." + expect(rule.eligibility_errors.full_messages.first) + .to eq "You need to login before applying this coupon code." end it "sets an error code" do rule.eligible?(order) - expect(rule.eligibility_errors.details[:base].first[:error_code]). - to eq :no_user_specified + expect(rule.eligibility_errors.details[:base].first[:error_code]) + .to eq :no_user_specified end end end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_role_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_role_spec.rb index beae7821091..49c6f0060bc 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_role_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_role_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" RSpec.describe SolidusFriendlyPromotions::Rules::UserRole, type: :model do let(:rule) { described_class.new(preferred_role_ids: roles_for_rule.map(&:id)) } @@ -10,75 +10,75 @@ subject { rule } - shared_examples 'eligibility' do - context 'no roles on rule' do + shared_examples "eligibility" do + context "no roles on rule" do let(:roles_for_user) { [create(:role)] } - it 'should not be eligible' do + it "should not be eligible" do expect(rule).to_not be_eligible(order) end end - context 'no roles on user' do + context "no roles on user" do let(:roles_for_rule) { [create(:role)] } - it 'should not be eligible' do + it "should not be eligible" do expect(rule).to_not be_eligible(order) end end - context 'mismatched roles' do + context "mismatched roles" do let(:roles_for_user) { [create(:role)] } let(:roles_for_rule) { [create(:role)] } - it 'should not be eligible' do + it "should not be eligible" do expect(rule).to_not be_eligible(order) end end - context 'matching all roles' do + context "matching all roles" do let(:roles_for_user) { [create(:role), create(:role)] } let(:roles_for_rule) { roles_for_user } - it 'should be eligible' do + it "should be eligible" do expect(rule).to be_eligible(order) end end end - context '#eligible?(order)' do - context 'order with no user' do + context "#eligible?(order)" do + context "order with no user" do let(:order) { Spree::Order.new } - it 'should not be eligible' do + it "should not be eligible" do expect(rule).to_not be_eligible(order) end end - context 'order with user' do + context "order with user" do let(:order) { Spree::Order.new(user: user) } - context 'with any match policy' do - before { rule.preferred_match_policy = 'any' } + context "with any match policy" do + before { rule.preferred_match_policy = "any" } - include_examples 'eligibility' + include_examples "eligibility" - context 'one shared role' do + context "one shared role" do let(:shared_role) { create(:role) } let(:roles_for_user) { [create(:role), shared_role] } let(:roles_for_rule) { [create(:role), shared_role] } - it 'should be eligible' do + it "should be eligible" do expect(rule).to be_eligible(order) end end end - context 'with all match policy' do - before { rule.preferred_match_policy = 'all' } + context "with all match policy" do + before { rule.preferred_match_policy = "all" } - include_examples 'eligibility' + include_examples "eligibility" - context 'one shared role' do + context "one shared role" do let(:shared_role) { create(:role) } let(:roles_for_user) { [create(:role), shared_role] } let(:roles_for_rule) { [create(:role), shared_role] } - it 'should not be eligible' do + it "should not be eligible" do expect(rule).to_not be_eligible(order) end end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_spec.rb index c1df8349180..56bf4b4a97f 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" RSpec.describe SolidusFriendlyPromotions::Rules::User, type: :model do let(:rule) { SolidusFriendlyPromotions::Rules::User.new } diff --git a/friendly_promotions/spec/spec_helper.rb b/friendly_promotions/spec/spec_helper.rb index 1cb2698e589..0c2fae49a39 100644 --- a/friendly_promotions/spec/spec_helper.rb +++ b/friendly_promotions/spec/spec_helper.rb @@ -1,21 +1,21 @@ # frozen_string_literal: true # Configure Rails Environment -ENV['RAILS_ENV'] = 'test' +ENV["RAILS_ENV"] = "test" # Run Coverage report -require 'solidus_dev_support/rspec/coverage' +require "solidus_dev_support/rspec/coverage" # Create the dummy app if it's still missing. dummy_env = "#{__dir__}/dummy/config/environment.rb" -system 'bin/rake extension:test_app' unless File.exist? dummy_env +system "bin/rake extension:test_app" unless File.exist? dummy_env require dummy_env # Requires factories and other useful helpers defined in spree_core. -require 'solidus_dev_support/rspec/feature_helper' +require "solidus_dev_support/rspec/feature_helper" # Explicitly load activemodel mocks -require 'rspec-activemodel-mocks' +require "rspec-activemodel-mocks" # Requires supporting ruby files with custom matchers and macros, etc, # in spec/support/ and its subdirectories. @@ -29,7 +29,7 @@ config.infer_spec_type_from_file_location! config.use_transactional_fixtures = false - if Spree.solidus_gem_version < Gem::Version.new('2.11') + if Spree.solidus_gem_version < Gem::Version.new("2.11") config.extend Spree::TestingSupport::AuthorizationHelpers::Request, type: :system end end From dbc3c2ef60e1e88e6e2da59c4aa78611fddcc6a0 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 31 May 2023 12:59:23 +0200 Subject: [PATCH 186/834] Remove deprecated code --- .../solidus_friendly_promotions/rules/item_total.rb | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb index b80c3d45357..c865830c642 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb @@ -8,8 +8,6 @@ module Rules # To add extra operators please override `self.operators_map` or any other helper method. # To customize the error message you can also override `ineligible_message`. class ItemTotal < ::Spree::PromotionRule - include ActiveSupport::Deprecation::DeprecatedConstantAccessor - preference :amount, :decimal, default: 100.00 preference :currency, :string, default: -> { Spree::Config[:currency] } preference :operator, :string, default: "gt" @@ -28,15 +26,6 @@ def self.operator_options end end - # @deprecated - OPERATORS = operators_map.keys.map(&:to_s) - deprecate_constant( - :OPERATORS, - :operators_map, - message: "OPERATORS is deprecated! Use `operators_map.keys.map(&:to_s)` instead.", - deprecator: Spree::Deprecation - ) - def applicable?(promotable) promotable.is_a?(Spree::Order) end From 0a1081a3078498c0284049daedd93b57885a66ff Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 6 Jun 2023 11:16:57 +0200 Subject: [PATCH 187/834] Minimal working version This removes promotion stuff from `Spree::OrderContents` and adds services classes that handle adjustments in-memory. I'm still debating with myself whether to have separate tables for discounts, as that will make a lot of that much easier to work with. --- .../order_contents_decorator.rb | 16 +++++ .../actions/adjust_line_item.rb | 11 ++++ .../actions/adjust_shipment.rb | 11 ++++ .../actions/base.rb | 43 +++++++++++++ .../line_item_adjuster.rb | 31 ++++++++++ .../order_promotion_adjuster.rb | 61 +++++++++++++++++++ .../promotion_adjustment_chooser.rb | 19 ++++++ .../promotion_eligibility.rb | 24 ++++++++ .../shipment_adjuster.rb | 31 ++++++++++ .../spec/models/promotion/integration_spec.rb | 42 +++---------- .../actions/base_spec.rb | 34 +++++++++++ .../order_promotion_adjuster_spec.rb | 0 12 files changed, 288 insertions(+), 35 deletions(-) create mode 100644 friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_contents_decorator.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/actions/base.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/line_item_adjuster.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/order_promotion_adjuster.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/promotion_adjustment_chooser.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/promotion_eligibility.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/shipment_adjuster.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/actions/base_spec.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/order_promotion_adjuster_spec.rb diff --git a/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_contents_decorator.rb b/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_contents_decorator.rb new file mode 100644 index 00000000000..029fa3447f4 --- /dev/null +++ b/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_contents_decorator.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module OrderContentsDecorator + private + + def after_add_or_remove(line_item, options = {}) + shipment = options[:shipment] + shipment.present? ? shipment.update_amounts : order.ensure_updated_shipments + reload_totals + line_item + end + + Spree::OrderContents.prepend self + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb new file mode 100644 index 00000000000..73eaa92b863 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module Actions + class AdjustLineItem < Base + def can_adjust?(object) + object.is_a? Spree::LineItem + end + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb new file mode 100644 index 00000000000..bcc0a03bdec --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module Actions + class AdjustShipment < Base + def can_adjust?(object) + object.is_a? Spree::Shipment + end + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/actions/base.rb b/friendly_promotions/app/models/solidus_friendly_promotions/actions/base.rb new file mode 100644 index 00000000000..e26f14ab339 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/actions/base.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module Actions + class Base < ::Spree::PromotionAction + include Spree::CalculatedAdjustments + include Spree::AdjustmentSource + + has_many :adjustments, as: :source + + def preload_relations + [:calculator] + end + + def can_adjust?(object) + raise NotImplementedError + end + + def adjust(adjustable) + adjustment = adjustable.adjustments.detect do |adjustment| + adjustment.source == self + end || adjustable.adjustments.build(source: self) + adjustment.label = adjustment_label(adjustable) + adjustment.amount = compute_amount(adjustable) + adjustment + end + + # Ensure a negative amount which does not exceed the object's amount + def compute_amount(adjustable) + promotion_amount = calculator.compute(adjustable) || BigDecimal(0) + [adjustable.amount, promotion_amount.abs].min * -1 + end + + def adjustment_label(adjustable) + I18n.t( + "spree.adjustment_labels.#{adjustable.class.name.demodulize.underscore}", + promotion: Spree::Promotion.model_name.human, + promotion_name: promotion.name, + ) + end + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/line_item_adjuster.rb b/friendly_promotions/app/models/solidus_friendly_promotions/line_item_adjuster.rb new file mode 100644 index 00000000000..e60b92a2c71 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/line_item_adjuster.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + class LineItemAdjuster + attr_reader :promotions + + def initialize(promotions:) + @promotions = promotions + end + + def call(line_item) + return unless line_item.variant.product.promotionable? + non_promotion_adjustments = line_item.adjustments.reject(&:promotion?) + + eligible_promotions = PromotionEligibility.new(promotable: line_item, possible_promotions: promotions).call + + possible_adjustments = eligible_promotions.flat_map do |promotion| + promotion.actions.select do |action| + action.can_adjust?(line_item) + end.map do |action| + action.adjust(line_item) + end + end + + chosen_adjustments = Spree::Config.promotion_chooser_class.new(line_item).call(possible_adjustments) + + line_item.promo_total = chosen_adjustments.sum(&:amount) + line_item.adjustments = non_promotion_adjustments + chosen_adjustments + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/order_promotion_adjuster.rb b/friendly_promotions/app/models/solidus_friendly_promotions/order_promotion_adjuster.rb new file mode 100644 index 00000000000..c9277510810 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/order_promotion_adjuster.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + class OrderPromotionAdjuster + attr_reader :order, :promotions + + def initialize(order) + @order = order + @promotions = PromotionEligibility.new(promotable: order, possible_promotions: possible_promotions).call + end + + def call + adjust_line_items + adjust_shipments + order.promo_total = (order.line_items + order.shipments).sum { |item| item.promo_total } + order + end + + private + + def adjust_line_items + line_item_adjuster = LineItemAdjuster.new(promotions: promotions) + order.line_items.each { |line_item| line_item_adjuster.call(line_item) } + end + + def adjust_shipments + shipment_adjuster = ShipmentAdjuster.new(promotions: promotions) + order.shipments.each { |shipment| shipment_adjuster.call(shipment) } + end + + def possible_promotions + promos = connected_order_promotions | sale_promotions + promos.flat_map(&:promotion_actions).group_by(&:preload_relations).each do |preload_relations, actions| + preload(records: actions, associations: preload_relations) + end + promos.flat_map(&:promotion_rules).group_by(&:preload_relations).each do |preload_relations, rules| + preload(records: rules, associations: preload_relations) + end + promos.reject { |promotion| promotion.usage_limit_exceeded?(excluded_orders: [order]) } + end + + def preload(records:, associations:) + ActiveRecord::Associations::Preloader.new(records: records, associations: associations).call + end + + def connected_order_promotions + order.promotions.active.includes(promotion_includes) + end + + def sale_promotions + Spree::Promotion.where(apply_automatically: true).active.includes(promotion_includes) + end + + def promotion_includes + [ + :promotion_rules, + :promotion_actions, + ] + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_adjustment_chooser.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_adjustment_chooser.rb new file mode 100644 index 00000000000..4408a1d5f95 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_adjustment_chooser.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + class PromotionAdjustmentChooser + attr_reader :adjustable + + def initialize(adjustable) + @adjustable = adjustable + end + + def call(adjustments) + Array.wrap( + adjustments.select(&:eligible?).min_by do |adjustment| + [adjustment.amount, -adjustment.id.to_i] + end + ) + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_eligibility.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_eligibility.rb new file mode 100644 index 00000000000..ef6c4d7df18 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_eligibility.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + class PromotionEligibility + attr_reader :promotable, :possible_promotions + + def initialize(promotable:, possible_promotions:) + @promotable = promotable + @possible_promotions = possible_promotions + end + + def call + possible_promotions.select do |candidate| + applicable_rules = candidate.rules.select do |rule| + rule.applicable?(promotable) + end + + applicable_rules.all? do |applicable_rule| + applicable_rule.eligible?(promotable) + end + end + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/shipment_adjuster.rb b/friendly_promotions/app/models/solidus_friendly_promotions/shipment_adjuster.rb new file mode 100644 index 00000000000..5f43851ac97 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/shipment_adjuster.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + class ShipmentAdjuster + attr_reader :promotions + + def initialize(promotions:) + @promotions = promotions + end + + def call(shipment) + non_promotion_adjustments = shipment.adjustments.reject(&:promotion?) + + eligible_promotions = promotions.select do |promotion| + PromotionEligibility.new(promotable: shipment, possible_promotions: promotions).call + end + + possible_adjustments = eligible_promotions.flat_map do |promotion| + promotion.actions.select do |action| + action.can_adjust?(shipment) + end.map do |action| + action.adjust(shipment) + end + end + + chosen_adjustments = Spree::Config.promotion_chooser_class.new(shipment).call(possible_adjustments) + shipment.promo_total = chosen_adjustments.sum(&:amount) + shipment.adjustments = non_promotion_adjustments + chosen_adjustments + end + end +end diff --git a/friendly_promotions/spec/models/promotion/integration_spec.rb b/friendly_promotions/spec/models/promotion/integration_spec.rb index 4d065132bd0..d6d8dc1ac5a 100644 --- a/friendly_promotions/spec/models/promotion/integration_spec.rb +++ b/friendly_promotions/spec/models/promotion/integration_spec.rb @@ -4,12 +4,14 @@ RSpec.describe "Promotion System" do context "A promotion that creates line item adjustments" do - let(:shirt) { create(:product) } - let(:pants) { create(:product) } + let(:shirt) { create(:product, name: "Shirt") } + let(:pants) { create(:product, name: "Pants") } let(:promotion) { create(:promotion, name: "20% off Shirts", apply_automatically: true) } let(:order) { create(:order) } before do + Spree::Config.promotion_adjuster_class = "SolidusFriendlyPromotions::OrderPromotionAdjuster" + Spree::Config.promotion_chooser_class = "SolidusFriendlyPromotions::PromotionAdjustmentChooser" promotion.rules << rule promotion.actions << action order.contents.add(shirt.master, 1) @@ -19,25 +21,11 @@ context "with an order-level rule" do let(:rule) { SolidusFriendlyPromotions::Rules::Product.new(products: [shirt]) } - context "with an order level action" do - let(:calculator) { Spree::Calculator::FlatPercentItemTotal.new(preferred_flat_percent: 20) } - let(:action) { Spree::Promotion::Actions::CreateAdjustment.new(calculator: calculator) } - - it "creates one order-level adjustment" do - expect(order.adjustments.length).to eq(1) - expect(order.total).to eq(31.98) - expect(order.item_total).to eq(39.98) - # This is wrong! But order level adjustments can't work any other way - expect(order.item_total_before_tax).to eq(39.98) - expect(order.line_items.flat_map(&:adjustments)).to be_empty - end - end - context "with an line item level action" do let(:calculator) { Spree::Calculator::PercentOnLineItem.new(preferred_percent: 20) } - let(:action) { Spree::Promotion::Actions::CreateItemAdjustments.new(calculator: calculator) } + let(:action) { SolidusFriendlyPromotions::Actions::AdjustLineItem.new(calculator: calculator) } - it "creates one order-level adjustment" do + it "creates one line item level adjustment" do expect(order.adjustments).to be_empty expect(order.total).to eq(31.98) expect(order.item_total).to eq(39.98) @@ -50,27 +38,11 @@ context "with a line-item level rule" do let(:rule) { SolidusFriendlyPromotions::Rules::LineItemProduct.new(products: [shirt]) } - context "with an order level action" do - let(:calculator) { Spree::Calculator::FlatPercentItemTotal.new(preferred_flat_percent: 20) } - let(:action) { Spree::Promotion::Actions::CreateAdjustment.new(calculator: calculator) } - - it "creates one order-level adjustment" do - # Whoops - this works because line item level rules don't affect order-level actions :( - expect(order.adjustments.length).to eq(1) - expect(order.total).to eq(31.98) - expect(order.item_total).to eq(39.98) - # This is wrong! But order level adjustments can't work any other way - expect(order.item_total_before_tax).to eq(39.98) - expect(order.line_items.flat_map(&:adjustments)).to be_empty - end - end - context "with an line item level action" do let(:calculator) { Spree::Calculator::PercentOnLineItem.new(preferred_percent: 20) } - let(:action) { Spree::Promotion::Actions::CreateItemAdjustments.new(calculator: calculator) } + let(:action) { SolidusFriendlyPromotions::Actions::AdjustLineItem.new(calculator: calculator) } it "creates one line item level adjustment" do - pending expect(order.adjustments).to be_empty expect(order.total).to eq(35.98) expect(order.item_total).to eq(39.98) diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/actions/base_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/actions/base_spec.rb new file mode 100644 index 00000000000..7e967b4aef9 --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/actions/base_spec.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe SolidusFriendlyPromotions::Actions::Base do + it { is_expected.to respond_to :adjust } + it { is_expected.to respond_to :can_adjust? } + + describe "#can_adjust?" do + subject { described_class.new.can_adjust?(double) } + + it "raises a NotImplementedError" do + expect { subject }.to raise_exception(NotImplementedError) + end + end + + describe "#adjust" do + let(:variant) { create(:variant) } + let(:order) { create(:order) } + let(:adjustable) { Spree::LineItem.new(order: order, variant: variant, price: 10)} + let(:promotion) { Spree::Promotion.new(name: "20 Perzent off") } + let(:action) { described_class.new(promotion: promotion)} + before do + allow(action).to receive(:compute_amount).and_return(-1) + end + + subject { action.adjust(adjustable) } + + it "adds an adjustment to the adjustable" do + expect { subject }.to change { adjustable.adjustments.length }.by(1) + expect(adjustable.adjustments.first.label).to eq("Promotion (20 Perzent off)") + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/order_promotion_adjuster_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/order_promotion_adjuster_spec.rb new file mode 100644 index 00000000000..e69de29bb2d From 88a4e1c97cca15943e505ef9b57e54aee3b8d6ec Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 13 Jun 2023 09:14:16 +0200 Subject: [PATCH 188/834] Add order promotion adjuster spec --- .../order_promotion_adjuster_spec.rb | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/order_promotion_adjuster_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/order_promotion_adjuster_spec.rb index e69de29bb2d..75ac9adfa99 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/order_promotion_adjuster_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/order_promotion_adjuster_spec.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SolidusFriendlyPromotions::OrderPromotionAdjuster, type: :model do + let(:line_item) { create(:line_item) } + let(:order) { line_item.order } + let(:promotion) { create(:promotion, apply_automatically: true) } + let(:calculator) { Spree::Calculator::FlatPercentItemTotal.new(preferred_flat_percent: 10) } + + subject { described_class.new(order) } + + context "adjusting line items" do + let!(:action) { SolidusFriendlyPromotions::Actions::AdjustLineItem.create(promotion: promotion, calculator: calculator) } + let(:adjustable) { line_item } + + context "promotion with no rules" do + context "creates the adjustment" do + it "creates the adjustment" do + expect { + subject.call + }.to change { adjustable.adjustments.length }.by(1) + end + end + + context "for a non-sale promotion" do + let(:promotion) { create(:promotion, apply_automatically: false) } + + it "doesn't connect the promotion to the order" do + expect { + subject.call + }.to change { order.promotions.length }.by(0) + end + + it "doesn't create an adjustment" do + expect { + subject.call + }.to change { adjustable.adjustments.length }.by(0) + end + end + end + + context "promotion includes item involved" do + let!(:rule) { SolidusFriendlyPromotions::Rules::Product.create(products: [line_item.product], promotion: promotion) } + + context "creates the adjustment" do + it "creates the adjustment" do + expect { + subject.call + }.to change { adjustable.adjustments.length }.by(1) + end + end + end + + context "promotion has item total rule" do + let!(:rule) { SolidusFriendlyPromotions::Rules::ItemTotal.create(preferred_operator: 'gt', preferred_amount: 50, promotion: promotion) } + + before do + # Makes the order eligible for this promotion + order.item_total = 100 + order.save + end + + context "creates the adjustment" do + it "creates the adjustment" do + expect { + subject.call + }.to change { adjustable.adjustments.length }.by(1) + end + end + end + end +end From 2a8ca6f9307cbfc757db484959f26fe80f2d7d9d Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 6 Jun 2023 12:02:27 +0200 Subject: [PATCH 189/834] Replace OrderContents decorator with configurable class This also imports the Solidus specs and revealed an omission in the Actions base class. --- .../order_contents_decorator.rb | 16 - .../actions/base.rb | 1 + .../line_item_adjuster.rb | 1 + .../simple_order_contents.rb | 28 ++ .../simple_order_contents_spec.rb | 361 ++++++++++++++++++ friendly_promotions/spec/spec_helper.rb | 4 + 6 files changed, 395 insertions(+), 16 deletions(-) delete mode 100644 friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_contents_decorator.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/simple_order_contents.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/simple_order_contents_spec.rb diff --git a/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_contents_decorator.rb b/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_contents_decorator.rb deleted file mode 100644 index 029fa3447f4..00000000000 --- a/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_contents_decorator.rb +++ /dev/null @@ -1,16 +0,0 @@ -# frozen_string_literal: true - -module SolidusFriendlyPromotions - module OrderContentsDecorator - private - - def after_add_or_remove(line_item, options = {}) - shipment = options[:shipment] - shipment.present? ? shipment.update_amounts : order.ensure_updated_shipments - reload_totals - line_item - end - - Spree::OrderContents.prepend self - end -end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/actions/base.rb b/friendly_promotions/app/models/solidus_friendly_promotions/actions/base.rb index e26f14ab339..e30fd21c711 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/actions/base.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/actions/base.rb @@ -22,6 +22,7 @@ def adjust(adjustable) end || adjustable.adjustments.build(source: self) adjustment.label = adjustment_label(adjustable) adjustment.amount = compute_amount(adjustable) + adjustment.order = adjustable.order adjustment end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/line_item_adjuster.rb b/friendly_promotions/app/models/solidus_friendly_promotions/line_item_adjuster.rb index e60b92a2c71..6de84fa0e52 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/line_item_adjuster.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/line_item_adjuster.rb @@ -26,6 +26,7 @@ def call(line_item) line_item.promo_total = chosen_adjustments.sum(&:amount) line_item.adjustments = non_promotion_adjustments + chosen_adjustments + line_item end end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/simple_order_contents.rb b/friendly_promotions/app/models/solidus_friendly_promotions/simple_order_contents.rb new file mode 100644 index 00000000000..e2dabf06327 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/simple_order_contents.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + class SimpleOrderContents < Spree::OrderContents + + def update_cart(params) + if order.update(params) + unless order.completed? + order.line_items = order.line_items.select { |li| li.quantity > 0 } + order.ensure_updated_shipments + end + reload_totals + true + else + false + end + end + + private + + def after_add_or_remove(line_item, options = {}) + shipment = options[:shipment] + shipment.present? ? shipment.update_amounts : order.ensure_updated_shipments + reload_totals + line_item + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/simple_order_contents_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/simple_order_contents_spec.rb new file mode 100644 index 00000000000..a10b2396c3b --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/simple_order_contents_spec.rb @@ -0,0 +1,361 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SolidusFriendlyPromotions::SimpleOrderContents, type: :model do + let!(:store) { create :store } + let(:order) { create(:order) } + let(:variant) { create(:variant) } + let!(:stock_location) { variant.stock_locations.first } + let(:stock_location_2) { create(:stock_location) } + + subject(:order_contents) { described_class.new(order) } + + context "#add" do + context 'given quantity is not explicitly provided' do + it 'should add one line item' do + line_item = subject.add(variant) + expect(line_item.quantity).to eq(1) + expect(order.line_items.size).to eq(1) + end + end + + context 'given a shipment' do + let!(:shipment) { create(:shipment, order: order) } + + it "ensure shipment calls update_amounts instead of order calling ensure_updated_shipments" do + expect(subject.order).to_not receive(:ensure_updated_shipments) + expect(shipment).to receive(:update_amounts).at_least(:once) + subject.add(variant, 1, shipment: shipment) + end + + context "with quantity=1" do + it "creates correct inventory" do + subject.add(variant, 1, shipment: shipment) + expect(order.inventory_units.count).to eq(1) + end + end + + context "with quantity=2" do + it "creates correct inventory" do + subject.add(variant, 2, shipment: shipment) + expect(order.inventory_units.count).to eq(2) + end + end + + context "called multiple times" do + it "creates correct inventory" do + subject.add(variant, 1, shipment: shipment) + subject.add(variant, 1, shipment: shipment) + expect(order.inventory_units.count).to eq(2) + end + end + end + + context 'not given a shipment' do + it "ensures updated shipments" do + expect(subject.order).to receive(:ensure_updated_shipments) + subject.add(variant) + end + end + + it 'should add line item if one does not exist' do + line_item = subject.add(variant, 1) + expect(line_item.quantity).to eq(1) + expect(order.line_items.size).to eq(1) + end + + it 'should update line item if one exists' do + subject.add(variant, 1) + line_item = subject.add(variant, 1) + expect(line_item.quantity).to eq(2) + expect(order.line_items.size).to eq(1) + end + + it "should update order totals" do + expect(order.item_total.to_f).to eq(0.00) + expect(order.total.to_f).to eq(0.00) + + subject.add(variant, 1) + + expect(order.item_total.to_f).to eq(19.99) + expect(order.total.to_f).to eq(19.99) + end + + context "running promotions" do + let(:promotion) { create(:promotion, apply_automatically: true) } + let(:calculator) { Spree::Calculator::FlatRate.new(preferred_amount: 10) } + + before do + Spree::Config.promotion_adjuster_class = "SolidusFriendlyPromotions::OrderPromotionAdjuster" + Spree::Config.promotion_chooser_class = "SolidusFriendlyPromotions::PromotionAdjustmentChooser" + end + + context "one active line item promotion" do + let!(:action) { SolidusFriendlyPromotions::Actions::AdjustLineItem.create(promotion: promotion, calculator: calculator) } + + it "creates valid discount on order" do + subject.add(variant, 1) + subject.order.line_item_adjustments.reset + expect(subject.order.line_item_adjustments.to_a.sum(&:amount)).not_to eq 0 + end + + context "discount changes order total" do + before { subject.add(variant, 1) } + it { expect(subject.order.total).not_to eq variant.price } + end + end + end + + describe 'tax calculations' do + let!(:zone) { create(:global_zone) } + let!(:tax_rate) do + create(:tax_rate, zone: zone, tax_categories: [variant.tax_category]) + end + + context 'when the order has a taxable address' do + before do + expect(order.tax_address.country_id).to be_present + end + + it 'creates a tax adjustment' do + order_contents.add(variant) + line_item = order.find_line_item_by_variant(variant) + expect(line_item.adjustments.tax.count).to eq(1) + end + end + + context 'when the order does not have a taxable address' do + before do + order.update!(ship_address: nil, bill_address: nil) + expect(order.tax_address.country_id).to be_nil + end + + it 'creates a tax adjustment' do + order_contents.add(variant) + line_item = order.find_line_item_by_variant(variant) + expect(line_item.adjustments.tax.count).to eq(0) + end + end + end + end + + context "#remove" do + context "given an invalid variant" do + it "raises an exception" do + expect { + subject.remove(variant, 1) + }.to raise_error(ActiveRecord::RecordNotFound) + end + end + + context 'given quantity is not explicitly provided' do + it 'should remove one line item' do + line_item = subject.add(variant, 3) + subject.remove(variant) + + expect(line_item.reload.quantity).to eq(2) + end + end + + context 'given a shipment' do + it "ensure shipment calls update_amounts instead of order calling ensure_updated_shipments" do + subject.add(variant, 1) + shipment = create(:shipment) + expect(subject.order).to_not receive(:ensure_updated_shipments) + expect(shipment).to receive(:update_amounts) + subject.remove(variant, 1, shipment: shipment) + end + end + + context 'not given a shipment' do + it "ensures updated shipments" do + subject.add(variant, 1) + expect(subject.order).to receive(:ensure_updated_shipments) + subject.remove(variant) + end + end + + it 'should reduce line_item quantity if quantity is less the line_item quantity' do + line_item = subject.add(variant, 3) + subject.remove(variant, 1) + + expect(line_item.reload.quantity).to eq(2) + end + + it 'should remove line_item if quantity matches line_item quantity' do + subject.add(variant, 1) + subject.remove(variant, 1) + + expect(order.reload.find_line_item_by_variant(variant)).to be_nil + end + + it 'should remove line_item if quantity is greater than line_item quantity' do + subject.add(variant, 1) + subject.remove(variant, 2) + + expect(order.reload.find_line_item_by_variant(variant)).to be_nil + end + + it "should update order totals" do + expect(order.item_total.to_f).to eq(0.00) + expect(order.total.to_f).to eq(0.00) + + subject.add(variant, 2) + + expect(order.item_total.to_f).to eq(39.98) + expect(order.total.to_f).to eq(39.98) + + subject.remove(variant, 1) + expect(order.item_total.to_f).to eq(19.99) + expect(order.total.to_f).to eq(19.99) + end + end + + context "#remove_line_item" do + context 'given a shipment' do + it "ensure shipment calls update_amounts instead of order calling ensure_updated_shipments" do + line_item = subject.add(variant, 1) + shipment = create(:shipment) + expect(subject.order).to_not receive(:ensure_updated_shipments) + expect(shipment).to receive(:update_amounts) + subject.remove_line_item(line_item, shipment: shipment) + end + end + + context 'not given a shipment' do + it "ensures updated shipments" do + line_item = subject.add(variant, 1) + expect(subject.order).to receive(:ensure_updated_shipments) + subject.remove_line_item(line_item) + end + end + + it 'should remove line_item' do + line_item = subject.add(variant, 1) + subject.remove_line_item(line_item) + + expect(order.reload.line_items).to_not include(line_item) + end + + it "should update order totals" do + expect(order.item_total.to_f).to eq(0.00) + expect(order.total.to_f).to eq(0.00) + + line_item = subject.add(variant, 2) + + expect(order.item_total.to_f).to eq(39.98) + expect(order.total.to_f).to eq(39.98) + + subject.remove_line_item(line_item) + expect(order.item_total.to_f).to eq(0.00) + expect(order.total.to_f).to eq(0.00) + end + end + + context "update cart" do + let!(:shirt) { subject.add variant, 1 } + + let(:params) do + { line_items_attributes: { + "0" => { id: shirt.id, quantity: 3 } + } } + end + + it "changes item quantity" do + subject.update_cart params + expect(shirt.reload.quantity).to eq 3 + end + + it "updates order totals" do + expect { + subject.update_cart params + }.to change { subject.order.total } + end + + context "submits item quantity 0" do + let(:params) do + { line_items_attributes: { + "0" => { id: shirt.id, quantity: 0 } + } } + end + + it "removes item from order" do + expect { + subject.update_cart params + }.to change { subject.order.line_items.count } + end + end + + it "ensures updated shipments" do + expect(subject.order).to receive(:ensure_updated_shipments) + subject.update_cart params + end + end + + context "completed order" do + let(:order) do + Spree::Order.create!( + state: 'complete', + completed_at: Time.current, + email: "test@example.com" + ) + end + + before { order.shipments.create! stock_location_id: variant.stock_location_ids.first } + + it "updates order payment state" do + expect { + subject.add variant + }.to change { order.payment_state } + + expect { + subject.remove variant + }.to change { order.payment_state } + end + end + + describe "#approve" do + context 'when a name is supplied' do + it 'approves the order' do + order.contents.approve(name: 'Jordan') + expect(order.approver).to be_nil + expect(order.approver_name).to eq('Jordan') + expect(order.approved_at).to be_present + expect(order.approved?).to be_truthy + end + end + + context 'when a user is supplied' do + let(:user) { create(:user) } + + it 'approves the order' do + order.contents.approve(user: user) + expect(order.approver).to eq(user) + expect(order.approver_name).to be_nil + expect(order.approved_at).to be_present + expect(order.approved?).to be_truthy + end + end + + context 'when a user and a name are supplied' do + let(:user) { create(:user) } + + it 'approves the order' do + order.contents.approve(user: user, name: 'Jordan') + expect(order.approver).to eq(user) + expect(order.approver_name).to eq('Jordan') + expect(order.approved_at).to be_present + expect(order.approved?).to be_truthy + end + end + + context 'when neither a user nor a name are supplied' do + it 'raises' do + expect { + order.contents.approve + }.to raise_error(ArgumentError, 'user or name must be specified') + end + end + end +end diff --git a/friendly_promotions/spec/spec_helper.rb b/friendly_promotions/spec/spec_helper.rb index 0c2fae49a39..d17e8ccd2b4 100644 --- a/friendly_promotions/spec/spec_helper.rb +++ b/friendly_promotions/spec/spec_helper.rb @@ -32,4 +32,8 @@ if Spree.solidus_gem_version < Gem::Version.new("2.11") config.extend Spree::TestingSupport::AuthorizationHelpers::Request, type: :system end + + config.before do + Spree::Config.order_contents_class = "SolidusFriendlyPromotions::SimpleOrderContents" + end end From a846ff323d1f478a9841227d487cfc08fd9b8f2c Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 13 Jun 2023 09:10:19 +0200 Subject: [PATCH 190/834] Set order when creating adjustments We don't have to re-set the order every time an adjustment changes, it's enough the first time around. --- .../app/models/solidus_friendly_promotions/actions/base.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/actions/base.rb b/friendly_promotions/app/models/solidus_friendly_promotions/actions/base.rb index e30fd21c711..bd1574b1715 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/actions/base.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/actions/base.rb @@ -19,10 +19,9 @@ def can_adjust?(object) def adjust(adjustable) adjustment = adjustable.adjustments.detect do |adjustment| adjustment.source == self - end || adjustable.adjustments.build(source: self) + end || adjustable.adjustments.build(source: self, order: adjustable.order) adjustment.label = adjustment_label(adjustable) adjustment.amount = compute_amount(adjustable) - adjustment.order = adjustable.order adjustment end From 8e3e56d4c5c0e0d8e5759110e7a05952a06345a5 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 13 Jun 2023 09:11:26 +0200 Subject: [PATCH 191/834] Add a promotion chooser class configuration option The interface here is slightly different than the one in core Solidus. This is because we want the class to work in-memory rather than at the DB level. --- .../line_item_adjuster.rb | 2 +- .../shipment_adjuster.rb | 4 +++- .../configuration.rb | 6 ++---- .../configuration_spec.rb | 21 +++++++++++++++++++ 4 files changed, 27 insertions(+), 6 deletions(-) create mode 100644 friendly_promotions/spec/lib/solidus_friendly_promotions/configuration_spec.rb diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/line_item_adjuster.rb b/friendly_promotions/app/models/solidus_friendly_promotions/line_item_adjuster.rb index 6de84fa0e52..ef748830aa2 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/line_item_adjuster.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/line_item_adjuster.rb @@ -22,7 +22,7 @@ def call(line_item) end end - chosen_adjustments = Spree::Config.promotion_chooser_class.new(line_item).call(possible_adjustments) + chosen_adjustments = SolidusFriendlyPromotions.config.promotion_chooser_class.new(line_item).call(possible_adjustments) line_item.promo_total = chosen_adjustments.sum(&:amount) line_item.adjustments = non_promotion_adjustments + chosen_adjustments diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/shipment_adjuster.rb b/friendly_promotions/app/models/solidus_friendly_promotions/shipment_adjuster.rb index 5f43851ac97..73bab73e396 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/shipment_adjuster.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/shipment_adjuster.rb @@ -23,9 +23,11 @@ def call(shipment) end end - chosen_adjustments = Spree::Config.promotion_chooser_class.new(shipment).call(possible_adjustments) + chosen_adjustments = SolidusFriendlyPromotions.config.promotion_chooser_class.new(shipment).call(possible_adjustments) + shipment.promo_total = chosen_adjustments.sum(&:amount) shipment.adjustments = non_promotion_adjustments + chosen_adjustments + shipment end end end diff --git a/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb b/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb index bb9d78a8c19..2813c447f15 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb @@ -1,10 +1,8 @@ # frozen_string_literal: true module SolidusFriendlyPromotions - class Configuration - # Define here the settings for this extension, e.g.: - # - # attr_accessor :my_setting + class Configuration < Spree::Preferences::Configuration + class_name_attribute :promotion_chooser_class, default: "SolidusFriendlyPromotions::PromotionAdjustmentChooser" end class << self diff --git a/friendly_promotions/spec/lib/solidus_friendly_promotions/configuration_spec.rb b/friendly_promotions/spec/lib/solidus_friendly_promotions/configuration_spec.rb new file mode 100644 index 00000000000..92df9f120e3 --- /dev/null +++ b/friendly_promotions/spec/lib/solidus_friendly_promotions/configuration_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe SolidusFriendlyPromotions::Configuration do + subject { SolidusFriendlyPromotions.config } + + it "has a nice accessor" do + expect(subject).to be_a(described_class) + end + + it "is an instance of Spree::Configuration" do + expect(subject).to be_a(Spree::Preferences::Configuration) + end + + describe ".promotion_chooser_class" do + it "is the promotion chooser" do + expect(subject.promotion_chooser_class).to eq(SolidusFriendlyPromotions::PromotionAdjustmentChooser) + end + end +end From 4b9f2b78ce56c7e3bb0f17a6421fdcc86ac52b5d Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 13 Jun 2023 09:24:45 +0200 Subject: [PATCH 192/834] Require at least Solidus 4 We're not developing for the past here. --- friendly_promotions/solidus_friendly_promotions.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/friendly_promotions/solidus_friendly_promotions.gemspec b/friendly_promotions/solidus_friendly_promotions.gemspec index 5180cdf27a8..d066341da4e 100644 --- a/friendly_promotions/solidus_friendly_promotions.gemspec +++ b/friendly_promotions/solidus_friendly_promotions.gemspec @@ -28,7 +28,7 @@ Gem::Specification.new do |spec| spec.executables = files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] - spec.add_dependency "solidus_core", [">= 3.0.0", "< 5"] + spec.add_dependency "solidus_core", [">= 4.0.0", "< 5"] spec.add_dependency "solidus_support", "~> 0.5" spec.add_development_dependency "solidus_dev_support", "~> 2.6" From 7bf478b6c211f7557a09356968c2aa401dfb61ff Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 13 Jun 2023 11:10:57 +0200 Subject: [PATCH 193/834] Remove Solidus promotions tab from menu --- .../config/initializers/solidus_friendly_promotions.rb | 1 + 1 file changed, 1 insertion(+) create mode 100644 friendly_promotions/config/initializers/solidus_friendly_promotions.rb diff --git a/friendly_promotions/config/initializers/solidus_friendly_promotions.rb b/friendly_promotions/config/initializers/solidus_friendly_promotions.rb new file mode 100644 index 00000000000..472576bf09f --- /dev/null +++ b/friendly_promotions/config/initializers/solidus_friendly_promotions.rb @@ -0,0 +1 @@ +Spree::Backend::Config.menu_items.delete_if { |menu_item| menu_item.url == :admin_promotions_path } From 388842173ca061249714f42d333f3494155636ed Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 13 Jun 2023 13:50:38 +0200 Subject: [PATCH 194/834] Import TieredPercent Calculator from Solidus --- .../calculators/tiered_percent.rb | 64 ++++++ .../calculators/tiered_percent_spec.rb | 197 ++++++++++++++++++ .../calculator_shared_examples.rb | 10 + 3 files changed, 271 insertions(+) create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_percent.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_percent_spec.rb create mode 100644 friendly_promotions/spec/shared_examples/calculator_shared_examples.rb diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_percent.rb b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_percent.rb new file mode 100644 index 00000000000..9ab212f24bf --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_percent.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +require_dependency 'spree/calculator' + +module SolidusFriendlyPromotions + module Calculators + class TieredPercent < Spree::Calculator + preference :base_percent, :decimal, default: 0 + preference :tiers, :hash, default: {} + preference :currency, :string, default: -> { Spree::Config[:currency] } + + before_validation do + # Convert tier values to decimals. Strings don't do us much good. + if preferred_tiers.is_a?(Hash) + self.preferred_tiers = preferred_tiers.map do |key, value| + [cast_to_d(key.to_s), cast_to_d(value.to_s)] + end.to_h + end + end + + validates :preferred_base_percent, numericality: { + greater_than_or_equal_to: 0, + less_than_or_equal_to: 100 + } + validate :preferred_tiers_content + + def compute(object) + order = object.is_a?(Spree::Order) ? object : object.order + + _base, percent = preferred_tiers.sort.reverse.detect do |value, _| + order.item_total >= value + end + + if preferred_currency.casecmp(order.currency).zero? + currency_exponent = ::Money::Currency.find(preferred_currency).exponent + (object.amount * (percent || preferred_base_percent) / 100).round(currency_exponent) + else + 0 + end + end + + private + + def cast_to_d(value) + value.to_s.to_d + rescue ArgumentError + BigDecimal(0) + end + + def preferred_tiers_content + if preferred_tiers.is_a? Hash + unless preferred_tiers.keys.all?{ |key| key.is_a?(Numeric) && key > 0 } + errors.add(:base, :keys_should_be_positive_number) + end + unless preferred_tiers.values.all?{ |key| key.is_a?(Numeric) && key >= 0 && key <= 100 } + errors.add(:base, :values_should_be_percent) + end + else + errors.add(:preferred_tiers, :should_be_hash) + end + end + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_percent_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_percent_spec.rb new file mode 100644 index 00000000000..7d8cacde5ee --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_percent_spec.rb @@ -0,0 +1,197 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'shared_examples/calculator_shared_examples' + +RSpec.describe SolidusFriendlyPromotions::Calculators::TieredPercent, type: :model do + let(:calculator) { described_class.new } + + it_behaves_like 'a calculator with a description' + + describe "#valid?" do + subject { calculator.valid? } + + context "when base percent is less than zero" do + before { calculator.preferred_base_percent = -1 } + it { is_expected.to be false } + end + + context "when base percent is greater than 100" do + before { calculator.preferred_base_percent = 110 } + it { is_expected.to be false } + end + + context "when tiers is a hash" do + context "and the key is not a positive number" do + before { calculator.preferred_tiers = { "nope" => 20 } } + it { is_expected.to be false } + end + + context "and one of the values is not a percent" do + before { calculator.preferred_tiers = { 10 => 110 } } + it { is_expected.to be false } + end + + context "and the key is an integer" do + before { calculator.preferred_tiers = { 20 => 20 } } + it "converts successfully" do + is_expected.to be true + expect(calculator.preferred_tiers).to eq({ BigDecimal('20') => BigDecimal('20') }) + end + end + + context "and the key is a float" do + before { calculator.preferred_tiers = { 20.5 => 20.5 } } + it "converts successfully" do + is_expected.to be true + expect(calculator.preferred_tiers).to eq({ BigDecimal('20.5') => BigDecimal('20.5') }) + end + end + + context "and the key is a string number" do + before { calculator.preferred_tiers = { "20" => 20 } } + it "converts successfully" do + is_expected.to be true + expect(calculator.preferred_tiers).to eq({ BigDecimal('20') => BigDecimal('20') }) + end + end + + context "and the key is a numeric string with spaces" do + before { calculator.preferred_tiers = { " 20 " => 20 } } + it "converts successfully" do + is_expected.to be true + expect(calculator.preferred_tiers).to eq({ BigDecimal('20') => BigDecimal('20') }) + end + end + + context "and the key is a string number with decimals" do + before { calculator.preferred_tiers = { "20.5" => "20.5" } } + it "converts successfully" do + is_expected.to be true + expect(calculator.preferred_tiers).to eq({ BigDecimal('20.5') => BigDecimal('20.5') }) + end + end + end + end + + describe "#compute" do + let(:order) do + create( + :order_with_line_items, + line_items_count: line_item_count, + line_items_price: price + ) + end + let(:price) { 10 } + let(:preferred_currency) { "USD" } + + before do + calculator.preferred_base_percent = 10 + calculator.preferred_tiers = { + 20 => 15, + 30 => 20 + } + calculator.preferred_currency = preferred_currency + end + + context "with a line item" do + let(:line_item) { order.line_items.first } + subject { calculator.compute(line_item) } + + context "for multiple line items" do + context "when amount falls within the first tier" do + let(:line_item_count) { 1 } + it { is_expected.to eq 1.0 } + end + + context "when amount falls within the second tier" do + let(:line_item_count) { 2 } + it { is_expected.to eq 1.5 } + end + + context "when amount falls within the third tier" do + let(:line_item_count) { 3 } + it { is_expected.to eq 2.0 } + end + end + + context "for a single line item" do + let(:line_item_count) { 1 } + + context "when amount falls within the first tier" do + let(:price) { 10 } + it { is_expected.to eq 1.0 } + end + + context "when amount falls within the second tier" do + let(:price) { 20 } + it { is_expected.to eq 3.0 } + end + + context "when amount falls within the third tier" do + let(:price) { 30 } + it { is_expected.to eq 6.0 } + end + end + + context "when the order's currency does not match the calculator" do + let(:preferred_currency) { "JPY" } + let(:line_item_count) { 1 } + let(:price) { 15 } + it { is_expected.to eq 0 } + + it "rounds based on currency" do + allow(order).to receive_messages currency: "JPY" + expect(subject).to eq(2) + end + end + end + + context "with an order" do + subject { calculator.compute(order) } + + let(:line_item_count) { 1 } + + context "for multiple line items" do + context "when amount falls within the first tier" do + let(:line_item_count) { 1 } + it { is_expected.to eq 1.0 } + end + + context "when amount falls within the second tier" do + let(:line_item_count) { 2 } + it { is_expected.to eq 3.0 } + end + + context "when amount falls within the third tier" do + let(:line_item_count) { 3 } + it { is_expected.to eq 6.0 } + end + end + + context "for a single line item" do + let(:line_item_count) { 1 } + + context "when amount falls within the first tier" do + let(:price) { 10 } + it { is_expected.to eq 1.0 } + end + + context "when amount falls within the second tier" do + let(:price) { 20 } + it { is_expected.to eq 3.0 } + end + + context "when amount falls within the third tier" do + let(:price) { 30 } + it { is_expected.to eq 6.0 } + end + end + + context "when the order's currency does not match the calculator" do + let(:preferred_currency) { "CAD" } + it { is_expected.to eq 0 } + end + end + end +end diff --git a/friendly_promotions/spec/shared_examples/calculator_shared_examples.rb b/friendly_promotions/spec/shared_examples/calculator_shared_examples.rb new file mode 100644 index 00000000000..c41b3c7e4e6 --- /dev/null +++ b/friendly_promotions/spec/shared_examples/calculator_shared_examples.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +RSpec.shared_examples_for 'a calculator with a description' do + describe ".description" do + subject { described_class.description } + it "has a description" do + expect(subject.size).to be > 0 + end + end +end From b60b51306f704503263f9c1e9e73d04d12cf8abc Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 13 Jun 2023 14:05:39 +0200 Subject: [PATCH 195/834] Adapt tiered calculator spec to shipments We want to be able to calculate shipments with this calculator, not orders. --- .../calculators/tiered_percent.rb | 6 ++++-- .../calculators/tiered_percent_spec.rb | 13 +++++++++---- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_percent.rb b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_percent.rb index 9ab212f24bf..7e3f75d1005 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_percent.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_percent.rb @@ -24,8 +24,8 @@ class TieredPercent < Spree::Calculator } validate :preferred_tiers_content - def compute(object) - order = object.is_a?(Spree::Order) ? object : object.order + def compute_item(object) + order = object.order _base, percent = preferred_tiers.sort.reverse.detect do |value, _| order.item_total >= value @@ -38,6 +38,8 @@ def compute(object) 0 end end + alias_method :compute_shipment, :compute_item + alias_method :compute_line_item, :compute_item private diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_percent_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_percent_spec.rb index 7d8cacde5ee..02db8f2bfbe 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_percent_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_percent_spec.rb @@ -147,8 +147,11 @@ end end - context "with an order" do - subject { calculator.compute(order) } + context "with a shipment" do + let(:shipment) { Spree::Shipment.new(order: order, amount: shipping_cost) } + let(:shipping_cost) { 10 } + + subject { calculator.compute(shipment) } let(:line_item_count) { 1 } @@ -160,12 +163,12 @@ context "when amount falls within the second tier" do let(:line_item_count) { 2 } - it { is_expected.to eq 3.0 } + it { is_expected.to eq 1.5 } end context "when amount falls within the third tier" do let(:line_item_count) { 3 } - it { is_expected.to eq 6.0 } + it { is_expected.to eq 2.0 } end end @@ -179,11 +182,13 @@ context "when amount falls within the second tier" do let(:price) { 20 } + let(:shipping_cost) { 20 } it { is_expected.to eq 3.0 } end context "when amount falls within the third tier" do let(:price) { 30 } + let(:shipping_cost) { 30 } it { is_expected.to eq 6.0 } end end From f6b029ebe1fd6549a6c57902c771dc752ef08e73 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 14 Jun 2023 11:57:53 +0200 Subject: [PATCH 196/834] Add TieredFlatRate calculator Interestingly, this calculator works differently to the `TieredPercent` calculator: `TieredPercent` gets the tier from the order's item total, where as this calculator gets it from the line item or shipment being calculated. --- .../calculators/tiered_flat_rate.rb | 56 ++++++++ .../calculators/tiered_flat_rate_spec.rb | 126 ++++++++++++++++++ 2 files changed, 182 insertions(+) create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_flat_rate.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_flat_rate_spec.rb diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_flat_rate.rb b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_flat_rate.rb new file mode 100644 index 00000000000..77e04246fe7 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_flat_rate.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +require_dependency 'spree/calculator' + +module SolidusFriendlyPromotions + module Calculators + class TieredFlatRate < Spree::Calculator + preference :base_amount, :decimal, default: 0 + preference :tiers, :hash, default: {} + preference :currency, :string, default: -> { Spree::Config[:currency] } + + before_validation do + # Convert tier values to decimals. Strings don't do us much good. + if preferred_tiers.is_a?(Hash) + self.preferred_tiers = preferred_tiers.map do |key, value| + [cast_to_d(key.to_s), cast_to_d(value.to_s)] + end.to_h + end + end + + validate :preferred_tiers_content + + def compute_item(object) + _base, amount = preferred_tiers.sort.reverse.detect do |value, _| + object.amount >= value + end + + if preferred_currency.casecmp(object.currency).zero? + amount || preferred_base_amount + else + 0 + end + end + alias_method :compute_shipment, :compute_item + alias_method :compute_line_item, :compute_item + + private + + def cast_to_d(value) + value.to_s.to_d + rescue ArgumentError + BigDecimal(0) + end + + def preferred_tiers_content + if preferred_tiers.is_a? Hash + unless preferred_tiers.keys.all?{ |key| key.is_a?(Numeric) && key > 0 } + errors.add(:base, :keys_should_be_positive_number) + end + else + errors.add(:preferred_tiers, :should_be_hash) + end + end + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_flat_rate_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_flat_rate_spec.rb new file mode 100644 index 00000000000..c42b5c34cf4 --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_flat_rate_spec.rb @@ -0,0 +1,126 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'shared_examples/calculator_shared_examples' + +RSpec.describe SolidusFriendlyPromotions::Calculators::TieredFlatRate, type: :model do + let(:calculator) { described_class.new } + + it_behaves_like 'a calculator with a description' + + describe "#valid?" do + subject { calculator.valid? } + context "when tiers is a hash" do + context "and the key is not a positive number" do + before { calculator.preferred_tiers = { "nope" => 20 } } + it { is_expected.to be false } + end + + context "and the key is an integer" do + before { calculator.preferred_tiers = { 20 => 20 } } + it "converts successfully" do + is_expected.to be true + expect(calculator.preferred_tiers).to eq({ BigDecimal('20') => BigDecimal('20') }) + end + end + + context "and the key is a float" do + before { calculator.preferred_tiers = { 20.5 => 20.5 } } + it "converts successfully" do + is_expected.to be true + expect(calculator.preferred_tiers).to eq({ BigDecimal('20.5') => BigDecimal('20.5') }) + end + end + + context "and the key is a string number" do + before { calculator.preferred_tiers = { "20" => 20 } } + it "converts successfully" do + is_expected.to be true + expect(calculator.preferred_tiers).to eq({ BigDecimal('20') => BigDecimal('20') }) + end + end + + context "and the key is a numeric string with spaces" do + before { calculator.preferred_tiers = { " 20 " => 20 } } + it "converts successfully" do + is_expected.to be true + expect(calculator.preferred_tiers).to eq({ BigDecimal('20') => BigDecimal('20') }) + end + end + + context "and the key is a string number with decimals" do + before { calculator.preferred_tiers = { "20.5" => "20.5" } } + it "converts successfully" do + is_expected.to be true + expect(calculator.preferred_tiers).to eq({ BigDecimal('20.5') => BigDecimal('20.5') }) + end + end + end + end + + describe "#compute" do + let(:order) do + create( + :order_with_line_items, + line_items_count: 1, + line_items_price: amount + ) + end + let(:line_item) { order.line_items.first } + let(:preferred_currency) { "USD" } + + before do + calculator.preferred_base_amount = 10 + calculator.preferred_tiers = { + 100 => 15, + 200 => 20 + } + calculator.preferred_currency = preferred_currency + end + + subject { calculator.compute(line_item) } + + context "when amount falls within the first tier" do + let(:amount) { 50 } + it { is_expected.to eq 10 } + end + + context "when amount falls within the second tier" do + let(:amount) { 150 } + it { is_expected.to eq 15 } + end + + context "when the order's currency does not match the calculator" do + let(:preferred_currency) { "CAD" } + let(:amount) { 50 } + it { is_expected.to eq 0 } + end + + context "with a shipment" do + let(:shipment) { Spree::Shipment.new(order: order, amount: shipping_cost) } + let(:amount) { 10 } + + subject { calculator.compute(shipment) } + + let(:line_item_count) { 1 } + + context "for multiple line items" do + context "when amount falls within the first tier" do + let(:shipping_cost) { 110 } + it { is_expected.to eq 15 } + end + + context "when amount falls within the second tier" do + let(:shipping_cost) { 210 } + it { is_expected.to eq 20 } + end + + context "when the order's currency does not match the calculator" do + let(:preferred_currency) { "CAD" } + let(:shipping_cost) { 110 } + it { is_expected.to eq 0 } + end + end + end + end +end From 7084f89ee8bba7a428ab12c90d49229eab39c0bf Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 14 Jun 2023 12:15:17 +0200 Subject: [PATCH 197/834] Add "Percent" calculator Over time, it turned out that Solidus core has two calculators doing the same thing, `FlatPercentItemTotal` as well as `PercentOnLineItem`. This calculator has all the features of both (except for the protection against overpromoting items, which the adjuster classes do). --- .../calculators/percent.rb | 17 ++++++++++++++++ .../calculators/percent_spec.rb | 20 +++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/calculators/percent.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/calculators/percent_spec.rb diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/percent.rb b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/percent.rb new file mode 100644 index 00000000000..1ab796788e9 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/percent.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require_dependency 'spree/calculator' + +module SolidusFriendlyPromotions + module Calculators + class Percent < Spree::Calculator + preference :percent, :decimal, default: 0 + + def compute(object) + preferred_currency = object.order.currency + currency_exponent = ::Money::Currency.find(preferred_currency).exponent + (object.amount * preferred_percent / 100).round(currency_exponent) + end + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/percent_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/percent_spec.rb new file mode 100644 index 00000000000..78ce5c5e59a --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/percent_spec.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'shared_examples/calculator_shared_examples' + +RSpec.describe SolidusFriendlyPromotions::Calculators::Percent, type: :model do + context "compute" do + let(:currency) { "USD" } + let(:order) { double(currency: currency) } + let(:line_item) { double("LineItem", amount: 100, order: order) } + + before { subject.preferred_percent = 15 } + + it "computes based on item price and quantity" do + expect(subject.compute(line_item)).to eq 15 + end + end + + it_behaves_like 'a calculator with a description' +end From 4745ccea47517f27f9133a95eab19fecbb4d6b8e Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 14 Jun 2023 13:09:00 +0200 Subject: [PATCH 198/834] Add Flat Rate calculator --- .../calculators/flat_rate.rb | 21 ++++++++ .../calculators/flat_rate_spec.rb | 49 +++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/calculators/flat_rate.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/calculators/flat_rate_spec.rb diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/flat_rate.rb b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/flat_rate.rb new file mode 100644 index 00000000000..0b6bab74482 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/flat_rate.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require_dependency 'spree/calculator' + +module SolidusFriendlyPromotions + module Calculators + class FlatRate < Spree::Calculator + preference :amount, :decimal, default: 0 + preference :currency, :string, default: ->{ Spree::Config[:currency] } + + def compute(object = nil) + currency = object.order.currency + if object && preferred_currency.casecmp(currency).zero? + preferred_amount + else + 0 + end + end + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/flat_rate_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/flat_rate_spec.rb new file mode 100644 index 00000000000..d893989e153 --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/flat_rate_spec.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'shared_examples/calculator_shared_examples' + +RSpec.describe SolidusFriendlyPromotions::Calculators::FlatRate, type: :model do + it_behaves_like 'a calculator with a description' + + let(:calculator) do + described_class.new( + preferred_amount: preferred_amount, + preferred_currency: preferred_currency + ) + end + let(:order) { mock_model(Spree::Order, currency: order_currency) } + let(:line_item) { mock_model(Spree::LineItem, order: order) } + + subject { calculator.compute(line_item) } + + context "compute" do + describe "when preferred currency matches order" do + let(:preferred_currency) { "GBP" } + let(:order_currency) { "GBP" } + let(:preferred_amount) { 25 } + it { is_expected.to eq(25.0) } + end + + describe "when preferred currency does not match order" do + let(:preferred_currency) { "GBP" } + let(:order_currency) { "USD" } + let(:preferred_amount) { 25 } + it { is_expected.to be_zero } + end + + describe "when preferred currency does not match order" do + let(:preferred_currency) { "" } + let(:order_currency) { "USD" } + let(:preferred_amount) { 25 } + it { is_expected.to be_zero } + end + + describe "when preferred currency and order currency use different casing" do + let(:preferred_currency) { "gbP" } + let(:order_currency) { "GBP" } + let(:preferred_amount) { 25 } + it { is_expected.to eq(25.0) } + end + end +end From ef36907e7e8f3c227fb8169caf8d66a01b5cd94a Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 14 Jun 2023 14:16:26 +0200 Subject: [PATCH 199/834] Add Distributed Amounts Calculator --- .../calculators/distributed_amount.rb | 38 +++++++++++ .../calculators/distributed_amount_spec.rb | 66 +++++++++++++++++++ 2 files changed, 104 insertions(+) create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/calculators/distributed_amount.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/calculators/distributed_amount_spec.rb diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/distributed_amount.rb b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/distributed_amount.rb new file mode 100644 index 00000000000..5403d548e74 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/distributed_amount.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require_dependency 'spree/calculator' + +# This is a calculator for line item adjustment actions. It accepts a line item +# and calculates its weighted adjustment amount based on the value of the +# preferred amount and the price of the other line items. More expensive line +# items will receive a greater share of the preferred amount. +module SolidusFriendlyPromotions + module Calculators + class DistributedAmount < Spree::Calculator + preference :amount, :decimal, default: 0 + preference :currency, :string, default: -> { Spree::Config[:currency] } + + def compute_line_item(line_item) + return 0 unless line_item + return 0 unless preferred_currency.casecmp(line_item.currency).zero? + distributable_line_items = eligible_line_items(line_item.order) + return 0 unless line_item.in?(distributable_line_items) + Spree::DistributedAmountsHandler.new( + distributable_line_items, + preferred_amount + ).amount(line_item) + end + + private + + def eligible_line_items(order) + order.line_items.reject do |line_item| + SolidusFriendlyPromotions::PromotionEligibility.new( + promotable: line_item, + possible_promotions: [calculable.promotion] + ).call.empty? + end + end + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/distributed_amount_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/distributed_amount_spec.rb new file mode 100644 index 00000000000..3d1eeed0dbd --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/distributed_amount_spec.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'shared_examples/calculator_shared_examples' + +RSpec.describe SolidusFriendlyPromotions::Calculators::DistributedAmount, type: :model do + let(:calculator) { described_class.new(preferred_amount: 15, preferred_currency: currency) } + let!(:promotion) { create :promotion, apply_automatically: true, name: '15 spread', promotion_actions: [action], promotion_rules: rules } + let(:rules) { [] } + let(:action) { SolidusFriendlyPromotions::Actions::AdjustLineItem.create(calculator: calculator) } + let(:order) { create(:order_with_line_items, line_items_attributes: line_items_attributes) } + let(:currency) { "USD" } + + before do + Spree::Config.promotion_adjuster_class = "SolidusFriendlyPromotions::OrderPromotionAdjuster" + Spree::Config.promotion_chooser_class = "SolidusFriendlyPromotions::PromotionAdjustmentChooser" + end + + context 'applied to an order' do + let(:line_items_attributes) { [{ price: 20 }, { price: 30 }, { price: 100 }] } + before do + order.recalculate + end + + it 'correctly distributes the entire discount' do + expect(order.promo_total).to eq(-15) + expect(order.line_items.map(&:adjustment_total)).to eq([-2, -3, -10]) + end + + context 'with product promotion rule' do + let(:first_product) { order.line_items.first.product } + let(:rules) do + [ + SolidusFriendlyPromotions::Rules::LineItemProduct.new(products: [first_product]) + ] + end + + before do + order.recalculate + end + + it 'still distributes the entire discount' do + expect(order.promo_total).to eq(-15) + expect(order.line_items.map(&:adjustment_total)).to eq([-15, 0, 0]) + end + end + end + + describe "#compute_line_item" do + let(:line_items_attributes) { [{ price: 50 }, { price: 50 }, { price: 50 }] } + + subject { calculator.compute_line_item(order.line_items.first) } + + + context "when the order currency matches the store's currency" do + let(:currency) { "USD" } + it { is_expected.to eq 5 } + it { is_expected.to be_a BigDecimal } + end + + context "when the order currency does not match the store's currency" do + let(:currency) { "CAD" } + it { is_expected.to eq 0 } + end + end +end From de1adc26155f728ab37e8091566581eafafd70f5 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 14 Jun 2023 14:22:14 +0200 Subject: [PATCH 200/834] Add DistributedAmountsHandler This class is only used for the DistributedAmountsCalculator which we have also imported. --- .../calculators/distributed_amount.rb | 2 +- .../distributed_amounts_handler.rb | 43 ++++++++++ .../distributed_amounts_handler_spec.rb | 83 +++++++++++++++++++ 3 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/distributed_amounts_handler.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/distributed_amounts_handler_spec.rb diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/distributed_amount.rb b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/distributed_amount.rb index 5403d548e74..5513190d90e 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/distributed_amount.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/distributed_amount.rb @@ -17,7 +17,7 @@ def compute_line_item(line_item) return 0 unless preferred_currency.casecmp(line_item.currency).zero? distributable_line_items = eligible_line_items(line_item.order) return 0 unless line_item.in?(distributable_line_items) - Spree::DistributedAmountsHandler.new( + DistributedAmountsHandler.new( distributable_line_items, preferred_amount ).amount(line_item) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/distributed_amounts_handler.rb b/friendly_promotions/app/models/solidus_friendly_promotions/distributed_amounts_handler.rb new file mode 100644 index 00000000000..1129e91da7c --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/distributed_amounts_handler.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + class DistributedAmountsHandler + attr_reader :line_items, :total_amount + + def initialize(line_items, total_amount) + @line_items = line_items + @total_amount = total_amount + end + + # @param line_item [LineItem] one of the line_items distributed over + # @return [BigDecimal] the weighted adjustment for this line_item + def amount(line_item) + distributed_amounts[line_item.id].to_d + end + + private + + # @private + # @return [Hash] a hash of line item IDs and their + # corresponding weighted adjustments + def distributed_amounts + Hash[line_item_ids.zip(allocated_amounts)] + end + + def line_item_ids + line_items.map(&:id) + end + + def elligible_amounts + line_items.map(&:amount) + end + + def subtotal + elligible_amounts.sum + end + + def allocated_amounts + total_amount.to_money.allocate(elligible_amounts).map(&:to_money) + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/distributed_amounts_handler_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/distributed_amounts_handler_spec.rb new file mode 100644 index 00000000000..4780f71f144 --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/distributed_amounts_handler_spec.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe SolidusFriendlyPromotions::DistributedAmountsHandler, type: :model do + let(:order) do + FactoryBot.create( + :order_with_line_items, + line_items_attributes: line_items_attributes + ) + end + + let(:handler) { + described_class.new(order.line_items, total_amount) + } + + describe "#amount" do + let(:total_amount) { 15 } + + context "when there is only one line item" do + let(:line_items_attributes) { [{ price: 100 }] } + let(:line_item) { order.line_items.first } + + it "applies the entire amount to the line item" do + expect(handler.amount(line_item)).to eq(15) + end + end + + context "when there are multiple line items" do + let(:line_items_attributes) do + [{ price: 50 }, { price: 50 }, { price: 50 }] + end + + context "and the line items are equally priced" do + it "evenly distributes the total amount" do + expect( + [ + handler.amount(order.line_items[0]), + handler.amount(order.line_items[1]), + handler.amount(order.line_items[2]) + ] + ).to eq( + [5, 5, 5] + ) + end + + context "and the total amount cannot be equally distributed" do + let(:total_amount) { 10 } + + it "applies the remainder of the total amount to the last item" do + expect( + [ + handler.amount(order.line_items[0]), + handler.amount(order.line_items[1]), + handler.amount(order.line_items[2]) + ] + ).to match_array( + [3.33, 3.33, 3.34] + ) + end + end + end + + context "and the line items do not have equal subtotal amounts" do + let(:line_items_attributes) do + [{ price: 50, quantity: 3 }, { price: 50, quantity: 1 }, { price: 50, quantity: 2 }] + end + + it "distributes the total amount relative to the item's price" do + expect( + [ + handler.amount(order.line_items[0]), + handler.amount(order.line_items[1]), + handler.amount(order.line_items[2]) + ] + ).to eq( + [7.5, 2.5, 5] + ) + end + end + end + end +end From 16743bd44a87325258cb7ca5be84830b7917727e Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 14 Jun 2023 14:25:13 +0200 Subject: [PATCH 201/834] Make sure Solidus is around when running `bin/rake` --- friendly_promotions/lib/solidus_friendly_promotions.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/friendly_promotions/lib/solidus_friendly_promotions.rb b/friendly_promotions/lib/solidus_friendly_promotions.rb index bf6950e2e15..3d288c4212b 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true +require "spree" require "solidus_friendly_promotions/configuration" require "solidus_friendly_promotions/version" require "solidus_friendly_promotions/engine" From 1a69d7d55e66331916b3060db31cea96d5b3bdef Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 14 Jun 2023 15:18:34 +0200 Subject: [PATCH 202/834] Add model name and description translations --- friendly_promotions/config/locales/en.yml | 68 ++++++++++++++++++- .../actions/adjust_line_item_spec.rb | 13 ++++ .../actions/adjust_shipment_spec.rb | 13 ++++ 3 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_line_item_spec.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_shipment_spec.rb diff --git a/friendly_promotions/config/locales/en.yml b/friendly_promotions/config/locales/en.yml index f341cf450bc..8ffb28a35ab 100644 --- a/friendly_promotions/config/locales/en.yml +++ b/friendly_promotions/config/locales/en.yml @@ -2,4 +2,70 @@ # See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. en: - hello: Hello world + activerecord: + models: + solidus_friendly_promotions/actions/adjust_shipment: Discount matching shipments + solidus_friendly_promotions/actions/adjust_line_item: Discount matching line items + solidus_friendly_promotions/calculators/distributed_amount: Distributed Amount + solidus_friendly_promotions/calculators/percent: Flat Percent + solidus_friendly_promotions/calculators/flat_rate: Flat Rate + solidus_friendly_promotions/calculators/flexi_rate: Flexible Rate + solidus_friendly_promotions/calculators/tiered_flat_rate: Tiered Flat Rate + solidus_friendly_promotions/calculators/tiered_percent: Tiered Percent + solidus_friendly_promotions/rules/first_order: First Order + solidus_friendly_promotions/rules/first_repeat_purchase_since: First Repeat Purchase Since + solidus_friendly_promotions/rules/item_total: Item Total + solidus_friendly_promotions/rules/landing_page: Landing Page + solidus_friendly_promotions/rules/nth_order: Nth Order + solidus_friendly_promotions/rules/one_use_per_user: One Use Per User + solidus_friendly_promotions/rules/option_value: Option Value(s) + solidus_friendly_promotions/rules/line_item_option_value: Line Item Option Value(s) + solidus_friendly_promotions/rules/product: Order Product(s) + solidus_friendly_promotions/rules/line_item_product: Line Item Product(s) + solidus_friendly_promotions/rules/taxon: Order Taxon(s) + solidus_friendly_promotions/rules/line_item_taxon: Line Item Taxon(s) + solidus_friendly_promotions/rules/user: User + solidus_friendly_promotions/rules/user_logged_in: User Logged In + solidus_friendly_promotions/rules/user_role: User Role(s) + attributes: + solidus_friendly_promotions/actions/adjust_line_item: + description: Creates a promotion credit on matching line items + solidus_friendly_promotions/actions/adjust_shipment: + description: Creates a promotion credit on matching shipments + solidus_friendly_promotions/rules/first_order: + description: Must be the customer's first order + solidus_friendly_promotions/rules/first_repeat_purchase_since: + description: Available only to user who have not purchased in a while + form_text: 'Apply this promotion to users whose last order was more than X + days ago: ' + solidus_friendly_promotions/rules/item_total: + description: Order total meets these criteria + solidus_friendly_promotions/rules/landing_page: + description: Customer must have visited the specified page + solidus_friendly_promotions/rules/nth_order: + description: Apply a promotion to every nth order a user has completed. + form_text: 'Apply this promotion on the users Nth order: ' + solidus_friendly_promotions/rules/one_use_per_user: + description: Only one use per user + solidus_friendly_promotions/rules/option_value: + description: Order includes specified product(s) with matching option value(s) + solidus_friendly_promotions/rules/line_item_option_value: + description: Line Item has specified product with matching option value + solidus_friendly_promotions/rules/product: + description: Order includes specified product(s) + solidus_friendly_promotions/rules/line_item_product: + description: Line item matches specified product(s) + preferred_match_policy: Match Policy + solidus_friendly_promotions/rules/store: + description: Available only to the specified stores + solidus_friendly_promotions/rules/taxon: + description: Order includes products with specified taxon(s) + solidus_friendly_promotions/rules/line_item_taxon: + description: Line Item has product with specified taxon(s) + preferred_match_policy: Match Policy + solidus_friendly_promotions/rules/user: + description: Available only to the specified users + solidus_friendly_promotions/rules/user_logged_in: + description: Available only to logged in users + solidus_friendly_promotions/rules/user_role: + description: Order includes User with specified Role(s) diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_line_item_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_line_item_spec.rb new file mode 100644 index 00000000000..45214a5d818 --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_line_item_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SolidusFriendlyPromotions::Actions::AdjustLineItem do + subject(:action) { described_class.new } + + describe "name" do + subject(:name) { action.model_name.human } + + it { is_expected.to eq("Discount matching line items") } + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_shipment_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_shipment_spec.rb new file mode 100644 index 00000000000..5495a3eb512 --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_shipment_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SolidusFriendlyPromotions::Actions::AdjustShipment do + subject(:action) { described_class.new } + + describe "name" do + subject(:name) { action.model_name.human } + + it { is_expected.to eq("Discount matching shipments") } + end +end From 428c2c2ee7e45a353e2f2a53923ab2ecaf5a5045 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 14 Jun 2023 18:10:26 +0200 Subject: [PATCH 203/834] Add FlexRate calculcator --- .../calculators/flexi_rate.rb | 24 +++ .../calculators/flexi_rate_spec.rb | 158 ++++++++++++++++++ 2 files changed, 182 insertions(+) create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/calculators/flexi_rate.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/calculators/flexi_rate_spec.rb diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/flexi_rate.rb b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/flexi_rate.rb new file mode 100644 index 00000000000..2ab4ed53063 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/flexi_rate.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require_dependency 'spree/calculator' + +module SolidusFriendlyPromotions + module Calculators + class FlexiRate < Spree::Calculator + preference :first_item, :decimal, default: 0 + preference :additional_item, :decimal, default: 0 + preference :max_items, :integer, default: 0 + preference :currency, :string, default: ->{ Spree::Config[:currency] } + + def compute(object) + items_count = object.quantity + items_count = [items_count, preferred_max_items].min unless preferred_max_items.zero? + + return BigDecimal(0) if items_count == 0 + + additional_items_count = items_count - 1 + preferred_first_item + preferred_additional_item * additional_items_count + end + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/flexi_rate_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/flexi_rate_spec.rb new file mode 100644 index 00000000000..e1c2056b27d --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/flexi_rate_spec.rb @@ -0,0 +1,158 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'shared_examples/calculator_shared_examples' + +RSpec.describe SolidusFriendlyPromotions::Calculators::FlexiRate, type: :model do + let(:calculator) do + described_class.new( + preferred_first_item: first_item, + preferred_additional_item: additional_item, + preferred_max_items: max_items + ) + end + let(:first_item) { 0 } + let(:additional_item) { 0 } + let(:max_items) { 0 } + + it_behaves_like 'a calculator with a description' + + let(:line_item) do + mock_model( + Spree::LineItem, quantity: quantity + ) + end + + context "compute" do + subject { calculator.compute(line_item) } + context "with all amounts 0" do + context "with quantity 0" do + let(:quantity) { 0 } + it { should eq 0 } + end + + context "with quantity 1" do + let(:quantity) { 1 } + it { should eq 0 } + end + + context "with quantity 2" do + let(:quantity) { 2 } + it { should eq 0 } + end + + context "with quantity 10" do + let(:quantity) { 10 } + it { should eq 0 } + end + end + + context "when first_item has a value" do + let(:first_item) { 1.23 } + + context "with quantity 0" do + let(:quantity) { 0 } + it { should eq 0 } + end + + context "with quantity 1" do + let(:quantity) { 1 } + it { should eq 1.23 } + end + + context "with quantity 2" do + let(:quantity) { 2 } + it { should eq 1.23 } + end + + context "with quantity 10" do + let(:quantity) { 10 } + it { should eq 1.23 } + end + end + + context "when additional_items has a value" do + let(:additional_item) { 1.23 } + + context "with quantity 0" do + let(:quantity) { 0 } + it { should eq 0 } + end + + context "with quantity 1" do + let(:quantity) { 1 } + it { should eq 0 } + end + + context "with quantity 2" do + let(:quantity) { 2 } + it { should eq 1.23 } + end + + context "with quantity 10" do + let(:quantity) { 10 } + it { should eq 11.07 } + end + end + + context "when first_item and additional_items has a value" do + let(:first_item) { 1.13 } + let(:additional_item) { 2.11 } + + context "with quantity 0" do + let(:quantity) { 0 } + it { should eq 0 } + end + + context "with quantity 1" do + let(:quantity) { 1 } + it { should eq 1.13 } + end + + context "with quantity 2" do + let(:quantity) { 2 } + it { should eq 3.24 } + end + + context "with quantity 10" do + let(:quantity) { 10 } + it { should eq 20.12 } + end + + context "with max_items 5" do + let(:max_items) { 5 } + + context "with quantity 0" do + let(:quantity) { 0 } + it { should eq 0 } + end + + context "with quantity 1" do + let(:quantity) { 1 } + it { should eq 1.13 } + end + + context "with quantity 2" do + let(:quantity) { 2 } + it { should eq 3.24 } + end + + context "with quantity 5" do + let(:quantity) { 5 } + it { should eq 9.57 } + end + + context "with quantity 10" do + let(:quantity) { 10 } + it { should eq 9.57 } + end + end + end + end + + it "should allow creation of new object with all the attributes" do + attributes = { preferred_first_item: 1, preferred_additional_item: 1, preferred_max_items: 1 } + calculator = Spree::Calculator::FlexiRate.new(attributes) + expect(calculator).to have_attributes(attributes) + end +end From 5708e14e76119feb50448b9f6fec4407db75f110 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 14 Jun 2023 18:12:20 +0200 Subject: [PATCH 204/834] Add available calculators endpoint on AdjustShipment action --- .../actions/adjust_shipment.rb | 2 ++ .../install/templates/initializer.rb | 10 ++++++++++ .../actions/adjust_shipment_spec.rb | 14 ++++++++++++++ 3 files changed, 26 insertions(+) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb index bcc0a03bdec..da8117b3216 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb @@ -3,6 +3,8 @@ module SolidusFriendlyPromotions module Actions class AdjustShipment < Base + class_attribute :available_calculators, default: [] + def can_adjust?(object) object.is_a? Spree::Shipment end diff --git a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb index 79e8c8e6990..1946eac2042 100644 --- a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb +++ b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb @@ -4,3 +4,13 @@ # TODO: Remember to change this with the actual preferences you have implemented! # config.sample_preference = 'sample_value' end + +Rails.application.config.to_prepare do + SolidusFriendlyPromotions::Actions::AdjustShipment.available_calculators += [ + SolidusFriendlyPromotions::Calculators::FlatRate, + SolidusFriendlyPromotions::Calculators::FlexiRate, + SolidusFriendlyPromotions::Calculators::Percent, + SolidusFriendlyPromotions::Calculators::TieredFlatRate, + SolidusFriendlyPromotions::Calculators::TieredPercent, + ] +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_shipment_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_shipment_spec.rb index 5495a3eb512..b9d9fd9d0c5 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_shipment_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_shipment_spec.rb @@ -10,4 +10,18 @@ it { is_expected.to eq("Discount matching shipments") } end + + describe ".available_calculators" do + subject { described_class.available_calculators } + + it do + is_expected.to contain_exactly( + SolidusFriendlyPromotions::Calculators::FlatRate, + SolidusFriendlyPromotions::Calculators::FlexiRate, + SolidusFriendlyPromotions::Calculators::Percent, + SolidusFriendlyPromotions::Calculators::TieredFlatRate, + SolidusFriendlyPromotions::Calculators::TieredPercent, + ) + end + end end From f68e0400dcc6f3a5f64fa07958bcb641e9e8c742 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 14 Jun 2023 18:14:38 +0200 Subject: [PATCH 205/834] Allow configuring available calculators for line item --- .../actions/adjust_line_item.rb | 2 ++ .../install/templates/initializer.rb | 8 ++++++++ .../actions/adjust_line_item_spec.rb | 15 +++++++++++++++ 3 files changed, 25 insertions(+) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb index 73eaa92b863..e96cb769749 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb @@ -3,6 +3,8 @@ module SolidusFriendlyPromotions module Actions class AdjustLineItem < Base + class_attribute :available_calculators, default: [] + def can_adjust?(object) object.is_a? Spree::LineItem end diff --git a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb index 1946eac2042..4b9e223db55 100644 --- a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb +++ b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb @@ -13,4 +13,12 @@ SolidusFriendlyPromotions::Calculators::TieredFlatRate, SolidusFriendlyPromotions::Calculators::TieredPercent, ] + SolidusFriendlyPromotions::Actions::AdjustLineItem.available_calculators += [ + SolidusFriendlyPromotions::Calculators::DistributedAmount, + SolidusFriendlyPromotions::Calculators::FlatRate, + SolidusFriendlyPromotions::Calculators::FlexiRate, + SolidusFriendlyPromotions::Calculators::Percent, + SolidusFriendlyPromotions::Calculators::TieredFlatRate, + SolidusFriendlyPromotions::Calculators::TieredPercent, + ] end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_line_item_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_line_item_spec.rb index 45214a5d818..6ea291929df 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_line_item_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_line_item_spec.rb @@ -10,4 +10,19 @@ it { is_expected.to eq("Discount matching line items") } end + + describe ".available_calculators" do + subject { described_class.available_calculators } + + it do + is_expected.to contain_exactly( + SolidusFriendlyPromotions::Calculators::DistributedAmount, + SolidusFriendlyPromotions::Calculators::FlatRate, + SolidusFriendlyPromotions::Calculators::FlexiRate, + SolidusFriendlyPromotions::Calculators::Percent, + SolidusFriendlyPromotions::Calculators::TieredFlatRate, + SolidusFriendlyPromotions::Calculators::TieredPercent, + ) + end + end end From 9d65f405f7796d2bceab4f3f68fc397cbee205e9 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 15 Jun 2023 10:33:40 +0200 Subject: [PATCH 206/834] Remove frontend stuff from generator We don't support the old frontend. --- .../solidus_friendly_promotions/install/install_generator.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/install_generator.rb b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/install_generator.rb index cf402d0bd68..1e2de6828d5 100644 --- a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/install_generator.rb +++ b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/install_generator.rb @@ -15,12 +15,10 @@ def copy_initializer end def add_javascripts - append_file "vendor/assets/javascripts/spree/frontend/all.js", "//= require spree/frontend/solidus_friendly_promotions\n" append_file "vendor/assets/javascripts/spree/backend/all.js", "//= require spree/backend/solidus_friendly_promotions\n" end def add_stylesheets - inject_into_file "vendor/assets/stylesheets/spree/frontend/all.css", " *= require spree/frontend/solidus_friendly_promotions\n", before: %r{\*/}, verbose: true # rubocop:disable Layout/LineLength inject_into_file "vendor/assets/stylesheets/spree/backend/all.css", " *= require spree/backend/solidus_friendly_promotions\n", before: %r{\*/}, verbose: true # rubocop:disable Layout/LineLength end From e7c63950d896d75f30940c8f0c7987acc732e7c8 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 15 Jun 2023 10:41:52 +0200 Subject: [PATCH 207/834] Fix sandbox command --- friendly_promotions/bin/sandbox | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/friendly_promotions/bin/sandbox b/friendly_promotions/bin/sandbox index bf577b65c23..6fc4884c492 100755 --- a/friendly_promotions/bin/sandbox +++ b/friendly_promotions/bin/sandbox @@ -8,7 +8,7 @@ test "$DB" = "sqlite" && export DB="sqlite3" if [ -z "$SOLIDUS_BRANCH" ] then echo "~~> Use 'export SOLIDUS_BRANCH=[master|v3.2|...]' to control the Solidus branch" - SOLIDUS_BRANCH="master" + SOLIDUS_BRANCH="main" fi echo "~~> Using branch $SOLIDUS_BRANCH of solidus" @@ -47,6 +47,7 @@ cat <> Gemfile gem 'solidus', github: 'solidusio/solidus', branch: '$SOLIDUS_BRANCH' gem 'rails-i18n' gem 'solidus_i18n' +gem 'solidus_auth_devise' gem '$extension_name', path: '..' From d2ceaeafd276b4d5f01322b0d18602821cf74abf Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 15 Jun 2023 13:59:36 +0200 Subject: [PATCH 208/834] Move configuration for available calculators to SFP.config --- .../actions/adjust_line_item.rb | 2 -- .../actions/adjust_shipment.rb | 2 -- .../install/templates/initializer.rb | 29 +++++++++---------- .../configuration.rb | 7 +++++ .../configuration_spec.rb | 14 ++++++++- .../actions/adjust_line_item_spec.rb | 15 ---------- .../actions/adjust_shipment_spec.rb | 14 --------- 7 files changed, 33 insertions(+), 50 deletions(-) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb index e96cb769749..73eaa92b863 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb @@ -3,8 +3,6 @@ module SolidusFriendlyPromotions module Actions class AdjustLineItem < Base - class_attribute :available_calculators, default: [] - def can_adjust?(object) object.is_a? Spree::LineItem end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb index da8117b3216..bcc0a03bdec 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb @@ -3,8 +3,6 @@ module SolidusFriendlyPromotions module Actions class AdjustShipment < Base - class_attribute :available_calculators, default: [] - def can_adjust?(object) object.is_a? Spree::Shipment end diff --git a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb index 4b9e223db55..ca957c682aa 100644 --- a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb +++ b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb @@ -3,22 +3,19 @@ SolidusFriendlyPromotions.configure do |config| # TODO: Remember to change this with the actual preferences you have implemented! # config.sample_preference = 'sample_value' -end - -Rails.application.config.to_prepare do - SolidusFriendlyPromotions::Actions::AdjustShipment.available_calculators += [ - SolidusFriendlyPromotions::Calculators::FlatRate, - SolidusFriendlyPromotions::Calculators::FlexiRate, - SolidusFriendlyPromotions::Calculators::Percent, - SolidusFriendlyPromotions::Calculators::TieredFlatRate, - SolidusFriendlyPromotions::Calculators::TieredPercent, + config.shipment_discount_calculators = [ + "SolidusFriendlyPromotions::Calculators::FlatRate", + "SolidusFriendlyPromotions::Calculators::FlexiRate", + "SolidusFriendlyPromotions::Calculators::Percent", + "SolidusFriendlyPromotions::Calculators::TieredFlatRate", + "SolidusFriendlyPromotions::Calculators::TieredPercent", ] - SolidusFriendlyPromotions::Actions::AdjustLineItem.available_calculators += [ - SolidusFriendlyPromotions::Calculators::DistributedAmount, - SolidusFriendlyPromotions::Calculators::FlatRate, - SolidusFriendlyPromotions::Calculators::FlexiRate, - SolidusFriendlyPromotions::Calculators::Percent, - SolidusFriendlyPromotions::Calculators::TieredFlatRate, - SolidusFriendlyPromotions::Calculators::TieredPercent, + config.line_item_discount_calculators = [ + "SolidusFriendlyPromotions::Calculators::DistributedAmount", + "SolidusFriendlyPromotions::Calculators::FlatRate", + "SolidusFriendlyPromotions::Calculators::FlexiRate", + "SolidusFriendlyPromotions::Calculators::Percent", + "SolidusFriendlyPromotions::Calculators::TieredFlatRate", + "SolidusFriendlyPromotions::Calculators::TieredPercent", ] end diff --git a/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb b/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb index 2813c447f15..45e22bbd073 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb @@ -1,7 +1,14 @@ # frozen_string_literal: true +require 'spree/core/environment_extension' + module SolidusFriendlyPromotions class Configuration < Spree::Preferences::Configuration + include Spree::Core::EnvironmentExtension + + add_class_set :line_item_discount_calculators + add_class_set :shipment_discount_calculators + class_name_attribute :promotion_chooser_class, default: "SolidusFriendlyPromotions::PromotionAdjustmentChooser" end diff --git a/friendly_promotions/spec/lib/solidus_friendly_promotions/configuration_spec.rb b/friendly_promotions/spec/lib/solidus_friendly_promotions/configuration_spec.rb index 92df9f120e3..cd1155944a0 100644 --- a/friendly_promotions/spec/lib/solidus_friendly_promotions/configuration_spec.rb +++ b/friendly_promotions/spec/lib/solidus_friendly_promotions/configuration_spec.rb @@ -3,7 +3,7 @@ require "spec_helper" RSpec.describe SolidusFriendlyPromotions::Configuration do - subject { SolidusFriendlyPromotions.config } + subject(:config) { SolidusFriendlyPromotions.config } it "has a nice accessor" do expect(subject).to be_a(described_class) @@ -18,4 +18,16 @@ expect(subject.promotion_chooser_class).to eq(SolidusFriendlyPromotions::PromotionAdjustmentChooser) end end + + describe ".shipment_discount_calculators" do + subject { config.shipment_discount_calculators } + + it { is_expected.to be_a(Spree::Core::ClassConstantizer::Set) } + end + + describe ".line_item_discount_calculators" do + subject { config.line_item_discount_calculators } + + it { is_expected.to be_a(Spree::Core::ClassConstantizer::Set) } + end end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_line_item_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_line_item_spec.rb index 6ea291929df..45214a5d818 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_line_item_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_line_item_spec.rb @@ -10,19 +10,4 @@ it { is_expected.to eq("Discount matching line items") } end - - describe ".available_calculators" do - subject { described_class.available_calculators } - - it do - is_expected.to contain_exactly( - SolidusFriendlyPromotions::Calculators::DistributedAmount, - SolidusFriendlyPromotions::Calculators::FlatRate, - SolidusFriendlyPromotions::Calculators::FlexiRate, - SolidusFriendlyPromotions::Calculators::Percent, - SolidusFriendlyPromotions::Calculators::TieredFlatRate, - SolidusFriendlyPromotions::Calculators::TieredPercent, - ) - end - end end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_shipment_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_shipment_spec.rb index b9d9fd9d0c5..5495a3eb512 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_shipment_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_shipment_spec.rb @@ -10,18 +10,4 @@ it { is_expected.to eq("Discount matching shipments") } end - - describe ".available_calculators" do - subject { described_class.available_calculators } - - it do - is_expected.to contain_exactly( - SolidusFriendlyPromotions::Calculators::FlatRate, - SolidusFriendlyPromotions::Calculators::FlexiRate, - SolidusFriendlyPromotions::Calculators::Percent, - SolidusFriendlyPromotions::Calculators::TieredFlatRate, - SolidusFriendlyPromotions::Calculators::TieredPercent, - ) - end - end end From c7bb2818afc82d4edd8a114929e9d7155b56b7ff Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 15 Jun 2023 14:01:57 +0200 Subject: [PATCH 209/834] Add line item, shipment, and order rules to configuration --- .../configuration.rb | 4 ++++ .../configuration_spec.rb | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb b/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb index 45e22bbd073..21fe9c8f71d 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb @@ -9,6 +9,10 @@ class Configuration < Spree::Preferences::Configuration add_class_set :line_item_discount_calculators add_class_set :shipment_discount_calculators + add_class_set :order_rules + add_class_set :line_item_rules + add_class_set :shipment_rules + class_name_attribute :promotion_chooser_class, default: "SolidusFriendlyPromotions::PromotionAdjustmentChooser" end diff --git a/friendly_promotions/spec/lib/solidus_friendly_promotions/configuration_spec.rb b/friendly_promotions/spec/lib/solidus_friendly_promotions/configuration_spec.rb index cd1155944a0..3a14b9c824c 100644 --- a/friendly_promotions/spec/lib/solidus_friendly_promotions/configuration_spec.rb +++ b/friendly_promotions/spec/lib/solidus_friendly_promotions/configuration_spec.rb @@ -30,4 +30,22 @@ it { is_expected.to be_a(Spree::Core::ClassConstantizer::Set) } end + + describe ".order_rules" do + subject { config.order_rules } + + it { is_expected.to be_a(Spree::Core::ClassConstantizer::Set) } + end + + describe ".line_item_rules" do + subject { config.line_item_rules } + + it { is_expected.to be_a(Spree::Core::ClassConstantizer::Set) } + end + + describe ".shipment_rules" do + subject { config.line_item_rules } + + it { is_expected.to be_a(Spree::Core::ClassConstantizer::Set) } + end end From e75c0408fea2887c4125be668ed41f74a851f8f1 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 15 Jun 2023 14:07:16 +0200 Subject: [PATCH 210/834] Add Rules sets to initializer --- .../install/templates/initializer.rb | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb index ca957c682aa..fa9924c9270 100644 --- a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb +++ b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb @@ -18,4 +18,25 @@ "SolidusFriendlyPromotions::Calculators::TieredFlatRate", "SolidusFriendlyPromotions::Calculators::TieredPercent", ] + + config.order_rules = [ + "SolidusFriendlyPromotions::Rules::FirstOrder", + "SolidusFriendlyPromotions::Rules::FirstRepeatPurchaseSince", + "SolidusFriendlyPromotions::Rules::ItemTotal", + "SolidusFriendlyPromotions::Rules::NthOrder", + "SolidusFriendlyPromotions::Rules::OneUsePerUser", + "SolidusFriendlyPromotions::Rules::OptionValue", + "SolidusFriendlyPromotions::Rules::Product", + "SolidusFriendlyPromotions::Rules::Store", + "SolidusFriendlyPromotions::Rules::Taxon", + "SolidusFriendlyPromotions::Rules::UserLoggedIn", + "SolidusFriendlyPromotions::Rules::UserRole", + "SolidusFriendlyPromotions::Rules::User", + ] + config.line_item_rules = [ + "SolidusFriendlyPromotions::Rules::LineItemOptionValue", + "SolidusFriendlyPromotions::Rules::LineItemProduct", + "SolidusFriendlyPromotions::Rules::LineItemTaxon", + ] + config.shipment_rules = [] end From 8217f81854ce86f7bed18321b3e3c975589abdd9 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 15 Jun 2023 17:35:59 +0200 Subject: [PATCH 211/834] No-op admin promotions controller This just adds a controller which we can fill with functionality from Solidus core. --- .../admin/promotions_controller.rb | 10 ++ .../admin/promotions/index.html.erb | 124 ++++++++++++++++++ friendly_promotions/config/routes.rb | 6 +- .../install/install_generator.rb | 7 + .../lib/solidus_friendly_promotions/engine.rb | 2 +- .../admin/promotions_spec.rb | 14 ++ friendly_promotions/spec/spec_helper.rb | 2 + 7 files changed, 162 insertions(+), 3 deletions(-) create mode 100644 friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/index.html.erb create mode 100644 friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotions_spec.rb diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb new file mode 100644 index 00000000000..2596c80457a --- /dev/null +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module Admin + class PromotionsController < ::Spree::Admin::ResourceController + def index + end + end + end +end diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/index.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/index.html.erb new file mode 100644 index 00000000000..4c35e9d766c --- /dev/null +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/index.html.erb @@ -0,0 +1,124 @@ +<% admin_breadcrumb(plural_resource_name(Spree::Promotion)) %> + +<% content_for :page_actions do %> + <% if can? :create, Spree::Promotion %> +
  • + <%= link_to t('spree.new_promotion'), spree.new_admin_promotion_path, class: 'btn btn-primary' %> +
  • + <% end %> +<% end %> + +<% content_for :table_filter_title do %> + <%= t('spree.search') %> +<% end %> + +<% content_for :table_filter do %> +
    + <%= search_form_for [:admin, @search] do |f| %> +
    +
    +
    + <%= label_tag :q_name_cont, Spree::Promotion.human_attribute_name(:name) %> + <%= f.text_field :name_cont, tabindex: 1 %> +
    +
    + +
    +
    + <%= label_tag :q_codes_value_cont, Spree::Promotion.human_attribute_name(:code) %> + <%= f.text_field :codes_value_cont, tabindex: 1 %> +
    +
    + +
    +
    + <%= label_tag :q_path_cont, Spree::Promotion.human_attribute_name(:path) %> + <%= f.text_field :path_cont, tabindex: 1 %> +
    +
    + +
    +
    + <%= label_tag :q_promotion_category_id_eq, Spree::PromotionCategory.model_name.human %>
    + <%= f.collection_select(:promotion_category_id_eq, @promotion_categories, :id, :name, { include_blank: t('spree.match_choices.all') }, { class: 'custom-select fullwidth' }) %> +
    +
    + +
    +
    + <%= label_tag :active, t('spree.active') %>
    + <%= f.check_box :active, label: false, as: :boolean, checked_value: true %> +
    +
    +
    + +
    + +
    +
    + <%= button_tag t('spree.filter_results'), class: 'btn btn-primary' %> +
    +
    + <% end %> +
    +<% end %> + +<%= paginate @promotions, theme: "solidus_admin" %> + +<% if @promotions.length > 0 %> + + + + + + + + + + + + + + + <% @promotions.each do |promotion| %> + + + + + + + + + + + <% end %> + +
    <%= Spree::Promotion.human_attribute_name(:name) %><%= Spree::Promotion.human_attribute_name(:code) %><%= Spree::Promotion.human_attribute_name(:status) %><%= Spree::Promotion.human_attribute_name(:usage_limit) %><%= Spree::Promotion.human_attribute_name(:uses) %><%= Spree::Promotion.human_attribute_name(:starts_at) %><%= Spree::Promotion.human_attribute_name(:expires_at) %>
    <%= promotion.name %> + <%= (promotion.codes.size == 1) ? promotion.codes.pluck(:value).first : t('spree.number_of_codes', count: promotion.codes.size) %> + + + <%= t(admin_promotion_status(promotion), scope: 'spree.admin.promotion_status') %> + + + <%= promotion.usage_limit.nil? ? "∞" : promotion.usage_limit %> + + <%= promotion.usage_count %> + + <%= promotion.starts_at.to_fs(:long) if promotion.starts_at %> + + <%= promotion.expires_at.to_fs(:long) if promotion.expires_at %> + + <% if can?(:edit, promotion) %> + <%= link_to_edit promotion, no_text: true %> + <% end %> + <% if can?(:destroy, promotion) %> + <%= link_to_delete promotion, no_text: true %> + <% end %> +
    +<% else %> +
    + <%= render 'spree/admin/shared/no_objects_found', + resource: Spree::Promotion, + new_resource_url: new_object_url %> +
    +<% end %> diff --git a/friendly_promotions/config/routes.rb b/friendly_promotions/config/routes.rb index 59d02443fbd..ded5869ba85 100644 --- a/friendly_promotions/config/routes.rb +++ b/friendly_promotions/config/routes.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true -Spree::Core::Engine.routes.draw do - # Add your extension routes here +SolidusFriendlyPromotions::Engine.routes.draw do + namespace :admin do + resources :promotions, only: :index + end end diff --git a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/install_generator.rb b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/install_generator.rb index 1e2de6828d5..3c8d47d459a 100644 --- a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/install_generator.rb +++ b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/install_generator.rb @@ -26,6 +26,13 @@ def add_migrations run "bin/rails railties:install:migrations FROM=solidus_friendly_promotions" end + def mount_engine + inject_into_file "config/routes.rb", + " mount SolidusFriendlyPromotions::Engine => '/'\n", + before: %r{ mount Spree::Core::Engine.*}, + verbose: true + end + def run_migrations run_migrations = options[:auto_run_migrations] || ["", "y", "Y"].include?(ask("Would you like to run the migrations now? [Y/n]")) # rubocop:disable Layout/LineLength if run_migrations diff --git a/friendly_promotions/lib/solidus_friendly_promotions/engine.rb b/friendly_promotions/lib/solidus_friendly_promotions/engine.rb index 47a740fd133..b4fef4a447a 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions/engine.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions/engine.rb @@ -7,7 +7,7 @@ module SolidusFriendlyPromotions class Engine < Rails::Engine include SolidusSupport::EngineExtensions - isolate_namespace ::Spree + isolate_namespace ::SolidusFriendlyPromotions engine_name "solidus_friendly_promotions" diff --git a/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotions_spec.rb b/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotions_spec.rb new file mode 100644 index 00000000000..9c98a0df6e4 --- /dev/null +++ b/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotions_spec.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe "Admin::Promotions", type: :request do + describe "GET /index" do + stub_authorization! + + it "is successful" do + get admin_promotions_path + expect(response).to be_successful + end + end +end diff --git a/friendly_promotions/spec/spec_helper.rb b/friendly_promotions/spec/spec_helper.rb index d17e8ccd2b4..bd47c3aa520 100644 --- a/friendly_promotions/spec/spec_helper.rb +++ b/friendly_promotions/spec/spec_helper.rb @@ -33,6 +33,8 @@ config.extend Spree::TestingSupport::AuthorizationHelpers::Request, type: :system end + config.include SolidusFriendlyPromotions::Engine.routes.url_helpers, type: :request + config.before do Spree::Config.order_contents_class = "SolidusFriendlyPromotions::SimpleOrderContents" end From 35ca8897fc35cb9b2821e36b042cbc0e2126e732 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Mon, 19 Jun 2023 14:48:54 +0200 Subject: [PATCH 212/834] Fix sandbox generation --- friendly_promotions/bin/sandbox | 38 +++++++++++++++------------------ 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/friendly_promotions/bin/sandbox b/friendly_promotions/bin/sandbox index 6fc4884c492..9266d651af3 100755 --- a/friendly_promotions/bin/sandbox +++ b/friendly_promotions/bin/sandbox @@ -5,49 +5,50 @@ test -z "${DEBUG+empty_string}" || set -x test "$DB" = "sqlite" && export DB="sqlite3" -if [ -z "$SOLIDUS_BRANCH" ] +if [ -z "$PAYMENT_METHOD" ] then - echo "~~> Use 'export SOLIDUS_BRANCH=[master|v3.2|...]' to control the Solidus branch" - SOLIDUS_BRANCH="main" + PAYMENT_METHOD="none" fi -echo "~~> Using branch $SOLIDUS_BRANCH of solidus" -if [ -z "$SOLIDUS_FRONTEND" ] +if [ -z "$SOLIDUS_BRANCH" ] then - echo "~~> Use 'export SOLIDUS_FRONTEND=[solidus_frontend|solidus_starter_frontend]' to control the Solidus frontend" - SOLIDUS_FRONTEND="solidus_frontend" + echo "~~> Use 'export SOLIDUS_BRANCH=[main|v4.0|...]' to control the Solidus branch" + SOLIDUS_BRANCH="main" fi -echo "~~> Using branch $SOLIDUS_FRONTEND as the solidus frontend" +echo "~~> Using branch $SOLIDUS_BRANCH of solidus" extension_name="solidus_friendly_promotions" # Stay away from the bundler env of the containing extension. function unbundled { - ruby -rbundler -e'b = proc {system *ARGV}; Bundler.respond_to?(:with_unbundled_env) ? Bundler.with_unbundled_env(&b) : Bundler.with_clean_env(&b)' -- $@ + ruby -rbundler -e' + Bundler.with_unbundled_env {system *ARGV}' -- \ + env BUNDLE_SUPPRESS_INSTALL_USING_MESSAGES=true $@ } +echo "~~~> Removing the old sandbox" rm -rf ./sandbox -unbundled bundle exec rails new sandbox \ + +echo "~~~> Creating a pristine Rails app" +rails new sandbox \ --database="${DB:-sqlite3}" \ - --skip-bundle \ --skip-git \ --skip-keeps \ --skip-rc \ - --skip-spring \ - --skip-test \ - --skip-javascript + --skip-bootsnap \ + --skip-test if [ ! -d "sandbox" ]; then echo 'sandbox rails application failed' exit 1 fi +echo "~~~> Adding solidus (with i18n and authentication) and your gem to the Gemfile" cd ./sandbox cat <> Gemfile gem 'solidus', github: 'solidusio/solidus', branch: '$SOLIDUS_BRANCH' gem 'rails-i18n' gem 'solidus_i18n' -gem 'solidus_auth_devise' gem '$extension_name', path: '..' @@ -62,13 +63,8 @@ unbundled bundle install --gemfile Gemfile unbundled bundle exec rake db:drop db:create -unbundled bundle exec rails generate solidus:install \ +unbundled bin/rails generate solidus:install \ --auto-accept \ - --user_class=Spree::User \ - --enforce_available_locales=true \ - --with-authentication=true \ - --payment-method=none \ - --frontend=${SOLIDUS_FRONTEND} \ $@ unbundled bundle exec rails generate solidus:auth:install --auto-run-migrations From c7620996a8ec23881223b01aff7b70303dd7fc97 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Mon, 19 Jun 2023 17:25:30 +0200 Subject: [PATCH 213/834] Import index action from Solidus With feature specs. --- .../admin/promotions_controller.rb | 27 +++++++- .../solidus_friendly_promotions.rb | 1 - .../install/templates/initializer.rb | 15 +++++ friendly_promotions/spec/spec_helper.rb | 2 +- .../admin/promotions_spec.rb | 61 +++++++++++++++++++ 5 files changed, 103 insertions(+), 3 deletions(-) create mode 100644 friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotions_spec.rb diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb index 2596c80457a..7a29b5f946c 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb @@ -3,7 +3,32 @@ module SolidusFriendlyPromotions module Admin class PromotionsController < ::Spree::Admin::ResourceController - def index + before_action :load_data + + private + + def collection + return @collection if @collection + params[:q] ||= HashWithIndifferentAccess.new + params[:q][:s] ||= 'id desc' + + @collection = super + @search = @collection.ransack(params[:q]) + @collection = @search.result(distinct: true). + includes(promotion_includes). + page(params[:page]). + per(params[:per_page] || Spree::Config[:promotions_per_page]) + + @collection + end + + def promotion_includes + [:promotion_actions] + end + + def load_data + @calculators = Rails.application.config.spree.calculators.promotion_actions_create_adjustments + @promotion_categories = Spree::PromotionCategory.order(:name) end end end diff --git a/friendly_promotions/config/initializers/solidus_friendly_promotions.rb b/friendly_promotions/config/initializers/solidus_friendly_promotions.rb index 472576bf09f..e69de29bb2d 100644 --- a/friendly_promotions/config/initializers/solidus_friendly_promotions.rb +++ b/friendly_promotions/config/initializers/solidus_friendly_promotions.rb @@ -1 +0,0 @@ -Spree::Backend::Config.menu_items.delete_if { |menu_item| menu_item.url == :admin_promotions_path } diff --git a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb index fa9924c9270..743e89ef52d 100644 --- a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb +++ b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb @@ -1,5 +1,20 @@ # frozen_string_literal: true +# Replace the promotions menu from core with ours +Spree::Backend::Config.configure do |config| + config.menu_items = Spree::Backend::Config.menu_items.map do |item| + next item unless item.url == :admin_promotions_path + Spree::BackendConfiguration::MenuItem.new( + [:promotions, :promotion_categories], + 'bullhorn', + partial: 'spree/admin/shared/promotion_sub_menu', + condition: -> { can?(:admin, Spree::Promotion) }, + url: -> { SolidusFriendlyPromotions::Engine.routes.url_helpers.admin_promotions_path }, + position: 2 + ) + end +end + SolidusFriendlyPromotions.configure do |config| # TODO: Remember to change this with the actual preferences you have implemented! # config.sample_preference = 'sample_value' diff --git a/friendly_promotions/spec/spec_helper.rb b/friendly_promotions/spec/spec_helper.rb index bd47c3aa520..36851bd3aa9 100644 --- a/friendly_promotions/spec/spec_helper.rb +++ b/friendly_promotions/spec/spec_helper.rb @@ -27,7 +27,7 @@ RSpec.configure do |config| config.infer_spec_type_from_file_location! - config.use_transactional_fixtures = false + config.use_transactional_fixtures = true if Spree.solidus_gem_version < Gem::Version.new("2.11") config.extend Spree::TestingSupport::AuthorizationHelpers::Request, type: :system diff --git a/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotions_spec.rb b/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotions_spec.rb new file mode 100644 index 00000000000..00af683f4d6 --- /dev/null +++ b/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotions_spec.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe "Promotions admin", type: :system do + stub_authorization! + + describe "#index" do + let!(:promotion1) { create(:promotion, :with_action, name: "name1", code: "code1", path: "path1") } + let!(:promotion2) { create(:promotion, :with_action, name: "name2", code: "code2", path: "path2") } + let!(:promotion3) do + create(:promotion, :with_action, name: "name3", code: "code3", path: "path3", expires_at: Date.yesterday) + end + let!(:category) { create :promotion_category } + + it "succeeds" do + visit solidus_friendly_promotions.admin_promotions_path + [promotion3, promotion2, promotion1].map(&:name).each do |promotion_name| + expect(page).to have_content promotion_name + end + end + + it "shows promotion categories" do + visit solidus_friendly_promotions.admin_promotions_path + expect(page).to have_select(Spree::PromotionCategory.model_name.human, options: ["All", category.name]) + end + + context "search" do + it "pages results" do + visit solidus_friendly_promotions.admin_promotions_path(per_page: '1') + expect(page).to have_content(promotion3.name) + expect(page).not_to have_content(promotion1.name) + end + + it "filters by name" do + visit solidus_friendly_promotions.admin_promotions_path(q: { name_cont: promotion1.name }) + expect(page).to have_content(promotion1.name) + expect(page).not_to have_content(promotion2.name) + end + + it "filters by code" do + visit solidus_friendly_promotions.admin_promotions_path(q: { codes_value_cont: promotion1.codes.first.value }) + expect(page).to have_content(promotion1.name) + expect(page).not_to have_content(promotion2.name) + end + + it "filters by path" do + visit solidus_friendly_promotions.admin_promotions_path(q: { path_cont: promotion1.path }) + expect(page).to have_content(promotion1.name) + expect(page).not_to have_content(promotion2.name) + end + + it "filters by active" do + visit solidus_friendly_promotions.admin_promotions_path(q: { active: true }) + expect(page).to have_content(promotion1.name) + expect(page).to have_content(promotion2.name) + expect(page).not_to have_content(promotion3.name) + end + end + end +end From 1827b22e12ce2def12f9d8e332b25aa1731071fd Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 20 Jun 2023 10:50:32 +0200 Subject: [PATCH 214/834] Add promotion form This also imports a bunch of translations in order to keep the interaction with Solidus down to just things that are related to the resources controller. --- .../promotions/_activations_edit.html.erb | 22 +++++ .../promotions/_activations_new.html.erb | 43 ++++++++++ .../admin/promotions/_form.html.erb | 83 +++++++++++++++++++ .../admin/promotions/new.html.erb | 9 ++ friendly_promotions/config/locales/en.yml | 20 +++++ friendly_promotions/config/routes.rb | 2 +- .../admin/promotions_spec.rb | 17 ++++ 7 files changed, 195 insertions(+), 1 deletion(-) create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_activations_edit.html.erb create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_activations_new.html.erb create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_form.html.erb create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/new.html.erb diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_activations_edit.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_activations_edit.html.erb new file mode 100644 index 00000000000..8f82fe4accf --- /dev/null +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_activations_edit.html.erb @@ -0,0 +1,22 @@ +<% if @promotion.apply_automatically? %> +

    + <%= t('.auto') %> +

    +<% end %> + +<% if @promotion.codes.count == 1 %> +

    + <%= t('.single_code_html', code: @promotion.codes.first.value) %> +

    +<% elsif @promotion.codes.count > 1 %> +

    + <%= t('.multiple_codes_html', count: @promotion.codes.count) %> +

    +<% end %> + +<% if @promotion.path %> +
    + <%= f.label :path %> + <%= f.text_field :path, class: "fullwidth" %> +
    +<% end %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_activations_new.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_activations_new.html.erb new file mode 100644 index 00000000000..47cf2da5283 --- /dev/null +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_activations_new.html.erb @@ -0,0 +1,43 @@ +<% activation_type = params[:activation_type] || 'single_code' %> +
    +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    + +
    + +
    + +
    +
    + <%= label_tag :single_code, Spree::PromotionCode.model_name.human, class: "required" %> + <%= text_field_tag :single_code, @promotion.codes.first.try!(:value), class: "fullwidth", required: true %> +
    +
    + +
    + <%= fields_for :promotion_code_batch, @promotion_code_batch do |batch| %> + <%= render partial: 'spree/admin/promotion_code_batches/form_fields', locals: {f: f, batch: batch, promotion_id: params[:promotion_id]} %> + <% end %> +
    + +
    +
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_form.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_form.html.erb new file mode 100644 index 00000000000..405f88c6d01 --- /dev/null +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_form.html.erb @@ -0,0 +1,83 @@ +<%= render partial: 'spree/shared/error_messages', locals: { target: @promotion } %> +
    +
    +
    +
    +
    + <%= f.field_container :name do %> + <%= f.label :name, class: 'required' %> + <%= f.text_field :name, class: 'fullwidth', required: true %> + <% end %> + + <%= f.field_container :description do %> + <%= f.label :description %>
    + <%= f.text_area :description, rows: 7, class: 'fullwidth' %> + + <%= t('spree.character_limit') %> + + <% end %> + + <%= f.field_container :category do %> + <%= f.label :promotion_category_id, Spree::PromotionCategory.model_name.human %>
    + <%= + f.collection_select(:promotion_category_id, @promotion_categories, :id, :name, { include_blank: t('spree.match_choices.none') }, + { class: 'custom-select fullwidth' }) + %> + <% end %> +
    +
    +
    + +
    + <%= f.field_container :overall_usage_limit do %> + <%= f.label :usage_limit %>
    + <%= f.number_field :usage_limit, min: 0, class: 'fullwidth' %>
    + + <%= t('solidus_friendly_promotions.current_promotion_usage', count: @promotion.usage_count) %> + + <% end %> + + <% if @promotion.persisted? %> + <%= f.field_container :per_code_usage_limit do %> + <%= f.label :per_code_usage_limit %>
    + <%= f.number_field :per_code_usage_limit, min: 0, class: 'fullwidth' %>
    + <% end %> + <% end %> + +
    + <%= f.label :starts_at %> + <%= f.field_hint :starts_at %> + <%= + f.text_field :starts_at, + value: datepicker_field_value(@promotion.starts_at, with_time: true), + placeholder: t(".starts_at_placeholder"), + class: 'datepicker datepicker-from fullwidth', + data: { :'enable-time' => true, :'default-hour' => 0 } + %> +
    + +
    + <%= f.label :expires_at %> + <%= f.field_hint :expires_at %> + <%= + f.text_field :expires_at, + value: datepicker_field_value(@promotion.expires_at, + with_time: true), + placeholder: t(".expires_at_placeholder"), + class: 'datepicker datepicker-to fullwidth', + data: { :'enable-time' => true, :'default-hour' => 0 } + %> +
    +
    +
    +
    + +
    + <%= t '.activation' %> + + <% if @promotion.new_record? %> + <%= render 'spree/admin/promotions/activations_new', f: f %> + <% else %> + <%= render 'spree/admin/promotions/activations_edit', f: f %> + <% end %> +
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/new.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/new.html.erb new file mode 100644 index 00000000000..d9302bd67cb --- /dev/null +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/new.html.erb @@ -0,0 +1,9 @@ +<% admin_layout "full-width" %> + +<% admin_breadcrumb(link_to plural_resource_name(Spree::Promotion), solidus_friendly_promotions.admin_promotions_path) %> +<% admin_breadcrumb(t('solidus_friendly_promotions.new_promotion')) %> + +<%= form_for @promotion, url: collection_url do |f| %> + <%= render partial: 'form', locals: { f: f } %> + <%= render partial: 'spree/admin/shared/new_resource_links' %> +<% end %> diff --git a/friendly_promotions/config/locales/en.yml b/friendly_promotions/config/locales/en.yml index 8ffb28a35ab..b97e5d2e961 100644 --- a/friendly_promotions/config/locales/en.yml +++ b/friendly_promotions/config/locales/en.yml @@ -2,6 +2,26 @@ # See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. en: + solidus_friendly_promotions: + new_promotion: New Promotion + current_promotion_usage: 'Current Usage: %{count}' + admin: + promotions: + actions: + calculator_label: Calculated by + activations_edit: + auto: All orders will attempt to use this promotion + multiple_codes_html: This promotion uses %{count} promotion codes + single_code_html: 'This promotion uses the promotion code: %{code}' + activations_new: + auto: Apply to all orders + multiple_codes: Multiple promotion codes + single_code: Single promotion code + form: + activation: Activation + expires_at_placeholder: Never + general: General + starts_at_placeholder: Immediately activerecord: models: solidus_friendly_promotions/actions/adjust_shipment: Discount matching shipments diff --git a/friendly_promotions/config/routes.rb b/friendly_promotions/config/routes.rb index ded5869ba85..a439442403c 100644 --- a/friendly_promotions/config/routes.rb +++ b/friendly_promotions/config/routes.rb @@ -2,6 +2,6 @@ SolidusFriendlyPromotions::Engine.routes.draw do namespace :admin do - resources :promotions, only: :index + resources :promotions, only: [:index, :new] end end diff --git a/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotions_spec.rb b/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotions_spec.rb index 00af683f4d6..909c02e1e55 100644 --- a/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotions_spec.rb +++ b/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotions_spec.rb @@ -58,4 +58,21 @@ end end end + + describe "Creating a promotion" do + it "allows creating a promotion with the new UI" do + visit solidus_friendly_promotions.admin_promotions_path + click_link "New Promotion" + expect(page).to have_field("Name") + expect(page).to have_field("Start") + expect(page).to have_field("End") + expect(page).to have_field("Description") + fill_in("Name", with: "20 percent off") + fill_in("Start", with: Time.current) + fill_in("End", with: 1.week.from_now) + choose("Apply to all orders") + click_button("Create") + expect(page).to have_content("20 percent off") + end + end end From 3354de46b581fe8b96bb7b3e7e03e992c257e01f Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 20 Jun 2023 11:27:16 +0200 Subject: [PATCH 215/834] Import promotion rules controller --- .../admin/promotion_rules_controller.rb | 64 +++++++++++++++++++ .../admin/promotion_rules/create.js.erb | 8 +++ .../admin/promotion_rules/destroy.js.erb | 3 + .../admin/promotion_rules_request_spec.rb | 35 ++++++++++ 4 files changed, 110 insertions(+) create mode 100644 friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/create.js.erb create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/destroy.js.erb create mode 100644 friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_rules_request_spec.rb diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb new file mode 100644 index 00000000000..340311655f1 --- /dev/null +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module Admin + class PromotionRulesController < Spree::Admin::BaseController + helper 'spree/promotion_rules' + + before_action :load_promotion, only: [:create, :destroy] + before_action :validate_promotion_rule_type, only: :create + + def create + @promotion_rule = @promotion_rule_type.new(promotion_rule_params) + @promotion_rule.promotion = @promotion + if @promotion_rule.save + flash[:success] = t('spree.successfully_created', resource: t('spree.promotion_rule')) + end + respond_to do |format| + format.html { redirect_to spree.edit_admin_promotion_path(@promotion) } + format.js { render layout: false } + end + end + + def destroy + @promotion_rule = @promotion.promotion_rules.find(params[:id]) + if @promotion_rule.destroy + flash[:success] = t('spree.successfully_removed', resource: t('spree.promotion_rule')) + end + respond_to do |format| + format.html { redirect_to spree.edit_admin_promotion_path(@promotion) } + format.js { render layout: false } + end + end + + private + + def load_promotion + @promotion = Spree::Promotion.find(params[:promotion_id]) + end + + def model_class + Spree::PromotionRule + end + + def validate_promotion_rule_type + requested_type = params[:promotion_rule].delete(:type) + promotion_rule_types = Rails.application.config.spree.promotions.rules + @promotion_rule_type = promotion_rule_types.detect do |klass| + klass.name == requested_type + end + if !@promotion_rule_type + flash[:error] = t('spree.invalid_promotion_rule') + respond_to do |format| + format.html { redirect_to spree.edit_admin_promotion_path(@promotion) } + format.js { render layout: false } + end + end + end + + def promotion_rule_params + params[:promotion_rule].permit! + end + end + end +end diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/create.js.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/create.js.erb new file mode 100644 index 00000000000..9d806a88dfe --- /dev/null +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/create.js.erb @@ -0,0 +1,8 @@ +$('#rules').append('<%= escape_javascript( render(partial: 'spree/admin/promotions/promotion_rule', object: @promotion_rule) ) %>'); +$('#rules .no-objects-found').hide(); + +$('.product_picker').productAutocomplete(); +$('.user_picker').userAutocomplete(); +$('.taxon_picker').taxonAutocomplete(); + +$('#promotion_rule_type').html('<%= escape_javascript options_for_promotion_rule_types(@promotion) %>'); diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/destroy.js.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/destroy.js.erb new file mode 100644 index 00000000000..c5490e5bd47 --- /dev/null +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/destroy.js.erb @@ -0,0 +1,3 @@ +$('#<%= dom_id @promotion_rule %>').fadeOut().remove(); + +$('#promotion_rule_type').html('<%= escape_javascript options_for_promotion_rule_types(@promotion) %>'); \ No newline at end of file diff --git a/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_rules_request_spec.rb b/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_rules_request_spec.rb new file mode 100644 index 00000000000..a555188e8c6 --- /dev/null +++ b/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_rules_request_spec.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe SolidusFriendlyPromotions::Admin::PromotionRulesController, type: :request do + let!(:promotion) { create(:promotion) } + + context "when the user is authorized" do + stub_authorization! do |_u| + Spree::PermissionSets::PromotionManagement.new(self).activate! + end + + it "can create a promotion rule of a valid type" do + post solidus_friendly_promotions.admin_promotion_promotion_rules_path(promotion_id: promotion.id, promotion_rule: { type: "Spree::Promotion::Rules::Product" } ) + expect(response).to be_redirect + expect(response).to redirect_to solidus_friendly_promotions.edit_admin_promotion_path(promotion) + expect(promotion.rules.count).to eq(1) + end + + it "can not create a promotion rule of an invalid type" do + post solidus_friendly_promotions.admin_promotion_promotion_rules_path(promotion_id: promotion.id, promotion_rule: { type: "Spree::InvalidType" }) + expect(response).to be_redirect + expect(response).to redirect_to solidus_friendly_promotions.edit_admin_promotion_path(promotion) + expect(promotion.rules.count).to eq(0) + end + end + + context "when the user is not authorized" do + it "redirects the user to login" do + post solidus_friendly_promotions.admin_promotion_promotion_rules_path(promotion_id: promotion.id, promotion_rule: { type: "Spree::Promotion::Rules::Product" }) + + expect(response).to redirect_to('/admin/login') + end + end +end From 870af34e8ca73b8c2c6c2b0f6a26362698bb1b31 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 20 Jun 2023 11:31:29 +0200 Subject: [PATCH 216/834] Import Promotion Actions Controller --- .../admin/promotion_actions_controller.rb | 55 +++++++++++++++++++ .../admin/promotion_actions/create.js.erb | 10 ++++ .../admin/promotion_actions/destroy.js.erb | 1 + .../admin/promotion_actions_request_spec.rb | 23 ++++++++ 4 files changed, 89 insertions(+) create mode 100644 friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/create.js.erb create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/destroy.js.erb create mode 100644 friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_actions_request_spec.rb diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb new file mode 100644 index 00000000000..ad7b8de0629 --- /dev/null +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module Admin + class PromotionActionsController < Spree::Admin::BaseController + before_action :load_promotion, only: [:create, :destroy] + before_action :validate_promotion_action_type, only: :create + + def create + @calculators = Rails.application.config.spree.calculators.promotion_actions_create_adjustments + @promotion_action = @promotion_action_type.new(params[:promotion_action]) + @promotion_action.promotion = @promotion + if @promotion_action.save + flash[:success] = t('spree.successfully_created', resource: t('spree.promotion_action')) + end + respond_to do |format| + format.html { redirect_to spree.edit_admin_promotion_path(@promotion) } + format.js { render layout: false } + end + end + + def destroy + @promotion_action = @promotion.promotion_actions.find(params[:id]) + if @promotion_action.discard + flash[:success] = t('spree.successfully_removed', resource: t('spree.promotion_action')) + end + respond_to do |format| + format.html { redirect_to spree.edit_admin_promotion_path(@promotion) } + format.js { render layout: false } + end + end + + private + + def load_promotion + @promotion = Spree::Promotion.find(params[:promotion_id]) + end + + def validate_promotion_action_type + requested_type = params[:action_type] + promotion_action_types = Rails.application.config.spree.promotions.actions + @promotion_action_type = promotion_action_types.detect do |klass| + klass.name == requested_type + end + if !@promotion_action_type + flash[:error] = t('spree.invalid_promotion_action') + respond_to do |format| + format.html { redirect_to spree.edit_admin_promotion_path(@promotion) } + format.js { render layout: false } + end + end + end + end + end +end diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/create.js.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/create.js.erb new file mode 100644 index 00000000000..6e855ab0a29 --- /dev/null +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/create.js.erb @@ -0,0 +1,10 @@ +$('#actions').append('<%= escape_javascript( render(partial: 'spree/admin/promotions/promotion_action', object: @promotion_action) ) %>'); +$('#actions .no-objects-found').hide(); +$(".variant_autocomplete").variantAutocomplete(); + +initPromotionActions(); + + +$('#<%= dom_id @promotion_action %>').hide(); +$('#<%= dom_id @promotion_action %>').fadeIn(); +new Spree.CalculatorEditView({el: $('#<%= dom_id @promotion_action %> .js-calculator-fields')}); diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/destroy.js.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/destroy.js.erb new file mode 100644 index 00000000000..6b40385a82b --- /dev/null +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/destroy.js.erb @@ -0,0 +1 @@ +$('#<%= dom_id @promotion_action %>').fadeOut().remove(); \ No newline at end of file diff --git a/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_actions_request_spec.rb b/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_actions_request_spec.rb new file mode 100644 index 00000000000..ad25638effb --- /dev/null +++ b/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_actions_request_spec.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe SolidusFriendlyPromotions::Admin::PromotionActionsController, type: :request do + stub_authorization! + + let!(:promotion) { create(:promotion) } + + it "can create a promotion action of a valid type" do + post solidus_friendly_promotions.admin_promotion_promotion_actions_path(promotion_id: promotion.id, action_type: "Spree::Promotion::Actions::CreateAdjustment") + expect(response).to be_redirect + expect(response).to redirect_to spree.edit_admin_promotion_path(promotion) + expect(promotion.actions.count).to eq(1) + end + + it "can not create a promotion action of an invalid type" do + post solidus_friendly_promotions.admin_promotion_promotion_actions_path(promotion_id: promotion.id, action_type: "Spree::InvalidType") + expect(response).to be_redirect + expect(response).to redirect_to spree.edit_admin_promotion_path(promotion) + expect(promotion.rules.count).to eq(0) + end +end From cedfbc1a4fb2b56be915b14052c3e4528f6049d6 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 20 Jun 2023 11:40:55 +0200 Subject: [PATCH 217/834] Add Promotion Categories Admin --- .../admin/promotion_categories_controller.rb | 8 ++ .../admin/promotion_categories/_form.html.erb | 14 +++ .../admin/promotion_categories/edit.html.erb | 10 +++ .../admin/promotion_categories/index.html.erb | 47 ++++++++++ .../admin/promotion_categories/new.html.erb | 10 +++ .../admin/promotion_categories_spec.rb | 85 +++++++++++++++++++ 6 files changed, 174 insertions(+) create mode 100644 friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_categories_controller.rb create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_categories/_form.html.erb create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_categories/edit.html.erb create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_categories/index.html.erb create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_categories/new.html.erb create mode 100644 friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotion_categories_spec.rb diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_categories_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_categories_controller.rb new file mode 100644 index 00000000000..02050100ba8 --- /dev/null +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_categories_controller.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module Admin + class PromotionCategoriesController < Spree::Admin::ResourceController + end + end +end diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_categories/_form.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_categories/_form.html.erb new file mode 100644 index 00000000000..ca965cc93d0 --- /dev/null +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_categories/_form.html.erb @@ -0,0 +1,14 @@ +<%= render partial: 'spree/shared/error_messages', locals: { target: @promotion_category } %> + +
    +
    + <%= f.field_container :name do %> + <%= f.label :name %> + <%= f.text_field :name, class: 'fullwidth' %> + <% end %> + <%= f.field_container :code do %> + <%= f.label :code %> + <%= f.text_field :code, class: 'fullwidth' %> + <% end %> +
    +
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_categories/edit.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_categories/edit.html.erb new file mode 100644 index 00000000000..f6b3136f964 --- /dev/null +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_categories/edit.html.erb @@ -0,0 +1,10 @@ +<% admin_breadcrumb(link_to plural_resource_name(Spree::Promotion), solidus_friendly_promotions.admin_promotions_path) %> +<% admin_breadcrumb(link_to plural_resource_name(Spree::PromotionCategory), solidus_friendly_promotions.admin_promotion_categories_path) %> +<% admin_breadcrumb(@promotion_category.name) %> + +<%= form_for @promotion_category, url: object_url, method: :put do |f| %> +
    + <%= render partial: 'form', locals: { f: f } %> + <%= render partial: 'spree/admin/shared/edit_resource_links' %> +
    +<% end %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_categories/index.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_categories/index.html.erb new file mode 100644 index 00000000000..f9c93f64a1a --- /dev/null +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_categories/index.html.erb @@ -0,0 +1,47 @@ +<% admin_breadcrumb(link_to plural_resource_name(Spree::Promotion), solidus_friendly_promotions.admin_promotions_path) %> +<% admin_breadcrumb(plural_resource_name(Spree::PromotionCategory)) %> + +<% content_for :page_actions do %> + <% if can?(:create, Spree::PromotionCategory) %> +
  • + <%= link_to t('solidus_friendly_promotions.new_promotion_category'), solidus_friendly_promotions.new_admin_promotion_category_path, class: 'btn btn-primary' %> +
  • + <% end %> +<% end %> + +<% if @promotion_categories.any? %> + + + + + + + + + + + + + <% @promotion_categories.each do |promotion_category| %> + + + + + + <% end %> + +
    <%= Spree::PromotionCategory.human_attribute_name :name %><%= Spree::PromotionCategory.human_attribute_name :code %>
    <%= promotion_category.name %><%= promotion_category.code %> + <% if can?(:update, promotion_category) %> + <%= link_to_edit promotion_category, no_text: true %> + <% end %> + <% if can?(:destroy, promotion_category) %> + <%= link_to_delete promotion_category, no_text: true %> + <% end %> +
    +<% else %> +
    + <%= render 'spree/admin/shared/no_objects_found', + resource: Spree::PromotionCategory, + new_resource_url: new_object_url %> +
    +<% end %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_categories/new.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_categories/new.html.erb new file mode 100644 index 00000000000..7b557fc61c2 --- /dev/null +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_categories/new.html.erb @@ -0,0 +1,10 @@ +<% admin_breadcrumb(link_to plural_resource_name(Spree::Promotion), solidus_friendly_promotions.admin_promotions_path) %> +<% admin_breadcrumb(link_to plural_resource_name(Spree::PromotionCategory), solidus_friendly_promotions.admin_promotion_categories_path) %> +<% admin_breadcrumb(t('spree.new_promotion_category')) %> + +<%= form_for :promotion_category, url: collection_url do |f| %> +
    + <%= render partial: 'form', locals: { f: f } %> + <%= render partial: 'spree/admin/shared/new_resource_links' %> +
    +<% end %> diff --git a/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotion_categories_spec.rb b/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotion_categories_spec.rb new file mode 100644 index 00000000000..96a72be5c50 --- /dev/null +++ b/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotion_categories_spec.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'Promotion Categories', type: :system do + stub_authorization! + + context "index" do + before do + create(:promotion_category, name: 'name1', code: 'code1') + create(:promotion_category, name: 'name2', code: 'code2') + visit solidus_friendly_promotions.admin_promotion_categories_path + end + + context "listing promotion categories" do + it "should list the existing promotion categories" do + within_row(1) do + expect(column_text(1)).to eq("name1") + expect(column_text(2)).to eq("code1") + end + + within_row(2) do + expect(column_text(1)).to eq("name2") + expect(column_text(2)).to eq("code2") + end + end + end + end + + context "create" do + before do + visit solidus_friendly_promotions.admin_promotion_categories_path + click_on "New Promotion Category" + end + + it "should allow an admin to create a new promotion category" do + fill_in "promotion_category_name", with: "promotion test" + fill_in "promotion_category_code", with: "prtest" + click_button "Create" + expect(page).to have_content("successfully created!") + end + + it "should not allow admin to create promotion category when invalid data" do + fill_in "promotion_category_name", with: "" + fill_in "promotion_category_code", with: "prtest" + click_button "Create" + expect(page).to have_content("Name can't be blank") + end + end + + context "edit" do + before(:each) do + create(:promotion_category, name: 'name1') + visit solidus_friendly_promotions.admin_promotion_categories_path + within_row(1) { click_icon :edit } + end + + it "should allow an admin to edit an existing promotion category" do + fill_in "promotion_category_name", with: "name 99" + click_button "Update" + expect(page).to have_content("successfully updated!") + expect(page).to have_content("name 99") + end + + it "should show validation errors" do + fill_in "promotion_category_name", with: "" + click_button "Update" + expect(page).to have_content("Name can't be blank") + end + end + + context "delete" do + before(:each) do + create(:promotion_category, name: 'name1') + visit solidus_friendly_promotions.admin_promotion_categories_path + end + + it "should allow an admin to delete an existing promotion category", js: true do + accept_alert do + click_icon :trash + end + expect(page).to have_content("successfully removed!") + end + end +end From aaef28488ccc68d09c1daa968f5ce30093b99d7e Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 20 Jun 2023 11:50:00 +0200 Subject: [PATCH 218/834] Add promotion codes controller --- .../admin/promotion_codes_controller.rb | 48 +++++++++++++++++++ .../admin/promotion_codes/index.csv.ruby | 8 ++++ .../admin/promotion_codes/index.html.erb | 32 +++++++++++++ .../admin/promotion_codes/new.html.erb | 31 ++++++++++++ 4 files changed, 119 insertions(+) create mode 100644 friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_codes_controller.rb create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_codes/index.csv.ruby create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_codes/index.html.erb create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_codes/new.html.erb diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_codes_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_codes_controller.rb new file mode 100644 index 00000000000..5cf5628f98c --- /dev/null +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_codes_controller.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +require 'csv' + +module SolidusFriendlyPromotions + module Admin + class PromotionCodesController < Spree::Admin::ResourceController + def index + @promotion = Spree::Promotion.accessible_by(current_ability, :show).find(params[:promotion_id]) + @promotion_codes = @promotion.promotion_codes.order(:value) + + respond_to do |format| + format.html do + @promotion_codes = @promotion_codes.page(params[:page]).per(50) + end + format.csv do + filename = "promotion-code-list-#{@promotion.id}.csv" + headers["Content-Type"] = "text/csv" + headers["Content-disposition"] = "attachment; filename=\"#{filename}\"" + end + end + end + + def new + @promotion = Spree::Promotion.accessible_by(current_ability, :show).find(params[:promotion_id]) + if @promotion.apply_automatically + flash[:error] = t('activerecord.errors.models.spree/promotion_code.attributes.base.disallowed_with_apply_automatically') + redirect_to admin_promotion_promotion_codes_url(@promotion) + else + @promotion_code = @promotion.promotion_codes.build + end + end + + def create + @promotion = Spree::Promotion.accessible_by(current_ability, :show).find(params[:promotion_id]) + @promotion_code = @promotion.promotion_codes.build(value: params[:promotion_code][:value]) + + if @promotion_code.save + flash[:success] = flash_message_for(@promotion_code, :successfully_created) + redirect_to admin_promotion_promotion_codes_url(@promotion) + else + flash.now[:error] = @promotion_code.errors.full_messages.to_sentence + render_after_create_error + end + end + end + end +end diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_codes/index.csv.ruby b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_codes/index.csv.ruby new file mode 100644 index 00000000000..9299279b0e0 --- /dev/null +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_codes/index.csv.ruby @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +CSV.generate do |csv| + csv << ['Code'] + @promotion_codes.order(:id).pluck(:value).each do |value| + csv << [value] + end +end diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_codes/index.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_codes/index.html.erb new file mode 100644 index 00000000000..75e00969544 --- /dev/null +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_codes/index.html.erb @@ -0,0 +1,32 @@ +<% admin_breadcrumb link_to plural_resource_name(Spree::Promotion), spree.admin_promotions_path %> +<% admin_breadcrumb link_to(@promotion.name, spree.edit_admin_promotion_path(@promotion)) %> +<% admin_breadcrumb plural_resource_name(Spree::PromotionCode) %> + +<% content_for :page_actions do %> +
  • + <% if can?(:create, Spree::PromotionCode) && !@promotion.apply_automatically? %> + <%= link_to t('spree.create_promotion_code'), new_admin_promotion_promotion_code_path(promotion_id: @promotion.id), class: 'btn btn-primary' %> + <% end %> + + <%= link_to t('spree.download_promotion_codes_list'), admin_promotion_promotion_codes_path(promotion_id: @promotion.id, format: :csv), class: 'btn btn-primary' %> +
  • +<% end %> + +
    + <%= page_entries_info(@promotion_codes) %> +
    + + + + + + + <% @promotion_codes.each do |promotion_code| %> + + + + <% end %> + +
    <%= Spree::PromotionCode.human_attribute_name :value %>
    <%= promotion_code.value %>
    + +<%= paginate @promotion_codes, theme: "solidus_admin" %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_codes/new.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_codes/new.html.erb new file mode 100644 index 00000000000..894df09e29e --- /dev/null +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_codes/new.html.erb @@ -0,0 +1,31 @@ +<% admin_breadcrumb link_to plural_resource_name(Spree::Promotion), solidus_friendly_promotions.admin_promotions_path %> +<% admin_breadcrumb link_to(@promotion.name, solidus_friendly_promotions.edit_admin_promotion_path(@promotion)) %> +<% admin_breadcrumb plural_resource_name(Spree::PromotionCode) %> + +<% content_for :page_actions do %> +
  • + <%= link_to t('solidus_friendly_promotions.view_promotion_codes_list'), admin_promotion_promotion_codes_path(promotion_id: @promotion.id), class: 'btn btn-primary' %> + + <%= link_to t('solidus_friendly_promotions.download_promotion_codes_list'), admin_promotion_promotion_codes_path(promotion_id: @promotion.id, format: :csv), class: 'btn btn-primary' %> +
  • +<% end %> + +<%= form_for [:admin, @promotion, @promotion_code], method: :post do |f| %> +
    + <%= render partial: 'spree/shared/error_messages', locals: { target: @promotion_code } %> + +
    +
    + <%= f.field_container :value do %> + <%= f.label :value, class: 'required' %> + <%= f.text_field :value, class: 'fullwidth', required: true %> + <% end %> +
    +
    + +
    + <%= f.submit t('spree.actions.create'), class: 'btn btn-primary' %> + <%= link_to t('spree.actions.cancel'), solidus_friendly_promotions.admin_promotion_promotion_codes_url(@promotion), class: 'button' %> +
    +
    +<% end %> From 761c6a463bfccbf08e602c81b81ea77e9ca74088 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 20 Jun 2023 12:20:19 +0200 Subject: [PATCH 219/834] Adapt the partial path of actions We don't want these namespaced to spree. --- .../app/models/solidus_friendly_promotions/actions/base.rb | 4 ++++ .../actions/adjust_line_item_spec.rb | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/actions/base.rb b/friendly_promotions/app/models/solidus_friendly_promotions/actions/base.rb index bd1574b1715..6f7ed273b20 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/actions/base.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/actions/base.rb @@ -38,6 +38,10 @@ def adjustment_label(adjustable) promotion_name: promotion.name, ) end + + def to_partial_path + "solidus_friendly_promotions/admin/promotions/actions/#{model_name.element}" + end end end end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_line_item_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_line_item_spec.rb index 45214a5d818..d5ee10e2ae4 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_line_item_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_line_item_spec.rb @@ -10,4 +10,9 @@ it { is_expected.to eq("Discount matching line items") } end + + describe ".to_partial_path" do + subject { described_class.new.to_partial_path } + it { is_expected.to eq("solidus_friendly_promotions/admin/promotions/actions/adjust_line_item") } + end end From 0f101313e0464fdcbbd1aaf9f76e5fbd8d1ec5c3 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 20 Jun 2023 12:25:40 +0200 Subject: [PATCH 220/834] Add base rule, implement '#to_partial_path' --- .../models/solidus_friendly_promotions/rules/base.rb | 11 +++++++++++ .../solidus_friendly_promotions/rules/first_order.rb | 2 +- .../rules/first_repeat_purchase_since.rb | 2 +- .../solidus_friendly_promotions/rules/item_total.rb | 2 +- .../rules/line_item_option_value.rb | 2 +- .../rules/line_item_product.rb | 2 +- .../rules/line_item_taxon.rb | 2 +- .../solidus_friendly_promotions/rules/nth_order.rb | 2 +- .../rules/one_use_per_user.rb | 2 +- .../solidus_friendly_promotions/rules/option_value.rb | 2 +- .../solidus_friendly_promotions/rules/product.rb | 2 +- .../models/solidus_friendly_promotions/rules/store.rb | 2 +- .../models/solidus_friendly_promotions/rules/taxon.rb | 2 +- .../models/solidus_friendly_promotions/rules/user.rb | 2 +- .../rules/user_logged_in.rb | 2 +- .../solidus_friendly_promotions/rules/user_role.rb | 2 +- .../rules/first_order_spec.rb | 5 +++++ 17 files changed, 31 insertions(+), 15 deletions(-) create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/rules/base.rb diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/base.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/base.rb new file mode 100644 index 00000000000..4fa28c8f554 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/base.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module Rules + class Base < Spree::PromotionRule + def to_partial_path + "solidus_friendly_promotions/admin/promotions/rules/#{model_name.element}" + end + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_order.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_order.rb index 5b92a0cf9a4..81f00186b20 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_order.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_order.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Rules - class FirstOrder < ::Spree::PromotionRule + class FirstOrder < Base attr_reader :user, :email def applicable?(promotable) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb index 409b2f1ea99..264c262623e 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Rules - class FirstRepeatPurchaseSince < ::Spree::PromotionRule + class FirstRepeatPurchaseSince < Base preference :days_ago, :integer, default: 365 validates :preferred_days_ago, numericality: {only_integer: true, greater_than: 0} diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb index c865830c642..0722f106a8c 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb @@ -7,7 +7,7 @@ module Rules # # To add extra operators please override `self.operators_map` or any other helper method. # To customize the error message you can also override `ineligible_message`. - class ItemTotal < ::Spree::PromotionRule + class ItemTotal < Base preference :amount, :decimal, default: 100.00 preference :currency, :string, default: -> { Spree::Config[:currency] } preference :operator, :string, default: "gt" diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_option_value.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_option_value.rb index 27a2eb46ba3..ac8ef3cb28f 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_option_value.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_option_value.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Rules - class LineItemOptionValue < ::Spree::PromotionRule + class LineItemOptionValue < Base preference :eligible_values, :hash def applicable?(promotable) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb index ad969ce628c..eff1ab54792 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb @@ -3,7 +3,7 @@ module SolidusFriendlyPromotions module Rules # A rule to apply a promotion only to line items with or without a chosen product - class LineItemProduct < Spree::PromotionRule + class LineItemProduct < Base MATCH_POLICIES = %w[include exclude] has_many :product_promotion_rules, diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb index 45caa7fc53f..858d57a680a 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Rules - class LineItemTaxon < ::Spree::PromotionRule + class LineItemTaxon < Base has_many :promotion_rule_taxons, class_name: "Spree::PromotionRuleTaxon", foreign_key: :promotion_rule_id, dependent: :destroy has_many :taxons, through: :promotion_rule_taxons, class_name: "Spree::Taxon" diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb index a8ceb652431..8adba7157c1 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Rules - class NthOrder < ::Spree::PromotionRule + class NthOrder < Base preference :nth_order, :integer, default: 2 # It does not make sense to have this apply to the first order using preferred_nth_order == 1 # Instead we could use the first_order rule diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb index 8cbc905d02a..20edae46d7d 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Rules - class OneUsePerUser < ::Spree::PromotionRule + class OneUsePerUser < Base def applicable?(promotable) promotable.is_a?(Spree::Order) end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/option_value.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/option_value.rb index 84f79875b90..a5004233dee 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/option_value.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/option_value.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Rules - class OptionValue < ::Spree::PromotionRule + class OptionValue < Base preference :eligible_values, :hash def applicable?(promotable) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb index e257adc952a..6de81662aa8 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb @@ -6,7 +6,7 @@ module Rules # require all or any of the products to be present. Valid products # either come from assigned product group or are assingned directly to # the rule. - class Product < ::Spree::PromotionRule + class Product < Base has_many :product_promotion_rules, dependent: :destroy, foreign_key: :promotion_rule_id, class_name: "Spree::ProductPromotionRule" has_many :products, class_name: "Spree::Product", through: :product_promotion_rules diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb index 680600542ff..0ce363c3acd 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Rules - class Store < ::Spree::PromotionRule + class Store < Base has_many :promotion_rule_stores, class_name: "Spree::PromotionRuleStore", foreign_key: :promotion_rule_id, dependent: :destroy diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb index 6b67fea88cf..6e7f5b0077c 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Rules - class Taxon < ::Spree::PromotionRule + class Taxon < Base has_many :promotion_rule_taxons, class_name: "Spree::PromotionRuleTaxon", foreign_key: :promotion_rule_id, dependent: :destroy has_many :taxons, through: :promotion_rule_taxons, class_name: "Spree::Taxon" diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb index 7eee578248b..1be7a6f74d3 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Rules - class User < ::Spree::PromotionRule + class User < Base has_many :promotion_rule_users, class_name: "Spree::PromotionRuleUser", foreign_key: :promotion_rule_id, dependent: :destroy diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb index bec0ae0b101..27a62132408 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Rules - class UserLoggedIn < ::Spree::PromotionRule + class UserLoggedIn < Base def applicable?(promotable) promotable.is_a?(Spree::Order) end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_role.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_role.rb index 42c1bb07f57..1b75e130a7a 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_role.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_role.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Rules - class UserRole < ::Spree::PromotionRule + class UserRole < Base preference :role_ids, :array, default: [] MATCH_POLICIES = %w[any all] diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_order_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_order_spec.rb index 74e95b91a54..8be4c2fcbe7 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_order_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_order_spec.rb @@ -7,6 +7,11 @@ let(:order) { mock_model(Spree::Order, user: nil, email: nil) } let(:user) { mock_model(Spree::LegacyUser) } + describe ".to_partial_path" do + subject { rule.to_partial_path } + it { is_expected.to eq("solidus_friendly_promotions/admin/promotions/rules/first_order") } + end + context "without a user or email" do it { expect(rule).to be_eligible(order) } it "does not set an error message" do From 55ea5eca9575bd1bb8af0835e5b68fd18fffee1f Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 20 Jun 2023 12:29:46 +0200 Subject: [PATCH 221/834] Add rule partials These are imported from the `discount-system` branch. --- .../promotions/rules/_first_order.html.erb | 0 .../_first_repeat_purchase_since.html.erb | 13 +++++++++++++ .../promotions/rules/_item_total.html.erb | 14 ++++++++++++++ .../rules/_line_item_option_value.html.erb | 12 ++++++++++++ .../rules/_line_item_product.html.erb | 18 ++++++++++++++++++ .../promotions/rules/_line_item_taxon.html.erb | 14 ++++++++++++++ .../admin/promotions/rules/_nth_order.html.erb | 12 ++++++++++++ .../rules/_one_use_per_user.html.erb | 0 .../promotions/rules/_option_value.html.erb | 13 +++++++++++++ .../admin/promotions/rules/_product.html.erb | 15 +++++++++++++++ .../admin/promotions/rules/_store.html.erb | 6 ++++++ .../admin/promotions/rules/_taxon.html.erb | 9 +++++++++ .../admin/promotions/rules/_user.html.erb | 4 ++++ .../promotions/rules/_user_logged_in.html.erb | 0 .../admin/promotions/rules/_user_role.html.erb | 12 ++++++++++++ 15 files changed, 142 insertions(+) create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_first_order.html.erb create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_first_repeat_purchase_since.html.erb create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_item_total.html.erb create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_option_value.html.erb create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_product.html.erb create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_taxon.html.erb create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_nth_order.html.erb create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_one_use_per_user.html.erb create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_option_value.html.erb create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_product.html.erb create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_store.html.erb create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_taxon.html.erb create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_user.html.erb create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_user_logged_in.html.erb create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_user_role.html.erb diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_first_order.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_first_order.html.erb new file mode 100644 index 00000000000..e69de29bb2d diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_first_repeat_purchase_since.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_first_repeat_purchase_since.html.erb new file mode 100644 index 00000000000..8e2e587aab7 --- /dev/null +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_first_repeat_purchase_since.html.erb @@ -0,0 +1,13 @@ +
    +
    +
    + <%= Spree::Promotion::Rules::FirstRepeatPurchaseSince.human_attribute_name(:form_text) %> +
    +
    + +
    +
    + <%= number_field_tag "#{param_prefix}[preferred_days_ago]", promotion_rule.preferred_days_ago, class: 'fullwidth' %> +
    +
    +
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_item_total.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_item_total.html.erb new file mode 100644 index 00000000000..1b0767b2ea9 --- /dev/null +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_item_total.html.erb @@ -0,0 +1,14 @@ +
    +
    +
    + <%= select_tag "#{param_prefix}[preferred_operator]", options_for_select(promotion_rule.class.operator_options, promotion_rule.preferred_operator), {class: 'custom-select select_item_total fullwidth'} %> +
    +
    +
    +
    + <%= fields_for param_prefix, promotion_rule do |f| %> + <%= render "spree/admin/shared/number_with_currency", f: f, amount_attr: :preferred_amount, currency_attr: :preferred_currency %> + <% end %> +
    +
    +
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_option_value.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_option_value.html.erb new file mode 100644 index 00000000000..99ca90d34af --- /dev/null +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_option_value.html.erb @@ -0,0 +1,12 @@ +
    + +
    +
    <%= label_tag nil, Spree::Product.model_name.human %>
    +
    <%= label_tag nil, plural_resource_name(Spree::OptionValue) %>
    +
    + + <%= content_tag :div, nil, class: "js-promo-rule-option-values", + data: { :'original-option-values' => promotion_rule.preferred_eligible_values } %> + + +
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_product.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_product.html.erb new file mode 100644 index 00000000000..972486873b6 --- /dev/null +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_product.html.erb @@ -0,0 +1,18 @@ +
    +
    +
    + <%= label_tag "#{param_prefix}_product_ids_string", t('spree.product_rule.choose_products') %> + <%= hidden_field_tag "#{param_prefix}[product_ids_string]", promotion_rule.product_ids.join(","), class: "product_picker fullwidth" %> +
    +
    +
    +
    + <%= label_tag("#{param_prefix}_preferred_match_policy", promotion_rule.class.human_attribute_name(:preferred_match_policy)) %> + <%= select_tag( + "#{param_prefix}[preferred_match_policy]", + options_for_select(I18n.t("spree.promotion_rules.line_item_product.match_policies").to_a.map(&:reverse), promotion_rule.preferred_match_policy), + class: "custom-select fullwidth" + ) %> +
    +
    +
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_taxon.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_taxon.html.erb new file mode 100644 index 00000000000..c06d03c79d8 --- /dev/null +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_taxon.html.erb @@ -0,0 +1,14 @@ +
    + <%= label_tag "#{param_prefix}_taxon_ids_string", t("spree.taxon_rule.choose_taxons") %> + <%= hidden_field_tag "#{param_prefix}[taxon_ids_string]", promotion_rule.taxon_ids.join(","), class: "taxon_picker fullwidth", id: "product_taxon_ids" %> +
    +
    +
    + <%= label_tag("#{param_prefix}_preferred_match_policy", promotion_rule.class.human_attribute_name(:preferred_match_policy)) %> + <%= select_tag( + "#{param_prefix}[preferred_match_policy]", + options_for_select(I18n.t("spree.promotion_rules.line_item_taxon.match_policies").to_a.map(&:reverse), promotion_rule.preferred_match_policy), + class: "custom-select fullwidth", + ) %> +
    +
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_nth_order.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_nth_order.html.erb new file mode 100644 index 00000000000..0a37ce60cf1 --- /dev/null +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_nth_order.html.erb @@ -0,0 +1,12 @@ +
    +
    +
    + <%= Spree::Promotion::Rules::NthOrder.human_attribute_name(:form_text) %> +
    +
    +
    +
    + <%= number_field_tag "#{param_prefix}[preferred_nth_order]", promotion_rule.preferred_nth_order, class: 'fullwidth' %> +
    +
    +
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_one_use_per_user.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_one_use_per_user.html.erb new file mode 100644 index 00000000000..e69de29bb2d diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_option_value.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_option_value.html.erb new file mode 100644 index 00000000000..093148c1cfe --- /dev/null +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_option_value.html.erb @@ -0,0 +1,13 @@ +
    + +
    +
    <%= label_tag nil, Spree::Product.model_name.human %>
    +
    <%= label_tag nil, plural_resource_name(Spree::OptionValue) %>
    +
    + + <%= content_tag :div, nil, class: "js-promo-rule-option-values", + data: { :'original-option-values' => promotion_rule.preferred_eligible_values } %> + + + +
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_product.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_product.html.erb new file mode 100644 index 00000000000..2d11f098832 --- /dev/null +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_product.html.erb @@ -0,0 +1,15 @@ +
    +
    +
    + <%= label_tag "#{param_prefix}_product_ids_string", t('spree.product_rule.choose_products') %> + <%= hidden_field_tag "#{param_prefix}[product_ids_string]", promotion_rule.product_ids.join(","), class: "product_picker fullwidth" %> +
    +
    +
    +
    + +
    +
    +
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_store.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_store.html.erb new file mode 100644 index 00000000000..cce8ac2e37f --- /dev/null +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_store.html.erb @@ -0,0 +1,6 @@ +
    + + <%= select_tag "#{param_prefix}[store_ids][]", + options_from_collection_for_select(Spree::Store.all, :id, :name, promotion_rule.store_ids), + multiple: true, class: "select2 fullwidth" %> +
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_taxon.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_taxon.html.erb new file mode 100644 index 00000000000..bec9eb7e9c3 --- /dev/null +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_taxon.html.erb @@ -0,0 +1,9 @@ +
    + <%= label_tag "#{param_prefix}_taxon_ids_string", t('spree.taxon_rule.choose_taxons') %> + <%= hidden_field_tag "#{param_prefix}[taxon_ids_string]", promotion_rule.taxon_ids.join(","), class: "taxon_picker fullwidth", id: 'product_taxon_ids' %> +
    +
    + +
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_user.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_user.html.erb new file mode 100644 index 00000000000..91391a18fd0 --- /dev/null +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_user.html.erb @@ -0,0 +1,4 @@ +
    +
    + +
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_user_logged_in.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_user_logged_in.html.erb new file mode 100644 index 00000000000..e69de29bb2d diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_user_role.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_user_role.html.erb new file mode 100644 index 00000000000..e078526d08f --- /dev/null +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_user_role.html.erb @@ -0,0 +1,12 @@ +
    + <%= label_tag "#{param_prefix}_preferred_role_ids", t('spree.user_role_rule.choose_roles') %> + <%= select_tag "#{param_prefix}[preferred_role_ids]", + options_from_collection_for_select( + Spree::Role.all, :id, :name, promotion_rule.preferred_role_ids + ), class: 'select2 fullwidth', multiple: true %> +
    +
    + +
    From e5c74dfb109cadd5f5b50fc190d95dd184b71888 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 20 Jun 2023 16:21:16 +0200 Subject: [PATCH 222/834] Depend on Turbo-Rails We want to use Turbo for the promotions admin --- friendly_promotions/solidus_friendly_promotions.gemspec | 1 + 1 file changed, 1 insertion(+) diff --git a/friendly_promotions/solidus_friendly_promotions.gemspec b/friendly_promotions/solidus_friendly_promotions.gemspec index d066341da4e..b485d014438 100644 --- a/friendly_promotions/solidus_friendly_promotions.gemspec +++ b/friendly_promotions/solidus_friendly_promotions.gemspec @@ -30,6 +30,7 @@ Gem::Specification.new do |spec| spec.add_dependency "solidus_core", [">= 4.0.0", "< 5"] spec.add_dependency "solidus_support", "~> 0.5" + spec.add_dependency "turbo-rails", "~> 1.4" spec.add_development_dependency "solidus_dev_support", "~> 2.6" spec.add_development_dependency "rspec-activemodel-mocks", "~> 1.0" From 5f5b8d03dab70062df391a59bb8d6581748f5514 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 20 Jun 2023 16:22:22 +0200 Subject: [PATCH 223/834] Add more translations from Solidus --- friendly_promotions/config/locales/en.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/friendly_promotions/config/locales/en.yml b/friendly_promotions/config/locales/en.yml index b97e5d2e961..8f8a5f945ed 100644 --- a/friendly_promotions/config/locales/en.yml +++ b/friendly_promotions/config/locales/en.yml @@ -3,8 +3,16 @@ en: solidus_friendly_promotions: - new_promotion: New Promotion + create_promotion_code: Create promotion code current_promotion_usage: 'Current Usage: %{count}' + discount_rules: Promotion Rules + download_promotion_codes_list: Download codes list + new_promotion: New Promotion + new_promotion_category: New Promotion Category + new_promotion_code_batch: New Promotion Code Batch + no_rules_addes: No Rules Added + promotion_successfully_created: Promotion has been successfully created! + view_promotion_codes_list: View codes list admin: promotions: actions: From 1dcc6735395f2d898908a69e842e4fdd1da16c05 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 20 Jun 2023 16:22:55 +0200 Subject: [PATCH 224/834] Add all promotion routes --- friendly_promotions/config/routes.rb | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/friendly_promotions/config/routes.rb b/friendly_promotions/config/routes.rb index a439442403c..3ad87c0cbe9 100644 --- a/friendly_promotions/config/routes.rb +++ b/friendly_promotions/config/routes.rb @@ -2,6 +2,14 @@ SolidusFriendlyPromotions::Engine.routes.draw do namespace :admin do - resources :promotions, only: [:index, :new] + resources :promotions do + resources :promotion_rules + resources :promotion_actions + resources :promotion_codes, only: [:index, :new, :create] + resources :promotion_code_batches, only: [:index, :new, :create] do + get '/download', to: "promotion_code_batches#download", defaults: { format: "csv" } + end + end + resources :promotion_categories, except: [:show] end end From c88a598ce266940de48de2d14ef108973abc5de7 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 20 Jun 2023 16:24:02 +0200 Subject: [PATCH 225/834] Add promotions create action --- .../admin/promotions_controller.rb | 20 +++++++++++++++++++ .../admin/promotion_rules_helper.rb | 14 +++++++++++++ .../configuration.rb | 2 ++ 3 files changed, 36 insertions(+) create mode 100644 friendly_promotions/app/helpers/solidus_friendly_promotions/admin/promotion_rules_helper.rb diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb index 7a29b5f946c..9ecc56a34a1 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb @@ -5,6 +5,26 @@ module Admin class PromotionsController < ::Spree::Admin::ResourceController before_action :load_data + helper 'solidus_friendly_promotions/admin/promotion_rules' + + def create + @promotion = Spree::Promotion.new(permitted_resource_params) + @promotion.codes.new(value: params[:single_code]) if params[:single_code].present? + + if params[:promotion_code_batch] + @promotion_code_batch = @promotion.promotion_code_batches.new(promotion_code_batch_params) + end + + if @promotion.save + @promotion_code_batch.process if @promotion_code_batch + flash[:success] = t('solidus_friendly_promotions.promotion_successfully_created') + redirect_to location_after_save + else + flash[:error] = @promotion.errors.full_messages.to_sentence + render action: 'new' + end + end + private def collection diff --git a/friendly_promotions/app/helpers/solidus_friendly_promotions/admin/promotion_rules_helper.rb b/friendly_promotions/app/helpers/solidus_friendly_promotions/admin/promotion_rules_helper.rb new file mode 100644 index 00000000000..ae7c1e21174 --- /dev/null +++ b/friendly_promotions/app/helpers/solidus_friendly_promotions/admin/promotion_rules_helper.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module Admin + module PromotionRulesHelper + def options_for_promotion_rule_types(promotion, level) + existing = promotion.rules.map { |rule| rule.class.name } + rules = SolidusFriendlyPromotions.config.send("#{level}_rules").reject { |rule| existing.include? rule.name } + options = rules.map { |rule| [rule.model_name.human, rule.name] } + options_for_select(options) + end + end + end +end diff --git a/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb b/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb index 21fe9c8f71d..825bca57453 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb @@ -13,6 +13,8 @@ class Configuration < Spree::Preferences::Configuration add_class_set :line_item_rules add_class_set :shipment_rules + add_class_set :actions + class_name_attribute :promotion_chooser_class, default: "SolidusFriendlyPromotions::PromotionAdjustmentChooser" end From 896eeb73a9022aa389a7880b59760105fa2c362d Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 20 Jun 2023 16:25:08 +0200 Subject: [PATCH 226/834] Validate with our gem's promotion actions --- .../admin/promotion_actions_controller.rb | 2 +- .../install/templates/initializer.rb | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb index ad7b8de0629..5ee79681992 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb @@ -38,7 +38,7 @@ def load_promotion def validate_promotion_action_type requested_type = params[:action_type] - promotion_action_types = Rails.application.config.spree.promotions.actions + promotion_action_types = SolidusFriendlyPromotions.config.actions @promotion_action_type = promotion_action_types.detect do |klass| klass.name == requested_type end diff --git a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb index 743e89ef52d..6db51718192 100644 --- a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb +++ b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb @@ -54,4 +54,9 @@ "SolidusFriendlyPromotions::Rules::LineItemTaxon", ] config.shipment_rules = [] + + config.actions = [ + "SolidusFriendlyPromotions::Actions::AdjustLineItem", + "SolidusFriendlyPromotions::Actions::AdjustShipment", + ] end From 50b33a090d295d8a7e22ec5c19e9c4d7b481d738 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 20 Jun 2023 16:26:32 +0200 Subject: [PATCH 227/834] Import Promotion Code Batches Controller --- .../promotion_code_batches_controller.rb | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_code_batches_controller.rb diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_code_batches_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_code_batches_controller.rb new file mode 100644 index 00000000000..850c9ce772f --- /dev/null +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_code_batches_controller.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module Admin + class PromotionCodeBatchesController < Spree::Admin::ResourceController + belongs_to 'spree/promotion' + + create.after :build_promotion_code_batch + + def download + require "csv" + + @promotion_code_batch = Spree::PromotionCodeBatch.find( + params[:promotion_code_batch_id] + ) + + send_data( + render_to_string, + filename: "promotion-code-batch-list-#{@promotion_code_batch.id}.csv" + ) + end + + private + + def build_promotion_code_batch + @promotion_code_batch.process + end + end + end +end From c443e5099dff683148b89fe724384bfdc5b8bca9 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 21 Jun 2023 09:13:14 +0200 Subject: [PATCH 228/834] Always redirect to edit page after promo edit Taken from Solidus. --- .../admin/promotions_controller.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb index 9ecc56a34a1..bf76eda3694 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb @@ -50,6 +50,10 @@ def load_data @calculators = Rails.application.config.spree.calculators.promotion_actions_create_adjustments @promotion_categories = Spree::PromotionCategory.order(:name) end + + def location_after_save + solidus_friendly_promotions.edit_admin_promotion_url(@promotion) + end end end end From 529dd4c55e50c57297509f915ed107973a30ab8d Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 21 Jun 2023 12:18:51 +0200 Subject: [PATCH 229/834] Add promotions edit view This will only allow you to edit order-level rules for now. --- .../admin/promotion_rules_controller.rb | 27 +++++++------ .../admin/promotion_rules/create.js.erb | 8 ---- .../admin/promotion_rules/destroy.js.erb | 3 -- .../admin/promotion_rules/new.html.erb | 16 ++++++++ .../admin/promotions/_new_rule_form.html.erb | 18 +++++++++ .../admin/promotions/_promotion_rule.html.erb | 18 +++++++++ .../admin/promotions/edit.html.erb | 38 +++++++++++++++++++ friendly_promotions/config/locales/en.yml | 2 + 8 files changed, 107 insertions(+), 23 deletions(-) delete mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/create.js.erb delete mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/destroy.js.erb create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/new.html.erb create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_new_rule_form.html.erb create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_promotion_rule.html.erb create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/edit.html.erb diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb index 340311655f1..2e7b3c21670 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb @@ -2,11 +2,16 @@ module SolidusFriendlyPromotions module Admin - class PromotionRulesController < Spree::Admin::BaseController + class PromotionRulesController < Spree::Admin::ResourceController helper 'spree/promotion_rules' - before_action :load_promotion, only: [:create, :destroy] - before_action :validate_promotion_rule_type, only: :create + before_action :load_promotion, only: [:create, :destroy, :update, :new] + before_action :validate_promotion_rule_type, only: [:create, :new] + + def new + @promotion_rule = @promotion.promotion_rules.build(type: @promotion_rule_type) + render layout: false + end def create @promotion_rule = @promotion_rule_type.new(promotion_rule_params) @@ -14,10 +19,7 @@ def create if @promotion_rule.save flash[:success] = t('spree.successfully_created', resource: t('spree.promotion_rule')) end - respond_to do |format| - format.html { redirect_to spree.edit_admin_promotion_path(@promotion) } - format.js { render layout: false } - end + redirect_to location_after_save end def destroy @@ -25,14 +27,15 @@ def destroy if @promotion_rule.destroy flash[:success] = t('spree.successfully_removed', resource: t('spree.promotion_rule')) end - respond_to do |format| - format.html { redirect_to spree.edit_admin_promotion_path(@promotion) } - format.js { render layout: false } - end + redirect_to location_after_save end private + def location_after_save + solidus_friendly_promotions.edit_admin_promotion_path(@promotion) + end + def load_promotion @promotion = Spree::Promotion.find(params[:promotion_id]) end @@ -43,7 +46,7 @@ def model_class def validate_promotion_rule_type requested_type = params[:promotion_rule].delete(:type) - promotion_rule_types = Rails.application.config.spree.promotions.rules + promotion_rule_types = SolidusFriendlyPromotions.config.order_rules @promotion_rule_type = promotion_rule_types.detect do |klass| klass.name == requested_type end diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/create.js.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/create.js.erb deleted file mode 100644 index 9d806a88dfe..00000000000 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/create.js.erb +++ /dev/null @@ -1,8 +0,0 @@ -$('#rules').append('<%= escape_javascript( render(partial: 'spree/admin/promotions/promotion_rule', object: @promotion_rule) ) %>'); -$('#rules .no-objects-found').hide(); - -$('.product_picker').productAutocomplete(); -$('.user_picker').userAutocomplete(); -$('.taxon_picker').taxonAutocomplete(); - -$('#promotion_rule_type').html('<%= escape_javascript options_for_promotion_rule_types(@promotion) %>'); diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/destroy.js.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/destroy.js.erb deleted file mode 100644 index c5490e5bd47..00000000000 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/destroy.js.erb +++ /dev/null @@ -1,3 +0,0 @@ -$('#<%= dom_id @promotion_rule %>').fadeOut().remove(); - -$('#promotion_rule_type').html('<%= escape_javascript options_for_promotion_rule_types(@promotion) %>'); \ No newline at end of file diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/new.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/new.html.erb new file mode 100644 index 00000000000..fcc28fc2ce6 --- /dev/null +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/new.html.erb @@ -0,0 +1,16 @@ +<%= turbo_frame_tag @promotion, "new_promotion_rule" do %> + <%= form_tag solidus_friendly_promotions.admin_promotion_promotion_rules_path(@promotion) do %> +
    +
    <%= @promotion_rule.class.human_attribute_name(:description) %>
    + <%= link_to_with_icon 'trash', '', spree.edit_admin_promotion_path(@promotion), class: 'delete' %> + <%= render partial: "spree/shared/error_messages", locals: { target: @promotion_rule } %> + <%= hidden_field_tag "promotion_rule[type]", @promotion_rule.class.name %> + <%= render partial: @promotion_rule.to_partial_path, locals: { promotion_rule: @promotion_rule, param_prefix: "promotion_rule" } %> +
    +
    + <%= button_tag "Add", class: "btn btn-secondary float-right", data: { "turbo-frame" => "_top" } %> +
    +
    +
    + <% end %> +<% end %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_new_rule_form.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_new_rule_form.html.erb new file mode 100644 index 00000000000..02c09e3fd04 --- /dev/null +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_new_rule_form.html.erb @@ -0,0 +1,18 @@ +<%= turbo_frame_tag @promotion, "new_promotion_rule" do %> +
    + <%= form_tag solidus_friendly_promotions.new_admin_promotion_promotion_rule_path(@promotion), method: :get, remote: false do %> + <% if can?(:update, @promotion) %> +
    + <%= label_tag :promotion_rule_type, Spree::PromotionRule.human_attribute_name(:type) %> +
    + <%= select_tag('promotion_rule[type]', options_for_promotion_rule_types(@promotion, level), include_blank: t(:choose_promotion_rule, scope: 'spree'), class: 'custom-select fullwidth', required: true) %> + <%= hidden_field_tag("level", level) %> +
    + <%= button_tag t('spree.actions.add'), class: 'btn btn-secondary' %> +
    +
    +
    + <% end %> + <% end %> +
    +<% end %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_promotion_rule.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_promotion_rule.html.erb new file mode 100644 index 00000000000..d02651a055a --- /dev/null +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_promotion_rule.html.erb @@ -0,0 +1,18 @@ + +
    + <%= form_tag solidus_friendly_promotions.admin_promotion_promotion_rule_path(@promotion, promotion_rule), method: :patch do %> +
    <%= promotion_rule.class.human_attribute_name(:description) %>
    + <% if can?(:destroy, promotion_rule) %> + <%= link_to_with_icon 'trash', '', spree.admin_promotion_promotion_rule_path(@promotion, promotion_rule), method: :delete, class: 'delete' %> + <% end %> + <% param_prefix = "promotion[promotion_rules_attributes][#{promotion_rule.id}]" %> + <%= hidden_field_tag "#{param_prefix}[id]", promotion_rule.id %> + <%= render partial: "spree/shared/error_messages", locals: { target: promotion_rule } %> + <%= render partial: promotion_rule.to_partial_path, locals: { promotion_rule: promotion_rule, param_prefix: "promotion_rule" } %> +
    +
    + <%= button_tag "Update", class: "btn btn-secondary float-right" if promotion_rule.preferences.any? %> +
    +
    + <% end %> +
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/edit.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/edit.html.erb new file mode 100644 index 00000000000..b10f312f621 --- /dev/null +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/edit.html.erb @@ -0,0 +1,38 @@ +<% admin_layout "full-width" %> + +<% admin_breadcrumb(link_to plural_resource_name(Spree::Promotion), solidus_friendly_promotions.admin_promotions_path) %> +<% admin_breadcrumb(@promotion.name) %> + +<%= content_for :head do %> + <%= javascript_importmap_tags "@hotwired/turbo-rails" %> +<% end %> + +<% content_for :page_actions do %> +
  • + <% if can?(:show, Spree::PromotionCode) %> + <%= link_to t('solidus_friendly_promotions.view_promotion_codes_list'), solidus_friendly_promotions.admin_promotion_promotion_codes_path(promotion_id: @promotion.id), class: 'btn btn-primary' %> + + <%= link_to t('solidus_friendly_promotions.download_promotion_codes_list'), solidus_friendly_promotions.admin_promotion_promotion_codes_path(promotion_id: @promotion.id, format: :csv), class: 'btn btn-primary' %> + <% end %> + + <% if can?(:show, Spree::PromotionCodeBatch) %> + <%= link_to plural_resource_name(Spree::PromotionCodeBatch), solidus_friendly_promotions.admin_promotion_promotion_code_batches_path(promotion_id: @promotion.id), class: 'btn btn-primary' %> + <% end %> +
  • +<% end %> + +<%= form_for @promotion, url: object_url, method: :put do |f| %> + <%= render partial: 'form', locals: { f: f } %> + <% if can?(:update, @promotion) %> + <%= render partial: 'spree/admin/shared/edit_resource_links' %> + <% end %> +<% end %> + +
    + <%= t('.order_rules') %> + +
    + <%= render partial: 'promotion_rule', collection: @promotion.rules, locals: {} %> + <%= render "new_rule_form", level: :order %> +
    +
    diff --git a/friendly_promotions/config/locales/en.yml b/friendly_promotions/config/locales/en.yml index 8f8a5f945ed..b83a7be7e54 100644 --- a/friendly_promotions/config/locales/en.yml +++ b/friendly_promotions/config/locales/en.yml @@ -30,6 +30,8 @@ en: expires_at_placeholder: Never general: General starts_at_placeholder: Immediately + edit: + order_rules: Order Rules activerecord: models: solidus_friendly_promotions/actions/adjust_shipment: Discount matching shipments From 52dbc82611c88d4fde78458a154defd26c11369d Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 21 Jun 2023 12:21:05 +0200 Subject: [PATCH 230/834] Add Turbo-Rails to install generator --- .../install/install_generator.rb | 8 ++------ friendly_promotions/lib/solidus_friendly_promotions.rb | 1 + 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/install_generator.rb b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/install_generator.rb index 3c8d47d459a..ec1ebee171f 100644 --- a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/install_generator.rb +++ b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/install_generator.rb @@ -14,12 +14,8 @@ def copy_initializer template "initializer.rb", "config/initializers/solidus_friendly_promotions.rb" end - def add_javascripts - append_file "vendor/assets/javascripts/spree/backend/all.js", "//= require spree/backend/solidus_friendly_promotions\n" - end - - def add_stylesheets - inject_into_file "vendor/assets/stylesheets/spree/backend/all.css", " *= require spree/backend/solidus_friendly_promotions\n", before: %r{\*/}, verbose: true # rubocop:disable Layout/LineLength + def add_turbo_rails + run "bin/rails turbo:install" end def add_migrations diff --git a/friendly_promotions/lib/solidus_friendly_promotions.rb b/friendly_promotions/lib/solidus_friendly_promotions.rb index 3d288c4212b..06fe7d45aa4 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require "spree" +require "turbo-rails" require "solidus_friendly_promotions/configuration" require "solidus_friendly_promotions/version" require "solidus_friendly_promotions/engine" From c122aa0985159e28dd6711a0d49a0bbf4a25348b Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 23 Jun 2023 11:20:37 +0200 Subject: [PATCH 231/834] Minimally working actions --- .../admin/promotion_actions_controller.rb | 33 ++++++----- .../admin/promotions_controller.rb | 1 - .../admin/promotion_rules_helper.rb | 18 ++++++ .../admin/promotion_actions/create.js.erb | 10 ---- .../admin/promotion_actions/destroy.js.erb | 1 - .../admin/promotion_actions/new.html.erb | 26 +++++++++ .../promotions/_new_action_form.html.erb | 23 ++++++++ .../promotions/_promotion_action.html.erb | 21 +++++++ .../actions/_adjust_line_item.html.erb | 5 ++ .../actions/_adjust_shipment.html.erb | 5 ++ .../promotions/actions/_calculator_fields.erb | 11 ++++ .../calculators/_default_fields.html.erb | 6 ++ .../distributed_amount/_fields.html.erb | 56 +++++++++++++++++++ .../calculators/flat_rate/_fields.html.erb | 6 ++ .../tiered_flat_rate/_fields.html.erb | 30 ++++++++++ .../tiered_percent/_fields.html.erb | 30 ++++++++++ .../admin/promotions/edit.html.erb | 11 ++++ friendly_promotions/config/locales/en.yml | 8 +++ 18 files changed, 275 insertions(+), 26 deletions(-) delete mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/create.js.erb delete mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/destroy.js.erb create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/new.html.erb create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_new_action_form.html.erb create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_promotion_action.html.erb create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/actions/_adjust_line_item.html.erb create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/actions/_adjust_shipment.html.erb create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/actions/_calculator_fields.erb create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/calculators/_default_fields.html.erb create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/calculators/distributed_amount/_fields.html.erb create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/calculators/flat_rate/_fields.html.erb create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/calculators/tiered_flat_rate/_fields.html.erb create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/calculators/tiered_percent/_fields.html.erb diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb index 5ee79681992..d02821ef38a 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb @@ -2,21 +2,25 @@ module SolidusFriendlyPromotions module Admin - class PromotionActionsController < Spree::Admin::BaseController - before_action :load_promotion, only: [:create, :destroy] - before_action :validate_promotion_action_type, only: :create + class PromotionActionsController < Spree::Admin::ResourceController + before_action :load_promotion + before_action :validate_promotion_action_type, only: [:create, :new] + + def new + @promotion_action = @promotion.promotion_actions.build( + type: @promotion_action_type, + calculator_type: params[:promotion_action][:calculator_type] + ) + render layout: false + end def create - @calculators = Rails.application.config.spree.calculators.promotion_actions_create_adjustments - @promotion_action = @promotion_action_type.new(params[:promotion_action]) + @promotion_action = @promotion_action_type.new(permitted_resource_params) @promotion_action.promotion = @promotion if @promotion_action.save flash[:success] = t('spree.successfully_created', resource: t('spree.promotion_action')) end - respond_to do |format| - format.html { redirect_to spree.edit_admin_promotion_path(@promotion) } - format.js { render layout: false } - end + redirect_to location_after_save end def destroy @@ -24,20 +28,21 @@ def destroy if @promotion_action.discard flash[:success] = t('spree.successfully_removed', resource: t('spree.promotion_action')) end - respond_to do |format| - format.html { redirect_to spree.edit_admin_promotion_path(@promotion) } - format.js { render layout: false } - end + redirect_to location_after_save end private + def location_after_save + solidus_friendly_promotions.edit_admin_promotion_path(@promotion) + end + def load_promotion @promotion = Spree::Promotion.find(params[:promotion_id]) end def validate_promotion_action_type - requested_type = params[:action_type] + requested_type = params[:promotion_action].delete(:type) promotion_action_types = SolidusFriendlyPromotions.config.actions @promotion_action_type = promotion_action_types.detect do |klass| klass.name == requested_type diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb index bf76eda3694..f5f7d27ae23 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb @@ -47,7 +47,6 @@ def promotion_includes end def load_data - @calculators = Rails.application.config.spree.calculators.promotion_actions_create_adjustments @promotion_categories = Spree::PromotionCategory.order(:name) end diff --git a/friendly_promotions/app/helpers/solidus_friendly_promotions/admin/promotion_rules_helper.rb b/friendly_promotions/app/helpers/solidus_friendly_promotions/admin/promotion_rules_helper.rb index ae7c1e21174..48f577d8b26 100644 --- a/friendly_promotions/app/helpers/solidus_friendly_promotions/admin/promotion_rules_helper.rb +++ b/friendly_promotions/app/helpers/solidus_friendly_promotions/admin/promotion_rules_helper.rb @@ -9,6 +9,24 @@ def options_for_promotion_rule_types(promotion, level) options = rules.map { |rule| [rule.model_name.human, rule.name] } options_for_select(options) end + + def promotion_rules_by_level(promotion, level) + promotion.rules.select do |rule| + rule.class.name.in?(SolidusFriendlyPromotions.config.send("#{level}_rules")) + end + end + + def promotion_actions_by_level(promotion, level) + promotion.actions.select do |rule| + rule.class.name.demodulize.underscore.ends_with?(level.to_s) + end + end + + def options_for_promotion_action_calculator_types(level) + calculators = SolidusFriendlyPromotions.config.send("#{level}_discount_calculators") + options = calculators.map { |calculator| [calculator.model_name.human, calculator.name] } + options_for_select(options) + end end end end diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/create.js.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/create.js.erb deleted file mode 100644 index 6e855ab0a29..00000000000 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/create.js.erb +++ /dev/null @@ -1,10 +0,0 @@ -$('#actions').append('<%= escape_javascript( render(partial: 'spree/admin/promotions/promotion_action', object: @promotion_action) ) %>'); -$('#actions .no-objects-found').hide(); -$(".variant_autocomplete").variantAutocomplete(); - -initPromotionActions(); - - -$('#<%= dom_id @promotion_action %>').hide(); -$('#<%= dom_id @promotion_action %>').fadeIn(); -new Spree.CalculatorEditView({el: $('#<%= dom_id @promotion_action %> .js-calculator-fields')}); diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/destroy.js.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/destroy.js.erb deleted file mode 100644 index 6b40385a82b..00000000000 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/destroy.js.erb +++ /dev/null @@ -1 +0,0 @@ -$('#<%= dom_id @promotion_action %>').fadeOut().remove(); \ No newline at end of file diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/new.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/new.html.erb new file mode 100644 index 00000000000..f4e5ae6a249 --- /dev/null +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/new.html.erb @@ -0,0 +1,26 @@ +<%= turbo_frame_tag @promotion, "new_promotion_action" do %> + <%= form_tag solidus_friendly_promotions.admin_promotion_promotion_actions_path(@promotion) do %> +
    +
    <%= @promotion_action.class.human_attribute_name(:description) %>
    + <%= link_to_with_icon 'trash', '', spree.edit_admin_promotion_path(@promotion), class: 'delete' %> + <%= render partial: "spree/shared/error_messages", locals: { target: @promotion_action } %> + <%= hidden_field_tag "promotion_action[type]", @promotion_action.class.name %> + <%= hidden_field_tag "promotion_action[calculator_attributes][type]", @promotion_action.calculator.class.name %> + <% type_name = @promotion_action.calculator.type.demodulize.underscore %> + <% param_prefix = "promotion_action" %> + <% if lookup_context.exists?("fields", + ["spree/admin/promotions/calculators/#{type_name}"], true) %> + <%= render "spree/admin/promotions/calculators/#{type_name}/fields", + calculator: @promotion_action.calculator, prefix: param_prefix %> + <% else %> + <%= render "spree/admin/promotions/calculators/default_fields", + calculator: @promotion_action.calculator, prefix: param_prefix %> + <% end %> +
    +
    + <%= button_tag "Add", class: "btn btn-secondary float-right", data: { "turbo-frame" => "_top" } %> +
    +
    +
    + <% end %> +<% end %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_new_action_form.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_new_action_form.html.erb new file mode 100644 index 00000000000..3e78bf1a905 --- /dev/null +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_new_action_form.html.erb @@ -0,0 +1,23 @@ +<%= turbo_frame_tag @promotion, "new_promotion_action" do %> +
    +
    <%= t(level, scope: 'solidus_friendly_promotions.add_action') %>
    + <%= form_tag solidus_friendly_promotions.new_admin_promotion_promotion_action_path(@promotion), method: :get, remote: false do %> + <% if can?(:update, @promotion) %> +
    + <%= label_tag :promotion_action_calculator_type, Spree::PromotionAction.human_attribute_name(:calculator_type) %> + <%= + select_tag( + 'promotion_action[calculator_type]', options_for_promotion_action_calculator_types(level), + include_blank: t(:choose_promotion_rule, scope: 'spree'), + class: 'custom-select fullwidth', + onchange: 'this.form.requestSubmit()', + required: true + ) + %> + <%= hidden_field_tag('promotion_action[type]', "SolidusFriendlyPromotions::Actions::Adjust#{level.to_s.camelize}") %> + <%= hidden_field_tag("level", level) %> +
    + <% end %> + <% end %> +
    +<% end %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_promotion_action.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_promotion_action.html.erb new file mode 100644 index 00000000000..8ee9a2421a4 --- /dev/null +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_promotion_action.html.erb @@ -0,0 +1,21 @@ +
    +
    <%= promotion_action.class.human_attribute_name(:description) %>
    + <% if promotion_action.persisted? && can?(:destroy, promotion_action) %> + <%= link_to_with_icon 'trash', '', solidus_friendly_promotions.admin_promotion_promotion_action_path(@promotion, promotion_action), method: :delete, class: 'delete' %> + <% end %> + <%= form_tag solidus_friendly_promotions.admin_promotion_promotion_action_path(@promotion, promotion_action), method: :patch do %> + + <% param_prefix = "promotion_action" %> + <%= hidden_field_tag "#{param_prefix}[type]", promotion_action.type %> + + <%= render partial: "spree/shared/error_messages", locals: { target: promotion_action } %> + <%= render partial: promotion_action.to_partial_path, + locals: { promotion_action: promotion_action, param_prefix: param_prefix } %> +
    +
    + <% button_label = promotion_action.persisted? ? :update : :add %> + <%= button_tag t(button_label, scope: 'solidus_friendly_promotions.crud'), class: "btn btn-secondary float-right"%> +
    +
    + <% end %> +
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/actions/_adjust_line_item.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/actions/_adjust_line_item.html.erb new file mode 100644 index 00000000000..c421dba62b0 --- /dev/null +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/actions/_adjust_line_item.html.erb @@ -0,0 +1,5 @@ +<%= render( + "solidus_friendly_promotions/admin/promotions/actions/calculator_fields", + promotion_action: promotion_action, + param_prefix: param_prefix +) %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/actions/_adjust_shipment.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/actions/_adjust_shipment.html.erb new file mode 100644 index 00000000000..c421dba62b0 --- /dev/null +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/actions/_adjust_shipment.html.erb @@ -0,0 +1,5 @@ +<%= render( + "solidus_friendly_promotions/admin/promotions/actions/calculator_fields", + promotion_action: promotion_action, + param_prefix: param_prefix +) %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/actions/_calculator_fields.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/actions/_calculator_fields.erb new file mode 100644 index 00000000000..e412b3bc0d7 --- /dev/null +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/actions/_calculator_fields.erb @@ -0,0 +1,11 @@ + +<% calculator = promotion_action.calculator %> +<% type_name = calculator.type.demodulize.underscore %> +<% if lookup_context.exists?("fields", + ["solidus_friendly_promotions/admin/promotions/calculators/#{type_name}"], true) %> +<%= render "solidus_friendly_promotions/admin/promotions/calculators/#{type_name}/fields", + calculator: calculator, prefix: param_prefix %> +<% else %> +<%= render "solidus_friendly_promotions/admin/promotions/calculators/default_fields", + calculator: calculator, prefix: param_prefix %> +<% end %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/calculators/_default_fields.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/calculators/_default_fields.html.erb new file mode 100644 index 00000000000..0bdcfa9286e --- /dev/null +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/calculators/_default_fields.html.erb @@ -0,0 +1,6 @@ +<% calculator.admin_form_preference_names.map do |name| %> + <%= render "spree/admin/shared/preference_fields/#{calculator.preference_type(name)}", + name: "#{prefix}[calculator_attributes][preferred_#{name}]", + value: calculator.get_preference(name), + label: t(name.to_s, scope: 'spree', default: name.to_s.humanize) %> +<% end %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/calculators/distributed_amount/_fields.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/calculators/distributed_amount/_fields.html.erb new file mode 100644 index 00000000000..333913c1b05 --- /dev/null +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/calculators/distributed_amount/_fields.html.erb @@ -0,0 +1,56 @@ +
    + <%= fields_for "#{prefix}[calculator_attributes]", calculator do |f| %> + <%= f.label :preferred_amount %> + <%= render "spree/admin/shared/number_with_currency", f: f, amount_attr: :preferred_amount, currency_attr: :preferred_currency %> + <% end %> +
    + +
    +

    + <%= admin_hint( + calculator.model_name.human, + """ +

    + This amount will be distributed to line items weighted relative to + their price. More expensive line items will receive a greater share + of the adjustment. +

    + +

    + For example, with three line items and a preferred amount of $15 we + would end up with the following distribution: +

    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    PriceWeighted Adj.
    Socks$5-$1.5
    Shoes$30-$9
    Slippers$15-$4.5
    + """ + ) %> + + This amount will be the total discount spread amongst all + of the line items. +

    +
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/calculators/flat_rate/_fields.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/calculators/flat_rate/_fields.html.erb new file mode 100644 index 00000000000..bd6a78eeed0 --- /dev/null +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/calculators/flat_rate/_fields.html.erb @@ -0,0 +1,6 @@ +
    + <%= fields_for "#{prefix}[calculator_attributes]", calculator do |f| %> + <%= f.label :preferred_amount %> + <%= render "spree/admin/shared/number_with_currency", f: f, amount_attr: :preferred_amount, currency_attr: :preferred_currency %> + <% end %> +
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/calculators/tiered_flat_rate/_fields.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/calculators/tiered_flat_rate/_fields.html.erb new file mode 100644 index 00000000000..f19d927f29a --- /dev/null +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/calculators/tiered_flat_rate/_fields.html.erb @@ -0,0 +1,30 @@ +<%= render "spree/admin/shared/preference_fields/#{calculator.preference_type(:base_amount)}", + name: "#{prefix}[calculator_attributes][preferred_base_amount]", + value: calculator.preferred_base_amount, label: t('spree.base_amount') %> + +
    + <%= label_tag( + "#{prefix}[calculator_attributes][preferred_currency]", + t('spree.currency') + ) %> + <%= select_tag( + "#{prefix}[calculator_attributes][preferred_currency]", + options_for_select( + Spree::Config.available_currencies, + calculator.preferred_currency || Spree::Config[:currency] + ), + { class: 'custom-select fullwidth' } + ) %> +
    + +
    + <%= label_tag nil, t('spree.tiers') %> +
    + <%= content_tag :div, nil, class: "js-tiers", data: { + 'original-tiers' => Hash[calculator.preferred_tiers.sort], + 'form-prefix' => prefix, + 'calculator' => 'tiered_flat_rate' + } %> + <%= t('spree.actions.add') %> +
    +
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/calculators/tiered_percent/_fields.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/calculators/tiered_percent/_fields.html.erb new file mode 100644 index 00000000000..ff6bb6797b8 --- /dev/null +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/calculators/tiered_percent/_fields.html.erb @@ -0,0 +1,30 @@ +<%= render "spree/admin/shared/preference_fields/#{calculator.preference_type(:base_percent)}", + name: "#{prefix}[calculator_attributes][preferred_base_percent]", + value: calculator.preferred_base_percent, label: t('spree.base_percent') %> + +
    + <%= label_tag( + "#{prefix}[calculator_attributes][preferred_currency]", + t('spree.currency') + ) %> + <%= select_tag( + "#{prefix}[calculator_attributes][preferred_currency]", + options_for_select( + Spree::Config.available_currencies, + calculator.preferred_currency || Spree::Config[:currency] + ), + { class: 'custom-select fullwidth' } + ) %> +
    + +
    + <%= label_tag nil, t('spree.tiers') %> +
    + <%= content_tag :div, nil, class: "js-tiers", data: { + 'original-tiers' => Hash[calculator.preferred_tiers.sort], + 'form-prefix' => prefix, + 'calculator' => 'tiered_percent' + } %> + <%= t('spree.actions.add') %> +
    +
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/edit.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/edit.html.erb index b10f312f621..6d9a3d9cc0c 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/edit.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/edit.html.erb @@ -28,6 +28,17 @@ <% end %> <% end %> + +
    + <%= t(:actions, scope: :solidus_friendly_promotions) %> + + <% [:line_item, :shipment].each do |level| %> + <%= render partial: 'promotion_action', collection: promotion_actions_by_level(@promotion, level), locals: {} %> + <%= render "new_action_form", level: level %> + <% end %> +
    + +
    <%= t('.order_rules') %> diff --git a/friendly_promotions/config/locales/en.yml b/friendly_promotions/config/locales/en.yml index b83a7be7e54..e71f3d211b6 100644 --- a/friendly_promotions/config/locales/en.yml +++ b/friendly_promotions/config/locales/en.yml @@ -3,6 +3,10 @@ en: solidus_friendly_promotions: + actions: Actions + add_action: + line_item: Add line item discount + shipment: Add shipment discount create_promotion_code: Create promotion code current_promotion_usage: 'Current Usage: %{count}' discount_rules: Promotion Rules @@ -13,6 +17,10 @@ en: no_rules_addes: No Rules Added promotion_successfully_created: Promotion has been successfully created! view_promotion_codes_list: View codes list + crud: + add: Add + destroy: Delete + update: Update admin: promotions: actions: From acfe3050620355cb403fae33f51aee19c712c62c Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 23 Jun 2023 11:29:27 +0200 Subject: [PATCH 232/834] Render existing actions before new forms --- .../admin/promotions/edit.html.erb | 3 ++- friendly_promotions/config/locales/en.yml | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/edit.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/edit.html.erb index 6d9a3d9cc0c..30bcd98f016 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/edit.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/edit.html.erb @@ -32,8 +32,9 @@
    <%= t(:actions, scope: :solidus_friendly_promotions) %> + <%= render partial: 'promotion_action', collection: @promotion.actions, locals: {} %> + <% [:line_item, :shipment].each do |level| %> - <%= render partial: 'promotion_action', collection: promotion_actions_by_level(@promotion, level), locals: {} %> <%= render "new_action_form", level: level %> <% end %>
    diff --git a/friendly_promotions/config/locales/en.yml b/friendly_promotions/config/locales/en.yml index e71f3d211b6..58fa4139bc8 100644 --- a/friendly_promotions/config/locales/en.yml +++ b/friendly_promotions/config/locales/en.yml @@ -5,8 +5,8 @@ en: solidus_friendly_promotions: actions: Actions add_action: - line_item: Add line item discount - shipment: Add shipment discount + line_item: Add line item discount action + shipment: Add shipment discount action create_promotion_code: Create promotion code current_promotion_usage: 'Current Usage: %{count}' discount_rules: Promotion Rules From 82311fa249bde8c13f7d83ddfd8760e74f31957e Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 23 Jun 2023 20:51:00 +0200 Subject: [PATCH 233/834] Refactor actions form This is a complicated beast. --- .../admin/promotion_actions_controller.rb | 35 ++++++++++++-- .../admin/promotions_controller.rb | 1 + .../admin/promotion_actions_helper.rb | 19 ++++++++ .../admin/promotion_rules_helper.rb | 6 --- .../actions/adjust_line_item.rb | 4 ++ .../actions/adjust_shipment.rb | 4 ++ .../actions/base.rb | 4 ++ .../_calculator_select.html.erb | 14 ++++++ .../admin/promotion_actions/_form.html.erb | 3 ++ .../_promotion_action.html.erb | 34 ++++++++++++++ .../promotion_actions/_type_select.html.erb | 14 ++++++ .../admin/promotion_actions/edit.html.erb | 23 ++++++++++ .../admin/promotion_actions/new.html.erb | 46 +++++++++---------- .../promotions/_new_action_form.html.erb | 23 ---------- .../promotions/_promotion_action.html.erb | 21 --------- .../admin/promotions/edit.html.erb | 19 +++----- friendly_promotions/config/locales/en.yml | 4 +- 17 files changed, 181 insertions(+), 93 deletions(-) create mode 100644 friendly_promotions/app/helpers/solidus_friendly_promotions/admin/promotion_actions_helper.rb create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/_calculator_select.html.erb create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/_form.html.erb create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/_promotion_action.html.erb create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/_type_select.html.erb create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/edit.html.erb delete mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_new_action_form.html.erb delete mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_promotion_action.html.erb diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb index d02821ef38a..083334d4bd4 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb @@ -4,13 +4,27 @@ module SolidusFriendlyPromotions module Admin class PromotionActionsController < Spree::Admin::ResourceController before_action :load_promotion - before_action :validate_promotion_action_type, only: [:create, :new] + before_action :validate_promotion_action_type, only: [:create] + + helper 'solidus_friendly_promotions/admin/promotion_actions' def new + if params.dig(:promotion_action, :type) + validate_promotion_action_type + end @promotion_action = @promotion.promotion_actions.build( type: @promotion_action_type, - calculator_type: params[:promotion_action][:calculator_type] ) + if @promotion_action.respond_to?(:calculator_type) && params.dig(:promotion_action, :calculator_type) + @promotion_action.calculator_type = params.dig(:promotion_action, :calculator_type) + end + render layout: false + end + + def edit + if @promotion_action.calculator.class.name != params.dig(:promotion_action, :calculator_type) + @promotion_action.calculator = permitted_resource_params[:calculator_type].constantize.new + end render layout: false end @@ -19,8 +33,21 @@ def create @promotion_action.promotion = @promotion if @promotion_action.save flash[:success] = t('spree.successfully_created', resource: t('spree.promotion_action')) + redirect_to location_after_save, format: :html + else + render :new, layout: false + end + end + + def update + @promotion_action = @promotion.promotion_actions.find(params[:id]) + @promotion_action.assign_attributes(permitted_resource_params) + if @promotion_action.save + flash[:success] = t('spree.successfully_updated', resource: t('spree.promotion_action')) + redirect_to location_after_save, format: :html + else + render :edit end - redirect_to location_after_save end def destroy @@ -28,7 +55,7 @@ def destroy if @promotion_action.discard flash[:success] = t('spree.successfully_removed', resource: t('spree.promotion_action')) end - redirect_to location_after_save + redirect_to location_after_save, format: :html end private diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb index f5f7d27ae23..57af8f7bc7a 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb @@ -6,6 +6,7 @@ class PromotionsController < ::Spree::Admin::ResourceController before_action :load_data helper 'solidus_friendly_promotions/admin/promotion_rules' + helper 'solidus_friendly_promotions/admin/promotion_actions' def create @promotion = Spree::Promotion.new(permitted_resource_params) diff --git a/friendly_promotions/app/helpers/solidus_friendly_promotions/admin/promotion_actions_helper.rb b/friendly_promotions/app/helpers/solidus_friendly_promotions/admin/promotion_actions_helper.rb new file mode 100644 index 00000000000..9d26203ca7d --- /dev/null +++ b/friendly_promotions/app/helpers/solidus_friendly_promotions/admin/promotion_actions_helper.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module Admin + module PromotionActionsHelper + def options_for_promotion_action_calculator_types(promotion_action) + calculators = promotion_action.available_calculators + options = calculators.map { |calculator| [calculator.model_name.human, calculator.name] } + options_for_select(options, promotion_action.calculator_type.to_s) + end + + def options_for_promotion_action_types(promotion_action) + actions = SolidusFriendlyPromotions.config.actions + options = actions.map { |action| [action.model_name.human, action.name] } + options_for_select(options, promotion_action.type.to_s) + end + end + end +end diff --git a/friendly_promotions/app/helpers/solidus_friendly_promotions/admin/promotion_rules_helper.rb b/friendly_promotions/app/helpers/solidus_friendly_promotions/admin/promotion_rules_helper.rb index 48f577d8b26..691dcace0ef 100644 --- a/friendly_promotions/app/helpers/solidus_friendly_promotions/admin/promotion_rules_helper.rb +++ b/friendly_promotions/app/helpers/solidus_friendly_promotions/admin/promotion_rules_helper.rb @@ -21,12 +21,6 @@ def promotion_actions_by_level(promotion, level) rule.class.name.demodulize.underscore.ends_with?(level.to_s) end end - - def options_for_promotion_action_calculator_types(level) - calculators = SolidusFriendlyPromotions.config.send("#{level}_discount_calculators") - options = calculators.map { |calculator| [calculator.model_name.human, calculator.name] } - options_for_select(options) - end end end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb index 73eaa92b863..d345eb551b8 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb @@ -6,6 +6,10 @@ class AdjustLineItem < Base def can_adjust?(object) object.is_a? Spree::LineItem end + + def available_calculators + SolidusFriendlyPromotions.config.line_item_discount_calculators + end end end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb index bcc0a03bdec..6d19427113e 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb @@ -6,6 +6,10 @@ class AdjustShipment < Base def can_adjust?(object) object.is_a? Spree::Shipment end + + def available_calculators + SolidusFriendlyPromotions.config.shipment_discount_calculators + end end end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/actions/base.rb b/friendly_promotions/app/models/solidus_friendly_promotions/actions/base.rb index 6f7ed273b20..b5801b2eab6 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/actions/base.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/actions/base.rb @@ -42,6 +42,10 @@ def adjustment_label(adjustable) def to_partial_path "solidus_friendly_promotions/admin/promotions/actions/#{model_name.element}" end + + def available_calculators + raise NotImplementedError + end end end end diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/_calculator_select.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/_calculator_select.html.erb new file mode 100644 index 00000000000..dcd28136831 --- /dev/null +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/_calculator_select.html.erb @@ -0,0 +1,14 @@ +<%= form_with model: @promotion_action, scope: :promotion_action, url: path, method: :get do |form| %> + <%= form.hidden_field :type %> + <%= form.label :calculator_type %> + <%= + form.select :calculator_type, + options_for_promotion_action_calculator_types(form.object), + { + include_blank: t(:choose_promotion_action_calculator, scope: 'solidus_friendly_shipping') + }, + class: 'custom-select fullwidth', + onchange: 'this.form.requestSubmit()', + required: true + %> +<% end %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/_form.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/_form.html.erb new file mode 100644 index 00000000000..4934c97215a --- /dev/null +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/_form.html.erb @@ -0,0 +1,3 @@ +<%= form.hidden_field :type %> +<%= form.hidden_field :calculator_type %> +<%= render form.object.to_partial_path, promotion_action: form.object, param_prefix: form.object_name %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/_promotion_action.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/_promotion_action.html.erb new file mode 100644 index 00000000000..b2d32181f04 --- /dev/null +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/_promotion_action.html.erb @@ -0,0 +1,34 @@ +<%= turbo_frame_tag @promotion, promotion_action do %> +
    +
    <%= promotion_action.model_name.human %>
    + <% if can?(:destroy, promotion_action) %> + <%= link_to_with_icon 'trash', '', solidus_friendly_promotions.admin_promotion_promotion_action_path(@promotion, promotion_action), method: :delete, class: 'delete' %> + <% end %> + <%= form_with model: promotion_action, scope: :promotion_action, url: solidus_friendly_promotions.edit_admin_promotion_promotion_action_path(@promotion, promotion_action), method: :get do |form| %> + <%= form.label :calculator_type %> + <%= + form.select :calculator_type, + options_for_promotion_action_calculator_types(form.object), + { + include_blank: t(:choose_promotion_action_calculator, scope: 'solidus_friendly_shipping') + }, + class: 'custom-select fullwidth', + onchange: 'this.form.requestSubmit()' + %> + <% end %> + <%= + form_with( + model: promotion_action, + scope: :promotion_action, + url: solidus_friendly_promotions.admin_promotion_promotion_action_path(@promotion, promotion_action), + data: { turbo: false } + ) do |form| %> + <%= render 'solidus_friendly_promotions/admin/promotion_actions/form', form: form %> +
    +
    + <%= button_tag "Update", class: "btn btn-secondary float-right" %> +
    +
    + <% end %> +
    +<% end %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/_type_select.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/_type_select.html.erb new file mode 100644 index 00000000000..2823d3ab3b9 --- /dev/null +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/_type_select.html.erb @@ -0,0 +1,14 @@ +<%= form_with model: @promotion_action, scope: :promotion_action, url: solidus_friendly_promotions.new_admin_promotion_promotion_action_path(@promotion), method: :get do |form| %> + <%= form.label :type %> + <%= admin_hint t('spree.adjustment_type'), t(:promotions, scope: [:spree, :hints, "spree/calculator"]) %> + <%= + form.select :type, + options_for_promotion_action_types(form.object), + { + include_blank: t(:choose_promotion_action, scope: 'solidus_friendly_shipping') + }, + class: 'custom-select fullwidth', + onchange: 'this.form.requestSubmit()', + required: true + %> +<% end %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/edit.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/edit.html.erb new file mode 100644 index 00000000000..eab50b0f466 --- /dev/null +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/edit.html.erb @@ -0,0 +1,23 @@ +<%= turbo_frame_tag @promotion, @promotion_action do %> +
    +
    <%= @promotion_action.class.human_attribute_name(:description) %>
    + <%= link_to_with_icon 'trash', '', solidus_friendly_promotions.admin_promotion_promotion_action_path(@promotion, @promotion_action), method: :delete, class: 'delete' %> + <%= render 'calculator_select', path: solidus_friendly_promotions.edit_admin_promotion_promotion_action_path(@promotion, @promotion_action) %> + + <%= + form_with( + model: @promotion_action, + scope: :promotion_action, + url: solidus_friendly_promotions.admin_promotion_promotion_action_path(@promotion, @promotion_action), + data: { turbo: false } + ) do |form| %> + <%= render "form", form: form %> + +
    +
    + <%= button_tag "Update", class: "btn btn-secondary float-right" %> +
    +
    + <% end %> +
    +<% end %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/new.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/new.html.erb index f4e5ae6a249..744a40d09a5 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/new.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/new.html.erb @@ -1,26 +1,26 @@ <%= turbo_frame_tag @promotion, "new_promotion_action" do %> - <%= form_tag solidus_friendly_promotions.admin_promotion_promotion_actions_path(@promotion) do %> -
    -
    <%= @promotion_action.class.human_attribute_name(:description) %>
    - <%= link_to_with_icon 'trash', '', spree.edit_admin_promotion_path(@promotion), class: 'delete' %> - <%= render partial: "spree/shared/error_messages", locals: { target: @promotion_action } %> - <%= hidden_field_tag "promotion_action[type]", @promotion_action.class.name %> - <%= hidden_field_tag "promotion_action[calculator_attributes][type]", @promotion_action.calculator.class.name %> - <% type_name = @promotion_action.calculator.type.demodulize.underscore %> - <% param_prefix = "promotion_action" %> - <% if lookup_context.exists?("fields", - ["spree/admin/promotions/calculators/#{type_name}"], true) %> - <%= render "spree/admin/promotions/calculators/#{type_name}/fields", - calculator: @promotion_action.calculator, prefix: param_prefix %> - <% else %> - <%= render "spree/admin/promotions/calculators/default_fields", - calculator: @promotion_action.calculator, prefix: param_prefix %> +
    +
    <%= t(:add_action, scope: :solidus_friendly_promotions) %>
    + <%= link_to_with_icon 'trash', '', spree.edit_admin_promotion_path(@promotion), class: 'delete' %> + <%= render 'type_select' %> + <% if @promotion_action.respond_to?(:calculator) %> + <%= render 'calculator_select', path: solidus_friendly_promotions.new_admin_promotion_promotion_action_path(@promotion) %> + <% if @promotion_action.calculator %> + <%= + form_with( + model: @promotion_action, + scope: :promotion_action, + url: solidus_friendly_promotions.admin_promotion_promotion_actions_path(@promotion), + data: { turbo: false } + ) do |form| %> + <%= render 'form', form: form %> +
    +
    + <%= button_tag "Add", class: "btn btn-secondary float-right" %> +
    +
    + <% end %> <% end %> -
    -
    - <%= button_tag "Add", class: "btn btn-secondary float-right", data: { "turbo-frame" => "_top" } %> -
    -
    -
    - <% end %> + <% end %> +
    <% end %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_new_action_form.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_new_action_form.html.erb deleted file mode 100644 index 3e78bf1a905..00000000000 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_new_action_form.html.erb +++ /dev/null @@ -1,23 +0,0 @@ -<%= turbo_frame_tag @promotion, "new_promotion_action" do %> -
    -
    <%= t(level, scope: 'solidus_friendly_promotions.add_action') %>
    - <%= form_tag solidus_friendly_promotions.new_admin_promotion_promotion_action_path(@promotion), method: :get, remote: false do %> - <% if can?(:update, @promotion) %> -
    - <%= label_tag :promotion_action_calculator_type, Spree::PromotionAction.human_attribute_name(:calculator_type) %> - <%= - select_tag( - 'promotion_action[calculator_type]', options_for_promotion_action_calculator_types(level), - include_blank: t(:choose_promotion_rule, scope: 'spree'), - class: 'custom-select fullwidth', - onchange: 'this.form.requestSubmit()', - required: true - ) - %> - <%= hidden_field_tag('promotion_action[type]', "SolidusFriendlyPromotions::Actions::Adjust#{level.to_s.camelize}") %> - <%= hidden_field_tag("level", level) %> -
    - <% end %> - <% end %> -
    -<% end %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_promotion_action.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_promotion_action.html.erb deleted file mode 100644 index 8ee9a2421a4..00000000000 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_promotion_action.html.erb +++ /dev/null @@ -1,21 +0,0 @@ -
    -
    <%= promotion_action.class.human_attribute_name(:description) %>
    - <% if promotion_action.persisted? && can?(:destroy, promotion_action) %> - <%= link_to_with_icon 'trash', '', solidus_friendly_promotions.admin_promotion_promotion_action_path(@promotion, promotion_action), method: :delete, class: 'delete' %> - <% end %> - <%= form_tag solidus_friendly_promotions.admin_promotion_promotion_action_path(@promotion, promotion_action), method: :patch do %> - - <% param_prefix = "promotion_action" %> - <%= hidden_field_tag "#{param_prefix}[type]", promotion_action.type %> - - <%= render partial: "spree/shared/error_messages", locals: { target: promotion_action } %> - <%= render partial: promotion_action.to_partial_path, - locals: { promotion_action: promotion_action, param_prefix: param_prefix } %> -
    -
    - <% button_label = promotion_action.persisted? ? :update : :add %> - <%= button_tag t(button_label, scope: 'solidus_friendly_promotions.crud'), class: "btn btn-secondary float-right"%> -
    -
    - <% end %> -
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/edit.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/edit.html.erb index 30bcd98f016..ecbc6580c3d 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/edit.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/edit.html.erb @@ -5,6 +5,9 @@ <%= content_for :head do %> <%= javascript_importmap_tags "@hotwired/turbo-rails" %> + <% end %> <% content_for :page_actions do %> @@ -32,19 +35,9 @@
    <%= t(:actions, scope: :solidus_friendly_promotions) %> - <%= render partial: 'promotion_action', collection: @promotion.actions, locals: {} %> + <%= render partial: 'solidus_friendly_promotions/admin/promotion_actions/promotion_action', collection: @promotion.actions, locals: {} %> - <% [:line_item, :shipment].each do |level| %> - <%= render "new_action_form", level: level %> + <%= turbo_frame_tag @promotion, "new_promotion_action" do %> + <%= link_to t(:add_action, scope: :solidus_friendly_promotions), solidus_friendly_promotions.new_admin_promotion_promotion_action_path(@promotion), class: 'btn btn-secondary' %> <% end %>
    - - -
    - <%= t('.order_rules') %> - -
    - <%= render partial: 'promotion_rule', collection: @promotion.rules, locals: {} %> - <%= render "new_rule_form", level: :order %> -
    -
    diff --git a/friendly_promotions/config/locales/en.yml b/friendly_promotions/config/locales/en.yml index 58fa4139bc8..e206f210336 100644 --- a/friendly_promotions/config/locales/en.yml +++ b/friendly_promotions/config/locales/en.yml @@ -4,9 +4,7 @@ en: solidus_friendly_promotions: actions: Actions - add_action: - line_item: Add line item discount action - shipment: Add shipment discount action + add_action: Add action create_promotion_code: Create promotion code current_promotion_usage: 'Current Usage: %{count}' discount_rules: Promotion Rules From f34c505f299be924f1b79d5e5364fa5dbd4c4ece Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 23 Jun 2023 21:20:18 +0200 Subject: [PATCH 234/834] Make sure labels are wrapped in fields So that the font size is always the same. Prep for the next commit in here: Where the BUTTON appears, a button should appear to add rules to actions. --- .../_calculator_select.html.erb | 28 ++++++----- .../_promotion_action.html.erb | 49 ++++++++++--------- .../admin/promotion_actions/edit.html.erb | 36 ++++++++------ .../admin/promotion_actions/new.html.erb | 2 +- 4 files changed, 63 insertions(+), 52 deletions(-) diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/_calculator_select.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/_calculator_select.html.erb index dcd28136831..90cf01e4eac 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/_calculator_select.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/_calculator_select.html.erb @@ -1,14 +1,16 @@ -<%= form_with model: @promotion_action, scope: :promotion_action, url: path, method: :get do |form| %> - <%= form.hidden_field :type %> - <%= form.label :calculator_type %> - <%= - form.select :calculator_type, - options_for_promotion_action_calculator_types(form.object), - { - include_blank: t(:choose_promotion_action_calculator, scope: 'solidus_friendly_shipping') - }, - class: 'custom-select fullwidth', - onchange: 'this.form.requestSubmit()', - required: true - %> +<%= form_with model: promotion_action, scope: :promotion_action, url: path, method: :get do |form| %> +
    + <%= form.hidden_field :type %> + <%= form.label :calculator_type %> + <%= + form.select :calculator_type, + options_for_promotion_action_calculator_types(form.object), + { + include_blank: t(:choose_promotion_action_calculator, scope: 'solidus_friendly_shipping') + }, + class: 'custom-select fullwidth', + onchange: 'this.form.requestSubmit()', + required: true + %> +
    <% end %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/_promotion_action.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/_promotion_action.html.erb index b2d32181f04..516cadba59c 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/_promotion_action.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/_promotion_action.html.erb @@ -1,34 +1,35 @@ <%= turbo_frame_tag @promotion, promotion_action do %>
    <%= promotion_action.model_name.human %>
    + <% if can?(:destroy, promotion_action) %> <%= link_to_with_icon 'trash', '', solidus_friendly_promotions.admin_promotion_promotion_action_path(@promotion, promotion_action), method: :delete, class: 'delete' %> <% end %> - <%= form_with model: promotion_action, scope: :promotion_action, url: solidus_friendly_promotions.edit_admin_promotion_promotion_action_path(@promotion, promotion_action), method: :get do |form| %> - <%= form.label :calculator_type %> + +
    +
    + <%= render "solidus_friendly_promotions/admin/promotion_actions/calculator_select", + path: solidus_friendly_promotions.edit_admin_promotion_promotion_action_path(@promotion, promotion_action), + promotion_action: promotion_action %> + <%= - form.select :calculator_type, - options_for_promotion_action_calculator_types(form.object), - { - include_blank: t(:choose_promotion_action_calculator, scope: 'solidus_friendly_shipping') - }, - class: 'custom-select fullwidth', - onchange: 'this.form.requestSubmit()' - %> - <% end %> - <%= - form_with( - model: promotion_action, - scope: :promotion_action, - url: solidus_friendly_promotions.admin_promotion_promotion_action_path(@promotion, promotion_action), - data: { turbo: false } - ) do |form| %> - <%= render 'solidus_friendly_promotions/admin/promotion_actions/form', form: form %> -
    -
    - <%= button_tag "Update", class: "btn btn-secondary float-right" %> -
    + form_with( + model: promotion_action, + scope: :promotion_action, + url: solidus_friendly_promotions.admin_promotion_promotion_action_path(@promotion, promotion_action), + data: { turbo: false } + ) do |form| %> + <%= render 'solidus_friendly_promotions/admin/promotion_actions/form', form: form %> +
    +
    + <%= button_tag "Update", class: "btn btn-secondary float-right" %> +
    +
    + <% end %>
    - <% end %> +
    + BUTTON +
    +
    <% end %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/edit.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/edit.html.erb index eab50b0f466..c2f4cdef315 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/edit.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/edit.html.erb @@ -2,22 +2,30 @@
    <%= @promotion_action.class.human_attribute_name(:description) %>
    <%= link_to_with_icon 'trash', '', solidus_friendly_promotions.admin_promotion_promotion_action_path(@promotion, @promotion_action), method: :delete, class: 'delete' %> - <%= render 'calculator_select', path: solidus_friendly_promotions.edit_admin_promotion_promotion_action_path(@promotion, @promotion_action) %> +
    +
    + <%= render 'calculator_select', path: solidus_friendly_promotions.edit_admin_promotion_promotion_action_path(@promotion, @promotion_action), promotion_action: @promotion_action %> - <%= - form_with( - model: @promotion_action, - scope: :promotion_action, - url: solidus_friendly_promotions.admin_promotion_promotion_action_path(@promotion, @promotion_action), - data: { turbo: false } - ) do |form| %> - <%= render "form", form: form %> + <%= + form_with( + model: @promotion_action, + scope: :promotion_action, + url: solidus_friendly_promotions.admin_promotion_promotion_action_path(@promotion, @promotion_action), + data: { turbo: false } + ) do |form| %> + <%= render "form", form: form %> -
    -
    - <%= button_tag "Update", class: "btn btn-secondary float-right" %> -
    +
    +
    + <%= button_tag "Update", class: "btn btn-secondary float-right" %> +
    +
    + + <% end %> +
    +
    + BUTTON
    - <% end %> +
    <% end %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/new.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/new.html.erb index 744a40d09a5..f7ea1bd4cdb 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/new.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/new.html.erb @@ -4,7 +4,7 @@ <%= link_to_with_icon 'trash', '', spree.edit_admin_promotion_path(@promotion), class: 'delete' %> <%= render 'type_select' %> <% if @promotion_action.respond_to?(:calculator) %> - <%= render 'calculator_select', path: solidus_friendly_promotions.new_admin_promotion_promotion_action_path(@promotion) %> + <%= render 'calculator_select', path: solidus_friendly_promotions.new_admin_promotion_promotion_action_path(@promotion), promotion_action: @promotion_action %> <% if @promotion_action.calculator %> <%= form_with( From 557277d2c3ec4b3bec02113876c3c4f54c0b5fb6 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 28 Jun 2023 14:35:48 +0200 Subject: [PATCH 235/834] Move action partials under promotion_actions directory --- .../app/models/solidus_friendly_promotions/actions/base.rb | 2 +- .../admin/promotion_actions/_form.html.erb | 2 +- .../actions/_adjust_line_item.html.erb | 2 +- .../actions/_adjust_shipment.html.erb | 2 +- .../actions/_calculator_fields.erb | 0 5 files changed, 4 insertions(+), 4 deletions(-) rename friendly_promotions/app/views/solidus_friendly_promotions/admin/{promotions => promotion_actions}/actions/_adjust_line_item.html.erb (50%) rename friendly_promotions/app/views/solidus_friendly_promotions/admin/{promotions => promotion_actions}/actions/_adjust_shipment.html.erb (50%) rename friendly_promotions/app/views/solidus_friendly_promotions/admin/{promotions => promotion_actions}/actions/_calculator_fields.erb (100%) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/actions/base.rb b/friendly_promotions/app/models/solidus_friendly_promotions/actions/base.rb index b5801b2eab6..949a061c58d 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/actions/base.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/actions/base.rb @@ -40,7 +40,7 @@ def adjustment_label(adjustable) end def to_partial_path - "solidus_friendly_promotions/admin/promotions/actions/#{model_name.element}" + "solidus_friendly_promotions/admin/promotion_actions/actions/#{model_name.element}" end def available_calculators diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/_form.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/_form.html.erb index 4934c97215a..fdefca6a212 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/_form.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/_form.html.erb @@ -1,3 +1,3 @@ <%= form.hidden_field :type %> <%= form.hidden_field :calculator_type %> -<%= render form.object.to_partial_path, promotion_action: form.object, param_prefix: form.object_name %> +<%= render form.object, promotion_action: form.object, param_prefix: form.object_name %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/actions/_adjust_line_item.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/actions/_adjust_line_item.html.erb similarity index 50% rename from friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/actions/_adjust_line_item.html.erb rename to friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/actions/_adjust_line_item.html.erb index c421dba62b0..ab28206107b 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/actions/_adjust_line_item.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/actions/_adjust_line_item.html.erb @@ -1,5 +1,5 @@ <%= render( - "solidus_friendly_promotions/admin/promotions/actions/calculator_fields", + "solidus_friendly_promotions/admin/promotion_actions/actions/calculator_fields", promotion_action: promotion_action, param_prefix: param_prefix ) %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/actions/_adjust_shipment.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/actions/_adjust_shipment.html.erb similarity index 50% rename from friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/actions/_adjust_shipment.html.erb rename to friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/actions/_adjust_shipment.html.erb index c421dba62b0..ab28206107b 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/actions/_adjust_shipment.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/actions/_adjust_shipment.html.erb @@ -1,5 +1,5 @@ <%= render( - "solidus_friendly_promotions/admin/promotions/actions/calculator_fields", + "solidus_friendly_promotions/admin/promotion_actions/actions/calculator_fields", promotion_action: promotion_action, param_prefix: param_prefix ) %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/actions/_calculator_fields.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/actions/_calculator_fields.erb similarity index 100% rename from friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/actions/_calculator_fields.erb rename to friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/actions/_calculator_fields.erb From e0c95499733b1b2c45b0ee4a1ddf736305e77141 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 28 Jun 2023 14:37:19 +0200 Subject: [PATCH 236/834] Move calculator forms to promotion_actions view folder --- .../admin/promotion_actions/actions/_calculator_fields.erb | 6 +++--- .../calculators/_default_fields.html.erb | 0 .../calculators/distributed_amount/_fields.html.erb | 0 .../calculators/flat_rate/_fields.html.erb | 0 .../calculators/tiered_flat_rate/_fields.html.erb | 0 .../calculators/tiered_percent/_fields.html.erb | 0 6 files changed, 3 insertions(+), 3 deletions(-) rename friendly_promotions/app/views/solidus_friendly_promotions/admin/{promotions => promotion_actions}/calculators/_default_fields.html.erb (100%) rename friendly_promotions/app/views/solidus_friendly_promotions/admin/{promotions => promotion_actions}/calculators/distributed_amount/_fields.html.erb (100%) rename friendly_promotions/app/views/solidus_friendly_promotions/admin/{promotions => promotion_actions}/calculators/flat_rate/_fields.html.erb (100%) rename friendly_promotions/app/views/solidus_friendly_promotions/admin/{promotions => promotion_actions}/calculators/tiered_flat_rate/_fields.html.erb (100%) rename friendly_promotions/app/views/solidus_friendly_promotions/admin/{promotions => promotion_actions}/calculators/tiered_percent/_fields.html.erb (100%) diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/actions/_calculator_fields.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/actions/_calculator_fields.erb index e412b3bc0d7..33ea65d6aba 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/actions/_calculator_fields.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/actions/_calculator_fields.erb @@ -2,10 +2,10 @@ <% calculator = promotion_action.calculator %> <% type_name = calculator.type.demodulize.underscore %> <% if lookup_context.exists?("fields", - ["solidus_friendly_promotions/admin/promotions/calculators/#{type_name}"], true) %> -<%= render "solidus_friendly_promotions/admin/promotions/calculators/#{type_name}/fields", + ["solidus_friendly_promotions/admin/promotion_actions/calculators/#{type_name}"], true) %> +<%= render "solidus_friendly_promotions/admin/promotion_actions/calculators/#{type_name}/fields", calculator: calculator, prefix: param_prefix %> <% else %> -<%= render "solidus_friendly_promotions/admin/promotions/calculators/default_fields", +<%= render "solidus_friendly_promotions/admin/promotion_actions/calculators/default_fields", calculator: calculator, prefix: param_prefix %> <% end %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/calculators/_default_fields.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/calculators/_default_fields.html.erb similarity index 100% rename from friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/calculators/_default_fields.html.erb rename to friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/calculators/_default_fields.html.erb diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/calculators/distributed_amount/_fields.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/calculators/distributed_amount/_fields.html.erb similarity index 100% rename from friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/calculators/distributed_amount/_fields.html.erb rename to friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/calculators/distributed_amount/_fields.html.erb diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/calculators/flat_rate/_fields.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/calculators/flat_rate/_fields.html.erb similarity index 100% rename from friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/calculators/flat_rate/_fields.html.erb rename to friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/calculators/flat_rate/_fields.html.erb diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/calculators/tiered_flat_rate/_fields.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/calculators/tiered_flat_rate/_fields.html.erb similarity index 100% rename from friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/calculators/tiered_flat_rate/_fields.html.erb rename to friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/calculators/tiered_flat_rate/_fields.html.erb diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/calculators/tiered_percent/_fields.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/calculators/tiered_percent/_fields.html.erb similarity index 100% rename from friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/calculators/tiered_percent/_fields.html.erb rename to friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/calculators/tiered_percent/_fields.html.erb From 809b07ac47dbe54a2fd0c53eda0e3c2f0e082788 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 28 Jun 2023 15:36:46 +0200 Subject: [PATCH 237/834] Add proper importmap support --- .../solidus_friendly_promotions/manifest.js | 1 + .../javascript/solidus_friendly_promotions.js | 3 +++ .../admin/promotions/edit.html.erb | 5 +---- friendly_promotions/bin/importmap | 4 ++++ friendly_promotions/config/importmap.rb | 2 ++ .../lib/solidus_friendly_promotions.rb | 8 ++++++++ .../lib/solidus_friendly_promotions/engine.rb | 18 ++++++++++++++++++ .../solidus_friendly_promotions.gemspec | 1 + 8 files changed, 38 insertions(+), 4 deletions(-) create mode 100644 friendly_promotions/app/assets/config/solidus_friendly_promotions/manifest.js create mode 100644 friendly_promotions/app/javascript/solidus_friendly_promotions.js create mode 100755 friendly_promotions/bin/importmap create mode 100644 friendly_promotions/config/importmap.rb diff --git a/friendly_promotions/app/assets/config/solidus_friendly_promotions/manifest.js b/friendly_promotions/app/assets/config/solidus_friendly_promotions/manifest.js new file mode 100644 index 00000000000..4ed0da56709 --- /dev/null +++ b/friendly_promotions/app/assets/config/solidus_friendly_promotions/manifest.js @@ -0,0 +1 @@ +//= link solidus_friendly_promotions.js diff --git a/friendly_promotions/app/javascript/solidus_friendly_promotions.js b/friendly_promotions/app/javascript/solidus_friendly_promotions.js new file mode 100644 index 00000000000..40809761f14 --- /dev/null +++ b/friendly_promotions/app/javascript/solidus_friendly_promotions.js @@ -0,0 +1,3 @@ +import "@hotwired/turbo-rails"; + +Turbo.session.drive = false; diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/edit.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/edit.html.erb index ecbc6580c3d..a14083a6b0c 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/edit.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/edit.html.erb @@ -4,10 +4,7 @@ <% admin_breadcrumb(@promotion.name) %> <%= content_for :head do %> - <%= javascript_importmap_tags "@hotwired/turbo-rails" %> - + <%= javascript_importmap_tags "solidus_friendly_promotions", importmap: SolidusFriendlyPromotions.importmap %> <% end %> <% content_for :page_actions do %> diff --git a/friendly_promotions/bin/importmap b/friendly_promotions/bin/importmap new file mode 100755 index 00000000000..e300bd5a959 --- /dev/null +++ b/friendly_promotions/bin/importmap @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby + +require_relative "../spec/dummy/config/application" +require "importmap/commands" diff --git a/friendly_promotions/config/importmap.rb b/friendly_promotions/config/importmap.rb new file mode 100644 index 00000000000..1dfd19cecf4 --- /dev/null +++ b/friendly_promotions/config/importmap.rb @@ -0,0 +1,2 @@ +pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true +pin "solidus_friendly_promotions", to: "solidus_friendly_promotions.js", preload: true diff --git a/friendly_promotions/lib/solidus_friendly_promotions.rb b/friendly_promotions/lib/solidus_friendly_promotions.rb index 06fe7d45aa4..bcf6fb40006 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions.rb @@ -2,6 +2,14 @@ require "spree" require "turbo-rails" +require "importmap-rails" require "solidus_friendly_promotions/configuration" require "solidus_friendly_promotions/version" require "solidus_friendly_promotions/engine" + +module SolidusFriendlyPromotions + + # JS Importmap instance + singleton_class.attr_accessor :importmap + self.importmap = Importmap::Map.new +end diff --git a/friendly_promotions/lib/solidus_friendly_promotions/engine.rb b/friendly_promotions/lib/solidus_friendly_promotions/engine.rb index b4fef4a447a..955b405032b 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions/engine.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions/engine.rb @@ -15,5 +15,23 @@ class Engine < Rails::Engine config.generators do |g| g.test_framework :rspec end + + initializer "solidus_friendly_promotions.assets" do |app| + app.config.assets.precompile << "solidus_friendly_promotions/manifest.js" + end + + initializer "solidus_friendly_promotions.importmap" do |app| + SolidusFriendlyPromotions.importmap.draw(Engine.root.join("config", "importmap.rb")) + + package_path = Engine.root.join("app/javascript") + app.config.assets.paths << package_path + + if app.config.importmap.sweep_cache + SolidusFriendlyPromotions.importmap.cache_sweeper(watches: package_path) + ActiveSupport.on_load(:action_controller_base) do + before_action { SolidusFriendlyPromotions.importmap.cache_sweeper.execute_if_updated } + end + end + end end end diff --git a/friendly_promotions/solidus_friendly_promotions.gemspec b/friendly_promotions/solidus_friendly_promotions.gemspec index b485d014438..fb6b5946c32 100644 --- a/friendly_promotions/solidus_friendly_promotions.gemspec +++ b/friendly_promotions/solidus_friendly_promotions.gemspec @@ -33,5 +33,6 @@ Gem::Specification.new do |spec| spec.add_dependency "turbo-rails", "~> 1.4" spec.add_development_dependency "solidus_dev_support", "~> 2.6" + spec.add_development_dependency "importmap-rails", "~> 1.2" spec.add_development_dependency "rspec-activemodel-mocks", "~> 1.0" end From 26850d09bc8d6455196e6e8fb112dcd0a1e58d00 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 29 Jun 2023 11:37:45 +0200 Subject: [PATCH 238/834] Make tiered calculators work through Stimulus I'd rather not import Handlebars and Backbone.js here, so I opt to add Stimulus instead. --- .../solidus_friendly_promotions/manifest.js | 3 ++ .../javascript/solidus_friendly_promotions.js | 1 + .../controllers/application.js | 9 +++++ .../calculator_tiers_controller.js | 37 +++++++++++++++++++ .../controllers/index.js | 8 ++++ .../admin/promotion_actions/_form.html.erb | 2 +- .../actions/_adjust_line_item.html.erb | 3 +- .../actions/_adjust_shipment.html.erb | 3 +- .../actions/_calculator_fields.erb | 9 ++--- .../tiered_flat_rate/_fields.html.erb | 17 +++++---- .../tiered_flat_rate/_tier_fields.html.erb | 32 ++++++++++++++++ .../tiered_percent/_fields.html.erb | 17 +++++---- .../tiered_percent/_tier_fields.html.erb | 32 ++++++++++++++++ friendly_promotions/config/importmap.rb | 5 +++ 14 files changed, 155 insertions(+), 23 deletions(-) create mode 100644 friendly_promotions/app/javascript/solidus_friendly_promotions/controllers/application.js create mode 100644 friendly_promotions/app/javascript/solidus_friendly_promotions/controllers/calculator_tiers_controller.js create mode 100644 friendly_promotions/app/javascript/solidus_friendly_promotions/controllers/index.js create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/calculators/tiered_flat_rate/_tier_fields.html.erb create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/calculators/tiered_percent/_tier_fields.html.erb diff --git a/friendly_promotions/app/assets/config/solidus_friendly_promotions/manifest.js b/friendly_promotions/app/assets/config/solidus_friendly_promotions/manifest.js index 4ed0da56709..89e5209526e 100644 --- a/friendly_promotions/app/assets/config/solidus_friendly_promotions/manifest.js +++ b/friendly_promotions/app/assets/config/solidus_friendly_promotions/manifest.js @@ -1 +1,4 @@ //= link solidus_friendly_promotions.js +//= link solidus_friendly_promotions/controllers/application.js +//= link solidus_friendly_promotions/controllers/index.js +//= link solidus_friendly_promotions/controllers/calculator_tiers_controller.js diff --git a/friendly_promotions/app/javascript/solidus_friendly_promotions.js b/friendly_promotions/app/javascript/solidus_friendly_promotions.js index 40809761f14..d8587a050db 100644 --- a/friendly_promotions/app/javascript/solidus_friendly_promotions.js +++ b/friendly_promotions/app/javascript/solidus_friendly_promotions.js @@ -1,3 +1,4 @@ import "@hotwired/turbo-rails"; +import "solidus_friendly_promotions/controllers"; Turbo.session.drive = false; diff --git a/friendly_promotions/app/javascript/solidus_friendly_promotions/controllers/application.js b/friendly_promotions/app/javascript/solidus_friendly_promotions/controllers/application.js new file mode 100644 index 00000000000..d6fe5ebece7 --- /dev/null +++ b/friendly_promotions/app/javascript/solidus_friendly_promotions/controllers/application.js @@ -0,0 +1,9 @@ +import { Application } from "@hotwired/stimulus"; + +const application = Application.start(); + +// Configure Stimulus development experience +application.debug = false; +window.Stimulus = application; + +export { application }; diff --git a/friendly_promotions/app/javascript/solidus_friendly_promotions/controllers/calculator_tiers_controller.js b/friendly_promotions/app/javascript/solidus_friendly_promotions/controllers/calculator_tiers_controller.js new file mode 100644 index 00000000000..b977952fbeb --- /dev/null +++ b/friendly_promotions/app/javascript/solidus_friendly_promotions/controllers/calculator_tiers_controller.js @@ -0,0 +1,37 @@ +import { Controller } from "@hotwired/stimulus"; + +export default class extends Controller { + static targets = ["links", "template"]; + + connect() { + this.wrapperClass = this.data.get("wrapperClass") || "calculator-tiers"; + } + + add_association(event) { + event.preventDefault(); + + var content = this.templateTarget.innerHTML; + this.linksTarget.insertAdjacentHTML("beforebegin", content); + } + + propagate_base_to_value_input(event) { + event.preventDefault(); + + // targets the content of the last pair of square brackets + // we first need to greedily match all other square brackets + const regEx = /(\[.*\])\[.*?\]$/; + let wrapper = event.target.closest("." + this.wrapperClass); + let valueInput = wrapper.querySelector(".js-value-input"); + valueInput.name = valueInput.name.replace( + regEx, + `$1[${event.target.value}]` + ); + } + + remove_association(event) { + event.preventDefault(); + + let wrapper = event.target.closest("." + this.wrapperClass); + wrapper.remove(); + } +} diff --git a/friendly_promotions/app/javascript/solidus_friendly_promotions/controllers/index.js b/friendly_promotions/app/javascript/solidus_friendly_promotions/controllers/index.js new file mode 100644 index 00000000000..53c61308c25 --- /dev/null +++ b/friendly_promotions/app/javascript/solidus_friendly_promotions/controllers/index.js @@ -0,0 +1,8 @@ +import { application } from "solidus_friendly_promotions/controllers/application"; + +// Eager load all controllers defined in the import map under controllers/**/*_controller +import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading"; +eagerLoadControllersFrom( + "solidus_friendly_promotions/controllers", + application +); diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/_form.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/_form.html.erb index fdefca6a212..67ecdc1e213 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/_form.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/_form.html.erb @@ -1,3 +1,3 @@ <%= form.hidden_field :type %> <%= form.hidden_field :calculator_type %> -<%= render form.object, promotion_action: form.object, param_prefix: form.object_name %> +<%= render form.object, promotion_action: form.object, param_prefix: form.object_name, form: form %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/actions/_adjust_line_item.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/actions/_adjust_line_item.html.erb index ab28206107b..70787941d53 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/actions/_adjust_line_item.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/actions/_adjust_line_item.html.erb @@ -1,5 +1,6 @@ <%= render( "solidus_friendly_promotions/admin/promotion_actions/actions/calculator_fields", promotion_action: promotion_action, - param_prefix: param_prefix + param_prefix: param_prefix, + form: form ) %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/actions/_adjust_shipment.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/actions/_adjust_shipment.html.erb index ab28206107b..70787941d53 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/actions/_adjust_shipment.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/actions/_adjust_shipment.html.erb @@ -1,5 +1,6 @@ <%= render( "solidus_friendly_promotions/admin/promotion_actions/actions/calculator_fields", promotion_action: promotion_action, - param_prefix: param_prefix + param_prefix: param_prefix, + form: form ) %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/actions/_calculator_fields.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/actions/_calculator_fields.erb index 33ea65d6aba..0973024cd09 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/actions/_calculator_fields.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/actions/_calculator_fields.erb @@ -1,11 +1,8 @@ <% calculator = promotion_action.calculator %> <% type_name = calculator.type.demodulize.underscore %> -<% if lookup_context.exists?("fields", - ["solidus_friendly_promotions/admin/promotion_actions/calculators/#{type_name}"], true) %> -<%= render "solidus_friendly_promotions/admin/promotion_actions/calculators/#{type_name}/fields", - calculator: calculator, prefix: param_prefix %> +<% if lookup_context.exists?("fields", ["solidus_friendly_promotions/admin/promotion_actions/calculators/#{type_name}"], true) %> + <%= render "solidus_friendly_promotions/admin/promotion_actions/calculators/#{type_name}/fields", calculator: calculator, prefix: param_prefix, form: form %> <% else %> -<%= render "solidus_friendly_promotions/admin/promotion_actions/calculators/default_fields", - calculator: calculator, prefix: param_prefix %> + <%= render "solidus_friendly_promotions/admin/promotion_actions/calculators/default_fields", calculator: calculator, prefix: param_prefix, form: form %> <% end %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/calculators/tiered_flat_rate/_fields.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/calculators/tiered_flat_rate/_fields.html.erb index f19d927f29a..05e86e4a52d 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/calculators/tiered_flat_rate/_fields.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/calculators/tiered_flat_rate/_fields.html.erb @@ -19,12 +19,15 @@
    <%= label_tag nil, t('spree.tiers') %> -
    - <%= content_tag :div, nil, class: "js-tiers", data: { - 'original-tiers' => Hash[calculator.preferred_tiers.sort], - 'form-prefix' => prefix, - 'calculator' => 'tiered_flat_rate' - } %> - <%= t('spree.actions.add') %> +
    + + <% form.object.calculator.preferred_tiers.each do |tier| %> + <%= render "solidus_friendly_promotions/admin/promotion_actions/calculators/tiered_flat_rate/tier_fields", tier: tier, form: form %> + <% end %> +
    + <%= link_to "Add Tier", "#", class: "btn btn-outline-primary", data: { action: "click->calculator-tiers#add_association" } %> +
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/calculators/tiered_flat_rate/_tier_fields.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/calculators/tiered_flat_rate/_tier_fields.html.erb new file mode 100644 index 00000000000..ab1824fee4a --- /dev/null +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/calculators/tiered_flat_rate/_tier_fields.html.erb @@ -0,0 +1,32 @@ +
    + +
    +
    +
    +
    + $ +
    + +
    +
    +
    +
    +
    + $ +
    + +
    +
    +
    +
    +
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/calculators/tiered_percent/_fields.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/calculators/tiered_percent/_fields.html.erb index ff6bb6797b8..2c1caf585ef 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/calculators/tiered_percent/_fields.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/calculators/tiered_percent/_fields.html.erb @@ -19,12 +19,15 @@
    <%= label_tag nil, t('spree.tiers') %> -
    - <%= content_tag :div, nil, class: "js-tiers", data: { - 'original-tiers' => Hash[calculator.preferred_tiers.sort], - 'form-prefix' => prefix, - 'calculator' => 'tiered_percent' - } %> - <%= t('spree.actions.add') %> +
    + + <% form.object.calculator.preferred_tiers.each do |tier| %> + <%= render "solidus_friendly_promotions/admin/promotion_actions/calculators/tiered_percent/tier_fields", tier: tier, form: form %> + <% end %> +
    + <%= link_to "Add Tier", "#", class: "btn btn-outline-primary", data: { action: "click->calculator-tiers#add_association" } %> +
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/calculators/tiered_percent/_tier_fields.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/calculators/tiered_percent/_tier_fields.html.erb new file mode 100644 index 00000000000..1eb134d2fa7 --- /dev/null +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/calculators/tiered_percent/_tier_fields.html.erb @@ -0,0 +1,32 @@ +
    + +
    +
    +
    +
    + $ +
    + +
    +
    +
    +
    + +
    + % +
    +
    +
    +
    +
    +
    diff --git a/friendly_promotions/config/importmap.rb b/friendly_promotions/config/importmap.rb index 1dfd19cecf4..de0cba41fce 100644 --- a/friendly_promotions/config/importmap.rb +++ b/friendly_promotions/config/importmap.rb @@ -1,2 +1,7 @@ pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true + +pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true +pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true +pin_all_from SolidusFriendlyPromotions::Engine.root.join("app/javascript/solidus_friendly_promotions/controllers"), under: "solidus_friendly_promotions/controllers" + pin "solidus_friendly_promotions", to: "solidus_friendly_promotions.js", preload: true From 7681c84ce41057e68bcf1f16e732ff35132b0580 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 29 Jun 2023 11:51:56 +0200 Subject: [PATCH 239/834] Properly render number with currency forms --- .../app/javascript/solidus_friendly_promotions.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/friendly_promotions/app/javascript/solidus_friendly_promotions.js b/friendly_promotions/app/javascript/solidus_friendly_promotions.js index d8587a050db..02df6384785 100644 --- a/friendly_promotions/app/javascript/solidus_friendly_promotions.js +++ b/friendly_promotions/app/javascript/solidus_friendly_promotions.js @@ -2,3 +2,7 @@ import "@hotwired/turbo-rails"; import "solidus_friendly_promotions/controllers"; Turbo.session.drive = false; + +document.addEventListener("turbo:frame-load", ({ _target }) => { + Spree.initNumberWithCurrency(); +}); From 89381d1fff03241ca4eb082d92088ea3c0872053 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 29 Jun 2023 16:14:25 +0200 Subject: [PATCH 240/834] Add promotion rules to UI This features also working product, taxon, and user pickers. --- .../solidus_friendly_promotions/manifest.js | 1 + .../admin/promotion_rules_controller.rb | 22 ++++++++-- .../admin/promotion_actions_helper.rb | 6 +++ .../admin/promotion_rules_helper.rb | 14 ++----- .../javascript/solidus_friendly_promotions.js | 3 ++ .../controllers/flash_controller.js | 10 +++++ .../_promotion_action.html.erb | 40 ++++++++----------- .../_promotion_rule.html.erb | 5 ++- .../promotion_rules/_type_select.html.erb | 20 ++++++++++ .../admin/promotion_rules/new.html.erb | 33 +++++++++------ .../admin/promotions/_new_rule_form.html.erb | 2 +- .../admin/promotions/edit.html.erb | 35 ++++++++++++++-- .../rules/_line_item_product.html.erb | 4 +- friendly_promotions/config/locales/en.yml | 27 ++++++++++++- 14 files changed, 164 insertions(+), 58 deletions(-) create mode 100644 friendly_promotions/app/javascript/solidus_friendly_promotions/controllers/flash_controller.js rename friendly_promotions/app/views/solidus_friendly_promotions/admin/{promotions => promotion_rules}/_promotion_rule.html.erb (85%) create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/_type_select.html.erb diff --git a/friendly_promotions/app/assets/config/solidus_friendly_promotions/manifest.js b/friendly_promotions/app/assets/config/solidus_friendly_promotions/manifest.js index 89e5209526e..b03f91fc335 100644 --- a/friendly_promotions/app/assets/config/solidus_friendly_promotions/manifest.js +++ b/friendly_promotions/app/assets/config/solidus_friendly_promotions/manifest.js @@ -2,3 +2,4 @@ //= link solidus_friendly_promotions/controllers/application.js //= link solidus_friendly_promotions/controllers/index.js //= link solidus_friendly_promotions/controllers/calculator_tiers_controller.js +//= link solidus_friendly_promotions/controllers/flash_controller.js diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb index 2e7b3c21670..c9e0f494123 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb @@ -2,14 +2,18 @@ module SolidusFriendlyPromotions module Admin - class PromotionRulesController < Spree::Admin::ResourceController + class PromotionRulesController < Spree::Admin::BaseController helper 'spree/promotion_rules' + before_action :validate_level, only: [:new, :edit, :create] before_action :load_promotion, only: [:create, :destroy, :update, :new] - before_action :validate_promotion_rule_type, only: [:create, :new] + before_action :validate_promotion_rule_type, only: [:create] def new - @promotion_rule = @promotion.promotion_rules.build(type: @promotion_rule_type) + if params.dig(:promotion_rule, :type) + validate_promotion_rule_type + @promotion_rule = @promotion.promotion_rules.build(type: @promotion_rule_type) + end render layout: false end @@ -46,7 +50,7 @@ def model_class def validate_promotion_rule_type requested_type = params[:promotion_rule].delete(:type) - promotion_rule_types = SolidusFriendlyPromotions.config.order_rules + promotion_rule_types = SolidusFriendlyPromotions.config.send("#{@level}_rules") @promotion_rule_type = promotion_rule_types.detect do |klass| klass.name == requested_type end @@ -59,6 +63,16 @@ def validate_promotion_rule_type end end + def validate_level + requested_level = params[:level].to_s + if requested_level.in?(["order", "line_item", "shipment"]) + @level = requested_level + else + @level = "order" + flash.now[:error] = t(:invalid_promotion_rule_level, scope: :solidus_friendly_promotions) + end + end + def promotion_rule_params params[:promotion_rule].permit! end diff --git a/friendly_promotions/app/helpers/solidus_friendly_promotions/admin/promotion_actions_helper.rb b/friendly_promotions/app/helpers/solidus_friendly_promotions/admin/promotion_actions_helper.rb index 9d26203ca7d..ba897d8b304 100644 --- a/friendly_promotions/app/helpers/solidus_friendly_promotions/admin/promotion_actions_helper.rb +++ b/friendly_promotions/app/helpers/solidus_friendly_promotions/admin/promotion_actions_helper.rb @@ -14,6 +14,12 @@ def options_for_promotion_action_types(promotion_action) options = actions.map { |action| [action.model_name.human, action.name] } options_for_select(options, promotion_action.type.to_s) end + + def promotion_actions_by_level(promotion, level) + promotion.actions.select do |rule| + rule.class.name.demodulize.underscore.ends_with?(level.to_s) + end + end end end end diff --git a/friendly_promotions/app/helpers/solidus_friendly_promotions/admin/promotion_rules_helper.rb b/friendly_promotions/app/helpers/solidus_friendly_promotions/admin/promotion_rules_helper.rb index 691dcace0ef..3eedd5c90e1 100644 --- a/friendly_promotions/app/helpers/solidus_friendly_promotions/admin/promotion_rules_helper.rb +++ b/friendly_promotions/app/helpers/solidus_friendly_promotions/admin/promotion_rules_helper.rb @@ -3,22 +3,16 @@ module SolidusFriendlyPromotions module Admin module PromotionRulesHelper - def options_for_promotion_rule_types(promotion, level) - existing = promotion.rules.map { |rule| rule.class.name } + def options_for_promotion_rule_types(promotion_rule, level) + existing = promotion_rule.promotion.rules.select(&:persisted?).map { |rule| rule.class.name } rules = SolidusFriendlyPromotions.config.send("#{level}_rules").reject { |rule| existing.include? rule.name } options = rules.map { |rule| [rule.model_name.human, rule.name] } - options_for_select(options) + options_for_select(options, promotion_rule.type.to_s) end def promotion_rules_by_level(promotion, level) promotion.rules.select do |rule| - rule.class.name.in?(SolidusFriendlyPromotions.config.send("#{level}_rules")) - end - end - - def promotion_actions_by_level(promotion, level) - promotion.actions.select do |rule| - rule.class.name.demodulize.underscore.ends_with?(level.to_s) + rule.class.in?(SolidusFriendlyPromotions.config.send("#{level}_rules").to_a) end end end diff --git a/friendly_promotions/app/javascript/solidus_friendly_promotions.js b/friendly_promotions/app/javascript/solidus_friendly_promotions.js index 02df6384785..1c46292c453 100644 --- a/friendly_promotions/app/javascript/solidus_friendly_promotions.js +++ b/friendly_promotions/app/javascript/solidus_friendly_promotions.js @@ -5,4 +5,7 @@ Turbo.session.drive = false; document.addEventListener("turbo:frame-load", ({ _target }) => { Spree.initNumberWithCurrency(); + $(".product_picker").productAutocomplete(); + $(".user_picker").userAutocomplete(); + $(".taxon_picker").taxonAutocomplete(); }); diff --git a/friendly_promotions/app/javascript/solidus_friendly_promotions/controllers/flash_controller.js b/friendly_promotions/app/javascript/solidus_friendly_promotions/controllers/flash_controller.js new file mode 100644 index 00000000000..7ee234e7399 --- /dev/null +++ b/friendly_promotions/app/javascript/solidus_friendly_promotions/controllers/flash_controller.js @@ -0,0 +1,10 @@ +import { Controller } from "@hotwired/stimulus"; + +export default class extends Controller { + connect() { + window.show_flash( + this.element.dataset.severity, + this.element.dataset.message + ); + } +} diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/_promotion_action.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/_promotion_action.html.erb index 516cadba59c..8daa59697a6 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/_promotion_action.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/_promotion_action.html.erb @@ -6,30 +6,24 @@ <%= link_to_with_icon 'trash', '', solidus_friendly_promotions.admin_promotion_promotion_action_path(@promotion, promotion_action), method: :delete, class: 'delete' %> <% end %> -
    -
    - <%= render "solidus_friendly_promotions/admin/promotion_actions/calculator_select", - path: solidus_friendly_promotions.edit_admin_promotion_promotion_action_path(@promotion, promotion_action), - promotion_action: promotion_action %> - <%= - form_with( - model: promotion_action, - scope: :promotion_action, - url: solidus_friendly_promotions.admin_promotion_promotion_action_path(@promotion, promotion_action), - data: { turbo: false } - ) do |form| %> - <%= render 'solidus_friendly_promotions/admin/promotion_actions/form', form: form %> -
    -
    - <%= button_tag "Update", class: "btn btn-secondary float-right" %> -
    -
    - <% end %> -
    -
    - BUTTON + <%= render "solidus_friendly_promotions/admin/promotion_actions/calculator_select", + path: solidus_friendly_promotions.edit_admin_promotion_promotion_action_path(@promotion, promotion_action), + promotion_action: promotion_action %> + + <%= + form_with( + model: promotion_action, + scope: :promotion_action, + url: solidus_friendly_promotions.admin_promotion_promotion_action_path(@promotion, promotion_action), + data: { turbo: false } + ) do |form| %> + <%= render 'solidus_friendly_promotions/admin/promotion_actions/form', form: form %> +
    +
    + <%= button_tag "Update", class: "btn btn-secondary float-right" %> +
    -
    + <% end %>
    <% end %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_promotion_rule.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/_promotion_rule.html.erb similarity index 85% rename from friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_promotion_rule.html.erb rename to friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/_promotion_rule.html.erb index d02651a055a..f9ed14489b6 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_promotion_rule.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/_promotion_rule.html.erb @@ -1,10 +1,13 @@
    <%= form_tag solidus_friendly_promotions.admin_promotion_promotion_rule_path(@promotion, promotion_rule), method: :patch do %> -
    <%= promotion_rule.class.human_attribute_name(:description) %>
    +
    <%= promotion_rule.class.model_name.human %>
    <% if can?(:destroy, promotion_rule) %> <%= link_to_with_icon 'trash', '', spree.admin_promotion_promotion_rule_path(@promotion, promotion_rule), method: :delete, class: 'delete' %> <% end %> +

    + <%= promotion_rule.class.human_attribute_name(:description) %> +

    <% param_prefix = "promotion[promotion_rules_attributes][#{promotion_rule.id}]" %> <%= hidden_field_tag "#{param_prefix}[id]", promotion_rule.id %> <%= render partial: "spree/shared/error_messages", locals: { target: promotion_rule } %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/_type_select.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/_type_select.html.erb new file mode 100644 index 00000000000..5dd101e1256 --- /dev/null +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/_type_select.html.erb @@ -0,0 +1,20 @@ +<%= form_with( + model: @promotion_rule || Spree::PromotionRule.new(promotion: @promotion), + scope: :promotion_rule, + url: solidus_friendly_promotions.new_admin_promotion_promotion_rule_path(@promotion), + method: :get + ) do |form| %> + <%= hidden_field_tag :level, @level %> + <%= form.label :type %> + <%= admin_hint t('spree.adjustment_type'), t(:promotions, scope: [:spree, :hints, "spree/calculator"]) %> + <%= + form.select :type, + options_for_promotion_rule_types(form.object, level), + { + include_blank: t(:choose_promotion_rule, scope: 'solidus_friendly_shipping') + }, + class: 'custom-select fullwidth', + onchange: 'this.form.requestSubmit()', + required: true + %> +<% end %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/new.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/new.html.erb index fcc28fc2ce6..1ab443efec0 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/new.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/new.html.erb @@ -1,16 +1,25 @@ -<%= turbo_frame_tag @promotion, "new_promotion_rule" do %> - <%= form_tag solidus_friendly_promotions.admin_promotion_promotion_rules_path(@promotion) do %> -
    -
    <%= @promotion_rule.class.human_attribute_name(:description) %>
    - <%= link_to_with_icon 'trash', '', spree.edit_admin_promotion_path(@promotion), class: 'delete' %> - <%= render partial: "spree/shared/error_messages", locals: { target: @promotion_rule } %> - <%= hidden_field_tag "promotion_rule[type]", @promotion_rule.class.name %> - <%= render partial: @promotion_rule.to_partial_path, locals: { promotion_rule: @promotion_rule, param_prefix: "promotion_rule" } %> -
    -
    - <%= button_tag "Add", class: "btn btn-secondary float-right", data: { "turbo-frame" => "_top" } %> +<%= turbo_frame_tag @promotion, "new_#{@level}_promotion_rule" do %> +
    +
    <%= t(:add_rule, scope: :solidus_friendly_promotions) %>
    + <%= link_to_with_icon 'trash', '', spree.edit_admin_promotion_path(@promotion), class: 'delete' %> + <%= render 'type_select', level: @level %> + <% flash.each do |severity, message| %> + <%= content_tag(:div, "", data: { controller: :flash, severity: severity, message: message }) %> + <% end %> + <% if @promotion_rule %> +

    + <%= @promotion_rule.class.human_attribute_name(:description) %> +

    + <%= form_tag solidus_friendly_promotions.admin_promotion_promotion_rules_path(@promotion), data: { turbo: false } do %> + <%= hidden_field_tag :level, @level %> + <%= hidden_field_tag "promotion_rule[type]", @promotion_rule.class.name %> + <%= render partial: @promotion_rule.to_partial_path, locals: { promotion_rule: @promotion_rule, param_prefix: "promotion_rule" } %> +
    +
    + <%= button_tag "Add", class: "btn btn-secondary float-right", data: { "turbo-frame" => "_top" } %> +
    -
    + <% end %>
    <% end %> <% end %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_new_rule_form.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_new_rule_form.html.erb index 02c09e3fd04..a4ff1196a01 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_new_rule_form.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_new_rule_form.html.erb @@ -6,7 +6,7 @@ <%= label_tag :promotion_rule_type, Spree::PromotionRule.human_attribute_name(:type) %>
    <%= select_tag('promotion_rule[type]', options_for_promotion_rule_types(@promotion, level), include_blank: t(:choose_promotion_rule, scope: 'spree'), class: 'custom-select fullwidth', required: true) %> - <%= hidden_field_tag("level", level) %> + <%= hidden_field_tag("level", @level) %>
    <%= button_tag t('spree.actions.add'), class: 'btn btn-secondary' %>
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/edit.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/edit.html.erb index a14083a6b0c..650f229bf62 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/edit.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/edit.html.erb @@ -30,11 +30,38 @@
    - <%= t(:actions, scope: :solidus_friendly_promotions) %> + <%= t(:order_rules, scope: :solidus_friendly_promotions) %> - <%= render partial: 'solidus_friendly_promotions/admin/promotion_actions/promotion_action', collection: @promotion.actions, locals: {} %> + <%= render partial: 'solidus_friendly_promotions/admin/promotion_rules/promotion_rule', collection: promotion_rules_by_level(@promotion, :order), locals: { level: :order } %> - <%= turbo_frame_tag @promotion, "new_promotion_action" do %> - <%= link_to t(:add_action, scope: :solidus_friendly_promotions), solidus_friendly_promotions.new_admin_promotion_promotion_action_path(@promotion), class: 'btn btn-secondary' %> + <%= turbo_frame_tag @promotion, "new_order_promotion_rule" do %> + <%= link_to t(:add_rule, scope: :solidus_friendly_promotions), solidus_friendly_promotions.new_admin_promotion_promotion_rule_path(@promotion, level: :order), class: 'btn btn-secondary' %> <% end %>
    + +
    + <% [:line_item, :shipment].each do |level| %> +
    +
    + <%= t("#{level}_actions", scope: :solidus_friendly_promotions) %> + + <%= render partial: 'solidus_friendly_promotions/admin/promotion_actions/promotion_action', collection: promotion_actions_by_level(@promotion, level), locals: {} %> + + <%= turbo_frame_tag @promotion, "new_#{level}_promotion_action" do %> + <%= link_to t(:add_action, scope: :solidus_friendly_promotions), solidus_friendly_promotions.new_admin_promotion_promotion_action_path(@promotion), class: 'btn btn-secondary' %> + <% end %> +
    +
    +
    +
    + <%= t("#{level}_rules", scope: :solidus_friendly_promotions) %> + + <%= render partial: 'solidus_friendly_promotions/admin/promotion_rules/promotion_rule', collection: promotion_rules_by_level(@promotion, level), locals: { level: level } %> + + <%= turbo_frame_tag @promotion, "new_#{level}_promotion_rule" do %> + <%= link_to t(:add_rule, scope: :solidus_friendly_promotions), solidus_friendly_promotions.new_admin_promotion_promotion_rule_path(@promotion, level: level), class: 'btn btn-secondary' %> + <% end %> +
    +
    + <% end %> +
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_product.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_product.html.erb index 972486873b6..2c6f7dd5288 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_product.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_product.html.erb @@ -1,7 +1,7 @@
    - <%= label_tag "#{param_prefix}_product_ids_string", t('spree.product_rule.choose_products') %> + <%= label_tag "#{param_prefix}_product_ids_string", t('solidus_friendly_promotions.product_rule.choose_products') %> <%= hidden_field_tag "#{param_prefix}[product_ids_string]", promotion_rule.product_ids.join(","), class: "product_picker fullwidth" %>
    @@ -10,7 +10,7 @@ <%= label_tag("#{param_prefix}_preferred_match_policy", promotion_rule.class.human_attribute_name(:preferred_match_policy)) %> <%= select_tag( "#{param_prefix}[preferred_match_policy]", - options_for_select(I18n.t("spree.promotion_rules.line_item_product.match_policies").to_a.map(&:reverse), promotion_rule.preferred_match_policy), + options_for_select(I18n.t("solidus_friendly_promotions.promotion_rules.line_item_product.match_policies").to_a.map(&:reverse), promotion_rule.preferred_match_policy), class: "custom-select fullwidth" ) %>
    diff --git a/friendly_promotions/config/locales/en.yml b/friendly_promotions/config/locales/en.yml index e206f210336..f92ce2b5968 100644 --- a/friendly_promotions/config/locales/en.yml +++ b/friendly_promotions/config/locales/en.yml @@ -4,7 +4,14 @@ en: solidus_friendly_promotions: actions: Actions - add_action: Add action + add_action: New Action + add_rule: New Rule + order_rules: Order Rules + line_item_rules: Line Item Rules + shipment_rules: Shipment Rules + line_item_actions: Line Item Actions + shipment_actions: Shipment Actions + invalid_promotion_rule_level: Invalid Promotion Rule Level. Must be one of "order", "shipment", or "line_item" create_promotion_code: Create promotion code current_promotion_usage: 'Current Usage: %{count}' discount_rules: Promotion Rules @@ -15,6 +22,24 @@ en: no_rules_addes: No Rules Added promotion_successfully_created: Promotion has been successfully created! view_promotion_codes_list: View codes list + promotion_rules: + line_item_product: + match_policies: + include: Line item's product is one of the chosen products + exclude: Line item's product is not one of the chosen products + line_item_taxon: + match_policies: + include: Line item's product has one of the chosen taxons + exclude: Line item's product does not have one of the chosen taxons + product_rule: + choose_products: Choose products + label: Order must contain %{select} of these products + match_all: all + match_any: at least one + match_none: none + product_source: + group: From product group + manual: Manually choose crud: add: Add destroy: Delete From c60234bd7baf0462e75bd75b8946fd7b9047a886 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 29 Jun 2023 16:21:52 +0200 Subject: [PATCH 241/834] Only display rule buttons if they are useful If there are no line item-level actions, we need no line-item level rules. --- .../admin/promotions/edit.html.erb | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/edit.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/edit.html.erb index 650f229bf62..f40de46bebb 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/edit.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/edit.html.erb @@ -53,15 +53,17 @@
    -
    - <%= t("#{level}_rules", scope: :solidus_friendly_promotions) %> + <% if promotion_actions_by_level(@promotion, level).any? %> +
    + <%= t("#{level}_rules", scope: :solidus_friendly_promotions) %> - <%= render partial: 'solidus_friendly_promotions/admin/promotion_rules/promotion_rule', collection: promotion_rules_by_level(@promotion, level), locals: { level: level } %> + <%= render partial: 'solidus_friendly_promotions/admin/promotion_rules/promotion_rule', collection: promotion_rules_by_level(@promotion, level), locals: { level: level } %> - <%= turbo_frame_tag @promotion, "new_#{level}_promotion_rule" do %> - <%= link_to t(:add_rule, scope: :solidus_friendly_promotions), solidus_friendly_promotions.new_admin_promotion_promotion_rule_path(@promotion, level: level), class: 'btn btn-secondary' %> - <% end %> -
    + <%= turbo_frame_tag @promotion, "new_#{level}_promotion_rule" do %> + <%= link_to t(:add_rule, scope: :solidus_friendly_promotions), solidus_friendly_promotions.new_admin_promotion_promotion_rule_path(@promotion, level: level), class: 'btn btn-secondary' %> + <% end %> +
    + <% end %>
    <% end %> From d0e84f4e48f6c0a1c434549727f4be5b4134a328 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 29 Jun 2023 16:22:34 +0200 Subject: [PATCH 242/834] Add translations for taxon rules --- .../admin/promotions/rules/_line_item_taxon.html.erb | 4 ++-- friendly_promotions/config/locales/en.yml | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_taxon.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_taxon.html.erb index c06d03c79d8..1a2dbcf7e48 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_taxon.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_taxon.html.erb @@ -1,5 +1,5 @@
    - <%= label_tag "#{param_prefix}_taxon_ids_string", t("spree.taxon_rule.choose_taxons") %> + <%= label_tag "#{param_prefix}_taxon_ids_string", t("solidus_friendly_promotions.taxon_rule.choose_taxons") %> <%= hidden_field_tag "#{param_prefix}[taxon_ids_string]", promotion_rule.taxon_ids.join(","), class: "taxon_picker fullwidth", id: "product_taxon_ids" %>
    @@ -7,7 +7,7 @@ <%= label_tag("#{param_prefix}_preferred_match_policy", promotion_rule.class.human_attribute_name(:preferred_match_policy)) %> <%= select_tag( "#{param_prefix}[preferred_match_policy]", - options_for_select(I18n.t("spree.promotion_rules.line_item_taxon.match_policies").to_a.map(&:reverse), promotion_rule.preferred_match_policy), + options_for_select(I18n.t("solidus_friendly_promotions.promotion_rules.line_item_taxon.match_policies").to_a.map(&:reverse), promotion_rule.preferred_match_policy), class: "custom-select fullwidth", ) %>
    diff --git a/friendly_promotions/config/locales/en.yml b/friendly_promotions/config/locales/en.yml index f92ce2b5968..5d0b0fc91d8 100644 --- a/friendly_promotions/config/locales/en.yml +++ b/friendly_promotions/config/locales/en.yml @@ -40,6 +40,12 @@ en: product_source: group: From product group manual: Manually choose + taxon_rule: + choose_taxons: Choose taxons + label: Order must contain %{select} of these taxons + match_all: all + match_any: at least one + match_none: none crud: add: Add destroy: Delete From 10ecf76b266b7a08c7fe2d5b31d32cd1111cc23c Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 29 Jun 2023 16:27:13 +0200 Subject: [PATCH 243/834] Add promotion rule update action --- .../admin/promotion_rules_controller.rb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb index c9e0f494123..1c237425034 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb @@ -26,6 +26,15 @@ def create redirect_to location_after_save end + def update + @promotion_rule = @promotion.promotion_rules.find(params[:id]) + @promotion_rule.assign_attributes(promotion_rule_params) + if @promotion_rule.save + flash[:success] = t('spree.successfully_updated', resource: t('spree.promotion_rule')) + end + redirect_to location_after_save + end + def destroy @promotion_rule = @promotion.promotion_rules.find(params[:id]) if @promotion_rule.destroy From 098a18fee6f992760c9e24cff8f2335a75f9bed4 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Mon, 3 Jul 2023 10:42:08 +0200 Subject: [PATCH 244/834] Add Line Item Option Values Controller --- .../solidus_friendly_promotions/manifest.js | 1 + .../product_option_values_controller.js | 62 +++++++++++++++++++ .../promotion_rules/_promotion_rule.html.erb | 4 +- .../admin/promotion_rules/new.html.erb | 4 +- .../rules/_line_item_option_value.html.erb | 17 +++-- .../_option_value_fields.html.erb | 17 +++++ 6 files changed, 97 insertions(+), 8 deletions(-) create mode 100644 friendly_promotions/app/javascript/solidus_friendly_promotions/controllers/product_option_values_controller.js create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/line_item_option_value/_option_value_fields.html.erb diff --git a/friendly_promotions/app/assets/config/solidus_friendly_promotions/manifest.js b/friendly_promotions/app/assets/config/solidus_friendly_promotions/manifest.js index b03f91fc335..46a78bb3b35 100644 --- a/friendly_promotions/app/assets/config/solidus_friendly_promotions/manifest.js +++ b/friendly_promotions/app/assets/config/solidus_friendly_promotions/manifest.js @@ -3,3 +3,4 @@ //= link solidus_friendly_promotions/controllers/index.js //= link solidus_friendly_promotions/controllers/calculator_tiers_controller.js //= link solidus_friendly_promotions/controllers/flash_controller.js +//= link solidus_friendly_promotions/controllers/product_option_values_controller.js diff --git a/friendly_promotions/app/javascript/solidus_friendly_promotions/controllers/product_option_values_controller.js b/friendly_promotions/app/javascript/solidus_friendly_promotions/controllers/product_option_values_controller.js new file mode 100644 index 00000000000..70003800a17 --- /dev/null +++ b/friendly_promotions/app/javascript/solidus_friendly_promotions/controllers/product_option_values_controller.js @@ -0,0 +1,62 @@ +import { Controller } from "@hotwired/stimulus"; + +export default class extends Controller { + static targets = ["links", "template"]; + + connect() { + this.wrapperClass = + this.data.get("wrapperClass") || "promo-rule-option-value"; + + this.element.querySelectorAll("." + this.wrapperClass).forEach((element) => this.buildSelects(element)) + } + + add_row(event) { + event.preventDefault(); + + var content = this.templateTarget.innerHTML; + this.linksTarget.insertAdjacentHTML("beforebegin", content); + this.buildSelects(this.linksTarget.previousElementSibling) + } + + propagate_product_id_to_value_input(event) { + event.preventDefault(); + // targets the content of the last pair of square brackets + // we first need to greedily match all other square brackets + const regEx = /(\[.*\])\[.*?\]$/; + let wrapper = event.target.closest("." + this.wrapperClass); + let optionValuesInput = wrapper.querySelector(".option-values-select[type='hidden']"); + optionValuesInput.name = optionValuesInput.name.replace( + regEx, + `$1[${event.target.value}]` + ); + } + + remove_row(event) { + event.preventDefault(); + + let wrapper = event.target.closest("." + this.wrapperClass); + wrapper.remove(); + } + + // helper functions + + buildSelects(wrapper) { + let productSelect = wrapper.querySelector(".product-select") + let optionValueSelect = wrapper.querySelector(".option-values-select[type='hidden']") + this.buildProductSelect(productSelect) + $(optionValueSelect).optionValueAutocomplete({ productSelect }); + } + + buildProductSelect(productSelect) { + var jQueryProductSelect = $(productSelect) + jQueryProductSelect.productAutocomplete({ + multiple: false, + }) + // capture the jQuery "change" event and re-emit it as DOM event "select2Change" + // so that Stimulus can capture it + jQueryProductSelect.on('change', function () { + let event = new Event('select2Change', { bubbles: true }) // fire a native event + productSelect.dispatchEvent(event); + }); + } +} diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/_promotion_rule.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/_promotion_rule.html.erb index f9ed14489b6..bc7accac723 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/_promotion_rule.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/_promotion_rule.html.erb @@ -1,6 +1,6 @@
    - <%= form_tag solidus_friendly_promotions.admin_promotion_promotion_rule_path(@promotion, promotion_rule), method: :patch do %> + <%= form_with model: promotion_rule, scope: :promotion_rule, url: solidus_friendly_promotions.admin_promotion_promotion_rule_path(@promotion, promotion_rule), method: :patch do |form| %>
    <%= promotion_rule.class.model_name.human %>
    <% if can?(:destroy, promotion_rule) %> <%= link_to_with_icon 'trash', '', spree.admin_promotion_promotion_rule_path(@promotion, promotion_rule), method: :delete, class: 'delete' %> @@ -11,7 +11,7 @@ <% param_prefix = "promotion[promotion_rules_attributes][#{promotion_rule.id}]" %> <%= hidden_field_tag "#{param_prefix}[id]", promotion_rule.id %> <%= render partial: "spree/shared/error_messages", locals: { target: promotion_rule } %> - <%= render partial: promotion_rule.to_partial_path, locals: { promotion_rule: promotion_rule, param_prefix: "promotion_rule" } %> + <%= render promotion_rule, promotion_rule: promotion_rule, param_prefix: "promotion_rule", form: form %>
    <%= button_tag "Update", class: "btn btn-secondary float-right" if promotion_rule.preferences.any? %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/new.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/new.html.erb index 1ab443efec0..01ebdd15db5 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/new.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/new.html.erb @@ -10,10 +10,10 @@

    <%= @promotion_rule.class.human_attribute_name(:description) %>

    - <%= form_tag solidus_friendly_promotions.admin_promotion_promotion_rules_path(@promotion), data: { turbo: false } do %> + <%= form_with model: @promotion_rule, scope: "promotion_rule", url: solidus_friendly_promotions.admin_promotion_promotion_rules_path(@promotion), data: { turbo: false } do |form| %> <%= hidden_field_tag :level, @level %> <%= hidden_field_tag "promotion_rule[type]", @promotion_rule.class.name %> - <%= render partial: @promotion_rule.to_partial_path, locals: { promotion_rule: @promotion_rule, param_prefix: "promotion_rule" } %> + <%= render @promotion_rule, promotion_rule: @promotion_rule, param_prefix: "promotion_rule", form: form %>
    <%= button_tag "Add", class: "btn btn-secondary float-right", data: { "turbo-frame" => "_top" } %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_option_value.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_option_value.html.erb index 99ca90d34af..49d25160eca 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_option_value.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_option_value.html.erb @@ -5,8 +5,17 @@
    <%= label_tag nil, plural_resource_name(Spree::OptionValue) %>
    - <%= content_tag :div, nil, class: "js-promo-rule-option-values", - data: { :'original-option-values' => promotion_rule.preferred_eligible_values } %> - - +
    +
    + + <% form.object.preferred_eligible_values.each do |product_option_values| %> + <%= render "solidus_friendly_promotions/admin/promotions/rules/line_item_option_value/option_value_fields", product_option_values: product_option_values, form: form %> + <% end %> +
    + <%= link_to "Add Row", "#", class: "btn btn-outline-primary", data: { action: "click->product-option-values#add_row" } %> +
    +
    +
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/line_item_option_value/_option_value_fields.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/line_item_option_value/_option_value_fields.html.erb new file mode 100644 index 00000000000..26d117035a5 --- /dev/null +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/line_item_option_value/_option_value_fields.html.erb @@ -0,0 +1,17 @@ +
    +
    +
    + +
    +
    + "> +
    +
    + +
    +
    From 7786cd21f5a5fcb99c53fd8eeb21f42dd1ab2533 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Mon, 3 Jul 2023 10:42:42 +0200 Subject: [PATCH 245/834] Add level to promotion rules controller We need to know which rules to show --- .../admin/promotion_actions_controller.rb | 11 +++++++++++ .../admin/promotion_actions/new.html.erb | 2 +- .../admin/promotions/edit.html.erb | 2 +- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb index 083334d4bd4..7a2603281b8 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb @@ -4,6 +4,7 @@ module SolidusFriendlyPromotions module Admin class PromotionActionsController < Spree::Admin::ResourceController before_action :load_promotion + before_action :validate_level, only: :new before_action :validate_promotion_action_type, only: [:create] helper 'solidus_friendly_promotions/admin/promotion_actions' @@ -68,6 +69,16 @@ def load_promotion @promotion = Spree::Promotion.find(params[:promotion_id]) end + def validate_level + requested_level = params[:level].to_s + if requested_level.in?(["line_item", "shipment"]) + @level = requested_level + else + @level = "line_item" + flash.now[:error] = t(:invalid_promotion_rule_level, scope: :solidus_friendly_promotions) + end + end + def validate_promotion_action_type requested_type = params[:promotion_action].delete(:type) promotion_action_types = SolidusFriendlyPromotions.config.actions diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/new.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/new.html.erb index f7ea1bd4cdb..b95fe080550 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/new.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/new.html.erb @@ -1,4 +1,4 @@ -<%= turbo_frame_tag @promotion, "new_promotion_action" do %> +<%= turbo_frame_tag @promotion, "new_#{@level}_promotion_action" do %>
    <%= t(:add_action, scope: :solidus_friendly_promotions) %>
    <%= link_to_with_icon 'trash', '', spree.edit_admin_promotion_path(@promotion), class: 'delete' %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/edit.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/edit.html.erb index f40de46bebb..97ed6162bf6 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/edit.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/edit.html.erb @@ -48,7 +48,7 @@ <%= render partial: 'solidus_friendly_promotions/admin/promotion_actions/promotion_action', collection: promotion_actions_by_level(@promotion, level), locals: {} %> <%= turbo_frame_tag @promotion, "new_#{level}_promotion_action" do %> - <%= link_to t(:add_action, scope: :solidus_friendly_promotions), solidus_friendly_promotions.new_admin_promotion_promotion_action_path(@promotion), class: 'btn btn-secondary' %> + <%= link_to t(:add_action, scope: :solidus_friendly_promotions), solidus_friendly_promotions.new_admin_promotion_promotion_action_path(@promotion, level: level), class: 'btn btn-secondary' %> <% end %>
    From 6eddd87f10b4871151c4535b2cdb7379f52baf67 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Mon, 3 Jul 2023 10:50:36 +0200 Subject: [PATCH 246/834] Import JQuery option value picker This isn't used anywhere else in Solidus, so let's import it. --- .../solidus_friendly_promotions/manifest.js | 1 + .../javascript/solidus_friendly_promotions.js | 1 + .../jquery/option_value_picker.js | 44 +++++++++++++++++++ friendly_promotions/config/importmap.rb | 1 + 4 files changed, 47 insertions(+) create mode 100644 friendly_promotions/app/javascript/solidus_friendly_promotions/jquery/option_value_picker.js diff --git a/friendly_promotions/app/assets/config/solidus_friendly_promotions/manifest.js b/friendly_promotions/app/assets/config/solidus_friendly_promotions/manifest.js index 46a78bb3b35..cffce0f1866 100644 --- a/friendly_promotions/app/assets/config/solidus_friendly_promotions/manifest.js +++ b/friendly_promotions/app/assets/config/solidus_friendly_promotions/manifest.js @@ -4,3 +4,4 @@ //= link solidus_friendly_promotions/controllers/calculator_tiers_controller.js //= link solidus_friendly_promotions/controllers/flash_controller.js //= link solidus_friendly_promotions/controllers/product_option_values_controller.js +//= link solidus_friendly_promotions/jquery/option_value_picker.js diff --git a/friendly_promotions/app/javascript/solidus_friendly_promotions.js b/friendly_promotions/app/javascript/solidus_friendly_promotions.js index 1c46292c453..5a6d4506083 100644 --- a/friendly_promotions/app/javascript/solidus_friendly_promotions.js +++ b/friendly_promotions/app/javascript/solidus_friendly_promotions.js @@ -1,5 +1,6 @@ import "@hotwired/turbo-rails"; import "solidus_friendly_promotions/controllers"; +import "solidus_friendly_promotions/jquery/option_value_picker" Turbo.session.drive = false; diff --git a/friendly_promotions/app/javascript/solidus_friendly_promotions/jquery/option_value_picker.js b/friendly_promotions/app/javascript/solidus_friendly_promotions/jquery/option_value_picker.js new file mode 100644 index 00000000000..17b4c71eeb3 --- /dev/null +++ b/friendly_promotions/app/javascript/solidus_friendly_promotions/jquery/option_value_picker.js @@ -0,0 +1,44 @@ +$.fn.optionValueAutocomplete = function (options) { + 'use strict'; + + // Default options + options = options || {} + var multiple = typeof(options['multiple']) !== 'undefined' ? options['multiple'] : true; + var productSelect = options['productSelect']; + + function formatOptionValue(option_value) { + return Select2.util.escapeMarkup(option_value.name); + } + + this.select2({ + minimumInputLength: 3, + multiple: multiple, + initSelection: function (element, callback) { + $.get(Spree.pathFor('api/option_values'), { + ids: element.val().split(','), + token: Spree.api_key + }, function (data) { + callback(multiple ? data : data[0]); + }); + }, + ajax: { + url: Spree.pathFor('api/option_values'), + datatype: 'json', + data: function (term, page) { + var productId = typeof(productSelect) !== 'undefined' ? $(productSelect).select2('val') : null; + return { + q: { + name_cont: term, + variants_product_id_eq: productId + }, + token: Spree.api_key + }; + }, + results: function (data, page) { + return { results: data }; + } + }, + formatResult: formatOptionValue, + formatSelection: formatOptionValue + }); +}; diff --git a/friendly_promotions/config/importmap.rb b/friendly_promotions/config/importmap.rb index de0cba41fce..f0ffda37249 100644 --- a/friendly_promotions/config/importmap.rb +++ b/friendly_promotions/config/importmap.rb @@ -3,5 +3,6 @@ pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true pin_all_from SolidusFriendlyPromotions::Engine.root.join("app/javascript/solidus_friendly_promotions/controllers"), under: "solidus_friendly_promotions/controllers" +pin_all_from SolidusFriendlyPromotions::Engine.root.join("app/javascript/solidus_friendly_promotions/jquery"), under: "solidus_friendly_promotions/jquery" pin "solidus_friendly_promotions", to: "solidus_friendly_promotions.js", preload: true From d209a5fa9205a1b0f6ac89ac9ed234659ebcd8e8 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Mon, 3 Jul 2023 10:52:28 +0200 Subject: [PATCH 247/834] Fix Order option value rule This can use the same JS and templates as the line item option value rule. --- .../promotions/rules/_option_value.html.erb | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_option_value.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_option_value.html.erb index 093148c1cfe..49d25160eca 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_option_value.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_option_value.html.erb @@ -5,9 +5,17 @@
    <%= label_tag nil, plural_resource_name(Spree::OptionValue) %>
    - <%= content_tag :div, nil, class: "js-promo-rule-option-values", - data: { :'original-option-values' => promotion_rule.preferred_eligible_values } %> - - - +
    +
    + + <% form.object.preferred_eligible_values.each do |product_option_values| %> + <%= render "solidus_friendly_promotions/admin/promotions/rules/line_item_option_value/option_value_fields", product_option_values: product_option_values, form: form %> + <% end %> +
    + <%= link_to "Add Row", "#", class: "btn btn-outline-primary", data: { action: "click->product-option-values#add_row" } %> +
    +
    +
    From 947519608eddeb1f518e50efe886593e6e5d944f Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Mon, 3 Jul 2023 10:58:09 +0200 Subject: [PATCH 248/834] Move rule form submit button into partials It's cleaner to have the button inside the partials in order to not give a button to rules that have no preferences or relations. --- .../admin/promotion_rules/_promotion_rule.html.erb | 5 ----- .../admin/promotion_rules/new.html.erb | 5 ----- .../promotions/rules/_first_repeat_purchase_since.html.erb | 6 ++++++ .../admin/promotions/rules/_item_total.html.erb | 6 ++++++ .../admin/promotions/rules/_line_item_option_value.html.erb | 6 ++++++ .../admin/promotions/rules/_line_item_product.html.erb | 6 ++++++ .../admin/promotions/rules/_line_item_taxon.html.erb | 6 ++++++ .../admin/promotions/rules/_nth_order.html.erb | 6 ++++++ .../admin/promotions/rules/_option_value.html.erb | 6 ++++++ .../admin/promotions/rules/_product.html.erb | 6 ++++++ .../admin/promotions/rules/_store.html.erb | 6 ++++++ .../admin/promotions/rules/_taxon.html.erb | 6 ++++++ .../admin/promotions/rules/_user.html.erb | 6 ++++++ .../admin/promotions/rules/_user_role.html.erb | 6 ++++++ 14 files changed, 72 insertions(+), 10 deletions(-) diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/_promotion_rule.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/_promotion_rule.html.erb index bc7accac723..a2564b48829 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/_promotion_rule.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/_promotion_rule.html.erb @@ -12,10 +12,5 @@ <%= hidden_field_tag "#{param_prefix}[id]", promotion_rule.id %> <%= render partial: "spree/shared/error_messages", locals: { target: promotion_rule } %> <%= render promotion_rule, promotion_rule: promotion_rule, param_prefix: "promotion_rule", form: form %> -
    -
    - <%= button_tag "Update", class: "btn btn-secondary float-right" if promotion_rule.preferences.any? %> -
    -
    <% end %>
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/new.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/new.html.erb index 01ebdd15db5..022c47c3e7a 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/new.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/new.html.erb @@ -14,11 +14,6 @@ <%= hidden_field_tag :level, @level %> <%= hidden_field_tag "promotion_rule[type]", @promotion_rule.class.name %> <%= render @promotion_rule, promotion_rule: @promotion_rule, param_prefix: "promotion_rule", form: form %> -
    -
    - <%= button_tag "Add", class: "btn btn-secondary float-right", data: { "turbo-frame" => "_top" } %> -
    -
    <% end %> <% end %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_first_repeat_purchase_since.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_first_repeat_purchase_since.html.erb index 8e2e587aab7..a19c540b415 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_first_repeat_purchase_since.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_first_repeat_purchase_since.html.erb @@ -11,3 +11,9 @@ + +
    +
    + <%= form.submit(t((form.object.persisted? ? :update : :add), scope: [:solidus_friendly_promotions, :crud]), class: "btn btn-secondary float-right") %> +
    +
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_item_total.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_item_total.html.erb index 1b0767b2ea9..90c9d17c967 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_item_total.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_item_total.html.erb @@ -12,3 +12,9 @@ + +
    +
    + <%= form.submit(t((form.object.persisted? ? :update : :add), scope: [:solidus_friendly_promotions, :crud]), class: "btn btn-secondary float-right") %> +
    +
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_option_value.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_option_value.html.erb index 49d25160eca..94414312519 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_option_value.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_option_value.html.erb @@ -19,3 +19,9 @@ + +
    +
    + <%= form.submit(t((form.object.persisted? ? :update : :add), scope: [:solidus_friendly_promotions, :crud]), class: "btn btn-secondary float-right") %> +
    +
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_product.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_product.html.erb index 2c6f7dd5288..e2d7ac86d95 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_product.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_product.html.erb @@ -16,3 +16,9 @@ + +
    +
    + <%= form.submit(t((form.object.persisted? ? :update : :add), scope: [:solidus_friendly_promotions, :crud]), class: "btn btn-secondary float-right") %> +
    +
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_taxon.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_taxon.html.erb index 1a2dbcf7e48..14db1d05fc8 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_taxon.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_taxon.html.erb @@ -12,3 +12,9 @@ ) %> + +
    +
    + <%= form.submit(t((form.object.persisted? ? :update : :add), scope: [:solidus_friendly_promotions, :crud]), class: "btn btn-secondary float-right") %> +
    +
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_nth_order.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_nth_order.html.erb index 0a37ce60cf1..2ccb5f0eebc 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_nth_order.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_nth_order.html.erb @@ -10,3 +10,9 @@ + +
    +
    + <%= form.submit(t((form.object.persisted? ? :update : :add), scope: [:solidus_friendly_promotions, :crud]), class: "btn btn-secondary float-right") %> +
    +
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_option_value.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_option_value.html.erb index 49d25160eca..94414312519 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_option_value.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_option_value.html.erb @@ -19,3 +19,9 @@ + +
    +
    + <%= form.submit(t((form.object.persisted? ? :update : :add), scope: [:solidus_friendly_promotions, :crud]), class: "btn btn-secondary float-right") %> +
    +
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_product.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_product.html.erb index 2d11f098832..c0ec650e450 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_product.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_product.html.erb @@ -13,3 +13,9 @@ + +
    +
    + <%= form.submit(t((form.object.persisted? ? :update : :add), scope: [:solidus_friendly_promotions, :crud]), class: "btn btn-secondary float-right") %> +
    +
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_store.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_store.html.erb index cce8ac2e37f..1018cccf30d 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_store.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_store.html.erb @@ -4,3 +4,9 @@ options_from_collection_for_select(Spree::Store.all, :id, :name, promotion_rule.store_ids), multiple: true, class: "select2 fullwidth" %> + +
    +
    + <%= form.submit(t((form.object.persisted? ? :update : :add), scope: [:solidus_friendly_promotions, :crud]), class: "btn btn-secondary float-right") %> +
    +
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_taxon.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_taxon.html.erb index bec9eb7e9c3..3df5bf3bde5 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_taxon.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_taxon.html.erb @@ -7,3 +7,9 @@ <%= t('spree.taxon_rule.label', select: select_tag("#{param_prefix}[preferred_match_policy]", options_for_select(Spree::Promotion::Rules::Taxon::MATCH_POLICIES.map{|s| [t("spree.taxon_rule.match_#{s}"),s] }, promotion_rule.preferred_match_policy), {class: 'select_taxon custom-select'})).html_safe %> + +
    +
    + <%= form.submit(t((form.object.persisted? ? :update : :add), scope: [:solidus_friendly_promotions, :crud]), class: "btn btn-secondary float-right") %> +
    +
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_user.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_user.html.erb index 91391a18fd0..1f76adf4a52 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_user.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_user.html.erb @@ -2,3 +2,9 @@
    + +
    +
    + <%= form.submit(t((form.object.persisted? ? :update : :add), scope: [:solidus_friendly_promotions, :crud]), class: "btn btn-secondary float-right") %> +
    +
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_user_role.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_user_role.html.erb index e078526d08f..cd82a42faa1 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_user_role.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_user_role.html.erb @@ -10,3 +10,9 @@ <%= t('spree.user_role_rule.label', select: select_tag("#{param_prefix}[preferred_match_policy]", options_for_select(Spree::Promotion::Rules::UserRole::MATCH_POLICIES.map{ |s| [t("spree.user_role_rule.match_#{s}"),s] }, promotion_rule.preferred_match_policy), { class: 'custom-select'})).html_safe %> + +
    +
    + <%= form.submit(t((form.object.persisted? ? :update : :add), scope: [:solidus_friendly_promotions, :crud]), class: "btn btn-secondary float-right") %> +
    +
    From 00fa3a479b390367fecdb582558f25026ef1c8f1 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Mon, 3 Jul 2023 11:07:18 +0200 Subject: [PATCH 249/834] Move promotion rule partials into app/views/admin/promotion_rules This is the way. --- .../app/models/solidus_friendly_promotions/rules/base.rb | 2 +- .../rules/_first_order.html.erb | 0 .../rules/_first_repeat_purchase_since.html.erb | 0 .../rules/_item_total.html.erb | 0 .../rules/_line_item_option_value.html.erb | 4 ++-- .../rules/_line_item_product.html.erb | 0 .../rules/_line_item_taxon.html.erb | 0 .../{promotions => promotion_rules}/rules/_nth_order.html.erb | 0 .../rules/_one_use_per_user.html.erb | 0 .../rules/_option_value.html.erb | 4 ++-- .../{promotions => promotion_rules}/rules/_product.html.erb | 0 .../{promotions => promotion_rules}/rules/_store.html.erb | 0 .../{promotions => promotion_rules}/rules/_taxon.html.erb | 0 .../{promotions => promotion_rules}/rules/_user.html.erb | 0 .../rules/_user_logged_in.html.erb | 0 .../{promotions => promotion_rules}/rules/_user_role.html.erb | 0 .../line_item_option_value/_option_value_fields.html.erb | 0 .../solidus_friendly_promotions/rules/first_order_spec.rb | 2 +- 18 files changed, 6 insertions(+), 6 deletions(-) rename friendly_promotions/app/views/solidus_friendly_promotions/admin/{promotions => promotion_rules}/rules/_first_order.html.erb (100%) rename friendly_promotions/app/views/solidus_friendly_promotions/admin/{promotions => promotion_rules}/rules/_first_repeat_purchase_since.html.erb (100%) rename friendly_promotions/app/views/solidus_friendly_promotions/admin/{promotions => promotion_rules}/rules/_item_total.html.erb (100%) rename friendly_promotions/app/views/solidus_friendly_promotions/admin/{promotions => promotion_rules}/rules/_line_item_option_value.html.erb (74%) rename friendly_promotions/app/views/solidus_friendly_promotions/admin/{promotions => promotion_rules}/rules/_line_item_product.html.erb (100%) rename friendly_promotions/app/views/solidus_friendly_promotions/admin/{promotions => promotion_rules}/rules/_line_item_taxon.html.erb (100%) rename friendly_promotions/app/views/solidus_friendly_promotions/admin/{promotions => promotion_rules}/rules/_nth_order.html.erb (100%) rename friendly_promotions/app/views/solidus_friendly_promotions/admin/{promotions => promotion_rules}/rules/_one_use_per_user.html.erb (100%) rename friendly_promotions/app/views/solidus_friendly_promotions/admin/{promotions => promotion_rules}/rules/_option_value.html.erb (74%) rename friendly_promotions/app/views/solidus_friendly_promotions/admin/{promotions => promotion_rules}/rules/_product.html.erb (100%) rename friendly_promotions/app/views/solidus_friendly_promotions/admin/{promotions => promotion_rules}/rules/_store.html.erb (100%) rename friendly_promotions/app/views/solidus_friendly_promotions/admin/{promotions => promotion_rules}/rules/_taxon.html.erb (100%) rename friendly_promotions/app/views/solidus_friendly_promotions/admin/{promotions => promotion_rules}/rules/_user.html.erb (100%) rename friendly_promotions/app/views/solidus_friendly_promotions/admin/{promotions => promotion_rules}/rules/_user_logged_in.html.erb (100%) rename friendly_promotions/app/views/solidus_friendly_promotions/admin/{promotions => promotion_rules}/rules/_user_role.html.erb (100%) rename friendly_promotions/app/views/solidus_friendly_promotions/admin/{promotions => promotion_rules}/rules/line_item_option_value/_option_value_fields.html.erb (100%) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/base.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/base.rb index 4fa28c8f554..e511e305fc6 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/base.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/base.rb @@ -4,7 +4,7 @@ module SolidusFriendlyPromotions module Rules class Base < Spree::PromotionRule def to_partial_path - "solidus_friendly_promotions/admin/promotions/rules/#{model_name.element}" + "solidus_friendly_promotions/admin/promotion_rules/rules/#{model_name.element}" end end end diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_first_order.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_first_order.html.erb similarity index 100% rename from friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_first_order.html.erb rename to friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_first_order.html.erb diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_first_repeat_purchase_since.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_first_repeat_purchase_since.html.erb similarity index 100% rename from friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_first_repeat_purchase_since.html.erb rename to friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_first_repeat_purchase_since.html.erb diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_item_total.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_item_total.html.erb similarity index 100% rename from friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_item_total.html.erb rename to friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_item_total.html.erb diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_option_value.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_line_item_option_value.html.erb similarity index 74% rename from friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_option_value.html.erb rename to friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_line_item_option_value.html.erb index 94414312519..bedd2b5cb63 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_option_value.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_line_item_option_value.html.erb @@ -8,10 +8,10 @@
    <% form.object.preferred_eligible_values.each do |product_option_values| %> - <%= render "solidus_friendly_promotions/admin/promotions/rules/line_item_option_value/option_value_fields", product_option_values: product_option_values, form: form %> + <%= render "solidus_friendly_promotions/admin/promotion_rules/rules/line_item_option_value/option_value_fields", product_option_values: product_option_values, form: form %> <% end %>
    <%= link_to "Add Row", "#", class: "btn btn-outline-primary", data: { action: "click->product-option-values#add_row" } %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_product.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_line_item_product.html.erb similarity index 100% rename from friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_product.html.erb rename to friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_line_item_product.html.erb diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_taxon.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_line_item_taxon.html.erb similarity index 100% rename from friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_taxon.html.erb rename to friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_line_item_taxon.html.erb diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_nth_order.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_nth_order.html.erb similarity index 100% rename from friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_nth_order.html.erb rename to friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_nth_order.html.erb diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_one_use_per_user.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_one_use_per_user.html.erb similarity index 100% rename from friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_one_use_per_user.html.erb rename to friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_one_use_per_user.html.erb diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_option_value.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_option_value.html.erb similarity index 74% rename from friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_option_value.html.erb rename to friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_option_value.html.erb index 94414312519..bedd2b5cb63 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_option_value.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_option_value.html.erb @@ -8,10 +8,10 @@
    <% form.object.preferred_eligible_values.each do |product_option_values| %> - <%= render "solidus_friendly_promotions/admin/promotions/rules/line_item_option_value/option_value_fields", product_option_values: product_option_values, form: form %> + <%= render "solidus_friendly_promotions/admin/promotion_rules/rules/line_item_option_value/option_value_fields", product_option_values: product_option_values, form: form %> <% end %>
    <%= link_to "Add Row", "#", class: "btn btn-outline-primary", data: { action: "click->product-option-values#add_row" } %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_product.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_product.html.erb similarity index 100% rename from friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_product.html.erb rename to friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_product.html.erb diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_store.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_store.html.erb similarity index 100% rename from friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_store.html.erb rename to friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_store.html.erb diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_taxon.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_taxon.html.erb similarity index 100% rename from friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_taxon.html.erb rename to friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_taxon.html.erb diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_user.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_user.html.erb similarity index 100% rename from friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_user.html.erb rename to friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_user.html.erb diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_user_logged_in.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_user_logged_in.html.erb similarity index 100% rename from friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_user_logged_in.html.erb rename to friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_user_logged_in.html.erb diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_user_role.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_user_role.html.erb similarity index 100% rename from friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_user_role.html.erb rename to friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_user_role.html.erb diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/line_item_option_value/_option_value_fields.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/line_item_option_value/_option_value_fields.html.erb similarity index 100% rename from friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/line_item_option_value/_option_value_fields.html.erb rename to friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/line_item_option_value/_option_value_fields.html.erb diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_order_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_order_spec.rb index 8be4c2fcbe7..bc6c2e82ba8 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_order_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_order_spec.rb @@ -9,7 +9,7 @@ describe ".to_partial_path" do subject { rule.to_partial_path } - it { is_expected.to eq("solidus_friendly_promotions/admin/promotions/rules/first_order") } + it { is_expected.to eq("solidus_friendly_promotions/admin/promotion_rules/rules/first_order") } end context "without a user or email" do From 735e81dfcf2dea833cf05a26786d832c37c448a1 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Mon, 3 Jul 2023 14:49:13 +0200 Subject: [PATCH 250/834] Fix promotion actions request spec --- .../admin/promotion_actions_request_spec.rb | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_actions_request_spec.rb b/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_actions_request_spec.rb index ad25638effb..63fd5325357 100644 --- a/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_actions_request_spec.rb +++ b/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_actions_request_spec.rb @@ -8,14 +8,19 @@ let!(:promotion) { create(:promotion) } it "can create a promotion action of a valid type" do - post solidus_friendly_promotions.admin_promotion_promotion_actions_path(promotion_id: promotion.id, action_type: "Spree::Promotion::Actions::CreateAdjustment") + post solidus_friendly_promotions.admin_promotion_promotion_actions_path(promotion_id: promotion.id), params: { + promotion_action: { + type: "SolidusFriendlyPromotions::Actions::AdjustLineItem", + calculator_attributes: { type: "SolidusFriendlyPromotions::Calculators::FlatRate" } + } + } expect(response).to be_redirect expect(response).to redirect_to spree.edit_admin_promotion_path(promotion) expect(promotion.actions.count).to eq(1) end it "can not create a promotion action of an invalid type" do - post solidus_friendly_promotions.admin_promotion_promotion_actions_path(promotion_id: promotion.id, action_type: "Spree::InvalidType") + post solidus_friendly_promotions.admin_promotion_promotion_actions_path(promotion_id: promotion.id, promotion_action: { type: "Spree::InvalidType" }) expect(response).to be_redirect expect(response).to redirect_to spree.edit_admin_promotion_path(promotion) expect(promotion.rules.count).to eq(0) From 1033a9ff19f3dca7cddaf378cca32aae95a1dac0 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Mon, 3 Jul 2023 14:56:45 +0200 Subject: [PATCH 251/834] Fix Action Path spec --- .../actions/adjust_line_item_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_line_item_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_line_item_spec.rb index d5ee10e2ae4..8d8ab9ae105 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_line_item_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_line_item_spec.rb @@ -13,6 +13,6 @@ describe ".to_partial_path" do subject { described_class.new.to_partial_path } - it { is_expected.to eq("solidus_friendly_promotions/admin/promotions/actions/adjust_line_item") } + it { is_expected.to eq("solidus_friendly_promotions/admin/promotion_actions/actions/adjust_line_item") } end end From 73af3c5ab67a45c936526d9931ce23b13629261b Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Mon, 3 Jul 2023 14:58:59 +0200 Subject: [PATCH 252/834] Fix promotion rules creation spec --- .../admin/promotion_rules_request_spec.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_rules_request_spec.rb b/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_rules_request_spec.rb index a555188e8c6..ac71afa53c6 100644 --- a/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_rules_request_spec.rb +++ b/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_rules_request_spec.rb @@ -11,7 +11,9 @@ end it "can create a promotion rule of a valid type" do - post solidus_friendly_promotions.admin_promotion_promotion_rules_path(promotion_id: promotion.id, promotion_rule: { type: "Spree::Promotion::Rules::Product" } ) + post solidus_friendly_promotions.admin_promotion_promotion_rules_path(promotion_id: promotion.id), params: { + promotion_rule: { type: "SolidusFriendlyPromotions::Rules::Product" } + } expect(response).to be_redirect expect(response).to redirect_to solidus_friendly_promotions.edit_admin_promotion_path(promotion) expect(promotion.rules.count).to eq(1) From 8d354b9eac6ae356906d73eefcafc1a36d5c6a91 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Mon, 3 Jul 2023 15:06:50 +0200 Subject: [PATCH 253/834] Solidus 4.1: Fix deprecation warning in Simple Order Updater --- .../simple_order_contents.rb | 4 ++-- .../simple_order_contents_spec.rb | 20 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/simple_order_contents.rb b/friendly_promotions/app/models/solidus_friendly_promotions/simple_order_contents.rb index e2dabf06327..c66dfa8c979 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/simple_order_contents.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/simple_order_contents.rb @@ -7,7 +7,7 @@ def update_cart(params) if order.update(params) unless order.completed? order.line_items = order.line_items.select { |li| li.quantity > 0 } - order.ensure_updated_shipments + order.check_shipments_and_restart_checkout end reload_totals true @@ -20,7 +20,7 @@ def update_cart(params) def after_add_or_remove(line_item, options = {}) shipment = options[:shipment] - shipment.present? ? shipment.update_amounts : order.ensure_updated_shipments + shipment.present? ? shipment.update_amounts : order.check_shipments_and_restart_checkout reload_totals line_item end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/simple_order_contents_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/simple_order_contents_spec.rb index a10b2396c3b..a101a7a9c66 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/simple_order_contents_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/simple_order_contents_spec.rb @@ -23,8 +23,8 @@ context 'given a shipment' do let!(:shipment) { create(:shipment, order: order) } - it "ensure shipment calls update_amounts instead of order calling ensure_updated_shipments" do - expect(subject.order).to_not receive(:ensure_updated_shipments) + it "ensure shipment calls update_amounts instead of order calling check_shipments_and_restart_checkout" do + expect(subject.order).to_not receive(:check_shipments_and_restart_checkout) expect(shipment).to receive(:update_amounts).at_least(:once) subject.add(variant, 1, shipment: shipment) end @@ -54,7 +54,7 @@ context 'not given a shipment' do it "ensures updated shipments" do - expect(subject.order).to receive(:ensure_updated_shipments) + expect(subject.order).to receive(:check_shipments_and_restart_checkout) subject.add(variant) end end @@ -159,10 +159,10 @@ end context 'given a shipment' do - it "ensure shipment calls update_amounts instead of order calling ensure_updated_shipments" do + it "ensure shipment calls update_amounts instead of order calling check_shipments_and_restart_checkout" do subject.add(variant, 1) shipment = create(:shipment) - expect(subject.order).to_not receive(:ensure_updated_shipments) + expect(subject.order).to_not receive(:check_shipments_and_restart_checkout) expect(shipment).to receive(:update_amounts) subject.remove(variant, 1, shipment: shipment) end @@ -171,7 +171,7 @@ context 'not given a shipment' do it "ensures updated shipments" do subject.add(variant, 1) - expect(subject.order).to receive(:ensure_updated_shipments) + expect(subject.order).to receive(:check_shipments_and_restart_checkout) subject.remove(variant) end end @@ -214,10 +214,10 @@ context "#remove_line_item" do context 'given a shipment' do - it "ensure shipment calls update_amounts instead of order calling ensure_updated_shipments" do + it "ensure shipment calls update_amounts instead of order calling check_shipments_and_restart_checkout" do line_item = subject.add(variant, 1) shipment = create(:shipment) - expect(subject.order).to_not receive(:ensure_updated_shipments) + expect(subject.order).to_not receive(:check_shipments_and_restart_checkout) expect(shipment).to receive(:update_amounts) subject.remove_line_item(line_item, shipment: shipment) end @@ -226,7 +226,7 @@ context 'not given a shipment' do it "ensures updated shipments" do line_item = subject.add(variant, 1) - expect(subject.order).to receive(:ensure_updated_shipments) + expect(subject.order).to receive(:check_shipments_and_restart_checkout) subject.remove_line_item(line_item) end end @@ -288,7 +288,7 @@ end it "ensures updated shipments" do - expect(subject.order).to receive(:ensure_updated_shipments) + expect(subject.order).to receive(:check_shipments_and_restart_checkout) subject.update_cart params end end From c2ad7a8d6947c13d08bdd788833bffd536443304 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Mon, 3 Jul 2023 14:33:36 +0200 Subject: [PATCH 254/834] Create Promotion class, table and spec --- .../solidus_friendly_promotions/promotion.rb | 11 +++++++ .../20230703101637_create_promotions.rb | 16 ++++++++++ .../promotion_spec.rb | 30 +++++++++++++++++++ 3 files changed, 57 insertions(+) create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb create mode 100644 friendly_promotions/db/migrate/20230703101637_create_promotions.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb new file mode 100644 index 00000000000..b47d5a2c9d0 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + class Promotion < Spree::Base + validates :name, presence: true + validates :path, uniqueness: { allow_blank: true, case_sensitive: true } + validates :usage_limit, numericality: { greater_than: 0, allow_nil: true } + validates :per_code_usage_limit, numericality: { greater_than_or_equal_to: 0, allow_nil: true } + validates :description, length: { maximum: 255 } + end +end diff --git a/friendly_promotions/db/migrate/20230703101637_create_promotions.rb b/friendly_promotions/db/migrate/20230703101637_create_promotions.rb new file mode 100644 index 00000000000..b0c98b1d077 --- /dev/null +++ b/friendly_promotions/db/migrate/20230703101637_create_promotions.rb @@ -0,0 +1,16 @@ +class CreatePromotions < ActiveRecord::Migration[7.0] + def change + create_table :solidus_friendly_promotions_promotions do |t| + t.string :description + t.datetime :expires_at, precision: nil + t.datetime :starts_at, precision: nil + t.string :name + t.integer :usage_limit + t.boolean :advertise, default: false + t.string :path + t.integer :per_code_usage_limit + t.boolean :apply_automatically, default: false + t.timestamps + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb new file mode 100644 index 00000000000..2026db2c59e --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SolidusFriendlyPromotions::Promotion, type: :model do + let(:promotion) { SolidusFriendlyPromotions::Promotion.new } + + describe "validations" do + before :each do + @valid_promotion = SolidusFriendlyPromotions::Promotion.new name: "A promotion" + end + + it "valid_promotion is valid" do + expect(@valid_promotion).to be_valid + end + + it "validates usage limit" do + @valid_promotion.usage_limit = -1 + expect(@valid_promotion).not_to be_valid + + @valid_promotion.usage_limit = 100 + expect(@valid_promotion).to be_valid + end + + it "validates name" do + @valid_promotion.name = nil + expect(@valid_promotion).not_to be_valid + end + end +end From a8c9d78fa560fc9771162bdb7087e9cf0a4f16a1 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Mon, 3 Jul 2023 16:35:12 +0200 Subject: [PATCH 255/834] Add shoulda-matchers gem Good for testing associations --- friendly_promotions/solidus_friendly_promotions.gemspec | 1 + friendly_promotions/spec/spec_helper.rb | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/friendly_promotions/solidus_friendly_promotions.gemspec b/friendly_promotions/solidus_friendly_promotions.gemspec index fb6b5946c32..af999ef9f5d 100644 --- a/friendly_promotions/solidus_friendly_promotions.gemspec +++ b/friendly_promotions/solidus_friendly_promotions.gemspec @@ -35,4 +35,5 @@ Gem::Specification.new do |spec| spec.add_development_dependency "solidus_dev_support", "~> 2.6" spec.add_development_dependency "importmap-rails", "~> 1.2" spec.add_development_dependency "rspec-activemodel-mocks", "~> 1.0" + spec.add_development_dependency "shoulda-matchers", "~> 5.3" end diff --git a/friendly_promotions/spec/spec_helper.rb b/friendly_promotions/spec/spec_helper.rb index 36851bd3aa9..3413d8d94b9 100644 --- a/friendly_promotions/spec/spec_helper.rb +++ b/friendly_promotions/spec/spec_helper.rb @@ -13,7 +13,7 @@ # Requires factories and other useful helpers defined in spree_core. require "solidus_dev_support/rspec/feature_helper" - +require "shoulda-matchers" # Explicitly load activemodel mocks require "rspec-activemodel-mocks" @@ -39,3 +39,10 @@ Spree::Config.order_contents_class = "SolidusFriendlyPromotions::SimpleOrderContents" end end + +Shoulda::Matchers.configure do |config| + config.integrate do |with| + with.test_framework :rspec + with.library :rails + end +end From 9ff28d772c5921191f7fcb4e1b908b887fbf294f Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Mon, 3 Jul 2023 16:35:29 +0200 Subject: [PATCH 256/834] Add promotion categories --- .../solidus_friendly_promotions/category.rb | 9 ++++++++ .../solidus_friendly_promotions/promotion.rb | 1 + ...30703141116_create_promotion_categories.rb | 11 ++++++++++ .../category_spec.rb | 21 +++++++++++++++++++ .../promotion_spec.rb | 6 ++++-- 5 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/category.rb create mode 100644 friendly_promotions/db/migrate/20230703141116_create_promotion_categories.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/category_spec.rb diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/category.rb b/friendly_promotions/app/models/solidus_friendly_promotions/category.rb new file mode 100644 index 00000000000..b800d746abb --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/category.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + class Category < Spree::Base + has_many :promotions + + validates :name, presence: true + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb index b47d5a2c9d0..31fd745f16b 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb @@ -2,6 +2,7 @@ module SolidusFriendlyPromotions class Promotion < Spree::Base + belongs_to :category, optional: true validates :name, presence: true validates :path, uniqueness: { allow_blank: true, case_sensitive: true } validates :usage_limit, numericality: { greater_than: 0, allow_nil: true } diff --git a/friendly_promotions/db/migrate/20230703141116_create_promotion_categories.rb b/friendly_promotions/db/migrate/20230703141116_create_promotion_categories.rb new file mode 100644 index 00000000000..e0a84bfc5c1 --- /dev/null +++ b/friendly_promotions/db/migrate/20230703141116_create_promotion_categories.rb @@ -0,0 +1,11 @@ +class CreatePromotionCategories < ActiveRecord::Migration[7.0] + def change + create_table :solidus_friendly_promotions_categories do |t| + t.string :name + + t.timestamps + end + + add_reference :solidus_friendly_promotions_promotions, :category, foreign_key: { to_table: :solidus_friendly_promotions_categories } + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/category_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/category_spec.rb new file mode 100644 index 00000000000..9fda2e04a9d --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/category_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SolidusFriendlyPromotions::Category, type: :model do + it { is_expected.to have_many :promotions } + + describe 'validation' do + let(:name) { 'Nom' } + subject { SolidusFriendlyPromotions::Category.new name: name } + + context 'when all required attributes are specified' do + it { is_expected.to be_valid } + end + + context 'when name is missing' do + let(:name) { nil } + it { is_expected.not_to be_valid } + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb index 2026db2c59e..3396ec5f0e6 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb @@ -3,11 +3,13 @@ require 'spec_helper' RSpec.describe SolidusFriendlyPromotions::Promotion, type: :model do - let(:promotion) { SolidusFriendlyPromotions::Promotion.new } + let(:promotion) { described_class.new } + + it { is_expected.to belong_to(:category).optional } describe "validations" do before :each do - @valid_promotion = SolidusFriendlyPromotions::Promotion.new name: "A promotion" + @valid_promotion = described_class.new name: "A promotion" end it "valid_promotion is valid" do From 1a0ab45f8e9f6cef21b91335a68b4134832848ee Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Mon, 3 Jul 2023 16:59:47 +0200 Subject: [PATCH 257/834] Add our own promotion rule base class --- .../solidus_friendly_promotions/promotion.rb | 2 + .../solidus_friendly_promotions/rule.rb | 47 +++++++++++++++++++ .../solidus_friendly_promotions/rules/base.rb | 11 ----- .../rules/first_order.rb | 2 +- .../rules/first_repeat_purchase_since.rb | 2 +- .../rules/item_total.rb | 2 +- .../rules/line_item_option_value.rb | 2 +- .../rules/line_item_product.rb | 2 +- .../rules/line_item_taxon.rb | 2 +- .../rules/nth_order.rb | 2 +- .../rules/one_use_per_user.rb | 2 +- .../rules/option_value.rb | 2 +- .../rules/product.rb | 2 +- .../rules/store.rb | 2 +- .../rules/taxon.rb | 2 +- .../solidus_friendly_promotions/rules/user.rb | 2 +- .../rules/user_logged_in.rb | 2 +- .../rules/user_role.rb | 2 +- .../db/migrate/20230703143943_create_rules.rb | 11 +++++ .../promotion_spec.rb | 1 + .../solidus_friendly_promotions/rule_spec.rb | 38 +++++++++++++++ 21 files changed, 114 insertions(+), 26 deletions(-) create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/rule.rb delete mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/rules/base.rb create mode 100644 friendly_promotions/db/migrate/20230703143943_create_rules.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/rule_spec.rb diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb index 31fd745f16b..28fa0c05c81 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb @@ -3,6 +3,8 @@ module SolidusFriendlyPromotions class Promotion < Spree::Base belongs_to :category, optional: true + has_many :rules + validates :name, presence: true validates :path, uniqueness: { allow_blank: true, case_sensitive: true } validates :usage_limit, numericality: { greater_than: 0, allow_nil: true } diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rule.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rule.rb new file mode 100644 index 00000000000..86efa637f7f --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rule.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require 'spree/preferences/persistable' + +module SolidusFriendlyPromotions + class Rule < Spree::Base + include Spree::Preferences::Persistable + + belongs_to :promotion + + scope :of_type, ->(type) { where(type: type) } + + validate :unique_per_promotion, on: :create + + def preload_relations + [] + end + + def applicable?(_promotable) + raise NotImplementedError, "applicable? should be implemented in a sub-class of SolidusFriendlyPromotions::Rule" + end + + def eligible?(_promotable, _options = {}) + raise NotImplementedError, "eligible? should be implemented in a sub-class of SolidusFriendlyPromotions::Rule" + end + + def eligibility_errors + @eligibility_errors ||= ActiveModel::Errors.new(self) + end + + def to_partial_path + "solidus_friendly_promotions/admin/promotion_rules/rules/#{model_name.element}" + end + + private + + def unique_per_promotion + if self.class.exists?(promotion_id: promotion_id, type: self.class.name) + errors[:base] << "Promotion already contains this rule type" + end + end + + def eligibility_error_message(key, options = {}) + I18n.t(key, **{ scope: [:spree, :eligibility_errors, :messages] }.merge(options)) + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/base.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/base.rb deleted file mode 100644 index e511e305fc6..00000000000 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/base.rb +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true - -module SolidusFriendlyPromotions - module Rules - class Base < Spree::PromotionRule - def to_partial_path - "solidus_friendly_promotions/admin/promotion_rules/rules/#{model_name.element}" - end - end - end -end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_order.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_order.rb index 81f00186b20..ca90c5b3f2e 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_order.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_order.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Rules - class FirstOrder < Base + class FirstOrder < Rule attr_reader :user, :email def applicable?(promotable) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb index 264c262623e..087f6314e8f 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Rules - class FirstRepeatPurchaseSince < Base + class FirstRepeatPurchaseSince < Rule preference :days_ago, :integer, default: 365 validates :preferred_days_ago, numericality: {only_integer: true, greater_than: 0} diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb index 0722f106a8c..d2af73b3772 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb @@ -7,7 +7,7 @@ module Rules # # To add extra operators please override `self.operators_map` or any other helper method. # To customize the error message you can also override `ineligible_message`. - class ItemTotal < Base + class ItemTotal < Rule preference :amount, :decimal, default: 100.00 preference :currency, :string, default: -> { Spree::Config[:currency] } preference :operator, :string, default: "gt" diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_option_value.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_option_value.rb index ac8ef3cb28f..f57bfb17d42 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_option_value.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_option_value.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Rules - class LineItemOptionValue < Base + class LineItemOptionValue < Rule preference :eligible_values, :hash def applicable?(promotable) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb index eff1ab54792..f74933a1676 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb @@ -3,7 +3,7 @@ module SolidusFriendlyPromotions module Rules # A rule to apply a promotion only to line items with or without a chosen product - class LineItemProduct < Base + class LineItemProduct < Rule MATCH_POLICIES = %w[include exclude] has_many :product_promotion_rules, diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb index 858d57a680a..48b73009922 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Rules - class LineItemTaxon < Base + class LineItemTaxon < Rule has_many :promotion_rule_taxons, class_name: "Spree::PromotionRuleTaxon", foreign_key: :promotion_rule_id, dependent: :destroy has_many :taxons, through: :promotion_rule_taxons, class_name: "Spree::Taxon" diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb index 8adba7157c1..bee23a67e22 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Rules - class NthOrder < Base + class NthOrder < Rule preference :nth_order, :integer, default: 2 # It does not make sense to have this apply to the first order using preferred_nth_order == 1 # Instead we could use the first_order rule diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb index 20edae46d7d..2cefe588199 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Rules - class OneUsePerUser < Base + class OneUsePerUser < Rule def applicable?(promotable) promotable.is_a?(Spree::Order) end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/option_value.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/option_value.rb index a5004233dee..4d5451a1dcb 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/option_value.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/option_value.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Rules - class OptionValue < Base + class OptionValue < Rule preference :eligible_values, :hash def applicable?(promotable) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb index 6de81662aa8..34847df0ba7 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb @@ -6,7 +6,7 @@ module Rules # require all or any of the products to be present. Valid products # either come from assigned product group or are assingned directly to # the rule. - class Product < Base + class Product < Rule has_many :product_promotion_rules, dependent: :destroy, foreign_key: :promotion_rule_id, class_name: "Spree::ProductPromotionRule" has_many :products, class_name: "Spree::Product", through: :product_promotion_rules diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb index 0ce363c3acd..baab2f1666d 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Rules - class Store < Base + class Store < Rule has_many :promotion_rule_stores, class_name: "Spree::PromotionRuleStore", foreign_key: :promotion_rule_id, dependent: :destroy diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb index 6e7f5b0077c..ea49627dfa0 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Rules - class Taxon < Base + class Taxon < Rule has_many :promotion_rule_taxons, class_name: "Spree::PromotionRuleTaxon", foreign_key: :promotion_rule_id, dependent: :destroy has_many :taxons, through: :promotion_rule_taxons, class_name: "Spree::Taxon" diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb index 1be7a6f74d3..2fcadd2e989 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Rules - class User < Base + class User < Rule has_many :promotion_rule_users, class_name: "Spree::PromotionRuleUser", foreign_key: :promotion_rule_id, dependent: :destroy diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb index 27a62132408..7c109cc956a 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Rules - class UserLoggedIn < Base + class UserLoggedIn < Rule def applicable?(promotable) promotable.is_a?(Spree::Order) end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_role.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_role.rb index 1b75e130a7a..5f5c5ac9d93 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_role.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_role.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Rules - class UserRole < Base + class UserRole < Rule preference :role_ids, :array, default: [] MATCH_POLICIES = %w[any all] diff --git a/friendly_promotions/db/migrate/20230703143943_create_rules.rb b/friendly_promotions/db/migrate/20230703143943_create_rules.rb new file mode 100644 index 00000000000..674672f9536 --- /dev/null +++ b/friendly_promotions/db/migrate/20230703143943_create_rules.rb @@ -0,0 +1,11 @@ +class CreateRules < ActiveRecord::Migration[7.0] + def change + create_table :solidus_friendly_promotions_rules do |t| + t.references :promotion, foreign_key: { to_table: :solidus_friendly_promotions_promotions } + t.string :type + t.text :preferences + + t.timestamps + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb index 3396ec5f0e6..7a0e1ce116d 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb @@ -6,6 +6,7 @@ let(:promotion) { described_class.new } it { is_expected.to belong_to(:category).optional } + it { is_expected.to have_many :rules } describe "validations" do before :each do diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rule_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rule_spec.rb new file mode 100644 index 00000000000..57d8bda464b --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rule_spec.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe SolidusFriendlyPromotions::Rule do + class BadTestRule < SolidusFriendlyPromotions::Rule; end + + class TestRule < SolidusFriendlyPromotions::Rule + def eligible?(_promotable, _options = {}) + true + end + end + + describe "preferences" do + subject { described_class.new.preferences } + + it { is_expected.to be_a(Hash) } + end + + it "forces developer to implement eligible? method" do + expect { BadTestRule.new.eligible?("promotable") }.to raise_error NotImplementedError + end + + it "validates unique rules for a promotion" do + promotion_one = TestRule.new + promotion_one.promotion_id = 1 + promotion_one.save + + promotion_two = TestRule.new + promotion_two.promotion_id = 1 + expect(promotion_two).not_to be_valid + end + + it "generates its own partial path" do + rule = TestRule.new + expect(rule.to_partial_path).to eq 'solidus_friendly_promotions/admin/promotion_rules/rules/test_rule' + end +end From 1a021772a2d6d5897dc3695b95fb247401b5537d Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 4 Jul 2023 11:35:04 +0200 Subject: [PATCH 258/834] Add join table between rules and Solidus models This is four join tables, for the product, store, taxon, and user rules. --- .../products_rule.rb | 8 +++++ .../rules/product.rb | 5 ++- .../rules/store.rb | 6 ++-- .../rules/taxon.rb | 5 ++- .../solidus_friendly_promotions/rules/user.rb | 6 ++-- .../rules_store.rb | 8 +++++ .../rules_taxon.rb | 8 +++++ .../solidus_friendly_promotions/rules_user.rb | 8 +++++ .../migrate/20230704083830_add_rule_tables.rb | 31 +++++++++++++++++++ .../products_rule_spec.rb | 8 +++++ .../rules/product_spec.rb | 4 ++- .../rules/store_spec.rb | 2 ++ .../rules/taxon_spec.rb | 2 ++ .../rules/user_spec.rb | 3 +- .../rules_store_spec.rb | 8 +++++ .../rules_taxon_spec.rb | 8 +++++ .../rules_user_spec.rb | 8 +++++ 17 files changed, 112 insertions(+), 16 deletions(-) create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/products_rule.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/rules_store.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/rules_taxon.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/rules_user.rb create mode 100644 friendly_promotions/db/migrate/20230704083830_add_rule_tables.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/products_rule_spec.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/rules_store_spec.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/rules_taxon_spec.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/rules_user_spec.rb diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/products_rule.rb b/friendly_promotions/app/models/solidus_friendly_promotions/products_rule.rb new file mode 100644 index 00000000000..d33f5970fa6 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/products_rule.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + class ProductsRule < Spree::Base + belongs_to :product, class_name: "Spree::Product" + belongs_to :rule + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb index 34847df0ba7..1c35ffd0ec4 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb @@ -7,9 +7,8 @@ module Rules # either come from assigned product group or are assingned directly to # the rule. class Product < Rule - has_many :product_promotion_rules, dependent: :destroy, foreign_key: :promotion_rule_id, - class_name: "Spree::ProductPromotionRule" - has_many :products, class_name: "Spree::Product", through: :product_promotion_rules + has_many :products_rules, inverse_of: :rule, dependent: :destroy + has_many :products, class_name: "Spree::Product", through: :products_rules def preload_relations [:products] diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb index baab2f1666d..4c3ed6446c4 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb @@ -3,10 +3,8 @@ module SolidusFriendlyPromotions module Rules class Store < Rule - has_many :promotion_rule_stores, class_name: "Spree::PromotionRuleStore", - foreign_key: :promotion_rule_id, - dependent: :destroy - has_many :stores, through: :promotion_rule_stores, class_name: "Spree::Store" + has_many :rules_stores, inverse_of: :rule, dependent: :destroy + has_many :stores, through: :rules_stores, class_name: "Spree::Store" def preload_relations [:stores] diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb index ea49627dfa0..811f7998295 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb @@ -3,9 +3,8 @@ module SolidusFriendlyPromotions module Rules class Taxon < Rule - has_many :promotion_rule_taxons, class_name: "Spree::PromotionRuleTaxon", foreign_key: :promotion_rule_id, - dependent: :destroy - has_many :taxons, through: :promotion_rule_taxons, class_name: "Spree::Taxon" + has_many :rules_taxons, inverse_of: :rule, dependent: :destroy + has_many :taxons, through: :rules_taxons, class_name: "Spree::Taxon" def preload_relations [:taxons] diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb index 2fcadd2e989..023b1b9e92c 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb @@ -3,10 +3,8 @@ module SolidusFriendlyPromotions module Rules class User < Rule - has_many :promotion_rule_users, class_name: "Spree::PromotionRuleUser", - foreign_key: :promotion_rule_id, - dependent: :destroy - has_many :users, through: :promotion_rule_users, class_name: Spree::UserClassHandle.new + has_many :rules_users, inverse_of: :rule, dependent: :destroy + has_many :users, through: :rules_users, class_name: Spree::UserClassHandle.new def preload_relations [:users] diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules_store.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules_store.rb new file mode 100644 index 00000000000..a4ab6dae755 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules_store.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + class RulesStore < Spree::Base + belongs_to :rule + belongs_to :store, class_name: "Spree::Store" + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules_taxon.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules_taxon.rb new file mode 100644 index 00000000000..f9858940773 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules_taxon.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + class RulesTaxon < Spree::Base + belongs_to :rule + belongs_to :taxon, class_name: "Spree::Taxon" + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules_user.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules_user.rb new file mode 100644 index 00000000000..3283ce6f8a4 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules_user.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + class RulesUser < Spree::Base + belongs_to :rule + belongs_to :user, class_name: Spree::UserClassHandle.new + end +end diff --git a/friendly_promotions/db/migrate/20230704083830_add_rule_tables.rb b/friendly_promotions/db/migrate/20230704083830_add_rule_tables.rb new file mode 100644 index 00000000000..89e49b35e37 --- /dev/null +++ b/friendly_promotions/db/migrate/20230704083830_add_rule_tables.rb @@ -0,0 +1,31 @@ +class AddRuleTables < ActiveRecord::Migration[7.0] + def change + create_table :solidus_friendly_promotions_products_rules, force: :cascade do |t| + t.references :product, index: true, null: false, foreign_key: { to_table: :spree_products } + t.references :rule, index: true, null: false, foreign_key: { to_table: :solidus_friendly_promotions_rules } + + t.timestamps + end + + create_table :solidus_friendly_promotions_rules_taxons, force: :cascade do |t| + t.references :taxon, index: true, null: false, foreign_key: { to_table: :spree_taxons } + t.references :rule, index: true, null: false, foreign_key: { to_table: :solidus_friendly_promotions_rules } + + t.timestamps + end + + create_table :solidus_friendly_promotions_rules_users, force: :cascade do |t| + t.references :user, index: true, null: false, foreign_key: { to_table: Spree.user_class.table_name } + t.references :rule, index: true, null: false, foreign_key: { to_table: :solidus_friendly_promotions_rules } + + t.timestamps + end + + create_table :solidus_friendly_promotions_rules_stores do |t| + t.references :store, index: true, null: false, foreign_key: { to_table: :spree_users } + t.references :rule, index: true, null: false + + t.timestamps + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/products_rule_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/products_rule_spec.rb new file mode 100644 index 00000000000..3d8f7a01044 --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/products_rule_spec.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SolidusFriendlyPromotions::ProductsRule do + it { is_expected.to belong_to(:product) } + it { is_expected.to belong_to(:rule) } +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/product_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/product_spec.rb index 192f20531df..19ec4525fd5 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/product_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/product_spec.rb @@ -3,7 +3,9 @@ require "spec_helper" RSpec.describe SolidusFriendlyPromotions::Rules::Product, type: :model do - let(:rule) { SolidusFriendlyPromotions::Rules::Product.new(rule_options) } + it { is_expected.to have_many(:products) } + + let(:rule) { described_class.new(rule_options) } let(:rule_options) { {} } context "#eligible?(order)" do diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/store_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/store_spec.rb index f7e3b558bc9..50120abb572 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/store_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/store_spec.rb @@ -3,6 +3,8 @@ require "spec_helper" RSpec.describe SolidusFriendlyPromotions::Rules::Store, type: :model do + it { is_expected.to have_many(:stores) } + let(:rule) { described_class.new } context "#eligible?(order)" do diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/taxon_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/taxon_spec.rb index 0e24cec66cd..40d692b5855 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/taxon_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/taxon_spec.rb @@ -3,6 +3,8 @@ require "spec_helper" RSpec.describe SolidusFriendlyPromotions::Rules::Taxon, type: :model do + it { is_expected.to have_many(:taxons) } + let(:taxon) { create :taxon, name: "first" } let(:taxon2) { create :taxon, name: "second" } let(:order) { create :order_with_line_items } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_spec.rb index 56bf4b4a97f..0b78257bb31 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_spec.rb @@ -3,7 +3,8 @@ require "spec_helper" RSpec.describe SolidusFriendlyPromotions::Rules::User, type: :model do - let(:rule) { SolidusFriendlyPromotions::Rules::User.new } + it { is_expected.to have_many(:users) } + let(:rule) { described_class.new } context "#eligible?(order)" do let(:order) { Spree::Order.new } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules_store_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules_store_spec.rb new file mode 100644 index 00000000000..c0b2635a37e --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules_store_spec.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SolidusFriendlyPromotions::RulesStore do + it { is_expected.to belong_to(:store) } + it { is_expected.to belong_to(:rule) } +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules_taxon_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules_taxon_spec.rb new file mode 100644 index 00000000000..cceff860d66 --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules_taxon_spec.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SolidusFriendlyPromotions::RulesTaxon do + it { is_expected.to belong_to(:taxon) } + it { is_expected.to belong_to(:rule) } +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules_user_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules_user_spec.rb new file mode 100644 index 00000000000..0fdb7b479ce --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules_user_spec.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SolidusFriendlyPromotions::RulesUser do + it { is_expected.to belong_to(:user) } + it { is_expected.to belong_to(:rule) } +end From f0a02203f47bc38b983335cad30e97cb04025792 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 4 Jul 2023 11:53:40 +0200 Subject: [PATCH 259/834] Add Action base class This combines the previous "Base" class from this gem and the "PromotionAction" base class from Solidus. --- .../solidus_friendly_promotions/action.rb | 59 +++++++++++++++++++ .../actions/adjust_line_item.rb | 2 +- .../actions/adjust_shipment.rb | 2 +- .../actions/base.rb | 51 ---------------- .../solidus_friendly_promotions/promotion.rb | 1 + .../migrate/20230704093625_create_actions.rb | 14 +++++ .../{actions/base_spec.rb => action_spec.rb} | 5 +- 7 files changed, 80 insertions(+), 54 deletions(-) create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/action.rb delete mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/actions/base.rb create mode 100644 friendly_promotions/db/migrate/20230704093625_create_actions.rb rename friendly_promotions/spec/models/solidus_friendly_promotions/{actions/base_spec.rb => action_spec.rb} (87%) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/action.rb b/friendly_promotions/app/models/solidus_friendly_promotions/action.rb new file mode 100644 index 00000000000..14195714c51 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/action.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true +require 'spree/preferences/persistable' + +module SolidusFriendlyPromotions + # Base class for all types of promotion action. + # + # PromotionActions perform the necessary tasks when a promotion is activated + # by an event and determined to be eligible. + class Action < Spree::Base + include Spree::Preferences::Persistable + include Spree::SoftDeletable + include Spree::CalculatedAdjustments + include Spree::AdjustmentSource + + belongs_to :promotion, inverse_of: :actions + has_many :adjustments, as: :source + + scope :of_type, ->(type) { where(type: Array.wrap(type).map(&:to_s)) } + + def preload_relations + [:calculator] + end + + def can_adjust?(object) + raise NotImplementedError + end + + def adjust(adjustable) + adjustment = adjustable.adjustments.detect do |adjustment| + adjustment.source == self + end || adjustable.adjustments.build(source: self, order: adjustable.order) + adjustment.label = adjustment_label(adjustable) + adjustment.amount = compute_amount(adjustable) + adjustment + end + + # Ensure a negative amount which does not exceed the object's amount + def compute_amount(adjustable) + promotion_amount = calculator.compute(adjustable) || BigDecimal(0) + [adjustable.amount, promotion_amount.abs].min * -1 + end + + def adjustment_label(adjustable) + I18n.t( + "spree.adjustment_labels.#{adjustable.class.name.demodulize.underscore}", + promotion: Spree::Promotion.model_name.human, + promotion_name: promotion.name, + ) + end + + def to_partial_path + "solidus_friendly_promotions/admin/promotion_actions/actions/#{model_name.element}" + end + + def available_calculators + raise NotImplementedError + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb index d345eb551b8..b106f84af22 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Actions - class AdjustLineItem < Base + class AdjustLineItem < Action def can_adjust?(object) object.is_a? Spree::LineItem end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb index 6d19427113e..a14c953c7d0 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Actions - class AdjustShipment < Base + class AdjustShipment < Action def can_adjust?(object) object.is_a? Spree::Shipment end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/actions/base.rb b/friendly_promotions/app/models/solidus_friendly_promotions/actions/base.rb deleted file mode 100644 index 949a061c58d..00000000000 --- a/friendly_promotions/app/models/solidus_friendly_promotions/actions/base.rb +++ /dev/null @@ -1,51 +0,0 @@ -# frozen_string_literal: true - -module SolidusFriendlyPromotions - module Actions - class Base < ::Spree::PromotionAction - include Spree::CalculatedAdjustments - include Spree::AdjustmentSource - - has_many :adjustments, as: :source - - def preload_relations - [:calculator] - end - - def can_adjust?(object) - raise NotImplementedError - end - - def adjust(adjustable) - adjustment = adjustable.adjustments.detect do |adjustment| - adjustment.source == self - end || adjustable.adjustments.build(source: self, order: adjustable.order) - adjustment.label = adjustment_label(adjustable) - adjustment.amount = compute_amount(adjustable) - adjustment - end - - # Ensure a negative amount which does not exceed the object's amount - def compute_amount(adjustable) - promotion_amount = calculator.compute(adjustable) || BigDecimal(0) - [adjustable.amount, promotion_amount.abs].min * -1 - end - - def adjustment_label(adjustable) - I18n.t( - "spree.adjustment_labels.#{adjustable.class.name.demodulize.underscore}", - promotion: Spree::Promotion.model_name.human, - promotion_name: promotion.name, - ) - end - - def to_partial_path - "solidus_friendly_promotions/admin/promotion_actions/actions/#{model_name.element}" - end - - def available_calculators - raise NotImplementedError - end - end - end -end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb index 28fa0c05c81..13c1d23c690 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb @@ -4,6 +4,7 @@ module SolidusFriendlyPromotions class Promotion < Spree::Base belongs_to :category, optional: true has_many :rules + has_many :actions validates :name, presence: true validates :path, uniqueness: { allow_blank: true, case_sensitive: true } diff --git a/friendly_promotions/db/migrate/20230704093625_create_actions.rb b/friendly_promotions/db/migrate/20230704093625_create_actions.rb new file mode 100644 index 00000000000..41e7cb21120 --- /dev/null +++ b/friendly_promotions/db/migrate/20230704093625_create_actions.rb @@ -0,0 +1,14 @@ +class CreateActions < ActiveRecord::Migration[7.0] + def change + create_table :solidus_friendly_promotions_actions do |t| + t.references :promotion, index: true, null: false, foreign_key: { to_table: :solidus_friendly_promotions_promotions } + t.string :type + t.datetime :deleted_at, precision: nil + t.text :preferences + t.index [:deleted_at], name: :index_solidus_friendly_promotions_actions_on_deleted_at + t.index [:id, :type], name: :index_solidus_friendly_promotions_actions_on_id_and_type + + t.timestamps + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/actions/base_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/action_spec.rb similarity index 87% rename from friendly_promotions/spec/models/solidus_friendly_promotions/actions/base_spec.rb rename to friendly_promotions/spec/models/solidus_friendly_promotions/action_spec.rb index 7e967b4aef9..63217e6b1a3 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/actions/base_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/action_spec.rb @@ -2,7 +2,10 @@ require "spec_helper" -RSpec.describe SolidusFriendlyPromotions::Actions::Base do +RSpec.describe SolidusFriendlyPromotions::Action do + it { is_expected.to belong_to(:promotion) } + it { is_expected.to have_one(:calculator) } + it { is_expected.to respond_to :adjust } it { is_expected.to respond_to :can_adjust? } From cbf9241100520f060e61f16eb61c4371e2b5e51f Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 4 Jul 2023 12:10:59 +0200 Subject: [PATCH 260/834] Add Friendly Promotion Factory --- .../testing_support/factories.rb | 2 + .../friendly_promotion_factory.rb | 100 ++++++++++++++++++ .../testing_support/factories_spec.rb | 18 ++++ 3 files changed, 120 insertions(+) create mode 100644 friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_factory.rb create mode 100644 friendly_promotions/spec/lib/solidus_friendly_promotions/testing_support/factories_spec.rb diff --git a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/factories.rb b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/factories.rb index 745a01e4c27..0b97b8fd914 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/factories.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/factories.rb @@ -1,4 +1,6 @@ # frozen_string_literal: true +require "solidus_friendly_promotions/testing_support/friendly_promotion_factory" + FactoryBot.define do end diff --git a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_factory.rb b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_factory.rb new file mode 100644 index 00000000000..b48f67d86ad --- /dev/null +++ b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_factory.rb @@ -0,0 +1,100 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :friendly_promotion, class: 'SolidusFriendlyPromotions::Promotion' do + name { 'Promo' } + + transient do + code { nil } + end + before(:create) do |promotion, evaluator| + if evaluator.code + promotion.codes << build(:friendly_promotion_code, promotion: promotion, value: evaluator.code) + end + end + + trait :with_action do + transient do + promotion_action_class { SolidusFriendlyPromotions::Actions::AdjustLineItem } + end + + after(:create) do |promotion, evaluator| + promotion.actions << evaluator.promotion_action_class.new + end + end + + trait :with_adjustable_action do + transient do + preferred_amount { 10 } + calculator_class { SolidusFriendlyPromotions::Calculators::FlatRate } + promotion_action_class { SolidusFriendlyPromotions::Actions::AdjustLineItem } + end + + after(:create) do |promotion, evaluator| + calculator = evaluator.calculator_class.new + calculator.preferred_amount = evaluator.preferred_amount + evaluator.promotion_action_class.create!(calculator: calculator, promotion: promotion) + end + end + + factory :friendly_promotion_with_action_adjustment, traits: [:with_adjustable_action] + + trait :with_line_item_adjustment do + transient do + adjustment_rate { 10 } + end + + with_adjustable_action + preferred_amount { adjustment_rate } + end + + factory :friendly_promotion_with_item_adjustment, traits: [:with_line_item_adjustment] + + trait :with_free_shipping do + after(:create) do |promotion| + calculator = SolidusFriendlyPromotions::Calculators::Percent.new(preferred_percent: 100) + + SolidusFriendlyPromotions::Actions::AdjustShipment.create!(promotion: promotion, calculator: calculator) + end + end + + trait :with_order_adjustment do + transient do + weighted_order_adjustment_amount { 10 } + end + + with_adjustable_action + preferred_amount { weighted_order_adjustment_amount } + calculator_class { SolidusFriendlyPromotions::Calculators::DistributedAmount } + end + + factory :friendly_promotion_with_order_adjustment, traits: [:with_order_adjustment] + + trait :with_item_total_rule do + transient do + item_total_threshold_amount { 10 } + end + + after(:create) do |promotion, evaluator| + rule = SolidusFriendlyPromotions::Rules::ItemTotal.create!( + promotion: promotion, + preferred_operator: 'gte', + preferred_amount: evaluator.item_total_threshold_amount + ) + promotion.rules << rule + promotion.save! + end + end + factory :friendly_promotion_with_item_total_rule, traits: [:with_item_total_rule] + trait :with_first_order_rule do + after(:create) do |promotion, _evaluator| + rule = SolidusFriendlyPromotions::Rules::FirstOrder.create!( + promotion: promotion, + ) + promotion.rules << rule + promotion.save! + end + end + factory :friendly_promotion_with_first_order_rule, traits: [:with_first_order_rule] + end +end diff --git a/friendly_promotions/spec/lib/solidus_friendly_promotions/testing_support/factories_spec.rb b/friendly_promotions/spec/lib/solidus_friendly_promotions/testing_support/factories_spec.rb new file mode 100644 index 00000000000..23ee0b5c8c8 --- /dev/null +++ b/friendly_promotions/spec/lib/solidus_friendly_promotions/testing_support/factories_spec.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe "Friendly Factories" do + it "has a bunch of working factories" do + [ + :friendly_promotion, + :friendly_promotion_with_action_adjustment, + :friendly_promotion_with_first_order_rule, + :friendly_promotion_with_item_adjustment, + :friendly_promotion_with_item_total_rule, + :friendly_promotion_with_order_adjustment + ].each do |factory| + expect { FactoryBot.create(factory) }.not_to raise_exception + end + end +end From eb2cc5ce7dd7b4b9b850bb6f74932d1fbf2a96cb Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 4 Jul 2023 12:16:55 +0200 Subject: [PATCH 261/834] Use friendly promotion in specs --- .../rules/line_item_taxon_spec.rb | 2 +- .../rules/one_use_per_user_spec.rb | 2 +- .../solidus_friendly_promotions/rules/product_spec.rb | 6 +++--- .../models/solidus_friendly_promotions/rules/taxon_spec.rb | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/line_item_taxon_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/line_item_taxon_spec.rb index c65fe5e792a..fa2fe051259 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/line_item_taxon_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/line_item_taxon_spec.rb @@ -9,7 +9,7 @@ let(:product) { order.products.first } let(:rule) do - described_class.create!(promotion: create(:promotion)) + described_class.create!(promotion: create(:friendly_promotion)) end describe "#eligible?" do diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/one_use_per_user_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/one_use_per_user_spec.rb index d95968fff81..d04bf2c16d5 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/one_use_per_user_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/one_use_per_user_spec.rb @@ -9,7 +9,7 @@ subject { rule.eligible?(order) } let(:order) { double Spree::Order, user: user } let(:user) { double Spree::LegacyUser } - let(:promotion) { stub_model Spree::Promotion, used_by?: used_by } + let(:promotion) { stub_model SolidusFriendlyPromotions::Promotion, used_by?: used_by } let(:used_by) { false } before { rule.promotion = promotion } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/product_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/product_spec.rb index 19ec4525fd5..aa2ab511209 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/product_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/product_spec.rb @@ -107,9 +107,9 @@ context "with an invalid match policy" do let(:rule) do SolidusFriendlyPromotions::Rules::Product.create!( - promotion: create(:promotion), - product_promotion_rules: [ - Spree::ProductPromotionRule.new(product: product) + promotion: create(:friendly_promotion), + products_rules: [ + SolidusFriendlyPromotions::ProductsRule.new(product: product) ] ).tap do |rule| rule.preferred_match_policy = "invalid" diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/taxon_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/taxon_spec.rb index 40d692b5855..c3108065d28 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/taxon_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/taxon_spec.rb @@ -11,7 +11,7 @@ let(:product) { order.products.first } let(:rule) do - SolidusFriendlyPromotions::Rules::Taxon.create!(promotion: create(:promotion)) + SolidusFriendlyPromotions::Rules::Taxon.create!(promotion: create(:friendly_promotion)) end context "#eligible?(order)" do From 236f105fe230672402e2fdbfd8077ca46c8f441f Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 4 Jul 2023 12:24:26 +0200 Subject: [PATCH 262/834] Use SFP Promotions in our admin This doesn't work yet because codes are missing. --- .../admin/promotions_controller.rb | 8 ++++++-- .../app/models/solidus_friendly_promotions/promotion.rb | 4 ++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb index 57af8f7bc7a..c5a5d43d02e 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb @@ -44,11 +44,15 @@ def collection end def promotion_includes - [:promotion_actions] + [:actions] + end + + def model_class + SolidusFriendlyPromotions::Promotion end def load_data - @promotion_categories = Spree::PromotionCategory.order(:name) + @promotion_categories = Category.order(:name) end def location_after_save diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb index 13c1d23c690..8c1d07a04ed 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb @@ -11,5 +11,9 @@ class Promotion < Spree::Base validates :usage_limit, numericality: { greater_than: 0, allow_nil: true } validates :per_code_usage_limit, numericality: { greater_than_or_equal_to: 0, allow_nil: true } validates :description, length: { maximum: 255 } + + self.allowed_ransackable_associations = ['codes'] + self.allowed_ransackable_attributes = %w[name path promotion_category_id] + self.allowed_ransackable_scopes = %i[active] end end From dd582842dcd729fc89a5ed05cbaad6ec28916ffa Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 4 Jul 2023 14:11:22 +0200 Subject: [PATCH 263/834] Add promotion code, code batches and related classes This chunky commit adds a lot of code from Solidus core. --- .../code_batch_job.rb | 26 ++ .../code_batch_mailer.rb | 15 ++ .../solidus_friendly_promotions/code.rb | 55 ++++ .../code/batch_builder.rb | 68 +++++ .../solidus_friendly_promotions/code_batch.rb | 27 ++ .../solidus_friendly_promotions/promotion.rb | 2 + .../code_batch_errored.text.erb | 2 + .../code_batch_finished.text.erb | 2 + friendly_promotions/config/locales/en.yml | 13 + .../db/migrate/20230704102444_create_codes.rb | 11 + .../20230704102656_create_code_batches.rb | 39 +++ .../configuration.rb | 1 + .../code_batch_job_spec.rb | 96 +++++++ .../code_batch_mailer_spec.rb | 48 ++++ .../code/batch_builder_spec.rb | 107 ++++++++ .../code_batch_spec.rb | 54 ++++ .../solidus_friendly_promotions/code_spec.rb | 250 ++++++++++++++++++ 17 files changed, 816 insertions(+) create mode 100644 friendly_promotions/app/jobs/solidus_friendly_promotions/code_batch_job.rb create mode 100644 friendly_promotions/app/mailers/solidus_friendly_promotions/code_batch_mailer.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/code.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/code/batch_builder.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/code_batch.rb create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/code_batch_mailer/code_batch_errored.text.erb create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/code_batch_mailer/code_batch_finished.text.erb create mode 100644 friendly_promotions/db/migrate/20230704102444_create_codes.rb create mode 100644 friendly_promotions/db/migrate/20230704102656_create_code_batches.rb create mode 100644 friendly_promotions/spec/jobs/solidus_friendly_promotions/code_batch_job_spec.rb create mode 100644 friendly_promotions/spec/mailers/solidus_friendly_promotions/code_batch_mailer_spec.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/code/batch_builder_spec.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/code_batch_spec.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/code_spec.rb diff --git a/friendly_promotions/app/jobs/solidus_friendly_promotions/code_batch_job.rb b/friendly_promotions/app/jobs/solidus_friendly_promotions/code_batch_job.rb new file mode 100644 index 00000000000..d30dbda0e25 --- /dev/null +++ b/friendly_promotions/app/jobs/solidus_friendly_promotions/code_batch_job.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + class CodeBatchJob < ActiveJob::Base + queue_as :default + + def perform(code_batch) + Code::BatchBuilder.new( + code_batch + ).build_promotion_codes + + if code_batch.email? + SolidusFriendlyPromotions.config.code_batch_mailer_class + .code_batch_finished(code_batch) + .deliver_now + end + rescue StandardError => error + if code_batch.email? + SolidusFriendlyPromotions.config.code_batch_mailer_class + .code_batch_errored(code_batch) + .deliver_now + end + raise error + end + end +end diff --git a/friendly_promotions/app/mailers/solidus_friendly_promotions/code_batch_mailer.rb b/friendly_promotions/app/mailers/solidus_friendly_promotions/code_batch_mailer.rb new file mode 100644 index 00000000000..11078ae985a --- /dev/null +++ b/friendly_promotions/app/mailers/solidus_friendly_promotions/code_batch_mailer.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + class CodeBatchMailer < Spree::BaseMailer + def code_batch_finished(code_batch) + @code_batch = code_batch + mail(to: code_batch.email) + end + + def code_batch_errored(code_batch) + @code_batch = code_batch + mail(to: code_batch.email) + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/code.rb b/friendly_promotions/app/models/solidus_friendly_promotions/code.rb new file mode 100644 index 00000000000..eec5fbe4a90 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/code.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + class Code < Spree::Base + belongs_to :promotion, inverse_of: :codes + belongs_to :code_batch, inverse_of: :codes, optional: true + has_many :adjustments + + before_validation :normalize_code + + validates :value, presence: true, uniqueness: { allow_blank: true, case_sensitive: true } + validate :promotion_not_apply_automatically, on: :create + + self.allowed_ransackable_attributes = ['value'] + + # Whether the promotion code has exceeded its usage restrictions + # + # @param excluded_orders [Array] Orders to exclude from usage limit + # @return true or false + def usage_limit_exceeded?(excluded_orders: []) + if usage_limit + usage_count(excluded_orders: excluded_orders) >= usage_limit + end + end + + # Number of times the code has been used overall + # + # @param excluded_orders [Array] Orders to exclude from usage count + # @return [Integer] usage count + def usage_count(excluded_orders: []) + promotion. + discounted_orders. + complete. + where.not(spree_orders: { state: :canceled }). + joins(:order_promotions). + where(spree_orders_promotions: { promotion_code_id: self.id }). + where.not(id: excluded_orders.map(&:id)). + count + end + + def usage_limit + promotion.per_code_usage_limit + end + + def promotion_not_apply_automatically + errors.add(:base, :disallowed_with_apply_automatically) if promotion.apply_automatically + end + + private + + def normalize_code + self.value = value.downcase.strip + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/code/batch_builder.rb b/friendly_promotions/app/models/solidus_friendly_promotions/code/batch_builder.rb new file mode 100644 index 00000000000..5ef85e50ce9 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/code/batch_builder.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + class Code < Spree::Base + class BatchBuilder + attr_reader :code_batch, :options + delegate :promotion, :number_of_codes, :base_code, to: :code_batch + + DEFAULT_OPTIONS = { + random_code_length: 6, + batch_size: 1000, + sample_characters: ('a'..'z').to_a + (2..9).to_a.map(&:to_s) + } + + def initialize(code_batch, options = {}) + @code_batch = code_batch + options.assert_valid_keys(*DEFAULT_OPTIONS.keys) + @options = DEFAULT_OPTIONS.merge(options) + end + + def build_promotion_codes + generate_random_codes + code_batch.update!(state: "completed") + rescue StandardError => error + code_batch.update!( + error: error.inspect, + state: "failed" + ) + raise error + end + + private + + def generate_random_codes + created_codes = 0 + batch_size = @options[:batch_size] + + while created_codes < number_of_codes + max_codes_to_generate = [batch_size, number_of_codes - created_codes].min + + new_codes = Array.new(max_codes_to_generate) { generate_random_code }.uniq + codes_for_current_batch = get_unique_codes(new_codes) + + codes_for_current_batch.each do |value| + Code.create!( + value: value, + promotion: promotion, + code_batch: code_batch + ) + end + created_codes += codes_for_current_batch.size + end + end + + def generate_random_code + suffix = Array.new(@options[:random_code_length]) do + @options[:sample_characters].sample + end.join + + "#{base_code}#{@code_batch.join_characters}#{suffix}" + end + + def get_unique_codes(code_set) + code_set - Code.where(value: code_set.to_a).pluck(:value) + end + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/code_batch.rb b/friendly_promotions/app/models/solidus_friendly_promotions/code_batch.rb new file mode 100644 index 00000000000..0d47e7ea970 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/code_batch.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + class CodeBatch < Spree::Base + class CantProcessStartedBatch < StandardError + end + + belongs_to :promotion + has_many :codes, dependent: :destroy + + validates :number_of_codes, numericality: { greater_than: 0 } + validates_presence_of :base_code, :number_of_codes + + def finished? + state == "completed" + end + + def process + if state == "pending" + update!(state: "processing") + CodeBatchJob.perform_later(self) + else + raise CantProcessStartedBatch.new("Batch #{id} already started") + end + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb index 8c1d07a04ed..f995272b5ae 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb @@ -5,6 +5,8 @@ class Promotion < Spree::Base belongs_to :category, optional: true has_many :rules has_many :actions + has_many :codes + has_many :code_batches validates :name, presence: true validates :path, uniqueness: { allow_blank: true, case_sensitive: true } diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/code_batch_mailer/code_batch_errored.text.erb b/friendly_promotions/app/views/solidus_friendly_promotions/code_batch_mailer/code_batch_errored.text.erb new file mode 100644 index 00000000000..21aea738112 --- /dev/null +++ b/friendly_promotions/app/views/solidus_friendly_promotions/code_batch_mailer/code_batch_errored.text.erb @@ -0,0 +1,2 @@ +<%= t(".message", error: @code_batch.error) %> +<%= @code_batch.promotion.name %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/code_batch_mailer/code_batch_finished.text.erb b/friendly_promotions/app/views/solidus_friendly_promotions/code_batch_mailer/code_batch_finished.text.erb new file mode 100644 index 00000000000..872138b0c1b --- /dev/null +++ b/friendly_promotions/app/views/solidus_friendly_promotions/code_batch_mailer/code_batch_finished.text.erb @@ -0,0 +1,2 @@ +<%= t(".message", number_of_codes: 10) %> +<%= @code_batch.promotion.name %> diff --git a/friendly_promotions/config/locales/en.yml b/friendly_promotions/config/locales/en.yml index 5d0b0fc91d8..62331987703 100644 --- a/friendly_promotions/config/locales/en.yml +++ b/friendly_promotions/config/locales/en.yml @@ -69,6 +69,13 @@ en: starts_at_placeholder: Immediately edit: order_rules: Order Rules + code_batch_mailer: + code_batch_errored: + message: 'Promotion code batch errored (%{error}) for promotion: ' + subject: Promotion code batch errored + code_batch_finished: + message: 'All %{number_of_codes} codes have been created for promotion: ' + subject: Promotion code batch finished activerecord: models: solidus_friendly_promotions/actions/adjust_shipment: Discount matching shipments @@ -136,3 +143,9 @@ en: description: Available only to logged in users solidus_friendly_promotions/rules/user_role: description: Order includes User with specified Role(s) + errors: + models: + solidus_friendly_promotions/code: + attributes: + base: + disallowed_with_apply_automatically: Could not create promotion code on promotion that apply automatically diff --git a/friendly_promotions/db/migrate/20230704102444_create_codes.rb b/friendly_promotions/db/migrate/20230704102444_create_codes.rb new file mode 100644 index 00000000000..022eed3aa3f --- /dev/null +++ b/friendly_promotions/db/migrate/20230704102444_create_codes.rb @@ -0,0 +1,11 @@ +class CreateCodes < ActiveRecord::Migration[7.0] + def change + create_table :solidus_friendly_promotions_codes, force: :cascade do |t| + t.references :promotion, null: false, index: true, foreign_key: { to_table: :solidus_friendly_promotions_promotions } + t.string :value, null: false + t.timestamps + + t.index ["value"], name: "index_solidus_friendly_promotions_codes_on_value", unique: true + end + end +end diff --git a/friendly_promotions/db/migrate/20230704102656_create_code_batches.rb b/friendly_promotions/db/migrate/20230704102656_create_code_batches.rb new file mode 100644 index 00000000000..f70db0ec4ff --- /dev/null +++ b/friendly_promotions/db/migrate/20230704102656_create_code_batches.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +class CreateCodeBatches < ActiveRecord::Migration[7.0] + def change + create_table :solidus_friendly_promotions_code_batches do |t| + t.references :promotion, null: false, index: true, foreign_key: { to_table: :solidus_friendly_promotions_promotions } + t.string :base_code, null: false + t.integer :number_of_codes, null: false + t.string :join_characters, null: false, default: "_" + t.string :email + t.string :error + t.string :state, default: "pending" + t.timestamps precision: 6 + end + + add_foreign_key( + :solidus_friendly_promotions_code_batches, + :solidus_friendly_promotions_promotions, + column: :promotion_id + ) + + add_column( + :solidus_friendly_promotions_codes, + :code_batch_id, + :integer + ) + + add_foreign_key( + :solidus_friendly_promotions_codes, + :solidus_friendly_promotions_code_batches, + column: :code_batch_id + ) + + add_index( + :solidus_friendly_promotions_codes, + :code_batch_id + ) + end +end diff --git a/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb b/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb index 825bca57453..32d5dcaac8c 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb @@ -16,6 +16,7 @@ class Configuration < Spree::Preferences::Configuration add_class_set :actions class_name_attribute :promotion_chooser_class, default: "SolidusFriendlyPromotions::PromotionAdjustmentChooser" + class_name_attribute :code_batch_mailer_class, default: "SolidusFriendlyPromotions::CodeBatchMailer" end class << self diff --git a/friendly_promotions/spec/jobs/solidus_friendly_promotions/code_batch_job_spec.rb b/friendly_promotions/spec/jobs/solidus_friendly_promotions/code_batch_job_spec.rb new file mode 100644 index 00000000000..bad0f6c2744 --- /dev/null +++ b/friendly_promotions/spec/jobs/solidus_friendly_promotions/code_batch_job_spec.rb @@ -0,0 +1,96 @@ +# frozen_string_literal: true + +require 'spec_helper' +RSpec.describe SolidusFriendlyPromotions::CodeBatchJob, type: :job do + let(:email) { "test@email.com" } + let(:code_batch) do + SolidusFriendlyPromotions::CodeBatch.create!( + promotion: create(:friendly_promotion), + base_code: "test", + number_of_codes: 10, + email: email + ) + end + context "with a successful build" do + before do + allow(SolidusFriendlyPromotions::CodeBatchMailer) + .to receive(:code_batch_finished) + .and_call_original + end + + def codes + SolidusFriendlyPromotions::Code.pluck(:value) + end + + context 'with the default join character' do + it 'uses the default join characters', :aggregate_failures do + subject.perform(code_batch) + codes.each do |code| + expect(code).to match(/^test_/) + end + end + end + context 'with a custom join character' do + let(:code_batch) do + SolidusFriendlyPromotions::CodeBatch.create!( + promotion: create(:friendly_promotion), + base_code: "test", + number_of_codes: 10, + email: email, + join_characters: '-' + ) + end + it 'uses the custom join characters', :aggregate_failures do + subject.perform(code_batch) + codes.each do |code| + expect(code).to match(/^test-/) + end + end + end + context "with an email address" do + it "sends an email" do + subject.perform(code_batch) + expect(SolidusFriendlyPromotions::CodeBatchMailer) + .to have_received(:code_batch_finished) + end + end + context "with no email address" do + let(:email) { nil } + it "sends an email" do + subject.perform(code_batch) + expect(SolidusFriendlyPromotions::CodeBatchMailer) + .to_not have_received(:code_batch_finished) + end + end + end + + context "with a failed build" do + before do + allow_any_instance_of(SolidusFriendlyPromotions::Code::BatchBuilder) + .to receive(:build_promotion_codes) + .and_raise("Error") + + allow(SolidusFriendlyPromotions::CodeBatchMailer) + .to receive(:code_batch_errored) + .and_call_original + + expect { subject.perform(code_batch) } + .to raise_error RuntimeError + end + + context "with an email address" do + it "sends an email" do + expect(SolidusFriendlyPromotions::CodeBatchMailer) + .to have_received(:code_batch_errored) + end + end + + context "with no email address" do + let(:email) { nil } + it "sends an email" do + expect(SolidusFriendlyPromotions::CodeBatchMailer) + .to_not have_received(:code_batch_errored) + end + end + end +end diff --git a/friendly_promotions/spec/mailers/solidus_friendly_promotions/code_batch_mailer_spec.rb b/friendly_promotions/spec/mailers/solidus_friendly_promotions/code_batch_mailer_spec.rb new file mode 100644 index 00000000000..129cd5a19e7 --- /dev/null +++ b/friendly_promotions/spec/mailers/solidus_friendly_promotions/code_batch_mailer_spec.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SolidusFriendlyPromotions::CodeBatchMailer, type: :mailer do + let(:promotion) { create(:friendly_promotion, name: "Promotion Test") } + let(:code_batch) do + SolidusFriendlyPromotions::CodeBatch.create!( + promotion_id: promotion.id, + base_code: "test", + number_of_codes: 10, + email: "test@email.com" + ) + end + + describe "#code_batch_finished" do + subject { described_class.code_batch_finished(code_batch) } + + it "sends the email to the email attached to the promotion code batch " do + expect(subject.to).to eq([code_batch.email]) + end + + it "contains the number of codes created" do + expect(subject.body).to include("All 10 codes have been created") + end + + it "contains the name of the promotion" do + expect(subject.body).to include(promotion.name) + end + end + + describe "#code_batch_errored" do + before { code_batch.update(error: "Test error") } + subject { described_class.code_batch_errored(code_batch) } + + it "sends the email to the email attached to the promotion code batch " do + expect(subject.to).to eq([code_batch.email]) + end + + it "contains the error" do + expect(subject.body).to include("Test error") + end + + it "contains the name of the promotion" do + expect(subject.body).to include(promotion.name) + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/code/batch_builder_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/code/batch_builder_spec.rb new file mode 100644 index 00000000000..4d0e3156a71 --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/code/batch_builder_spec.rb @@ -0,0 +1,107 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe SolidusFriendlyPromotions::Code::BatchBuilder do + let(:promotion) { create(:friendly_promotion) } + let(:base_code) { "abc" } + let(:options) { {} } + let(:number_of_codes) { 10 } + let(:code_batch) do + SolidusFriendlyPromotions::CodeBatch.create!( + promotion_id: promotion.id, + base_code: base_code, + number_of_codes: number_of_codes, + email: "test@email.com" + ) + end + + subject { described_class.new(code_batch, options) } + + describe "#build_promotion_codes" do + context "with a failed build" do + before do + allow(subject).to receive(:generate_random_codes).and_raise "Error" + + expect { subject.build_promotion_codes }.to raise_error RuntimeError + end + + it "updates the error and state on promotion batch" do + expect(code_batch.reload.error).to eq("#") + expect(code_batch.reload.state).to eq("failed") + end + end + + context "with a successful build" do + before do + allow(Spree::PromotionCodeBatchMailer) + .to receive(:code_batch_finished) + .and_call_original + + subject.build_promotion_codes + end + + it "update the promotion codes count for the batch" do + expect(code_batch.codes.count).to eq(10) + end + + it "builds the correct number of codes" do + expect(subject.promotion.codes.size).to eq(10) + end + + it "builds codes with distinct values" do + expect(subject.promotion.codes.map(&:value).uniq.size).to eq(10) + end + + it "updates the promotion code batch state to completed" do + expect(code_batch.state).to eq("completed") + end + end + + context "with likely code contention" do + let(:number_of_codes) { 50 } + let(:options) do + { + batch_size: 10, + sample_characters: (0..9).to_a.map(&:to_s), + random_code_length: 2 + } + end + + it "creates the correct number of codes" do + subject.build_promotion_codes + expect(promotion.codes.size).to eq(number_of_codes) + end + end + end + + describe "#join_character" do + context "with the default join charachter _" do + it "builds codes with the same base prefix" do + subject.build_promotion_codes + + values = subject.promotion.codes.map(&:value) + expect(values.all? { |val| val.starts_with?("#{base_code}_") }).to be true + end + end + + context "with a custom join separator" do + let(:code_batch) do + SolidusFriendlyPromotions::CodeBatch.create!( + promotion_id: promotion.id, + base_code: base_code, + number_of_codes: number_of_codes, + email: "test@email.com", + join_characters: "x" + ) + end + + it "builds codes with the same base prefix" do + subject.build_promotion_codes + + values = subject.promotion.codes.map(&:value) + expect(values.all? { |val| val.starts_with?("#{base_code}x") }).to be true + end + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/code_batch_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/code_batch_spec.rb new file mode 100644 index 00000000000..b4b23104249 --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/code_batch_spec.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe SolidusFriendlyPromotions::CodeBatch, type: :model do + subject do + described_class.create!( + promotion_id: create(:friendly_promotion).id, + base_code: "abc", + number_of_codes: 1, + error: nil, + email: "test@email.com" + ) + end + + describe "#process" do + context "with a pending code batch" do + it "should call the worker" do + expect { subject.process } + .to have_enqueued_job(SolidusFriendlyPromotions::CodeBatchJob) + end + + it "should update the state to processing" do + subject.process + + expect(subject.state).to eq("processing") + end + end + + context "with a processing batch" do + before { subject.update_attribute(:state, "processing") } + + it "should raise an error" do + expect{ subject.process }.to raise_error SolidusFriendlyPromotions::CodeBatch::CantProcessStartedBatch + end + end + + context "with a completed batch" do + before { subject.update_attribute(:state, "completed") } + + it "should raise an error" do + expect{ subject.process }.to raise_error SolidusFriendlyPromotions::CodeBatch::CantProcessStartedBatch + end + end + + context "with a failed batch" do + before { subject.update_attribute(:state, "failed") } + + it "should raise an error" do + expect{ subject.process }.to raise_error SolidusFriendlyPromotions::CodeBatch::CantProcessStartedBatch + end + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/code_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/code_spec.rb new file mode 100644 index 00000000000..f80cbbcf661 --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/code_spec.rb @@ -0,0 +1,250 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SolidusFriendlyPromotions::Code do + context 'callbacks' do + subject { promotion_code.save } + + describe '#normalize_code' do + let(:promotion) { create(:promotion, code: code) } + + before { subject } + + context 'when no other code with the same value exists' do + let(:promotion_code) { promotion.codes.first } + + context 'with mixed case' do + let(:code) { 'NewCoDe' } + + it 'downcases the value before saving' do + expect(promotion_code.value).to eq('newcode') + end + end + + context 'with extra spacing' do + let(:code) { ' new code ' } + + it 'removes surrounding whitespace' do + expect(promotion_code.value).to eq 'new code' + end + end + end + + context 'when another code with the same value exists' do + let(:promotion_code) { promotion.codes.build(value: code) } + + context 'with mixed case' do + let(:code) { 'NewCoDe' } + + it 'does not save the record and marks it as invalid' do + expect(promotion_code.valid?).to eq false + + expect(promotion_code.errors.messages[:value]).to contain_exactly( + 'has already been taken' + ) + end + end + + context 'with extra spacing' do + let(:code) { ' new code ' } + + it 'does not save the record and marks it as invalid' do + expect(promotion_code.valid?).to eq false + + expect(promotion_code.errors.messages[:value]).to contain_exactly( + 'has already been taken' + ) + end + end + end + end + end + + describe "#usage_limit_exceeded?" do + subject { code.usage_limit_exceeded? } + + shared_examples "it should" do + context "when there is a usage limit" do + context "and the limit is not exceeded" do + let(:usage_limit) { 10 } + it { is_expected.to be_falsy } + end + context "and the limit is exceeded" do + let(:usage_limit) { 1 } + context "on a different order" do + before do + FactoryBot.create( + :completed_order_with_promotion, + promotion: promotion + ) + code.adjustments.update_all(eligible: true) + end + it { is_expected.to be_truthy } + end + context "on the same order" do + it { is_expected.to be_falsy } + end + end + end + context "when there is no usage limit" do + let(:usage_limit) { nil } + it { is_expected.to be_falsy } + end + end + + let(:code) { promotion.codes.first } + + context "with an order-level adjustment" do + let(:promotion) do + FactoryBot.create( + :promotion, + :with_order_adjustment, + code: "discount", + per_code_usage_limit: usage_limit + ) + end + let(:promotable) do + FactoryBot.create( + :completed_order_with_promotion, + promotion: promotion + ) + end + it_behaves_like "it should" + end + + context "with an item-level adjustment" do + let(:promotion) do + FactoryBot.create( + :promotion, + :with_line_item_adjustment, + code: "discount", + per_code_usage_limit: usage_limit + ) + end + before do + promotion.actions.first.perform({ + order: order, + promotion: promotion, + promotion_code: code + }) + end + context "when there are multiple line items" do + let(:order) { FactoryBot.create(:order_with_line_items, line_items_count: 2) } + describe "the first item" do + let(:promotable) { order.line_items.first } + it_behaves_like "it should" + end + describe "the second item" do + let(:promotable) { order.line_items.last } + it_behaves_like "it should" + end + end + context "when there is a single line item" do + let(:order) { FactoryBot.create(:order_with_line_items) } + let(:promotable) { order.line_items.first } + it_behaves_like "it should" + end + end + end + + describe "#usage_count" do + let(:promotion) do + FactoryBot.create( + :promotion, + :with_order_adjustment, + code: "discount" + ) + end + let(:code) { promotion.codes.first } + + subject { code.usage_count } + + context "when the code is applied to a non-complete order" do + let(:order) { FactoryBot.create(:order_with_line_items) } + before { promotion.activate(order: order, promotion_code: code) } + it { is_expected.to eq 0 } + end + context "when the code is applied to a complete order" do + let!(:order) do + FactoryBot.create( + :completed_order_with_promotion, + promotion: promotion + ) + end + context "and the promo is eligible" do + it { is_expected.to eq 1 } + end + context "and the promo is ineligible" do + before { order.adjustments.promotion.update_all(eligible: false) } + it { is_expected.to eq 0 } + end + context "and the order is canceled" do + before { order.cancel! } + it { is_expected.to eq 0 } + it { expect(order.state).to eq 'canceled' } + end + end + end + + describe "completing multiple orders with the same code", slow: true do + let(:promotion) do + FactoryBot.create( + :promotion, + :with_order_adjustment, + code: "discount", + per_code_usage_limit: 1, + weighted_order_adjustment_amount: 10 + ) + end + let(:code) { promotion.codes.first } + let(:order) do + FactoryBot.create(:order_with_line_items, line_items_price: 40, shipment_cost: 0).tap do |order| + FactoryBot.create(:payment, amount: 30, order: order) + promotion.activate(order: order, promotion_code: code) + end + end + let(:promo_adjustment) { order.adjustments.promotion.first } + before do + order.next! until order.can_complete? + + FactoryBot.create(:order_with_line_items, line_items_price: 40, shipment_cost: 0).tap do |order| + FactoryBot.create(:payment, amount: 30, order: order) + promotion.activate(order: order, promotion_code: code) + order.next! until order.can_complete? + order.complete! + end + end + + it "makes the promotion ineligible" do + expect{ + order.complete + }.to change{ promo_adjustment.reload.eligible }.to(false) + end + + it "adjusts the promo_total" do + expect{ + order.complete + }.to change(order, :promo_total).by(10) + end + + it "increases the total to remove the promo" do + expect{ + order.complete + }.to change(order, :total).from(30).to(40) + end + + it "resets the state of the order" do + expect{ + order.complete + }.to change{ order.reload.state }.from("confirm").to("address") + end + end + + it "cannot create promotion code on apply automatically promotion" do + promotion = create(:promotion, apply_automatically: true) + expect { + create(:promotion_code, promotion: promotion) + }.to raise_error ActiveRecord::RecordInvalid, "Validation failed: Could not create promotion code on promotion that apply automatically" + end +end From a704fc124b27e828bc1a4fb9f0ee1f873a624b16 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 5 Jul 2023 10:31:19 +0200 Subject: [PATCH 264/834] Rename Category to PromotionCategory Things turn really funky in Rails routing otherwise. --- .../admin/promotion_categories_controller.rb | 9 +++++++ .../admin/promotions_controller.rb | 2 +- .../solidus_friendly_promotions/promotion.rb | 2 +- .../{category.rb => promotion_category.rb} | 2 +- .../admin/promotion_categories/index.html.erb | 26 +++++++++---------- .../admin/promotions/_form.html.erb | 4 +-- friendly_promotions/config/routes.rb | 3 ++- ...30703141116_create_promotion_categories.rb | 8 ++++-- ...ory_spec.rb => promotion_category_spec.rb} | 4 +-- 9 files changed, 37 insertions(+), 23 deletions(-) rename friendly_promotions/app/models/solidus_friendly_promotions/{category.rb => promotion_category.rb} (77%) rename friendly_promotions/spec/models/solidus_friendly_promotions/{category_spec.rb => promotion_category_spec.rb} (73%) diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_categories_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_categories_controller.rb index 02050100ba8..a9861ca3693 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_categories_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_categories_controller.rb @@ -3,6 +3,15 @@ module SolidusFriendlyPromotions module Admin class PromotionCategoriesController < Spree::Admin::ResourceController + private + + def model_class + SolidusFriendlyPromotions::PromotionCategory + end + + def routes_proxy + solidus_friendly_promotions + end end end end diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb index c5a5d43d02e..d42806b4f1d 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb @@ -52,7 +52,7 @@ def model_class end def load_data - @promotion_categories = Category.order(:name) + @promotion_categories = PromotionCategory.order(:name) end def location_after_save diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb index f995272b5ae..7b152b56c74 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions class Promotion < Spree::Base - belongs_to :category, optional: true + belongs_to :category, class_name: "SolidusFriendlyPromotions::PromotionCategory", foreign_key: :promotion_category_id, optional: true has_many :rules has_many :actions has_many :codes diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/category.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_category.rb similarity index 77% rename from friendly_promotions/app/models/solidus_friendly_promotions/category.rb rename to friendly_promotions/app/models/solidus_friendly_promotions/promotion_category.rb index b800d746abb..b7130ec00bc 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/category.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_category.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module SolidusFriendlyPromotions - class Category < Spree::Base + class PromotionCategory < Spree::Base has_many :promotions validates :name, presence: true diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_categories/index.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_categories/index.html.erb index f9c93f64a1a..ad0196414e8 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_categories/index.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_categories/index.html.erb @@ -1,8 +1,8 @@ -<% admin_breadcrumb(link_to plural_resource_name(Spree::Promotion), solidus_friendly_promotions.admin_promotions_path) %> -<% admin_breadcrumb(plural_resource_name(Spree::PromotionCategory)) %> +<% admin_breadcrumb(link_to plural_resource_name(SolidusFriendlyPromotions::Promotion), solidus_friendly_promotions.admin_promotions_path) %> +<% admin_breadcrumb(plural_resource_name(SolidusFriendlyPromotions::PromotionCategory)) %> <% content_for :page_actions do %> - <% if can?(:create, Spree::PromotionCategory) %> + <% if can?(:create, SolidusFriendlyPromotions::PromotionCategory) %>
  • <%= link_to t('solidus_friendly_promotions.new_promotion_category'), solidus_friendly_promotions.new_admin_promotion_category_path, class: 'btn btn-primary' %>
  • @@ -17,21 +17,21 @@ - <%= Spree::PromotionCategory.human_attribute_name :name %> - <%= Spree::PromotionCategory.human_attribute_name :code %> + <%= SolidusFriendlyPromotions::PromotionCategory.human_attribute_name :name %> + <%= SolidusFriendlyPromotions::PromotionCategory.human_attribute_name :code %> - <% @promotion_categories.each do |promotion_category| %> + <% @promotion_categories.each do |category| %> - <%= promotion_category.name %> - <%= promotion_category.code %> + <%= category.name %> + <%= category.code %> - <% if can?(:update, promotion_category) %> - <%= link_to_edit promotion_category, no_text: true %> + <% if can?(:update, category) %> + <%= link_to_edit category, no_text: true %> <% end %> - <% if can?(:destroy, promotion_category) %> - <%= link_to_delete promotion_category, no_text: true %> + <% if can?(:destroy, category) %> + <%= link_to_delete category, no_text: true %> <% end %> @@ -41,7 +41,7 @@ <% else %>
    <%= render 'spree/admin/shared/no_objects_found', - resource: Spree::PromotionCategory, + resource: SolidusFriendlyPromotions::PromotionCategory, new_resource_url: new_object_url %>
    <% end %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_form.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_form.html.erb index 405f88c6d01..5f89e10c3ca 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_form.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_form.html.erb @@ -18,9 +18,9 @@ <% end %> <%= f.field_container :category do %> - <%= f.label :promotion_category_id, Spree::PromotionCategory.model_name.human %>
    + <%= f.label :category_id, Spree::PromotionCategory.model_name.human %>
    <%= - f.collection_select(:promotion_category_id, @promotion_categories, :id, :name, { include_blank: t('spree.match_choices.none') }, + f.collection_select(:category_id, @promotion_categories, :id, :name, { include_blank: t('spree.match_choices.none') }, { class: 'custom-select fullwidth' }) %> <% end %> diff --git a/friendly_promotions/config/routes.rb b/friendly_promotions/config/routes.rb index 3ad87c0cbe9..1dd211b9c62 100644 --- a/friendly_promotions/config/routes.rb +++ b/friendly_promotions/config/routes.rb @@ -2,6 +2,8 @@ SolidusFriendlyPromotions::Engine.routes.draw do namespace :admin do + resources :promotion_categories, except: [:show] + resources :promotions do resources :promotion_rules resources :promotion_actions @@ -10,6 +12,5 @@ get '/download', to: "promotion_code_batches#download", defaults: { format: "csv" } end end - resources :promotion_categories, except: [:show] end end diff --git a/friendly_promotions/db/migrate/20230703141116_create_promotion_categories.rb b/friendly_promotions/db/migrate/20230703141116_create_promotion_categories.rb index e0a84bfc5c1..f31f9a2ed90 100644 --- a/friendly_promotions/db/migrate/20230703141116_create_promotion_categories.rb +++ b/friendly_promotions/db/migrate/20230703141116_create_promotion_categories.rb @@ -1,11 +1,15 @@ class CreatePromotionCategories < ActiveRecord::Migration[7.0] def change - create_table :solidus_friendly_promotions_categories do |t| + create_table :solidus_friendly_promotions_promotion_categories do |t| t.string :name + t.string :code t.timestamps end - add_reference :solidus_friendly_promotions_promotions, :category, foreign_key: { to_table: :solidus_friendly_promotions_categories } + add_reference :solidus_friendly_promotions_promotions, + :promotion_category, + foreign_key: { to_table: :solidus_friendly_promotions_promotion_categories }, + index: { name: :index_solidus_friendly_promotions_promotions_categories } end end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/category_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_category_spec.rb similarity index 73% rename from friendly_promotions/spec/models/solidus_friendly_promotions/category_spec.rb rename to friendly_promotions/spec/models/solidus_friendly_promotions/promotion_category_spec.rb index 9fda2e04a9d..25d2a5bfbb1 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/category_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_category_spec.rb @@ -2,12 +2,12 @@ require 'spec_helper' -RSpec.describe SolidusFriendlyPromotions::Category, type: :model do +RSpec.describe SolidusFriendlyPromotions::PromotionCategory, type: :model do it { is_expected.to have_many :promotions } describe 'validation' do let(:name) { 'Nom' } - subject { SolidusFriendlyPromotions::Category.new name: name } + subject { described_class.new name: name } context 'when all required attributes are specified' do it { is_expected.to be_valid } From 9819932c3fa758b0d8e42428924edfc53b95438f Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 5 Jul 2023 10:32:03 +0200 Subject: [PATCH 265/834] Make promotions index page work --- .../admin/promotions_controller.rb | 8 +++--- .../solidus_friendly_promotions/promotion.rb | 26 +++++++++++++++++++ 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb index d42806b4f1d..b6584c09bcf 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb @@ -9,15 +9,15 @@ class PromotionsController < ::Spree::Admin::ResourceController helper 'solidus_friendly_promotions/admin/promotion_actions' def create - @promotion = Spree::Promotion.new(permitted_resource_params) + @promotion = model_class.new(permitted_resource_params) @promotion.codes.new(value: params[:single_code]) if params[:single_code].present? - if params[:promotion_code_batch] - @promotion_code_batch = @promotion.promotion_code_batches.new(promotion_code_batch_params) + if params[:code_batch] + @code_batch = @promotion.code_batches.new(code_batch_params) end if @promotion.save - @promotion_code_batch.process if @promotion_code_batch + @code_batch.process if @code_batch flash[:success] = t('solidus_friendly_promotions.promotion_successfully_created') redirect_to location_after_save else diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb index 7b152b56c74..c3a3b9da2b0 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb @@ -17,5 +17,31 @@ class Promotion < Spree::Base self.allowed_ransackable_associations = ['codes'] self.allowed_ransackable_attributes = %w[name path promotion_category_id] self.allowed_ransackable_scopes = %i[active] + + + # All orders that have been discounted using this promotion + def discounted_orders + Spree::Order. + joins(:all_adjustments). + where( + spree_adjustments: { + source_type: "SolidusFriendlyPromotions::Action", + source_id: actions.map(&:id), + eligible: true + } + ).distinct + end + + # Number of times the code has been used overall + # + # @param excluded_orders [Array] Orders to exclude from usage count + # @return [Integer] usage count + def usage_count(excluded_orders: []) + discounted_orders. + complete. + where.not(id: [excluded_orders.map(&:id)]). + where.not(spree_orders: { state: :canceled }). + count + end end end From 73e504c7e16240879665c001a504e707531fd24f Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 5 Jul 2023 10:32:29 +0200 Subject: [PATCH 266/834] Use friendly promotion factories in specs --- .../testing_support/factories.rb | 2 ++ .../friendly_promotion_category_factory.rb | 7 +++++++ .../friendly_promotion_code_factory.rb | 8 ++++++++ .../calculators/distributed_amount_spec.rb | 2 +- .../solidus_friendly_promotions/code_spec.rb | 14 +++++++------- .../order_promotion_adjuster_spec.rb | 4 ++-- .../simple_order_contents_spec.rb | 4 ++-- .../admin/promotion_actions_request_spec.rb | 2 +- .../admin/promotion_rules_request_spec.rb | 7 ++++--- .../admin/promotion_categories_spec.rb | 8 ++++---- .../admin/promotions_spec.rb | 10 +++++----- 11 files changed, 43 insertions(+), 25 deletions(-) create mode 100644 friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_category_factory.rb create mode 100644 friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_code_factory.rb diff --git a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/factories.rb b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/factories.rb index 0b97b8fd914..9ffd7e6ee9c 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/factories.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/factories.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require "solidus_friendly_promotions/testing_support/friendly_promotion_code_factory" +require "solidus_friendly_promotions/testing_support/friendly_promotion_category_factory" require "solidus_friendly_promotions/testing_support/friendly_promotion_factory" FactoryBot.define do diff --git a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_category_factory.rb b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_category_factory.rb new file mode 100644 index 00000000000..07155b7b7fc --- /dev/null +++ b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_category_factory.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :friendly_promotion_category, class: 'SolidusFriendlyPromotions::PromotionCategory' do + name { 'Promotion Category' } + end +end diff --git a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_code_factory.rb b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_code_factory.rb new file mode 100644 index 00000000000..267b8139d95 --- /dev/null +++ b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_code_factory.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :friendly_promotion_code, class: 'SolidusFriendlyPromotions::Code' do + promotion factory: :friendly_promotion + sequence(:value) { |i| "code#{i}" } + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/distributed_amount_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/distributed_amount_spec.rb index 3d1eeed0dbd..e43050cf6d9 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/distributed_amount_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/distributed_amount_spec.rb @@ -5,7 +5,7 @@ RSpec.describe SolidusFriendlyPromotions::Calculators::DistributedAmount, type: :model do let(:calculator) { described_class.new(preferred_amount: 15, preferred_currency: currency) } - let!(:promotion) { create :promotion, apply_automatically: true, name: '15 spread', promotion_actions: [action], promotion_rules: rules } + let!(:promotion) { create :friendly_promotion, apply_automatically: true, name: '15 spread', actions: [action], rules: rules } let(:rules) { [] } let(:action) { SolidusFriendlyPromotions::Actions::AdjustLineItem.create(calculator: calculator) } let(:order) { create(:order_with_line_items, line_items_attributes: line_items_attributes) } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/code_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/code_spec.rb index f80cbbcf661..7ed0a8cacd2 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/code_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/code_spec.rb @@ -7,7 +7,7 @@ subject { promotion_code.save } describe '#normalize_code' do - let(:promotion) { create(:promotion, code: code) } + let(:promotion) { create(:friendly_promotion, code: code) } before { subject } @@ -98,7 +98,7 @@ context "with an order-level adjustment" do let(:promotion) do FactoryBot.create( - :promotion, + :friendly_promotion, :with_order_adjustment, code: "discount", per_code_usage_limit: usage_limit @@ -116,7 +116,7 @@ context "with an item-level adjustment" do let(:promotion) do FactoryBot.create( - :promotion, + :friendly_promotion, :with_line_item_adjustment, code: "discount", per_code_usage_limit: usage_limit @@ -151,7 +151,7 @@ describe "#usage_count" do let(:promotion) do FactoryBot.create( - :promotion, + :friendly_promotion, :with_order_adjustment, code: "discount" ) @@ -190,7 +190,7 @@ describe "completing multiple orders with the same code", slow: true do let(:promotion) do FactoryBot.create( - :promotion, + :friendly_promotion, :with_order_adjustment, code: "discount", per_code_usage_limit: 1, @@ -242,9 +242,9 @@ end it "cannot create promotion code on apply automatically promotion" do - promotion = create(:promotion, apply_automatically: true) + promotion = create(:friendly_promotion, apply_automatically: true) expect { - create(:promotion_code, promotion: promotion) + create(:friendly_promotion_code, promotion: promotion) }.to raise_error ActiveRecord::RecordInvalid, "Validation failed: Could not create promotion code on promotion that apply automatically" end end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/order_promotion_adjuster_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/order_promotion_adjuster_spec.rb index 75ac9adfa99..3b6b948f4f4 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/order_promotion_adjuster_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/order_promotion_adjuster_spec.rb @@ -5,7 +5,7 @@ RSpec.describe SolidusFriendlyPromotions::OrderPromotionAdjuster, type: :model do let(:line_item) { create(:line_item) } let(:order) { line_item.order } - let(:promotion) { create(:promotion, apply_automatically: true) } + let(:promotion) { create(:friendly_promotion, apply_automatically: true) } let(:calculator) { Spree::Calculator::FlatPercentItemTotal.new(preferred_flat_percent: 10) } subject { described_class.new(order) } @@ -24,7 +24,7 @@ end context "for a non-sale promotion" do - let(:promotion) { create(:promotion, apply_automatically: false) } + let(:promotion) { create(:friendly_promotion, apply_automatically: false) } it "doesn't connect the promotion to the order" do expect { diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/simple_order_contents_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/simple_order_contents_spec.rb index a101a7a9c66..c6e68a392bd 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/simple_order_contents_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/simple_order_contents_spec.rb @@ -83,8 +83,8 @@ end context "running promotions" do - let(:promotion) { create(:promotion, apply_automatically: true) } - let(:calculator) { Spree::Calculator::FlatRate.new(preferred_amount: 10) } + let(:promotion) { create(:friendly_promotion, apply_automatically: true) } + let(:calculator) { SolidusFriendlyPromotions::Calculators::FlatRate.new(preferred_amount: 10) } before do Spree::Config.promotion_adjuster_class = "SolidusFriendlyPromotions::OrderPromotionAdjuster" diff --git a/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_actions_request_spec.rb b/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_actions_request_spec.rb index 63fd5325357..bec5489a4e9 100644 --- a/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_actions_request_spec.rb +++ b/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_actions_request_spec.rb @@ -5,7 +5,7 @@ describe SolidusFriendlyPromotions::Admin::PromotionActionsController, type: :request do stub_authorization! - let!(:promotion) { create(:promotion) } + let!(:promotion) { create(:friendly_promotion) } it "can create a promotion action of a valid type" do post solidus_friendly_promotions.admin_promotion_promotion_actions_path(promotion_id: promotion.id), params: { diff --git a/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_rules_request_spec.rb b/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_rules_request_spec.rb index ac71afa53c6..d4067f55ace 100644 --- a/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_rules_request_spec.rb +++ b/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_rules_request_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe SolidusFriendlyPromotions::Admin::PromotionRulesController, type: :request do - let!(:promotion) { create(:promotion) } + let!(:promotion) { create(:friendly_promotion) } context "when the user is authorized" do stub_authorization! do |_u| @@ -29,8 +29,9 @@ context "when the user is not authorized" do it "redirects the user to login" do - post solidus_friendly_promotions.admin_promotion_promotion_rules_path(promotion_id: promotion.id, promotion_rule: { type: "Spree::Promotion::Rules::Product" }) - + post solidus_friendly_promotions.admin_promotion_promotion_rules_path(promotion_id: promotion.id), params: { + promotion_rule: { type: "SolidusFriendlyPromotions::Rules::Product" } + } expect(response).to redirect_to('/admin/login') end end diff --git a/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotion_categories_spec.rb b/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotion_categories_spec.rb index 96a72be5c50..34c534ae262 100644 --- a/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotion_categories_spec.rb +++ b/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotion_categories_spec.rb @@ -7,8 +7,8 @@ context "index" do before do - create(:promotion_category, name: 'name1', code: 'code1') - create(:promotion_category, name: 'name2', code: 'code2') + create(:friendly_promotion_category, name: 'name1', code: 'code1') + create(:friendly_promotion_category, name: 'name2', code: 'code2') visit solidus_friendly_promotions.admin_promotion_categories_path end @@ -50,7 +50,7 @@ context "edit" do before(:each) do - create(:promotion_category, name: 'name1') + create(:friendly_promotion_category, name: 'name1') visit solidus_friendly_promotions.admin_promotion_categories_path within_row(1) { click_icon :edit } end @@ -71,7 +71,7 @@ context "delete" do before(:each) do - create(:promotion_category, name: 'name1') + create(:friendly_promotion_category, name: 'name1') visit solidus_friendly_promotions.admin_promotion_categories_path end diff --git a/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotions_spec.rb b/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotions_spec.rb index 909c02e1e55..d73fc22bd13 100644 --- a/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotions_spec.rb +++ b/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotions_spec.rb @@ -6,12 +6,12 @@ stub_authorization! describe "#index" do - let!(:promotion1) { create(:promotion, :with_action, name: "name1", code: "code1", path: "path1") } - let!(:promotion2) { create(:promotion, :with_action, name: "name2", code: "code2", path: "path2") } + let!(:promotion1) { create(:friendly_promotion, :with_action, name: "name1", code: "code1", path: "path1") } + let!(:promotion2) { create(:friendly_promotion, :with_action, name: "name2", code: "code2", path: "path2") } let!(:promotion3) do - create(:promotion, :with_action, name: "name3", code: "code3", path: "path3", expires_at: Date.yesterday) + create(:friendly_promotion, :with_action, name: "name3", code: "code3", path: "path3", expires_at: Date.yesterday) end - let!(:category) { create :promotion_category } + let!(:category) { create :friendly_promotion_category } it "succeeds" do visit solidus_friendly_promotions.admin_promotions_path @@ -22,7 +22,7 @@ it "shows promotion categories" do visit solidus_friendly_promotions.admin_promotions_path - expect(page).to have_select(Spree::PromotionCategory.model_name.human, options: ["All", category.name]) + expect(page).to have_select(SolidusFriendlyPromotions::PromotionCategory.model_name.human, options: ["All", category.name]) end context "search" do From 21395b1f22a737c38e1b37d92c748e925c4048ff Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 5 Jul 2023 14:35:38 +0200 Subject: [PATCH 267/834] Fix Order Promotion Adjuster to work with new models --- .../order_promotion_adjuster.rb | 10 ++++---- .../solidus_friendly_promotions/promotion.rb | 23 ++++++++++++++++++- .../spec/models/promotion/integration_spec.rb | 2 +- 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/order_promotion_adjuster.rb b/friendly_promotions/app/models/solidus_friendly_promotions/order_promotion_adjuster.rb index c9277510810..c58da2c46e9 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/order_promotion_adjuster.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/order_promotion_adjuster.rb @@ -30,10 +30,10 @@ def adjust_shipments def possible_promotions promos = connected_order_promotions | sale_promotions - promos.flat_map(&:promotion_actions).group_by(&:preload_relations).each do |preload_relations, actions| + promos.flat_map(&:actions).group_by(&:preload_relations).each do |preload_relations, actions| preload(records: actions, associations: preload_relations) end - promos.flat_map(&:promotion_rules).group_by(&:preload_relations).each do |preload_relations, rules| + promos.flat_map(&:rules).group_by(&:preload_relations).each do |preload_relations, rules| preload(records: rules, associations: preload_relations) end promos.reject { |promotion| promotion.usage_limit_exceeded?(excluded_orders: [order]) } @@ -48,13 +48,13 @@ def connected_order_promotions end def sale_promotions - Spree::Promotion.where(apply_automatically: true).active.includes(promotion_includes) + SolidusFriendlyPromotions::Promotion.where(apply_automatically: true).active.includes(promotion_includes) end def promotion_includes [ - :promotion_rules, - :promotion_actions, + :rules, + :actions, ] end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb index c3a3b9da2b0..4f609db64a2 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb @@ -14,11 +14,22 @@ class Promotion < Spree::Base validates :per_code_usage_limit, numericality: { greater_than_or_equal_to: 0, allow_nil: true } validates :description, length: { maximum: 255 } + scope :active, -> { has_actions.started_and_unexpired } + scope :started_and_unexpired, -> do + table = arel_table + time = Time.current + + where(table[:starts_at].eq(nil).or(table[:starts_at].lt(time))). + where(table[:expires_at].eq(nil).or(table[:expires_at].gt(time))) + end + scope :has_actions, -> do + joins(:actions).distinct + end + self.allowed_ransackable_associations = ['codes'] self.allowed_ransackable_attributes = %w[name path promotion_category_id] self.allowed_ransackable_scopes = %i[active] - # All orders that have been discounted using this promotion def discounted_orders Spree::Order. @@ -43,5 +54,15 @@ def usage_count(excluded_orders: []) where.not(spree_orders: { state: :canceled }). count end + + # Whether the promotion has exceeded its usage restrictions. + # + # @param excluded_orders [Array] Orders to exclude from usage limit + # @return true or false + def usage_limit_exceeded?(excluded_orders: []) + if usage_limit + usage_count(excluded_orders: excluded_orders) >= usage_limit + end + end end end diff --git a/friendly_promotions/spec/models/promotion/integration_spec.rb b/friendly_promotions/spec/models/promotion/integration_spec.rb index d6d8dc1ac5a..d03c7c8b42e 100644 --- a/friendly_promotions/spec/models/promotion/integration_spec.rb +++ b/friendly_promotions/spec/models/promotion/integration_spec.rb @@ -6,7 +6,7 @@ context "A promotion that creates line item adjustments" do let(:shirt) { create(:product, name: "Shirt") } let(:pants) { create(:product, name: "Pants") } - let(:promotion) { create(:promotion, name: "20% off Shirts", apply_automatically: true) } + let(:promotion) { create(:friendly_promotion, name: "20% off Shirts", apply_automatically: true) } let(:order) { create(:order) } before do From 30470c932512a7408a418b26ae5580cf17cf3e1f Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 6 Jul 2023 11:28:01 +0200 Subject: [PATCH 268/834] Giant commit to be taken apart later --- .../admin/promotion_actions_controller.rb | 27 ++-------- .../admin/promotion_rules_controller.rb | 11 ++++ .../order_decorator.rb | 9 ++++ ...tch_job.rb => promotion_code_batch_job.rb} | 4 +- ...iler.rb => promotion_code_batch_mailer.rb} | 6 +-- .../app/models/solidus_friendly_promotions.rb | 5 ++ .../actions/adjust_line_item.rb | 2 +- .../actions/adjust_shipment.rb | 2 +- .../order_promotion.rb | 23 +++++++++ .../order_promotion_adjuster.rb | 2 +- ...cts_rule.rb => products_promotion_rule.rb} | 4 +- .../solidus_friendly_promotions/promotion.rb | 8 +-- .../{action.rb => promotion_action.rb} | 2 +- .../{code.rb => promotion_code.rb} | 4 +- .../{code => promotion_code}/batch_builder.rb | 22 ++++---- ...{code_batch.rb => promotion_code_batch.rb} | 6 +-- .../{rule.rb => promotion_rule.rb} | 2 +- ...ules_store.rb => promotion_rules_store.rb} | 4 +- ...ules_taxon.rb => promotion_rules_taxon.rb} | 4 +- ...{rules_user.rb => promotion_rules_user.rb} | 4 +- .../rules/first_order.rb | 2 +- .../rules/first_repeat_purchase_since.rb | 2 +- .../rules/item_total.rb | 2 +- .../rules/line_item_option_value.rb | 2 +- .../rules/line_item_product.rb | 2 +- .../rules/line_item_taxon.rb | 2 +- .../rules/nth_order.rb | 2 +- .../rules/one_use_per_user.rb | 2 +- .../rules/option_value.rb | 2 +- .../rules/product.rb | 6 +-- .../rules/store.rb | 6 +-- .../rules/taxon.rb | 6 +-- .../solidus_friendly_promotions/rules/user.rb | 6 +-- .../rules/user_logged_in.rb | 2 +- .../rules/user_role.rb | 2 +- .../promotion_code_batch_errored.text.erb} | 0 .../promotion_code_batch_finished.text.erb} | 0 friendly_promotions/config/locales/en.yml | 2 +- .../20230703101637_create_promotions.rb | 2 +- ...30703141116_create_promotion_categories.rb | 7 ++- .../20230703143943_create_promotion_rules.rb | 12 +++++ .../db/migrate/20230703143943_create_rules.rb | 11 ---- .../migrate/20230704083830_add_rule_tables.rb | 16 +++--- .../migrate/20230704093625_create_actions.rb | 14 ------ ...20230704093625_create_promotion_actions.rb | 14 ++++++ .../db/migrate/20230704102444_create_codes.rb | 11 ---- .../20230704102444_create_promotion_codes.rb | 11 ++++ ...04102656_create_promotion_code_batches.rb} | 24 ++++----- ...171556_create_friendly_order_promotions.rb | 11 ++++ .../lib/solidus_friendly_promotions.rb | 1 - .../testing_support/factories.rb | 6 +++ .../friendly_order_promotion_factory.rb | 8 +++ .../friendly_promotion_code_factory.rb | 4 +- .../order_promotion_spec.rb | 50 +++++++++++++++++++ .../products_promotion_rule_spec.rb | 8 +++ .../products_rule_spec.rb | 8 --- ...ction_spec.rb => promotion_action_spec.rb} | 4 +- .../batch_builder_spec.rb | 8 +-- ...h_spec.rb => promotion_code_batch_spec.rb} | 10 ++-- .../{code_spec.rb => promotion_code_spec.rb} | 2 +- .../{rule_spec.rb => promotion_rule_spec.rb} | 6 +-- .../promotion_rules_store_spec.rb | 8 +++ .../promotion_rules_taxon_spec.rb | 8 +++ .../promotion_rules_user_spec.rb | 8 +++ .../rules/product_spec.rb | 4 +- .../rules_store_spec.rb | 8 --- .../rules_taxon_spec.rb | 8 --- .../rules_user_spec.rb | 8 --- .../spec/models/spree/order_spec.rb | 8 +++ 69 files changed, 309 insertions(+), 198 deletions(-) create mode 100644 friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_decorator.rb rename friendly_promotions/app/jobs/solidus_friendly_promotions/{code_batch_job.rb => promotion_code_batch_job.rb} (86%) rename friendly_promotions/app/mailers/solidus_friendly_promotions/{code_batch_mailer.rb => promotion_code_batch_mailer.rb} (59%) create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/order_promotion.rb rename friendly_promotions/app/models/solidus_friendly_promotions/{products_rule.rb => products_promotion_rule.rb} (63%) rename friendly_promotions/app/models/solidus_friendly_promotions/{action.rb => promotion_action.rb} (97%) rename friendly_promotions/app/models/solidus_friendly_promotions/{code.rb => promotion_code.rb} (92%) rename friendly_promotions/app/models/solidus_friendly_promotions/{code => promotion_code}/batch_builder.rb (69%) rename friendly_promotions/app/models/solidus_friendly_promotions/{code_batch.rb => promotion_code_batch.rb} (78%) rename friendly_promotions/app/models/solidus_friendly_promotions/{rule.rb => promotion_rule.rb} (97%) rename friendly_promotions/app/models/solidus_friendly_promotions/{rules_store.rb => promotion_rules_store.rb} (62%) rename friendly_promotions/app/models/solidus_friendly_promotions/{rules_taxon.rb => promotion_rules_taxon.rb} (62%) rename friendly_promotions/app/models/solidus_friendly_promotions/{rules_user.rb => promotion_rules_user.rb} (65%) rename friendly_promotions/app/views/solidus_friendly_promotions/{code_batch_mailer/code_batch_errored.text.erb => promotion_code_batch_mailer/promotion_code_batch_errored.text.erb} (100%) rename friendly_promotions/app/views/solidus_friendly_promotions/{code_batch_mailer/code_batch_finished.text.erb => promotion_code_batch_mailer/promotion_code_batch_finished.text.erb} (100%) create mode 100644 friendly_promotions/db/migrate/20230703143943_create_promotion_rules.rb delete mode 100644 friendly_promotions/db/migrate/20230703143943_create_rules.rb delete mode 100644 friendly_promotions/db/migrate/20230704093625_create_actions.rb create mode 100644 friendly_promotions/db/migrate/20230704093625_create_promotion_actions.rb delete mode 100644 friendly_promotions/db/migrate/20230704102444_create_codes.rb create mode 100644 friendly_promotions/db/migrate/20230704102444_create_promotion_codes.rb rename friendly_promotions/db/migrate/{20230704102656_create_code_batches.rb => 20230704102656_create_promotion_code_batches.rb} (50%) create mode 100644 friendly_promotions/db/migrate/20230705171556_create_friendly_order_promotions.rb create mode 100644 friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_order_promotion_factory.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/order_promotion_spec.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/products_promotion_rule_spec.rb delete mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/products_rule_spec.rb rename friendly_promotions/spec/models/solidus_friendly_promotions/{action_spec.rb => promotion_action_spec.rb} (87%) rename friendly_promotions/spec/models/solidus_friendly_promotions/{code => promotion_code}/batch_builder_spec.rb (91%) rename friendly_promotions/spec/models/solidus_friendly_promotions/{code_batch_spec.rb => promotion_code_batch_spec.rb} (68%) rename friendly_promotions/spec/models/solidus_friendly_promotions/{code_spec.rb => promotion_code_spec.rb} (99%) rename friendly_promotions/spec/models/solidus_friendly_promotions/{rule_spec.rb => promotion_rule_spec.rb} (81%) create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_store_spec.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_taxon_spec.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_user_spec.rb delete mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/rules_store_spec.rb delete mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/rules_taxon_spec.rb delete mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/rules_user_spec.rb create mode 100644 friendly_promotions/spec/models/spree/order_spec.rb diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb index 7a2603281b8..0d592ff055d 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb @@ -2,37 +2,18 @@ module SolidusFriendlyPromotions module Admin - class PromotionActionsController < Spree::Admin::ResourceController - before_action :load_promotion - before_action :validate_level, only: :new - before_action :validate_promotion_action_type, only: [:create] - - helper 'solidus_friendly_promotions/admin/promotion_actions' + class PromotionActionsController < Spree::Admin::BaseController + before_action :load_promotion, only: [:create, :destroy, :new] + before_action :validate_promotion_action_type, only: :create def new - if params.dig(:promotion_action, :type) - validate_promotion_action_type - end - @promotion_action = @promotion.promotion_actions.build( - type: @promotion_action_type, - ) - if @promotion_action.respond_to?(:calculator_type) && params.dig(:promotion_action, :calculator_type) - @promotion_action.calculator_type = params.dig(:promotion_action, :calculator_type) - end - render layout: false - end - - def edit - if @promotion_action.calculator.class.name != params.dig(:promotion_action, :calculator_type) - @promotion_action.calculator = permitted_resource_params[:calculator_type].constantize.new - end render layout: false end def create @promotion_action = @promotion_action_type.new(permitted_resource_params) @promotion_action.promotion = @promotion - if @promotion_action.save + if @promotion_action.save(validate: false) flash[:success] = t('spree.successfully_created', resource: t('spree.promotion_action')) redirect_to location_after_save, format: :html else diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb index 1c237425034..3cfe1f55c63 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb @@ -85,6 +85,17 @@ def validate_level def promotion_rule_params params[:promotion_rule].permit! end + + def promotion_rule_types + case params.dig(:level) + when "order" + SolidusFriendlyPromotions.config.order_rules + when "line_item" + SolidusFriendlyPromotions.config.line_item_rules + when "shipment" + SolidusFriendlyPromotions.config.shipment_rules + end + end end end end diff --git a/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_decorator.rb b/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_decorator.rb new file mode 100644 index 00000000000..0604d5237d7 --- /dev/null +++ b/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_decorator.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions::OrderDecorator + def self.prepended(base) + base.has_many :friendly_order_promotions, class_name: "SolidusFriendlyPromotions::OrderPromotion", inverse_of: :order + base.has_many :friendly_promotions, through: :friendly_order_promotions, source: :promotion + end + Spree::Order.prepend self + end diff --git a/friendly_promotions/app/jobs/solidus_friendly_promotions/code_batch_job.rb b/friendly_promotions/app/jobs/solidus_friendly_promotions/promotion_code_batch_job.rb similarity index 86% rename from friendly_promotions/app/jobs/solidus_friendly_promotions/code_batch_job.rb rename to friendly_promotions/app/jobs/solidus_friendly_promotions/promotion_code_batch_job.rb index d30dbda0e25..7d2f8010928 100644 --- a/friendly_promotions/app/jobs/solidus_friendly_promotions/code_batch_job.rb +++ b/friendly_promotions/app/jobs/solidus_friendly_promotions/promotion_code_batch_job.rb @@ -1,11 +1,11 @@ # frozen_string_literal: true module SolidusFriendlyPromotions - class CodeBatchJob < ActiveJob::Base + class PromotionCodeBatchJob < ActiveJob::Base queue_as :default def perform(code_batch) - Code::BatchBuilder.new( + PromotionCode::BatchBuilder.new( code_batch ).build_promotion_codes diff --git a/friendly_promotions/app/mailers/solidus_friendly_promotions/code_batch_mailer.rb b/friendly_promotions/app/mailers/solidus_friendly_promotions/promotion_code_batch_mailer.rb similarity index 59% rename from friendly_promotions/app/mailers/solidus_friendly_promotions/code_batch_mailer.rb rename to friendly_promotions/app/mailers/solidus_friendly_promotions/promotion_code_batch_mailer.rb index 11078ae985a..4ad53b93e92 100644 --- a/friendly_promotions/app/mailers/solidus_friendly_promotions/code_batch_mailer.rb +++ b/friendly_promotions/app/mailers/solidus_friendly_promotions/promotion_code_batch_mailer.rb @@ -1,13 +1,13 @@ # frozen_string_literal: true module SolidusFriendlyPromotions - class CodeBatchMailer < Spree::BaseMailer - def code_batch_finished(code_batch) + class PromotionCodeBatchMailer < Spree::BaseMailer + def promotion_code_batch_finished(code_batch) @code_batch = code_batch mail(to: code_batch.email) end - def code_batch_errored(code_batch) + def promotion_code_batch_errored(code_batch) @code_batch = code_batch mail(to: code_batch.email) end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions.rb b/friendly_promotions/app/models/solidus_friendly_promotions.rb new file mode 100644 index 00000000000..56b3908fe0c --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions.rb @@ -0,0 +1,5 @@ +module SolidusFriendlyPromotions + def self.table_name_prefix + 'friendly_' + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb index b106f84af22..0870be2522a 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Actions - class AdjustLineItem < Action + class AdjustLineItem < PromotionAction def can_adjust?(object) object.is_a? Spree::LineItem end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb index a14c953c7d0..e5c8502eca6 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Actions - class AdjustShipment < Action + class AdjustShipment < PromotionAction def can_adjust?(object) object.is_a? Spree::Shipment end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/order_promotion.rb b/friendly_promotions/app/models/solidus_friendly_promotions/order_promotion.rb new file mode 100644 index 00000000000..623cf9154e2 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/order_promotion.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + # SolidusFriendlyPromotions::OrderPromotion represents the relationship between: + # + # 1. A promotion that a user attempted to apply to their order + # 2. The specific code that they used + class OrderPromotion < Spree::Base + belongs_to :order, class_name: 'Spree::Order' + belongs_to :promotion, class_name: 'SolidusFriendlyPromotions::Promotion' + belongs_to :promotion_code, class_name: 'SolidusFriendlyPromotions::PromotionCode', optional: true + + validates :promotion_code, presence: true, if: :require_promotion_code? + + self.allowed_ransackable_associations = %w[promotion_code] + + private + + def require_promotion_code? + promotion && !promotion.apply_automatically && promotion.codes.any? + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/order_promotion_adjuster.rb b/friendly_promotions/app/models/solidus_friendly_promotions/order_promotion_adjuster.rb index c58da2c46e9..8edb21594d5 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/order_promotion_adjuster.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/order_promotion_adjuster.rb @@ -44,7 +44,7 @@ def preload(records:, associations:) end def connected_order_promotions - order.promotions.active.includes(promotion_includes) + order.friendly_promotions.active.includes(promotion_includes) end def sale_promotions diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/products_rule.rb b/friendly_promotions/app/models/solidus_friendly_promotions/products_promotion_rule.rb similarity index 63% rename from friendly_promotions/app/models/solidus_friendly_promotions/products_rule.rb rename to friendly_promotions/app/models/solidus_friendly_promotions/products_promotion_rule.rb index d33f5970fa6..b4091a1e3ad 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/products_rule.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/products_promotion_rule.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true module SolidusFriendlyPromotions - class ProductsRule < Spree::Base + class ProductsPromotionRule < Spree::Base belongs_to :product, class_name: "Spree::Product" - belongs_to :rule + belongs_to :promotion_rule end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb index 4f609db64a2..bca33c18ac3 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb @@ -3,10 +3,10 @@ module SolidusFriendlyPromotions class Promotion < Spree::Base belongs_to :category, class_name: "SolidusFriendlyPromotions::PromotionCategory", foreign_key: :promotion_category_id, optional: true - has_many :rules - has_many :actions - has_many :codes - has_many :code_batches + has_many :rules, class_name: "SolidusFriendlyPromotions::PromotionRule" + has_many :actions, class_name: "SolidusFriendlyPromotions::PromotionAction" + has_many :codes, class_name: "SolidusFriendlyPromotions::PromotionCode" + has_many :code_batches, class_name: "SolidusFriendlyPromotions::PromotionCodeBatch" validates :name, presence: true validates :path, uniqueness: { allow_blank: true, case_sensitive: true } diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/action.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb similarity index 97% rename from friendly_promotions/app/models/solidus_friendly_promotions/action.rb rename to friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb index 14195714c51..265a42c4ee0 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/action.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb @@ -6,7 +6,7 @@ module SolidusFriendlyPromotions # # PromotionActions perform the necessary tasks when a promotion is activated # by an event and determined to be eligible. - class Action < Spree::Base + class PromotionAction < Spree::Base include Spree::Preferences::Persistable include Spree::SoftDeletable include Spree::CalculatedAdjustments diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/code.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code.rb similarity index 92% rename from friendly_promotions/app/models/solidus_friendly_promotions/code.rb rename to friendly_promotions/app/models/solidus_friendly_promotions/promotion_code.rb index eec5fbe4a90..23a1a546149 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/code.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true module SolidusFriendlyPromotions - class Code < Spree::Base + class PromotionCode < Spree::Base belongs_to :promotion, inverse_of: :codes - belongs_to :code_batch, inverse_of: :codes, optional: true + belongs_to :promotion_code_batch, inverse_of: :promotion_codes, optional: true has_many :adjustments before_validation :normalize_code diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/code/batch_builder.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code/batch_builder.rb similarity index 69% rename from friendly_promotions/app/models/solidus_friendly_promotions/code/batch_builder.rb rename to friendly_promotions/app/models/solidus_friendly_promotions/promotion_code/batch_builder.rb index 5ef85e50ce9..37dc95aead3 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/code/batch_builder.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code/batch_builder.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true module SolidusFriendlyPromotions - class Code < Spree::Base + class PromotionCode < Spree::Base class BatchBuilder - attr_reader :code_batch, :options - delegate :promotion, :number_of_codes, :base_code, to: :code_batch + attr_reader :promotion_code_batch, :options + delegate :promotion, :number_of_codes, :base_code, to: :promotion_code_batch DEFAULT_OPTIONS = { random_code_length: 6, @@ -12,17 +12,17 @@ class BatchBuilder sample_characters: ('a'..'z').to_a + (2..9).to_a.map(&:to_s) } - def initialize(code_batch, options = {}) - @code_batch = code_batch + def initialize(promotion_code_batch, options = {}) + @promotion_code_batch = promotion_code_batch options.assert_valid_keys(*DEFAULT_OPTIONS.keys) @options = DEFAULT_OPTIONS.merge(options) end def build_promotion_codes generate_random_codes - code_batch.update!(state: "completed") + promotion_code_batch.update!(state: "completed") rescue StandardError => error - code_batch.update!( + promotion_code_batch.update!( error: error.inspect, state: "failed" ) @@ -42,10 +42,10 @@ def generate_random_codes codes_for_current_batch = get_unique_codes(new_codes) codes_for_current_batch.each do |value| - Code.create!( + PromotionCode.create!( value: value, promotion: promotion, - code_batch: code_batch + promotion_code_batch: promotion_code_batch ) end created_codes += codes_for_current_batch.size @@ -57,11 +57,11 @@ def generate_random_code @options[:sample_characters].sample end.join - "#{base_code}#{@code_batch.join_characters}#{suffix}" + "#{base_code}#{@promotion_code_batch.join_characters}#{suffix}" end def get_unique_codes(code_set) - code_set - Code.where(value: code_set.to_a).pluck(:value) + code_set - PromotionCode.where(value: code_set.to_a).pluck(:value) end end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/code_batch.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code_batch.rb similarity index 78% rename from friendly_promotions/app/models/solidus_friendly_promotions/code_batch.rb rename to friendly_promotions/app/models/solidus_friendly_promotions/promotion_code_batch.rb index 0d47e7ea970..135fbb8998f 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/code_batch.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code_batch.rb @@ -1,12 +1,12 @@ # frozen_string_literal: true module SolidusFriendlyPromotions - class CodeBatch < Spree::Base + class PromotionCodeBatch < Spree::Base class CantProcessStartedBatch < StandardError end belongs_to :promotion - has_many :codes, dependent: :destroy + has_many :promotion_codes, dependent: :destroy validates :number_of_codes, numericality: { greater_than: 0 } validates_presence_of :base_code, :number_of_codes @@ -18,7 +18,7 @@ def finished? def process if state == "pending" update!(state: "processing") - CodeBatchJob.perform_later(self) + PromotionCodeBatchJob.perform_later(self) else raise CantProcessStartedBatch.new("Batch #{id} already started") end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rule.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rule.rb similarity index 97% rename from friendly_promotions/app/models/solidus_friendly_promotions/rule.rb rename to friendly_promotions/app/models/solidus_friendly_promotions/promotion_rule.rb index 86efa637f7f..9e083d1bc07 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rule.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rule.rb @@ -3,7 +3,7 @@ require 'spree/preferences/persistable' module SolidusFriendlyPromotions - class Rule < Spree::Base + class PromotionRule < Spree::Base include Spree::Preferences::Persistable belongs_to :promotion diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules_store.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rules_store.rb similarity index 62% rename from friendly_promotions/app/models/solidus_friendly_promotions/rules_store.rb rename to friendly_promotions/app/models/solidus_friendly_promotions/promotion_rules_store.rb index a4ab6dae755..2f6dd6c8a5c 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules_store.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rules_store.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true module SolidusFriendlyPromotions - class RulesStore < Spree::Base - belongs_to :rule + class PromotionRulesStore < Spree::Base + belongs_to :promotion_rule belongs_to :store, class_name: "Spree::Store" end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules_taxon.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rules_taxon.rb similarity index 62% rename from friendly_promotions/app/models/solidus_friendly_promotions/rules_taxon.rb rename to friendly_promotions/app/models/solidus_friendly_promotions/promotion_rules_taxon.rb index f9858940773..d020bdaeb04 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules_taxon.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rules_taxon.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true module SolidusFriendlyPromotions - class RulesTaxon < Spree::Base - belongs_to :rule + class PromotionRulesTaxon < Spree::Base + belongs_to :promotion_rule belongs_to :taxon, class_name: "Spree::Taxon" end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules_user.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rules_user.rb similarity index 65% rename from friendly_promotions/app/models/solidus_friendly_promotions/rules_user.rb rename to friendly_promotions/app/models/solidus_friendly_promotions/promotion_rules_user.rb index 3283ce6f8a4..91f2f209be8 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules_user.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rules_user.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true module SolidusFriendlyPromotions - class RulesUser < Spree::Base - belongs_to :rule + class PromotionRulesUser < Spree::Base + belongs_to :promotion_rule belongs_to :user, class_name: Spree::UserClassHandle.new end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_order.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_order.rb index ca90c5b3f2e..bd4df181558 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_order.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_order.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Rules - class FirstOrder < Rule + class FirstOrder < PromotionRule attr_reader :user, :email def applicable?(promotable) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb index 087f6314e8f..dfca964597d 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Rules - class FirstRepeatPurchaseSince < Rule + class FirstRepeatPurchaseSince < PromotionRule preference :days_ago, :integer, default: 365 validates :preferred_days_ago, numericality: {only_integer: true, greater_than: 0} diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb index d2af73b3772..a1573067ea8 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb @@ -7,7 +7,7 @@ module Rules # # To add extra operators please override `self.operators_map` or any other helper method. # To customize the error message you can also override `ineligible_message`. - class ItemTotal < Rule + class ItemTotal < PromotionRule preference :amount, :decimal, default: 100.00 preference :currency, :string, default: -> { Spree::Config[:currency] } preference :operator, :string, default: "gt" diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_option_value.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_option_value.rb index f57bfb17d42..b8252820c6e 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_option_value.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_option_value.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Rules - class LineItemOptionValue < Rule + class LineItemOptionValue < PromotionRule preference :eligible_values, :hash def applicable?(promotable) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb index f74933a1676..732a008c6aa 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb @@ -3,7 +3,7 @@ module SolidusFriendlyPromotions module Rules # A rule to apply a promotion only to line items with or without a chosen product - class LineItemProduct < Rule + class LineItemProduct < PromotionRule MATCH_POLICIES = %w[include exclude] has_many :product_promotion_rules, diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb index 48b73009922..13af307926a 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Rules - class LineItemTaxon < Rule + class LineItemTaxon < PromotionRule has_many :promotion_rule_taxons, class_name: "Spree::PromotionRuleTaxon", foreign_key: :promotion_rule_id, dependent: :destroy has_many :taxons, through: :promotion_rule_taxons, class_name: "Spree::Taxon" diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb index bee23a67e22..04cb592bd6c 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Rules - class NthOrder < Rule + class NthOrder < PromotionRule preference :nth_order, :integer, default: 2 # It does not make sense to have this apply to the first order using preferred_nth_order == 1 # Instead we could use the first_order rule diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb index 2cefe588199..770d964371f 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Rules - class OneUsePerUser < Rule + class OneUsePerUser < PromotionRule def applicable?(promotable) promotable.is_a?(Spree::Order) end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/option_value.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/option_value.rb index 4d5451a1dcb..f8b25aa040e 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/option_value.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/option_value.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Rules - class OptionValue < Rule + class OptionValue < PromotionRule preference :eligible_values, :hash def applicable?(promotable) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb index 1c35ffd0ec4..c8906689faf 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb @@ -6,9 +6,9 @@ module Rules # require all or any of the products to be present. Valid products # either come from assigned product group or are assingned directly to # the rule. - class Product < Rule - has_many :products_rules, inverse_of: :rule, dependent: :destroy - has_many :products, class_name: "Spree::Product", through: :products_rules + class Product < PromotionRule + has_many :products_promotion_rules, inverse_of: :promotion_rule, dependent: :destroy + has_many :products, class_name: "Spree::Product", through: :products_promotion_rules def preload_relations [:products] diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb index 4c3ed6446c4..70da23578a6 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb @@ -2,9 +2,9 @@ module SolidusFriendlyPromotions module Rules - class Store < Rule - has_many :rules_stores, inverse_of: :rule, dependent: :destroy - has_many :stores, through: :rules_stores, class_name: "Spree::Store" + class Store < PromotionRule + has_many :promotion_rules_stores, inverse_of: :promotion_rule, dependent: :destroy + has_many :stores, through: :promotion_rules_stores, class_name: "Spree::Store" def preload_relations [:stores] diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb index 811f7998295..a2d4750af0d 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb @@ -2,9 +2,9 @@ module SolidusFriendlyPromotions module Rules - class Taxon < Rule - has_many :rules_taxons, inverse_of: :rule, dependent: :destroy - has_many :taxons, through: :rules_taxons, class_name: "Spree::Taxon" + class Taxon < PromotionRule + has_many :promotion_rules_taxons, inverse_of: :promotion_rule, dependent: :destroy + has_many :taxons, through: :promotion_rules_taxons, class_name: "Spree::Taxon" def preload_relations [:taxons] diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb index 023b1b9e92c..44dd285880b 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb @@ -2,9 +2,9 @@ module SolidusFriendlyPromotions module Rules - class User < Rule - has_many :rules_users, inverse_of: :rule, dependent: :destroy - has_many :users, through: :rules_users, class_name: Spree::UserClassHandle.new + class User < PromotionRule + has_many :promotion_rules_users, inverse_of: :promotion_rule, dependent: :destroy + has_many :users, through: :promotion_rules_users, class_name: Spree::UserClassHandle.new def preload_relations [:users] diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb index 7c109cc956a..f43e85083c9 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Rules - class UserLoggedIn < Rule + class UserLoggedIn < PromotionRule def applicable?(promotable) promotable.is_a?(Spree::Order) end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_role.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_role.rb index 5f5c5ac9d93..7763145e979 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_role.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_role.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Rules - class UserRole < Rule + class UserRole < PromotionRule preference :role_ids, :array, default: [] MATCH_POLICIES = %w[any all] diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/code_batch_mailer/code_batch_errored.text.erb b/friendly_promotions/app/views/solidus_friendly_promotions/promotion_code_batch_mailer/promotion_code_batch_errored.text.erb similarity index 100% rename from friendly_promotions/app/views/solidus_friendly_promotions/code_batch_mailer/code_batch_errored.text.erb rename to friendly_promotions/app/views/solidus_friendly_promotions/promotion_code_batch_mailer/promotion_code_batch_errored.text.erb diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/code_batch_mailer/code_batch_finished.text.erb b/friendly_promotions/app/views/solidus_friendly_promotions/promotion_code_batch_mailer/promotion_code_batch_finished.text.erb similarity index 100% rename from friendly_promotions/app/views/solidus_friendly_promotions/code_batch_mailer/code_batch_finished.text.erb rename to friendly_promotions/app/views/solidus_friendly_promotions/promotion_code_batch_mailer/promotion_code_batch_finished.text.erb diff --git a/friendly_promotions/config/locales/en.yml b/friendly_promotions/config/locales/en.yml index 62331987703..81d13a6461e 100644 --- a/friendly_promotions/config/locales/en.yml +++ b/friendly_promotions/config/locales/en.yml @@ -145,7 +145,7 @@ en: description: Order includes User with specified Role(s) errors: models: - solidus_friendly_promotions/code: + solidus_friendly_promotions/promotion_code: attributes: base: disallowed_with_apply_automatically: Could not create promotion code on promotion that apply automatically diff --git a/friendly_promotions/db/migrate/20230703101637_create_promotions.rb b/friendly_promotions/db/migrate/20230703101637_create_promotions.rb index b0c98b1d077..84c0c2c0ca6 100644 --- a/friendly_promotions/db/migrate/20230703101637_create_promotions.rb +++ b/friendly_promotions/db/migrate/20230703101637_create_promotions.rb @@ -1,6 +1,6 @@ class CreatePromotions < ActiveRecord::Migration[7.0] def change - create_table :solidus_friendly_promotions_promotions do |t| + create_table :friendly_promotions do |t| t.string :description t.datetime :expires_at, precision: nil t.datetime :starts_at, precision: nil diff --git a/friendly_promotions/db/migrate/20230703141116_create_promotion_categories.rb b/friendly_promotions/db/migrate/20230703141116_create_promotion_categories.rb index f31f9a2ed90..97b1499d7ab 100644 --- a/friendly_promotions/db/migrate/20230703141116_create_promotion_categories.rb +++ b/friendly_promotions/db/migrate/20230703141116_create_promotion_categories.rb @@ -1,15 +1,14 @@ class CreatePromotionCategories < ActiveRecord::Migration[7.0] def change - create_table :solidus_friendly_promotions_promotion_categories do |t| + create_table :friendly_promotion_categories do |t| t.string :name t.string :code t.timestamps end - add_reference :solidus_friendly_promotions_promotions, + add_reference :friendly_promotions, :promotion_category, - foreign_key: { to_table: :solidus_friendly_promotions_promotion_categories }, - index: { name: :index_solidus_friendly_promotions_promotions_categories } + foreign_key: { to_table: :friendly_promotion_categories } end end diff --git a/friendly_promotions/db/migrate/20230703143943_create_promotion_rules.rb b/friendly_promotions/db/migrate/20230703143943_create_promotion_rules.rb new file mode 100644 index 00000000000..980ac111d56 --- /dev/null +++ b/friendly_promotions/db/migrate/20230703143943_create_promotion_rules.rb @@ -0,0 +1,12 @@ +class CreatePromotionRules < ActiveRecord::Migration[7.0] + def change + create_table :friendly_promotion_rules do |t| + t.references :promotion, + foreign_key: { to_table: :friendly_promotions } + t.string :type + t.text :preferences + + t.timestamps + end + end +end diff --git a/friendly_promotions/db/migrate/20230703143943_create_rules.rb b/friendly_promotions/db/migrate/20230703143943_create_rules.rb deleted file mode 100644 index 674672f9536..00000000000 --- a/friendly_promotions/db/migrate/20230703143943_create_rules.rb +++ /dev/null @@ -1,11 +0,0 @@ -class CreateRules < ActiveRecord::Migration[7.0] - def change - create_table :solidus_friendly_promotions_rules do |t| - t.references :promotion, foreign_key: { to_table: :solidus_friendly_promotions_promotions } - t.string :type - t.text :preferences - - t.timestamps - end - end -end diff --git a/friendly_promotions/db/migrate/20230704083830_add_rule_tables.rb b/friendly_promotions/db/migrate/20230704083830_add_rule_tables.rb index 89e49b35e37..314b2d8cf4d 100644 --- a/friendly_promotions/db/migrate/20230704083830_add_rule_tables.rb +++ b/friendly_promotions/db/migrate/20230704083830_add_rule_tables.rb @@ -1,29 +1,29 @@ class AddRuleTables < ActiveRecord::Migration[7.0] def change - create_table :solidus_friendly_promotions_products_rules, force: :cascade do |t| + create_table :friendly_products_promotion_rules, force: :cascade do |t| t.references :product, index: true, null: false, foreign_key: { to_table: :spree_products } - t.references :rule, index: true, null: false, foreign_key: { to_table: :solidus_friendly_promotions_rules } + t.references :promotion_rule, index: true, null: false, foreign_key: { to_table: :friendly_promotion_rules } t.timestamps end - create_table :solidus_friendly_promotions_rules_taxons, force: :cascade do |t| + create_table :friendly_promotion_rules_taxons, force: :cascade do |t| t.references :taxon, index: true, null: false, foreign_key: { to_table: :spree_taxons } - t.references :rule, index: true, null: false, foreign_key: { to_table: :solidus_friendly_promotions_rules } + t.references :promotion_rule, index: true, null: false, foreign_key: { to_table: :friendly_promotion_rules } t.timestamps end - create_table :solidus_friendly_promotions_rules_users, force: :cascade do |t| + create_table :friendly_promotion_rules_users, force: :cascade do |t| t.references :user, index: true, null: false, foreign_key: { to_table: Spree.user_class.table_name } - t.references :rule, index: true, null: false, foreign_key: { to_table: :solidus_friendly_promotions_rules } + t.references :promotion_rule, index: true, null: false, foreign_key: { to_table: :friendly_promotion_rules } t.timestamps end - create_table :solidus_friendly_promotions_rules_stores do |t| + create_table :friendly_promotion_rules_stores do |t| t.references :store, index: true, null: false, foreign_key: { to_table: :spree_users } - t.references :rule, index: true, null: false + t.references :promotion_rule, index: true, null: false t.timestamps end diff --git a/friendly_promotions/db/migrate/20230704093625_create_actions.rb b/friendly_promotions/db/migrate/20230704093625_create_actions.rb deleted file mode 100644 index 41e7cb21120..00000000000 --- a/friendly_promotions/db/migrate/20230704093625_create_actions.rb +++ /dev/null @@ -1,14 +0,0 @@ -class CreateActions < ActiveRecord::Migration[7.0] - def change - create_table :solidus_friendly_promotions_actions do |t| - t.references :promotion, index: true, null: false, foreign_key: { to_table: :solidus_friendly_promotions_promotions } - t.string :type - t.datetime :deleted_at, precision: nil - t.text :preferences - t.index [:deleted_at], name: :index_solidus_friendly_promotions_actions_on_deleted_at - t.index [:id, :type], name: :index_solidus_friendly_promotions_actions_on_id_and_type - - t.timestamps - end - end -end diff --git a/friendly_promotions/db/migrate/20230704093625_create_promotion_actions.rb b/friendly_promotions/db/migrate/20230704093625_create_promotion_actions.rb new file mode 100644 index 00000000000..731ee6abd44 --- /dev/null +++ b/friendly_promotions/db/migrate/20230704093625_create_promotion_actions.rb @@ -0,0 +1,14 @@ +class CreatePromotionActions < ActiveRecord::Migration[7.0] + def change + create_table :friendly_promotion_actions do |t| + t.references :promotion, index: true, null: false, foreign_key: { to_table: :friendly_promotions } + t.string :type + t.datetime :deleted_at, precision: nil + t.text :preferences + t.index [:deleted_at], name: :index_friendly_promotion_actions_on_deleted_at + t.index [:id, :type], name: :index_friendly_promotion_actions_on_id_and_type + + t.timestamps + end + end +end diff --git a/friendly_promotions/db/migrate/20230704102444_create_codes.rb b/friendly_promotions/db/migrate/20230704102444_create_codes.rb deleted file mode 100644 index 022eed3aa3f..00000000000 --- a/friendly_promotions/db/migrate/20230704102444_create_codes.rb +++ /dev/null @@ -1,11 +0,0 @@ -class CreateCodes < ActiveRecord::Migration[7.0] - def change - create_table :solidus_friendly_promotions_codes, force: :cascade do |t| - t.references :promotion, null: false, index: true, foreign_key: { to_table: :solidus_friendly_promotions_promotions } - t.string :value, null: false - t.timestamps - - t.index ["value"], name: "index_solidus_friendly_promotions_codes_on_value", unique: true - end - end -end diff --git a/friendly_promotions/db/migrate/20230704102444_create_promotion_codes.rb b/friendly_promotions/db/migrate/20230704102444_create_promotion_codes.rb new file mode 100644 index 00000000000..9168098cb96 --- /dev/null +++ b/friendly_promotions/db/migrate/20230704102444_create_promotion_codes.rb @@ -0,0 +1,11 @@ +class CreatePromotionCodes < ActiveRecord::Migration[7.0] + def change + create_table :friendly_promotion_codes, force: :cascade do |t| + t.references :promotion, null: false, index: true, foreign_key: { to_table: :friendly_promotions } + t.string :value, null: false + t.timestamps + + t.index ["value"], name: "index_friendly_promotion_codes_on_value", unique: true + end + end +end diff --git a/friendly_promotions/db/migrate/20230704102656_create_code_batches.rb b/friendly_promotions/db/migrate/20230704102656_create_promotion_code_batches.rb similarity index 50% rename from friendly_promotions/db/migrate/20230704102656_create_code_batches.rb rename to friendly_promotions/db/migrate/20230704102656_create_promotion_code_batches.rb index f70db0ec4ff..0a1cd5aba53 100644 --- a/friendly_promotions/db/migrate/20230704102656_create_code_batches.rb +++ b/friendly_promotions/db/migrate/20230704102656_create_promotion_code_batches.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true -class CreateCodeBatches < ActiveRecord::Migration[7.0] +class CreatePromotionCodeBatches < ActiveRecord::Migration[7.0] def change - create_table :solidus_friendly_promotions_code_batches do |t| - t.references :promotion, null: false, index: true, foreign_key: { to_table: :solidus_friendly_promotions_promotions } + create_table :friendly_promotion_code_batches do |t| + t.references :promotion, null: false, index: true, foreign_key: { to_table: :friendly_promotions } t.string :base_code, null: false t.integer :number_of_codes, null: false t.string :join_characters, null: false, default: "_" @@ -14,26 +14,26 @@ def change end add_foreign_key( - :solidus_friendly_promotions_code_batches, - :solidus_friendly_promotions_promotions, + :friendly_promotion_code_batches, + :friendly_promotions, column: :promotion_id ) add_column( - :solidus_friendly_promotions_codes, - :code_batch_id, + :friendly_promotion_codes, + :promotion_code_batch_id, :integer ) add_foreign_key( - :solidus_friendly_promotions_codes, - :solidus_friendly_promotions_code_batches, - column: :code_batch_id + :friendly_promotion_codes, + :friendly_promotion_code_batches, + column: :promotion_code_batch_id ) add_index( - :solidus_friendly_promotions_codes, - :code_batch_id + :friendly_promotion_codes, + :promotion_code_batch_id ) end end diff --git a/friendly_promotions/db/migrate/20230705171556_create_friendly_order_promotions.rb b/friendly_promotions/db/migrate/20230705171556_create_friendly_order_promotions.rb new file mode 100644 index 00000000000..a04e320aa0b --- /dev/null +++ b/friendly_promotions/db/migrate/20230705171556_create_friendly_order_promotions.rb @@ -0,0 +1,11 @@ +class CreateFriendlyOrderPromotions < ActiveRecord::Migration[7.0] + def change + create_table :friendly_order_promotions do |t| + t.references :order, index: true, null: false, foreign_key: { to_table: :spree_orders } + t.references :promotion, index: true, null: false, foreign_key: { to_table: :friendly_promotions } + t.references :promotion_code, index: true, null: true, foreign_key: { to_table: :friendly_promotion_codes } + + t.timestamps + end + end +end diff --git a/friendly_promotions/lib/solidus_friendly_promotions.rb b/friendly_promotions/lib/solidus_friendly_promotions.rb index bcf6fb40006..5a91e257a3e 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions.rb @@ -8,7 +8,6 @@ require "solidus_friendly_promotions/engine" module SolidusFriendlyPromotions - # JS Importmap instance singleton_class.attr_accessor :importmap self.importmap = Importmap::Map.new diff --git a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/factories.rb b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/factories.rb index 9ffd7e6ee9c..0d01d1d037a 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/factories.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/factories.rb @@ -1,8 +1,14 @@ # frozen_string_literal: true +module SolidusFriendlyPromotions + def self.table_name_prefix + "friendly_" + end +end require "solidus_friendly_promotions/testing_support/friendly_promotion_code_factory" require "solidus_friendly_promotions/testing_support/friendly_promotion_category_factory" require "solidus_friendly_promotions/testing_support/friendly_promotion_factory" +require "solidus_friendly_promotions/testing_support/friendly_order_promotion_factory" FactoryBot.define do end diff --git a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_order_promotion_factory.rb b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_order_promotion_factory.rb new file mode 100644 index 00000000000..2361cbedae5 --- /dev/null +++ b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_order_promotion_factory.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :friendly_order_promotion, class: 'SolidusFriendlyPromotions::OrderPromotion' do + association :order, factory: :order + association :promotion, factory: :friendly_promotion + end +end diff --git a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_code_factory.rb b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_code_factory.rb index 267b8139d95..dbf38774456 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_code_factory.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_code_factory.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true FactoryBot.define do - factory :friendly_promotion_code, class: 'SolidusFriendlyPromotions::Code' do - promotion factory: :friendly_promotion + factory :friendly_promotion_code, class: 'SolidusFriendlyPromotions::PromotionCode' do + association :promotion, factory: :friendly_promotion sequence(:value) { |i| "code#{i}" } end end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/order_promotion_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/order_promotion_spec.rb new file mode 100644 index 00000000000..b1d79ff4910 --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/order_promotion_spec.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SolidusFriendlyPromotions::OrderPromotion do + subject do + order_promotion + end + let(:promotion) { build(:friendly_promotion) } + let(:order_promotion) { build(:friendly_order_promotion, promotion: promotion) } + + describe "promotion code presence error" do + subject do + order_promotion.valid? + order_promotion.errors[:promotion_code] + end + + context "when the promotion does not have a code" do + it { is_expected.to be_blank } + end + + context "when the promotion has a code" do + let!(:promotion_code) do + promotion.codes << build(:friendly_promotion_code, promotion: promotion) + end + + it { is_expected.to include("can't be blank") } + end + end + + describe "promotion code presence error on promotion that apply automatically" do + subject do + order_promotion.promotion.apply_automatically = true + order_promotion.valid? + order_promotion.errors[:promotion_code] + end + + context "when the promotion does not have a code" do + it { is_expected.to be_blank } + end + + context "when the promotion has a code" do + let!(:promotion_code) do + promotion.codes << build(:friendly_promotion_code, promotion: promotion) + end + + it { is_expected.to be_blank } + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/products_promotion_rule_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/products_promotion_rule_spec.rb new file mode 100644 index 00000000000..fff40dd1c28 --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/products_promotion_rule_spec.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SolidusFriendlyPromotions::ProductsPromotionRule do + it { is_expected.to belong_to(:product) } + it { is_expected.to belong_to(:promotion_rule) } +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/products_rule_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/products_rule_spec.rb deleted file mode 100644 index 3d8f7a01044..00000000000 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/products_rule_spec.rb +++ /dev/null @@ -1,8 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe SolidusFriendlyPromotions::ProductsRule do - it { is_expected.to belong_to(:product) } - it { is_expected.to belong_to(:rule) } -end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/action_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_action_spec.rb similarity index 87% rename from friendly_promotions/spec/models/solidus_friendly_promotions/action_spec.rb rename to friendly_promotions/spec/models/solidus_friendly_promotions/promotion_action_spec.rb index 63217e6b1a3..3099a5d20d6 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/action_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_action_spec.rb @@ -2,7 +2,7 @@ require "spec_helper" -RSpec.describe SolidusFriendlyPromotions::Action do +RSpec.describe SolidusFriendlyPromotions::PromotionAction do it { is_expected.to belong_to(:promotion) } it { is_expected.to have_one(:calculator) } @@ -21,7 +21,7 @@ let(:variant) { create(:variant) } let(:order) { create(:order) } let(:adjustable) { Spree::LineItem.new(order: order, variant: variant, price: 10)} - let(:promotion) { Spree::Promotion.new(name: "20 Perzent off") } + let(:promotion) { SolidusFriendlyPromotions::Promotion.new(name: "20 Perzent off") } let(:action) { described_class.new(promotion: promotion)} before do allow(action).to receive(:compute_amount).and_return(-1) diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/code/batch_builder_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code/batch_builder_spec.rb similarity index 91% rename from friendly_promotions/spec/models/solidus_friendly_promotions/code/batch_builder_spec.rb rename to friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code/batch_builder_spec.rb index 4d0e3156a71..b124f031cc7 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/code/batch_builder_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code/batch_builder_spec.rb @@ -2,13 +2,13 @@ require "spec_helper" -RSpec.describe SolidusFriendlyPromotions::Code::BatchBuilder do +RSpec.describe SolidusFriendlyPromotions::PromotionCode::BatchBuilder do let(:promotion) { create(:friendly_promotion) } let(:base_code) { "abc" } let(:options) { {} } let(:number_of_codes) { 10 } let(:code_batch) do - SolidusFriendlyPromotions::CodeBatch.create!( + SolidusFriendlyPromotions::PromotionCodeBatch.create!( promotion_id: promotion.id, base_code: base_code, number_of_codes: number_of_codes, @@ -42,7 +42,7 @@ end it "update the promotion codes count for the batch" do - expect(code_batch.codes.count).to eq(10) + expect(code_batch.promotion_codes.count).to eq(10) end it "builds the correct number of codes" do @@ -87,7 +87,7 @@ context "with a custom join separator" do let(:code_batch) do - SolidusFriendlyPromotions::CodeBatch.create!( + SolidusFriendlyPromotions::PromotionCodeBatch.create!( promotion_id: promotion.id, base_code: base_code, number_of_codes: number_of_codes, diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/code_batch_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code_batch_spec.rb similarity index 68% rename from friendly_promotions/spec/models/solidus_friendly_promotions/code_batch_spec.rb rename to friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code_batch_spec.rb index b4b23104249..ed0472183e1 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/code_batch_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code_batch_spec.rb @@ -2,7 +2,7 @@ require "spec_helper" -RSpec.describe SolidusFriendlyPromotions::CodeBatch, type: :model do +RSpec.describe SolidusFriendlyPromotions::PromotionCodeBatch, type: :model do subject do described_class.create!( promotion_id: create(:friendly_promotion).id, @@ -17,7 +17,7 @@ context "with a pending code batch" do it "should call the worker" do expect { subject.process } - .to have_enqueued_job(SolidusFriendlyPromotions::CodeBatchJob) + .to have_enqueued_job(SolidusFriendlyPromotions::PromotionCodeBatchJob) end it "should update the state to processing" do @@ -31,7 +31,7 @@ before { subject.update_attribute(:state, "processing") } it "should raise an error" do - expect{ subject.process }.to raise_error SolidusFriendlyPromotions::CodeBatch::CantProcessStartedBatch + expect{ subject.process }.to raise_error described_class::CantProcessStartedBatch end end @@ -39,7 +39,7 @@ before { subject.update_attribute(:state, "completed") } it "should raise an error" do - expect{ subject.process }.to raise_error SolidusFriendlyPromotions::CodeBatch::CantProcessStartedBatch + expect{ subject.process }.to raise_error described_class::CantProcessStartedBatch end end @@ -47,7 +47,7 @@ before { subject.update_attribute(:state, "failed") } it "should raise an error" do - expect{ subject.process }.to raise_error SolidusFriendlyPromotions::CodeBatch::CantProcessStartedBatch + expect{ subject.process }.to raise_error described_class::CantProcessStartedBatch end end end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/code_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code_spec.rb similarity index 99% rename from friendly_promotions/spec/models/solidus_friendly_promotions/code_spec.rb rename to friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code_spec.rb index 7ed0a8cacd2..39d751d9841 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/code_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe SolidusFriendlyPromotions::Code do +RSpec.describe SolidusFriendlyPromotions::PromotionCode do context 'callbacks' do subject { promotion_code.save } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rule_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rule_spec.rb similarity index 81% rename from friendly_promotions/spec/models/solidus_friendly_promotions/rule_spec.rb rename to friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rule_spec.rb index 57d8bda464b..85e31ad9c27 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rule_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rule_spec.rb @@ -2,10 +2,10 @@ require "spec_helper" -RSpec.describe SolidusFriendlyPromotions::Rule do - class BadTestRule < SolidusFriendlyPromotions::Rule; end +RSpec.describe SolidusFriendlyPromotions::PromotionRule do + class BadTestRule < SolidusFriendlyPromotions::PromotionRule; end - class TestRule < SolidusFriendlyPromotions::Rule + class TestRule < SolidusFriendlyPromotions::PromotionRule def eligible?(_promotable, _options = {}) true end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_store_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_store_spec.rb new file mode 100644 index 00000000000..e212e065331 --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_store_spec.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SolidusFriendlyPromotions::PromotionRulesStore do + it { is_expected.to belong_to(:store) } + it { is_expected.to belong_to(:promotion_rule) } +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_taxon_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_taxon_spec.rb new file mode 100644 index 00000000000..9214b152643 --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_taxon_spec.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SolidusFriendlyPromotions::PromotionRulesTaxon do + it { is_expected.to belong_to(:taxon) } + it { is_expected.to belong_to(:promotion_rule) } +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_user_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_user_spec.rb new file mode 100644 index 00000000000..e7003c49341 --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_user_spec.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SolidusFriendlyPromotions::PromotionRulesUser do + it { is_expected.to belong_to(:user) } + it { is_expected.to belong_to(:promotion_rule) } +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/product_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/product_spec.rb index aa2ab511209..1f5c574e77f 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/product_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/product_spec.rb @@ -108,8 +108,8 @@ let(:rule) do SolidusFriendlyPromotions::Rules::Product.create!( promotion: create(:friendly_promotion), - products_rules: [ - SolidusFriendlyPromotions::ProductsRule.new(product: product) + products_promotion_rules: [ + SolidusFriendlyPromotions::ProductsPromotionRule.new(product: product) ] ).tap do |rule| rule.preferred_match_policy = "invalid" diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules_store_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules_store_spec.rb deleted file mode 100644 index c0b2635a37e..00000000000 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules_store_spec.rb +++ /dev/null @@ -1,8 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe SolidusFriendlyPromotions::RulesStore do - it { is_expected.to belong_to(:store) } - it { is_expected.to belong_to(:rule) } -end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules_taxon_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules_taxon_spec.rb deleted file mode 100644 index cceff860d66..00000000000 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules_taxon_spec.rb +++ /dev/null @@ -1,8 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe SolidusFriendlyPromotions::RulesTaxon do - it { is_expected.to belong_to(:taxon) } - it { is_expected.to belong_to(:rule) } -end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules_user_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules_user_spec.rb deleted file mode 100644 index 0fdb7b479ce..00000000000 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules_user_spec.rb +++ /dev/null @@ -1,8 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe SolidusFriendlyPromotions::RulesUser do - it { is_expected.to belong_to(:user) } - it { is_expected.to belong_to(:rule) } -end diff --git a/friendly_promotions/spec/models/spree/order_spec.rb b/friendly_promotions/spec/models/spree/order_spec.rb new file mode 100644 index 00000000000..51bfa5af2b9 --- /dev/null +++ b/friendly_promotions/spec/models/spree/order_spec.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe Spree::Order do + it { is_expected.to have_many :friendly_promotions } + it { is_expected.to have_many :friendly_order_promotions } +end From 42425d530309f7d19b92f4f4812cb0fb33475a79 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 6 Jul 2023 14:09:38 +0200 Subject: [PATCH 269/834] Fix Promotion Code spec and usage-count based eligibility --- .../adjustment_decorator.rb | 14 +++++ .../order_decorator.rb | 12 +++++ .../line_item_adjuster.rb | 2 +- .../order_promotion_adjuster.rb | 5 +- .../solidus_friendly_promotions/promotion.rb | 2 +- .../promotion_code.rb | 6 +-- .../testing_support/factories.rb | 1 + .../testing_support/friendly_order_factory.rb | 21 ++++++++ .../promotion_code_spec.rb | 51 +++++++++++++------ friendly_promotions/spec/spec_helper.rb | 2 + 10 files changed, 94 insertions(+), 22 deletions(-) create mode 100644 friendly_promotions/app/decorators/models/solidus_friendly_promotions/adjustment_decorator.rb create mode 100644 friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_order_factory.rb diff --git a/friendly_promotions/app/decorators/models/solidus_friendly_promotions/adjustment_decorator.rb b/friendly_promotions/app/decorators/models/solidus_friendly_promotions/adjustment_decorator.rb new file mode 100644 index 00000000000..35567c97c13 --- /dev/null +++ b/friendly_promotions/app/decorators/models/solidus_friendly_promotions/adjustment_decorator.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module AdjustmentDecorator + def self.prepended(base) + base.scope :friendly_promotion, -> { where(source_type: "SolidusFriendlyPromotions::PromotionAction") } + end + + def friendly_promotion? + source_type == "SolidusFriendlyPromotions::PromotionAction" + end + Spree::Adjustment.prepend self + end +end diff --git a/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_decorator.rb b/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_decorator.rb index 0604d5237d7..29d97327d9b 100644 --- a/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_decorator.rb +++ b/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_decorator.rb @@ -5,5 +5,17 @@ def self.prepended(base) base.has_many :friendly_order_promotions, class_name: "SolidusFriendlyPromotions::OrderPromotion", inverse_of: :order base.has_many :friendly_promotions, through: :friendly_order_promotions, source: :promotion end + + def ensure_promotions_eligible + Spree::Config.promotion_adjuster_class.new(self).call + if promo_total_changed? + restart_checkout_flow + recalculate + errors.add(:base, I18n.t('spree.promotion_total_changed_before_complete')) + end + + super + end + Spree::Order.prepend self end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/line_item_adjuster.rb b/friendly_promotions/app/models/solidus_friendly_promotions/line_item_adjuster.rb index ef748830aa2..0b195f421cd 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/line_item_adjuster.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/line_item_adjuster.rb @@ -10,7 +10,7 @@ def initialize(promotions:) def call(line_item) return unless line_item.variant.product.promotionable? - non_promotion_adjustments = line_item.adjustments.reject(&:promotion?) + non_promotion_adjustments = line_item.adjustments.reject(&:friendly_promotion?) eligible_promotions = PromotionEligibility.new(promotable: line_item, possible_promotions: promotions).call diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/order_promotion_adjuster.rb b/friendly_promotions/app/models/solidus_friendly_promotions/order_promotion_adjuster.rb index 8edb21594d5..b7a569696dd 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/order_promotion_adjuster.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/order_promotion_adjuster.rb @@ -44,7 +44,10 @@ def preload(records:, associations:) end def connected_order_promotions - order.friendly_promotions.active.includes(promotion_includes) + eligible_connected_promotion_ids = order.friendly_order_promotions.select do |order_promotion| + order_promotion.promotion_code.nil? || !order_promotion.promotion_code.usage_limit_exceeded?(excluded_orders: [order]) + end.map(&:promotion_id) + order.friendly_promotions.active.where(id: eligible_connected_promotion_ids).includes(promotion_includes) end def sale_promotions diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb index bca33c18ac3..9f7e91283d8 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb @@ -36,7 +36,7 @@ def discounted_orders joins(:all_adjustments). where( spree_adjustments: { - source_type: "SolidusFriendlyPromotions::Action", + source_type: "SolidusFriendlyPromotions::PromotionAction", source_id: actions.map(&:id), eligible: true } diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code.rb index 23a1a546149..6ed524a5b17 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code.rb @@ -4,7 +4,7 @@ module SolidusFriendlyPromotions class PromotionCode < Spree::Base belongs_to :promotion, inverse_of: :codes belongs_to :promotion_code_batch, inverse_of: :promotion_codes, optional: true - has_many :adjustments + has_many :adjustments, class_name: "Spree::Adjustment" before_validation :normalize_code @@ -32,8 +32,8 @@ def usage_count(excluded_orders: []) discounted_orders. complete. where.not(spree_orders: { state: :canceled }). - joins(:order_promotions). - where(spree_orders_promotions: { promotion_code_id: self.id }). + joins(:friendly_order_promotions). + where(friendly_order_promotions: { promotion_code_id: self.id }). where.not(id: excluded_orders.map(&:id)). count end diff --git a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/factories.rb b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/factories.rb index 0d01d1d037a..cf315d7e46d 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/factories.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/factories.rb @@ -9,6 +9,7 @@ def self.table_name_prefix require "solidus_friendly_promotions/testing_support/friendly_promotion_category_factory" require "solidus_friendly_promotions/testing_support/friendly_promotion_factory" require "solidus_friendly_promotions/testing_support/friendly_order_promotion_factory" +require "solidus_friendly_promotions/testing_support/friendly_order_factory" FactoryBot.define do end diff --git a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_order_factory.rb b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_order_factory.rb new file mode 100644 index 00000000000..04819d8b05b --- /dev/null +++ b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_order_factory.rb @@ -0,0 +1,21 @@ + +# frozen_string_literal: true + +FactoryBot.define do + factory :completed_order_with_friendly_promotion, parent: :order_with_line_items do + transient do + completed_at { Time.current } + promotion { nil } + end + + after(:create) do |order, evaluator| + promotion = evaluator.promotion || create(:friendly_promotion, code: "test") + promotion_code = promotion.codes.first || create(:promotion_code, promotion: promotion) + + order.friendly_order_promotions.create!(promotion: promotion, promotion_code: promotion_code) + order.recalculate + order.update_column(:completed_at, evaluator.completed_at) + order.update_column(:state, "complete") + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code_spec.rb index 39d751d9841..81003cf63bf 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code_spec.rb @@ -75,7 +75,7 @@ context "on a different order" do before do FactoryBot.create( - :completed_order_with_promotion, + :completed_order_with_friendly_promotion, promotion: promotion ) code.adjustments.update_all(eligible: true) @@ -106,7 +106,7 @@ end let(:promotable) do FactoryBot.create( - :completed_order_with_promotion, + :completed_order_with_friendly_promotion, promotion: promotion ) end @@ -119,15 +119,11 @@ :friendly_promotion, :with_line_item_adjustment, code: "discount", - per_code_usage_limit: usage_limit + per_code_usage_limit: usage_limit, ) end before do - promotion.actions.first.perform({ - order: order, - promotion: promotion, - promotion_code: code - }) + order.recalculate end context "when there are multiple line items" do let(:order) { FactoryBot.create(:order_with_line_items, line_items_count: 2) } @@ -160,15 +156,25 @@ subject { code.usage_count } + context "when the code is applied to a non-complete order" do let(:order) { FactoryBot.create(:order_with_line_items) } - before { promotion.activate(order: order, promotion_code: code) } + + before do + order.friendly_order_promotions.create( + promotion: promotion, + promotion_code: code + ) + order.recalculate + end + it { is_expected.to eq 0 } end + context "when the code is applied to a complete order" do let!(:order) do FactoryBot.create( - :completed_order_with_promotion, + :completed_order_with_friendly_promotion, promotion: promotion ) end @@ -176,7 +182,7 @@ it { is_expected.to eq 1 } end context "and the promo is ineligible" do - before { order.adjustments.promotion.update_all(eligible: false) } + before { order.all_adjustments.update_all(eligible: false) } it { is_expected.to eq 0 } end context "and the order is canceled" do @@ -198,28 +204,41 @@ ) end let(:code) { promotion.codes.first } + let(:order) do FactoryBot.create(:order_with_line_items, line_items_price: 40, shipment_cost: 0).tap do |order| FactoryBot.create(:payment, amount: 30, order: order) - promotion.activate(order: order, promotion_code: code) end end - let(:promo_adjustment) { order.adjustments.promotion.first } + + let(:promo_adjustment) { order.all_adjustments.friendly_promotion.first } + before do + order.friendly_order_promotions.create!( + order: order, + promotion: promotion, + promotion_code: SolidusFriendlyPromotions::PromotionCode.find_by(value: "discount") + ) + order.recalculate order.next! until order.can_complete? FactoryBot.create(:order_with_line_items, line_items_price: 40, shipment_cost: 0).tap do |order| FactoryBot.create(:payment, amount: 30, order: order) - promotion.activate(order: order, promotion_code: code) + order.friendly_order_promotions.create!( + order: order, + promotion: promotion, + promotion_code: SolidusFriendlyPromotions::PromotionCode.find_by(value: "discount") + ) + order.recalculate order.next! until order.can_complete? order.complete! end end - it "makes the promotion ineligible" do + it "makes the adjustment disappear" do expect{ order.complete - }.to change{ promo_adjustment.reload.eligible }.to(false) + }.to change { order.all_adjustments.friendly_promotion }.to([]) end it "adjusts the promo_total" do diff --git a/friendly_promotions/spec/spec_helper.rb b/friendly_promotions/spec/spec_helper.rb index 3413d8d94b9..7be29035966 100644 --- a/friendly_promotions/spec/spec_helper.rb +++ b/friendly_promotions/spec/spec_helper.rb @@ -37,6 +37,8 @@ config.before do Spree::Config.order_contents_class = "SolidusFriendlyPromotions::SimpleOrderContents" + Spree::Config.promotion_adjuster_class = "SolidusFriendlyPromotions::OrderPromotionAdjuster" + Spree::Config.promotion_chooser_class = "SolidusFriendlyPromotions::PromotionAdjustmentChooser" end end From aa57816f59f514124e1d1bb88589d0525ae871df Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 6 Jul 2023 14:18:14 +0200 Subject: [PATCH 270/834] Fix promotion code job and mailer --- .../promotion_code_batch_job.rb | 16 ++++----- .../promotion_code_batch_mailer.rb | 12 +++---- .../promotion_code_batch_errored.text.erb | 4 +-- .../promotion_code_batch_finished.text.erb | 2 +- friendly_promotions/config/locales/en.yml | 6 ++-- .../configuration.rb | 2 +- ...ec.rb => promotion_code_batch_job_spec.rb} | 34 +++++++++---------- ...rb => promotion_code_batch_mailer_spec.rb} | 12 +++---- 8 files changed, 44 insertions(+), 44 deletions(-) rename friendly_promotions/spec/jobs/solidus_friendly_promotions/{code_batch_job_spec.rb => promotion_code_batch_job_spec.rb} (62%) rename friendly_promotions/spec/mailers/solidus_friendly_promotions/{code_batch_mailer_spec.rb => promotion_code_batch_mailer_spec.rb} (73%) diff --git a/friendly_promotions/app/jobs/solidus_friendly_promotions/promotion_code_batch_job.rb b/friendly_promotions/app/jobs/solidus_friendly_promotions/promotion_code_batch_job.rb index 7d2f8010928..204bca2f01f 100644 --- a/friendly_promotions/app/jobs/solidus_friendly_promotions/promotion_code_batch_job.rb +++ b/friendly_promotions/app/jobs/solidus_friendly_promotions/promotion_code_batch_job.rb @@ -4,20 +4,20 @@ module SolidusFriendlyPromotions class PromotionCodeBatchJob < ActiveJob::Base queue_as :default - def perform(code_batch) + def perform(promotion_code_batch) PromotionCode::BatchBuilder.new( - code_batch + promotion_code_batch ).build_promotion_codes - if code_batch.email? - SolidusFriendlyPromotions.config.code_batch_mailer_class - .code_batch_finished(code_batch) + if promotion_code_batch.email? + SolidusFriendlyPromotions.config.promotion_code_batch_mailer_class + .promotion_code_batch_finished(promotion_code_batch) .deliver_now end rescue StandardError => error - if code_batch.email? - SolidusFriendlyPromotions.config.code_batch_mailer_class - .code_batch_errored(code_batch) + if promotion_code_batch.email? + SolidusFriendlyPromotions.config.promotion_code_batch_mailer_class + .promotion_code_batch_errored(promotion_code_batch) .deliver_now end raise error diff --git a/friendly_promotions/app/mailers/solidus_friendly_promotions/promotion_code_batch_mailer.rb b/friendly_promotions/app/mailers/solidus_friendly_promotions/promotion_code_batch_mailer.rb index 4ad53b93e92..d5c0e79f4c9 100644 --- a/friendly_promotions/app/mailers/solidus_friendly_promotions/promotion_code_batch_mailer.rb +++ b/friendly_promotions/app/mailers/solidus_friendly_promotions/promotion_code_batch_mailer.rb @@ -2,14 +2,14 @@ module SolidusFriendlyPromotions class PromotionCodeBatchMailer < Spree::BaseMailer - def promotion_code_batch_finished(code_batch) - @code_batch = code_batch - mail(to: code_batch.email) + def promotion_code_batch_finished(promotion_code_batch) + @promotion_code_batch = promotion_code_batch + mail(to: promotion_code_batch.email) end - def promotion_code_batch_errored(code_batch) - @code_batch = code_batch - mail(to: code_batch.email) + def promotion_code_batch_errored(promotion_code_batch) + @promotion_code_batch = promotion_code_batch + mail(to: promotion_code_batch.email) end end end diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/promotion_code_batch_mailer/promotion_code_batch_errored.text.erb b/friendly_promotions/app/views/solidus_friendly_promotions/promotion_code_batch_mailer/promotion_code_batch_errored.text.erb index 21aea738112..bb6b68eb69d 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/promotion_code_batch_mailer/promotion_code_batch_errored.text.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/promotion_code_batch_mailer/promotion_code_batch_errored.text.erb @@ -1,2 +1,2 @@ -<%= t(".message", error: @code_batch.error) %> -<%= @code_batch.promotion.name %> +<%= t(".message", error: @promotion_code_batch.error) %> +<%= @promotion_code_batch.promotion.name %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/promotion_code_batch_mailer/promotion_code_batch_finished.text.erb b/friendly_promotions/app/views/solidus_friendly_promotions/promotion_code_batch_mailer/promotion_code_batch_finished.text.erb index 872138b0c1b..9db0326bb62 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/promotion_code_batch_mailer/promotion_code_batch_finished.text.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/promotion_code_batch_mailer/promotion_code_batch_finished.text.erb @@ -1,2 +1,2 @@ <%= t(".message", number_of_codes: 10) %> -<%= @code_batch.promotion.name %> +<%= @promotion_code_batch.promotion.name %> diff --git a/friendly_promotions/config/locales/en.yml b/friendly_promotions/config/locales/en.yml index 81d13a6461e..5d47b489f33 100644 --- a/friendly_promotions/config/locales/en.yml +++ b/friendly_promotions/config/locales/en.yml @@ -69,11 +69,11 @@ en: starts_at_placeholder: Immediately edit: order_rules: Order Rules - code_batch_mailer: - code_batch_errored: + promotion_code_batch_mailer: + promotion_code_batch_errored: message: 'Promotion code batch errored (%{error}) for promotion: ' subject: Promotion code batch errored - code_batch_finished: + promotion_code_batch_finished: message: 'All %{number_of_codes} codes have been created for promotion: ' subject: Promotion code batch finished activerecord: diff --git a/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb b/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb index 32d5dcaac8c..78a16c3c344 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb @@ -16,7 +16,7 @@ class Configuration < Spree::Preferences::Configuration add_class_set :actions class_name_attribute :promotion_chooser_class, default: "SolidusFriendlyPromotions::PromotionAdjustmentChooser" - class_name_attribute :code_batch_mailer_class, default: "SolidusFriendlyPromotions::CodeBatchMailer" + class_name_attribute :promotion_code_batch_mailer_class, default: "SolidusFriendlyPromotions::PromotionCodeBatchMailer" end class << self diff --git a/friendly_promotions/spec/jobs/solidus_friendly_promotions/code_batch_job_spec.rb b/friendly_promotions/spec/jobs/solidus_friendly_promotions/promotion_code_batch_job_spec.rb similarity index 62% rename from friendly_promotions/spec/jobs/solidus_friendly_promotions/code_batch_job_spec.rb rename to friendly_promotions/spec/jobs/solidus_friendly_promotions/promotion_code_batch_job_spec.rb index bad0f6c2744..0ad6ca3b47a 100644 --- a/friendly_promotions/spec/jobs/solidus_friendly_promotions/code_batch_job_spec.rb +++ b/friendly_promotions/spec/jobs/solidus_friendly_promotions/promotion_code_batch_job_spec.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe SolidusFriendlyPromotions::CodeBatchJob, type: :job do +RSpec.describe SolidusFriendlyPromotions::PromotionCodeBatchJob, type: :job do let(:email) { "test@email.com" } let(:code_batch) do - SolidusFriendlyPromotions::CodeBatch.create!( + SolidusFriendlyPromotions::PromotionCodeBatch.create!( promotion: create(:friendly_promotion), base_code: "test", number_of_codes: 10, @@ -13,13 +13,13 @@ end context "with a successful build" do before do - allow(SolidusFriendlyPromotions::CodeBatchMailer) - .to receive(:code_batch_finished) + allow(SolidusFriendlyPromotions::PromotionCodeBatchMailer) + .to receive(:promotion_code_batch_finished) .and_call_original end def codes - SolidusFriendlyPromotions::Code.pluck(:value) + SolidusFriendlyPromotions::PromotionCode.pluck(:value) end context 'with the default join character' do @@ -32,7 +32,7 @@ def codes end context 'with a custom join character' do let(:code_batch) do - SolidusFriendlyPromotions::CodeBatch.create!( + SolidusFriendlyPromotions::PromotionCodeBatch.create!( promotion: create(:friendly_promotion), base_code: "test", number_of_codes: 10, @@ -50,28 +50,28 @@ def codes context "with an email address" do it "sends an email" do subject.perform(code_batch) - expect(SolidusFriendlyPromotions::CodeBatchMailer) - .to have_received(:code_batch_finished) + expect(SolidusFriendlyPromotions::PromotionCodeBatchMailer) + .to have_received(:promotion_code_batch_finished) end end context "with no email address" do let(:email) { nil } it "sends an email" do subject.perform(code_batch) - expect(SolidusFriendlyPromotions::CodeBatchMailer) - .to_not have_received(:code_batch_finished) + expect(SolidusFriendlyPromotions::PromotionCodeBatchMailer) + .to_not have_received(:promotion_code_batch_finished) end end end context "with a failed build" do before do - allow_any_instance_of(SolidusFriendlyPromotions::Code::BatchBuilder) + allow_any_instance_of(SolidusFriendlyPromotions::PromotionCode::BatchBuilder) .to receive(:build_promotion_codes) .and_raise("Error") - allow(SolidusFriendlyPromotions::CodeBatchMailer) - .to receive(:code_batch_errored) + allow(SolidusFriendlyPromotions::PromotionCodeBatchMailer) + .to receive(:promotion_code_batch_errored) .and_call_original expect { subject.perform(code_batch) } @@ -80,16 +80,16 @@ def codes context "with an email address" do it "sends an email" do - expect(SolidusFriendlyPromotions::CodeBatchMailer) - .to have_received(:code_batch_errored) + expect(SolidusFriendlyPromotions::PromotionCodeBatchMailer) + .to have_received(:promotion_code_batch_errored) end end context "with no email address" do let(:email) { nil } it "sends an email" do - expect(SolidusFriendlyPromotions::CodeBatchMailer) - .to_not have_received(:code_batch_errored) + expect(SolidusFriendlyPromotions::PromotionCodeBatchMailer) + .to_not have_received(:promotion_code_batch_errored) end end end diff --git a/friendly_promotions/spec/mailers/solidus_friendly_promotions/code_batch_mailer_spec.rb b/friendly_promotions/spec/mailers/solidus_friendly_promotions/promotion_code_batch_mailer_spec.rb similarity index 73% rename from friendly_promotions/spec/mailers/solidus_friendly_promotions/code_batch_mailer_spec.rb rename to friendly_promotions/spec/mailers/solidus_friendly_promotions/promotion_code_batch_mailer_spec.rb index 129cd5a19e7..9180710df43 100644 --- a/friendly_promotions/spec/mailers/solidus_friendly_promotions/code_batch_mailer_spec.rb +++ b/friendly_promotions/spec/mailers/solidus_friendly_promotions/promotion_code_batch_mailer_spec.rb @@ -2,10 +2,10 @@ require 'spec_helper' -RSpec.describe SolidusFriendlyPromotions::CodeBatchMailer, type: :mailer do +RSpec.describe SolidusFriendlyPromotions::PromotionCodeBatchMailer, type: :mailer do let(:promotion) { create(:friendly_promotion, name: "Promotion Test") } let(:code_batch) do - SolidusFriendlyPromotions::CodeBatch.create!( + SolidusFriendlyPromotions::PromotionCodeBatch.create!( promotion_id: promotion.id, base_code: "test", number_of_codes: 10, @@ -13,8 +13,8 @@ ) end - describe "#code_batch_finished" do - subject { described_class.code_batch_finished(code_batch) } + describe "#promotion_code_batch_finished" do + subject { described_class.promotion_code_batch_finished(code_batch) } it "sends the email to the email attached to the promotion code batch " do expect(subject.to).to eq([code_batch.email]) @@ -29,9 +29,9 @@ end end - describe "#code_batch_errored" do + describe "#promotion_code_batch_errored" do before { code_batch.update(error: "Test error") } - subject { described_class.code_batch_errored(code_batch) } + subject { described_class.promotion_code_batch_errored(code_batch) } it "sends the email to the email attached to the promotion code batch " do expect(subject.to).to eq([code_batch.email]) From aefece393aece5e01ab84aa3f888749400b9636d Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 6 Jul 2023 14:44:24 +0200 Subject: [PATCH 271/834] Require table_name_prefix in initializer --- .../config/initializers/solidus_friendly_promotions.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/friendly_promotions/config/initializers/solidus_friendly_promotions.rb b/friendly_promotions/config/initializers/solidus_friendly_promotions.rb index e69de29bb2d..d4678cdaad8 100644 --- a/friendly_promotions/config/initializers/solidus_friendly_promotions.rb +++ b/friendly_promotions/config/initializers/solidus_friendly_promotions.rb @@ -0,0 +1 @@ +require_relative SolidusFriendlyPromotions::Engine.root.join("app/models/solidus_friendly_promotions.rb") From d7710810bb6abaaca5d8dd211ca5c72182200713 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 6 Jul 2023 14:44:59 +0200 Subject: [PATCH 272/834] Adapt PromotionRulesController to work with new models --- .../admin/promotion_rules_controller.rb | 6 +++--- .../admin/promotion_rules/_type_select.html.erb | 2 +- .../admin/promotions/_form.html.erb | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb index 3cfe1f55c63..dcebbebc991 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb @@ -12,7 +12,7 @@ class PromotionRulesController < Spree::Admin::BaseController def new if params.dig(:promotion_rule, :type) validate_promotion_rule_type - @promotion_rule = @promotion.promotion_rules.build(type: @promotion_rule_type) + @promotion_rule = @promotion.rules.build(type: @promotion_rule_type) end render layout: false end @@ -50,11 +50,11 @@ def location_after_save end def load_promotion - @promotion = Spree::Promotion.find(params[:promotion_id]) + @promotion = SolidusFriendlyPromotions::Promotion.find(params[:promotion_id]) end def model_class - Spree::PromotionRule + SolidusFriendlyPromotions::PromotionRule end def validate_promotion_rule_type diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/_type_select.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/_type_select.html.erb index 5dd101e1256..223cf88fc65 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/_type_select.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/_type_select.html.erb @@ -1,5 +1,5 @@ <%= form_with( - model: @promotion_rule || Spree::PromotionRule.new(promotion: @promotion), + model: @promotion_rule || SolidusFriendlyPromotions::PromotionRule.new(promotion: @promotion), scope: :promotion_rule, url: solidus_friendly_promotions.new_admin_promotion_promotion_rule_path(@promotion), method: :get diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_form.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_form.html.erb index 5f89e10c3ca..307210a9ee3 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_form.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_form.html.erb @@ -18,9 +18,9 @@ <% end %> <%= f.field_container :category do %> - <%= f.label :category_id, Spree::PromotionCategory.model_name.human %>
    + <%= f.label :promotion_category_id, SolidusFriendlyPromotions::PromotionCategory.model_name.human %>
    <%= - f.collection_select(:category_id, @promotion_categories, :id, :name, { include_blank: t('spree.match_choices.none') }, + f.collection_select(:promotion_category_id, @promotion_categories, :id, :name, { include_blank: t('spree.match_choices.none') }, { class: 'custom-select fullwidth' }) %> <% end %> From 81d9e162937d975d677ae148a66a251701efafdf Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 6 Jul 2023 19:29:02 +0200 Subject: [PATCH 273/834] Moar Giant Commits --- .../admin/promotion_rules_controller.rb | 4 ++-- .../products_promotion_rule.rb | 2 +- .../solidus_friendly_promotions/promotion.rb | 22 +++++++++++++++++++ .../promotion_rule.rb | 4 ++++ .../rules/line_item_product.rb | 4 ++++ .../rules/line_item_taxon.rb | 4 ++++ .../rules/product.rb | 4 ++++ .../rules/store.rb | 4 ++++ .../solidus_friendly_promotions/rules/user.rb | 4 ++++ .../promotion_rules/_promotion_rule.html.erb | 8 +++++++ .../admin/promotion_rules/new.html.erb | 7 ++++++ .../_first_repeat_purchase_since.html.erb | 6 ----- .../rules/_item_total.html.erb | 6 ----- .../rules/_line_item_option_value.html.erb | 6 ----- .../rules/_line_item_product.html.erb | 6 ----- .../rules/_line_item_taxon.html.erb | 6 ----- .../promotion_rules/rules/_nth_order.html.erb | 6 ----- .../rules/_option_value.html.erb | 6 ----- .../promotion_rules/rules/_product.html.erb | 6 ----- .../promotion_rules/rules/_store.html.erb | 6 ----- .../promotion_rules/rules/_taxon.html.erb | 15 ------------- .../promotion_rules/rules/_user.html.erb | 6 ----- .../promotion_rules/rules/_user_role.html.erb | 6 ----- .../admin/promotions/_new_rule_form.html.erb | 18 --------------- .../migrate/20230704083830_add_rule_tables.rb | 2 +- 25 files changed, 65 insertions(+), 103 deletions(-) delete mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_taxon.html.erb delete mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_new_rule_form.html.erb diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb index dcebbebc991..dc19ee5e595 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb @@ -27,7 +27,7 @@ def create end def update - @promotion_rule = @promotion.promotion_rules.find(params[:id]) + @promotion_rule = @promotion.rules.find(params[:id]) @promotion_rule.assign_attributes(promotion_rule_params) if @promotion_rule.save flash[:success] = t('spree.successfully_updated', resource: t('spree.promotion_rule')) @@ -36,7 +36,7 @@ def update end def destroy - @promotion_rule = @promotion.promotion_rules.find(params[:id]) + @promotion_rule = @promotion.rules.find(params[:id]) if @promotion_rule.destroy flash[:success] = t('spree.successfully_removed', resource: t('spree.promotion_rule')) end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/products_promotion_rule.rb b/friendly_promotions/app/models/solidus_friendly_promotions/products_promotion_rule.rb index b4091a1e3ad..dbe45cfb096 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/products_promotion_rule.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/products_promotion_rule.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions class ProductsPromotionRule < Spree::Base - belongs_to :product, class_name: "Spree::Product" + belongs_to :product, class_name: "Spree::Product", optional: true belongs_to :promotion_rule end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb index 9f7e91283d8..dcd5c112d33 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb @@ -64,5 +64,27 @@ def usage_limit_exceeded?(excluded_orders: []) usage_count(excluded_orders: excluded_orders) >= usage_limit end end + + def not_expired? + !expired? + end + + def not_started? + !started? + end + def started? + starts_at.nil? || starts_at < Time.current + end + def active? + started? && not_expired? && actions.present? + end + + def not_expired? + !expired? + end + + def expired? + expires_at.present? && expires_at < Time.current + end end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rule.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rule.rb index 9e083d1bc07..06181f15f24 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rule.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rule.rb @@ -32,6 +32,10 @@ def to_partial_path "solidus_friendly_promotions/admin/promotion_rules/rules/#{model_name.element}" end + def updateable? + preferences.any? + end + private def unique_per_promotion diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb index 732a008c6aa..c6ee3621a60 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb @@ -36,6 +36,10 @@ def product_ids_string=(product_ids) self.product_ids = product_ids.to_s.split(",").map(&:strip) end + def updateable? + true + end + private def inverse? diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb index 13af307926a..57bffa744a0 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb @@ -41,6 +41,10 @@ def taxon_ids_string=(taxon_ids) self.taxons = Spree::Taxon.find(taxon_ids) end + def updateable? + true + end + private # ids of taxons rules and taxons rules children diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb index c8906689faf..28bcf80296b 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb @@ -62,6 +62,10 @@ def product_ids_string=(product_ids) self.product_ids = product_ids.to_s.split(",").map(&:strip) end + def updateable? + true + end + private def order_products(order) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb index 70da23578a6..5e0077c2eb8 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb @@ -17,6 +17,10 @@ def applicable?(promotable) def eligible?(order, _options = {}) stores.none? || stores.include?(order.store) end + + def updateable? + true + end end end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb index 44dd285880b..96ae2e51299 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb @@ -25,6 +25,10 @@ def user_ids_string def user_ids_string=(user_ids) self.user_ids = user_ids.to_s.split(",").map(&:strip) end + + def updateable? + true + end end end end diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/_promotion_rule.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/_promotion_rule.html.erb index a2564b48829..e1c55fcec50 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/_promotion_rule.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/_promotion_rule.html.erb @@ -12,5 +12,13 @@ <%= hidden_field_tag "#{param_prefix}[id]", promotion_rule.id %> <%= render partial: "spree/shared/error_messages", locals: { target: promotion_rule } %> <%= render promotion_rule, promotion_rule: promotion_rule, param_prefix: "promotion_rule", form: form %> + + <% if promotion_rule.updateable? %> +
    +
    + <%= form.submit(t(:update, scope: [:solidus_friendly_promotions, :crud]), class: "btn btn-secondary float-right") %> +
    +
    + <% end %> <% end %>
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/new.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/new.html.erb index 022c47c3e7a..ad0174a57bd 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/new.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/new.html.erb @@ -14,6 +14,13 @@ <%= hidden_field_tag :level, @level %> <%= hidden_field_tag "promotion_rule[type]", @promotion_rule.class.name %> <%= render @promotion_rule, promotion_rule: @promotion_rule, param_prefix: "promotion_rule", form: form %> + +
    +
    + <%= form.submit(t(:add, scope: [:solidus_friendly_promotions, :crud]), class: "btn btn-secondary float-right") %> +
    +
    + <% end %>
    <% end %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_first_repeat_purchase_since.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_first_repeat_purchase_since.html.erb index a19c540b415..8e2e587aab7 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_first_repeat_purchase_since.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_first_repeat_purchase_since.html.erb @@ -11,9 +11,3 @@
    - -
    -
    - <%= form.submit(t((form.object.persisted? ? :update : :add), scope: [:solidus_friendly_promotions, :crud]), class: "btn btn-secondary float-right") %> -
    -
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_item_total.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_item_total.html.erb index 90c9d17c967..1b0767b2ea9 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_item_total.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_item_total.html.erb @@ -12,9 +12,3 @@
    - -
    -
    - <%= form.submit(t((form.object.persisted? ? :update : :add), scope: [:solidus_friendly_promotions, :crud]), class: "btn btn-secondary float-right") %> -
    -
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_line_item_option_value.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_line_item_option_value.html.erb index bedd2b5cb63..e3de3176772 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_line_item_option_value.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_line_item_option_value.html.erb @@ -19,9 +19,3 @@ - -
    -
    - <%= form.submit(t((form.object.persisted? ? :update : :add), scope: [:solidus_friendly_promotions, :crud]), class: "btn btn-secondary float-right") %> -
    -
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_line_item_product.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_line_item_product.html.erb index e2d7ac86d95..2c6f7dd5288 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_line_item_product.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_line_item_product.html.erb @@ -16,9 +16,3 @@ - -
    -
    - <%= form.submit(t((form.object.persisted? ? :update : :add), scope: [:solidus_friendly_promotions, :crud]), class: "btn btn-secondary float-right") %> -
    -
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_line_item_taxon.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_line_item_taxon.html.erb index 14db1d05fc8..1a2dbcf7e48 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_line_item_taxon.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_line_item_taxon.html.erb @@ -12,9 +12,3 @@ ) %> - -
    -
    - <%= form.submit(t((form.object.persisted? ? :update : :add), scope: [:solidus_friendly_promotions, :crud]), class: "btn btn-secondary float-right") %> -
    -
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_nth_order.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_nth_order.html.erb index 2ccb5f0eebc..0a37ce60cf1 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_nth_order.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_nth_order.html.erb @@ -10,9 +10,3 @@ - -
    -
    - <%= form.submit(t((form.object.persisted? ? :update : :add), scope: [:solidus_friendly_promotions, :crud]), class: "btn btn-secondary float-right") %> -
    -
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_option_value.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_option_value.html.erb index bedd2b5cb63..e3de3176772 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_option_value.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_option_value.html.erb @@ -19,9 +19,3 @@ - -
    -
    - <%= form.submit(t((form.object.persisted? ? :update : :add), scope: [:solidus_friendly_promotions, :crud]), class: "btn btn-secondary float-right") %> -
    -
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_product.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_product.html.erb index c0ec650e450..2d11f098832 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_product.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_product.html.erb @@ -13,9 +13,3 @@ - -
    -
    - <%= form.submit(t((form.object.persisted? ? :update : :add), scope: [:solidus_friendly_promotions, :crud]), class: "btn btn-secondary float-right") %> -
    -
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_store.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_store.html.erb index 1018cccf30d..cce8ac2e37f 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_store.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_store.html.erb @@ -4,9 +4,3 @@ options_from_collection_for_select(Spree::Store.all, :id, :name, promotion_rule.store_ids), multiple: true, class: "select2 fullwidth" %> - -
    -
    - <%= form.submit(t((form.object.persisted? ? :update : :add), scope: [:solidus_friendly_promotions, :crud]), class: "btn btn-secondary float-right") %> -
    -
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_taxon.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_taxon.html.erb deleted file mode 100644 index 3df5bf3bde5..00000000000 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_taxon.html.erb +++ /dev/null @@ -1,15 +0,0 @@ -
    - <%= label_tag "#{param_prefix}_taxon_ids_string", t('spree.taxon_rule.choose_taxons') %> - <%= hidden_field_tag "#{param_prefix}[taxon_ids_string]", promotion_rule.taxon_ids.join(","), class: "taxon_picker fullwidth", id: 'product_taxon_ids' %> -
    -
    - -
    - -
    -
    - <%= form.submit(t((form.object.persisted? ? :update : :add), scope: [:solidus_friendly_promotions, :crud]), class: "btn btn-secondary float-right") %> -
    -
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_user.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_user.html.erb index 1f76adf4a52..91391a18fd0 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_user.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_user.html.erb @@ -2,9 +2,3 @@
    - -
    -
    - <%= form.submit(t((form.object.persisted? ? :update : :add), scope: [:solidus_friendly_promotions, :crud]), class: "btn btn-secondary float-right") %> -
    -
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_user_role.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_user_role.html.erb index cd82a42faa1..e078526d08f 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_user_role.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_user_role.html.erb @@ -10,9 +10,3 @@ <%= t('spree.user_role_rule.label', select: select_tag("#{param_prefix}[preferred_match_policy]", options_for_select(Spree::Promotion::Rules::UserRole::MATCH_POLICIES.map{ |s| [t("spree.user_role_rule.match_#{s}"),s] }, promotion_rule.preferred_match_policy), { class: 'custom-select'})).html_safe %> - -
    -
    - <%= form.submit(t((form.object.persisted? ? :update : :add), scope: [:solidus_friendly_promotions, :crud]), class: "btn btn-secondary float-right") %> -
    -
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_new_rule_form.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_new_rule_form.html.erb deleted file mode 100644 index a4ff1196a01..00000000000 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_new_rule_form.html.erb +++ /dev/null @@ -1,18 +0,0 @@ -<%= turbo_frame_tag @promotion, "new_promotion_rule" do %> -
    - <%= form_tag solidus_friendly_promotions.new_admin_promotion_promotion_rule_path(@promotion), method: :get, remote: false do %> - <% if can?(:update, @promotion) %> -
    - <%= label_tag :promotion_rule_type, Spree::PromotionRule.human_attribute_name(:type) %> -
    - <%= select_tag('promotion_rule[type]', options_for_promotion_rule_types(@promotion, level), include_blank: t(:choose_promotion_rule, scope: 'spree'), class: 'custom-select fullwidth', required: true) %> - <%= hidden_field_tag("level", @level) %> -
    - <%= button_tag t('spree.actions.add'), class: 'btn btn-secondary' %> -
    -
    -
    - <% end %> - <% end %> -
    -<% end %> diff --git a/friendly_promotions/db/migrate/20230704083830_add_rule_tables.rb b/friendly_promotions/db/migrate/20230704083830_add_rule_tables.rb index 314b2d8cf4d..bdabd239ccf 100644 --- a/friendly_promotions/db/migrate/20230704083830_add_rule_tables.rb +++ b/friendly_promotions/db/migrate/20230704083830_add_rule_tables.rb @@ -22,7 +22,7 @@ def change end create_table :friendly_promotion_rules_stores do |t| - t.references :store, index: true, null: false, foreign_key: { to_table: :spree_users } + t.references :store, index: true, null: false, foreign_key: { to_table: :spree_stores } t.references :promotion_rule, index: true, null: false t.timestamps From 7a434455f9a2a80525ae63ee2d6fa0e24214cc00 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 6 Jul 2023 20:41:58 +0200 Subject: [PATCH 274/834] Fix Taxon promotion rule --- .../promotion_rules_taxon.rb | 4 ++-- .../solidus_friendly_promotions/rules/taxon.rb | 14 +++++++++----- .../admin/promotion_rules/rules/_taxon.html.erb | 9 +++++++++ .../rules/taxon_spec.rb | 16 ++++++++++++++++ 4 files changed, 36 insertions(+), 7 deletions(-) create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_taxon.html.erb diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rules_taxon.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rules_taxon.rb index d020bdaeb04..976b6644e85 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rules_taxon.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rules_taxon.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions class PromotionRulesTaxon < Spree::Base - belongs_to :promotion_rule - belongs_to :taxon, class_name: "Spree::Taxon" + belongs_to :promotion_rule, class_name: "SolidusFriendlyPromotions::PromotionRule", optional: true + belongs_to :taxon, class_name: "Spree::Taxon", optional: true end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb index a2d4750af0d..38925b555cc 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb @@ -3,8 +3,9 @@ module SolidusFriendlyPromotions module Rules class Taxon < PromotionRule - has_many :promotion_rules_taxons, inverse_of: :promotion_rule, dependent: :destroy - has_many :taxons, through: :promotion_rules_taxons, class_name: "Spree::Taxon" + has_many :promotion_rules_taxons, class_name: 'SolidusFriendlyPromotions::PromotionRulesTaxon', foreign_key: :promotion_rule_id, + dependent: :destroy + has_many :taxons, through: :promotion_rules_taxons, class_name: 'Spree::Taxon' def preload_relations [:taxons] @@ -47,12 +48,15 @@ def eligible?(order, _options = {}) end def taxon_ids_string - taxons.pluck(:id).join(",") + taxon_ids.join(",") end def taxon_ids_string=(taxon_ids) - taxon_ids = taxon_ids.to_s.split(",").map(&:strip) - self.taxons = Spree::Taxon.find(taxon_ids) + self.taxon_ids = taxon_ids.to_s.split(",").map(&:strip) + end + + def updateable? + true end private diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_taxon.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_taxon.html.erb new file mode 100644 index 00000000000..bec9eb7e9c3 --- /dev/null +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_taxon.html.erb @@ -0,0 +1,9 @@ +
    + <%= label_tag "#{param_prefix}_taxon_ids_string", t('spree.taxon_rule.choose_taxons') %> + <%= hidden_field_tag "#{param_prefix}[taxon_ids_string]", promotion_rule.taxon_ids.join(","), class: "taxon_picker fullwidth", id: 'product_taxon_ids' %> +
    +
    + +
    diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/taxon_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/taxon_spec.rb index c3108065d28..4c237479243 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/taxon_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/taxon_spec.rb @@ -14,6 +14,22 @@ SolidusFriendlyPromotions::Rules::Taxon.create!(promotion: create(:friendly_promotion)) end + describe "taxon_ids_string=" do + let!(:promotion) { create(:friendly_promotion) } + let!(:taxon_1) { create(:taxon) } + let!(:taxon_2) { create(:taxon) } + let(:rule) { promotion.rules.build(type: described_class.to_s) } + + subject { rule.assign_attributes("taxon_ids_string" => taxon_2.id.to_s) } + + it "creates a valid rule with a taxon" do + subject + expect(rule).to be_valid + rule.save! + expect(rule.reload.taxons).to include(taxon_2) + end + end + context "#eligible?(order)" do context "with any match policy" do before do From 461afc00d5789219c71798310e93204d83aaf127 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 6 Jul 2023 20:45:31 +0200 Subject: [PATCH 275/834] Fix Promotion Rule User --- .../promotion_rules_user.rb | 4 ++-- .../models/solidus_friendly_promotions/rules/user.rb | 5 ++++- .../solidus_friendly_promotions/rules/user_spec.rb | 12 ++++++++++++ 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rules_user.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rules_user.rb index 91f2f209be8..01661959e33 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rules_user.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rules_user.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions class PromotionRulesUser < Spree::Base - belongs_to :promotion_rule - belongs_to :user, class_name: Spree::UserClassHandle.new + belongs_to :promotion_rule, class_name: "SolidusFriendlyPromotions::PromotionRule", optional: true + belongs_to :user, class_name: Spree::UserClassHandle.new, optional: true end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb index 96ae2e51299..98b2d94c0f0 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb @@ -3,7 +3,10 @@ module SolidusFriendlyPromotions module Rules class User < PromotionRule - has_many :promotion_rules_users, inverse_of: :promotion_rule, dependent: :destroy + has_many :promotion_rules_users, + class_name: 'SolidusFriendlyPromotions::PromotionRulesUser', + foreign_key: :promotion_rule_id, + dependent: :destroy has_many :users, through: :promotion_rules_users, class_name: Spree::UserClassHandle.new def preload_relations diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_spec.rb index 0b78257bb31..7f38bf41da5 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_spec.rb @@ -6,6 +6,18 @@ it { is_expected.to have_many(:users) } let(:rule) { described_class.new } + describe "user_ids=" do + let(:promotion) { create(:friendly_promotion) } + let(:user) { create(:user) } + let(:rule) { promotion.rules.new } + + subject { rule.user_ids = [user.id] } + + it "creates a valid rule with a user" do + expect(rule).to be_valid + end + end + context "#eligible?(order)" do let(:order) { Spree::Order.new } From 77cf7ce510ff889a5c2dd570ad5706d1a91e8f47 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 6 Jul 2023 23:45:46 +0200 Subject: [PATCH 276/834] Tweak PromotionRulesController --- .../admin/promotion_rules_controller.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb index dc19ee5e595..a12f4a1ad56 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb @@ -18,8 +18,9 @@ def new end def create - @promotion_rule = @promotion_rule_type.new(promotion_rule_params) - @promotion_rule.promotion = @promotion + @promotion_rule = @promotion.rules.build( + promotion_rule_params.merge(type: @promotion_rule_type.to_s) + ) if @promotion_rule.save flash[:success] = t('spree.successfully_created', resource: t('spree.promotion_rule')) end @@ -83,7 +84,7 @@ def validate_level end def promotion_rule_params - params[:promotion_rule].permit! + params[:promotion_rule].try(:permit!) || {} end def promotion_rule_types From 2b3524c2e070fc127954daa46d5a3de08f079756 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 6 Jul 2023 23:51:32 +0200 Subject: [PATCH 277/834] Fix Store promotion rule --- .../promotion_rules_store.rb | 4 ++-- .../solidus_friendly_promotions/rules/store.rb | 4 +++- .../rules/store_spec.rb | 15 +++++++++++++++ 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rules_store.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rules_store.rb index 2f6dd6c8a5c..b326a0f23cb 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rules_store.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rules_store.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions class PromotionRulesStore < Spree::Base - belongs_to :promotion_rule - belongs_to :store, class_name: "Spree::Store" + belongs_to :promotion_rule, class_name: "SolidusFriendlyPromotions::PromotionRule", optional: true + belongs_to :store, class_name: "Spree::Store", optional: true end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb index 5e0077c2eb8..3c08784975c 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb @@ -3,7 +3,9 @@ module SolidusFriendlyPromotions module Rules class Store < PromotionRule - has_many :promotion_rules_stores, inverse_of: :promotion_rule, dependent: :destroy + has_many :promotion_rules_stores, class_name: "SolidusFriendlyPromotions::PromotionRulesStore", + foreign_key: :promotion_rule_id, + dependent: :destroy has_many :stores, through: :promotion_rules_stores, class_name: "Spree::Store" def preload_relations diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/store_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/store_spec.rb index 50120abb572..7f4d9d2139b 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/store_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/store_spec.rb @@ -7,6 +7,21 @@ let(:rule) { described_class.new } + describe "store_ids=" do + let!(:promotion) { create(:friendly_promotion) } + let!(:unimportant_store) { create(:store) } + let!(:store) { create(:store) } + let(:rule) { promotion.rules.build(type: described_class.to_s) } + + subject { rule.store_ids = [store.id] } + + it "creates a valid rule with a store" do + subject + expect(rule).to be_valid + expect(rule.stores).to include(store) + end + end + context "#eligible?(order)" do let(:order) { Spree::Order.new } From 13a1ab579e80d80ada4e9d46c2f59aa64cfb5d42 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 6 Jul 2023 23:57:06 +0200 Subject: [PATCH 278/834] Fix promotion rules product --- .../products_promotion_rule.rb | 2 +- .../models/solidus_friendly_promotions/rules/product.rb | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/products_promotion_rule.rb b/friendly_promotions/app/models/solidus_friendly_promotions/products_promotion_rule.rb index dbe45cfb096..2f14e2d648d 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/products_promotion_rule.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/products_promotion_rule.rb @@ -3,6 +3,6 @@ module SolidusFriendlyPromotions class ProductsPromotionRule < Spree::Base belongs_to :product, class_name: "Spree::Product", optional: true - belongs_to :promotion_rule + belongs_to :promotion_rule, class_name: "SolidusFriendlyPromotions::PromotionRule", optional: true end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb index 28bcf80296b..9ae2b78f76d 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb @@ -7,8 +7,12 @@ module Rules # either come from assigned product group or are assingned directly to # the rule. class Product < PromotionRule - has_many :products_promotion_rules, inverse_of: :promotion_rule, dependent: :destroy - has_many :products, class_name: "Spree::Product", through: :products_promotion_rules + has_many :products_promotion_rules, + dependent: :destroy, + foreign_key: :promotion_rule_id, + class_name: 'SolidusFriendlyPromotions::ProductsPromotionRule' + has_many :products, class_name: 'Spree::Product', through: :products_promotion_rules + def preload_relations [:products] From aa0908462d40fc1f63df45f9d7e268505d7bdc97 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 7 Jul 2023 12:10:45 +0200 Subject: [PATCH 279/834] Fix Actions Controller for new actions --- .../admin/promotion_actions_controller.rb | 26 ++++++++--- .../admin/promotion_actions_helper.rb | 2 +- .../admin/promotion_actions/new.html.erb | 2 +- .../admin/promotions/edit.html.erb | 44 ++++++++++--------- 4 files changed, 46 insertions(+), 28 deletions(-) diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb index 0d592ff055d..ccbfbfada7d 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb @@ -3,15 +3,24 @@ module SolidusFriendlyPromotions module Admin class PromotionActionsController < Spree::Admin::BaseController - before_action :load_promotion, only: [:create, :destroy, :new] + before_action :validate_level, only: :new + before_action :load_promotion, only: [:create, :destroy, :new, :update] before_action :validate_promotion_action_type, only: :create def new + if params.dig(:promotion_action, :type) + validate_promotion_action_type + @promotion_action = @promotion.actions.build(type: @promotion_action_type) + + if params.dig(:promotion_action, :calculator_type) + @promotion_action.calculator_type = params[:promotion_action][:calculator_type] + end + end render layout: false end def create - @promotion_action = @promotion_action_type.new(permitted_resource_params) + @promotion_action = @promotion_action_type.new(promotion_action_params) @promotion_action.promotion = @promotion if @promotion_action.save(validate: false) flash[:success] = t('spree.successfully_created', resource: t('spree.promotion_action')) @@ -22,8 +31,8 @@ def create end def update - @promotion_action = @promotion.promotion_actions.find(params[:id]) - @promotion_action.assign_attributes(permitted_resource_params) + @promotion_action = @promotion.actions.find(params[:id]) + @promotion_action.assign_attributes(promotion_action_params) if @promotion_action.save flash[:success] = t('spree.successfully_updated', resource: t('spree.promotion_action')) redirect_to location_after_save, format: :html @@ -33,7 +42,7 @@ def update end def destroy - @promotion_action = @promotion.promotion_actions.find(params[:id]) + @promotion_action = @promotion.actions.find(params[:id]) if @promotion_action.discard flash[:success] = t('spree.successfully_removed', resource: t('spree.promotion_action')) end @@ -47,7 +56,7 @@ def location_after_save end def load_promotion - @promotion = Spree::Promotion.find(params[:promotion_id]) + @promotion = SolidusFriendlyPromotions::Promotion.find(params[:promotion_id]) end def validate_level @@ -60,6 +69,11 @@ def validate_level end end + + def promotion_action_params + params[:promotion_action].try(:permit!) || {} + end + def validate_promotion_action_type requested_type = params[:promotion_action].delete(:type) promotion_action_types = SolidusFriendlyPromotions.config.actions diff --git a/friendly_promotions/app/helpers/solidus_friendly_promotions/admin/promotion_actions_helper.rb b/friendly_promotions/app/helpers/solidus_friendly_promotions/admin/promotion_actions_helper.rb index ba897d8b304..1d97662340d 100644 --- a/friendly_promotions/app/helpers/solidus_friendly_promotions/admin/promotion_actions_helper.rb +++ b/friendly_promotions/app/helpers/solidus_friendly_promotions/admin/promotion_actions_helper.rb @@ -12,7 +12,7 @@ def options_for_promotion_action_calculator_types(promotion_action) def options_for_promotion_action_types(promotion_action) actions = SolidusFriendlyPromotions.config.actions options = actions.map { |action| [action.model_name.human, action.name] } - options_for_select(options, promotion_action.type.to_s) + options_for_select(options, promotion_action&.type&.to_s) end def promotion_actions_by_level(promotion, level) diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/new.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/new.html.erb index b95fe080550..f7ea1bd4cdb 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/new.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/new.html.erb @@ -1,4 +1,4 @@ -<%= turbo_frame_tag @promotion, "new_#{@level}_promotion_action" do %> +<%= turbo_frame_tag @promotion, "new_promotion_action" do %>
    <%= t(:add_action, scope: :solidus_friendly_promotions) %>
    <%= link_to_with_icon 'trash', '', spree.edit_admin_promotion_path(@promotion), class: 'delete' %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/edit.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/edit.html.erb index 97ed6162bf6..34402a4f4cf 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/edit.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/edit.html.erb @@ -40,30 +40,34 @@
    - <% [:line_item, :shipment].each do |level| %> -
    -
    - <%= t("#{level}_actions", scope: :solidus_friendly_promotions) %> - - <%= render partial: 'solidus_friendly_promotions/admin/promotion_actions/promotion_action', collection: promotion_actions_by_level(@promotion, level), locals: {} %> - - <%= turbo_frame_tag @promotion, "new_#{level}_promotion_action" do %> - <%= link_to t(:add_action, scope: :solidus_friendly_promotions), solidus_friendly_promotions.new_admin_promotion_promotion_action_path(@promotion, level: level), class: 'btn btn-secondary' %> - <% end %> -
    +
    + <%= turbo_frame_tag @promotion, "new_promotion_action" do %> + <%= link_to t(:add_action, scope: :solidus_friendly_promotions), solidus_friendly_promotions.new_admin_promotion_promotion_action_path(@promotion), class: 'btn btn-secondary' %> + <% end %>
    -
    +
    + +
    + <% [:line_item, :shipment].each do |level| %> <% if promotion_actions_by_level(@promotion, level).any? %> -
    - <%= t("#{level}_rules", scope: :solidus_friendly_promotions) %> +
    +
    + <%= t("#{level}_actions", scope: :solidus_friendly_promotions) %> - <%= render partial: 'solidus_friendly_promotions/admin/promotion_rules/promotion_rule', collection: promotion_rules_by_level(@promotion, level), locals: { level: level } %> + <%= render partial: 'solidus_friendly_promotions/admin/promotion_actions/promotion_action', collection: promotion_actions_by_level(@promotion, level), locals: {} %> +
    +
    +
    +
    + <%= t("#{level}_rules", scope: :solidus_friendly_promotions) %> - <%= turbo_frame_tag @promotion, "new_#{level}_promotion_rule" do %> - <%= link_to t(:add_rule, scope: :solidus_friendly_promotions), solidus_friendly_promotions.new_admin_promotion_promotion_rule_path(@promotion, level: level), class: 'btn btn-secondary' %> - <% end %> -
    + <%= render partial: 'solidus_friendly_promotions/admin/promotion_rules/promotion_rule', collection: promotion_rules_by_level(@promotion, level), locals: { level: level } %> + + <%= turbo_frame_tag @promotion, "new_#{level}_promotion_rule" do %> + <%= link_to t(:add_rule, scope: :solidus_friendly_promotions), solidus_friendly_promotions.new_admin_promotion_promotion_rule_path(@promotion, level: level), class: 'btn btn-secondary' %> + <% end %> +
    +
    <% end %> -
    <% end %>
    From 257bed8e2e186f8f8e61639620b6de6f0365b90c Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 7 Jul 2023 12:29:29 +0200 Subject: [PATCH 280/834] Fix admin promotions spec --- .../admin/promotions/index.html.erb | 8 ++++---- .../testing_support/friendly_promotion_factory.rb | 10 ---------- .../products_promotion_rule_spec.rb | 4 ++-- .../promotion_rules_store_spec.rb | 4 ++-- .../promotion_rules_taxon_spec.rb | 4 ++-- .../promotion_rules_user_spec.rb | 4 ++-- .../admin/promotions_spec.rb | 14 +++++++------- 7 files changed, 19 insertions(+), 29 deletions(-) diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/index.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/index.html.erb index 4c35e9d766c..8395681c812 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/index.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/index.html.erb @@ -18,28 +18,28 @@
    - <%= label_tag :q_name_cont, Spree::Promotion.human_attribute_name(:name) %> + <%= label_tag :q_name_cont, SolidusFriendlyPromotions::Promotion.human_attribute_name(:name) %> <%= f.text_field :name_cont, tabindex: 1 %>
    - <%= label_tag :q_codes_value_cont, Spree::Promotion.human_attribute_name(:code) %> + <%= label_tag :q_codes_value_cont, SolidusFriendlyPromotions::Promotion.human_attribute_name(:code) %> <%= f.text_field :codes_value_cont, tabindex: 1 %>
    - <%= label_tag :q_path_cont, Spree::Promotion.human_attribute_name(:path) %> + <%= label_tag :q_path_cont, SolidusFriendlyPromotions::Promotion.human_attribute_name(:path) %> <%= f.text_field :path_cont, tabindex: 1 %>
    - <%= label_tag :q_promotion_category_id_eq, Spree::PromotionCategory.model_name.human %>
    + <%= label_tag :q_promotion_category_id_eq, SolidusFriendlyPromotions::PromotionCategory.model_name.human %>
    <%= f.collection_select(:promotion_category_id_eq, @promotion_categories, :id, :name, { include_blank: t('spree.match_choices.all') }, { class: 'custom-select fullwidth' }) %>
    diff --git a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_factory.rb b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_factory.rb index b48f67d86ad..244716876c8 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_factory.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_factory.rb @@ -13,16 +13,6 @@ end end - trait :with_action do - transient do - promotion_action_class { SolidusFriendlyPromotions::Actions::AdjustLineItem } - end - - after(:create) do |promotion, evaluator| - promotion.actions << evaluator.promotion_action_class.new - end - end - trait :with_adjustable_action do transient do preferred_amount { 10 } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/products_promotion_rule_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/products_promotion_rule_spec.rb index fff40dd1c28..c837246e2a6 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/products_promotion_rule_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/products_promotion_rule_spec.rb @@ -3,6 +3,6 @@ require 'spec_helper' RSpec.describe SolidusFriendlyPromotions::ProductsPromotionRule do - it { is_expected.to belong_to(:product) } - it { is_expected.to belong_to(:promotion_rule) } + it { is_expected.to belong_to(:product).optional } + it { is_expected.to belong_to(:promotion_rule).optional } end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_store_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_store_spec.rb index e212e065331..02e47b4cdb5 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_store_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_store_spec.rb @@ -3,6 +3,6 @@ require 'spec_helper' RSpec.describe SolidusFriendlyPromotions::PromotionRulesStore do - it { is_expected.to belong_to(:store) } - it { is_expected.to belong_to(:promotion_rule) } + it { is_expected.to belong_to(:store).optional } + it { is_expected.to belong_to(:promotion_rule).optional } end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_taxon_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_taxon_spec.rb index 9214b152643..b0c06384862 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_taxon_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_taxon_spec.rb @@ -3,6 +3,6 @@ require 'spec_helper' RSpec.describe SolidusFriendlyPromotions::PromotionRulesTaxon do - it { is_expected.to belong_to(:taxon) } - it { is_expected.to belong_to(:promotion_rule) } + it { is_expected.to belong_to(:taxon).optional } + it { is_expected.to belong_to(:promotion_rule).optional } end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_user_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_user_spec.rb index e7003c49341..981cc07d43a 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_user_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_user_spec.rb @@ -3,6 +3,6 @@ require 'spec_helper' RSpec.describe SolidusFriendlyPromotions::PromotionRulesUser do - it { is_expected.to belong_to(:user) } - it { is_expected.to belong_to(:promotion_rule) } + it { is_expected.to belong_to(:user).optional } + it { is_expected.to belong_to(:promotion_rule).optional } end diff --git a/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotions_spec.rb b/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotions_spec.rb index d73fc22bd13..c12e2cc1d63 100644 --- a/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotions_spec.rb +++ b/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotions_spec.rb @@ -6,10 +6,10 @@ stub_authorization! describe "#index" do - let!(:promotion1) { create(:friendly_promotion, :with_action, name: "name1", code: "code1", path: "path1") } - let!(:promotion2) { create(:friendly_promotion, :with_action, name: "name2", code: "code2", path: "path2") } + let!(:promotion1) { create(:friendly_promotion, :with_adjustable_action, name: "name1", code: "code1", path: "path1") } + let!(:promotion2) { create(:friendly_promotion, :with_adjustable_action, name: "name2", code: "code2", path: "path2") } let!(:promotion3) do - create(:friendly_promotion, :with_action, name: "name3", code: "code3", path: "path3", expires_at: Date.yesterday) + create(:friendly_promotion, :with_adjustable_action, name: "name3", code: "code3", path: "path3", expires_at: Date.yesterday) end let!(:category) { create :friendly_promotion_category } @@ -64,12 +64,12 @@ visit solidus_friendly_promotions.admin_promotions_path click_link "New Promotion" expect(page).to have_field("Name") - expect(page).to have_field("Start") - expect(page).to have_field("End") + expect(page).to have_field("Starts at") + expect(page).to have_field("Expires at") expect(page).to have_field("Description") fill_in("Name", with: "20 percent off") - fill_in("Start", with: Time.current) - fill_in("End", with: 1.week.from_now) + fill_in("Starts at", with: Time.current) + fill_in("Expires at", with: 1.week.from_now) choose("Apply to all orders") click_button("Create") expect(page).to have_content("20 percent off") From 031a8a1a4294f8b63ac052092b2b65d5d64a44d7 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 7 Jul 2023 14:11:54 +0200 Subject: [PATCH 281/834] add more promotion interface --- .../solidus_friendly_promotions/promotion.rb | 30 ++ .../promotion_action.rb | 2 +- .../promotion_spec.rb | 458 ++++++++++++++++++ 3 files changed, 489 insertions(+), 1 deletion(-) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb index dcd5c112d33..362ee580158 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb @@ -13,8 +13,11 @@ class Promotion < Spree::Base validates :usage_limit, numericality: { greater_than: 0, allow_nil: true } validates :per_code_usage_limit, numericality: { greater_than_or_equal_to: 0, allow_nil: true } validates :description, length: { maximum: 255 } + validate :apply_automatically_disallowed_with_paths scope :active, -> { has_actions.started_and_unexpired } + scope :advertised, -> { where(advertise: true) } + scope :coupons, -> { joins(:codes).distinct } scope :started_and_unexpired, -> do table = arel_table time = Time.current @@ -55,6 +58,15 @@ def usage_count(excluded_orders: []) count end + def used_by?(user, excluded_orders = []) + discounted_orders. + complete. + where.not(id: excluded_orders.map(&:id)). + where(user: user). + where.not(spree_orders: { state: :canceled }). + exists? + end + # Whether the promotion has exceeded its usage restrictions. # # @param excluded_orders [Array] Orders to exclude from usage limit @@ -72,13 +84,19 @@ def not_expired? def not_started? !started? end + def started? starts_at.nil? || starts_at < Time.current end + def active? started? && not_expired? && actions.present? end + def inactive? + !active? + end + def not_expired? !expired? end @@ -86,5 +104,17 @@ def not_expired? def expired? expires_at.present? && expires_at < Time.current end + + def products + rules.where(type: "SolidusFriendlyPromotions::Rules::Product").flat_map(&:products).uniq + end + + private + + def apply_automatically_disallowed_with_paths + return unless apply_automatically + + errors.add(:apply_automatically, :disallowed_with_path) if path.present? + end end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb index 265a42c4ee0..ae0b075ca5b 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb @@ -13,7 +13,7 @@ class PromotionAction < Spree::Base include Spree::AdjustmentSource belongs_to :promotion, inverse_of: :actions - has_many :adjustments, as: :source + has_many :adjustments, class_name: "Spree::Adjustment", as: :source scope :of_type, ->(type) { where(type: Array.wrap(type).map(&:to_s)) } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb index 7a0e1ce116d..b60bcb9e93a 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb @@ -30,4 +30,462 @@ expect(@valid_promotion).not_to be_valid end end + + describe ".advertised" do + let(:promotion) { create(:friendly_promotion) } + let(:advertised_promotion) { create(:friendly_promotion, advertise: true) } + + it "only shows advertised promotions" do + advertised = described_class.advertised + expect(advertised).to include(advertised_promotion) + expect(advertised).not_to include(promotion) + end + end + + describe ".coupons" do + let(:promotion_code) { create(:friendly_promotion_code) } + let!(:promotion_with_code) { promotion_code.promotion } + let!(:another_promotion_code) { create(:friendly_promotion_code, promotion: promotion_with_code) } + let!(:promotion_without_code) { create(:friendly_promotion) } + + subject { described_class.coupons } + + it "returns only distinct promotions with a code associated" do + expect(subject).to eq [promotion_with_code] + end + end + + describe '.active' do + subject { described_class.active } + + let(:promotion) { create(:friendly_promotion, starts_at: Date.yesterday, name: "name1") } + + before { promotion } + + it "doesn't return promotion without actions" do + expect(subject).to be_empty + end + + context 'when promotion has an action' do + let(:promotion) { create(:friendly_promotion, :with_adjustable_action, starts_at: Date.yesterday, name: "name1") } + + it 'returns promotion with action' do + expect(subject).to match [promotion] + end + end + end + + describe '.has_actions' do + subject { described_class.has_actions } + + let(:promotion) { create(:friendly_promotion, starts_at: Date.yesterday, name: "name1") } + + before { promotion } + + it "doesn't return promotion without actions" do + expect(subject).to be_empty + end + + context 'when promotion has two actions' do + let(:promotion) { create(:friendly_promotion, :with_adjustable_action, starts_at: Date.yesterday, name: "name1") } + + before do + promotion.actions << SolidusFriendlyPromotions::Actions::AdjustShipment.new(calculator: SolidusFriendlyPromotions::Calculators::Percent.new) + end + + it 'returns distinct promotion' do + expect(subject).to match [promotion] + end + end + end + + describe "#apply_automatically" do + subject { build(:friendly_promotion) } + + it "defaults to false" do + expect(subject.apply_automatically).to eq(false) + end + + context "when set to true" do + before { subject.apply_automatically = true } + + it "should remain valid" do + expect(subject).to be_valid + end + + it "invalidates the promotion when it has a path" do + subject.path = "foo" + expect(subject).to_not be_valid + expect(subject.errors).to include(:apply_automatically) + end + end + end + + describe "#usage_limit_exceeded?" do + subject { promotion.usage_limit_exceeded? } + + shared_examples "it should" do + context "when there is a usage limit" do + context "and the limit is not exceeded" do + let(:usage_limit) { 10 } + it { is_expected.to be_falsy } + end + context "and the limit is exceeded" do + let(:usage_limit) { 1 } + context "on a different order" do + before do + FactoryBot.create( + :completed_order_with_friendly_promotion, + promotion: promotion + ) + promotion.actions.first.adjustments.update_all(eligible: true) + end + it { is_expected.to be_truthy } + end + context "on the same order" do + it { is_expected.to be_falsy } + end + end + end + context "when there is no usage limit" do + let(:usage_limit) { nil } + it { is_expected.to be_falsy } + end + end + + context "with an item-level adjustment" do + let(:promotion) do + FactoryBot.create( + :friendly_promotion, + :with_line_item_adjustment, + code: "discount", + usage_limit: usage_limit + ) + end + before do + order.friendly_order_promotions.create( + promotion_code: promotion.codes.first, + promotion: promotion + ) + order.recalculate + end + context "when there are multiple line items" do + let(:order) { FactoryBot.create(:order_with_line_items, line_items_count: 2) } + describe "the first item" do + let(:promotable) { order.line_items.first } + it_behaves_like "it should" + end + describe "the second item" do + let(:promotable) { order.line_items.last } + it_behaves_like "it should" + end + end + context "when there is a single line item" do + let(:order) { FactoryBot.create(:order_with_line_items) } + let(:promotable) { order.line_items.first } + it_behaves_like "it should" + end + end + end + + describe "#usage_count" do + let(:promotion) do + FactoryBot.create( + :friendly_promotion, + :with_line_item_adjustment, + code: "discount" + ) + end + + subject { promotion.usage_count } + + context "when the code is applied to a non-complete order" do + let(:order) { FactoryBot.create(:order_with_line_items) } + before do + order.friendly_order_promotions.create( + promotion_code: promotion.codes.first, + promotion: promotion + ) + order.recalculate + end + it { is_expected.to eq 0 } + end + context "when the code is applied to a complete order" do + let!(:order) do + FactoryBot.create( + :completed_order_with_friendly_promotion, + promotion: promotion + ) + end + context "and the promo is eligible" do + it { is_expected.to eq 1 } + end + context "and the promo is ineligible" do + before { order.all_adjustments.friendly_promotion.update_all(eligible: false) } + it { is_expected.to eq 0 } + end + context "and the order is canceled" do + before { order.cancel! } + it { is_expected.to eq 0 } + it { expect(order.state).to eq 'canceled' } + end + end + end + + + context "#inactive" do + let(:promotion) { create(:friendly_promotion, :with_adjustable_action) } + + it "should not be exipired" do + expect(promotion).not_to be_inactive + end + + it "should be inactive if it hasn't started yet" do + promotion.starts_at = Time.current + 1.day + expect(promotion).to be_inactive + end + + it "should be inactive if it has already ended" do + promotion.expires_at = Time.current - 1.day + expect(promotion).to be_inactive + end + + it "should not be inactive if it has started already" do + promotion.starts_at = Time.current - 1.day + expect(promotion).not_to be_inactive + end + + it "should not be inactive if it has not ended yet" do + promotion.expires_at = Time.current + 1.day + expect(promotion).not_to be_inactive + end + + it "should not be inactive if current time is within starts_at and expires_at range" do + promotion.starts_at = Time.current - 1.day + promotion.expires_at = Time.current + 1.day + expect(promotion).not_to be_inactive + end + end + + describe '#not_started?' do + let(:promotion) { SolidusFriendlyPromotions::Promotion.new(starts_at: starts_at) } + subject { promotion.not_started? } + + context 'no starts_at date' do + let(:starts_at) { nil } + it { is_expected.to be_falsey } + end + + context 'when starts_at date is in the past' do + let(:starts_at) { Time.current - 1.day } + it { is_expected.to be_falsey } + end + + context 'when starts_at date is not already reached' do + let(:starts_at) { Time.current + 1.day } + it { is_expected.to be_truthy } + end + end + + describe '#started?' do + let(:promotion) { SolidusFriendlyPromotions::Promotion.new(starts_at: starts_at) } + subject { promotion.started? } + + context 'when no starts_at date' do + let(:starts_at) { nil } + it { is_expected.to be_truthy } + end + + context 'when starts_at date is in the past' do + let(:starts_at) { Time.current - 1.day } + it { is_expected.to be_truthy } + end + + context 'when starts_at date is not already reached' do + let(:starts_at) { Time.current + 1.day } + it { is_expected.to be_falsey } + end + end + + describe '#expired?' do + let(:promotion) { SolidusFriendlyPromotions::Promotion.new(expires_at: expires_at) } + subject { promotion.expired? } + + context 'when no expires_at date' do + let(:expires_at) { nil } + it { is_expected.to be_falsey } + end + + context 'when expires_at date is not already reached' do + let(:expires_at) { Time.current + 1.day } + it { is_expected.to be_falsey } + end + + context 'when expires_at date is in the past' do + let(:expires_at) { Time.current - 1.day } + it { is_expected.to be_truthy } + end + end + + describe '#not_expired?' do + let(:promotion) { SolidusFriendlyPromotions::Promotion.new(expires_at: expires_at) } + subject { promotion.not_expired? } + + context 'when no expired_at date' do + let(:expires_at) { nil } + it { is_expected.to be_truthy } + end + + context 'when expires_at date is not already reached' do + let(:expires_at) { Time.current + 1.day } + it { is_expected.to be_truthy } + end + + context 'when expires_at date is in the past' do + let(:expires_at) { Time.current - 1.day } + it { is_expected.to be_falsey } + end + end + + context "#active" do + it "shouldn't be active if it has started already" do + promotion.starts_at = Time.current - 1.day + expect(promotion.active?).to eq(false) + end + + it "shouldn't be active if it has not ended yet" do + promotion.expires_at = Time.current + 1.day + expect(promotion.active?).to eq(false) + end + + it "shouldn't be active if current time is within starts_at and expires_at range" do + promotion.starts_at = Time.current - 1.day + promotion.expires_at = Time.current + 1.day + expect(promotion.active?).to eq(false) + end + + it "shouldn't be active if there are no start and end times set" do + promotion.starts_at = nil + promotion.expires_at = nil + expect(promotion.active?).to eq(false) + end + + context 'when promotion has an action' do + let(:promotion) { create(:promotion, :with_action, name: "name1") } + + it "should be active if it has started already" do + promotion.starts_at = Time.current - 1.day + expect(promotion.active?).to eq(true) + end + + it "should be active if it has not ended yet" do + promotion.expires_at = Time.current + 1.day + expect(promotion.active?).to eq(true) + end + + it "should be active if current time is within starts_at and expires_at range" do + promotion.starts_at = Time.current - 1.day + promotion.expires_at = Time.current + 1.day + expect(promotion.active?).to eq(true) + end + + it "should be active if there are no start and end times set" do + promotion.starts_at = nil + promotion.expires_at = nil + expect(promotion.active?).to eq(true) + end + end + end + + context "#products" do + let(:promotion) { create(:friendly_promotion) } + + context "when it has product rules with products associated" do + let(:promotion_rule) { SolidusFriendlyPromotions::Rules::Product.new } + + before do + promotion_rule.promotion = promotion + promotion_rule.products << create(:product) + promotion_rule.save + end + + it "should have products" do + expect(promotion.reload.products.size).to eq(1) + end + end + + context "when there's no product rule associated" do + it "should not have products but still return an empty array" do + expect(promotion.products).to be_blank + end + end + end + + # regression for https://github.com/spree/spree/issues/4059 + # admin form posts the code and path as empty string + describe "normalize blank values for path" do + it "will save blank value as nil value instead" do + promotion = Spree::Promotion.create(name: "A promotion", path: "") + expect(promotion.path).to be_nil + end + end + + describe '#used_by?' do + subject { promotion.used_by? user, [excluded_order] } + + let(:promotion) { create :friendly_promotion, :with_adjustable_action } + let(:user) { create :user } + let(:order) { create :order_with_line_items, user: user } + let(:excluded_order) { create :order_with_line_items, user: user } + + before do + order.user_id = user.id + order.save! + end + + context 'when the user has used this promo' do + before do + order.friendly_order_promotions.create( + promotion: promotion + ) + order.recalculate + order.completed_at = Time.current + order.save! + end + + context 'when the order is complete' do + it { is_expected.to be true } + + context 'when the promotion was not eligible' do + let(:adjustment) { order.all_adjustments.first } + + before do + adjustment.eligible = false + adjustment.save! + end + + it { is_expected.to be false } + end + + context 'when the only matching order is the excluded order' do + let(:excluded_order) { order } + it { is_expected.to be false } + end + end + + context 'when the order is not complete' do + let(:order) { create :order, user: user } + + # The before clause above sets the completed at + # value for this order + before { order.update completed_at: nil } + + it { is_expected.to be false } + end + end + + context 'when the user has not used this promo' do + it { is_expected.to be false } + end + end end From e0f225bffd3a7d2d551e128bf480ce9b5f16e933 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 7 Jul 2023 16:11:25 +0200 Subject: [PATCH 282/834] Several more fixes Remove unused JS and CSS Fix Promotion Codes Controllers --- .../backend/solidus_friendly_promotions.js | 2 -- .../frontend/solidus_friendly_promotions.js | 2 -- .../backend/solidus_friendly_promotions.css | 4 ---- .../frontend/solidus_friendly_promotions.css | 4 ---- .../admin/promotion_actions_controller.rb | 10 +++++----- .../admin/promotion_code_batches_controller.rb | 4 ++-- .../admin/promotion_codes_controller.rb | 18 +++++++++--------- .../admin/promotion_rules_controller.rb | 10 +++++----- .../order_decorator.rb | 2 +- .../promotion_action.rb | 4 ++-- .../_calculator_select.html.erb | 2 +- .../promotion_actions/_type_select.html.erb | 4 ++-- .../admin/promotion_actions/new.html.erb | 2 +- .../promotion_rules/_type_select.html.erb | 2 +- friendly_promotions/config/locales/en.yml | 10 ++++++++++ 15 files changed, 39 insertions(+), 41 deletions(-) delete mode 100644 friendly_promotions/app/assets/javascripts/spree/backend/solidus_friendly_promotions.js delete mode 100644 friendly_promotions/app/assets/javascripts/spree/frontend/solidus_friendly_promotions.js delete mode 100644 friendly_promotions/app/assets/stylesheets/spree/backend/solidus_friendly_promotions.css delete mode 100644 friendly_promotions/app/assets/stylesheets/spree/frontend/solidus_friendly_promotions.css diff --git a/friendly_promotions/app/assets/javascripts/spree/backend/solidus_friendly_promotions.js b/friendly_promotions/app/assets/javascripts/spree/backend/solidus_friendly_promotions.js deleted file mode 100644 index 8aa3b014409..00000000000 --- a/friendly_promotions/app/assets/javascripts/spree/backend/solidus_friendly_promotions.js +++ /dev/null @@ -1,2 +0,0 @@ -// Placeholder manifest file. -// the installer will append this file to the app vendored assets here: vendor/assets/javascripts/spree/backend/all.js' \ No newline at end of file diff --git a/friendly_promotions/app/assets/javascripts/spree/frontend/solidus_friendly_promotions.js b/friendly_promotions/app/assets/javascripts/spree/frontend/solidus_friendly_promotions.js deleted file mode 100644 index a79f2e948dd..00000000000 --- a/friendly_promotions/app/assets/javascripts/spree/frontend/solidus_friendly_promotions.js +++ /dev/null @@ -1,2 +0,0 @@ -// Placeholder manifest file. -// the installer will append this file to the app vendored assets here: vendor/assets/javascripts/spree/frontend/all.js' \ No newline at end of file diff --git a/friendly_promotions/app/assets/stylesheets/spree/backend/solidus_friendly_promotions.css b/friendly_promotions/app/assets/stylesheets/spree/backend/solidus_friendly_promotions.css deleted file mode 100644 index e3c236629e3..00000000000 --- a/friendly_promotions/app/assets/stylesheets/spree/backend/solidus_friendly_promotions.css +++ /dev/null @@ -1,4 +0,0 @@ -/* -Placeholder manifest file. -the installer will append this file to the app vendored assets here: 'vendor/assets/stylesheets/spree/backend/all.css' -*/ diff --git a/friendly_promotions/app/assets/stylesheets/spree/frontend/solidus_friendly_promotions.css b/friendly_promotions/app/assets/stylesheets/spree/frontend/solidus_friendly_promotions.css deleted file mode 100644 index da236237c52..00000000000 --- a/friendly_promotions/app/assets/stylesheets/spree/frontend/solidus_friendly_promotions.css +++ /dev/null @@ -1,4 +0,0 @@ -/* -Placeholder manifest file. -the installer will append this file to the app vendored assets here: 'vendor/assets/stylesheets/spree/frontend/all.css' -*/ diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb index ccbfbfada7d..8581e38a7ba 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb @@ -23,7 +23,7 @@ def create @promotion_action = @promotion_action_type.new(promotion_action_params) @promotion_action.promotion = @promotion if @promotion_action.save(validate: false) - flash[:success] = t('spree.successfully_created', resource: t('spree.promotion_action')) + flash[:success] = t('spree.successfully_created', resource: SolidusFriendlyPromotions::PromotionAction.model_name.human) redirect_to location_after_save, format: :html else render :new, layout: false @@ -34,7 +34,7 @@ def update @promotion_action = @promotion.actions.find(params[:id]) @promotion_action.assign_attributes(promotion_action_params) if @promotion_action.save - flash[:success] = t('spree.successfully_updated', resource: t('spree.promotion_action')) + flash[:success] = t('spree.successfully_updated', resource: SolidusFriendlyPromotions::PromotionAction.model_name.human) redirect_to location_after_save, format: :html else render :edit @@ -44,7 +44,7 @@ def update def destroy @promotion_action = @promotion.actions.find(params[:id]) if @promotion_action.discard - flash[:success] = t('spree.successfully_removed', resource: t('spree.promotion_action')) + flash[:success] = t('spree.successfully_removed', resource: SolidusFriendlyPromotions::PromotionAction.model_name.human) end redirect_to location_after_save, format: :html end @@ -81,9 +81,9 @@ def validate_promotion_action_type klass.name == requested_type end if !@promotion_action_type - flash[:error] = t('spree.invalid_promotion_action') + flash[:error] = t('solidus_friendly_promotions.invalid_promotion_action') respond_to do |format| - format.html { redirect_to spree.edit_admin_promotion_path(@promotion) } + format.html { redirect_to solidus_friendly_promotions.edit_admin_promotion_path(@promotion) } format.js { render layout: false } end end diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_code_batches_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_code_batches_controller.rb index 850c9ce772f..44cea4fde55 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_code_batches_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_code_batches_controller.rb @@ -3,14 +3,14 @@ module SolidusFriendlyPromotions module Admin class PromotionCodeBatchesController < Spree::Admin::ResourceController - belongs_to 'spree/promotion' + belongs_to 'solidus_friendly_promotions/promotion' create.after :build_promotion_code_batch def download require "csv" - @promotion_code_batch = Spree::PromotionCodeBatch.find( + @promotion_code_batch = SolidusFriendlyPromotions::PromotionCodeBatch.find( params[:promotion_code_batch_id] ) diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_codes_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_codes_controller.rb index 5cf5628f98c..42b857d0fbe 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_codes_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_codes_controller.rb @@ -6,8 +6,8 @@ module SolidusFriendlyPromotions module Admin class PromotionCodesController < Spree::Admin::ResourceController def index - @promotion = Spree::Promotion.accessible_by(current_ability, :show).find(params[:promotion_id]) - @promotion_codes = @promotion.promotion_codes.order(:value) + @promotion = SolidusFriendlyPromotions::Promotion.accessible_by(current_ability, :show).find(params[:promotion_id]) + @promotion_codes = @promotion.codes.order(:value) respond_to do |format| format.html do @@ -22,22 +22,22 @@ def index end def new - @promotion = Spree::Promotion.accessible_by(current_ability, :show).find(params[:promotion_id]) + @promotion = SolidusFriendlyPromotions::Promotion.accessible_by(current_ability, :show).find(params[:promotion_id]) if @promotion.apply_automatically - flash[:error] = t('activerecord.errors.models.spree/promotion_code.attributes.base.disallowed_with_apply_automatically') - redirect_to admin_promotion_promotion_codes_url(@promotion) + flash[:error] = t('activerecord.errors.models.solidus_friendly_promotions/promotion_code.attributes.base.disallowed_with_apply_automatically') + redirect_to solidus_friendly_promotions.admin_promotion_promotion_codes_url(@promotion) else - @promotion_code = @promotion.promotion_codes.build + @promotion_code = @promotion.codes.build end end def create - @promotion = Spree::Promotion.accessible_by(current_ability, :show).find(params[:promotion_id]) - @promotion_code = @promotion.promotion_codes.build(value: params[:promotion_code][:value]) + @promotion = SolidusFriendlyPromotions::Promotion.accessible_by(current_ability, :show).find(params[:promotion_id]) + @promotion_code = @promotion.codes.build(value: params[:promotion_code][:value]) if @promotion_code.save flash[:success] = flash_message_for(@promotion_code, :successfully_created) - redirect_to admin_promotion_promotion_codes_url(@promotion) + redirect_to solidus_friendly_promotions.admin_promotion_promotion_codes_url(@promotion) else flash.now[:error] = @promotion_code.errors.full_messages.to_sentence render_after_create_error diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb index a12f4a1ad56..9f0cb2c43d4 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb @@ -22,7 +22,7 @@ def create promotion_rule_params.merge(type: @promotion_rule_type.to_s) ) if @promotion_rule.save - flash[:success] = t('spree.successfully_created', resource: t('spree.promotion_rule')) + flash[:success] = t('spree.successfully_created', resource: SolidusFriendlyPromotions::PromotionRule.model_name.human) end redirect_to location_after_save end @@ -31,7 +31,7 @@ def update @promotion_rule = @promotion.rules.find(params[:id]) @promotion_rule.assign_attributes(promotion_rule_params) if @promotion_rule.save - flash[:success] = t('spree.successfully_updated', resource: t('spree.promotion_rule')) + flash[:success] = t('spree.successfully_updated', resource: SolidusFriendlyPromotions::PromotionRule.model_name.human) end redirect_to location_after_save end @@ -39,7 +39,7 @@ def update def destroy @promotion_rule = @promotion.rules.find(params[:id]) if @promotion_rule.destroy - flash[:success] = t('spree.successfully_removed', resource: t('spree.promotion_rule')) + flash[:success] = t('spree.successfully_removed', resource: SolidusFriendlyPromotions::PromotionRule.model_name.human) end redirect_to location_after_save end @@ -65,9 +65,9 @@ def validate_promotion_rule_type klass.name == requested_type end if !@promotion_rule_type - flash[:error] = t('spree.invalid_promotion_rule') + flash[:error] = t('solidus_friendly_promotions.invalid_promotion_rule') respond_to do |format| - format.html { redirect_to spree.edit_admin_promotion_path(@promotion) } + format.html { redirect_to solidus_friendly_promotions.edit_admin_promotion_path(@promotion) } format.js { render layout: false } end end diff --git a/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_decorator.rb b/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_decorator.rb index 29d97327d9b..e1b4e4d1d8e 100644 --- a/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_decorator.rb +++ b/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_decorator.rb @@ -11,7 +11,7 @@ def ensure_promotions_eligible if promo_total_changed? restart_checkout_flow recalculate - errors.add(:base, I18n.t('spree.promotion_total_changed_before_complete')) + errors.add(:base, I18n.t('solidus_friendly_promotions.promotion_total_changed_before_complete')) end super diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb index ae0b075ca5b..b5676cf3885 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb @@ -42,8 +42,8 @@ def compute_amount(adjustable) def adjustment_label(adjustable) I18n.t( - "spree.adjustment_labels.#{adjustable.class.name.demodulize.underscore}", - promotion: Spree::Promotion.model_name.human, + "solidus_friendly_promotions.adjustment_labels.#{adjustable.class.name.demodulize.underscore}", + promotion: SolidusFriendlyPromotions::Promotion.model_name.human, promotion_name: promotion.name, ) end diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/_calculator_select.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/_calculator_select.html.erb index 90cf01e4eac..17315241c0c 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/_calculator_select.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/_calculator_select.html.erb @@ -6,7 +6,7 @@ form.select :calculator_type, options_for_promotion_action_calculator_types(form.object), { - include_blank: t(:choose_promotion_action_calculator, scope: 'solidus_friendly_shipping') + include_blank: t(:choose_promotion_action_calculator, scope: 'solidus_friendly_promotions') }, class: 'custom-select fullwidth', onchange: 'this.form.requestSubmit()', diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/_type_select.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/_type_select.html.erb index 2823d3ab3b9..137858065cd 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/_type_select.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/_type_select.html.erb @@ -1,11 +1,11 @@ <%= form_with model: @promotion_action, scope: :promotion_action, url: solidus_friendly_promotions.new_admin_promotion_promotion_action_path(@promotion), method: :get do |form| %> <%= form.label :type %> - <%= admin_hint t('spree.adjustment_type'), t(:promotions, scope: [:spree, :hints, "spree/calculator"]) %> + <%= admin_hint t('solidus_friendly_promotions.adjustment_type'), t(:promotions, scope: [:solidus_friendly_promotions, :hints, "solidus_friendly_promotions/calculator"]) %> <%= form.select :type, options_for_promotion_action_types(form.object), { - include_blank: t(:choose_promotion_action, scope: 'solidus_friendly_shipping') + include_blank: t(:choose_promotion_action, scope: 'solidus_friendly_promotions') }, class: 'custom-select fullwidth', onchange: 'this.form.requestSubmit()', diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/new.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/new.html.erb index f7ea1bd4cdb..8a251db50a7 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/new.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/new.html.erb @@ -1,7 +1,7 @@ <%= turbo_frame_tag @promotion, "new_promotion_action" do %>
    <%= t(:add_action, scope: :solidus_friendly_promotions) %>
    - <%= link_to_with_icon 'trash', '', spree.edit_admin_promotion_path(@promotion), class: 'delete' %> + <%= link_to_with_icon 'trash', '', solidus_friendly_promotions.edit_admin_promotion_path(@promotion), class: 'delete' %> <%= render 'type_select' %> <% if @promotion_action.respond_to?(:calculator) %> <%= render 'calculator_select', path: solidus_friendly_promotions.new_admin_promotion_promotion_action_path(@promotion), promotion_action: @promotion_action %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/_type_select.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/_type_select.html.erb index 223cf88fc65..8e049733d25 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/_type_select.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/_type_select.html.erb @@ -11,7 +11,7 @@ form.select :type, options_for_promotion_rule_types(form.object, level), { - include_blank: t(:choose_promotion_rule, scope: 'solidus_friendly_shipping') + include_blank: t(:choose_promotion_rule, scope: 'solidus_friendly_promotions') }, class: 'custom-select fullwidth', onchange: 'this.form.requestSubmit()', diff --git a/friendly_promotions/config/locales/en.yml b/friendly_promotions/config/locales/en.yml index 5d47b489f33..33920c0e76a 100644 --- a/friendly_promotions/config/locales/en.yml +++ b/friendly_promotions/config/locales/en.yml @@ -4,6 +4,10 @@ en: solidus_friendly_promotions: actions: Actions + adjustment_labels: + line_item: "%{promotion} (%{promotion_name})" + shipment: "%{promotion} (%{promotion_name})" + adjustment_type: Adjustment type add_action: New Action add_rule: New Rule order_rules: Order Rules @@ -12,6 +16,8 @@ en: line_item_actions: Line Item Actions shipment_actions: Shipment Actions invalid_promotion_rule_level: Invalid Promotion Rule Level. Must be one of "order", "shipment", or "line_item" + invalid_promotion_action: Invalid promotion action. + invalid_promotion_rule: Invalid promotion rule. create_promotion_code: Create promotion code current_promotion_usage: 'Current Usage: %{count}' discount_rules: Promotion Rules @@ -21,6 +27,7 @@ en: new_promotion_code_batch: New Promotion Code Batch no_rules_addes: No Rules Added promotion_successfully_created: Promotion has been successfully created! + promotion_total_changed_before_complete: One or more of the promotions on your order have become ineligible and were removed. Please check the new order amounts and try again. view_promotion_codes_list: View codes list promotion_rules: line_item_product: @@ -31,6 +38,9 @@ en: match_policies: include: Line item's product has one of the chosen taxons exclude: Line item's product does not have one of the chosen taxons + hints: + solidus_friendly_promotions/calculator: + promotions: This is used to determine the promotional discount to be applied to an order, an item, or shipping charges. product_rule: choose_products: Choose products label: Order must contain %{select} of these products From 7c8e675cc6dc6b9a431f113f3d980bc0b2b4991e Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 14 Jul 2023 11:38:31 +0200 Subject: [PATCH 283/834] Fill out README. --- friendly_promotions/README.md | 63 +++++++++++++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 3 deletions(-) diff --git a/friendly_promotions/README.md b/friendly_promotions/README.md index 950e71818ea..b9dbab8a5b0 100644 --- a/friendly_promotions/README.md +++ b/friendly_promotions/README.md @@ -3,25 +3,82 @@ [![CircleCI](https://circleci.com/gh/solidusio-contrib/solidus_friendly_promotions.svg?style=shield)](https://circleci.com/gh/solidusio-contrib/solidus_friendly_promotions) [![codecov](https://codecov.io/gh/solidusio-contrib/solidus_friendly_promotions/branch/master/graph/badge.svg)](https://codecov.io/gh/solidusio-contrib/solidus_friendly_promotions) - +This extension replaces Solidus core's promotion system. It is intended as both a research project and a working alternative to how promotions work in core. + +The basic architecture is very similar to the one in core Solidus, but with a few decisive tweaks, which I'll explain in the coming sections. + +## Architecture + +This extension centralizes promotion handling in the order updater. A service class, the `SolidusFriendlyShipping::OrderPromotionAdjuster` applies the current promotion configuration to the order, adjusting or removing adjustments as necessary. + +In core, Promotion adjustments get recalculated twice on every change to the cart; once in `Spree::OrderContents#after_add_or_remove` and in `Spree::OrderUpdater#update_promotions`. To make things more complicated, `Spree::OrderContents` leverages the `Spree::PromotionHandler#cart`, while the order updater goes through `Spree::Adjustment#recalculate`. + +The design decision here is to make the code path easier to follow, and consequently to make it more performant ("Make it easy, then make it fast"). + +`SolidusFriendlyShipping::Promotion` objects have rules and actions, just like `Spree::Promotion`. However, both rules and actions work slightly differently. + +### Promotion Rules + +Promotion rules can be applicable to either `Spree::Order`, `Spree::LineItem`, or `Spree::Shipment` objects. If they are applicable, they will be asked for eligibility. Rules applicable to orders are processed first. If a promotion has a rule that makes it inapplicable for an order, line items and shipments will not be adjusted. + +### Promotion Actions + +There are only two actions by default that should cover most use cases: `AdjustLineItem` and `AdjustShipment`. Ther is no action that creates order-level adjustments, as this feature of core Solidus has proven to be very difficult for customer service and finance departments due to the difficulty of accruing order-level adjustments to individual line items when e.g. processing returns. In order to give a fixed discount to all line items in an order, use the `AdjustLineItem` action with the `DistributedAmount` calculator. + +Both actions are calculable. By setting their `calculator` to one of the classes provided, a great range of discount possibilities is maintained. + +### Connecting promotions to orders + +When there is a join record `SolidusFriendlyPromotions::OrderPromotion`, the promotion and the order will be "connected", and the promotion will be applied even if it does not `apply_automatically` (see below). There's a difference to Solidus' system here in that promotions are not automatically connected to orders when they apply. + +One way of connecting orders to promotions is via a promotion code. + +### Promotion categories + +Promotion categories simply allow admins to group promotion actions. They have no further significance with regards to the functionality of the promotion system. This is the same behavior as in core. + ## Installation Add solidus_friendly_promotions to your Gemfile: ```ruby -gem 'solidus_friendly_promotions' +gem 'solidus_friendly_promotions', github: 'friendlycart/solidus_friendly_promotion', branch: 'main' ``` +Once this project is out of the research phase, a proper gem release will follow. + Bundle your dependencies and run the installation generator: ```shell bin/rails generate solidus_friendly_promotions:install ``` +This will create the tables for this extension. It will also replace the promotion administration system under +`/admin/promotions` with a new one that needs `turbo-rails`. + +It will also create an initializer within which Solidus is configured to use this extension's `SimpleOrderContents` and `OrderPromotionAdjuster` classes. Feel free to override with your own implementations! + ## Usage - +Add a promotion using the admin. Add rules and actions, and observe promotions getting applied how you'd expect them to. + +In the admin screen, you can set a number of attributes on your promotion: +- Name: The name of the promotion. The `name` attribute will also be used to generate adjustment labels. In multi-lingual stores, you probably want different promotions per language for this reason. + +- Description: This is purely informative. Some stores use `description` in order display information about this promotion to customers, but there is nothing in core Solidus that does it. + +- Start and End: Outside of the time range between `starts_at` and `expires_at`, the promotion will not be eligible to any order. + +- Usage Limit: `usage_limit` controls to how many orders this promotion can be applied, independent of promotion code or user. This is most commonly used to limit the risk of losing too much revenue with a particular promotion. + +- Path: `path` is a URL path that connects the promotion to the order upon visitation. Not currently implemented in either Solidus core or this extension. + +- Per Code Usage Limit: How often each code can be used. Useful for limiting how many orders can be placed with a single promotion code. + +- Apply Automatically: Whether this promotion should apply automatically. This means that the promotion is checked for eligibility every time the customer's order is recalculated. Promotion Codes and automatic applications are incompatible. + +- Promotion Category: Used to group promotions in the admin view. ## Development From 9c74b0a5ef59995e0998aa5881e42b4fb1ef3376 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 14 Jul 2023 11:41:39 +0200 Subject: [PATCH 284/834] Move Solidus configuration to initializer It's documented like this in the README. --- .../install/templates/initializer.rb | 4 ++++ friendly_promotions/spec/models/promotion/integration_spec.rb | 1 - friendly_promotions/spec/spec_helper.rb | 2 -- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb index 6db51718192..ea1a5762cc4 100644 --- a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb +++ b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb @@ -1,5 +1,9 @@ # frozen_string_literal: true +# Replace solidus core's order contents and promotion adjuster classes with ours. +Spree::Config.order_contents_class = "SolidusFriendlyPromotions::SimpleOrderContents" +Spree::Config.promotion_adjuster_class = "SolidusFriendlyPromotions::OrderPromotionAdjuster" + # Replace the promotions menu from core with ours Spree::Backend::Config.configure do |config| config.menu_items = Spree::Backend::Config.menu_items.map do |item| diff --git a/friendly_promotions/spec/models/promotion/integration_spec.rb b/friendly_promotions/spec/models/promotion/integration_spec.rb index d03c7c8b42e..9fce597fbd7 100644 --- a/friendly_promotions/spec/models/promotion/integration_spec.rb +++ b/friendly_promotions/spec/models/promotion/integration_spec.rb @@ -10,7 +10,6 @@ let(:order) { create(:order) } before do - Spree::Config.promotion_adjuster_class = "SolidusFriendlyPromotions::OrderPromotionAdjuster" Spree::Config.promotion_chooser_class = "SolidusFriendlyPromotions::PromotionAdjustmentChooser" promotion.rules << rule promotion.actions << action diff --git a/friendly_promotions/spec/spec_helper.rb b/friendly_promotions/spec/spec_helper.rb index 7be29035966..94f54247705 100644 --- a/friendly_promotions/spec/spec_helper.rb +++ b/friendly_promotions/spec/spec_helper.rb @@ -36,8 +36,6 @@ config.include SolidusFriendlyPromotions::Engine.routes.url_helpers, type: :request config.before do - Spree::Config.order_contents_class = "SolidusFriendlyPromotions::SimpleOrderContents" - Spree::Config.promotion_adjuster_class = "SolidusFriendlyPromotions::OrderPromotionAdjuster" Spree::Config.promotion_chooser_class = "SolidusFriendlyPromotions::PromotionAdjustmentChooser" end end From a69aa2929efb91d5c7721010a75436120703e963 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 14 Jul 2023 11:45:50 +0200 Subject: [PATCH 285/834] Move spec setup to initializer This is more in-line with the README. --- .../install/templates/initializer.rb | 6 ++++-- .../spec/models/promotion/integration_spec.rb | 1 - .../calculators/distributed_amount_spec.rb | 5 ----- .../simple_order_contents_spec.rb | 5 ----- friendly_promotions/spec/spec_helper.rb | 4 ---- 5 files changed, 4 insertions(+), 17 deletions(-) diff --git a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb index ea1a5762cc4..f0ea79c260f 100644 --- a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb +++ b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb @@ -20,8 +20,10 @@ end SolidusFriendlyPromotions.configure do |config| - # TODO: Remember to change this with the actual preferences you have implemented! - # config.sample_preference = 'sample_value' + # This class chooses which promotion should apply to a line item in case + # that more than one promotion is eligible. + config.promotion_chooser_class = "SolidusFriendlyPromotions::PromotionAdjustmentChooser" + config.shipment_discount_calculators = [ "SolidusFriendlyPromotions::Calculators::FlatRate", "SolidusFriendlyPromotions::Calculators::FlexiRate", diff --git a/friendly_promotions/spec/models/promotion/integration_spec.rb b/friendly_promotions/spec/models/promotion/integration_spec.rb index 9fce597fbd7..f4623fc4878 100644 --- a/friendly_promotions/spec/models/promotion/integration_spec.rb +++ b/friendly_promotions/spec/models/promotion/integration_spec.rb @@ -10,7 +10,6 @@ let(:order) { create(:order) } before do - Spree::Config.promotion_chooser_class = "SolidusFriendlyPromotions::PromotionAdjustmentChooser" promotion.rules << rule promotion.actions << action order.contents.add(shirt.master, 1) diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/distributed_amount_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/distributed_amount_spec.rb index e43050cf6d9..469d3630965 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/distributed_amount_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/distributed_amount_spec.rb @@ -11,11 +11,6 @@ let(:order) { create(:order_with_line_items, line_items_attributes: line_items_attributes) } let(:currency) { "USD" } - before do - Spree::Config.promotion_adjuster_class = "SolidusFriendlyPromotions::OrderPromotionAdjuster" - Spree::Config.promotion_chooser_class = "SolidusFriendlyPromotions::PromotionAdjustmentChooser" - end - context 'applied to an order' do let(:line_items_attributes) { [{ price: 20 }, { price: 30 }, { price: 100 }] } before do diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/simple_order_contents_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/simple_order_contents_spec.rb index c6e68a392bd..90a287f2bf7 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/simple_order_contents_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/simple_order_contents_spec.rb @@ -86,11 +86,6 @@ let(:promotion) { create(:friendly_promotion, apply_automatically: true) } let(:calculator) { SolidusFriendlyPromotions::Calculators::FlatRate.new(preferred_amount: 10) } - before do - Spree::Config.promotion_adjuster_class = "SolidusFriendlyPromotions::OrderPromotionAdjuster" - Spree::Config.promotion_chooser_class = "SolidusFriendlyPromotions::PromotionAdjustmentChooser" - end - context "one active line item promotion" do let!(:action) { SolidusFriendlyPromotions::Actions::AdjustLineItem.create(promotion: promotion, calculator: calculator) } diff --git a/friendly_promotions/spec/spec_helper.rb b/friendly_promotions/spec/spec_helper.rb index 94f54247705..fa15aea38ff 100644 --- a/friendly_promotions/spec/spec_helper.rb +++ b/friendly_promotions/spec/spec_helper.rb @@ -34,10 +34,6 @@ end config.include SolidusFriendlyPromotions::Engine.routes.url_helpers, type: :request - - config.before do - Spree::Config.promotion_chooser_class = "SolidusFriendlyPromotions::PromotionAdjustmentChooser" - end end Shoulda::Matchers.configure do |config| From fea211f122950a919b44ed884b6694f192b2eea2 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 14 Jul 2023 11:54:49 +0200 Subject: [PATCH 286/834] Implement our own `promotions_per_page` preference If we want to replace Solidus' promotion system, we also have to care about the small things --- .../admin/promotions_controller.rb | 2 +- .../install/templates/initializer.rb | 3 +++ .../lib/solidus_friendly_promotions/configuration.rb | 4 ++++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb index b6584c09bcf..48dc47aae63 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb @@ -38,7 +38,7 @@ def collection @collection = @search.result(distinct: true). includes(promotion_includes). page(params[:page]). - per(params[:per_page] || Spree::Config[:promotions_per_page]) + per(params[:per_page] || SolidusFriendlyPromotions.config.promotions_per_page) @collection end diff --git a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb index f0ea79c260f..120c96dc04e 100644 --- a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb +++ b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb @@ -24,6 +24,9 @@ # that more than one promotion is eligible. config.promotion_chooser_class = "SolidusFriendlyPromotions::PromotionAdjustmentChooser" + # How many promotions should be displayed on the index page in the admin. + config.promotions_per_page = 25 + config.shipment_discount_calculators = [ "SolidusFriendlyPromotions::Calculators::FlatRate", "SolidusFriendlyPromotions::Calculators::FlexiRate", diff --git a/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb b/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb index 78a16c3c344..208064e3f95 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb @@ -17,6 +17,10 @@ class Configuration < Spree::Preferences::Configuration class_name_attribute :promotion_chooser_class, default: "SolidusFriendlyPromotions::PromotionAdjustmentChooser" class_name_attribute :promotion_code_batch_mailer_class, default: "SolidusFriendlyPromotions::PromotionCodeBatchMailer" + + # @!attribute [rw] promotions_per_page + # @return [Integer] Promotions to show per-page in the admin (default: +25+) + preference :promotions_per_page, :integer, default: 25 end class << self From 90c3b569984a739ab47c9520da012f24c672ec43 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 14 Jul 2023 11:55:20 +0200 Subject: [PATCH 287/834] Test class under test --- .../solidus_friendly_promotions/calculators/flexi_rate_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/flexi_rate_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/flexi_rate_spec.rb index e1c2056b27d..a3df09a76eb 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/flexi_rate_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/flexi_rate_spec.rb @@ -152,7 +152,7 @@ it "should allow creation of new object with all the attributes" do attributes = { preferred_first_item: 1, preferred_additional_item: 1, preferred_max_items: 1 } - calculator = Spree::Calculator::FlexiRate.new(attributes) + calculator = described_class.new(attributes) expect(calculator).to have_attributes(attributes) end end From 36f1d7423c0f0a0e4460ce0c1ef37106d22d8133 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 14 Jul 2023 12:08:37 +0200 Subject: [PATCH 288/834] Remove CodeCov badge --- friendly_promotions/README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/friendly_promotions/README.md b/friendly_promotions/README.md index b9dbab8a5b0..bdf3fbd8b10 100644 --- a/friendly_promotions/README.md +++ b/friendly_promotions/README.md @@ -1,7 +1,6 @@ # Solidus Friendly Promotions -[![CircleCI](https://circleci.com/gh/solidusio-contrib/solidus_friendly_promotions.svg?style=shield)](https://circleci.com/gh/solidusio-contrib/solidus_friendly_promotions) -[![codecov](https://codecov.io/gh/solidusio-contrib/solidus_friendly_promotions/branch/master/graph/badge.svg)](https://codecov.io/gh/solidusio-contrib/solidus_friendly_promotions) +[![CircleCI](https://circleci.com/gh/friendlycart/solidus_friendly_promotions.svg?style=shield)](https://circleci.com/gh/friendlycart/solidus_friendly_promotions) This extension replaces Solidus core's promotion system. It is intended as both a research project and a working alternative to how promotions work in core. From cc99602fbf1946f0b431571ea335558d6298fe7e Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Mon, 17 Jul 2023 10:47:42 +0200 Subject: [PATCH 289/834] Fix Ruby version in CI --- friendly_promotions/.circleci/config.yml | 55 ++++++++++-------------- 1 file changed, 22 insertions(+), 33 deletions(-) diff --git a/friendly_promotions/.circleci/config.yml b/friendly_promotions/.circleci/config.yml index 28d70ebd500..ec33318050e 100644 --- a/friendly_promotions/.circleci/config.yml +++ b/friendly_promotions/.circleci/config.yml @@ -1,53 +1,42 @@ -version: 2.1 - +version: '2.1' orbs: - # Required for feature specs. browser-tools: circleci/browser-tools@1.1 - - # Always take the latest version of the orb, this allows us to - # run specs against Solidus supported versions only without the need - # to change this configuration every time a Solidus version is released - # or goes EOL. solidusio_extensions: solidusio/extensions@volatile - jobs: - run-specs-with-sqlite: - executor: solidusio_extensions/sqlite + run-specs-with-mysql: + executor: + name: solidusio_extensions/mysql + ruby_version: '3.1' steps: - - browser-tools/install-chrome + - browser-tools/install-browser-tools - solidusio_extensions/run-tests run-specs-with-postgres: - executor: solidusio_extensions/postgres + executor: + name: solidusio_extensions/postgres + ruby_version: '3.2' steps: - - browser-tools/install-chrome + - browser-tools/install-browser-tools - solidusio_extensions/run-tests - run-specs-with-mysql: - executor: solidusio_extensions/mysql + run-specs-with-sqlite: + executor: + name: solidusio_extensions/sqlite + ruby_version: '3.0' steps: - - browser-tools/install-chrome + - browser-tools/install-browser-tools - solidusio_extensions/run-tests - lint-code: - executor: solidusio_extensions/sqlite-memory - steps: - - solidusio_extensions/lint-code - workflows: - "Run specs on supported Solidus versions": + Run specs on supported Solidus versions: jobs: - - run-specs-with-sqlite - run-specs-with-postgres - run-specs-with-mysql - - lint-code - - "Weekly run specs against master": + - run-specs-with-sqlite + Weekly run specs against main: + jobs: + - run-specs-with-sqlite triggers: - schedule: - cron: "0 0 * * 4" # every Thursday + cron: 0 0 * * 4 filters: branches: only: - - master - jobs: - - run-specs-with-sqlite - - run-specs-with-postgres - - run-specs-with-mysql + - main From f3d1aad7dbb96fda377bad9a2754e8360e7fe606 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 14 Jul 2023 12:28:05 +0200 Subject: [PATCH 290/834] Add OrderDiscounts / ItemDiscount class This set of classes is modelled after the OrderTaxation / ItemTax classes in Solidus, and shall facilitate discounts from a variety of sources, such as the promotion system in this gem or a third-party promotion system. --- .../item_discount.rb | 17 +++++++++++++++++ .../order_discounts.rb | 18 ++++++++++++++++++ .../item_discount_spec.rb | 11 +++++++++++ .../order_discounts_spec.rb | 7 +++++++ 4 files changed, 53 insertions(+) create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/item_discount.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/order_discounts.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/item_discount_spec.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/order_discounts_spec.rb diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/item_discount.rb b/friendly_promotions/app/models/solidus_friendly_promotions/item_discount.rb new file mode 100644 index 00000000000..e72f48de719 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/item_discount.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + # Simple object used to hold discount data for an item. + # + # This generic object will hold the amount of discount that should be applied to + # an item. (Either a {Spree::LineItem} or a {Spree::Shipment}.) + # + # @attr_reader [Integer] item_id the {Spree::LineItem} or {Spree::Shipment} ID. + # @attr_reader [String] label information about the discount + # @attr_reader [ApplicationRecord] source will be used as the source for adjustments + # @attr_reader [BigDecimal] amount the amount of discount applied to the item + class ItemDiscount + include ActiveModel::Model + attr_accessor :item_id, :label, :source, :amount + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/order_discounts.rb b/friendly_promotions/app/models/solidus_friendly_promotions/order_discounts.rb new file mode 100644 index 00000000000..83b3174c127 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/order_discounts.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + # Simple object to pass back discount data from a promoter. + # + # Will be used by {SolidusFriendlyPromotions::OrderDiscounter} to create or update promotion + # adjustments on an order. + # + # @attr_reader [Integer] order_id the {Spree::Order} these discounts apply to + # @attr_reader [Array] line_item_discounts an array of + # discount data for order's line items + # @attr_reader [Array] shipment_discounts an array of + # discount data for the order's shipments + class OrderDiscounts + include ActiveModel::Model + attr_accessor :order_id, :line_item_discounts, :shipment_discounts + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/item_discount_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/item_discount_spec.rb new file mode 100644 index 00000000000..c22e1c892c9 --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/item_discount_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require 'spec_helper' + + +RSpec.describe SolidusFriendlyPromotions::ItemDiscount do + it { is_expected.to respond_to(:item_id) } + it { is_expected.to respond_to(:source) } + it { is_expected.to respond_to(:amount) } + it { is_expected.to respond_to(:label) } +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/order_discounts_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/order_discounts_spec.rb new file mode 100644 index 00000000000..2654b2c452d --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/order_discounts_spec.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +RSpec.describe SolidusFriendlyPromotions::OrderDiscounts do + it { is_expected.to respond_to :order_id } + it { is_expected.to respond_to :line_item_discounts } + it { is_expected.to respond_to :shipment_discounts } +end From cd0312103ca3d234d46c0deb2381774826640bd7 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Mon, 17 Jul 2023 10:43:59 +0200 Subject: [PATCH 291/834] Add in-memory discount objects This refactor helps us with accepting discounts from multiple sources, and it adds a layer of indirection before we add adjustments or discounts. --- .../actions/adjust_line_item.rb | 2 +- .../actions/adjust_shipment.rb | 2 +- ...er.rb => friendly_promotion_discounter.rb} | 19 ++--- .../item_discount.rb | 4 + .../line_item_adjuster.rb | 32 -------- .../line_item_discounter.rb | 28 +++++++ .../order_discounter.rb | 77 +++++++++++++++++++ .../promotion_action.rb | 16 ++-- .../promotion_adjustment_chooser.rb | 4 +- ...ent_adjuster.rb => shipment_discounter.rb} | 14 +--- .../install/templates/initializer.rb | 8 +- .../configuration.rb | 3 +- .../configuration_spec.rb | 2 +- .../spec/models/promotion/integration_spec.rb | 4 +- .../friendly_promotion_discounter_spec.rb | 50 ++++++++++++ ...uster_spec.rb => order_discounter_spec.rb} | 2 +- .../promotion_action_spec.rb | 24 +++--- 17 files changed, 211 insertions(+), 80 deletions(-) rename friendly_promotions/app/models/solidus_friendly_promotions/{order_promotion_adjuster.rb => friendly_promotion_discounter.rb} (77%) delete mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/line_item_adjuster.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/line_item_discounter.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb rename friendly_promotions/app/models/solidus_friendly_promotions/{shipment_adjuster.rb => shipment_discounter.rb} (54%) create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/friendly_promotion_discounter_spec.rb rename friendly_promotions/spec/models/solidus_friendly_promotions/{order_promotion_adjuster_spec.rb => order_discounter_spec.rb} (96%) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb index 0870be2522a..394d0b660cd 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb @@ -3,7 +3,7 @@ module SolidusFriendlyPromotions module Actions class AdjustLineItem < PromotionAction - def can_adjust?(object) + def can_discount?(object) object.is_a? Spree::LineItem end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb index e5c8502eca6..0e252f7b05a 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb @@ -3,7 +3,7 @@ module SolidusFriendlyPromotions module Actions class AdjustShipment < PromotionAction - def can_adjust?(object) + def can_discount?(object) object.is_a? Spree::Shipment end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/order_promotion_adjuster.rb b/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb similarity index 77% rename from friendly_promotions/app/models/solidus_friendly_promotions/order_promotion_adjuster.rb rename to friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb index b7a569696dd..9778723d96f 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/order_promotion_adjuster.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module SolidusFriendlyPromotions - class OrderPromotionAdjuster + class FriendlyPromotionDiscounter attr_reader :order, :promotions def initialize(order) @@ -10,22 +10,23 @@ def initialize(order) end def call - adjust_line_items - adjust_shipments - order.promo_total = (order.line_items + order.shipments).sum { |item| item.promo_total } - order + OrderDiscounts.new( + order_id: order.id, + line_item_discounts: adjust_line_items, + shipment_discounts: adjust_shipments + ) end private def adjust_line_items - line_item_adjuster = LineItemAdjuster.new(promotions: promotions) - order.line_items.each { |line_item| line_item_adjuster.call(line_item) } + line_item_adjuster = LineItemDiscounter.new(promotions: promotions) + order.line_items.flat_map { |line_item| line_item_adjuster.call(line_item) } end def adjust_shipments - shipment_adjuster = ShipmentAdjuster.new(promotions: promotions) - order.shipments.each { |shipment| shipment_adjuster.call(shipment) } + shipment_adjuster = ShipmentDiscounter.new(promotions: promotions) + order.shipments.flat_map { |shipment| shipment_adjuster.call(shipment) } end def possible_promotions diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/item_discount.rb b/friendly_promotions/app/models/solidus_friendly_promotions/item_discount.rb index e72f48de719..32f6810cd8d 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/item_discount.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/item_discount.rb @@ -13,5 +13,9 @@ module SolidusFriendlyPromotions class ItemDiscount include ActiveModel::Model attr_accessor :item_id, :label, :source, :amount + + def ==(other) + item_id == other.item_id && label == other.label && source == other.source && amount == other.amount + end end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/line_item_adjuster.rb b/friendly_promotions/app/models/solidus_friendly_promotions/line_item_adjuster.rb deleted file mode 100644 index 0b195f421cd..00000000000 --- a/friendly_promotions/app/models/solidus_friendly_promotions/line_item_adjuster.rb +++ /dev/null @@ -1,32 +0,0 @@ -# frozen_string_literal: true - -module SolidusFriendlyPromotions - class LineItemAdjuster - attr_reader :promotions - - def initialize(promotions:) - @promotions = promotions - end - - def call(line_item) - return unless line_item.variant.product.promotionable? - non_promotion_adjustments = line_item.adjustments.reject(&:friendly_promotion?) - - eligible_promotions = PromotionEligibility.new(promotable: line_item, possible_promotions: promotions).call - - possible_adjustments = eligible_promotions.flat_map do |promotion| - promotion.actions.select do |action| - action.can_adjust?(line_item) - end.map do |action| - action.adjust(line_item) - end - end - - chosen_adjustments = SolidusFriendlyPromotions.config.promotion_chooser_class.new(line_item).call(possible_adjustments) - - line_item.promo_total = chosen_adjustments.sum(&:amount) - line_item.adjustments = non_promotion_adjustments + chosen_adjustments - line_item - end - end -end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/line_item_discounter.rb b/friendly_promotions/app/models/solidus_friendly_promotions/line_item_discounter.rb new file mode 100644 index 00000000000..4edb6a3a0ef --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/line_item_discounter.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + class LineItemDiscounter + attr_reader :promotions + + def initialize(promotions:) + @promotions = promotions + end + + def call(line_item) + return [] unless line_item.variant.product.promotionable? + + eligible_promotions = PromotionEligibility.new( + promotable: line_item, + possible_promotions: promotions + ).call + + possible_adjustments = eligible_promotions.flat_map do |promotion| + promotion.actions.select do |action| + action.can_discount?(line_item) + end.map do |action| + action.discount(line_item) + end + end + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb b/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb new file mode 100644 index 00000000000..1a807d3f000 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + class OrderDiscounter + def initialize(order) + @order = order + end + + def call + all_order_discounts = SolidusFriendlyPromotions.config.discounters.map do |discounter| + discounter.new(order).call + end + + @order.line_items.each do |item| + all_line_item_discounts = all_order_discounts.flat_map(&:line_item_discounts) + item_discounts = all_line_item_discounts.select { |element| element.item_id == item.id } + chosen_item_discounts = SolidusFriendlyPromotions.config.discount_chooser_class.new(item).call(item_discounts) + update_adjustments(item, chosen_item_discounts) + end + + @order.shipments.each do |item| + all_shipment_discounts = all_order_discounts.flat_map(&:shipment_discounts) + item_discounts = all_shipment_discounts.select { |element| element.item_id == item.id } + chosen_item_discounts = SolidusFriendlyPromotions.config.discount_chooser_class.new(item).call(item_discounts) + update_adjustments(item, chosen_item_discounts) + end + + @order.promo_total = (order.line_items + order.shipments).sum { |item| item.promo_total } + @order + end + + + private + attr_reader :order + + # Walk through the discounts for an item and update adjustments for it. Once + # all of the discounts have been added as adjustments, remove any old tax + # adjustments that weren't touched. + # + # @private + # @param [#adjustments] item a {Spree::LineItem} or {Spree::Shipment} + # @param [Array] taxed_items a list of calculated discounts for an item + # @return [void] + def update_adjustments(item, taxed_items) + promotion_adjustments = item.adjustments.select(&:friendly_promotion?) + + active_adjustments = taxed_items.map do |tax_item| + update_adjustment(item, tax_item) + end + item.update(promo_total: active_adjustments.sum(&:amount)) + # Remove any tax adjustments tied to rates which no longer match. + unmatched_adjustments = promotion_adjustments - active_adjustments + item.adjustments.destroy(unmatched_adjustments) + end + + # Update or create a new tax adjustment on an item. + # + # @private + # @param [#adjustments] item a {Spree::LineItem} or {Spree::Shipment} + # @param [SolidusFriendlyPromotions::ItemDiscount] tax_item calculated discounts for an item + # @return [Spree::Adjustment] the created or updated tax adjustment + def update_adjustment(item, discount_item) + adjustment = item.adjustments.detect do |adjustment| + adjustment.source == discount_item.source + end + + adjustment ||= item.adjustments.new( + source: discount_item.source, + order_id: item.is_a?(Spree::Order) ? item.id : item.order_id, + label: discount_item.label, + eligible: true + ) + adjustment.update!(amount: discount_item.amount) + adjustment + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb index b5676cf3885..e5203ccaff9 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb @@ -21,17 +21,17 @@ def preload_relations [:calculator] end - def can_adjust?(object) + def can_discount?(object) raise NotImplementedError end - def adjust(adjustable) - adjustment = adjustable.adjustments.detect do |adjustment| - adjustment.source == self - end || adjustable.adjustments.build(source: self, order: adjustable.order) - adjustment.label = adjustment_label(adjustable) - adjustment.amount = compute_amount(adjustable) - adjustment + def discount(adjustable) + ItemDiscount.new( + item_id: adjustable.id, + label: adjustment_label(adjustable), + amount: compute_amount(adjustable), + source: self + ) end # Ensure a negative amount which does not exceed the object's amount diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_adjustment_chooser.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_adjustment_chooser.rb index 4408a1d5f95..8c0d7a2645a 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_adjustment_chooser.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_adjustment_chooser.rb @@ -10,8 +10,8 @@ def initialize(adjustable) def call(adjustments) Array.wrap( - adjustments.select(&:eligible?).min_by do |adjustment| - [adjustment.amount, -adjustment.id.to_i] + adjustments.min_by do |adjustment| + [adjustment.amount, -adjustment.source&.id.to_i] end ) end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/shipment_adjuster.rb b/friendly_promotions/app/models/solidus_friendly_promotions/shipment_discounter.rb similarity index 54% rename from friendly_promotions/app/models/solidus_friendly_promotions/shipment_adjuster.rb rename to friendly_promotions/app/models/solidus_friendly_promotions/shipment_discounter.rb index 73bab73e396..5d9a59c22a2 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/shipment_adjuster.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/shipment_discounter.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module SolidusFriendlyPromotions - class ShipmentAdjuster + class ShipmentDiscounter attr_reader :promotions def initialize(promotions:) @@ -9,25 +9,17 @@ def initialize(promotions:) end def call(shipment) - non_promotion_adjustments = shipment.adjustments.reject(&:promotion?) - eligible_promotions = promotions.select do |promotion| PromotionEligibility.new(promotable: shipment, possible_promotions: promotions).call end possible_adjustments = eligible_promotions.flat_map do |promotion| promotion.actions.select do |action| - action.can_adjust?(shipment) + action.can_discount?(shipment) end.map do |action| - action.adjust(shipment) + action.discount(shipment) end end - - chosen_adjustments = SolidusFriendlyPromotions.config.promotion_chooser_class.new(shipment).call(possible_adjustments) - - shipment.promo_total = chosen_adjustments.sum(&:amount) - shipment.adjustments = non_promotion_adjustments + chosen_adjustments - shipment end end end diff --git a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb index 120c96dc04e..1b77f2b6ea4 100644 --- a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb +++ b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb @@ -2,7 +2,7 @@ # Replace solidus core's order contents and promotion adjuster classes with ours. Spree::Config.order_contents_class = "SolidusFriendlyPromotions::SimpleOrderContents" -Spree::Config.promotion_adjuster_class = "SolidusFriendlyPromotions::OrderPromotionAdjuster" +Spree::Config.promotion_adjuster_class = "SolidusFriendlyPromotions::OrderDiscounter" # Replace the promotions menu from core with ours Spree::Backend::Config.configure do |config| @@ -22,11 +22,15 @@ SolidusFriendlyPromotions.configure do |config| # This class chooses which promotion should apply to a line item in case # that more than one promotion is eligible. - config.promotion_chooser_class = "SolidusFriendlyPromotions::PromotionAdjustmentChooser" + config.discount_chooser_class = "SolidusFriendlyPromotions::PromotionAdjustmentChooser" # How many promotions should be displayed on the index page in the admin. config.promotions_per_page = 25 + config.discounters = [ + "SolidusFriendlyPromotions::FriendlyPromotionDiscounter" + ] + config.shipment_discount_calculators = [ "SolidusFriendlyPromotions::Calculators::FlatRate", "SolidusFriendlyPromotions::Calculators::FlexiRate", diff --git a/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb b/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb index 208064e3f95..a291e6a3b38 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb @@ -14,8 +14,9 @@ class Configuration < Spree::Preferences::Configuration add_class_set :shipment_rules add_class_set :actions + add_class_set :discounters - class_name_attribute :promotion_chooser_class, default: "SolidusFriendlyPromotions::PromotionAdjustmentChooser" + class_name_attribute :discount_chooser_class, default: "SolidusFriendlyPromotions::PromotionAdjustmentChooser" class_name_attribute :promotion_code_batch_mailer_class, default: "SolidusFriendlyPromotions::PromotionCodeBatchMailer" # @!attribute [rw] promotions_per_page diff --git a/friendly_promotions/spec/lib/solidus_friendly_promotions/configuration_spec.rb b/friendly_promotions/spec/lib/solidus_friendly_promotions/configuration_spec.rb index 3a14b9c824c..f62ec665ba3 100644 --- a/friendly_promotions/spec/lib/solidus_friendly_promotions/configuration_spec.rb +++ b/friendly_promotions/spec/lib/solidus_friendly_promotions/configuration_spec.rb @@ -15,7 +15,7 @@ describe ".promotion_chooser_class" do it "is the promotion chooser" do - expect(subject.promotion_chooser_class).to eq(SolidusFriendlyPromotions::PromotionAdjustmentChooser) + expect(subject.discount_chooser_class).to eq(SolidusFriendlyPromotions::PromotionAdjustmentChooser) end end diff --git a/friendly_promotions/spec/models/promotion/integration_spec.rb b/friendly_promotions/spec/models/promotion/integration_spec.rb index f4623fc4878..37f67bbbe92 100644 --- a/friendly_promotions/spec/models/promotion/integration_spec.rb +++ b/friendly_promotions/spec/models/promotion/integration_spec.rb @@ -20,7 +20,7 @@ let(:rule) { SolidusFriendlyPromotions::Rules::Product.new(products: [shirt]) } context "with an line item level action" do - let(:calculator) { Spree::Calculator::PercentOnLineItem.new(preferred_percent: 20) } + let(:calculator) { SolidusFriendlyPromotions::Calculators::Percent.new(preferred_percent: 20) } let(:action) { SolidusFriendlyPromotions::Actions::AdjustLineItem.new(calculator: calculator) } it "creates one line item level adjustment" do @@ -37,7 +37,7 @@ let(:rule) { SolidusFriendlyPromotions::Rules::LineItemProduct.new(products: [shirt]) } context "with an line item level action" do - let(:calculator) { Spree::Calculator::PercentOnLineItem.new(preferred_percent: 20) } + let(:calculator) { SolidusFriendlyPromotions::Calculators::Percent.new(preferred_percent: 20) } let(:action) { SolidusFriendlyPromotions::Actions::AdjustLineItem.new(calculator: calculator) } it "creates one line item level adjustment" do diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/friendly_promotion_discounter_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/friendly_promotion_discounter_spec.rb new file mode 100644 index 00000000000..e429c5c5e18 --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/friendly_promotion_discounter_spec.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SolidusFriendlyPromotions::FriendlyPromotionDiscounter do + describe "selecting promotions" do + let(:order) { create(:order) } + subject { described_class.new(order) } + + let!(:active_promotion) { create(:friendly_promotion, :with_adjustable_action, apply_automatically: true) } + let!(:inactive_promotion) { create(:friendly_promotion, :with_adjustable_action, expires_at: 2.days.ago, apply_automatically: true) } + let!(:connectable_promotion) { create(:friendly_promotion, :with_adjustable_action) } + let!(:connectable_inactive_promotion) { create(:friendly_promotion, :with_adjustable_action, expires_at: 2.days.ago) } + + context "no promo is connected to the order" do + it "checks only active promotions" do + expect(SolidusFriendlyPromotions::PromotionEligibility).to receive(:new). + with(promotable: order, possible_promotions: [active_promotion]). + and_call_original + subject + end + end + + context "an active promo is connected to the order" do + before do + order.friendly_promotions << connectable_promotion + end + + it "checks active and connected promotions" do + expect(SolidusFriendlyPromotions::PromotionEligibility).to receive(:new). + with(promotable: order, possible_promotions: array_including(active_promotion, connectable_promotion)). + and_call_original + subject + end + end + + context "an inactive promo is connected to the order" do + before do + order.friendly_promotions << connectable_inactive_promotion + end + + it "does not check connected inactive promotions" do + expect(SolidusFriendlyPromotions::PromotionEligibility).to receive(:new). + with(promotable: order, possible_promotions: array_including(active_promotion)). + and_call_original + subject + end + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/order_promotion_adjuster_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/order_discounter_spec.rb similarity index 96% rename from friendly_promotions/spec/models/solidus_friendly_promotions/order_promotion_adjuster_spec.rb rename to friendly_promotions/spec/models/solidus_friendly_promotions/order_discounter_spec.rb index 3b6b948f4f4..dbd6e4f76ac 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/order_promotion_adjuster_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/order_discounter_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe SolidusFriendlyPromotions::OrderPromotionAdjuster, type: :model do +RSpec.describe SolidusFriendlyPromotions::OrderDiscounter, type: :model do let(:line_item) { create(:line_item) } let(:order) { line_item.order } let(:promotion) { create(:friendly_promotion, apply_automatically: true) } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_action_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_action_spec.rb index 3099a5d20d6..2fed8b63a80 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_action_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_action_spec.rb @@ -6,18 +6,18 @@ it { is_expected.to belong_to(:promotion) } it { is_expected.to have_one(:calculator) } - it { is_expected.to respond_to :adjust } - it { is_expected.to respond_to :can_adjust? } + it { is_expected.to respond_to :discount } + it { is_expected.to respond_to :can_discount? } describe "#can_adjust?" do - subject { described_class.new.can_adjust?(double) } + subject { described_class.new.can_discount?(double) } it "raises a NotImplementedError" do expect { subject }.to raise_exception(NotImplementedError) end end - describe "#adjust" do + describe "#discount" do let(:variant) { create(:variant) } let(:order) { create(:order) } let(:adjustable) { Spree::LineItem.new(order: order, variant: variant, price: 10)} @@ -27,11 +27,17 @@ allow(action).to receive(:compute_amount).and_return(-1) end - subject { action.adjust(adjustable) } - - it "adds an adjustment to the adjustable" do - expect { subject }.to change { adjustable.adjustments.length }.by(1) - expect(adjustable.adjustments.first.label).to eq("Promotion (20 Perzent off)") + subject { action.discount(adjustable) } + + it "returs an discount to the adjustable" do + expect(subject).to eq( + SolidusFriendlyPromotions::ItemDiscount.new( + item_id: adjustable.id, + label: "Promotion (20 Perzent off)", + source: action, + amount: -1 + ) + ) end end end From 7f3b1c4050452a3b9d3aeb90b2d513e908fc50f5 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Mon, 17 Jul 2023 11:09:52 +0200 Subject: [PATCH 292/834] Use items instead of IDs Item IDs don't carry type information, and we will want to discount shipping rates, too, at some point. --- .../models/solidus_friendly_promotions/item_discount.rb | 8 ++++---- .../solidus_friendly_promotions/order_discounter.rb | 4 ++-- .../solidus_friendly_promotions/promotion_action.rb | 2 +- .../solidus_friendly_promotions/item_discount_spec.rb | 2 +- .../solidus_friendly_promotions/promotion_action_spec.rb | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/item_discount.rb b/friendly_promotions/app/models/solidus_friendly_promotions/item_discount.rb index 32f6810cd8d..f476fcde27a 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/item_discount.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/item_discount.rb @@ -4,18 +4,18 @@ module SolidusFriendlyPromotions # Simple object used to hold discount data for an item. # # This generic object will hold the amount of discount that should be applied to - # an item. (Either a {Spree::LineItem} or a {Spree::Shipment}.) + # an item. # - # @attr_reader [Integer] item_id the {Spree::LineItem} or {Spree::Shipment} ID. + # @attr_reader [Spree::LineItem,Spree::Shipment] the item to be discounted. # @attr_reader [String] label information about the discount # @attr_reader [ApplicationRecord] source will be used as the source for adjustments # @attr_reader [BigDecimal] amount the amount of discount applied to the item class ItemDiscount include ActiveModel::Model - attr_accessor :item_id, :label, :source, :amount + attr_accessor :item, :label, :source, :amount def ==(other) - item_id == other.item_id && label == other.label && source == other.source && amount == other.amount + item == other.item && label == other.label && source == other.source && amount == other.amount end end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb b/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb index 1a807d3f000..d08751ebfb8 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb @@ -13,14 +13,14 @@ def call @order.line_items.each do |item| all_line_item_discounts = all_order_discounts.flat_map(&:line_item_discounts) - item_discounts = all_line_item_discounts.select { |element| element.item_id == item.id } + item_discounts = all_line_item_discounts.select { |element| element.item == item } chosen_item_discounts = SolidusFriendlyPromotions.config.discount_chooser_class.new(item).call(item_discounts) update_adjustments(item, chosen_item_discounts) end @order.shipments.each do |item| all_shipment_discounts = all_order_discounts.flat_map(&:shipment_discounts) - item_discounts = all_shipment_discounts.select { |element| element.item_id == item.id } + item_discounts = all_shipment_discounts.select { |element| element.item == item } chosen_item_discounts = SolidusFriendlyPromotions.config.discount_chooser_class.new(item).call(item_discounts) update_adjustments(item, chosen_item_discounts) end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb index e5203ccaff9..17be3ed46fe 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb @@ -27,7 +27,7 @@ def can_discount?(object) def discount(adjustable) ItemDiscount.new( - item_id: adjustable.id, + item: adjustable, label: adjustment_label(adjustable), amount: compute_amount(adjustable), source: self diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/item_discount_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/item_discount_spec.rb index c22e1c892c9..0fc1feacc31 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/item_discount_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/item_discount_spec.rb @@ -4,7 +4,7 @@ RSpec.describe SolidusFriendlyPromotions::ItemDiscount do - it { is_expected.to respond_to(:item_id) } + it { is_expected.to respond_to(:item) } it { is_expected.to respond_to(:source) } it { is_expected.to respond_to(:amount) } it { is_expected.to respond_to(:label) } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_action_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_action_spec.rb index 2fed8b63a80..757145a5050 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_action_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_action_spec.rb @@ -32,7 +32,7 @@ it "returs an discount to the adjustable" do expect(subject).to eq( SolidusFriendlyPromotions::ItemDiscount.new( - item_id: adjustable.id, + item: adjustable, label: "Promotion (20 Perzent off)", source: action, amount: -1 From 1c015a8b2ae0450a9cac7a906402b863fa3a41d1 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Mon, 17 Jul 2023 11:40:41 +0200 Subject: [PATCH 293/834] Fix circleCI --- friendly_promotions/.circleci/config.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/friendly_promotions/.circleci/config.yml b/friendly_promotions/.circleci/config.yml index ec33318050e..11d33880218 100644 --- a/friendly_promotions/.circleci/config.yml +++ b/friendly_promotions/.circleci/config.yml @@ -9,21 +9,24 @@ jobs: ruby_version: '3.1' steps: - browser-tools/install-browser-tools - - solidusio_extensions/run-tests + - solidusio_extensions/run-tests: + working_directory: '~/project/solidus_friendly_promotions' run-specs-with-postgres: executor: name: solidusio_extensions/postgres ruby_version: '3.2' steps: - browser-tools/install-browser-tools - - solidusio_extensions/run-tests + - solidusio_extensions/run-tests: + working_directory: '~/project/solidus_friendly_promotions' run-specs-with-sqlite: executor: name: solidusio_extensions/sqlite ruby_version: '3.0' steps: - browser-tools/install-browser-tools - - solidusio_extensions/run-tests + - solidusio_extensions/run-tests: + working_directory: '~/project/solidus_friendly_promotions' workflows: Run specs on supported Solidus versions: jobs: From b9e1bd5163a94cc0650e7da42d2a1e7a38633f62 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 25 Jul 2023 10:14:52 +0200 Subject: [PATCH 294/834] Add Shipping Rate discount model and relationships This is useful for allowing customers to know what discounts will apply if they choose a particular shipping rate. The model is similar to the `Spree::ShippingRateTax` model that previews taxes on shipping rates. --- .../shipping_rate_decorator.rb | 26 +++++++++++ .../shipping_rate_discount.rb | 11 +++++ ...25074235_create_shipping_rate_discounts.rb | 10 ++++ .../testing_support/factories.rb | 1 + ...friendly_shipping_rate_discount_factory.rb | 10 ++++ .../shipping_rate_discount_spec.rb | 13 ++++++ .../spec/models/spree/shipping_rate_spec.rb | 46 +++++++++++++++++++ 7 files changed, 117 insertions(+) create mode 100644 friendly_promotions/app/decorators/models/solidus_friendly_promotions/shipping_rate_decorator.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/shipping_rate_discount.rb create mode 100644 friendly_promotions/db/migrate/20230725074235_create_shipping_rate_discounts.rb create mode 100644 friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_shipping_rate_discount_factory.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/shipping_rate_discount_spec.rb create mode 100644 friendly_promotions/spec/models/spree/shipping_rate_spec.rb diff --git a/friendly_promotions/app/decorators/models/solidus_friendly_promotions/shipping_rate_decorator.rb b/friendly_promotions/app/decorators/models/solidus_friendly_promotions/shipping_rate_decorator.rb new file mode 100644 index 00000000000..635bbf151e3 --- /dev/null +++ b/friendly_promotions/app/decorators/models/solidus_friendly_promotions/shipping_rate_decorator.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions::ShippingRateDecorator + def self.prepended(base) + base.class_eval do + has_many :discounts, + class_name: "SolidusFriendlyPromotions::ShippingRateDiscount", + foreign_key: :shipping_rate_id, + dependent: :destroy, + inverse_of: :shipping_rate, + autosave: true + + money_methods :total_before_tax, :promo_total + end + end + + def total_before_tax + amount + promo_total + end + + def promo_total + discounts.sum(&:amount) + end +end + +Spree::ShippingRate.prepend(SolidusFriendlyPromotions::ShippingRateDecorator) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/shipping_rate_discount.rb b/friendly_promotions/app/models/solidus_friendly_promotions/shipping_rate_discount.rb new file mode 100644 index 00000000000..2de000f7856 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/shipping_rate_discount.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + class ShippingRateDiscount < Spree::Base + belongs_to :shipping_rate, inverse_of: :discounts, class_name: "Spree::ShippingRate" + belongs_to :promotion_action, -> { with_discarded }, inverse_of: false + + extend Spree::DisplayMoney + money_methods :amount + end +end diff --git a/friendly_promotions/db/migrate/20230725074235_create_shipping_rate_discounts.rb b/friendly_promotions/db/migrate/20230725074235_create_shipping_rate_discounts.rb new file mode 100644 index 00000000000..bcbe7beb59a --- /dev/null +++ b/friendly_promotions/db/migrate/20230725074235_create_shipping_rate_discounts.rb @@ -0,0 +1,10 @@ +class CreateShippingRateDiscounts < ActiveRecord::Migration[7.0] + def change + create_table :friendly_shipping_rate_discounts do |t| + t.references :promotion_action, null: false, foreign_key: { to_table: :friendly_promotion_actions } + t.references :shipping_rate, null: false, foreign_key: { to_table: :spree_shipping_rates} + t.decimal :amount, precision: 10, scale: 2, null: false + t.string :label, null: false + end + end +end diff --git a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/factories.rb b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/factories.rb index cf315d7e46d..ced5cd0d102 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/factories.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/factories.rb @@ -10,6 +10,7 @@ def self.table_name_prefix require "solidus_friendly_promotions/testing_support/friendly_promotion_factory" require "solidus_friendly_promotions/testing_support/friendly_order_promotion_factory" require "solidus_friendly_promotions/testing_support/friendly_order_factory" +require "solidus_friendly_promotions/testing_support/friendly_shipping_rate_discount_factory" FactoryBot.define do end diff --git a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_shipping_rate_discount_factory.rb b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_shipping_rate_discount_factory.rb new file mode 100644 index 00000000000..1fbe1b7e46b --- /dev/null +++ b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_shipping_rate_discount_factory.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :friendly_shipping_rate_discount, class: 'SolidusFriendlyPromotions::ShippingRateDiscount' do + amount { BigDecimal("-4.00") } + shipping_rate + promotion_action { SolidusFriendlyPromotions::Actions::AdjustShipment.new } + label { "10% off" } + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/shipping_rate_discount_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/shipping_rate_discount_spec.rb new file mode 100644 index 00000000000..3cd42af13e5 --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/shipping_rate_discount_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe SolidusFriendlyPromotions::ShippingRateDiscount do + subject(:shipping_rate_discount) { build(:friendly_shipping_rate_discount) } + + it { is_expected.to respond_to(:shipping_rate) } + it { is_expected.to respond_to(:promotion_action) } + it { is_expected.to respond_to(:amount) } + it { is_expected.to respond_to(:display_amount) } + it { is_expected.to respond_to(:label) } +end diff --git a/friendly_promotions/spec/models/spree/shipping_rate_spec.rb b/friendly_promotions/spec/models/spree/shipping_rate_spec.rb new file mode 100644 index 00000000000..be172d70c29 --- /dev/null +++ b/friendly_promotions/spec/models/spree/shipping_rate_spec.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe Spree::ShippingRate do + let(:subject) { build(:shipping_rate) } + + describe '#display_price' do + before(:each) { subject.amount = 5 } + + it 'returns formatted amount' do + expect(subject.display_price).to eq('$5.00') + end + end + + describe "#total_before_tax" do + let(:shipping_rate) { build(:shipping_rate, cost: 4) } + + subject { shipping_rate.total_before_tax } + + it { is_expected.to eq(4) } + + context "with discounts" do + let(:shipping_rate) { build(:shipping_rate, cost: 4, discounts: discounts) } + let(:discounts) { build_list(:friendly_shipping_rate_discount, 2, amount: -1.5, label: "DISCOUNT") } + + it { is_expected.to eq(1) } + end + end + + describe "#display_total_before_tax" do + let(:shipping_rate) { build(:shipping_rate, cost: 10) } + + subject { shipping_rate.display_total_before_tax } + + it { is_expected.to eq(Spree::Money.new("10.00")) } + end + + describe "#display_promo_total" do + let(:shipping_rate) { build(:shipping_rate) } + + subject { shipping_rate.display_promo_total } + + it { is_expected.to eq(Spree::Money.new("0")) } + end +end From a469442fa7e91ddfb080bf501040b3378ba50837 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 25 Jul 2023 10:23:41 +0200 Subject: [PATCH 295/834] Allow Shipment discounter to also discount rates Because we've abstracted away the type of adjustment, the `AdjustShipment` action can now also discount shipping rates. --- .../actions/adjust_shipment.rb | 2 +- .../actions/adjust_shipment_spec.rb | 22 +++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb index 0e252f7b05a..82d73533943 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb @@ -4,7 +4,7 @@ module SolidusFriendlyPromotions module Actions class AdjustShipment < PromotionAction def can_discount?(object) - object.is_a? Spree::Shipment + object.is_a?(Spree::Shipment) || object.is_a?(Spree::ShippingRate) end def available_calculators diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_shipment_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_shipment_spec.rb index 5495a3eb512..fdd07df85e0 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_shipment_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_shipment_spec.rb @@ -10,4 +10,26 @@ it { is_expected.to eq("Discount matching shipments") } end + + describe "#can_discount?" do + subject { action.can_discount?(promotable) } + + context "with a line item" do + let(:promotable) { Spree::LineItem.new } + + it { is_expected.to be false } + end + + context "with a shipment" do + let(:promotable) { Spree::Shipment.new } + + it { is_expected.to be true } + end + + context "with a shipping rate" do + let(:promotable) { Spree::ShippingRate.new } + + it { is_expected.to be true } + end + end end From 78823cd5d92797b9b17de30875b250772eebfc77 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 25 Jul 2023 11:52:01 +0200 Subject: [PATCH 296/834] Add Shipping method rule The first rule that applies to shipments and shipping rates. --- .../rules/shipping_method.rb | 21 ++++++ .../rules/_shipping_method.html.erb | 7 ++ .../install/templates/initializer.rb | 4 +- .../rules/shipping_method_spec.rb | 67 +++++++++++++++++++ 4 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/rules/shipping_method.rb create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_shipping_method.html.erb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/rules/shipping_method_spec.rb diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/shipping_method.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/shipping_method.rb new file mode 100644 index 00000000000..46b118f9002 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/shipping_method.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module Rules + class ShippingMethod < PromotionRule + preference :shipping_method_ids, type: :array, default: [] + + def applicable?(promotable) + promotable.is_a?(Spree::Shipment) || promotable.is_a?(Spree::ShippingRate) + end + + def eligible?(promotable) + promotable.shipping_method&.id.in?(preferred_shipping_method_ids.map(&:to_i)) + end + + def updateable? + true + end + end + end +end diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_shipping_method.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_shipping_method.html.erb new file mode 100644 index 00000000000..fc5954ff32e --- /dev/null +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_shipping_method.html.erb @@ -0,0 +1,7 @@ +
    + <%= label_tag "#{param_prefix}_preferred_shipping_method_ids", SolidusFriendlyShipping::Rules::ShippingMethod.human_attribute_name(:preferred_shipping_method_ids) %> + <%= select_tag "#{param_prefix}[preferred_shipping_method_ids]", + options_from_collection_for_select( + Spree::ShippingMethod.all, :id, :name, promotion_rule.preferred_shipping_method_ids + ), class: 'select2 fullwidth', multiple: true %> +
    diff --git a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb index 1b77f2b6ea4..0dca999a063 100644 --- a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb +++ b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb @@ -66,7 +66,9 @@ "SolidusFriendlyPromotions::Rules::LineItemProduct", "SolidusFriendlyPromotions::Rules::LineItemTaxon", ] - config.shipment_rules = [] + config.shipment_rules = [ + "SolidusFriendlyPromotions::Rules::ShippingMethod", + ] config.actions = [ "SolidusFriendlyPromotions::Actions::AdjustLineItem", diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/shipping_method_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/shipping_method_spec.rb new file mode 100644 index 00000000000..1ee9f66b5ed --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/shipping_method_spec.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe SolidusFriendlyPromotions::Rules::ShippingMethod, type: :model do + it { is_expected.to respond_to(:preferred_shipping_method_ids) } + + let(:rule) { described_class.new } + + describe "preferred_shipping_methods_ids=" do + let!(:promotion) { create(:friendly_promotion) } + let(:ups_ground) { create(:shipping_method) } + let(:dhl_saver) { create(:shipping_method) } + let(:rule) { promotion.rules.build(type: described_class.to_s) } + + subject { rule.preferred_shipping_method_ids = [ups_ground.id] } + + it "creates a valid rule with a shipping method" do + subject + expect(rule).to be_valid + expect(rule.preferred_shipping_method_ids).to include(ups_ground.id) + end + end + + describe "#eligible?" do + let!(:promotion) { create(:friendly_promotion) } + let(:ups_ground) { create(:shipping_method) } + let(:dhl_saver) { create(:shipping_method) } + let(:rule) { promotion.rules.build(type: described_class.to_s, preferred_shipping_method_ids: [ups_ground.id]) } + + subject { rule.eligible?(promotable) } + + context "with a shipment" do + context "when the shipment has the right shipping method selected" do + let(:promotable) { create(:shipment, shipping_method: ups_ground) } + + it { is_expected.to be true } + end + + context "when the shipment does not have the right shipping method selected" do + let(:promotable) { create(:shipment, shipping_method: dhl_saver) } + + it { is_expected.to be false } + end + + context "when the shipment has no shipping method selected" do + let(:promotable) { create(:shipment, shipping_method: nil) } + + it { is_expected.to be false } + end + end + + context "with a shipping rate" do + context "when the shipping rate has the right shipping method selected" do + let(:promotable) { create(:shipping_rate, shipping_method: ups_ground) } + + it { is_expected.to be true } + end + + context "when the shipping rate does not have the right shipping method selected" do + let(:promotable) { create(:shipping_rate, shipping_method: dhl_saver) } + + it { is_expected.to be false } + end + end + end +end From 87b7e702723a214cc2ce0687a9ec0bbe16ff276c Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 25 Jul 2023 11:52:14 +0200 Subject: [PATCH 297/834] Fix Shipment Discounter Oops, an empty array is truthy. The select already happens in `PromotionEligibility`. --- .../solidus_friendly_promotions/shipment_discounter.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/shipment_discounter.rb b/friendly_promotions/app/models/solidus_friendly_promotions/shipment_discounter.rb index 5d9a59c22a2..01e3fbc7df9 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/shipment_discounter.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/shipment_discounter.rb @@ -9,9 +9,10 @@ def initialize(promotions:) end def call(shipment) - eligible_promotions = promotions.select do |promotion| - PromotionEligibility.new(promotable: shipment, possible_promotions: promotions).call - end + eligible_promotions = PromotionEligibility.new( + promotable: shipment, + possible_promotions: promotions + ).call possible_adjustments = eligible_promotions.flat_map do |promotion| promotion.actions.select do |action| From af7bd26f276bd21b2f8a686c04362c529c67e104 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 25 Jul 2023 11:53:45 +0200 Subject: [PATCH 298/834] Add shipment integration spec --- .../spec/models/promotion/integration_spec.rb | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/friendly_promotions/spec/models/promotion/integration_spec.rb b/friendly_promotions/spec/models/promotion/integration_spec.rb index 37f67bbbe92..58763a17d20 100644 --- a/friendly_promotions/spec/models/promotion/integration_spec.rb +++ b/friendly_promotions/spec/models/promotion/integration_spec.rb @@ -50,4 +50,63 @@ end end end + + context "with a shipment-level rule" do + let!(:ups_ground) { create(:shipping_method) } + let!(:dhl_saver) { create(:shipping_method) } + let(:promotion) { create(:friendly_promotion, name: "20 percent off UPS Ground", apply_automatically: true) } + let(:rule) { SolidusFriendlyPromotions::Rules::ShippingMethod.new(preferred_shipping_method_ids: [ups_ground.id]) } + let(:order) { create(:order_with_line_items, shipping_method: shipping_method) } + + before do + promotion.rules << rule + promotion.actions << action + end + + context "with a line item level action" do + let(:calculator) { SolidusFriendlyPromotions::Calculators::Percent.new(preferred_percent: 20) } + let(:action) { SolidusFriendlyPromotions::Actions::AdjustLineItem.new(calculator: calculator) } + let(:shipping_method) { ups_ground } + + it "creates adjustments" do + expect(order.adjustments).to be_empty + expect(order.total).to eq(139.98) + expect(order.item_total).to eq(49.98) + expect(order.item_total_before_tax).to eq(39.98) + expect(order.promo_total).to eq(-10) + expect(order.line_items.flat_map(&:adjustments).length).to eq(3) + end + end + + context "with a shipment level action" do + let(:calculator) { SolidusFriendlyPromotions::Calculators::Percent.new(preferred_percent: 20) } + let(:action) { SolidusFriendlyPromotions::Actions::AdjustShipment.new(calculator: calculator) } + + context "when the order is eligible" do + let(:shipping_method) { ups_ground } + it "creates adjustments" do + expect(order.adjustments).to be_empty + expect(order.total).to eq(90) + expect(order.item_total).to eq(10) + expect(order.item_total_before_tax).to eq(10) + expect(order.line_items.flat_map(&:adjustments)).to be_empty + expect(order.shipments.flat_map(&:adjustments)).not_to be_empty + end + end + + context "when the order is not eligible" do + let(:shipping_method) { dhl_saver } + + it "creates no adjustments" do + expect(order.adjustments).to be_empty + expect(order.total).to eq(110) + expect(order.item_total).to eq(10) + expect(order.item_total_before_tax).to eq(10) + expect(order.promo_total).to eq(0) + expect(order.line_items.flat_map(&:adjustments)).to be_empty + expect(order.shipments.flat_map(&:adjustments)).to be_empty + end + end + end + end end From 09561dd735ff802943caa978b55d1630949efac7 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 25 Jul 2023 11:58:21 +0200 Subject: [PATCH 299/834] Remove useless variable --- .../models/solidus_friendly_promotions/line_item_discounter.rb | 2 +- .../models/solidus_friendly_promotions/shipment_discounter.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/line_item_discounter.rb b/friendly_promotions/app/models/solidus_friendly_promotions/line_item_discounter.rb index 4edb6a3a0ef..1436104466e 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/line_item_discounter.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/line_item_discounter.rb @@ -16,7 +16,7 @@ def call(line_item) possible_promotions: promotions ).call - possible_adjustments = eligible_promotions.flat_map do |promotion| + eligible_promotions.flat_map do |promotion| promotion.actions.select do |action| action.can_discount?(line_item) end.map do |action| diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/shipment_discounter.rb b/friendly_promotions/app/models/solidus_friendly_promotions/shipment_discounter.rb index 01e3fbc7df9..d5e5fbf29cb 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/shipment_discounter.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/shipment_discounter.rb @@ -14,7 +14,7 @@ def call(shipment) possible_promotions: promotions ).call - possible_adjustments = eligible_promotions.flat_map do |promotion| + eligible_promotions.flat_map do |promotion| promotion.actions.select do |action| action.can_discount?(shipment) end.map do |action| From 6c859b6598c6a7d874ad7e5ad1839a679e91c247 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 25 Jul 2023 12:07:24 +0200 Subject: [PATCH 300/834] Refactor: Only use a single item discounter --- .../friendly_promotion_discounter.rb | 11 ++++---- ..._item_discounter.rb => item_discounter.rb} | 4 +-- .../shipment_discounter.rb | 26 ------------------- 3 files changed, 7 insertions(+), 34 deletions(-) rename friendly_promotions/app/models/solidus_friendly_promotions/{line_item_discounter.rb => item_discounter.rb} (86%) delete mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/shipment_discounter.rb diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb b/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb index 9778723d96f..70ed1c316cd 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb @@ -2,11 +2,12 @@ module SolidusFriendlyPromotions class FriendlyPromotionDiscounter - attr_reader :order, :promotions + attr_reader :order, :promotions, :item_discounter def initialize(order) @order = order @promotions = PromotionEligibility.new(promotable: order, possible_promotions: possible_promotions).call + @item_discounter = ItemDiscounter.new(promotions: promotions) end def call @@ -20,13 +21,13 @@ def call private def adjust_line_items - line_item_adjuster = LineItemDiscounter.new(promotions: promotions) - order.line_items.flat_map { |line_item| line_item_adjuster.call(line_item) } + order.line_items.select do |line_item| + line_item.variant.product.promotionable? + end.flat_map { |line_item| item_discounter.call(line_item) } end def adjust_shipments - shipment_adjuster = ShipmentDiscounter.new(promotions: promotions) - order.shipments.flat_map { |shipment| shipment_adjuster.call(shipment) } + order.shipments.flat_map { |shipment| item_discounter.call(shipment) } end def possible_promotions diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/line_item_discounter.rb b/friendly_promotions/app/models/solidus_friendly_promotions/item_discounter.rb similarity index 86% rename from friendly_promotions/app/models/solidus_friendly_promotions/line_item_discounter.rb rename to friendly_promotions/app/models/solidus_friendly_promotions/item_discounter.rb index 1436104466e..296fcc9eea5 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/line_item_discounter.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/item_discounter.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module SolidusFriendlyPromotions - class LineItemDiscounter + class ItemDiscounter attr_reader :promotions def initialize(promotions:) @@ -9,8 +9,6 @@ def initialize(promotions:) end def call(line_item) - return [] unless line_item.variant.product.promotionable? - eligible_promotions = PromotionEligibility.new( promotable: line_item, possible_promotions: promotions diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/shipment_discounter.rb b/friendly_promotions/app/models/solidus_friendly_promotions/shipment_discounter.rb deleted file mode 100644 index d5e5fbf29cb..00000000000 --- a/friendly_promotions/app/models/solidus_friendly_promotions/shipment_discounter.rb +++ /dev/null @@ -1,26 +0,0 @@ -# frozen_string_literal: true - -module SolidusFriendlyPromotions - class ShipmentDiscounter - attr_reader :promotions - - def initialize(promotions:) - @promotions = promotions - end - - def call(shipment) - eligible_promotions = PromotionEligibility.new( - promotable: shipment, - possible_promotions: promotions - ).call - - eligible_promotions.flat_map do |promotion| - promotion.actions.select do |action| - action.can_discount?(shipment) - end.map do |action| - action.discount(shipment) - end - end - end - end -end From 09b3165058f293c3fb635df4f2d0bdffc428ed55 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 25 Jul 2023 12:33:09 +0200 Subject: [PATCH 301/834] Fix promotion integration spec The system behaves correctly, the specs were off. --- .../spec/models/promotion/integration_spec.rb | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/friendly_promotions/spec/models/promotion/integration_spec.rb b/friendly_promotions/spec/models/promotion/integration_spec.rb index 58763a17d20..6d76ee7f632 100644 --- a/friendly_promotions/spec/models/promotion/integration_spec.rb +++ b/friendly_promotions/spec/models/promotion/integration_spec.rb @@ -70,11 +70,12 @@ it "creates adjustments" do expect(order.adjustments).to be_empty - expect(order.total).to eq(139.98) - expect(order.item_total).to eq(49.98) - expect(order.item_total_before_tax).to eq(39.98) - expect(order.promo_total).to eq(-10) - expect(order.line_items.flat_map(&:adjustments).length).to eq(3) + expect(order.total).to eq(108.00) + expect(order.item_total).to eq(10) + expect(order.item_total_before_tax).to eq(8) + expect(order.promo_total).to eq(-2) + expect(order.line_items.flat_map(&:adjustments).length).to eq(1) + expect(order.shipments.flat_map(&:adjustments)).to be_empty end end From 41971823759619ee4a3cabe0c3336f19be7ab66f Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 25 Jul 2023 12:33:48 +0200 Subject: [PATCH 302/834] Refactor item discounter it's not line items --- .../models/solidus_friendly_promotions/item_discounter.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/item_discounter.rb b/friendly_promotions/app/models/solidus_friendly_promotions/item_discounter.rb index 296fcc9eea5..e1fd61a16ba 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/item_discounter.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/item_discounter.rb @@ -8,17 +8,17 @@ def initialize(promotions:) @promotions = promotions end - def call(line_item) + def call(item) eligible_promotions = PromotionEligibility.new( - promotable: line_item, + promotable: item, possible_promotions: promotions ).call eligible_promotions.flat_map do |promotion| promotion.actions.select do |action| - action.can_discount?(line_item) + action.can_discount?(item) end.map do |action| - action.discount(line_item) + action.discount(item) end end end From bb2ed6ea57118eaa58787640cb58be03afd8a4b0 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 25 Jul 2023 13:56:08 +0200 Subject: [PATCH 303/834] Add support for shipping rate discounts --- .../friendly_promotion_discounter.rb | 7 ++- .../order_discounter.rb | 9 ++++ .../order_discounts.rb | 2 +- .../spec/models/promotion/integration_spec.rb | 44 +++++++++++++------ .../order_discounts_spec.rb | 1 + 5 files changed, 48 insertions(+), 15 deletions(-) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb b/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb index 70ed1c316cd..061a0be395e 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb @@ -14,7 +14,8 @@ def call OrderDiscounts.new( order_id: order.id, line_item_discounts: adjust_line_items, - shipment_discounts: adjust_shipments + shipment_discounts: adjust_shipments, + shipping_rate_discounts: adjust_shipping_rates ) end @@ -30,6 +31,10 @@ def adjust_shipments order.shipments.flat_map { |shipment| item_discounter.call(shipment) } end + def adjust_shipping_rates + order.shipments.flat_map(&:shipping_rates).flat_map { |rate| item_discounter.call(rate) } + end + def possible_promotions promos = connected_order_promotions | sale_promotions promos.flat_map(&:actions).group_by(&:preload_relations).each do |preload_relations, actions| diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb b/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb index d08751ebfb8..8db13a4855d 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb @@ -25,6 +25,15 @@ def call update_adjustments(item, chosen_item_discounts) end + @order.shipments.flat_map(&:shipping_rates).each do |item| + all_item_discounts = all_order_discounts.flat_map(&:shipping_rate_discounts) + item_discounts = all_item_discounts.select { |element| element.item == item } + chosen_item_discounts = SolidusFriendlyPromotions.config.discount_chooser_class.new(item).call(item_discounts) + item.discounts = chosen_item_discounts.map do |discount| + SolidusFriendlyPromotions::ShippingRateDiscount.new(shipping_rate: item, amount: discount.amount, label: discount.label) + end + end + @order.promo_total = (order.line_items + order.shipments).sum { |item| item.promo_total } @order end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/order_discounts.rb b/friendly_promotions/app/models/solidus_friendly_promotions/order_discounts.rb index 83b3174c127..df340659703 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/order_discounts.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/order_discounts.rb @@ -13,6 +13,6 @@ module SolidusFriendlyPromotions # discount data for the order's shipments class OrderDiscounts include ActiveModel::Model - attr_accessor :order_id, :line_item_discounts, :shipment_discounts + attr_accessor :order_id, :line_item_discounts, :shipment_discounts, :shipping_rate_discounts end end diff --git a/friendly_promotions/spec/models/promotion/integration_spec.rb b/friendly_promotions/spec/models/promotion/integration_spec.rb index 6d76ee7f632..b12c83cb3e8 100644 --- a/friendly_promotions/spec/models/promotion/integration_spec.rb +++ b/friendly_promotions/spec/models/promotion/integration_spec.rb @@ -52,15 +52,29 @@ end context "with a shipment-level rule" do - let!(:ups_ground) { create(:shipping_method) } - let!(:dhl_saver) { create(:shipping_method) } + let!(:address) { create(:address) } + let(:shipping_zone) { create(:global_zone) } + let(:store) { create(:store) } + let!(:ups_ground) { create(:shipping_method, zones: [shipping_zone], cost: 23) } + let!(:dhl_saver) { create(:shipping_method, zones: [shipping_zone], cost: 37) } + let(:variant) { create(:variant, price: 13) } let(:promotion) { create(:friendly_promotion, name: "20 percent off UPS Ground", apply_automatically: true) } let(:rule) { SolidusFriendlyPromotions::Rules::ShippingMethod.new(preferred_shipping_method_ids: [ups_ground.id]) } - let(:order) { create(:order_with_line_items, shipping_method: shipping_method) } + let(:order) { Spree::Order.create!(store: store) } before do promotion.rules << rule promotion.actions << action + + order.contents.add(variant, 1) + order.ship_address = address + order.bill_address = address + + order.create_proposed_shipments + + order.shipments.first.selected_shipping_rate_id = order.shipments.first.shipping_rates.detect { |r| r.shipping_method == shipping_method }.id + + order.recalculate end context "with a line item level action" do @@ -70,12 +84,13 @@ it "creates adjustments" do expect(order.adjustments).to be_empty - expect(order.total).to eq(108.00) - expect(order.item_total).to eq(10) - expect(order.item_total_before_tax).to eq(8) - expect(order.promo_total).to eq(-2) + expect(order.total).to eq(33.40) + expect(order.item_total).to eq(13) + expect(order.item_total_before_tax).to eq(10.40) + expect(order.promo_total).to eq(-2.60) expect(order.line_items.flat_map(&:adjustments).length).to eq(1) expect(order.shipments.flat_map(&:adjustments)).to be_empty + expect(order.shipments.flat_map(&:shipping_rates).flat_map(&:discounts)).to be_empty end end @@ -87,11 +102,13 @@ let(:shipping_method) { ups_ground } it "creates adjustments" do expect(order.adjustments).to be_empty - expect(order.total).to eq(90) - expect(order.item_total).to eq(10) - expect(order.item_total_before_tax).to eq(10) + expect(order.total).to eq(31.40) + expect(order.item_total).to eq(13) + expect(order.item_total_before_tax).to eq(13) + expect(order.promo_total).to eq(-4.6) expect(order.line_items.flat_map(&:adjustments)).to be_empty expect(order.shipments.flat_map(&:adjustments)).not_to be_empty + expect(order.shipments.flat_map(&:shipping_rates).flat_map(&:discounts)).not_to be_empty end end @@ -100,12 +117,13 @@ it "creates no adjustments" do expect(order.adjustments).to be_empty - expect(order.total).to eq(110) - expect(order.item_total).to eq(10) - expect(order.item_total_before_tax).to eq(10) + expect(order.total).to eq(50) + expect(order.item_total).to eq(13) + expect(order.item_total_before_tax).to eq(13) expect(order.promo_total).to eq(0) expect(order.line_items.flat_map(&:adjustments)).to be_empty expect(order.shipments.flat_map(&:adjustments)).to be_empty + expect(order.shipments.flat_map(&:shipping_rates).flat_map(&:discounts)).not_to be_empty end end end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/order_discounts_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/order_discounts_spec.rb index 2654b2c452d..9fe907d0f5a 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/order_discounts_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/order_discounts_spec.rb @@ -4,4 +4,5 @@ it { is_expected.to respond_to :order_id } it { is_expected.to respond_to :line_item_discounts } it { is_expected.to respond_to :shipment_discounts } + it { is_expected.to respond_to :shipping_rate_discounts } end From 7a08a966b63596c722a73e366b88cb868a2f20f5 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 25 Jul 2023 14:50:58 +0200 Subject: [PATCH 304/834] Rubocop -A --- .../admin/promotion_actions_controller.rb | 22 ++-- .../admin/promotion_codes_controller.rb | 16 ++- .../admin/promotion_rules_controller.rb | 23 ++-- .../admin/promotions_controller.rb | 9 +- .../order_decorator.rb | 8 +- .../shipping_rate_decorator.rb | 34 +++--- .../promotion_code_batch_job.rb | 14 +-- .../app/models/solidus_friendly_promotions.rb | 2 + .../calculators/distributed_amount.rb | 2 + .../calculators/flexi_rate.rb | 2 +- .../calculators/tiered_flat_rate.rb | 2 +- .../calculators/tiered_percent.rb | 2 +- .../order_discounter.rb | 10 +- .../solidus_friendly_promotions/promotion.rb | 9 +- .../promotion_action.rb | 3 +- .../promotion_code.rb | 8 +- .../promotion_code/batch_builder.rb | 9 +- .../promotion_code_batch.rb | 12 +-- .../promotion_rule.rb | 6 +- .../rules/first_order.rb | 6 +- .../rules/first_repeat_purchase_since.rb | 2 +- .../rules/item_total.rb | 6 +- .../rules/line_item_product.rb | 2 +- .../rules/line_item_taxon.rb | 4 +- .../rules/nth_order.rb | 2 +- .../rules/one_use_per_user.rb | 6 +- .../rules/product.rb | 5 +- .../rules/taxon.rb | 24 +++-- .../rules/user_logged_in.rb | 2 +- .../rules/user_role.rb | 3 +- .../simple_order_contents.rb | 1 - friendly_promotions/config/importmap.rb | 8 +- .../solidus_friendly_promotions.rb | 2 + ...25074235_create_shipping_rate_discounts.rb | 2 +- .../install/install_generator.rb | 2 +- .../install/templates/initializer.rb | 1 + .../configuration.rb | 3 +- .../testing_support/factories.rb | 2 +- .../testing_support/friendly_order_factory.rb | 1 - .../solidus_friendly_promotions.gemspec | 2 +- .../promotion_code_batch_job_spec.rb | 11 +- .../promotion_code_batch_mailer_spec.rb | 3 +- .../spec/models/promotion/integration_spec.rb | 5 +- .../actions/adjust_line_item_spec.rb | 1 + .../calculators/distributed_amount_spec.rb | 10 +- .../calculators/flat_rate_spec.rb | 12 ++- .../calculators/flexi_rate_spec.rb | 75 ++++++++----- .../calculators/tiered_flat_rate_spec.rb | 32 ++++-- .../calculators/tiered_percent_spec.rb | 41 +++++-- .../friendly_promotion_discounter_spec.rb | 11 +- .../item_discount_spec.rb | 1 - .../order_discounter_spec.rb | 20 +++- .../order_promotion_spec.rb | 1 + .../promotion_action_spec.rb | 9 +- .../promotion_category_spec.rb | 4 +- .../promotion_code/batch_builder_spec.rb | 4 +- .../promotion_code_batch_spec.rb | 10 +- .../promotion_code_spec.rb | 33 ++++-- .../promotion_spec.rb | 101 ++++++++++++------ .../rules/first_order_spec.rb | 19 +++- .../rules/first_repeat_purchase_since_spec.rb | 4 +- .../rules/item_total_spec.rb | 11 +- .../rules/line_item_product_spec.rb | 12 ++- .../rules/nth_order_spec.rb | 3 +- .../rules/one_use_per_user_spec.rb | 6 ++ .../rules/option_value_spec.rb | 26 +++-- .../rules/product_spec.rb | 35 +++--- .../rules/shipping_method_spec.rb | 12 +-- .../rules/store_spec.rb | 10 +- .../rules/taxon_spec.rb | 33 +++--- .../rules/user_logged_in_spec.rb | 9 +- .../rules/user_role_spec.rb | 46 ++++---- .../rules/user_spec.rb | 15 +-- .../simple_order_contents_spec.rb | 59 +++++----- .../spec/models/spree/shipping_rate_spec.rb | 14 +-- .../admin/promotion_actions_request_spec.rb | 6 +- .../admin/promotion_rules_request_spec.rb | 4 +- .../calculator_shared_examples.rb | 1 + .../admin/promotion_categories_spec.rb | 16 +-- .../admin/promotions_spec.rb | 22 +++- 80 files changed, 661 insertions(+), 365 deletions(-) diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb index 8581e38a7ba..69b307f8d6e 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb @@ -23,7 +23,8 @@ def create @promotion_action = @promotion_action_type.new(promotion_action_params) @promotion_action.promotion = @promotion if @promotion_action.save(validate: false) - flash[:success] = t('spree.successfully_created', resource: SolidusFriendlyPromotions::PromotionAction.model_name.human) + flash[:success] = + t('spree.successfully_created', resource: SolidusFriendlyPromotions::PromotionAction.model_name.human) redirect_to location_after_save, format: :html else render :new, layout: false @@ -34,7 +35,8 @@ def update @promotion_action = @promotion.actions.find(params[:id]) @promotion_action.assign_attributes(promotion_action_params) if @promotion_action.save - flash[:success] = t('spree.successfully_updated', resource: SolidusFriendlyPromotions::PromotionAction.model_name.human) + flash[:success] = + t('spree.successfully_updated', resource: SolidusFriendlyPromotions::PromotionAction.model_name.human) redirect_to location_after_save, format: :html else render :edit @@ -44,7 +46,8 @@ def update def destroy @promotion_action = @promotion.actions.find(params[:id]) if @promotion_action.discard - flash[:success] = t('spree.successfully_removed', resource: SolidusFriendlyPromotions::PromotionAction.model_name.human) + flash[:success] = + t('spree.successfully_removed', resource: SolidusFriendlyPromotions::PromotionAction.model_name.human) end redirect_to location_after_save, format: :html end @@ -69,7 +72,6 @@ def validate_level end end - def promotion_action_params params[:promotion_action].try(:permit!) || {} end @@ -80,12 +82,12 @@ def validate_promotion_action_type @promotion_action_type = promotion_action_types.detect do |klass| klass.name == requested_type end - if !@promotion_action_type - flash[:error] = t('solidus_friendly_promotions.invalid_promotion_action') - respond_to do |format| - format.html { redirect_to solidus_friendly_promotions.edit_admin_promotion_path(@promotion) } - format.js { render layout: false } - end + return if @promotion_action_type + + flash[:error] = t('solidus_friendly_promotions.invalid_promotion_action') + respond_to do |format| + format.html { redirect_to solidus_friendly_promotions.edit_admin_promotion_path(@promotion) } + format.js { render layout: false } end end end diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_codes_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_codes_controller.rb index 42b857d0fbe..d77de5f734e 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_codes_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_codes_controller.rb @@ -5,8 +5,9 @@ module SolidusFriendlyPromotions module Admin class PromotionCodesController < Spree::Admin::ResourceController + before_action :load_promotion + def index - @promotion = SolidusFriendlyPromotions::Promotion.accessible_by(current_ability, :show).find(params[:promotion_id]) @promotion_codes = @promotion.codes.order(:value) respond_to do |format| @@ -22,9 +23,9 @@ def index end def new - @promotion = SolidusFriendlyPromotions::Promotion.accessible_by(current_ability, :show).find(params[:promotion_id]) if @promotion.apply_automatically - flash[:error] = t('activerecord.errors.models.solidus_friendly_promotions/promotion_code.attributes.base.disallowed_with_apply_automatically') + flash[:error] = + t('activerecord.errors.models.solidus_friendly_promotions/promotion_code.attributes.base.disallowed_with_apply_automatically') redirect_to solidus_friendly_promotions.admin_promotion_promotion_codes_url(@promotion) else @promotion_code = @promotion.codes.build @@ -32,7 +33,6 @@ def new end def create - @promotion = SolidusFriendlyPromotions::Promotion.accessible_by(current_ability, :show).find(params[:promotion_id]) @promotion_code = @promotion.codes.build(value: params[:promotion_code][:value]) if @promotion_code.save @@ -43,6 +43,14 @@ def create render_after_create_error end end + + private + + def load_promotion + @promotion = SolidusFriendlyPromotions::Promotion. + accessible_by(current_ability, :show). + find(params[:promotion_id]) + end end end end diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb index 9f0cb2c43d4..7ecf39e4db0 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb @@ -22,7 +22,8 @@ def create promotion_rule_params.merge(type: @promotion_rule_type.to_s) ) if @promotion_rule.save - flash[:success] = t('spree.successfully_created', resource: SolidusFriendlyPromotions::PromotionRule.model_name.human) + flash[:success] = + t('spree.successfully_created', resource: SolidusFriendlyPromotions::PromotionRule.model_name.human) end redirect_to location_after_save end @@ -31,7 +32,8 @@ def update @promotion_rule = @promotion.rules.find(params[:id]) @promotion_rule.assign_attributes(promotion_rule_params) if @promotion_rule.save - flash[:success] = t('spree.successfully_updated', resource: SolidusFriendlyPromotions::PromotionRule.model_name.human) + flash[:success] = + t('spree.successfully_updated', resource: SolidusFriendlyPromotions::PromotionRule.model_name.human) end redirect_to location_after_save end @@ -39,7 +41,8 @@ def update def destroy @promotion_rule = @promotion.rules.find(params[:id]) if @promotion_rule.destroy - flash[:success] = t('spree.successfully_removed', resource: SolidusFriendlyPromotions::PromotionRule.model_name.human) + flash[:success] = + t('spree.successfully_removed', resource: SolidusFriendlyPromotions::PromotionRule.model_name.human) end redirect_to location_after_save end @@ -64,12 +67,12 @@ def validate_promotion_rule_type @promotion_rule_type = promotion_rule_types.detect do |klass| klass.name == requested_type end - if !@promotion_rule_type - flash[:error] = t('solidus_friendly_promotions.invalid_promotion_rule') - respond_to do |format| - format.html { redirect_to solidus_friendly_promotions.edit_admin_promotion_path(@promotion) } - format.js { render layout: false } - end + return if @promotion_rule_type + + flash[:error] = t('solidus_friendly_promotions.invalid_promotion_rule') + respond_to do |format| + format.html { redirect_to solidus_friendly_promotions.edit_admin_promotion_path(@promotion) } + format.js { render layout: false } end end @@ -88,7 +91,7 @@ def promotion_rule_params end def promotion_rule_types - case params.dig(:level) + case params[:level] when "order" SolidusFriendlyPromotions.config.order_rules when "line_item" diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb index 48dc47aae63..46ecf67558c 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb @@ -17,7 +17,7 @@ def create end if @promotion.save - @code_batch.process if @code_batch + @code_batch&.process flash[:success] = t('solidus_friendly_promotions.promotion_successfully_created') redirect_to location_after_save else @@ -30,15 +30,16 @@ def create def collection return @collection if @collection + params[:q] ||= HashWithIndifferentAccess.new params[:q][:s] ||= 'id desc' @collection = super @search = @collection.ransack(params[:q]) @collection = @search.result(distinct: true). - includes(promotion_includes). - page(params[:page]). - per(params[:per_page] || SolidusFriendlyPromotions.config.promotions_per_page) + includes(promotion_includes). + page(params[:page]). + per(params[:per_page] || SolidusFriendlyPromotions.config.promotions_per_page) @collection end diff --git a/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_decorator.rb b/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_decorator.rb index e1b4e4d1d8e..991f7d7c4ad 100644 --- a/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_decorator.rb +++ b/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_decorator.rb @@ -1,8 +1,11 @@ # frozen_string_literal: true -module SolidusFriendlyPromotions::OrderDecorator +module SolidusFriendlyPromotions + module OrderDecorator def self.prepended(base) - base.has_many :friendly_order_promotions, class_name: "SolidusFriendlyPromotions::OrderPromotion", inverse_of: :order + base.has_many :friendly_order_promotions, + class_name: "SolidusFriendlyPromotions::OrderPromotion", + inverse_of: :order base.has_many :friendly_promotions, through: :friendly_order_promotions, source: :promotion end @@ -19,3 +22,4 @@ def ensure_promotions_eligible Spree::Order.prepend self end +end diff --git a/friendly_promotions/app/decorators/models/solidus_friendly_promotions/shipping_rate_decorator.rb b/friendly_promotions/app/decorators/models/solidus_friendly_promotions/shipping_rate_decorator.rb index 635bbf151e3..66cf3cf5cbf 100644 --- a/friendly_promotions/app/decorators/models/solidus_friendly_promotions/shipping_rate_decorator.rb +++ b/friendly_promotions/app/decorators/models/solidus_friendly_promotions/shipping_rate_decorator.rb @@ -1,25 +1,27 @@ # frozen_string_literal: true -module SolidusFriendlyPromotions::ShippingRateDecorator - def self.prepended(base) - base.class_eval do - has_many :discounts, - class_name: "SolidusFriendlyPromotions::ShippingRateDiscount", - foreign_key: :shipping_rate_id, - dependent: :destroy, - inverse_of: :shipping_rate, - autosave: true +module SolidusFriendlyPromotions + module ShippingRateDecorator + def self.prepended(base) + base.class_eval do + has_many :discounts, + class_name: "SolidusFriendlyPromotions::ShippingRateDiscount", + foreign_key: :shipping_rate_id, + dependent: :destroy, + inverse_of: :shipping_rate, + autosave: true - money_methods :total_before_tax, :promo_total + money_methods :total_before_tax, :promo_total + end end - end - def total_before_tax - amount + promo_total - end + def total_before_tax + amount + promo_total + end - def promo_total - discounts.sum(&:amount) + def promo_total + discounts.sum(&:amount) + end end end diff --git a/friendly_promotions/app/jobs/solidus_friendly_promotions/promotion_code_batch_job.rb b/friendly_promotions/app/jobs/solidus_friendly_promotions/promotion_code_batch_job.rb index 204bca2f01f..e580d7dfead 100644 --- a/friendly_promotions/app/jobs/solidus_friendly_promotions/promotion_code_batch_job.rb +++ b/friendly_promotions/app/jobs/solidus_friendly_promotions/promotion_code_batch_job.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module SolidusFriendlyPromotions - class PromotionCodeBatchJob < ActiveJob::Base + class PromotionCodeBatchJob < ApplicationJob queue_as :default def perform(promotion_code_batch) @@ -11,16 +11,16 @@ def perform(promotion_code_batch) if promotion_code_batch.email? SolidusFriendlyPromotions.config.promotion_code_batch_mailer_class - .promotion_code_batch_finished(promotion_code_batch) - .deliver_now + .promotion_code_batch_finished(promotion_code_batch) + .deliver_now end - rescue StandardError => error + rescue StandardError => e if promotion_code_batch.email? SolidusFriendlyPromotions.config.promotion_code_batch_mailer_class - .promotion_code_batch_errored(promotion_code_batch) - .deliver_now + .promotion_code_batch_errored(promotion_code_batch) + .deliver_now end - raise error + raise e end end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions.rb b/friendly_promotions/app/models/solidus_friendly_promotions.rb index 56b3908fe0c..db8181d1339 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module SolidusFriendlyPromotions def self.table_name_prefix 'friendly_' diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/distributed_amount.rb b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/distributed_amount.rb index 5513190d90e..c2d940b3af2 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/distributed_amount.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/distributed_amount.rb @@ -15,8 +15,10 @@ class DistributedAmount < Spree::Calculator def compute_line_item(line_item) return 0 unless line_item return 0 unless preferred_currency.casecmp(line_item.currency).zero? + distributable_line_items = eligible_line_items(line_item.order) return 0 unless line_item.in?(distributable_line_items) + DistributedAmountsHandler.new( distributable_line_items, preferred_amount diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/flexi_rate.rb b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/flexi_rate.rb index 2ab4ed53063..29efc3e9605 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/flexi_rate.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/flexi_rate.rb @@ -14,7 +14,7 @@ def compute(object) items_count = object.quantity items_count = [items_count, preferred_max_items].min unless preferred_max_items.zero? - return BigDecimal(0) if items_count == 0 + return BigDecimal('0') if items_count == 0 additional_items_count = items_count - 1 preferred_first_item + preferred_additional_item * additional_items_count diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_flat_rate.rb b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_flat_rate.rb index 77e04246fe7..b09845a14f2 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_flat_rate.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_flat_rate.rb @@ -39,7 +39,7 @@ def compute_item(object) def cast_to_d(value) value.to_s.to_d rescue ArgumentError - BigDecimal(0) + BigDecimal('0') end def preferred_tiers_content diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_percent.rb b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_percent.rb index 7e3f75d1005..28b725eee2f 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_percent.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_percent.rb @@ -46,7 +46,7 @@ def compute_item(object) def cast_to_d(value) value.to_s.to_d rescue ArgumentError - BigDecimal(0) + BigDecimal('0') end def preferred_tiers_content diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb b/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb index 8db13a4855d..a9f950a3897 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb @@ -30,16 +30,20 @@ def call item_discounts = all_item_discounts.select { |element| element.item == item } chosen_item_discounts = SolidusFriendlyPromotions.config.discount_chooser_class.new(item).call(item_discounts) item.discounts = chosen_item_discounts.map do |discount| - SolidusFriendlyPromotions::ShippingRateDiscount.new(shipping_rate: item, amount: discount.amount, label: discount.label) + SolidusFriendlyPromotions::ShippingRateDiscount.new( + shipping_rate: item, + amount: discount.amount, + label: discount.label + ) end end - @order.promo_total = (order.line_items + order.shipments).sum { |item| item.promo_total } + @order.promo_total = (order.line_items + order.shipments).sum(&:promo_total) @order end - private + attr_reader :order # Walk through the discounts for an item and update adjustments for it. Once diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb index 362ee580158..50cb94d9fcf 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb @@ -2,7 +2,8 @@ module SolidusFriendlyPromotions class Promotion < Spree::Base - belongs_to :category, class_name: "SolidusFriendlyPromotions::PromotionCategory", foreign_key: :promotion_category_id, optional: true + belongs_to :category, class_name: "SolidusFriendlyPromotions::PromotionCategory", + foreign_key: :promotion_category_id, optional: true has_many :rules, class_name: "SolidusFriendlyPromotions::PromotionRule" has_many :actions, class_name: "SolidusFriendlyPromotions::PromotionAction" has_many :codes, class_name: "SolidusFriendlyPromotions::PromotionCode" @@ -72,9 +73,9 @@ def used_by?(user, excluded_orders = []) # @param excluded_orders [Array] Orders to exclude from usage limit # @return true or false def usage_limit_exceeded?(excluded_orders: []) - if usage_limit - usage_count(excluded_orders: excluded_orders) >= usage_limit - end + return unless usage_limit + + usage_count(excluded_orders: excluded_orders) >= usage_limit end def not_expired? diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb index 17be3ed46fe..2388485cadc 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require 'spree/preferences/persistable' module SolidusFriendlyPromotions @@ -36,7 +37,7 @@ def discount(adjustable) # Ensure a negative amount which does not exceed the object's amount def compute_amount(adjustable) - promotion_amount = calculator.compute(adjustable) || BigDecimal(0) + promotion_amount = calculator.compute(adjustable) || BigDecimal('0') [adjustable.amount, promotion_amount.abs].min * -1 end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code.rb index 6ed524a5b17..0f2a7c82fbd 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code.rb @@ -18,9 +18,9 @@ class PromotionCode < Spree::Base # @param excluded_orders [Array] Orders to exclude from usage limit # @return true or false def usage_limit_exceeded?(excluded_orders: []) - if usage_limit - usage_count(excluded_orders: excluded_orders) >= usage_limit - end + return unless usage_limit + + usage_count(excluded_orders: excluded_orders) >= usage_limit end # Number of times the code has been used overall @@ -33,7 +33,7 @@ def usage_count(excluded_orders: []) complete. where.not(spree_orders: { state: :canceled }). joins(:friendly_order_promotions). - where(friendly_order_promotions: { promotion_code_id: self.id }). + where(friendly_order_promotions: { promotion_code_id: id }). where.not(id: excluded_orders.map(&:id)). count end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code/batch_builder.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code/batch_builder.rb index 37dc95aead3..58ba96fb692 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code/batch_builder.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code/batch_builder.rb @@ -4,13 +4,14 @@ module SolidusFriendlyPromotions class PromotionCode < Spree::Base class BatchBuilder attr_reader :promotion_code_batch, :options + delegate :promotion, :number_of_codes, :base_code, to: :promotion_code_batch DEFAULT_OPTIONS = { random_code_length: 6, batch_size: 1000, sample_characters: ('a'..'z').to_a + (2..9).to_a.map(&:to_s) - } + }.freeze def initialize(promotion_code_batch, options = {}) @promotion_code_batch = promotion_code_batch @@ -21,12 +22,12 @@ def initialize(promotion_code_batch, options = {}) def build_promotion_codes generate_random_codes promotion_code_batch.update!(state: "completed") - rescue StandardError => error + rescue StandardError => e promotion_code_batch.update!( - error: error.inspect, + error: e.inspect, state: "failed" ) - raise error + raise e end private diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code_batch.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code_batch.rb index 135fbb8998f..cbb33bfb59b 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code_batch.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code_batch.rb @@ -9,19 +9,17 @@ class CantProcessStartedBatch < StandardError has_many :promotion_codes, dependent: :destroy validates :number_of_codes, numericality: { greater_than: 0 } - validates_presence_of :base_code, :number_of_codes + validates :base_code, :number_of_codes, presence: true def finished? state == "completed" end def process - if state == "pending" - update!(state: "processing") - PromotionCodeBatchJob.perform_later(self) - else - raise CantProcessStartedBatch.new("Batch #{id} already started") - end + raise CantProcessStartedBatch, "Batch #{id} already started" unless state == "pending" + + update!(state: "processing") + PromotionCodeBatchJob.perform_later(self) end end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rule.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rule.rb index 06181f15f24..79cf24e4d69 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rule.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rule.rb @@ -39,9 +39,9 @@ def updateable? private def unique_per_promotion - if self.class.exists?(promotion_id: promotion_id, type: self.class.name) - errors[:base] << "Promotion already contains this rule type" - end + return unless self.class.exists?(promotion_id: promotion_id, type: self.class.name) + + errors[:base] << "Promotion already contains this rule type" end def eligibility_error_message(key, options = {}) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_order.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_order.rb index bd4df181558..65ccd9921de 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_order.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_order.rb @@ -13,10 +13,8 @@ def eligible?(order, options = {}) @user = order.try(:user) || options[:user] @email = order.email - if user || email - if !completed_orders.blank? && completed_orders.first != order - eligibility_errors.add(:base, eligibility_error_message(:not_first_order), error_code: :not_first_order) - end + if (user || email) && (completed_orders.present? && completed_orders.first != order) + eligibility_errors.add(:base, eligibility_error_message(:not_first_order), error_code: :not_first_order) end eligibility_errors.empty? diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb index dfca964597d..1e23f2b2a98 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb @@ -4,7 +4,7 @@ module SolidusFriendlyPromotions module Rules class FirstRepeatPurchaseSince < PromotionRule preference :days_ago, :integer, default: 365 - validates :preferred_days_ago, numericality: {only_integer: true, greater_than: 0} + validates :preferred_days_ago, numericality: { only_integer: true, greater_than: 0 } # This promotion is applicable to orders only. def applicable?(promotable) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb index a1573067ea8..12df85239e9 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb @@ -68,7 +68,11 @@ def ineligible_message when "gt" eligibility_error_message(:item_total_less_than_or_equal, amount: formatted_amount) else - eligibility_error_message(:item_total_doesnt_match_with_operator, amount: formatted_amount, operator: preferred_operator) + eligibility_error_message( + :item_total_doesnt_match_with_operator, + amount: formatted_amount, + operator: preferred_operator + ) end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb index c6ee3621a60..b24e8e55a38 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb @@ -4,7 +4,7 @@ module SolidusFriendlyPromotions module Rules # A rule to apply a promotion only to line items with or without a chosen product class LineItemProduct < PromotionRule - MATCH_POLICIES = %w[include exclude] + MATCH_POLICIES = %w[include exclude].freeze has_many :product_promotion_rules, dependent: :destroy, diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb index 57bffa744a0..1308aacde98 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb @@ -7,9 +7,9 @@ class LineItemTaxon < PromotionRule dependent: :destroy has_many :taxons, through: :promotion_rule_taxons, class_name: "Spree::Taxon" - MATCH_POLICIES = %w[include exclude] + MATCH_POLICIES = %w[include exclude].freeze - validates_inclusion_of :preferred_match_policy, in: MATCH_POLICIES + validates :preferred_match_policy, inclusion: { in: MATCH_POLICIES } preference :match_policy, :string, default: MATCH_POLICIES.first def applicable?(promotable) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb index 04cb592bd6c..f542ef13250 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb @@ -6,7 +6,7 @@ class NthOrder < PromotionRule preference :nth_order, :integer, default: 2 # It does not make sense to have this apply to the first order using preferred_nth_order == 1 # Instead we could use the first_order rule - validates :preferred_nth_order, numericality: {only_integer: true, greater_than: 1} + validates :preferred_nth_order, numericality: { only_integer: true, greater_than: 1 } # This promotion is applicable to orders only. def applicable?(promotable) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb index 770d964371f..5c9d13a89ef 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb @@ -10,7 +10,11 @@ def applicable?(promotable) def eligible?(order, _options = {}) if order.user.present? if promotion.used_by?(order.user, [order]) - eligibility_errors.add(:base, eligibility_error_message(:limit_once_per_user), error_code: :limit_once_per_user) + eligibility_errors.add( + :base, + eligibility_error_message(:limit_once_per_user), + error_code: :limit_once_per_user + ) end else eligibility_errors.add(:base, eligibility_error_message(:no_user_specified), error_code: :no_user_specified) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb index 9ae2b78f76d..c5bbd4503be 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb @@ -13,14 +13,13 @@ class Product < PromotionRule class_name: 'SolidusFriendlyPromotions::ProductsPromotionRule' has_many :products, class_name: 'Spree::Product', through: :products_promotion_rules - def preload_relations [:products] end - MATCH_POLICIES = %w[any all none] + MATCH_POLICIES = %w[any all none].freeze - validates_inclusion_of :preferred_match_policy, in: MATCH_POLICIES + validates :preferred_match_policy, inclusion: { in: MATCH_POLICIES } preference :match_policy, :string, default: MATCH_POLICIES.first diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb index 38925b555cc..f972e4c1160 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb @@ -4,16 +4,16 @@ module SolidusFriendlyPromotions module Rules class Taxon < PromotionRule has_many :promotion_rules_taxons, class_name: 'SolidusFriendlyPromotions::PromotionRulesTaxon', foreign_key: :promotion_rule_id, - dependent: :destroy + dependent: :destroy has_many :taxons, through: :promotion_rules_taxons, class_name: 'Spree::Taxon' def preload_relations [:taxons] end - MATCH_POLICIES = %w[any all none] + MATCH_POLICIES = %w[any all none].freeze - validates_inclusion_of :preferred_match_policy, in: MATCH_POLICIES + validates :preferred_match_policy, inclusion: { in: MATCH_POLICIES } preference :match_policy, :string, default: MATCH_POLICIES.first def applicable?(promotable) @@ -34,11 +34,19 @@ def eligible?(order, _options = {}) end when "any" unless order_taxons.where(id: rule_taxon_ids_with_children).exists? - eligibility_errors.add(:base, eligibility_error_message(:no_matching_taxons), error_code: :no_matching_taxons) + eligibility_errors.add( + :base, + eligibility_error_message(:no_matching_taxons), + error_code: :no_matching_taxons + ) end when "none" if order_taxons.where(id: rule_taxon_ids_with_children).exists? - eligibility_errors.add(:base, eligibility_error_message(:has_excluded_taxon), error_code: :has_excluded_taxon) + eligibility_errors.add( + :base, + eligibility_error_message(:has_excluded_taxon), + error_code: :has_excluded_taxon + ) end else raise "unexpected match policy: #{preferred_match_policy.inspect}" @@ -63,8 +71,10 @@ def updateable? # All taxons in an order def taxons_in_order(order) - Spree::Taxon.joins(products: {variants_including_master: :line_items}) - .where(spree_line_items: {order_id: order.id}).distinct + Spree::Taxon. + joins(products: { variants_including_master: :line_items }). + where(spree_line_items: { order_id: order.id }). + distinct end # ids of taxons rules and taxons rules children diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb index f43e85083c9..5be6e2ea802 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb @@ -8,7 +8,7 @@ def applicable?(promotable) end def eligible?(order, _options = {}) - unless order.user.present? + if order.user.blank? eligibility_errors.add(:base, eligibility_error_message(:no_user_specified), error_code: :no_user_specified) end eligibility_errors.empty? diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_role.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_role.rb index 7763145e979..e8df12f71ba 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_role.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_role.rb @@ -5,7 +5,7 @@ module Rules class UserRole < PromotionRule preference :role_ids, :array, default: [] - MATCH_POLICIES = %w[any all] + MATCH_POLICIES = %w[any all].freeze preference :match_policy, default: MATCH_POLICIES.first def applicable?(promotable) @@ -14,6 +14,7 @@ def applicable?(promotable) def eligible?(order, _options = {}) return false unless order.user + if all_match_policy? match_all_roles?(order) else diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/simple_order_contents.rb b/friendly_promotions/app/models/solidus_friendly_promotions/simple_order_contents.rb index c66dfa8c979..015949aad9c 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/simple_order_contents.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/simple_order_contents.rb @@ -2,7 +2,6 @@ module SolidusFriendlyPromotions class SimpleOrderContents < Spree::OrderContents - def update_cart(params) if order.update(params) unless order.completed? diff --git a/friendly_promotions/config/importmap.rb b/friendly_promotions/config/importmap.rb index f0ffda37249..c9b8e9895a9 100644 --- a/friendly_promotions/config/importmap.rb +++ b/friendly_promotions/config/importmap.rb @@ -1,8 +1,12 @@ +# frozen_string_literal: true + pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true -pin_all_from SolidusFriendlyPromotions::Engine.root.join("app/javascript/solidus_friendly_promotions/controllers"), under: "solidus_friendly_promotions/controllers" -pin_all_from SolidusFriendlyPromotions::Engine.root.join("app/javascript/solidus_friendly_promotions/jquery"), under: "solidus_friendly_promotions/jquery" +pin_all_from SolidusFriendlyPromotions::Engine.root.join("app/javascript/solidus_friendly_promotions/controllers"), + under: "solidus_friendly_promotions/controllers" +pin_all_from SolidusFriendlyPromotions::Engine.root.join("app/javascript/solidus_friendly_promotions/jquery"), + under: "solidus_friendly_promotions/jquery" pin "solidus_friendly_promotions", to: "solidus_friendly_promotions.js", preload: true diff --git a/friendly_promotions/config/initializers/solidus_friendly_promotions.rb b/friendly_promotions/config/initializers/solidus_friendly_promotions.rb index d4678cdaad8..68f60b8fdc3 100644 --- a/friendly_promotions/config/initializers/solidus_friendly_promotions.rb +++ b/friendly_promotions/config/initializers/solidus_friendly_promotions.rb @@ -1 +1,3 @@ +# frozen_string_literal: true + require_relative SolidusFriendlyPromotions::Engine.root.join("app/models/solidus_friendly_promotions.rb") diff --git a/friendly_promotions/db/migrate/20230725074235_create_shipping_rate_discounts.rb b/friendly_promotions/db/migrate/20230725074235_create_shipping_rate_discounts.rb index bcbe7beb59a..2bb7c594f3e 100644 --- a/friendly_promotions/db/migrate/20230725074235_create_shipping_rate_discounts.rb +++ b/friendly_promotions/db/migrate/20230725074235_create_shipping_rate_discounts.rb @@ -2,7 +2,7 @@ class CreateShippingRateDiscounts < ActiveRecord::Migration[7.0] def change create_table :friendly_shipping_rate_discounts do |t| t.references :promotion_action, null: false, foreign_key: { to_table: :friendly_promotion_actions } - t.references :shipping_rate, null: false, foreign_key: { to_table: :spree_shipping_rates} + t.references :shipping_rate, null: false, foreign_key: { to_table: :spree_shipping_rates } t.decimal :amount, precision: 10, scale: 2, null: false t.string :label, null: false end diff --git a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/install_generator.rb b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/install_generator.rb index ec1ebee171f..7fb1a28b878 100644 --- a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/install_generator.rb +++ b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/install_generator.rb @@ -25,7 +25,7 @@ def add_migrations def mount_engine inject_into_file "config/routes.rb", " mount SolidusFriendlyPromotions::Engine => '/'\n", - before: %r{ mount Spree::Core::Engine.*}, + before: / mount Spree::Core::Engine.*/, verbose: true end diff --git a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb index 0dca999a063..b235d5ab74b 100644 --- a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb +++ b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb @@ -8,6 +8,7 @@ Spree::Backend::Config.configure do |config| config.menu_items = Spree::Backend::Config.menu_items.map do |item| next item unless item.url == :admin_promotions_path + Spree::BackendConfiguration::MenuItem.new( [:promotions, :promotion_categories], 'bullhorn', diff --git a/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb b/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb index a291e6a3b38..cb996be8f20 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb @@ -17,7 +17,8 @@ class Configuration < Spree::Preferences::Configuration add_class_set :discounters class_name_attribute :discount_chooser_class, default: "SolidusFriendlyPromotions::PromotionAdjustmentChooser" - class_name_attribute :promotion_code_batch_mailer_class, default: "SolidusFriendlyPromotions::PromotionCodeBatchMailer" + class_name_attribute :promotion_code_batch_mailer_class, + default: "SolidusFriendlyPromotions::PromotionCodeBatchMailer" # @!attribute [rw] promotions_per_page # @return [Integer] Promotions to show per-page in the admin (default: +25+) diff --git a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/factories.rb b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/factories.rb index ced5cd0d102..7c8483323fe 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/factories.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/factories.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions def self.table_name_prefix - "friendly_" + "friendly_" end end require "solidus_friendly_promotions/testing_support/friendly_promotion_code_factory" diff --git a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_order_factory.rb b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_order_factory.rb index 04819d8b05b..33388b50487 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_order_factory.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_order_factory.rb @@ -1,4 +1,3 @@ - # frozen_string_literal: true FactoryBot.define do diff --git a/friendly_promotions/solidus_friendly_promotions.gemspec b/friendly_promotions/solidus_friendly_promotions.gemspec index af999ef9f5d..8db44d9ff47 100644 --- a/friendly_promotions/solidus_friendly_promotions.gemspec +++ b/friendly_promotions/solidus_friendly_promotions.gemspec @@ -32,8 +32,8 @@ Gem::Specification.new do |spec| spec.add_dependency "solidus_support", "~> 0.5" spec.add_dependency "turbo-rails", "~> 1.4" - spec.add_development_dependency "solidus_dev_support", "~> 2.6" spec.add_development_dependency "importmap-rails", "~> 1.2" spec.add_development_dependency "rspec-activemodel-mocks", "~> 1.0" spec.add_development_dependency "shoulda-matchers", "~> 5.3" + spec.add_development_dependency "solidus_dev_support", "~> 2.6" end diff --git a/friendly_promotions/spec/jobs/solidus_friendly_promotions/promotion_code_batch_job_spec.rb b/friendly_promotions/spec/jobs/solidus_friendly_promotions/promotion_code_batch_job_spec.rb index 0ad6ca3b47a..46a497b8702 100644 --- a/friendly_promotions/spec/jobs/solidus_friendly_promotions/promotion_code_batch_job_spec.rb +++ b/friendly_promotions/spec/jobs/solidus_friendly_promotions/promotion_code_batch_job_spec.rb @@ -11,6 +11,7 @@ email: email ) end + context "with a successful build" do before do allow(SolidusFriendlyPromotions::PromotionCodeBatchMailer) @@ -30,6 +31,7 @@ def codes end end end + context 'with a custom join character' do let(:code_batch) do SolidusFriendlyPromotions::PromotionCodeBatch.create!( @@ -40,6 +42,7 @@ def codes join_characters: '-' ) end + it 'uses the custom join characters', :aggregate_failures do subject.perform(code_batch) codes.each do |code| @@ -47,6 +50,7 @@ def codes end end end + context "with an email address" do it "sends an email" do subject.perform(code_batch) @@ -54,12 +58,14 @@ def codes .to have_received(:promotion_code_batch_finished) end end + context "with no email address" do let(:email) { nil } + it "sends an email" do subject.perform(code_batch) expect(SolidusFriendlyPromotions::PromotionCodeBatchMailer) - .to_not have_received(:promotion_code_batch_finished) + .not_to have_received(:promotion_code_batch_finished) end end end @@ -87,9 +93,10 @@ def codes context "with no email address" do let(:email) { nil } + it "sends an email" do expect(SolidusFriendlyPromotions::PromotionCodeBatchMailer) - .to_not have_received(:promotion_code_batch_errored) + .not_to have_received(:promotion_code_batch_errored) end end end diff --git a/friendly_promotions/spec/mailers/solidus_friendly_promotions/promotion_code_batch_mailer_spec.rb b/friendly_promotions/spec/mailers/solidus_friendly_promotions/promotion_code_batch_mailer_spec.rb index 9180710df43..9ee8ba28729 100644 --- a/friendly_promotions/spec/mailers/solidus_friendly_promotions/promotion_code_batch_mailer_spec.rb +++ b/friendly_promotions/spec/mailers/solidus_friendly_promotions/promotion_code_batch_mailer_spec.rb @@ -30,9 +30,10 @@ end describe "#promotion_code_batch_errored" do - before { code_batch.update(error: "Test error") } subject { described_class.promotion_code_batch_errored(code_batch) } + before { code_batch.update(error: "Test error") } + it "sends the email to the email attached to the promotion code batch " do expect(subject.to).to eq([code_batch.email]) end diff --git a/friendly_promotions/spec/models/promotion/integration_spec.rb b/friendly_promotions/spec/models/promotion/integration_spec.rb index b12c83cb3e8..8ab354e7607 100644 --- a/friendly_promotions/spec/models/promotion/integration_spec.rb +++ b/friendly_promotions/spec/models/promotion/integration_spec.rb @@ -72,7 +72,9 @@ order.create_proposed_shipments - order.shipments.first.selected_shipping_rate_id = order.shipments.first.shipping_rates.detect { |r| r.shipping_method == shipping_method }.id + order.shipments.first.selected_shipping_rate_id = order.shipments.first.shipping_rates.detect do |r| + r.shipping_method == shipping_method + end.id order.recalculate end @@ -100,6 +102,7 @@ context "when the order is eligible" do let(:shipping_method) { ups_ground } + it "creates adjustments" do expect(order.adjustments).to be_empty expect(order.total).to eq(31.40) diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_line_item_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_line_item_spec.rb index 8d8ab9ae105..863f4be290a 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_line_item_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_line_item_spec.rb @@ -13,6 +13,7 @@ describe ".to_partial_path" do subject { described_class.new.to_partial_path } + it { is_expected.to eq("solidus_friendly_promotions/admin/promotion_actions/actions/adjust_line_item") } end end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/distributed_amount_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/distributed_amount_spec.rb index 469d3630965..9ad4690ce94 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/distributed_amount_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/distributed_amount_spec.rb @@ -5,7 +5,9 @@ RSpec.describe SolidusFriendlyPromotions::Calculators::DistributedAmount, type: :model do let(:calculator) { described_class.new(preferred_amount: 15, preferred_currency: currency) } - let!(:promotion) { create :friendly_promotion, apply_automatically: true, name: '15 spread', actions: [action], rules: rules } + let!(:promotion) do + create :friendly_promotion, apply_automatically: true, name: '15 spread', actions: [action], rules: rules + end let(:rules) { [] } let(:action) { SolidusFriendlyPromotions::Actions::AdjustLineItem.create(calculator: calculator) } let(:order) { create(:order_with_line_items, line_items_attributes: line_items_attributes) } @@ -13,6 +15,7 @@ context 'applied to an order' do let(:line_items_attributes) { [{ price: 20 }, { price: 30 }, { price: 100 }] } + before do order.recalculate end @@ -42,19 +45,20 @@ end describe "#compute_line_item" do - let(:line_items_attributes) { [{ price: 50 }, { price: 50 }, { price: 50 }] } - subject { calculator.compute_line_item(order.line_items.first) } + let(:line_items_attributes) { [{ price: 50 }, { price: 50 }, { price: 50 }] } context "when the order currency matches the store's currency" do let(:currency) { "USD" } + it { is_expected.to eq 5 } it { is_expected.to be_a BigDecimal } end context "when the order currency does not match the store's currency" do let(:currency) { "CAD" } + it { is_expected.to eq 0 } end end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/flat_rate_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/flat_rate_spec.rb index d893989e153..e89918d7865 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/flat_rate_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/flat_rate_spec.rb @@ -4,24 +4,25 @@ require 'shared_examples/calculator_shared_examples' RSpec.describe SolidusFriendlyPromotions::Calculators::FlatRate, type: :model do - it_behaves_like 'a calculator with a description' + subject { calculator.compute(line_item) } + let(:line_item) { mock_model(Spree::LineItem, order: order) } + let(:order) { mock_model(Spree::Order, currency: order_currency) } let(:calculator) do described_class.new( preferred_amount: preferred_amount, preferred_currency: preferred_currency ) end - let(:order) { mock_model(Spree::Order, currency: order_currency) } - let(:line_item) { mock_model(Spree::LineItem, order: order) } - subject { calculator.compute(line_item) } + it_behaves_like 'a calculator with a description' context "compute" do describe "when preferred currency matches order" do let(:preferred_currency) { "GBP" } let(:order_currency) { "GBP" } let(:preferred_amount) { 25 } + it { is_expected.to eq(25.0) } end @@ -29,6 +30,7 @@ let(:preferred_currency) { "GBP" } let(:order_currency) { "USD" } let(:preferred_amount) { 25 } + it { is_expected.to be_zero } end @@ -36,6 +38,7 @@ let(:preferred_currency) { "" } let(:order_currency) { "USD" } let(:preferred_amount) { 25 } + it { is_expected.to be_zero } end @@ -43,6 +46,7 @@ let(:preferred_currency) { "gbP" } let(:order_currency) { "GBP" } let(:preferred_amount) { 25 } + it { is_expected.to eq(25.0) } end end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/flexi_rate_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/flexi_rate_spec.rb index a3df09a76eb..078cecac60d 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/flexi_rate_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/flexi_rate_spec.rb @@ -11,39 +11,49 @@ preferred_max_items: max_items ) end + let(:line_item) do + mock_model( + Spree::LineItem, quantity: quantity + ) + end let(:first_item) { 0 } let(:additional_item) { 0 } let(:max_items) { 0 } - it_behaves_like 'a calculator with a description' - let(:line_item) do mock_model( Spree::LineItem, quantity: quantity ) end + it_behaves_like 'a calculator with a description' + context "compute" do subject { calculator.compute(line_item) } + context "with all amounts 0" do context "with quantity 0" do let(:quantity) { 0 } - it { should eq 0 } + + it { is_expected.to eq 0 } end context "with quantity 1" do let(:quantity) { 1 } - it { should eq 0 } + + it { is_expected.to eq 0 } end context "with quantity 2" do let(:quantity) { 2 } - it { should eq 0 } + + it { is_expected.to eq 0 } end context "with quantity 10" do let(:quantity) { 10 } - it { should eq 0 } + + it { is_expected.to eq 0 } end end @@ -52,22 +62,26 @@ context "with quantity 0" do let(:quantity) { 0 } - it { should eq 0 } + + it { is_expected.to eq 0 } end context "with quantity 1" do let(:quantity) { 1 } - it { should eq 1.23 } + + it { is_expected.to eq 1.23 } end context "with quantity 2" do let(:quantity) { 2 } - it { should eq 1.23 } + + it { is_expected.to eq 1.23 } end context "with quantity 10" do let(:quantity) { 10 } - it { should eq 1.23 } + + it { is_expected.to eq 1.23 } end end @@ -76,22 +90,26 @@ context "with quantity 0" do let(:quantity) { 0 } - it { should eq 0 } + + it { is_expected.to eq 0 } end context "with quantity 1" do let(:quantity) { 1 } - it { should eq 0 } + + it { is_expected.to eq 0 } end context "with quantity 2" do let(:quantity) { 2 } - it { should eq 1.23 } + + it { is_expected.to eq 1.23 } end context "with quantity 10" do let(:quantity) { 10 } - it { should eq 11.07 } + + it { is_expected.to eq 11.07 } end end @@ -101,22 +119,26 @@ context "with quantity 0" do let(:quantity) { 0 } - it { should eq 0 } + + it { is_expected.to eq 0 } end context "with quantity 1" do let(:quantity) { 1 } - it { should eq 1.13 } + + it { is_expected.to eq 1.13 } end context "with quantity 2" do let(:quantity) { 2 } - it { should eq 3.24 } + + it { is_expected.to eq 3.24 } end context "with quantity 10" do let(:quantity) { 10 } - it { should eq 20.12 } + + it { is_expected.to eq 20.12 } end context "with max_items 5" do @@ -124,33 +146,38 @@ context "with quantity 0" do let(:quantity) { 0 } - it { should eq 0 } + + it { is_expected.to eq 0 } end context "with quantity 1" do let(:quantity) { 1 } - it { should eq 1.13 } + + it { is_expected.to eq 1.13 } end context "with quantity 2" do let(:quantity) { 2 } - it { should eq 3.24 } + + it { is_expected.to eq 3.24 } end context "with quantity 5" do let(:quantity) { 5 } - it { should eq 9.57 } + + it { is_expected.to eq 9.57 } end context "with quantity 10" do let(:quantity) { 10 } - it { should eq 9.57 } + + it { is_expected.to eq 9.57 } end end end end - it "should allow creation of new object with all the attributes" do + it "allows creation of new object with all the attributes" do attributes = { preferred_first_item: 1, preferred_additional_item: 1, preferred_max_items: 1 } calculator = described_class.new(attributes) expect(calculator).to have_attributes(attributes) diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_flat_rate_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_flat_rate_spec.rb index c42b5c34cf4..b476776b7e6 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_flat_rate_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_flat_rate_spec.rb @@ -10,48 +10,55 @@ describe "#valid?" do subject { calculator.valid? } + context "when tiers is a hash" do context "and the key is not a positive number" do before { calculator.preferred_tiers = { "nope" => 20 } } + it { is_expected.to be false } end context "and the key is an integer" do before { calculator.preferred_tiers = { 20 => 20 } } + it "converts successfully" do - is_expected.to be true + expect(subject).to be true expect(calculator.preferred_tiers).to eq({ BigDecimal('20') => BigDecimal('20') }) end end context "and the key is a float" do before { calculator.preferred_tiers = { 20.5 => 20.5 } } + it "converts successfully" do - is_expected.to be true + expect(subject).to be true expect(calculator.preferred_tiers).to eq({ BigDecimal('20.5') => BigDecimal('20.5') }) end end context "and the key is a string number" do before { calculator.preferred_tiers = { "20" => 20 } } + it "converts successfully" do - is_expected.to be true + expect(subject).to be true expect(calculator.preferred_tiers).to eq({ BigDecimal('20') => BigDecimal('20') }) end end context "and the key is a numeric string with spaces" do before { calculator.preferred_tiers = { " 20 " => 20 } } + it "converts successfully" do - is_expected.to be true + expect(subject).to be true expect(calculator.preferred_tiers).to eq({ BigDecimal('20') => BigDecimal('20') }) end end context "and the key is a string number with decimals" do before { calculator.preferred_tiers = { "20.5" => "20.5" } } + it "converts successfully" do - is_expected.to be true + expect(subject).to be true expect(calculator.preferred_tiers).to eq({ BigDecimal('20.5') => BigDecimal('20.5') }) end end @@ -59,6 +66,8 @@ end describe "#compute" do + subject { calculator.compute(line_item) } + let(:order) do create( :order_with_line_items, @@ -78,46 +87,49 @@ calculator.preferred_currency = preferred_currency end - subject { calculator.compute(line_item) } - context "when amount falls within the first tier" do let(:amount) { 50 } + it { is_expected.to eq 10 } end context "when amount falls within the second tier" do let(:amount) { 150 } + it { is_expected.to eq 15 } end context "when the order's currency does not match the calculator" do let(:preferred_currency) { "CAD" } let(:amount) { 50 } + it { is_expected.to eq 0 } end context "with a shipment" do - let(:shipment) { Spree::Shipment.new(order: order, amount: shipping_cost) } - let(:amount) { 10 } - subject { calculator.compute(shipment) } + let(:shipment) { Spree::Shipment.new(order: order, amount: shipping_cost) } let(:line_item_count) { 1 } + let(:amount) { 10 } context "for multiple line items" do context "when amount falls within the first tier" do let(:shipping_cost) { 110 } + it { is_expected.to eq 15 } end context "when amount falls within the second tier" do let(:shipping_cost) { 210 } + it { is_expected.to eq 20 } end context "when the order's currency does not match the calculator" do let(:preferred_currency) { "CAD" } let(:shipping_cost) { 110 } + it { is_expected.to eq 0 } end end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_percent_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_percent_spec.rb index 02db8f2bfbe..73cd5fd6458 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_percent_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_percent_spec.rb @@ -13,61 +13,70 @@ context "when base percent is less than zero" do before { calculator.preferred_base_percent = -1 } + it { is_expected.to be false } end context "when base percent is greater than 100" do before { calculator.preferred_base_percent = 110 } + it { is_expected.to be false } end context "when tiers is a hash" do context "and the key is not a positive number" do before { calculator.preferred_tiers = { "nope" => 20 } } + it { is_expected.to be false } end context "and one of the values is not a percent" do before { calculator.preferred_tiers = { 10 => 110 } } + it { is_expected.to be false } end context "and the key is an integer" do before { calculator.preferred_tiers = { 20 => 20 } } + it "converts successfully" do - is_expected.to be true + expect(subject).to be true expect(calculator.preferred_tiers).to eq({ BigDecimal('20') => BigDecimal('20') }) end end context "and the key is a float" do before { calculator.preferred_tiers = { 20.5 => 20.5 } } + it "converts successfully" do - is_expected.to be true + expect(subject).to be true expect(calculator.preferred_tiers).to eq({ BigDecimal('20.5') => BigDecimal('20.5') }) end end context "and the key is a string number" do before { calculator.preferred_tiers = { "20" => 20 } } + it "converts successfully" do - is_expected.to be true + expect(subject).to be true expect(calculator.preferred_tiers).to eq({ BigDecimal('20') => BigDecimal('20') }) end end context "and the key is a numeric string with spaces" do before { calculator.preferred_tiers = { " 20 " => 20 } } + it "converts successfully" do - is_expected.to be true + expect(subject).to be true expect(calculator.preferred_tiers).to eq({ BigDecimal('20') => BigDecimal('20') }) end end context "and the key is a string number with decimals" do before { calculator.preferred_tiers = { "20.5" => "20.5" } } + it "converts successfully" do - is_expected.to be true + expect(subject).to be true expect(calculator.preferred_tiers).to eq({ BigDecimal('20.5') => BigDecimal('20.5') }) end end @@ -95,22 +104,26 @@ end context "with a line item" do - let(:line_item) { order.line_items.first } subject { calculator.compute(line_item) } + let(:line_item) { order.line_items.first } + context "for multiple line items" do context "when amount falls within the first tier" do let(:line_item_count) { 1 } + it { is_expected.to eq 1.0 } end context "when amount falls within the second tier" do let(:line_item_count) { 2 } + it { is_expected.to eq 1.5 } end context "when amount falls within the third tier" do let(:line_item_count) { 3 } + it { is_expected.to eq 2.0 } end end @@ -120,16 +133,19 @@ context "when amount falls within the first tier" do let(:price) { 10 } + it { is_expected.to eq 1.0 } end context "when amount falls within the second tier" do let(:price) { 20 } + it { is_expected.to eq 3.0 } end context "when amount falls within the third tier" do let(:price) { 30 } + it { is_expected.to eq 6.0 } end end @@ -138,6 +154,7 @@ let(:preferred_currency) { "JPY" } let(:line_item_count) { 1 } let(:price) { 15 } + it { is_expected.to eq 0 } it "rounds based on currency" do @@ -148,26 +165,28 @@ end context "with a shipment" do - let(:shipment) { Spree::Shipment.new(order: order, amount: shipping_cost) } - let(:shipping_cost) { 10 } - subject { calculator.compute(shipment) } + let(:shipment) { Spree::Shipment.new(order: order, amount: shipping_cost) } let(:line_item_count) { 1 } + let(:shipping_cost) { 10 } context "for multiple line items" do context "when amount falls within the first tier" do let(:line_item_count) { 1 } + it { is_expected.to eq 1.0 } end context "when amount falls within the second tier" do let(:line_item_count) { 2 } + it { is_expected.to eq 1.5 } end context "when amount falls within the third tier" do let(:line_item_count) { 3 } + it { is_expected.to eq 2.0 } end end @@ -177,24 +196,28 @@ context "when amount falls within the first tier" do let(:price) { 10 } + it { is_expected.to eq 1.0 } end context "when amount falls within the second tier" do let(:price) { 20 } let(:shipping_cost) { 20 } + it { is_expected.to eq 3.0 } end context "when amount falls within the third tier" do let(:price) { 30 } let(:shipping_cost) { 30 } + it { is_expected.to eq 6.0 } end end context "when the order's currency does not match the calculator" do let(:preferred_currency) { "CAD" } + it { is_expected.to eq 0 } end end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/friendly_promotion_discounter_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/friendly_promotion_discounter_spec.rb index e429c5c5e18..9965d27cb45 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/friendly_promotion_discounter_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/friendly_promotion_discounter_spec.rb @@ -4,13 +4,18 @@ RSpec.describe SolidusFriendlyPromotions::FriendlyPromotionDiscounter do describe "selecting promotions" do - let(:order) { create(:order) } subject { described_class.new(order) } + let(:order) { create(:order) } + let!(:active_promotion) { create(:friendly_promotion, :with_adjustable_action, apply_automatically: true) } - let!(:inactive_promotion) { create(:friendly_promotion, :with_adjustable_action, expires_at: 2.days.ago, apply_automatically: true) } + let!(:inactive_promotion) do + create(:friendly_promotion, :with_adjustable_action, expires_at: 2.days.ago, apply_automatically: true) + end let!(:connectable_promotion) { create(:friendly_promotion, :with_adjustable_action) } - let!(:connectable_inactive_promotion) { create(:friendly_promotion, :with_adjustable_action, expires_at: 2.days.ago) } + let!(:connectable_inactive_promotion) do + create(:friendly_promotion, :with_adjustable_action, expires_at: 2.days.ago) + end context "no promo is connected to the order" do it "checks only active promotions" do diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/item_discount_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/item_discount_spec.rb index 0fc1feacc31..1d53505d6e3 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/item_discount_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/item_discount_spec.rb @@ -2,7 +2,6 @@ require 'spec_helper' - RSpec.describe SolidusFriendlyPromotions::ItemDiscount do it { is_expected.to respond_to(:item) } it { is_expected.to respond_to(:source) } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/order_discounter_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/order_discounter_spec.rb index dbd6e4f76ac..43f2262f3f2 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/order_discounter_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/order_discounter_spec.rb @@ -3,15 +3,17 @@ require 'spec_helper' RSpec.describe SolidusFriendlyPromotions::OrderDiscounter, type: :model do + subject { described_class.new(order) } + let(:line_item) { create(:line_item) } let(:order) { line_item.order } let(:promotion) { create(:friendly_promotion, apply_automatically: true) } let(:calculator) { Spree::Calculator::FlatPercentItemTotal.new(preferred_flat_percent: 10) } - subject { described_class.new(order) } - context "adjusting line items" do - let!(:action) { SolidusFriendlyPromotions::Actions::AdjustLineItem.create(promotion: promotion, calculator: calculator) } + let!(:action) do + SolidusFriendlyPromotions::Actions::AdjustLineItem.create(promotion: promotion, calculator: calculator) + end let(:adjustable) { line_item } context "promotion with no rules" do @@ -41,7 +43,9 @@ end context "promotion includes item involved" do - let!(:rule) { SolidusFriendlyPromotions::Rules::Product.create(products: [line_item.product], promotion: promotion) } + let!(:rule) do + SolidusFriendlyPromotions::Rules::Product.create(products: [line_item.product], promotion: promotion) + end context "creates the adjustment" do it "creates the adjustment" do @@ -53,7 +57,13 @@ end context "promotion has item total rule" do - let!(:rule) { SolidusFriendlyPromotions::Rules::ItemTotal.create(preferred_operator: 'gt', preferred_amount: 50, promotion: promotion) } + let!(:rule) do + SolidusFriendlyPromotions::Rules::ItemTotal.create( + preferred_operator: 'gt', + preferred_amount: 50, + promotion: promotion + ) + end before do # Makes the order eligible for this promotion diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/order_promotion_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/order_promotion_spec.rb index b1d79ff4910..2a22422e019 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/order_promotion_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/order_promotion_spec.rb @@ -6,6 +6,7 @@ subject do order_promotion end + let(:promotion) { build(:friendly_promotion) } let(:order_promotion) { build(:friendly_order_promotion, promotion: promotion) } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_action_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_action_spec.rb index 757145a5050..20f580a0331 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_action_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_action_spec.rb @@ -18,17 +18,18 @@ end describe "#discount" do + subject { action.discount(adjustable) } + let(:variant) { create(:variant) } let(:order) { create(:order) } - let(:adjustable) { Spree::LineItem.new(order: order, variant: variant, price: 10)} + let(:adjustable) { Spree::LineItem.new(order: order, variant: variant, price: 10) } let(:promotion) { SolidusFriendlyPromotions::Promotion.new(name: "20 Perzent off") } - let(:action) { described_class.new(promotion: promotion)} + let(:action) { described_class.new(promotion: promotion) } + before do allow(action).to receive(:compute_amount).and_return(-1) end - subject { action.discount(adjustable) } - it "returs an discount to the adjustable" do expect(subject).to eq( SolidusFriendlyPromotions::ItemDiscount.new( diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_category_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_category_spec.rb index 25d2a5bfbb1..50ce646d63d 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_category_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_category_spec.rb @@ -6,15 +6,17 @@ it { is_expected.to have_many :promotions } describe 'validation' do - let(:name) { 'Nom' } subject { described_class.new name: name } + let(:name) { 'Nom' } + context 'when all required attributes are specified' do it { is_expected.to be_valid } end context 'when name is missing' do let(:name) { nil } + it { is_expected.not_to be_valid } end end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code/batch_builder_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code/batch_builder_spec.rb index b124f031cc7..cea4f8a78e5 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code/batch_builder_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code/batch_builder_spec.rb @@ -3,6 +3,8 @@ require "spec_helper" RSpec.describe SolidusFriendlyPromotions::PromotionCode::BatchBuilder do + subject { described_class.new(code_batch, options) } + let(:promotion) { create(:friendly_promotion) } let(:base_code) { "abc" } let(:options) { {} } @@ -16,8 +18,6 @@ ) end - subject { described_class.new(code_batch, options) } - describe "#build_promotion_codes" do context "with a failed build" do before do diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code_batch_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code_batch_spec.rb index ed0472183e1..1051afbe7c1 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code_batch_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code_batch_spec.rb @@ -15,12 +15,12 @@ describe "#process" do context "with a pending code batch" do - it "should call the worker" do + it "calls the worker" do expect { subject.process } .to have_enqueued_job(SolidusFriendlyPromotions::PromotionCodeBatchJob) end - it "should update the state to processing" do + it "updates the state to processing" do subject.process expect(subject.state).to eq("processing") @@ -30,7 +30,7 @@ context "with a processing batch" do before { subject.update_attribute(:state, "processing") } - it "should raise an error" do + it "raises an error" do expect{ subject.process }.to raise_error described_class::CantProcessStartedBatch end end @@ -38,7 +38,7 @@ context "with a completed batch" do before { subject.update_attribute(:state, "completed") } - it "should raise an error" do + it "raises an error" do expect{ subject.process }.to raise_error described_class::CantProcessStartedBatch end end @@ -46,7 +46,7 @@ context "with a failed batch" do before { subject.update_attribute(:state, "failed") } - it "should raise an error" do + it "raises an error" do expect{ subject.process }.to raise_error described_class::CantProcessStartedBatch end end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code_spec.rb index 81003cf63bf..755ecb8ddea 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code_spec.rb @@ -68,10 +68,13 @@ context "when there is a usage limit" do context "and the limit is not exceeded" do let(:usage_limit) { 10 } + it { is_expected.to be_falsy } end + context "and the limit is exceeded" do let(:usage_limit) { 1 } + context "on a different order" do before do FactoryBot.create( @@ -80,15 +83,19 @@ ) code.adjustments.update_all(eligible: true) end + it { is_expected.to be_truthy } end + context "on the same order" do it { is_expected.to be_falsy } end end end + context "when there is no usage limit" do let(:usage_limit) { nil } + it { is_expected.to be_falsy } end end @@ -110,6 +117,7 @@ promotion: promotion ) end + it_behaves_like "it should" end @@ -122,29 +130,39 @@ per_code_usage_limit: usage_limit, ) end + before do order.recalculate end + context "when there are multiple line items" do let(:order) { FactoryBot.create(:order_with_line_items, line_items_count: 2) } + describe "the first item" do let(:promotable) { order.line_items.first } + it_behaves_like "it should" end + describe "the second item" do let(:promotable) { order.line_items.last } + it_behaves_like "it should" end end + context "when there is a single line item" do let(:order) { FactoryBot.create(:order_with_line_items) } let(:promotable) { order.line_items.first } + it_behaves_like "it should" end end end describe "#usage_count" do + subject { code.usage_count } + let(:promotion) do FactoryBot.create( :friendly_promotion, @@ -154,9 +172,6 @@ end let(:code) { promotion.codes.first } - subject { code.usage_count } - - context "when the code is applied to a non-complete order" do let(:order) { FactoryBot.create(:order_with_line_items) } @@ -178,15 +193,20 @@ promotion: promotion ) end + context "and the promo is eligible" do it { is_expected.to eq 1 } end + context "and the promo is ineligible" do before { order.all_adjustments.update_all(eligible: false) } + it { is_expected.to eq 0 } end + context "and the order is canceled" do before { order.cancel! } + it { is_expected.to eq 0 } it { expect(order.state).to eq 'canceled' } end @@ -217,7 +237,7 @@ order.friendly_order_promotions.create!( order: order, promotion: promotion, - promotion_code: SolidusFriendlyPromotions::PromotionCode.find_by(value: "discount") + promotion_code: described_class.find_by(value: "discount") ) order.recalculate order.next! until order.can_complete? @@ -227,7 +247,7 @@ order.friendly_order_promotions.create!( order: order, promotion: promotion, - promotion_code: SolidusFriendlyPromotions::PromotionCode.find_by(value: "discount") + promotion_code: described_class.find_by(value: "discount") ) order.recalculate order.next! until order.can_complete? @@ -264,6 +284,7 @@ promotion = create(:friendly_promotion, apply_automatically: true) expect { create(:friendly_promotion_code, promotion: promotion) - }.to raise_error ActiveRecord::RecordInvalid, "Validation failed: Could not create promotion code on promotion that apply automatically" + }.to raise_error ActiveRecord::RecordInvalid, + "Validation failed: Could not create promotion code on promotion that apply automatically" end end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb index b60bcb9e93a..450ce485c45 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb @@ -9,7 +9,7 @@ it { is_expected.to have_many :rules } describe "validations" do - before :each do + before do @valid_promotion = described_class.new name: "A promotion" end @@ -43,13 +43,13 @@ end describe ".coupons" do + subject { described_class.coupons } + let(:promotion_code) { create(:friendly_promotion_code) } let!(:promotion_with_code) { promotion_code.promotion } let!(:another_promotion_code) { create(:friendly_promotion_code, promotion: promotion_with_code) } let!(:promotion_without_code) { create(:friendly_promotion) } - subject { described_class.coupons } - it "returns only distinct promotions with a code associated" do expect(subject).to eq [promotion_with_code] end @@ -109,13 +109,13 @@ context "when set to true" do before { subject.apply_automatically = true } - it "should remain valid" do + it "remains valid" do expect(subject).to be_valid end it "invalidates the promotion when it has a path" do subject.path = "foo" - expect(subject).to_not be_valid + expect(subject).not_to be_valid expect(subject.errors).to include(:apply_automatically) end end @@ -128,10 +128,13 @@ context "when there is a usage limit" do context "and the limit is not exceeded" do let(:usage_limit) { 10 } + it { is_expected.to be_falsy } end + context "and the limit is exceeded" do let(:usage_limit) { 1 } + context "on a different order" do before do FactoryBot.create( @@ -140,15 +143,19 @@ ) promotion.actions.first.adjustments.update_all(eligible: true) end + it { is_expected.to be_truthy } end + context "on the same order" do it { is_expected.to be_falsy } end end end + context "when there is no usage limit" do let(:usage_limit) { nil } + it { is_expected.to be_falsy } end end @@ -162,6 +169,7 @@ usage_limit: usage_limit ) end + before do order.friendly_order_promotions.create( promotion_code: promotion.codes.first, @@ -169,26 +177,35 @@ ) order.recalculate end + context "when there are multiple line items" do let(:order) { FactoryBot.create(:order_with_line_items, line_items_count: 2) } + describe "the first item" do let(:promotable) { order.line_items.first } + it_behaves_like "it should" end + describe "the second item" do let(:promotable) { order.line_items.last } + it_behaves_like "it should" end end + context "when there is a single line item" do let(:order) { FactoryBot.create(:order_with_line_items) } let(:promotable) { order.line_items.first } + it_behaves_like "it should" end end end describe "#usage_count" do + subject { promotion.usage_count } + let(:promotion) do FactoryBot.create( :friendly_promotion, @@ -197,10 +214,9 @@ ) end - subject { promotion.usage_count } - context "when the code is applied to a non-complete order" do let(:order) { FactoryBot.create(:order_with_line_items) } + before do order.friendly_order_promotions.create( promotion_code: promotion.codes.first, @@ -208,8 +224,10 @@ ) order.recalculate end + it { is_expected.to eq 0 } end + context "when the code is applied to a complete order" do let!(:order) do FactoryBot.create( @@ -217,50 +235,54 @@ promotion: promotion ) end + context "and the promo is eligible" do it { is_expected.to eq 1 } end + context "and the promo is ineligible" do before { order.all_adjustments.friendly_promotion.update_all(eligible: false) } + it { is_expected.to eq 0 } end + context "and the order is canceled" do before { order.cancel! } + it { is_expected.to eq 0 } it { expect(order.state).to eq 'canceled' } end end end - - context "#inactive" do + describe "#inactive" do let(:promotion) { create(:friendly_promotion, :with_adjustable_action) } - it "should not be exipired" do + it "is not exipired" do expect(promotion).not_to be_inactive end - it "should be inactive if it hasn't started yet" do + it "is inactive if it hasn't started yet" do promotion.starts_at = Time.current + 1.day expect(promotion).to be_inactive end - it "should be inactive if it has already ended" do + it "is inactive if it has already ended" do promotion.expires_at = Time.current - 1.day expect(promotion).to be_inactive end - it "should not be inactive if it has started already" do + it "is not inactive if it has started already" do promotion.starts_at = Time.current - 1.day expect(promotion).not_to be_inactive end - it "should not be inactive if it has not ended yet" do + it "is not inactive if it has not ended yet" do promotion.expires_at = Time.current + 1.day expect(promotion).not_to be_inactive end - it "should not be inactive if current time is within starts_at and expires_at range" do + it "is not inactive if current time is within starts_at and expires_at range" do promotion.starts_at = Time.current - 1.day promotion.expires_at = Time.current + 1.day expect(promotion).not_to be_inactive @@ -268,103 +290,119 @@ end describe '#not_started?' do - let(:promotion) { SolidusFriendlyPromotions::Promotion.new(starts_at: starts_at) } subject { promotion.not_started? } + let(:promotion) { described_class.new(starts_at: starts_at) } + context 'no starts_at date' do let(:starts_at) { nil } + it { is_expected.to be_falsey } end context 'when starts_at date is in the past' do let(:starts_at) { Time.current - 1.day } + it { is_expected.to be_falsey } end context 'when starts_at date is not already reached' do let(:starts_at) { Time.current + 1.day } + it { is_expected.to be_truthy } end end describe '#started?' do - let(:promotion) { SolidusFriendlyPromotions::Promotion.new(starts_at: starts_at) } subject { promotion.started? } + let(:promotion) { described_class.new(starts_at: starts_at) } + context 'when no starts_at date' do let(:starts_at) { nil } + it { is_expected.to be_truthy } end context 'when starts_at date is in the past' do let(:starts_at) { Time.current - 1.day } + it { is_expected.to be_truthy } end context 'when starts_at date is not already reached' do let(:starts_at) { Time.current + 1.day } + it { is_expected.to be_falsey } end end describe '#expired?' do - let(:promotion) { SolidusFriendlyPromotions::Promotion.new(expires_at: expires_at) } subject { promotion.expired? } + let(:promotion) { described_class.new(expires_at: expires_at) } + context 'when no expires_at date' do let(:expires_at) { nil } + it { is_expected.to be_falsey } end context 'when expires_at date is not already reached' do let(:expires_at) { Time.current + 1.day } + it { is_expected.to be_falsey } end context 'when expires_at date is in the past' do let(:expires_at) { Time.current - 1.day } + it { is_expected.to be_truthy } end end describe '#not_expired?' do - let(:promotion) { SolidusFriendlyPromotions::Promotion.new(expires_at: expires_at) } subject { promotion.not_expired? } + let(:promotion) { described_class.new(expires_at: expires_at) } + context 'when no expired_at date' do let(:expires_at) { nil } + it { is_expected.to be_truthy } end context 'when expires_at date is not already reached' do let(:expires_at) { Time.current + 1.day } + it { is_expected.to be_truthy } end context 'when expires_at date is in the past' do let(:expires_at) { Time.current - 1.day } + it { is_expected.to be_falsey } end end - context "#active" do - it "shouldn't be active if it has started already" do + describe "#active" do + it "is not active if it has started already" do promotion.starts_at = Time.current - 1.day expect(promotion.active?).to eq(false) end - it "shouldn't be active if it has not ended yet" do + it "is not active if it has not ended yet" do promotion.expires_at = Time.current + 1.day expect(promotion.active?).to eq(false) end - it "shouldn't be active if current time is within starts_at and expires_at range" do + it "is not active if current time is within starts_at and expires_at range" do promotion.starts_at = Time.current - 1.day promotion.expires_at = Time.current + 1.day expect(promotion.active?).to eq(false) end - it "shouldn't be active if there are no start and end times set" do + it "is not active if there are no start and end times set" do promotion.starts_at = nil promotion.expires_at = nil expect(promotion.active?).to eq(false) @@ -373,23 +411,23 @@ context 'when promotion has an action' do let(:promotion) { create(:promotion, :with_action, name: "name1") } - it "should be active if it has started already" do + it "is active if it has started already" do promotion.starts_at = Time.current - 1.day expect(promotion.active?).to eq(true) end - it "should be active if it has not ended yet" do + it "is active if it has not ended yet" do promotion.expires_at = Time.current + 1.day expect(promotion.active?).to eq(true) end - it "should be active if current time is within starts_at and expires_at range" do + it "is active if current time is within starts_at and expires_at range" do promotion.starts_at = Time.current - 1.day promotion.expires_at = Time.current + 1.day expect(promotion.active?).to eq(true) end - it "should be active if there are no start and end times set" do + it "is active if there are no start and end times set" do promotion.starts_at = nil promotion.expires_at = nil expect(promotion.active?).to eq(true) @@ -397,7 +435,7 @@ end end - context "#products" do + describe "#products" do let(:promotion) { create(:friendly_promotion) } context "when it has product rules with products associated" do @@ -409,13 +447,13 @@ promotion_rule.save end - it "should have products" do + it "has products" do expect(promotion.reload.products.size).to eq(1) end end context "when there's no product rule associated" do - it "should not have products but still return an empty array" do + it "does not have products but still return an empty array" do expect(promotion.products).to be_blank end end @@ -469,6 +507,7 @@ context 'when the only matching order is the excluded order' do let(:excluded_order) { order } + it { is_expected.to be false } end end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_order_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_order_spec.rb index bc6c2e82ba8..b9997ae30e9 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_order_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_order_spec.rb @@ -3,17 +3,19 @@ require "spec_helper" RSpec.describe SolidusFriendlyPromotions::Rules::FirstOrder, type: :model do - let(:rule) { SolidusFriendlyPromotions::Rules::FirstOrder.new } + let(:rule) { described_class.new } let(:order) { mock_model(Spree::Order, user: nil, email: nil) } let(:user) { mock_model(Spree::LegacyUser) } describe ".to_partial_path" do subject { rule.to_partial_path } + it { is_expected.to eq("solidus_friendly_promotions/admin/promotion_rules/rules/first_order") } end context "without a user or email" do it { expect(rule).to be_eligible(order) } + it "does not set an error message" do rule.eligible?(order) expect(rule.eligibility_errors.full_messages.first) @@ -24,7 +26,7 @@ context "first order" do context "for a signed user" do context "with no completed orders" do - before(:each) do + before do allow(user).to receive_message_chain(:orders, complete: []) end @@ -33,29 +35,32 @@ expect(rule).to be_eligible(order) end - it "should be eligible when user passed in payload data" do + it "is eligible when user passed in payload data" do expect(rule).to be_eligible(order, user: user) end end context "with completed orders" do - before(:each) do + before do allow(order).to receive_messages(user: user) end - it "should be eligible when checked against first completed order" do + it "is eligible when checked against first completed order" do allow(user).to receive_message_chain(:orders, complete: [order]) expect(rule).to be_eligible(order) end context "with another order" do before { allow(user).to receive_message_chain(:orders, complete: [mock_model(Spree::Order)]) } + it { expect(rule).not_to be_eligible(order) } + it "sets an error message" do rule.eligible?(order) expect(rule.eligibility_errors.full_messages.first) .to eq "This coupon code can only be applied to your first order." end + it "sets an error code" do rule.eligible?(order) expect(rule.eligibility_errors.details[:base].first[:error_code]) @@ -67,6 +72,7 @@ context "for a guest user" do let(:email) { "user@solidus.io" } + before { allow(order).to receive_messages email: "user@solidus.io" } context "with no other orders" do @@ -75,12 +81,15 @@ context "with another order" do before { allow(rule).to receive_messages(orders_by_email: [mock_model(Spree::Order)]) } + it { expect(rule).not_to be_eligible(order) } + it "sets an error message" do rule.eligible?(order) expect(rule.eligibility_errors.full_messages.first) .to eq "This coupon code can only be applied to your first order." end + it "sets an error code" do rule.eligible?(order) expect(rule.eligibility_errors.details[:base].first[:error_code]) diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_repeat_purchase_since_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_repeat_purchase_since_spec.rb index 33dd9487c7f..e23fd17e3a3 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_repeat_purchase_since_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_repeat_purchase_since_spec.rb @@ -20,9 +20,10 @@ end describe "eligible?" do - let(:instance) { described_class.new } subject { instance.eligible?(order) } + let(:instance) { described_class.new } + before do instance.preferred_days_ago = 365 end @@ -40,6 +41,7 @@ context "when the user has completed orders" do let(:order_completion_date_1) { 1.day.ago } let(:order_completion_date_2) { 1.day.ago } + before do old_order_1 = create :completed_order_with_totals, user: user old_order_1.update(completed_at: order_completion_date_1) diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/item_total_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/item_total_spec.rb index 4c396723103..73da9a6bab5 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/item_total_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/item_total_spec.rb @@ -4,7 +4,7 @@ RSpec.describe SolidusFriendlyPromotions::Rules::ItemTotal, type: :model do let(:rule) do - SolidusFriendlyPromotions::Rules::ItemTotal.new( + described_class.new( preferred_amount: preferred_amount, preferred_operator: preferred_operator ) @@ -19,7 +19,7 @@ context "item total is greater than preferred amount" do let(:item_total) { 51 } - it "should be eligible when item total is greater than preferred amount" do + it "is eligible when item total is greater than preferred amount" do expect(rule).to be_eligible(order) end @@ -44,6 +44,7 @@ expect(rule.eligibility_errors.full_messages.first) .to eq "This coupon code can't be applied to orders less than or equal to $50.00." end + it "sets an error code" do rule.eligible?(order) expect(rule.eligibility_errors.details[:base].first[:error_code]) @@ -63,6 +64,7 @@ expect(rule.eligibility_errors.full_messages.first) .to eq "This coupon code can't be applied to orders less than or equal to $50.00." end + it "sets an error code" do rule.eligible?(order) expect(rule.eligibility_errors.details[:base].first[:error_code]) @@ -77,7 +79,7 @@ context "total is greater than preferred amount" do let(:item_total) { 51 } - it "should be eligible when item total is greater than preferred amount" do + it "is eligible when item total is greater than preferred amount" do expect(rule).to be_eligible(order) end @@ -93,7 +95,7 @@ context "item total is equal to preferred amount" do let(:item_total) { 50 } - it "should be eligible" do + it "is eligible" do expect(rule).to be_eligible(order) end @@ -118,6 +120,7 @@ expect(rule.eligibility_errors.full_messages.first) .to eq "This coupon code can't be applied to orders less than $50.00." end + it "sets an error code" do rule.eligible?(order) expect(rule.eligibility_errors.details[:base].first[:error_code]) diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/line_item_product_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/line_item_product_spec.rb index ee5d634c574..53afccbb610 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/line_item_product_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/line_item_product_spec.rb @@ -6,7 +6,9 @@ let(:rule) { described_class.new(rule_options) } let(:rule_options) { {} } - context "#eligible?(line_item)" do + describe "#eligible?(line_item)" do + subject { rule.eligible?(line_item, {}) } + let(:rule_line_item) { Spree::LineItem.new(product: rule_product) } let(:other_line_item) { Spree::LineItem.new(product: other_product) } @@ -14,19 +16,19 @@ let(:rule_product) { mock_model(Spree::Product) } let(:other_product) { mock_model(Spree::Product) } - it "should be eligible if there are no products" do + it "is eligible if there are no products" do expect(rule).to be_eligible(rule_line_item) end - subject { rule.eligible?(line_item, {}) } - context "for product in rule" do let(:line_item) { rule_line_item } + it { is_expected.to be_truthy } end context "for product not in rule" do let(:line_item) { other_line_item } + it { is_expected.to be_falsey } end @@ -35,11 +37,13 @@ context "for product in rule" do let(:line_item) { rule_line_item } + it { is_expected.to be_falsey } end context "for product not in rule" do let(:line_item) { other_line_item } + it { is_expected.to be_truthy } end end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/nth_order_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/nth_order_spec.rb index e478a3630cc..d2464d35286 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/nth_order_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/nth_order_spec.rb @@ -20,9 +20,10 @@ end describe "eligible?" do - let(:instance) { described_class.new } subject { instance.eligible?(order) } + let(:instance) { described_class.new } + before do instance.preferred_nth_order = 2 end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/one_use_per_user_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/one_use_per_user_spec.rb index d04bf2c16d5..b04f8717849 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/one_use_per_user_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/one_use_per_user_spec.rb @@ -7,6 +7,7 @@ describe "#eligible?(order)" do subject { rule.eligible?(order) } + let(:order) { double Spree::Order, user: user } let(:user) { double Spree::LegacyUser } let(:promotion) { stub_model SolidusFriendlyPromotions::Promotion, used_by?: used_by } @@ -19,11 +20,13 @@ let(:used_by) { true } it { is_expected.to be false } + it "sets an error message" do subject expect(rule.eligibility_errors.full_messages.first) .to eq "This coupon code can only be used once per user." end + it "sets an error code" do rule.eligible?(order) expect(rule.eligibility_errors.details[:base].first[:error_code]) @@ -38,12 +41,15 @@ context "when the order is not assigned to a user" do let(:user) { nil } + it { is_expected.to be false } + it "sets an error message" do subject expect(rule.eligibility_errors.full_messages.first) .to eq "You need to login before applying this coupon code." end + it "sets an error code" do rule.eligible?(order) expect(rule.eligibility_errors.details[:base].first[:error_code]) diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/option_value_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/option_value_spec.rb index 913944f2bee..c8078873ac8 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/option_value_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/option_value_spec.rb @@ -3,45 +3,55 @@ require "spec_helper" RSpec.describe SolidusFriendlyPromotions::Rules::OptionValue do - let(:rule) { SolidusFriendlyPromotions::Rules::OptionValue.new } + let(:rule) { described_class.new } describe "#preferred_eligible_values" do subject { rule.preferred_eligible_values } + it "assigns a nicely formatted hash" do - rule.preferred_eligible_values= {"5" => "1,2", "6" => "1"} - expect(subject).to eq({5 => [1, 2], 6 => [1]}) + rule.preferred_eligible_values = { "5" => "1,2", "6" => "1" } + expect(subject).to eq({ 5 => [1, 2], 6 => [1] }) end end describe "#applicable?" do subject { rule.applicable?(promotable) } + context "when promotable is an order" do let(:promotable) { Spree::Order.new } + it { is_expected.to be true } end + context "when promotable is not an order" do let(:promotable) { Spree::LineItem.new } + it { is_expected.to be false } end end describe "#eligible?" do + subject { rule.eligible?(promotable) } + let(:variant) { create :variant } let(:line_item) { create :line_item, variant: variant } let(:promotable) { line_item.order } - subject { rule.eligible?(promotable) } + context "when there are any applicable line items" do before do - rule.preferred_eligible_values= {line_item.product.id => [ - line_item.variant.option_values.pluck(:id).first - ]} + rule.preferred_eligible_values = { line_item.product.id => [ + line_item.variant.option_values.pick(:id) + ] } end + it { is_expected.to be true } end + context "when there are no applicable line items" do before do - rule.preferred_eligible_values= {99 => [99]} + rule.preferred_eligible_values = { 99 => [99] } end + it { is_expected.to be false } end end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/product_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/product_spec.rb index 1f5c574e77f..b8fffa7429c 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/product_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/product_spec.rb @@ -3,27 +3,27 @@ require "spec_helper" RSpec.describe SolidusFriendlyPromotions::Rules::Product, type: :model do - it { is_expected.to have_many(:products) } - - let(:rule) { described_class.new(rule_options) } let(:rule_options) { {} } + let(:rule) { described_class.new(rule_options) } - context "#eligible?(order)" do - let(:order) { Spree::Order.new } + it { is_expected.to have_many(:products) } - it "should be eligible if there are no products" do - allow(rule).to receive_messages(eligible_products: []) - expect(rule).to be_eligible(order) - end + describe "#eligible?(order)" do + let(:order) { Spree::Order.new } before do 3.times { |i| instance_variable_set("@product#{i}", mock_model(Spree::Product)) } end + it "is eligible if there are no products" do + allow(rule).to receive_messages(eligible_products: []) + expect(rule).to be_eligible(order) + end + context "with 'any' match policy" do let(:rule_options) { super().merge(preferred_match_policy: "any") } - it "should be eligible if any of the products is in eligible products" do + it "is eligible if any of the products is in eligible products" do allow(rule).to receive_messages(order_products: [@product1, @product2]) allow(rule).to receive_messages(eligible_products: [@product2, @product3]) expect(rule).to be_eligible(order) @@ -34,12 +34,15 @@ allow(rule).to receive_messages(order_products: [@product1]) allow(rule).to receive_messages(eligible_products: [@product2, @product3]) end + it { expect(rule).not_to be_eligible(order) } + it "sets an error message" do rule.eligible?(order) expect(rule.eligibility_errors.full_messages.first) .to eq "You need to add an applicable product before applying this coupon code." end + it "sets an error code" do rule.eligible?(order) expect(rule.eligibility_errors.details[:base].first[:error_code]) @@ -51,7 +54,7 @@ context "with 'all' match policy" do let(:rule_options) { super().merge(preferred_match_policy: "all") } - it "should be eligible if all of the eligible products are ordered" do + it "is eligible if all of the eligible products are ordered" do allow(rule).to receive_messages(order_products: [@product3, @product2, @product1]) allow(rule).to receive_messages(eligible_products: [@product2, @product3]) expect(rule).to be_eligible(order) @@ -62,12 +65,15 @@ allow(rule).to receive_messages(order_products: [@product1, @product2]) allow(rule).to receive_messages(eligible_products: [@product1, @product2, @product3]) end + it { expect(rule).not_to be_eligible(order) } + it "sets an error message" do rule.eligible?(order) expect(rule.eligibility_errors.full_messages.first) .to eq "This coupon code can't be applied because you don't have all of the necessary products in your cart." end + it "sets an error code" do rule.eligible?(order) expect(rule.eligibility_errors.details[:base].first[:error_code]) @@ -79,7 +85,7 @@ context "with 'none' match policy" do let(:rule_options) { super().merge(preferred_match_policy: "none") } - it "should be eligible if none of the order's products are in eligible products" do + it "is eligible if none of the order's products are in eligible products" do allow(rule).to receive_messages(order_products: [@product1]) allow(rule).to receive_messages(eligible_products: [@product2, @product3]) expect(rule).to be_eligible(order) @@ -90,12 +96,15 @@ allow(rule).to receive_messages(order_products: [@product1, @product2]) allow(rule).to receive_messages(eligible_products: [@product2, @product3]) end + it { expect(rule).not_to be_eligible(order) } + it "sets an error message" do rule.eligible?(order) expect(rule.eligibility_errors.full_messages.first) .to eq "Your cart contains a product that prevents this coupon code from being applied." end + it "sets an error code" do rule.eligible?(order) expect(rule.eligibility_errors.details[:base].first[:error_code]) @@ -106,7 +115,7 @@ context "with an invalid match policy" do let(:rule) do - SolidusFriendlyPromotions::Rules::Product.create!( + described_class.create!( promotion: create(:friendly_promotion), products_promotion_rules: [ SolidusFriendlyPromotions::ProductsPromotionRule.new(product: product) diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/shipping_method_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/shipping_method_spec.rb index 1ee9f66b5ed..52d0d782776 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/shipping_method_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/shipping_method_spec.rb @@ -3,18 +3,18 @@ require "spec_helper" RSpec.describe SolidusFriendlyPromotions::Rules::ShippingMethod, type: :model do - it { is_expected.to respond_to(:preferred_shipping_method_ids) } - let(:rule) { described_class.new } + it { is_expected.to respond_to(:preferred_shipping_method_ids) } + describe "preferred_shipping_methods_ids=" do + subject { rule.preferred_shipping_method_ids = [ups_ground.id] } + let!(:promotion) { create(:friendly_promotion) } let(:ups_ground) { create(:shipping_method) } let(:dhl_saver) { create(:shipping_method) } let(:rule) { promotion.rules.build(type: described_class.to_s) } - subject { rule.preferred_shipping_method_ids = [ups_ground.id] } - it "creates a valid rule with a shipping method" do subject expect(rule).to be_valid @@ -23,13 +23,13 @@ end describe "#eligible?" do + subject { rule.eligible?(promotable) } + let!(:promotion) { create(:friendly_promotion) } let(:ups_ground) { create(:shipping_method) } let(:dhl_saver) { create(:shipping_method) } let(:rule) { promotion.rules.build(type: described_class.to_s, preferred_shipping_method_ids: [ups_ground.id]) } - subject { rule.eligible?(promotable) } - context "with a shipment" do context "when the shipment has the right shipping method selected" do let(:promotable) { create(:shipment, shipping_method: ups_ground) } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/store_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/store_spec.rb index 7f4d9d2139b..4a2eb9fd13f 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/store_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/store_spec.rb @@ -3,18 +3,18 @@ require "spec_helper" RSpec.describe SolidusFriendlyPromotions::Rules::Store, type: :model do - it { is_expected.to have_many(:stores) } - let(:rule) { described_class.new } + it { is_expected.to have_many(:stores) } + describe "store_ids=" do + subject { rule.store_ids = [store.id] } + let!(:promotion) { create(:friendly_promotion) } let!(:unimportant_store) { create(:store) } let!(:store) { create(:store) } let(:rule) { promotion.rules.build(type: described_class.to_s) } - subject { rule.store_ids = [store.id] } - it "creates a valid rule with a store" do subject expect(rule).to be_valid @@ -22,7 +22,7 @@ end end - context "#eligible?(order)" do + describe "#eligible?(order)" do let(:order) { Spree::Order.new } it "is eligible if no stores are provided" do diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/taxon_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/taxon_spec.rb index 4c237479243..7b2f635cbd2 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/taxon_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/taxon_spec.rb @@ -3,25 +3,24 @@ require "spec_helper" RSpec.describe SolidusFriendlyPromotions::Rules::Taxon, type: :model do - it { is_expected.to have_many(:taxons) } - - let(:taxon) { create :taxon, name: "first" } - let(:taxon2) { create :taxon, name: "second" } - let(:order) { create :order_with_line_items } - let(:product) { order.products.first } - let(:rule) do - SolidusFriendlyPromotions::Rules::Taxon.create!(promotion: create(:friendly_promotion)) + described_class.create!(promotion: create(:friendly_promotion)) end + let(:product) { order.products.first } + let(:order) { create :order_with_line_items } + let(:taxon2) { create :taxon, name: "second" } + let(:taxon) { create :taxon, name: "first" } + + it { is_expected.to have_many(:taxons) } describe "taxon_ids_string=" do + subject { rule.assign_attributes("taxon_ids_string" => taxon_2.id.to_s) } + let!(:promotion) { create(:friendly_promotion) } let!(:taxon_1) { create(:taxon) } let!(:taxon_2) { create(:taxon) } let(:rule) { promotion.rules.build(type: described_class.to_s) } - subject { rule.assign_attributes("taxon_ids_string" => taxon_2.id.to_s) } - it "creates a valid rule with a taxon" do subject expect(rule).to be_valid @@ -30,7 +29,7 @@ end end - context "#eligible?(order)" do + describe "#eligible?(order)" do context "with any match policy" do before do rule.update!(preferred_match_policy: "any") @@ -44,12 +43,15 @@ context "when order does not have any prefered taxon" do before { rule.taxons << taxon2 } + it { expect(rule).not_to be_eligible(order) } + it "sets an error message" do rule.eligible?(order) expect(rule.eligibility_errors.full_messages.first) .to eq "You need to add a product from an applicable category before applying this coupon code." end + it "sets an error code" do rule.eligible?(order) expect(rule.eligibility_errors.details[:base].first[:error_code]) @@ -84,12 +86,15 @@ context "when order does not have all prefered taxons" do before { rule.taxons << taxon } + it { expect(rule).not_to be_eligible(order) } + it "sets an error message" do rule.eligible?(order) expect(rule.eligibility_errors.full_messages.first) .to eq "You need to add a product from all applicable categories before applying this coupon code." end + it "sets an error code" do rule.eligible?(order) expect(rule.eligibility_errors.details[:base].first[:error_code]) @@ -120,6 +125,7 @@ context "none of the order's products are in listed taxon" do before { rule.taxons << taxon2 } + it { expect(rule).to be_eligible(order) } end @@ -128,14 +134,17 @@ order.products.first.taxons << taxon rule.taxons << taxon end - it "should not be eligible" do + + it "is not eligible" do expect(rule).not_to be_eligible(order) end + it "sets an error message" do rule.eligible?(order) expect(rule.eligibility_errors.full_messages.first) .to eq "Your cart contains a product from an excluded category that prevents this coupon code from being applied." end + it "sets an error code" do rule.eligible?(order) expect(rule.eligibility_errors.details[:base].first[:error_code]) diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_logged_in_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_logged_in_spec.rb index 4147c5d4bd0..4901483a5bb 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_logged_in_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_logged_in_spec.rb @@ -3,12 +3,12 @@ require "spec_helper" RSpec.describe SolidusFriendlyPromotions::Rules::UserLoggedIn, type: :model do - let(:rule) { SolidusFriendlyPromotions::Rules::UserLoggedIn.new } + let(:rule) { described_class.new } - context "#eligible?(order)" do + describe "#eligible?(order)" do let(:order) { Spree::Order.new } - it "should be eligible if order has an associated user" do + it "is eligible if order has an associated user" do user = double("User") allow(order).to receive_messages(user: user) @@ -17,12 +17,15 @@ context "when user is not logged in" do before { allow(order).to receive_messages(user: nil) } # better to be explicit here + it { expect(rule).not_to be_eligible(order) } + it "sets an error message" do rule.eligible?(order) expect(rule.eligibility_errors.full_messages.first) .to eq "You need to login before applying this coupon code." end + it "sets an error code" do rule.eligible?(order) expect(rule.eligibility_errors.details[:base].first[:error_code]) diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_role_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_role_spec.rb index 49c6f0060bc..36cabae0e42 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_role_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_role_spec.rb @@ -3,51 +3,55 @@ require "spec_helper" RSpec.describe SolidusFriendlyPromotions::Rules::UserRole, type: :model do + subject { rule } + let(:rule) { described_class.new(preferred_role_ids: roles_for_rule.map(&:id)) } let(:user) { create(:user, spree_roles: roles_for_user) } let(:roles_for_rule) { [] } let(:roles_for_user) { [] } - subject { rule } - shared_examples "eligibility" do context "no roles on rule" do - let(:roles_for_user) { [create(:role)] } - it "should not be eligible" do - expect(rule).to_not be_eligible(order) + let(:roles_for_user) { create_list(:role, 1) } + + it "is not eligible" do + expect(rule).not_to be_eligible(order) end end context "no roles on user" do - let(:roles_for_rule) { [create(:role)] } - it "should not be eligible" do - expect(rule).to_not be_eligible(order) + let(:roles_for_rule) { create_list(:role, 1) } + + it "is not eligible" do + expect(rule).not_to be_eligible(order) end end context "mismatched roles" do - let(:roles_for_user) { [create(:role)] } - let(:roles_for_rule) { [create(:role)] } - it "should not be eligible" do - expect(rule).to_not be_eligible(order) + let(:roles_for_user) { create_list(:role, 1) } + let(:roles_for_rule) { create_list(:role, 1) } + + it "is not eligible" do + expect(rule).not_to be_eligible(order) end end context "matching all roles" do - let(:roles_for_user) { [create(:role), create(:role)] } + let(:roles_for_user) { create_list(:role, 2) } let(:roles_for_rule) { roles_for_user } - it "should be eligible" do + + it "is eligible" do expect(rule).to be_eligible(order) end end end - context "#eligible?(order)" do + describe "#eligible?(order)" do context "order with no user" do let(:order) { Spree::Order.new } - it "should not be eligible" do - expect(rule).to_not be_eligible(order) + it "is not eligible" do + expect(rule).not_to be_eligible(order) end end @@ -63,7 +67,8 @@ let(:shared_role) { create(:role) } let(:roles_for_user) { [create(:role), shared_role] } let(:roles_for_rule) { [create(:role), shared_role] } - it "should be eligible" do + + it "is eligible" do expect(rule).to be_eligible(order) end end @@ -78,8 +83,9 @@ let(:shared_role) { create(:role) } let(:roles_for_user) { [create(:role), shared_role] } let(:roles_for_rule) { [create(:role), shared_role] } - it "should not be eligible" do - expect(rule).to_not be_eligible(order) + + it "is not eligible" do + expect(rule).not_to be_eligible(order) end end end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_spec.rb index 7f38bf41da5..087009f1150 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_spec.rb @@ -3,29 +3,30 @@ require "spec_helper" RSpec.describe SolidusFriendlyPromotions::Rules::User, type: :model do - it { is_expected.to have_many(:users) } let(:rule) { described_class.new } + it { is_expected.to have_many(:users) } + describe "user_ids=" do + subject { rule.user_ids = [user.id] } + let(:promotion) { create(:friendly_promotion) } let(:user) { create(:user) } let(:rule) { promotion.rules.new } - subject { rule.user_ids = [user.id] } - it "creates a valid rule with a user" do expect(rule).to be_valid end end - context "#eligible?(order)" do + describe "#eligible?(order)" do let(:order) { Spree::Order.new } - it "should not be eligible if users are not provided" do + it "is not eligible if users are not provided" do expect(rule).not_to be_eligible(order) end - it "should be eligible if users include user placing the order" do + it "is eligible if users include user placing the order" do user = mock_model(Spree::LegacyUser) users = [user, mock_model(Spree::LegacyUser)] allow(rule).to receive_messages(users: users) @@ -34,7 +35,7 @@ expect(rule).to be_eligible(order) end - it "should not be eligible if user placing the order is not listed" do + it "is not eligible if user placing the order is not listed" do allow(order).to receive_messages(user: mock_model(Spree::LegacyUser)) users = [mock_model(Spree::LegacyUser), mock_model(Spree::LegacyUser)] allow(rule).to receive_messages(users: users) diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/simple_order_contents_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/simple_order_contents_spec.rb index 90a287f2bf7..bd3c248d4eb 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/simple_order_contents_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/simple_order_contents_spec.rb @@ -3,17 +3,17 @@ require 'spec_helper' RSpec.describe SolidusFriendlyPromotions::SimpleOrderContents, type: :model do + subject(:order_contents) { described_class.new(order) } + let!(:store) { create :store } let(:order) { create(:order) } let(:variant) { create(:variant) } let!(:stock_location) { variant.stock_locations.first } let(:stock_location_2) { create(:stock_location) } - subject(:order_contents) { described_class.new(order) } - - context "#add" do + describe "#add" do context 'given quantity is not explicitly provided' do - it 'should add one line item' do + it 'adds one line item' do line_item = subject.add(variant) expect(line_item.quantity).to eq(1) expect(order.line_items.size).to eq(1) @@ -24,7 +24,7 @@ let!(:shipment) { create(:shipment, order: order) } it "ensure shipment calls update_amounts instead of order calling check_shipments_and_restart_checkout" do - expect(subject.order).to_not receive(:check_shipments_and_restart_checkout) + expect(subject.order).not_to receive(:check_shipments_and_restart_checkout) expect(shipment).to receive(:update_amounts).at_least(:once) subject.add(variant, 1, shipment: shipment) end @@ -59,20 +59,20 @@ end end - it 'should add line item if one does not exist' do + it 'adds line item if one does not exist' do line_item = subject.add(variant, 1) expect(line_item.quantity).to eq(1) expect(order.line_items.size).to eq(1) end - it 'should update line item if one exists' do + it 'updates line item if one exists' do subject.add(variant, 1) line_item = subject.add(variant, 1) expect(line_item.quantity).to eq(2) expect(order.line_items.size).to eq(1) end - it "should update order totals" do + it "updates order totals" do expect(order.item_total.to_f).to eq(0.00) expect(order.total.to_f).to eq(0.00) @@ -87,7 +87,9 @@ let(:calculator) { SolidusFriendlyPromotions::Calculators::FlatRate.new(preferred_amount: 10) } context "one active line item promotion" do - let!(:action) { SolidusFriendlyPromotions::Actions::AdjustLineItem.create(promotion: promotion, calculator: calculator) } + let!(:action) do + SolidusFriendlyPromotions::Actions::AdjustLineItem.create(promotion: promotion, calculator: calculator) + end it "creates valid discount on order" do subject.add(variant, 1) @@ -97,6 +99,7 @@ context "discount changes order total" do before { subject.add(variant, 1) } + it { expect(subject.order.total).not_to eq variant.price } end end @@ -135,7 +138,7 @@ end end - context "#remove" do + describe "#remove" do context "given an invalid variant" do it "raises an exception" do expect { @@ -145,7 +148,7 @@ end context 'given quantity is not explicitly provided' do - it 'should remove one line item' do + it 'removes one line item' do line_item = subject.add(variant, 3) subject.remove(variant) @@ -157,7 +160,7 @@ it "ensure shipment calls update_amounts instead of order calling check_shipments_and_restart_checkout" do subject.add(variant, 1) shipment = create(:shipment) - expect(subject.order).to_not receive(:check_shipments_and_restart_checkout) + expect(subject.order).not_to receive(:check_shipments_and_restart_checkout) expect(shipment).to receive(:update_amounts) subject.remove(variant, 1, shipment: shipment) end @@ -171,28 +174,28 @@ end end - it 'should reduce line_item quantity if quantity is less the line_item quantity' do + it 'reduces line_item quantity if quantity is less the line_item quantity' do line_item = subject.add(variant, 3) subject.remove(variant, 1) expect(line_item.reload.quantity).to eq(2) end - it 'should remove line_item if quantity matches line_item quantity' do + it 'removes line_item if quantity matches line_item quantity' do subject.add(variant, 1) subject.remove(variant, 1) expect(order.reload.find_line_item_by_variant(variant)).to be_nil end - it 'should remove line_item if quantity is greater than line_item quantity' do + it 'removes line_item if quantity is greater than line_item quantity' do subject.add(variant, 1) subject.remove(variant, 2) expect(order.reload.find_line_item_by_variant(variant)).to be_nil end - it "should update order totals" do + it "updates order totals" do expect(order.item_total.to_f).to eq(0.00) expect(order.total.to_f).to eq(0.00) @@ -207,12 +210,12 @@ end end - context "#remove_line_item" do + describe "#remove_line_item" do context 'given a shipment' do it "ensure shipment calls update_amounts instead of order calling check_shipments_and_restart_checkout" do line_item = subject.add(variant, 1) shipment = create(:shipment) - expect(subject.order).to_not receive(:check_shipments_and_restart_checkout) + expect(subject.order).not_to receive(:check_shipments_and_restart_checkout) expect(shipment).to receive(:update_amounts) subject.remove_line_item(line_item, shipment: shipment) end @@ -226,14 +229,14 @@ end end - it 'should remove line_item' do + it 'removes line_item' do line_item = subject.add(variant, 1) subject.remove_line_item(line_item) - expect(order.reload.line_items).to_not include(line_item) + expect(order.reload.line_items).not_to include(line_item) end - it "should update order totals" do + it "updates order totals" do expect(order.item_total.to_f).to eq(0.00) expect(order.total.to_f).to eq(0.00) @@ -265,7 +268,7 @@ it "updates order totals" do expect { subject.update_cart params - }.to change { subject.order.total } + }.to(change { subject.order.total }) end context "submits item quantity 0" do @@ -278,7 +281,7 @@ it "removes item from order" do expect { subject.update_cart params - }.to change { subject.order.line_items.count } + }.to(change { subject.order.line_items.count }) end end @@ -302,11 +305,11 @@ it "updates order payment state" do expect { subject.add variant - }.to change { order.payment_state } + }.to(change(order, :payment_state)) expect { subject.remove variant - }.to change { order.payment_state } + }.to(change(order, :payment_state)) end end @@ -317,7 +320,7 @@ expect(order.approver).to be_nil expect(order.approver_name).to eq('Jordan') expect(order.approved_at).to be_present - expect(order.approved?).to be_truthy + expect(order).to be_approved end end @@ -329,7 +332,7 @@ expect(order.approver).to eq(user) expect(order.approver_name).to be_nil expect(order.approved_at).to be_present - expect(order.approved?).to be_truthy + expect(order).to be_approved end end @@ -341,7 +344,7 @@ expect(order.approver).to eq(user) expect(order.approver_name).to eq('Jordan') expect(order.approved_at).to be_present - expect(order.approved?).to be_truthy + expect(order).to be_approved end end diff --git a/friendly_promotions/spec/models/spree/shipping_rate_spec.rb b/friendly_promotions/spec/models/spree/shipping_rate_spec.rb index be172d70c29..ccdddf887d8 100644 --- a/friendly_promotions/spec/models/spree/shipping_rate_spec.rb +++ b/friendly_promotions/spec/models/spree/shipping_rate_spec.rb @@ -6,7 +6,7 @@ let(:subject) { build(:shipping_rate) } describe '#display_price' do - before(:each) { subject.amount = 5 } + before { subject.amount = 5 } it 'returns formatted amount' do expect(subject.display_price).to eq('$5.00') @@ -14,10 +14,10 @@ end describe "#total_before_tax" do - let(:shipping_rate) { build(:shipping_rate, cost: 4) } - subject { shipping_rate.total_before_tax } + let(:shipping_rate) { build(:shipping_rate, cost: 4) } + it { is_expected.to eq(4) } context "with discounts" do @@ -29,18 +29,18 @@ end describe "#display_total_before_tax" do - let(:shipping_rate) { build(:shipping_rate, cost: 10) } - subject { shipping_rate.display_total_before_tax } + let(:shipping_rate) { build(:shipping_rate, cost: 10) } + it { is_expected.to eq(Spree::Money.new("10.00")) } end describe "#display_promo_total" do - let(:shipping_rate) { build(:shipping_rate) } - subject { shipping_rate.display_promo_total } + let(:shipping_rate) { build(:shipping_rate) } + it { is_expected.to eq(Spree::Money.new("0")) } end end diff --git a/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_actions_request_spec.rb b/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_actions_request_spec.rb index bec5489a4e9..7aef8b450b5 100644 --- a/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_actions_request_spec.rb +++ b/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_actions_request_spec.rb @@ -20,9 +20,11 @@ end it "can not create a promotion action of an invalid type" do - post solidus_friendly_promotions.admin_promotion_promotion_actions_path(promotion_id: promotion.id, promotion_action: { type: "Spree::InvalidType" }) + post solidus_friendly_promotions.admin_promotion_promotion_actions_path(promotion_id: promotion.id), params: { + promotion_action: { type: "Spree::InvalidType" } + } expect(response).to be_redirect expect(response).to redirect_to spree.edit_admin_promotion_path(promotion) - expect(promotion.rules.count).to eq(0) + expect(promotion.actions.count).to eq(0) end end diff --git a/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_rules_request_spec.rb b/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_rules_request_spec.rb index d4067f55ace..6c2469d9f09 100644 --- a/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_rules_request_spec.rb +++ b/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_rules_request_spec.rb @@ -20,7 +20,9 @@ end it "can not create a promotion rule of an invalid type" do - post solidus_friendly_promotions.admin_promotion_promotion_rules_path(promotion_id: promotion.id, promotion_rule: { type: "Spree::InvalidType" }) + post solidus_friendly_promotions.admin_promotion_promotion_rules_path(promotion_id: promotion.id), params: { + promotion_rule: { type: "Spree::InvalidType" } + } expect(response).to be_redirect expect(response).to redirect_to solidus_friendly_promotions.edit_admin_promotion_path(promotion) expect(promotion.rules.count).to eq(0) diff --git a/friendly_promotions/spec/shared_examples/calculator_shared_examples.rb b/friendly_promotions/spec/shared_examples/calculator_shared_examples.rb index c41b3c7e4e6..b4318576f91 100644 --- a/friendly_promotions/spec/shared_examples/calculator_shared_examples.rb +++ b/friendly_promotions/spec/shared_examples/calculator_shared_examples.rb @@ -3,6 +3,7 @@ RSpec.shared_examples_for 'a calculator with a description' do describe ".description" do subject { described_class.description } + it "has a description" do expect(subject.size).to be > 0 end diff --git a/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotion_categories_spec.rb b/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotion_categories_spec.rb index 34c534ae262..206b5bb2752 100644 --- a/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotion_categories_spec.rb +++ b/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotion_categories_spec.rb @@ -13,7 +13,7 @@ end context "listing promotion categories" do - it "should list the existing promotion categories" do + it "lists the existing promotion categories" do within_row(1) do expect(column_text(1)).to eq("name1") expect(column_text(2)).to eq("code1") @@ -33,14 +33,14 @@ click_on "New Promotion Category" end - it "should allow an admin to create a new promotion category" do + it "allows an admin to create a new promotion category" do fill_in "promotion_category_name", with: "promotion test" fill_in "promotion_category_code", with: "prtest" click_button "Create" expect(page).to have_content("successfully created!") end - it "should not allow admin to create promotion category when invalid data" do + it "does not allow admin to create promotion category when invalid data" do fill_in "promotion_category_name", with: "" fill_in "promotion_category_code", with: "prtest" click_button "Create" @@ -49,20 +49,20 @@ end context "edit" do - before(:each) do + before do create(:friendly_promotion_category, name: 'name1') visit solidus_friendly_promotions.admin_promotion_categories_path within_row(1) { click_icon :edit } end - it "should allow an admin to edit an existing promotion category" do + it "allows an admin to edit an existing promotion category" do fill_in "promotion_category_name", with: "name 99" click_button "Update" expect(page).to have_content("successfully updated!") expect(page).to have_content("name 99") end - it "should show validation errors" do + it "shows validation errors" do fill_in "promotion_category_name", with: "" click_button "Update" expect(page).to have_content("Name can't be blank") @@ -70,12 +70,12 @@ end context "delete" do - before(:each) do + before do create(:friendly_promotion_category, name: 'name1') visit solidus_friendly_promotions.admin_promotion_categories_path end - it "should allow an admin to delete an existing promotion category", js: true do + it "allows an admin to delete an existing promotion category", js: true do accept_alert do click_icon :trash end diff --git a/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotions_spec.rb b/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotions_spec.rb index c12e2cc1d63..3d6da8a3fbd 100644 --- a/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotions_spec.rb +++ b/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotions_spec.rb @@ -6,10 +6,21 @@ stub_authorization! describe "#index" do - let!(:promotion1) { create(:friendly_promotion, :with_adjustable_action, name: "name1", code: "code1", path: "path1") } - let!(:promotion2) { create(:friendly_promotion, :with_adjustable_action, name: "name2", code: "code2", path: "path2") } + let!(:promotion1) do + create(:friendly_promotion, :with_adjustable_action, name: "name1", code: "code1", path: "path1") + end + let!(:promotion2) do + create(:friendly_promotion, :with_adjustable_action, name: "name2", code: "code2", path: "path2") + end let!(:promotion3) do - create(:friendly_promotion, :with_adjustable_action, name: "name3", code: "code3", path: "path3", expires_at: Date.yesterday) + create( + :friendly_promotion, + :with_adjustable_action, + name: "name3", + code: "code3", + path: "path3", + expires_at: Date.yesterday + ) end let!(:category) { create :friendly_promotion_category } @@ -22,7 +33,10 @@ it "shows promotion categories" do visit solidus_friendly_promotions.admin_promotions_path - expect(page).to have_select(SolidusFriendlyPromotions::PromotionCategory.model_name.human, options: ["All", category.name]) + expect(page).to have_select( + SolidusFriendlyPromotions::PromotionCategory.model_name.human, + options: ["All", category.name] + ) end context "search" do From c81f3ef0db20d26a912a70ca4267076297232d8d Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 25 Jul 2023 15:07:27 +0200 Subject: [PATCH 305/834] Rubocop controller fixes --- .../admin/promotion_codes_controller.rb | 6 ++++-- .../admin/promotion_rules_controller.rb | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_codes_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_codes_controller.rb index d77de5f734e..38ee1184eeb 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_codes_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_codes_controller.rb @@ -24,8 +24,10 @@ def index def new if @promotion.apply_automatically - flash[:error] = - t('activerecord.errors.models.solidus_friendly_promotions/promotion_code.attributes.base.disallowed_with_apply_automatically') + flash[:error] = t( + :disallowed_with_apply_automatically, + scope: 'activerecord.errors.models.solidus_friendly_promotions/promotion_code.attributes.base' + ) redirect_to solidus_friendly_promotions.admin_promotion_promotion_codes_url(@promotion) else @promotion_code = @promotion.codes.build diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb index 7ecf39e4db0..fb4e960e38a 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb @@ -5,7 +5,7 @@ module Admin class PromotionRulesController < Spree::Admin::BaseController helper 'spree/promotion_rules' - before_action :validate_level, only: [:new, :edit, :create] + before_action :validate_level, only: [:new, :create] before_action :load_promotion, only: [:create, :destroy, :update, :new] before_action :validate_promotion_rule_type, only: [:create] From de8c45af8935c903cd3aaf2f4e5ba62db793c7ef Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 25 Jul 2023 15:15:37 +0200 Subject: [PATCH 306/834] Skip some cops in this project Solidus is complex. I'm upping the number of allowed expectations per spec, and of allowed memoized helpers. Spec setup in this system is hard enough as it is. --- friendly_promotions/.rubocop.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/friendly_promotions/.rubocop.yml b/friendly_promotions/.rubocop.yml index b075a8f6826..1ff53b95e64 100644 --- a/friendly_promotions/.rubocop.yml +++ b/friendly_promotions/.rubocop.yml @@ -3,3 +3,25 @@ require: AllCops: NewCops: disable + + +RSpec/MultipleExpectations: + Max: 10 + +RSpec/MultipleMemoizedHelpers: + Max: 10 + +RSpec/NamedSubject: + Enabled: false + +RSpec/MessageSpies: + Enabled: false + +RSpec/ContextWording: + Enabled: false + +RSpec/NestedGroups: + Max: 5 + +RSpec/LetSetup: + Enabled: false From da5e6e0f8fb23ea19098cfc3590a09ba43ea078a Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 25 Jul 2023 15:18:12 +0200 Subject: [PATCH 307/834] More rubocop fixes --- .../admin/promotion_actions_request_spec.rb | 2 +- .../admin/promotion_rules_request_spec.rb | 2 +- .../admin/{promotions_spec.rb => promotions_request_spec.rb} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename friendly_promotions/spec/requests/solidus_friendly_promotions/admin/{promotions_spec.rb => promotions_request_spec.rb} (100%) diff --git a/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_actions_request_spec.rb b/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_actions_request_spec.rb index 7aef8b450b5..ec0ecd06aa5 100644 --- a/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_actions_request_spec.rb +++ b/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_actions_request_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -describe SolidusFriendlyPromotions::Admin::PromotionActionsController, type: :request do +describe "Admin::PromotionActions", type: :request do stub_authorization! let!(:promotion) { create(:friendly_promotion) } diff --git a/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_rules_request_spec.rb b/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_rules_request_spec.rb index 6c2469d9f09..75e7ae83c79 100644 --- a/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_rules_request_spec.rb +++ b/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_rules_request_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -describe SolidusFriendlyPromotions::Admin::PromotionRulesController, type: :request do +describe "Admin::PromotionRules", type: :request do let!(:promotion) { create(:friendly_promotion) } context "when the user is authorized" do diff --git a/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotions_spec.rb b/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotions_request_spec.rb similarity index 100% rename from friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotions_spec.rb rename to friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotions_request_spec.rb From 37eb18415cfb52b8b8c591dd34b7b5f12fc7f563 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 25 Jul 2023 15:19:13 +0200 Subject: [PATCH 308/834] Fix RC offense --- .../models/solidus_friendly_promotions/order_discounter.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb b/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb index a9f950a3897..8333194982c 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb @@ -73,8 +73,8 @@ def update_adjustments(item, taxed_items) # @param [SolidusFriendlyPromotions::ItemDiscount] tax_item calculated discounts for an item # @return [Spree::Adjustment] the created or updated tax adjustment def update_adjustment(item, discount_item) - adjustment = item.adjustments.detect do |adjustment| - adjustment.source == discount_item.source + adjustment = item.adjustments.detect do |item_adjustment| + item_adjustment.source == discount_item.source end adjustment ||= item.adjustments.new( From 86a126efe6ea2b25caf013f84f01042d8021f6eb Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 25 Jul 2023 15:27:51 +0200 Subject: [PATCH 309/834] Fix RC offenses in simple order contents spec --- .../simple_order_contents_spec.rb | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/simple_order_contents_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/simple_order_contents_spec.rb index bd3c248d4eb..fefa74d8db7 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/simple_order_contents_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/simple_order_contents_spec.rb @@ -9,7 +9,6 @@ let(:order) { create(:order) } let(:variant) { create(:variant) } let!(:stock_location) { variant.stock_locations.first } - let(:stock_location_2) { create(:stock_location) } describe "#add" do context 'given quantity is not explicitly provided' do @@ -112,11 +111,8 @@ end context 'when the order has a taxable address' do - before do - expect(order.tax_address.country_id).to be_present - end - it 'creates a tax adjustment' do + expect(order.tax_address.country_id).to be_present order_contents.add(variant) line_item = order.find_line_item_by_variant(variant) expect(line_item.adjustments.tax.count).to eq(1) @@ -126,10 +122,10 @@ context 'when the order does not have a taxable address' do before do order.update!(ship_address: nil, bill_address: nil) - expect(order.tax_address.country_id).to be_nil end it 'creates a tax adjustment' do + expect(order.tax_address.country_id).to be_nil order_contents.add(variant) line_item = order.find_line_item_by_variant(variant) expect(line_item.adjustments.tax.count).to eq(0) From 235faa0f222e4b6ce797cc5e1739cbf3263a9e8f Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 25 Jul 2023 15:29:07 +0200 Subject: [PATCH 310/834] Fix user logged in spec --- .../rules/product_spec.rb | 36 +++++++++---------- .../rules/user_logged_in_spec.rb | 2 +- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/product_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/product_spec.rb index b8fffa7429c..3c0b03ab616 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/product_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/product_spec.rb @@ -10,10 +10,9 @@ describe "#eligible?(order)" do let(:order) { Spree::Order.new } - - before do - 3.times { |i| instance_variable_set("@product#{i}", mock_model(Spree::Product)) } - end + let(:product_one) { build(:product) } + let(:product_two) { build(:product) } + let(:product_three) { build(:product) } it "is eligible if there are no products" do allow(rule).to receive_messages(eligible_products: []) @@ -24,15 +23,15 @@ let(:rule_options) { super().merge(preferred_match_policy: "any") } it "is eligible if any of the products is in eligible products" do - allow(rule).to receive_messages(order_products: [@product1, @product2]) - allow(rule).to receive_messages(eligible_products: [@product2, @product3]) + allow(rule).to receive_messages(order_products: [product_one, product_two]) + allow(rule).to receive_messages(eligible_products: [product_two, product_three]) expect(rule).to be_eligible(order) end context "when none of the products are eligible products" do before do - allow(rule).to receive_messages(order_products: [@product1]) - allow(rule).to receive_messages(eligible_products: [@product2, @product3]) + allow(rule).to receive_messages(order_products: [product_one]) + allow(rule).to receive_messages(eligible_products: [product_two, product_three]) end it { expect(rule).not_to be_eligible(order) } @@ -55,23 +54,24 @@ let(:rule_options) { super().merge(preferred_match_policy: "all") } it "is eligible if all of the eligible products are ordered" do - allow(rule).to receive_messages(order_products: [@product3, @product2, @product1]) - allow(rule).to receive_messages(eligible_products: [@product2, @product3]) + allow(rule).to receive_messages(order_products: [product_three, product_two, product_one]) + allow(rule).to receive_messages(eligible_products: [product_two, product_three]) expect(rule).to be_eligible(order) end context "when any of the eligible products is not ordered" do before do - allow(rule).to receive_messages(order_products: [@product1, @product2]) - allow(rule).to receive_messages(eligible_products: [@product1, @product2, @product3]) + allow(rule).to receive_messages(order_products: [product_one, product_two]) + allow(rule).to receive_messages(eligible_products: [product_one, product_two, product_three]) end it { expect(rule).not_to be_eligible(order) } it "sets an error message" do rule.eligible?(order) - expect(rule.eligibility_errors.full_messages.first) - .to eq "This coupon code can't be applied because you don't have all of the necessary products in your cart." + expect(rule.eligibility_errors.full_messages.first).to eq( + "This coupon code can't be applied because you don't have all of the necessary products in your cart." + ) end it "sets an error code" do @@ -86,15 +86,15 @@ let(:rule_options) { super().merge(preferred_match_policy: "none") } it "is eligible if none of the order's products are in eligible products" do - allow(rule).to receive_messages(order_products: [@product1]) - allow(rule).to receive_messages(eligible_products: [@product2, @product3]) + allow(rule).to receive_messages(order_products: [product_one]) + allow(rule).to receive_messages(eligible_products: [product_two, product_three]) expect(rule).to be_eligible(order) end context "when any of the order's products are in eligible products" do before do - allow(rule).to receive_messages(order_products: [@product1, @product2]) - allow(rule).to receive_messages(eligible_products: [@product2, @product3]) + allow(rule).to receive_messages(order_products: [product_one, product_two]) + allow(rule).to receive_messages(eligible_products: [product_two, product_three]) end it { expect(rule).not_to be_eligible(order) } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_logged_in_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_logged_in_spec.rb index 4901483a5bb..1d60a17f1c1 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_logged_in_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_logged_in_spec.rb @@ -9,7 +9,7 @@ let(:order) { Spree::Order.new } it "is eligible if order has an associated user" do - user = double("User") + user = Spree::LegacyUser.new allow(order).to receive_messages(user: user) expect(rule).to be_eligible(order) From 1dc703b2915309b8fb09c647570d4236a437e480 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 25 Jul 2023 16:15:38 +0200 Subject: [PATCH 311/834] Fix taxon spec --- .../rules/taxon_spec.rb | 68 ++++++++++--------- 1 file changed, 35 insertions(+), 33 deletions(-) diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/taxon_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/taxon_spec.rb index 7b2f635cbd2..ff879ca3de5 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/taxon_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/taxon_spec.rb @@ -8,24 +8,23 @@ end let(:product) { order.products.first } let(:order) { create :order_with_line_items } - let(:taxon2) { create :taxon, name: "second" } - let(:taxon) { create :taxon, name: "first" } + let(:taxon_one) { create :taxon, name: "first" } + let(:taxon_two) { create :taxon, name: "second" } it { is_expected.to have_many(:taxons) } describe "taxon_ids_string=" do - subject { rule.assign_attributes("taxon_ids_string" => taxon_2.id.to_s) } + subject { rule.assign_attributes("taxon_ids_string" => taxon_two.id.to_s) } let!(:promotion) { create(:friendly_promotion) } - let!(:taxon_1) { create(:taxon) } - let!(:taxon_2) { create(:taxon) } + let(:rule) { promotion.rules.build(type: described_class.to_s) } it "creates a valid rule with a taxon" do subject expect(rule).to be_valid rule.save! - expect(rule.reload.taxons).to include(taxon_2) + expect(rule.reload.taxons).to include(taxon_two) end end @@ -36,20 +35,21 @@ end it "is eligible if order does have any prefered taxon" do - product.taxons << taxon - rule.taxons << taxon + product.taxons << taxon_one + rule.taxons << taxon_one expect(rule).to be_eligible(order) end context "when order does not have any prefered taxon" do - before { rule.taxons << taxon2 } + before { rule.taxons << taxon_two } it { expect(rule).not_to be_eligible(order) } it "sets an error message" do rule.eligible?(order) - expect(rule.eligibility_errors.full_messages.first) - .to eq "You need to add a product from an applicable category before applying this coupon code." + expect(rule.eligibility_errors.full_messages.first).to eq( + "You need to add a product from an applicable category before applying this coupon code." + ) end it "sets an error code" do @@ -61,9 +61,9 @@ context "when a product has a taxon child of a taxon rule" do before do - taxon.children << taxon2 - product.taxons << taxon2 - rule.taxons << taxon + taxon_one.children << taxon_two + product.taxons << taxon_two + rule.taxons << taxon_one end it { expect(rule).to be_eligible(order) } @@ -76,23 +76,24 @@ end it "is eligible order has all prefered taxons" do - product.taxons << taxon2 - order.products.last.taxons << taxon + product.taxons << taxon_two + order.products.last.taxons << taxon_one - rule.taxons = [taxon, taxon2] + rule.taxons = [taxon_one, taxon_two] expect(rule).to be_eligible(order) end context "when order does not have all prefered taxons" do - before { rule.taxons << taxon } + before { rule.taxons << taxon_one } it { expect(rule).not_to be_eligible(order) } it "sets an error message" do rule.eligible?(order) - expect(rule.eligibility_errors.full_messages.first) - .to eq "You need to add a product from all applicable categories before applying this coupon code." + expect(rule.eligibility_errors.full_messages.first).to eq( + "You need to add a product from all applicable categories before applying this coupon code." + ) end it "sets an error code" do @@ -103,15 +104,15 @@ end context "when a product has a taxon child of a taxon rule" do - let(:taxon3) { create :taxon } + let(:taxon_three) { create :taxon } before do - taxon.children << taxon2 - taxon.save! - taxon.reload + taxon_one.children << taxon_two + taxon_one.save! + taxon_one.reload - product.taxons = [taxon2, taxon3] - rule.taxons = [taxon, taxon3] + product.taxons = [taxon_two, taxon_three] + rule.taxons = [taxon_one, taxon_three] end it { expect(rule).to be_eligible(order) } @@ -124,15 +125,15 @@ end context "none of the order's products are in listed taxon" do - before { rule.taxons << taxon2 } + before { rule.taxons << taxon_two } it { expect(rule).to be_eligible(order) } end context "one of the order's products is in a listed taxon" do before do - order.products.first.taxons << taxon - rule.taxons << taxon + order.products.first.taxons << taxon_one + rule.taxons << taxon_one end it "is not eligible" do @@ -141,8 +142,9 @@ it "sets an error message" do rule.eligible?(order) - expect(rule.eligibility_errors.full_messages.first) - .to eq "Your cart contains a product from an excluded category that prevents this coupon code from being applied." + expect(rule.eligibility_errors.full_messages.first).to eq( + "Your cart contains a product from an excluded category that prevents this coupon code from being applied." + ) end it "sets an error code" do @@ -155,8 +157,8 @@ context "with an invalid match policy" do before do - order.products.first.taxons << taxon - rule.taxons << taxon + order.products.first.taxons << taxon_one + rule.taxons << taxon_one rule.preferred_match_policy = "invalid" rule.save!(validate: false) end From 4357bd8a927d80737f385e3ee22ab5b99e961879 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 25 Jul 2023 16:18:13 +0200 Subject: [PATCH 312/834] Fix one use per user spec --- .../rules/one_use_per_user_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/one_use_per_user_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/one_use_per_user_spec.rb index b04f8717849..d7a863ce539 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/one_use_per_user_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/one_use_per_user_spec.rb @@ -8,8 +8,8 @@ describe "#eligible?(order)" do subject { rule.eligible?(order) } - let(:order) { double Spree::Order, user: user } - let(:user) { double Spree::LegacyUser } + let(:order) { instance_double("Spree::Order", user: user) } + let(:user) { instance_double("Spree::LegacyUser") } let(:promotion) { stub_model SolidusFriendlyPromotions::Promotion, used_by?: used_by } let(:used_by) { false } From 1336288abb115e1c227d7036a1bf53869ce1e566 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 25 Jul 2023 16:21:40 +0200 Subject: [PATCH 313/834] RC Fix: Item total rule --- .../models/solidus_friendly_promotions/rules/item_total_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/item_total_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/item_total_spec.rb index 73da9a6bab5..38fc3a2f307 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/item_total_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/item_total_spec.rb @@ -9,7 +9,7 @@ preferred_operator: preferred_operator ) end - let(:order) { double(:order, item_total: item_total, currency: order_currency) } + let(:order) { instance_double('Spree::Order', item_total: item_total, currency: order_currency) } let(:preferred_amount) { 50 } let(:order_currency) { "USD" } From 61b19ad0a61897785e0c94794b01a18bff4b8beb Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 25 Jul 2023 16:54:56 +0200 Subject: [PATCH 314/834] Run on Chrome only --- friendly_promotions/.circleci/config.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/friendly_promotions/.circleci/config.yml b/friendly_promotions/.circleci/config.yml index 11d33880218..c153c92e2e5 100644 --- a/friendly_promotions/.circleci/config.yml +++ b/friendly_promotions/.circleci/config.yml @@ -8,7 +8,7 @@ jobs: name: solidusio_extensions/mysql ruby_version: '3.1' steps: - - browser-tools/install-browser-tools + - browser-tools/install-chrome - solidusio_extensions/run-tests: working_directory: '~/project/solidus_friendly_promotions' run-specs-with-postgres: @@ -16,7 +16,7 @@ jobs: name: solidusio_extensions/postgres ruby_version: '3.2' steps: - - browser-tools/install-browser-tools + - browser-tools/install-chrome - solidusio_extensions/run-tests: working_directory: '~/project/solidus_friendly_promotions' run-specs-with-sqlite: @@ -24,7 +24,7 @@ jobs: name: solidusio_extensions/sqlite ruby_version: '3.0' steps: - - browser-tools/install-browser-tools + - browser-tools/install-chrome - solidusio_extensions/run-tests: working_directory: '~/project/solidus_friendly_promotions' workflows: From 5a00b1f611f30596823f1fb660dc641404525694 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 25 Jul 2023 17:32:40 +0200 Subject: [PATCH 315/834] Fix Rubocop for good --- friendly_promotions/.rubocop.yml | 4 +- friendly_promotions/.rubocop_todo.yml | 157 ++++++++++++++++++++++++++ 2 files changed, 160 insertions(+), 1 deletion(-) create mode 100644 friendly_promotions/.rubocop_todo.yml diff --git a/friendly_promotions/.rubocop.yml b/friendly_promotions/.rubocop.yml index 1ff53b95e64..efca29831b3 100644 --- a/friendly_promotions/.rubocop.yml +++ b/friendly_promotions/.rubocop.yml @@ -1,3 +1,5 @@ +inherit_from: .rubocop_todo.yml + require: - solidus_dev_support/rubocop @@ -9,7 +11,7 @@ RSpec/MultipleExpectations: Max: 10 RSpec/MultipleMemoizedHelpers: - Max: 10 + Max: 12 RSpec/NamedSubject: Enabled: false diff --git a/friendly_promotions/.rubocop_todo.yml b/friendly_promotions/.rubocop_todo.yml new file mode 100644 index 00000000000..02169fb61eb --- /dev/null +++ b/friendly_promotions/.rubocop_todo.yml @@ -0,0 +1,157 @@ +# This configuration was generated by +# `rubocop --auto-gen-config --exclude-limit 10000` +# on 2023-07-25 15:26:43 UTC using RuboCop version 1.54.1. +# The point is for the user to remove these configuration records +# one by one as the offenses are removed from the code base. +# Note that changes in the inspected code, or installation of new +# versions of RuboCop, may require this file to be generated again. + +# Offense count: 2 +# Configuration parameters: AllowedMethods. +# AllowedMethods: enums +Lint/ConstantDefinitionInBlock: + Exclude: + - 'spec/models/solidus_friendly_promotions/promotion_rule_spec.rb' + +# Offense count: 1 +Lint/DuplicateMethods: + Exclude: + - 'app/models/solidus_friendly_promotions/promotion.rb' + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +Lint/RedundantCopDisableDirective: + Exclude: + - 'lib/generators/solidus_friendly_promotions/install/install_generator.rb' + +# Offense count: 8 +# Configuration parameters: EnforcedStyle, CheckMethodNames, CheckSymbols, AllowedIdentifiers, AllowedPatterns. +# SupportedStyles: snake_case, normalcase, non_integer +# AllowedIdentifiers: capture3, iso8601, rfc1123_date, rfc822, rfc2822, rfc3339, x86_64 +Naming/VariableNumber: + Exclude: + - 'spec/models/solidus_friendly_promotions/rules/first_repeat_purchase_since_spec.rb' + +# Offense count: 1 +RSpec/AnyInstance: + Exclude: + - 'spec/jobs/solidus_friendly_promotions/promotion_code_batch_job_spec.rb' + +# Offense count: 2 +# Configuration parameters: IgnoredMetadata. +RSpec/DescribeClass: + Exclude: + - 'spec/lib/solidus_friendly_promotions/testing_support/factories_spec.rb' + - 'spec/models/promotion/integration_spec.rb' + +# Offense count: 2 +RSpec/ExpectInHook: + Exclude: + - 'spec/jobs/solidus_friendly_promotions/promotion_code_batch_job_spec.rb' + - 'spec/models/solidus_friendly_promotions/promotion_code/batch_builder_spec.rb' + +# Offense count: 7 +# Configuration parameters: AssignmentOnly. +RSpec/InstanceVariable: + Exclude: + - 'spec/models/solidus_friendly_promotions/promotion_spec.rb' + +# Offense count: 2 +RSpec/IteratedExpectation: + Exclude: + - 'spec/jobs/solidus_friendly_promotions/promotion_code_batch_job_spec.rb' + +# Offense count: 2 +RSpec/LeakyConstantDeclaration: + Exclude: + - 'spec/models/solidus_friendly_promotions/promotion_rule_spec.rb' + +# Offense count: 3 +RSpec/MessageChain: + Exclude: + - 'spec/models/solidus_friendly_promotions/rules/first_order_spec.rb' + +# Offense count: 4 +# Configuration parameters: AllowSubject. +RSpec/MultipleMemoizedHelpers: + Max: 12 + +# Offense count: 1 +RSpec/OverwritingSetup: + Exclude: + - 'spec/models/solidus_friendly_promotions/calculators/flexi_rate_spec.rb' + +# Offense count: 2 +RSpec/RepeatedExampleGroupBody: + Exclude: + - 'spec/lib/solidus_friendly_promotions/configuration_spec.rb' + +# Offense count: 2 +RSpec/RepeatedExampleGroupDescription: + Exclude: + - 'spec/models/solidus_friendly_promotions/calculators/flat_rate_spec.rb' + +# Offense count: 1 +RSpec/SubjectStub: + Exclude: + - 'spec/models/solidus_friendly_promotions/promotion_code/batch_builder_spec.rb' + +# Offense count: 2 +# Configuration parameters: IgnoreNameless, IgnoreSymbolicNames. +RSpec/VerifiedDoubles: + Exclude: + - 'spec/models/solidus_friendly_promotions/calculators/percent_spec.rb' + +# Offense count: 1 +# Configuration parameters: Include. +# Include: db/**/*.rb +Rails/CreateTableWithTimestamps: + Exclude: + - 'db/migrate/20230725074235_create_shipping_rate_discounts.rb' + +# Offense count: 7 +# Configuration parameters: Include. +# Include: app/models/**/*.rb +Rails/HasManyOrHasOneDependent: + Exclude: + - 'app/models/solidus_friendly_promotions/promotion.rb' + - 'app/models/solidus_friendly_promotions/promotion_action.rb' + - 'app/models/solidus_friendly_promotions/promotion_category.rb' + - 'app/models/solidus_friendly_promotions/promotion_code.rb' + +# Offense count: 7 +# Configuration parameters: IgnoreScopes, Include. +# Include: app/models/**/*.rb +Rails/InverseOf: + Exclude: + - 'app/models/solidus_friendly_promotions/promotion.rb' + - 'app/models/solidus_friendly_promotions/rules/line_item_product.rb' + - 'app/models/solidus_friendly_promotions/rules/line_item_taxon.rb' + - 'app/models/solidus_friendly_promotions/rules/product.rb' + - 'app/models/solidus_friendly_promotions/rules/store.rb' + - 'app/models/solidus_friendly_promotions/rules/taxon.rb' + - 'app/models/solidus_friendly_promotions/rules/user.rb' + +# Offense count: 2 +# This cop supports unsafe autocorrection (--autocorrect-all). +Rails/ReflectionClassName: + Exclude: + - 'app/models/solidus_friendly_promotions/promotion_rules_user.rb' + - 'app/models/solidus_friendly_promotions/rules/user.rb' + +# Offense count: 9 +# Configuration parameters: ForbiddenMethods, AllowedMethods. +# ForbiddenMethods: decrement!, decrement_counter, increment!, increment_counter, insert, insert!, insert_all, insert_all!, toggle!, touch, touch_all, update_all, update_attribute, update_column, update_columns, update_counters, upsert, upsert_all +Rails/SkipsModelValidations: + Exclude: + - 'lib/solidus_friendly_promotions/testing_support/friendly_order_factory.rb' + - 'spec/models/solidus_friendly_promotions/promotion_code_batch_spec.rb' + - 'spec/models/solidus_friendly_promotions/promotion_code_spec.rb' + - 'spec/models/solidus_friendly_promotions/promotion_spec.rb' + +# Offense count: 5 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns. +# URISchemes: http, https +Layout/LineLength: + Max: 148 From cf62a428b2bec0f98ed596c29230e6396a38d644 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 25 Jul 2023 17:36:19 +0200 Subject: [PATCH 316/834] Only run tests on main --- friendly_promotions/.circleci/config.yml | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/friendly_promotions/.circleci/config.yml b/friendly_promotions/.circleci/config.yml index c153c92e2e5..3c38e460b05 100644 --- a/friendly_promotions/.circleci/config.yml +++ b/friendly_promotions/.circleci/config.yml @@ -9,24 +9,30 @@ jobs: ruby_version: '3.1' steps: - browser-tools/install-chrome - - solidusio_extensions/run-tests: - working_directory: '~/project/solidus_friendly_promotions' + - checkout + - solidusio_extensions/dependencies + - solidusio_extensions/run-tests-solidus-current + - solidusio_extensions/run-tests-solidus-main run-specs-with-postgres: executor: name: solidusio_extensions/postgres ruby_version: '3.2' steps: - browser-tools/install-chrome - - solidusio_extensions/run-tests: - working_directory: '~/project/solidus_friendly_promotions' + - checkout + - solidusio_extensions/dependencies + - solidusio_extensions/run-tests-solidus-current + - solidusio_extensions/run-tests-solidus-main run-specs-with-sqlite: executor: name: solidusio_extensions/sqlite ruby_version: '3.0' steps: - browser-tools/install-chrome - - solidusio_extensions/run-tests: - working_directory: '~/project/solidus_friendly_promotions' + - checkout + - solidusio_extensions/dependencies + - solidusio_extensions/run-tests-solidus-current + - solidusio_extensions/run-tests-solidus-main workflows: Run specs on supported Solidus versions: jobs: From 6677c0356451b4bba121cd3912de3d78f4c72bcf Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 26 Jul 2023 10:55:46 +0200 Subject: [PATCH 317/834] Remove duplicate index --- .../migrate/20230704102656_create_promotion_code_batches.rb | 6 ------ 1 file changed, 6 deletions(-) diff --git a/friendly_promotions/db/migrate/20230704102656_create_promotion_code_batches.rb b/friendly_promotions/db/migrate/20230704102656_create_promotion_code_batches.rb index 0a1cd5aba53..7eee38d0181 100644 --- a/friendly_promotions/db/migrate/20230704102656_create_promotion_code_batches.rb +++ b/friendly_promotions/db/migrate/20230704102656_create_promotion_code_batches.rb @@ -13,12 +13,6 @@ def change t.timestamps precision: 6 end - add_foreign_key( - :friendly_promotion_code_batches, - :friendly_promotions, - column: :promotion_id - ) - add_column( :friendly_promotion_codes, :promotion_code_batch_id, From ac9d516f2004bf775bc102613005a0cac005fd99 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 26 Jul 2023 12:15:27 +0200 Subject: [PATCH 318/834] Fix MySQL build --- .../db/migrate/20230704083830_add_rule_tables.rb | 8 ++++---- .../20230704102656_create_promotion_code_batches.rb | 2 +- .../20230705171556_create_friendly_order_promotions.rb | 2 +- .../20230725074235_create_shipping_rate_discounts.rb | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/friendly_promotions/db/migrate/20230704083830_add_rule_tables.rb b/friendly_promotions/db/migrate/20230704083830_add_rule_tables.rb index bdabd239ccf..e412ea601c0 100644 --- a/friendly_promotions/db/migrate/20230704083830_add_rule_tables.rb +++ b/friendly_promotions/db/migrate/20230704083830_add_rule_tables.rb @@ -1,28 +1,28 @@ class AddRuleTables < ActiveRecord::Migration[7.0] def change create_table :friendly_products_promotion_rules, force: :cascade do |t| - t.references :product, index: true, null: false, foreign_key: { to_table: :spree_products } + t.references :product, type: :integer, index: true, null: false, foreign_key: { to_table: :spree_products } t.references :promotion_rule, index: true, null: false, foreign_key: { to_table: :friendly_promotion_rules } t.timestamps end create_table :friendly_promotion_rules_taxons, force: :cascade do |t| - t.references :taxon, index: true, null: false, foreign_key: { to_table: :spree_taxons } + t.references :taxon, type: :integer, index: true, null: false, foreign_key: { to_table: :spree_taxons } t.references :promotion_rule, index: true, null: false, foreign_key: { to_table: :friendly_promotion_rules } t.timestamps end create_table :friendly_promotion_rules_users, force: :cascade do |t| - t.references :user, index: true, null: false, foreign_key: { to_table: Spree.user_class.table_name } + t.references :user, type: :integer, index: true, null: false, foreign_key: { to_table: Spree.user_class.table_name } t.references :promotion_rule, index: true, null: false, foreign_key: { to_table: :friendly_promotion_rules } t.timestamps end create_table :friendly_promotion_rules_stores do |t| - t.references :store, index: true, null: false, foreign_key: { to_table: :spree_stores } + t.references :store, type: :integer, index: true, null: false, foreign_key: { to_table: :spree_stores } t.references :promotion_rule, index: true, null: false t.timestamps diff --git a/friendly_promotions/db/migrate/20230704102656_create_promotion_code_batches.rb b/friendly_promotions/db/migrate/20230704102656_create_promotion_code_batches.rb index 7eee38d0181..0e7c2a9940d 100644 --- a/friendly_promotions/db/migrate/20230704102656_create_promotion_code_batches.rb +++ b/friendly_promotions/db/migrate/20230704102656_create_promotion_code_batches.rb @@ -16,7 +16,7 @@ def change add_column( :friendly_promotion_codes, :promotion_code_batch_id, - :integer + :bigint ) add_foreign_key( diff --git a/friendly_promotions/db/migrate/20230705171556_create_friendly_order_promotions.rb b/friendly_promotions/db/migrate/20230705171556_create_friendly_order_promotions.rb index a04e320aa0b..e1cdee49d82 100644 --- a/friendly_promotions/db/migrate/20230705171556_create_friendly_order_promotions.rb +++ b/friendly_promotions/db/migrate/20230705171556_create_friendly_order_promotions.rb @@ -1,7 +1,7 @@ class CreateFriendlyOrderPromotions < ActiveRecord::Migration[7.0] def change create_table :friendly_order_promotions do |t| - t.references :order, index: true, null: false, foreign_key: { to_table: :spree_orders } + t.references :order, type: :integer, index: true, null: false, foreign_key: { to_table: :spree_orders } t.references :promotion, index: true, null: false, foreign_key: { to_table: :friendly_promotions } t.references :promotion_code, index: true, null: true, foreign_key: { to_table: :friendly_promotion_codes } diff --git a/friendly_promotions/db/migrate/20230725074235_create_shipping_rate_discounts.rb b/friendly_promotions/db/migrate/20230725074235_create_shipping_rate_discounts.rb index 2bb7c594f3e..711375f001a 100644 --- a/friendly_promotions/db/migrate/20230725074235_create_shipping_rate_discounts.rb +++ b/friendly_promotions/db/migrate/20230725074235_create_shipping_rate_discounts.rb @@ -1,8 +1,8 @@ class CreateShippingRateDiscounts < ActiveRecord::Migration[7.0] def change create_table :friendly_shipping_rate_discounts do |t| - t.references :promotion_action, null: false, foreign_key: { to_table: :friendly_promotion_actions } - t.references :shipping_rate, null: false, foreign_key: { to_table: :spree_shipping_rates } + t.references :promotion_action, type: :bigint, null: false, foreign_key: { to_table: :friendly_promotion_actions } + t.references :shipping_rate, type: :integer, null: false, foreign_key: { to_table: :spree_shipping_rates } t.decimal :amount, precision: 10, scale: 2, null: false t.string :label, null: false end From a6bbd04ff50b6934b877d9e9c90c189164397f23 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 26 Jul 2023 14:06:21 +0200 Subject: [PATCH 319/834] Add timestamps to shipping rate discounts --- .../db/migrate/20230725074235_create_shipping_rate_discounts.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/friendly_promotions/db/migrate/20230725074235_create_shipping_rate_discounts.rb b/friendly_promotions/db/migrate/20230725074235_create_shipping_rate_discounts.rb index 711375f001a..a2072b1839f 100644 --- a/friendly_promotions/db/migrate/20230725074235_create_shipping_rate_discounts.rb +++ b/friendly_promotions/db/migrate/20230725074235_create_shipping_rate_discounts.rb @@ -5,6 +5,8 @@ def change t.references :shipping_rate, type: :integer, null: false, foreign_key: { to_table: :spree_shipping_rates } t.decimal :amount, precision: 10, scale: 2, null: false t.string :label, null: false + + t.timestamps end end end From 62861aeae3c297601ff5758abd6fa287005d1e37 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 26 Jul 2023 14:09:34 +0200 Subject: [PATCH 320/834] Rename PromotionAdjustmentChooser to DiscountChooser This reflects more accurately what objects this class operates on. --- .../discount_chooser.rb | 19 +++++++++++++++++++ .../promotion_adjustment_chooser.rb | 19 ------------------- .../install/templates/initializer.rb | 2 +- .../configuration.rb | 2 +- .../configuration_spec.rb | 2 +- 5 files changed, 22 insertions(+), 22 deletions(-) create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/discount_chooser.rb delete mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/promotion_adjustment_chooser.rb diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/discount_chooser.rb b/friendly_promotions/app/models/solidus_friendly_promotions/discount_chooser.rb new file mode 100644 index 00000000000..5e478e84df3 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/discount_chooser.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + class DiscountChooser + attr_reader :item + + def initialize(item) + @item = item + end + + def call(discounts) + Array.wrap( + discounts.min_by do |discount| + [discount.amount, -discount.source&.id.to_i] + end + ) + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_adjustment_chooser.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_adjustment_chooser.rb deleted file mode 100644 index 8c0d7a2645a..00000000000 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_adjustment_chooser.rb +++ /dev/null @@ -1,19 +0,0 @@ -# frozen_string_literal: true - -module SolidusFriendlyPromotions - class PromotionAdjustmentChooser - attr_reader :adjustable - - def initialize(adjustable) - @adjustable = adjustable - end - - def call(adjustments) - Array.wrap( - adjustments.min_by do |adjustment| - [adjustment.amount, -adjustment.source&.id.to_i] - end - ) - end - end -end diff --git a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb index b235d5ab74b..ce286030f46 100644 --- a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb +++ b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb @@ -23,7 +23,7 @@ SolidusFriendlyPromotions.configure do |config| # This class chooses which promotion should apply to a line item in case # that more than one promotion is eligible. - config.discount_chooser_class = "SolidusFriendlyPromotions::PromotionAdjustmentChooser" + config.discount_chooser_class = "SolidusFriendlyPromotions::DiscountChooser" # How many promotions should be displayed on the index page in the admin. config.promotions_per_page = 25 diff --git a/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb b/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb index cb996be8f20..e433417ce48 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb @@ -16,7 +16,7 @@ class Configuration < Spree::Preferences::Configuration add_class_set :actions add_class_set :discounters - class_name_attribute :discount_chooser_class, default: "SolidusFriendlyPromotions::PromotionAdjustmentChooser" + class_name_attribute :discount_chooser_class, default: "SolidusFriendlyPromotions::DiscountChooser" class_name_attribute :promotion_code_batch_mailer_class, default: "SolidusFriendlyPromotions::PromotionCodeBatchMailer" diff --git a/friendly_promotions/spec/lib/solidus_friendly_promotions/configuration_spec.rb b/friendly_promotions/spec/lib/solidus_friendly_promotions/configuration_spec.rb index f62ec665ba3..b726905e73b 100644 --- a/friendly_promotions/spec/lib/solidus_friendly_promotions/configuration_spec.rb +++ b/friendly_promotions/spec/lib/solidus_friendly_promotions/configuration_spec.rb @@ -15,7 +15,7 @@ describe ".promotion_chooser_class" do it "is the promotion chooser" do - expect(subject.discount_chooser_class).to eq(SolidusFriendlyPromotions::PromotionAdjustmentChooser) + expect(subject.discount_chooser_class).to eq(SolidusFriendlyPromotions::DiscountChooser) end end From e64f7d00f1360a1494882f0b66a8d12db85e01f8 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 26 Jul 2023 14:12:48 +0200 Subject: [PATCH 321/834] Update README --- friendly_promotions/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/friendly_promotions/README.md b/friendly_promotions/README.md index bdf3fbd8b10..8c1bc391297 100644 --- a/friendly_promotions/README.md +++ b/friendly_promotions/README.md @@ -8,9 +8,9 @@ The basic architecture is very similar to the one in core Solidus, but with a fe ## Architecture -This extension centralizes promotion handling in the order updater. A service class, the `SolidusFriendlyShipping::OrderPromotionAdjuster` applies the current promotion configuration to the order, adjusting or removing adjustments as necessary. +This extension centralizes promotion handling in the order updater. A service class, the `SolidusFriendlyPromotions::OrderDiscounter` applies the current promotion configuration to the order, adjusting or removing adjustments as necessary. -In core, Promotion adjustments get recalculated twice on every change to the cart; once in `Spree::OrderContents#after_add_or_remove` and in `Spree::OrderUpdater#update_promotions`. To make things more complicated, `Spree::OrderContents` leverages the `Spree::PromotionHandler#cart`, while the order updater goes through `Spree::Adjustment#recalculate`. +In Solidus Core, Promotion adjustments get recalculated twice on every change to the cart; once in `Spree::OrderContents#after_add_or_remove` and in `Spree::OrderUpdater#update_promotions`. To make things more complicated, `Spree::OrderContents` leverages the `Spree::PromotionHandler#cart`, while the order updater goes through `Spree::Adjustment#recalculate`. The design decision here is to make the code path easier to follow, and consequently to make it more performant ("Make it easy, then make it fast"). @@ -18,7 +18,7 @@ The design decision here is to make the code path easier to follow, and conseque ### Promotion Rules -Promotion rules can be applicable to either `Spree::Order`, `Spree::LineItem`, or `Spree::Shipment` objects. If they are applicable, they will be asked for eligibility. Rules applicable to orders are processed first. If a promotion has a rule that makes it inapplicable for an order, line items and shipments will not be adjusted. +Promotion rules can be applicable to either `Spree::Order`, `Spree::LineItem`, or `Spree::Shipment` objects. If they are applicable, they will be asked for eligibility. Rules applicable to orders are processed first. If a promotion has a rule that makes it ineligible for an order, line items and shipments will not be adjusted. If there are no rules that are applicable, the promotion will be considered eligible. ### Promotion Actions From 41b344f914c6f9ece889cd63e35caaa50d6bbe7e Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 1 Aug 2023 09:58:40 +0200 Subject: [PATCH 322/834] Update Readme --- friendly_promotions/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/friendly_promotions/README.md b/friendly_promotions/README.md index 8c1bc391297..5cbe02d1ee2 100644 --- a/friendly_promotions/README.md +++ b/friendly_promotions/README.md @@ -56,7 +56,7 @@ bin/rails generate solidus_friendly_promotions:install This will create the tables for this extension. It will also replace the promotion administration system under `/admin/promotions` with a new one that needs `turbo-rails`. -It will also create an initializer within which Solidus is configured to use this extension's `SimpleOrderContents` and `OrderPromotionAdjuster` classes. Feel free to override with your own implementations! +It will also create an initializer within which Solidus is configured to use this extension's `SimpleOrderContents` and `OrderDiscounter` classes. Feel free to override with your own implementations! ## Usage From 361f2551fee1dbcedf1a45214e54cc36405ec411 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 3 Aug 2023 16:09:07 +0200 Subject: [PATCH 323/834] Parameterize active? and friends These methods determine whether a promotion is currently active, but they can just as well be used to determine whether a promotion was active at a given time. --- .../solidus_friendly_promotions/promotion.rb | 28 +++++++++---------- .../promotion_spec.rb | 24 +++++++++++++++- 2 files changed, 37 insertions(+), 15 deletions(-) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb index 50cb94d9fcf..e0995df1c29 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb @@ -78,32 +78,32 @@ def usage_limit_exceeded?(excluded_orders: []) usage_count(excluded_orders: excluded_orders) >= usage_limit end - def not_expired? - !expired? + def not_expired?(time = Time.current) + !expired?(time) end - def not_started? - !started? + def not_started?(time = Time.current) + !started?(time) end - def started? - starts_at.nil? || starts_at < Time.current + def started?(time = Time.current) + starts_at.nil? || starts_at < time end - def active? - started? && not_expired? && actions.present? + def active?(time = Time.current) + started?(time) && not_expired?(time) && actions.present? end - def inactive? - !active? + def inactive?(time = Time.current) + !active?(time) end - def not_expired? - !expired? + def not_expired?(time = Time.current) + !expired?(time) end - def expired? - expires_at.present? && expires_at < Time.current + def expired?(time = Time.current) + expires_at.present? && expires_at < time end def products diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb index 450ce485c45..3376d1dab5a 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb @@ -409,7 +409,7 @@ end context 'when promotion has an action' do - let(:promotion) { create(:promotion, :with_action, name: "name1") } + let(:promotion) { create(:friendly_promotion, :with_adjustable_action, name: "name1") } it "is active if it has started already" do promotion.starts_at = Time.current - 1.day @@ -432,6 +432,28 @@ promotion.expires_at = nil expect(promotion.active?).to eq(true) end + + context "when called with a time" do + subject { promotion.active?(1.day.ago) } + + context "if promo was active a day ago" do + before do + promotion.starts_at = 2.days.ago + promotion.expires_at = 1.hour.ago + end + + it { is_expected.to be true } + end + + context "if promo was not active a day ago" do + before do + promotion.starts_at = 1.hour.ago + promotion.expires_at = 1.day.from_now + end + + it { is_expected.to be false } + end + end end end From 4f7d48a54012a0831b0dc1ce0db670d18b954253 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 3 Aug 2023 16:51:08 +0200 Subject: [PATCH 324/834] Allow `.active` scope to travel in time We can find out what was active in the past! --- .../solidus_friendly_promotions/promotion.rb | 5 ++--- .../promotion_spec.rb | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb index e0995df1c29..83c8656216e 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb @@ -16,12 +16,11 @@ class Promotion < Spree::Base validates :description, length: { maximum: 255 } validate :apply_automatically_disallowed_with_paths - scope :active, -> { has_actions.started_and_unexpired } + scope :active, ->(time = Time.current) { has_actions.started_and_unexpired(time) } scope :advertised, -> { where(advertise: true) } scope :coupons, -> { joins(:codes).distinct } - scope :started_and_unexpired, -> do + scope :started_and_unexpired, ->(time = Time.current) do table = arel_table - time = Time.current where(table[:starts_at].eq(nil).or(table[:starts_at].lt(time))). where(table[:expires_at].eq(nil).or(table[:expires_at].gt(time))) diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb index 3376d1dab5a..fb8ba937086 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb @@ -73,6 +73,24 @@ expect(subject).to match [promotion] end end + + context 'when called with a time that is not current' do + subject { described_class.active(4.days.ago) } + + let(:promotion) do + create( + :friendly_promotion, + :with_adjustable_action, + starts_at: 5.days.ago, + expires_at: 3.days.ago, + name: "name1" + ) + end + + it 'returns promotion that was active then' do + expect(subject).to match [promotion] + end + end end describe '.has_actions' do From ccae95c19b63afbcd93bc75d5e12f0d5b7c46499 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 3 Aug 2023 17:04:14 +0200 Subject: [PATCH 325/834] Deal with completed orders For orders that are complete but not shipped yet, use the promotions active at the time of order completion and recalculate the order. Shipped orders shouldn't really be changed; we return early in this case. --- .../friendly_promotion_discounter.rb | 10 +++++-- .../order_discounter.rb | 2 +- .../friendly_promotion_discounter_spec.rb | 29 +++++++++++++++++++ 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb b/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb index 061a0be395e..6fedc522ab7 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb @@ -11,6 +11,8 @@ def initialize(order) end def call + return nil if order.shipped? + OrderDiscounts.new( order_id: order.id, line_item_discounts: adjust_line_items, @@ -54,11 +56,15 @@ def connected_order_promotions eligible_connected_promotion_ids = order.friendly_order_promotions.select do |order_promotion| order_promotion.promotion_code.nil? || !order_promotion.promotion_code.usage_limit_exceeded?(excluded_orders: [order]) end.map(&:promotion_id) - order.friendly_promotions.active.where(id: eligible_connected_promotion_ids).includes(promotion_includes) + order.friendly_promotions.active(reference_time).where(id: eligible_connected_promotion_ids).includes(promotion_includes) end def sale_promotions - SolidusFriendlyPromotions::Promotion.where(apply_automatically: true).active.includes(promotion_includes) + SolidusFriendlyPromotions::Promotion.where(apply_automatically: true).active(reference_time).includes(promotion_includes) + end + + def reference_time + order.completed_at || Time.current end def promotion_includes diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb b/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb index 8333194982c..cc693364741 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb @@ -7,7 +7,7 @@ def initialize(order) end def call - all_order_discounts = SolidusFriendlyPromotions.config.discounters.map do |discounter| + all_order_discounts = SolidusFriendlyPromotions.config.discounters.filter_map do |discounter| discounter.new(order).call end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/friendly_promotion_discounter_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/friendly_promotion_discounter_spec.rb index 9965d27cb45..c4bcda58226 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/friendly_promotion_discounter_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/friendly_promotion_discounter_spec.rb @@ -52,4 +52,33 @@ end end end + + context "promotions in the past" do + let(:order) { create(:order, completed_at: 7.days.ago) } + let(:currently_active_promotion) { create(:friendly_promotion, :with_adjustable_action, starts_at: 1.hour.ago) } + let(:past_promotion) { create(:friendly_promotion, :with_adjustable_action, starts_at: 1.year.ago, expires_at: 11.months.ago) } + let(:order_promotion) { create(:friendly_promotion, :with_adjustable_action, starts_at: 8.days.ago, expires_at: 6.days.ago) } + + before do + order.friendly_promotions << past_promotion + order.friendly_promotions << order_promotion + order.friendly_promotions << currently_active_promotion + end + + subject { described_class.new(order) } + + it "only evaluates the past promotion that was active when the order was completed" do + expect(subject.promotions).to eq([order_promotion]) + end + end + + context "shipped orders" do + let(:order) { create(:order, shipment_state: "shipped") } + + subject { described_class.new(order).call } + + it "returns false" do + expect(subject).to be false + end + end end From f253c407bd25f26d54f7937121fceb779d7e9487 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Mon, 11 Sep 2023 09:49:37 +0200 Subject: [PATCH 326/834] Fix red test suite --- .../friendly_promotion_discounter_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/friendly_promotion_discounter_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/friendly_promotion_discounter_spec.rb index c4bcda58226..3574adadf0e 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/friendly_promotion_discounter_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/friendly_promotion_discounter_spec.rb @@ -77,8 +77,8 @@ subject { described_class.new(order).call } - it "returns false" do - expect(subject).to be false + it "returns nil" do + expect(subject).to be nil end end end From 6d67420c0527d2f97262c703a370721ce3d5e221 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 26 Sep 2023 18:24:12 +0200 Subject: [PATCH 327/834] Initializer: Use new MenuItem API from Solidus 4.2 onwards The API for the Menu Item has recently changed in Solidus' `main` branch. This commit makes the initializer work for both APIs. --- .../install/templates/initializer.rb | 43 ++++++++++++++----- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb index ce286030f46..67f7f40de0d 100644 --- a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb +++ b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb @@ -6,17 +6,40 @@ # Replace the promotions menu from core with ours Spree::Backend::Config.configure do |config| - config.menu_items = Spree::Backend::Config.menu_items.map do |item| - next item unless item.url == :admin_promotions_path + config.menu_items = config.menu_items.map do |item| + next item unless item.label.to_sym == :promotions - Spree::BackendConfiguration::MenuItem.new( - [:promotions, :promotion_categories], - 'bullhorn', - partial: 'spree/admin/shared/promotion_sub_menu', - condition: -> { can?(:admin, Spree::Promotion) }, - url: -> { SolidusFriendlyPromotions::Engine.routes.url_helpers.admin_promotions_path }, - position: 2 - ) + # The API of the MenuItem class changes in Solidus 4.2.0 + if item.respond_to?(:children) + Spree::BackendConfiguration::MenuItem.new( + label: :promotions, + icon: 'bullhorn', + condition: -> { can?(:admin, SolidusFriendlyPromotions::Promotion) }, + url: -> { SolidusFriendlyPromotions::Engine.routes.url_helpers.admin_promotions_path }, + data_hook: :admin_promotion_sub_tabs, + children: [ + Spree::BackendConfiguration::MenuItem.new( + label: :promotions, + url: -> { SolidusFriendlyPromotions::Engine.routes.url_helpers.admin_promotions_path }, + condition: -> { can?(:admin, SolidusFriendlyPromotions::Promotion) }, + ), + Spree::BackendConfiguration::MenuItem.new( + label: :promotion_categories, + url: -> { SolidusFriendlyPromotions::Engine.routes.url_helpers.admin_promotion_categories_path }, + condition: -> { can?(:admin, SolidusFriendlyPromotions::PromotionCategory) }, + ), + ], + ) + else + Spree::BackendConfiguration::MenuItem.new( + [:promotions, :promotion_categories], + 'bullhorn', + partial: 'spree/admin/shared/promotion_sub_menu', + condition: -> { can?(:admin, Spree::Promotion) }, + url: -> { SolidusFriendlyPromotions::Engine.routes.url_helpers.admin_promotions_path }, + position: 2 + ) + end end end From 5c66b8c1cc08ebd88fcd9b0ddb18dab394a12626 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 26 Sep 2023 23:07:17 +0200 Subject: [PATCH 328/834] Promotion Actions: Implement "edit" action Previously, when changing the promotion calculator type for an action, we would end up with an unrescued 500 error. This implements editing an action. --- .../admin/promotion_actions_controller.rb | 12 +++++-- .../admin/promotion_actions/edit.html.erb | 36 ++++++++----------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb index 69b307f8d6e..19dcabdd18b 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb @@ -4,8 +4,8 @@ module SolidusFriendlyPromotions module Admin class PromotionActionsController < Spree::Admin::BaseController before_action :validate_level, only: :new - before_action :load_promotion, only: [:create, :destroy, :new, :update] - before_action :validate_promotion_action_type, only: :create + before_action :load_promotion, only: [:create, :destroy, :new, :update, :edit] + before_action :validate_promotion_action_type, only: [:create, :edit] def new if params.dig(:promotion_action, :type) @@ -31,6 +31,14 @@ def create end end + def edit + @promotion_action = @promotion.actions.find(params[:id]) + if params.dig(:promotion_action, :calculator_type) + @promotion_action.calculator_type = params[:promotion_action][:calculator_type] + end + render layout: false + end + def update @promotion_action = @promotion.actions.find(params[:id]) @promotion_action.assign_attributes(promotion_action_params) diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/edit.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/edit.html.erb index c2f4cdef315..8103697d430 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/edit.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/edit.html.erb @@ -2,30 +2,22 @@
    <%= @promotion_action.class.human_attribute_name(:description) %>
    <%= link_to_with_icon 'trash', '', solidus_friendly_promotions.admin_promotion_promotion_action_path(@promotion, @promotion_action), method: :delete, class: 'delete' %> -
    -
    - <%= render 'calculator_select', path: solidus_friendly_promotions.edit_admin_promotion_promotion_action_path(@promotion, @promotion_action), promotion_action: @promotion_action %> + <%= render 'calculator_select', path: solidus_friendly_promotions.edit_admin_promotion_promotion_action_path(@promotion, @promotion_action), promotion_action: @promotion_action %> - <%= - form_with( - model: @promotion_action, - scope: :promotion_action, - url: solidus_friendly_promotions.admin_promotion_promotion_action_path(@promotion, @promotion_action), - data: { turbo: false } - ) do |form| %> - <%= render "form", form: form %> + <%= + form_with( + model: @promotion_action, + scope: :promotion_action, + url: solidus_friendly_promotions.admin_promotion_promotion_action_path(@promotion, @promotion_action), + ) do |form| %> + <%= render "form", form: form %> -
    -
    - <%= button_tag "Update", class: "btn btn-secondary float-right" %> -
    -
    - - <% end %> -
    -
    - BUTTON +
    +
    + <%= button_tag "Update", class: "btn btn-secondary float-right" %> +
    -
    + + <% end %>
    <% end %> From cf32993bdf5356849c8bd4dc9154c9070469879e Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 27 Sep 2023 17:28:12 +0200 Subject: [PATCH 329/834] Ignore sandbox and dummy app These apps are not our code, so we don't lint them. --- friendly_promotions/.standard.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 friendly_promotions/.standard.yml diff --git a/friendly_promotions/.standard.yml b/friendly_promotions/.standard.yml new file mode 100644 index 00000000000..8a60962ff40 --- /dev/null +++ b/friendly_promotions/.standard.yml @@ -0,0 +1,4 @@ +ruby_version: 3.0 +ignore: + - 'sandbox/**/*' + - 'spec/dummy/**/*' From 6a1673a4a759a97613558d625a64f247d6e0f6bd Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 27 Sep 2023 17:10:24 +0200 Subject: [PATCH 330/834] Apply standardrb --fix --- .../admin/promotion_actions_controller.rb | 10 +-- .../promotion_code_batches_controller.rb | 2 +- .../admin/promotion_codes_controller.rb | 10 +-- .../admin/promotion_rules_controller.rb | 12 +-- .../admin/promotions_controller.rb | 18 ++-- .../order_decorator.rb | 2 +- .../promotion_code_batch_job.rb | 10 +-- .../app/models/solidus_friendly_promotions.rb | 2 +- .../calculators/distributed_amount.rb | 2 +- .../calculators/flat_rate.rb | 4 +- .../calculators/flexi_rate.rb | 10 +-- .../calculators/percent.rb | 2 +- .../calculators/tiered_flat_rate.rb | 6 +- .../calculators/tiered_percent.rb | 8 +- .../friendly_promotion_discounter.rb | 2 +- .../order_promotion.rb | 6 +- .../solidus_friendly_promotions/promotion.rb | 42 +++++----- .../promotion_action.rb | 6 +- .../promotion_code.rb | 20 ++--- .../promotion_code/batch_builder.rb | 4 +- .../promotion_code_batch.rb | 2 +- .../promotion_rule.rb | 4 +- .../rules/first_repeat_purchase_since.rb | 2 +- .../rules/line_item_taxon.rb | 2 +- .../rules/nth_order.rb | 2 +- .../rules/product.rb | 6 +- .../rules/shipping_method.rb | 2 +- .../rules/taxon.rb | 14 ++-- .../solidus_friendly_promotions/rules/user.rb | 2 +- .../admin/promotion_codes/index.csv.ruby | 2 +- friendly_promotions/config/routes.rb | 2 +- ...30703141116_create_promotion_categories.rb | 2 +- .../20230703143943_create_promotion_rules.rb | 2 +- .../migrate/20230704083830_add_rule_tables.rb | 14 ++-- ...20230704093625_create_promotion_actions.rb | 2 +- .../20230704102444_create_promotion_codes.rb | 2 +- ...704102656_create_promotion_code_batches.rb | 2 +- ...171556_create_friendly_order_promotions.rb | 6 +- ...25074235_create_shipping_rate_discounts.rb | 4 +- .../install/templates/initializer.rb | 26 +++--- .../configuration.rb | 2 +- .../friendly_order_promotion_factory.rb | 2 +- .../friendly_promotion_category_factory.rb | 4 +- .../friendly_promotion_code_factory.rb | 2 +- .../friendly_promotion_factory.rb | 8 +- ...friendly_shipping_rate_discount_factory.rb | 2 +- .../promotion_code_batch_job_spec.rb | 12 +-- .../testing_support/factories_spec.rb | 2 +- .../promotion_code_batch_mailer_spec.rb | 2 +- .../actions/adjust_line_item_spec.rb | 2 +- .../actions/adjust_shipment_spec.rb | 2 +- .../calculators/distributed_amount_spec.rb | 18 ++-- .../calculators/flat_rate_spec.rb | 6 +- .../calculators/flexi_rate_spec.rb | 8 +- .../calculators/percent_spec.rb | 6 +- .../calculators/tiered_flat_rate_spec.rb | 28 +++---- .../calculators/tiered_percent_spec.rb | 30 +++---- .../distributed_amounts_handler_spec.rb | 6 +- .../friendly_promotion_discounter_spec.rb | 22 ++--- .../item_discount_spec.rb | 2 +- .../order_discounter_spec.rb | 4 +- .../order_promotion_spec.rb | 2 +- .../products_promotion_rule_spec.rb | 2 +- .../promotion_category_spec.rb | 10 +-- .../promotion_code_batch_spec.rb | 6 +- .../promotion_code_spec.rb | 56 ++++++------- .../promotion_rule_spec.rb | 2 +- .../promotion_rules_store_spec.rb | 2 +- .../promotion_rules_taxon_spec.rb | 2 +- .../promotion_rules_user_spec.rb | 2 +- .../promotion_spec.rb | 68 +++++++-------- .../rules/item_total_spec.rb | 2 +- .../rules/option_value_spec.rb | 10 +-- .../simple_order_contents_spec.rb | 84 +++++++++---------- .../spec/models/spree/shipping_rate_spec.rb | 6 +- .../admin/promotion_actions_request_spec.rb | 6 +- .../admin/promotion_rules_request_spec.rb | 10 +-- .../admin/promotions_request_spec.rb | 2 +- .../calculator_shared_examples.rb | 2 +- .../admin/promotion_categories_spec.rb | 12 +-- .../admin/promotions_spec.rb | 12 +-- 81 files changed, 373 insertions(+), 373 deletions(-) diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb index 19dcabdd18b..6d379ee3eba 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb @@ -24,7 +24,7 @@ def create @promotion_action.promotion = @promotion if @promotion_action.save(validate: false) flash[:success] = - t('spree.successfully_created', resource: SolidusFriendlyPromotions::PromotionAction.model_name.human) + t("spree.successfully_created", resource: SolidusFriendlyPromotions::PromotionAction.model_name.human) redirect_to location_after_save, format: :html else render :new, layout: false @@ -44,7 +44,7 @@ def update @promotion_action.assign_attributes(promotion_action_params) if @promotion_action.save flash[:success] = - t('spree.successfully_updated', resource: SolidusFriendlyPromotions::PromotionAction.model_name.human) + t("spree.successfully_updated", resource: SolidusFriendlyPromotions::PromotionAction.model_name.human) redirect_to location_after_save, format: :html else render :edit @@ -55,7 +55,7 @@ def destroy @promotion_action = @promotion.actions.find(params[:id]) if @promotion_action.discard flash[:success] = - t('spree.successfully_removed', resource: SolidusFriendlyPromotions::PromotionAction.model_name.human) + t("spree.successfully_removed", resource: SolidusFriendlyPromotions::PromotionAction.model_name.human) end redirect_to location_after_save, format: :html end @@ -92,10 +92,10 @@ def validate_promotion_action_type end return if @promotion_action_type - flash[:error] = t('solidus_friendly_promotions.invalid_promotion_action') + flash[:error] = t("solidus_friendly_promotions.invalid_promotion_action") respond_to do |format| format.html { redirect_to solidus_friendly_promotions.edit_admin_promotion_path(@promotion) } - format.js { render layout: false } + format.js { render layout: false } end end end diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_code_batches_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_code_batches_controller.rb index 44cea4fde55..24d5be4ddc8 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_code_batches_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_code_batches_controller.rb @@ -3,7 +3,7 @@ module SolidusFriendlyPromotions module Admin class PromotionCodeBatchesController < Spree::Admin::ResourceController - belongs_to 'solidus_friendly_promotions/promotion' + belongs_to "solidus_friendly_promotions/promotion" create.after :build_promotion_code_batch diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_codes_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_codes_controller.rb index 38ee1184eeb..291e3d3b315 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_codes_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_codes_controller.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'csv' +require "csv" module SolidusFriendlyPromotions module Admin @@ -26,7 +26,7 @@ def new if @promotion.apply_automatically flash[:error] = t( :disallowed_with_apply_automatically, - scope: 'activerecord.errors.models.solidus_friendly_promotions/promotion_code.attributes.base' + scope: "activerecord.errors.models.solidus_friendly_promotions/promotion_code.attributes.base" ) redirect_to solidus_friendly_promotions.admin_promotion_promotion_codes_url(@promotion) else @@ -49,9 +49,9 @@ def create private def load_promotion - @promotion = SolidusFriendlyPromotions::Promotion. - accessible_by(current_ability, :show). - find(params[:promotion_id]) + @promotion = SolidusFriendlyPromotions::Promotion + .accessible_by(current_ability, :show) + .find(params[:promotion_id]) end end end diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb index fb4e960e38a..41d234a1d44 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb @@ -3,7 +3,7 @@ module SolidusFriendlyPromotions module Admin class PromotionRulesController < Spree::Admin::BaseController - helper 'spree/promotion_rules' + helper "spree/promotion_rules" before_action :validate_level, only: [:new, :create] before_action :load_promotion, only: [:create, :destroy, :update, :new] @@ -23,7 +23,7 @@ def create ) if @promotion_rule.save flash[:success] = - t('spree.successfully_created', resource: SolidusFriendlyPromotions::PromotionRule.model_name.human) + t("spree.successfully_created", resource: SolidusFriendlyPromotions::PromotionRule.model_name.human) end redirect_to location_after_save end @@ -33,7 +33,7 @@ def update @promotion_rule.assign_attributes(promotion_rule_params) if @promotion_rule.save flash[:success] = - t('spree.successfully_updated', resource: SolidusFriendlyPromotions::PromotionRule.model_name.human) + t("spree.successfully_updated", resource: SolidusFriendlyPromotions::PromotionRule.model_name.human) end redirect_to location_after_save end @@ -42,7 +42,7 @@ def destroy @promotion_rule = @promotion.rules.find(params[:id]) if @promotion_rule.destroy flash[:success] = - t('spree.successfully_removed', resource: SolidusFriendlyPromotions::PromotionRule.model_name.human) + t("spree.successfully_removed", resource: SolidusFriendlyPromotions::PromotionRule.model_name.human) end redirect_to location_after_save end @@ -69,10 +69,10 @@ def validate_promotion_rule_type end return if @promotion_rule_type - flash[:error] = t('solidus_friendly_promotions.invalid_promotion_rule') + flash[:error] = t("solidus_friendly_promotions.invalid_promotion_rule") respond_to do |format| format.html { redirect_to solidus_friendly_promotions.edit_admin_promotion_path(@promotion) } - format.js { render layout: false } + format.js { render layout: false } end end diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb index 46ecf67558c..ef5bb8bbdbf 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb @@ -5,8 +5,8 @@ module Admin class PromotionsController < ::Spree::Admin::ResourceController before_action :load_data - helper 'solidus_friendly_promotions/admin/promotion_rules' - helper 'solidus_friendly_promotions/admin/promotion_actions' + helper "solidus_friendly_promotions/admin/promotion_rules" + helper "solidus_friendly_promotions/admin/promotion_actions" def create @promotion = model_class.new(permitted_resource_params) @@ -18,11 +18,11 @@ def create if @promotion.save @code_batch&.process - flash[:success] = t('solidus_friendly_promotions.promotion_successfully_created') + flash[:success] = t("solidus_friendly_promotions.promotion_successfully_created") redirect_to location_after_save else flash[:error] = @promotion.errors.full_messages.to_sentence - render action: 'new' + render action: "new" end end @@ -32,14 +32,14 @@ def collection return @collection if @collection params[:q] ||= HashWithIndifferentAccess.new - params[:q][:s] ||= 'id desc' + params[:q][:s] ||= "id desc" @collection = super @search = @collection.ransack(params[:q]) - @collection = @search.result(distinct: true). - includes(promotion_includes). - page(params[:page]). - per(params[:per_page] || SolidusFriendlyPromotions.config.promotions_per_page) + @collection = @search.result(distinct: true) + .includes(promotion_includes) + .page(params[:page]) + .per(params[:per_page] || SolidusFriendlyPromotions.config.promotions_per_page) @collection end diff --git a/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_decorator.rb b/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_decorator.rb index 991f7d7c4ad..23e13c39abc 100644 --- a/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_decorator.rb +++ b/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_decorator.rb @@ -14,7 +14,7 @@ def ensure_promotions_eligible if promo_total_changed? restart_checkout_flow recalculate - errors.add(:base, I18n.t('solidus_friendly_promotions.promotion_total_changed_before_complete')) + errors.add(:base, I18n.t("solidus_friendly_promotions.promotion_total_changed_before_complete")) end super diff --git a/friendly_promotions/app/jobs/solidus_friendly_promotions/promotion_code_batch_job.rb b/friendly_promotions/app/jobs/solidus_friendly_promotions/promotion_code_batch_job.rb index e580d7dfead..9de31e63471 100644 --- a/friendly_promotions/app/jobs/solidus_friendly_promotions/promotion_code_batch_job.rb +++ b/friendly_promotions/app/jobs/solidus_friendly_promotions/promotion_code_batch_job.rb @@ -11,14 +11,14 @@ def perform(promotion_code_batch) if promotion_code_batch.email? SolidusFriendlyPromotions.config.promotion_code_batch_mailer_class - .promotion_code_batch_finished(promotion_code_batch) - .deliver_now + .promotion_code_batch_finished(promotion_code_batch) + .deliver_now end - rescue StandardError => e + rescue => e if promotion_code_batch.email? SolidusFriendlyPromotions.config.promotion_code_batch_mailer_class - .promotion_code_batch_errored(promotion_code_batch) - .deliver_now + .promotion_code_batch_errored(promotion_code_batch) + .deliver_now end raise e end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions.rb b/friendly_promotions/app/models/solidus_friendly_promotions.rb index db8181d1339..62b0bdab0d4 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions.rb @@ -2,6 +2,6 @@ module SolidusFriendlyPromotions def self.table_name_prefix - 'friendly_' + "friendly_" end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/distributed_amount.rb b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/distributed_amount.rb index c2d940b3af2..05778fac1b9 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/distributed_amount.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/distributed_amount.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_dependency 'spree/calculator' +require_dependency "spree/calculator" # This is a calculator for line item adjustment actions. It accepts a line item # and calculates its weighted adjustment amount based on the value of the diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/flat_rate.rb b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/flat_rate.rb index 0b6bab74482..9b318340860 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/flat_rate.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/flat_rate.rb @@ -1,12 +1,12 @@ # frozen_string_literal: true -require_dependency 'spree/calculator' +require_dependency "spree/calculator" module SolidusFriendlyPromotions module Calculators class FlatRate < Spree::Calculator preference :amount, :decimal, default: 0 - preference :currency, :string, default: ->{ Spree::Config[:currency] } + preference :currency, :string, default: -> { Spree::Config[:currency] } def compute(object = nil) currency = object.order.currency diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/flexi_rate.rb b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/flexi_rate.rb index 29efc3e9605..af3f1e212cb 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/flexi_rate.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/flexi_rate.rb @@ -1,20 +1,20 @@ # frozen_string_literal: true -require_dependency 'spree/calculator' +require_dependency "spree/calculator" module SolidusFriendlyPromotions module Calculators class FlexiRate < Spree::Calculator - preference :first_item, :decimal, default: 0 + preference :first_item, :decimal, default: 0 preference :additional_item, :decimal, default: 0 - preference :max_items, :integer, default: 0 - preference :currency, :string, default: ->{ Spree::Config[:currency] } + preference :max_items, :integer, default: 0 + preference :currency, :string, default: -> { Spree::Config[:currency] } def compute(object) items_count = object.quantity items_count = [items_count, preferred_max_items].min unless preferred_max_items.zero? - return BigDecimal('0') if items_count == 0 + return BigDecimal("0") if items_count == 0 additional_items_count = items_count - 1 preferred_first_item + preferred_additional_item * additional_items_count diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/percent.rb b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/percent.rb index 1ab796788e9..8864904f254 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/percent.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/percent.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_dependency 'spree/calculator' +require_dependency "spree/calculator" module SolidusFriendlyPromotions module Calculators diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_flat_rate.rb b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_flat_rate.rb index b09845a14f2..1fda3e91a05 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_flat_rate.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_flat_rate.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_dependency 'spree/calculator' +require_dependency "spree/calculator" module SolidusFriendlyPromotions module Calculators @@ -39,12 +39,12 @@ def compute_item(object) def cast_to_d(value) value.to_s.to_d rescue ArgumentError - BigDecimal('0') + BigDecimal("0") end def preferred_tiers_content if preferred_tiers.is_a? Hash - unless preferred_tiers.keys.all?{ |key| key.is_a?(Numeric) && key > 0 } + unless preferred_tiers.keys.all? { |key| key.is_a?(Numeric) && key > 0 } errors.add(:base, :keys_should_be_positive_number) end else diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_percent.rb b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_percent.rb index 28b725eee2f..01819ca3919 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_percent.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_percent.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_dependency 'spree/calculator' +require_dependency "spree/calculator" module SolidusFriendlyPromotions module Calculators @@ -46,15 +46,15 @@ def compute_item(object) def cast_to_d(value) value.to_s.to_d rescue ArgumentError - BigDecimal('0') + BigDecimal("0") end def preferred_tiers_content if preferred_tiers.is_a? Hash - unless preferred_tiers.keys.all?{ |key| key.is_a?(Numeric) && key > 0 } + unless preferred_tiers.keys.all? { |key| key.is_a?(Numeric) && key > 0 } errors.add(:base, :keys_should_be_positive_number) end - unless preferred_tiers.values.all?{ |key| key.is_a?(Numeric) && key >= 0 && key <= 100 } + unless preferred_tiers.values.all? { |key| key.is_a?(Numeric) && key >= 0 && key <= 100 } errors.add(:base, :values_should_be_percent) end else diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb b/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb index 6fedc522ab7..1401bd811e2 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb @@ -70,7 +70,7 @@ def reference_time def promotion_includes [ :rules, - :actions, + :actions ] end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/order_promotion.rb b/friendly_promotions/app/models/solidus_friendly_promotions/order_promotion.rb index 623cf9154e2..46533d6d98c 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/order_promotion.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/order_promotion.rb @@ -6,9 +6,9 @@ module SolidusFriendlyPromotions # 1. A promotion that a user attempted to apply to their order # 2. The specific code that they used class OrderPromotion < Spree::Base - belongs_to :order, class_name: 'Spree::Order' - belongs_to :promotion, class_name: 'SolidusFriendlyPromotions::Promotion' - belongs_to :promotion_code, class_name: 'SolidusFriendlyPromotions::PromotionCode', optional: true + belongs_to :order, class_name: "Spree::Order" + belongs_to :promotion, class_name: "SolidusFriendlyPromotions::Promotion" + belongs_to :promotion_code, class_name: "SolidusFriendlyPromotions::PromotionCode", optional: true validates :promotion_code, presence: true, if: :require_promotion_code? diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb index 83c8656216e..02189238141 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb @@ -10,10 +10,10 @@ class Promotion < Spree::Base has_many :code_batches, class_name: "SolidusFriendlyPromotions::PromotionCodeBatch" validates :name, presence: true - validates :path, uniqueness: { allow_blank: true, case_sensitive: true } - validates :usage_limit, numericality: { greater_than: 0, allow_nil: true } - validates :per_code_usage_limit, numericality: { greater_than_or_equal_to: 0, allow_nil: true } - validates :description, length: { maximum: 255 } + validates :path, uniqueness: {allow_blank: true, case_sensitive: true} + validates :usage_limit, numericality: {greater_than: 0, allow_nil: true} + validates :per_code_usage_limit, numericality: {greater_than_or_equal_to: 0, allow_nil: true} + validates :description, length: {maximum: 255} validate :apply_automatically_disallowed_with_paths scope :active, ->(time = Time.current) { has_actions.started_and_unexpired(time) } @@ -22,22 +22,22 @@ class Promotion < Spree::Base scope :started_and_unexpired, ->(time = Time.current) do table = arel_table - where(table[:starts_at].eq(nil).or(table[:starts_at].lt(time))). - where(table[:expires_at].eq(nil).or(table[:expires_at].gt(time))) + where(table[:starts_at].eq(nil).or(table[:starts_at].lt(time))) + .where(table[:expires_at].eq(nil).or(table[:expires_at].gt(time))) end scope :has_actions, -> do joins(:actions).distinct end - self.allowed_ransackable_associations = ['codes'] + self.allowed_ransackable_associations = ["codes"] self.allowed_ransackable_attributes = %w[name path promotion_category_id] self.allowed_ransackable_scopes = %i[active] # All orders that have been discounted using this promotion def discounted_orders - Spree::Order. - joins(:all_adjustments). - where( + Spree::Order + .joins(:all_adjustments) + .where( spree_adjustments: { source_type: "SolidusFriendlyPromotions::PromotionAction", source_id: actions.map(&:id), @@ -51,20 +51,20 @@ def discounted_orders # @param excluded_orders [Array] Orders to exclude from usage count # @return [Integer] usage count def usage_count(excluded_orders: []) - discounted_orders. - complete. - where.not(id: [excluded_orders.map(&:id)]). - where.not(spree_orders: { state: :canceled }). - count + discounted_orders + .complete + .where.not(id: [excluded_orders.map(&:id)]) + .where.not(spree_orders: {state: :canceled}) + .count end def used_by?(user, excluded_orders = []) - discounted_orders. - complete. - where.not(id: excluded_orders.map(&:id)). - where(user: user). - where.not(spree_orders: { state: :canceled }). - exists? + discounted_orders + .complete + .where.not(id: excluded_orders.map(&:id)) + .where(user: user) + .where.not(spree_orders: {state: :canceled}) + .exists? end # Whether the promotion has exceeded its usage restrictions. diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb index 2388485cadc..27eb152b3b2 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spree/preferences/persistable' +require "spree/preferences/persistable" module SolidusFriendlyPromotions # Base class for all types of promotion action. @@ -37,7 +37,7 @@ def discount(adjustable) # Ensure a negative amount which does not exceed the object's amount def compute_amount(adjustable) - promotion_amount = calculator.compute(adjustable) || BigDecimal('0') + promotion_amount = calculator.compute(adjustable) || BigDecimal("0") [adjustable.amount, promotion_amount.abs].min * -1 end @@ -45,7 +45,7 @@ def adjustment_label(adjustable) I18n.t( "solidus_friendly_promotions.adjustment_labels.#{adjustable.class.name.demodulize.underscore}", promotion: SolidusFriendlyPromotions::Promotion.model_name.human, - promotion_name: promotion.name, + promotion_name: promotion.name ) end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code.rb index 0f2a7c82fbd..943bcc478c9 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code.rb @@ -8,10 +8,10 @@ class PromotionCode < Spree::Base before_validation :normalize_code - validates :value, presence: true, uniqueness: { allow_blank: true, case_sensitive: true } + validates :value, presence: true, uniqueness: {allow_blank: true, case_sensitive: true} validate :promotion_not_apply_automatically, on: :create - self.allowed_ransackable_attributes = ['value'] + self.allowed_ransackable_attributes = ["value"] # Whether the promotion code has exceeded its usage restrictions # @@ -28,14 +28,14 @@ def usage_limit_exceeded?(excluded_orders: []) # @param excluded_orders [Array] Orders to exclude from usage count # @return [Integer] usage count def usage_count(excluded_orders: []) - promotion. - discounted_orders. - complete. - where.not(spree_orders: { state: :canceled }). - joins(:friendly_order_promotions). - where(friendly_order_promotions: { promotion_code_id: id }). - where.not(id: excluded_orders.map(&:id)). - count + promotion + .discounted_orders + .complete + .where.not(spree_orders: {state: :canceled}) + .joins(:friendly_order_promotions) + .where(friendly_order_promotions: {promotion_code_id: id}) + .where.not(id: excluded_orders.map(&:id)) + .count end def usage_limit diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code/batch_builder.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code/batch_builder.rb index 58ba96fb692..f40dcd43f44 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code/batch_builder.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code/batch_builder.rb @@ -10,7 +10,7 @@ class BatchBuilder DEFAULT_OPTIONS = { random_code_length: 6, batch_size: 1000, - sample_characters: ('a'..'z').to_a + (2..9).to_a.map(&:to_s) + sample_characters: ("a".."z").to_a + (2..9).to_a.map(&:to_s) }.freeze def initialize(promotion_code_batch, options = {}) @@ -22,7 +22,7 @@ def initialize(promotion_code_batch, options = {}) def build_promotion_codes generate_random_codes promotion_code_batch.update!(state: "completed") - rescue StandardError => e + rescue => e promotion_code_batch.update!( error: e.inspect, state: "failed" diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code_batch.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code_batch.rb index cbb33bfb59b..85d076d1c1b 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code_batch.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code_batch.rb @@ -8,7 +8,7 @@ class CantProcessStartedBatch < StandardError belongs_to :promotion has_many :promotion_codes, dependent: :destroy - validates :number_of_codes, numericality: { greater_than: 0 } + validates :number_of_codes, numericality: {greater_than: 0} validates :base_code, :number_of_codes, presence: true def finished? diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rule.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rule.rb index 79cf24e4d69..9d4e1f74d98 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rule.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rule.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spree/preferences/persistable' +require "spree/preferences/persistable" module SolidusFriendlyPromotions class PromotionRule < Spree::Base @@ -45,7 +45,7 @@ def unique_per_promotion end def eligibility_error_message(key, options = {}) - I18n.t(key, **{ scope: [:spree, :eligibility_errors, :messages] }.merge(options)) + I18n.t(key, **{scope: [:spree, :eligibility_errors, :messages]}.merge(options)) end end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb index 1e23f2b2a98..dfca964597d 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb @@ -4,7 +4,7 @@ module SolidusFriendlyPromotions module Rules class FirstRepeatPurchaseSince < PromotionRule preference :days_ago, :integer, default: 365 - validates :preferred_days_ago, numericality: { only_integer: true, greater_than: 0 } + validates :preferred_days_ago, numericality: {only_integer: true, greater_than: 0} # This promotion is applicable to orders only. def applicable?(promotable) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb index 1308aacde98..e0fd27b693f 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb @@ -9,7 +9,7 @@ class LineItemTaxon < PromotionRule MATCH_POLICIES = %w[include exclude].freeze - validates :preferred_match_policy, inclusion: { in: MATCH_POLICIES } + validates :preferred_match_policy, inclusion: {in: MATCH_POLICIES} preference :match_policy, :string, default: MATCH_POLICIES.first def applicable?(promotable) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb index f542ef13250..04cb592bd6c 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb @@ -6,7 +6,7 @@ class NthOrder < PromotionRule preference :nth_order, :integer, default: 2 # It does not make sense to have this apply to the first order using preferred_nth_order == 1 # Instead we could use the first_order rule - validates :preferred_nth_order, numericality: { only_integer: true, greater_than: 1 } + validates :preferred_nth_order, numericality: {only_integer: true, greater_than: 1} # This promotion is applicable to orders only. def applicable?(promotable) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb index c5bbd4503be..c7a5d83732e 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb @@ -10,8 +10,8 @@ class Product < PromotionRule has_many :products_promotion_rules, dependent: :destroy, foreign_key: :promotion_rule_id, - class_name: 'SolidusFriendlyPromotions::ProductsPromotionRule' - has_many :products, class_name: 'Spree::Product', through: :products_promotion_rules + class_name: "SolidusFriendlyPromotions::ProductsPromotionRule" + has_many :products, class_name: "Spree::Product", through: :products_promotion_rules def preload_relations [:products] @@ -19,7 +19,7 @@ def preload_relations MATCH_POLICIES = %w[any all none].freeze - validates :preferred_match_policy, inclusion: { in: MATCH_POLICIES } + validates :preferred_match_policy, inclusion: {in: MATCH_POLICIES} preference :match_policy, :string, default: MATCH_POLICIES.first diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/shipping_method.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/shipping_method.rb index 46b118f9002..eb745f7605e 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/shipping_method.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/shipping_method.rb @@ -10,7 +10,7 @@ def applicable?(promotable) end def eligible?(promotable) - promotable.shipping_method&.id.in?(preferred_shipping_method_ids.map(&:to_i)) + promotable.shipping_method&.id&.in?(preferred_shipping_method_ids.map(&:to_i)) end def updateable? diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb index f972e4c1160..9130cadcfe2 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb @@ -3,9 +3,9 @@ module SolidusFriendlyPromotions module Rules class Taxon < PromotionRule - has_many :promotion_rules_taxons, class_name: 'SolidusFriendlyPromotions::PromotionRulesTaxon', foreign_key: :promotion_rule_id, + has_many :promotion_rules_taxons, class_name: "SolidusFriendlyPromotions::PromotionRulesTaxon", foreign_key: :promotion_rule_id, dependent: :destroy - has_many :taxons, through: :promotion_rules_taxons, class_name: 'Spree::Taxon' + has_many :taxons, through: :promotion_rules_taxons, class_name: "Spree::Taxon" def preload_relations [:taxons] @@ -13,7 +13,7 @@ def preload_relations MATCH_POLICIES = %w[any all none].freeze - validates :preferred_match_policy, inclusion: { in: MATCH_POLICIES } + validates :preferred_match_policy, inclusion: {in: MATCH_POLICIES} preference :match_policy, :string, default: MATCH_POLICIES.first def applicable?(promotable) @@ -71,10 +71,10 @@ def updateable? # All taxons in an order def taxons_in_order(order) - Spree::Taxon. - joins(products: { variants_including_master: :line_items }). - where(spree_line_items: { order_id: order.id }). - distinct + Spree::Taxon + .joins(products: {variants_including_master: :line_items}) + .where(spree_line_items: {order_id: order.id}) + .distinct end # ids of taxons rules and taxons rules children diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb index 98b2d94c0f0..1cb7e9fd9da 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb @@ -4,7 +4,7 @@ module SolidusFriendlyPromotions module Rules class User < PromotionRule has_many :promotion_rules_users, - class_name: 'SolidusFriendlyPromotions::PromotionRulesUser', + class_name: "SolidusFriendlyPromotions::PromotionRulesUser", foreign_key: :promotion_rule_id, dependent: :destroy has_many :users, through: :promotion_rules_users, class_name: Spree::UserClassHandle.new diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_codes/index.csv.ruby b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_codes/index.csv.ruby index 9299279b0e0..288b953b196 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_codes/index.csv.ruby +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_codes/index.csv.ruby @@ -1,7 +1,7 @@ # frozen_string_literal: true CSV.generate do |csv| - csv << ['Code'] + csv << ["Code"] @promotion_codes.order(:id).pluck(:value).each do |value| csv << [value] end diff --git a/friendly_promotions/config/routes.rb b/friendly_promotions/config/routes.rb index 1dd211b9c62..911a8da73f4 100644 --- a/friendly_promotions/config/routes.rb +++ b/friendly_promotions/config/routes.rb @@ -9,7 +9,7 @@ resources :promotion_actions resources :promotion_codes, only: [:index, :new, :create] resources :promotion_code_batches, only: [:index, :new, :create] do - get '/download', to: "promotion_code_batches#download", defaults: { format: "csv" } + get "/download", to: "promotion_code_batches#download", defaults: {format: "csv"} end end end diff --git a/friendly_promotions/db/migrate/20230703141116_create_promotion_categories.rb b/friendly_promotions/db/migrate/20230703141116_create_promotion_categories.rb index 97b1499d7ab..f8e2518db84 100644 --- a/friendly_promotions/db/migrate/20230703141116_create_promotion_categories.rb +++ b/friendly_promotions/db/migrate/20230703141116_create_promotion_categories.rb @@ -9,6 +9,6 @@ def change add_reference :friendly_promotions, :promotion_category, - foreign_key: { to_table: :friendly_promotion_categories } + foreign_key: {to_table: :friendly_promotion_categories} end end diff --git a/friendly_promotions/db/migrate/20230703143943_create_promotion_rules.rb b/friendly_promotions/db/migrate/20230703143943_create_promotion_rules.rb index 980ac111d56..3a382c42e13 100644 --- a/friendly_promotions/db/migrate/20230703143943_create_promotion_rules.rb +++ b/friendly_promotions/db/migrate/20230703143943_create_promotion_rules.rb @@ -2,7 +2,7 @@ class CreatePromotionRules < ActiveRecord::Migration[7.0] def change create_table :friendly_promotion_rules do |t| t.references :promotion, - foreign_key: { to_table: :friendly_promotions } + foreign_key: {to_table: :friendly_promotions} t.string :type t.text :preferences diff --git a/friendly_promotions/db/migrate/20230704083830_add_rule_tables.rb b/friendly_promotions/db/migrate/20230704083830_add_rule_tables.rb index e412ea601c0..0c95fba165a 100644 --- a/friendly_promotions/db/migrate/20230704083830_add_rule_tables.rb +++ b/friendly_promotions/db/migrate/20230704083830_add_rule_tables.rb @@ -1,28 +1,28 @@ class AddRuleTables < ActiveRecord::Migration[7.0] def change create_table :friendly_products_promotion_rules, force: :cascade do |t| - t.references :product, type: :integer, index: true, null: false, foreign_key: { to_table: :spree_products } - t.references :promotion_rule, index: true, null: false, foreign_key: { to_table: :friendly_promotion_rules } + t.references :product, type: :integer, index: true, null: false, foreign_key: {to_table: :spree_products} + t.references :promotion_rule, index: true, null: false, foreign_key: {to_table: :friendly_promotion_rules} t.timestamps end create_table :friendly_promotion_rules_taxons, force: :cascade do |t| - t.references :taxon, type: :integer, index: true, null: false, foreign_key: { to_table: :spree_taxons } - t.references :promotion_rule, index: true, null: false, foreign_key: { to_table: :friendly_promotion_rules } + t.references :taxon, type: :integer, index: true, null: false, foreign_key: {to_table: :spree_taxons} + t.references :promotion_rule, index: true, null: false, foreign_key: {to_table: :friendly_promotion_rules} t.timestamps end create_table :friendly_promotion_rules_users, force: :cascade do |t| - t.references :user, type: :integer, index: true, null: false, foreign_key: { to_table: Spree.user_class.table_name } - t.references :promotion_rule, index: true, null: false, foreign_key: { to_table: :friendly_promotion_rules } + t.references :user, type: :integer, index: true, null: false, foreign_key: {to_table: Spree.user_class.table_name} + t.references :promotion_rule, index: true, null: false, foreign_key: {to_table: :friendly_promotion_rules} t.timestamps end create_table :friendly_promotion_rules_stores do |t| - t.references :store, type: :integer, index: true, null: false, foreign_key: { to_table: :spree_stores } + t.references :store, type: :integer, index: true, null: false, foreign_key: {to_table: :spree_stores} t.references :promotion_rule, index: true, null: false t.timestamps diff --git a/friendly_promotions/db/migrate/20230704093625_create_promotion_actions.rb b/friendly_promotions/db/migrate/20230704093625_create_promotion_actions.rb index 731ee6abd44..5009c266dc0 100644 --- a/friendly_promotions/db/migrate/20230704093625_create_promotion_actions.rb +++ b/friendly_promotions/db/migrate/20230704093625_create_promotion_actions.rb @@ -1,7 +1,7 @@ class CreatePromotionActions < ActiveRecord::Migration[7.0] def change create_table :friendly_promotion_actions do |t| - t.references :promotion, index: true, null: false, foreign_key: { to_table: :friendly_promotions } + t.references :promotion, index: true, null: false, foreign_key: {to_table: :friendly_promotions} t.string :type t.datetime :deleted_at, precision: nil t.text :preferences diff --git a/friendly_promotions/db/migrate/20230704102444_create_promotion_codes.rb b/friendly_promotions/db/migrate/20230704102444_create_promotion_codes.rb index 9168098cb96..5154c629456 100644 --- a/friendly_promotions/db/migrate/20230704102444_create_promotion_codes.rb +++ b/friendly_promotions/db/migrate/20230704102444_create_promotion_codes.rb @@ -1,7 +1,7 @@ class CreatePromotionCodes < ActiveRecord::Migration[7.0] def change create_table :friendly_promotion_codes, force: :cascade do |t| - t.references :promotion, null: false, index: true, foreign_key: { to_table: :friendly_promotions } + t.references :promotion, null: false, index: true, foreign_key: {to_table: :friendly_promotions} t.string :value, null: false t.timestamps diff --git a/friendly_promotions/db/migrate/20230704102656_create_promotion_code_batches.rb b/friendly_promotions/db/migrate/20230704102656_create_promotion_code_batches.rb index 0e7c2a9940d..4dbcde3e298 100644 --- a/friendly_promotions/db/migrate/20230704102656_create_promotion_code_batches.rb +++ b/friendly_promotions/db/migrate/20230704102656_create_promotion_code_batches.rb @@ -3,7 +3,7 @@ class CreatePromotionCodeBatches < ActiveRecord::Migration[7.0] def change create_table :friendly_promotion_code_batches do |t| - t.references :promotion, null: false, index: true, foreign_key: { to_table: :friendly_promotions } + t.references :promotion, null: false, index: true, foreign_key: {to_table: :friendly_promotions} t.string :base_code, null: false t.integer :number_of_codes, null: false t.string :join_characters, null: false, default: "_" diff --git a/friendly_promotions/db/migrate/20230705171556_create_friendly_order_promotions.rb b/friendly_promotions/db/migrate/20230705171556_create_friendly_order_promotions.rb index e1cdee49d82..3b29f44bbcd 100644 --- a/friendly_promotions/db/migrate/20230705171556_create_friendly_order_promotions.rb +++ b/friendly_promotions/db/migrate/20230705171556_create_friendly_order_promotions.rb @@ -1,9 +1,9 @@ class CreateFriendlyOrderPromotions < ActiveRecord::Migration[7.0] def change create_table :friendly_order_promotions do |t| - t.references :order, type: :integer, index: true, null: false, foreign_key: { to_table: :spree_orders } - t.references :promotion, index: true, null: false, foreign_key: { to_table: :friendly_promotions } - t.references :promotion_code, index: true, null: true, foreign_key: { to_table: :friendly_promotion_codes } + t.references :order, type: :integer, index: true, null: false, foreign_key: {to_table: :spree_orders} + t.references :promotion, index: true, null: false, foreign_key: {to_table: :friendly_promotions} + t.references :promotion_code, index: true, null: true, foreign_key: {to_table: :friendly_promotion_codes} t.timestamps end diff --git a/friendly_promotions/db/migrate/20230725074235_create_shipping_rate_discounts.rb b/friendly_promotions/db/migrate/20230725074235_create_shipping_rate_discounts.rb index a2072b1839f..0abf8d24937 100644 --- a/friendly_promotions/db/migrate/20230725074235_create_shipping_rate_discounts.rb +++ b/friendly_promotions/db/migrate/20230725074235_create_shipping_rate_discounts.rb @@ -1,8 +1,8 @@ class CreateShippingRateDiscounts < ActiveRecord::Migration[7.0] def change create_table :friendly_shipping_rate_discounts do |t| - t.references :promotion_action, type: :bigint, null: false, foreign_key: { to_table: :friendly_promotion_actions } - t.references :shipping_rate, type: :integer, null: false, foreign_key: { to_table: :spree_shipping_rates } + t.references :promotion_action, type: :bigint, null: false, foreign_key: {to_table: :friendly_promotion_actions} + t.references :shipping_rate, type: :integer, null: false, foreign_key: {to_table: :spree_shipping_rates} t.decimal :amount, precision: 10, scale: 2, null: false t.string :label, null: false diff --git a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb index 67f7f40de0d..75343fc0892 100644 --- a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb +++ b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb @@ -13,7 +13,7 @@ if item.respond_to?(:children) Spree::BackendConfiguration::MenuItem.new( label: :promotions, - icon: 'bullhorn', + icon: "bullhorn", condition: -> { can?(:admin, SolidusFriendlyPromotions::Promotion) }, url: -> { SolidusFriendlyPromotions::Engine.routes.url_helpers.admin_promotions_path }, data_hook: :admin_promotion_sub_tabs, @@ -21,20 +21,20 @@ Spree::BackendConfiguration::MenuItem.new( label: :promotions, url: -> { SolidusFriendlyPromotions::Engine.routes.url_helpers.admin_promotions_path }, - condition: -> { can?(:admin, SolidusFriendlyPromotions::Promotion) }, + condition: -> { can?(:admin, SolidusFriendlyPromotions::Promotion) } ), Spree::BackendConfiguration::MenuItem.new( label: :promotion_categories, url: -> { SolidusFriendlyPromotions::Engine.routes.url_helpers.admin_promotion_categories_path }, - condition: -> { can?(:admin, SolidusFriendlyPromotions::PromotionCategory) }, - ), - ], + condition: -> { can?(:admin, SolidusFriendlyPromotions::PromotionCategory) } + ) + ] ) else Spree::BackendConfiguration::MenuItem.new( [:promotions, :promotion_categories], - 'bullhorn', - partial: 'spree/admin/shared/promotion_sub_menu', + "bullhorn", + partial: "spree/admin/shared/promotion_sub_menu", condition: -> { can?(:admin, Spree::Promotion) }, url: -> { SolidusFriendlyPromotions::Engine.routes.url_helpers.admin_promotions_path }, position: 2 @@ -60,7 +60,7 @@ "SolidusFriendlyPromotions::Calculators::FlexiRate", "SolidusFriendlyPromotions::Calculators::Percent", "SolidusFriendlyPromotions::Calculators::TieredFlatRate", - "SolidusFriendlyPromotions::Calculators::TieredPercent", + "SolidusFriendlyPromotions::Calculators::TieredPercent" ] config.line_item_discount_calculators = [ "SolidusFriendlyPromotions::Calculators::DistributedAmount", @@ -68,7 +68,7 @@ "SolidusFriendlyPromotions::Calculators::FlexiRate", "SolidusFriendlyPromotions::Calculators::Percent", "SolidusFriendlyPromotions::Calculators::TieredFlatRate", - "SolidusFriendlyPromotions::Calculators::TieredPercent", + "SolidusFriendlyPromotions::Calculators::TieredPercent" ] config.order_rules = [ @@ -83,19 +83,19 @@ "SolidusFriendlyPromotions::Rules::Taxon", "SolidusFriendlyPromotions::Rules::UserLoggedIn", "SolidusFriendlyPromotions::Rules::UserRole", - "SolidusFriendlyPromotions::Rules::User", + "SolidusFriendlyPromotions::Rules::User" ] config.line_item_rules = [ "SolidusFriendlyPromotions::Rules::LineItemOptionValue", "SolidusFriendlyPromotions::Rules::LineItemProduct", - "SolidusFriendlyPromotions::Rules::LineItemTaxon", + "SolidusFriendlyPromotions::Rules::LineItemTaxon" ] config.shipment_rules = [ - "SolidusFriendlyPromotions::Rules::ShippingMethod", + "SolidusFriendlyPromotions::Rules::ShippingMethod" ] config.actions = [ "SolidusFriendlyPromotions::Actions::AdjustLineItem", - "SolidusFriendlyPromotions::Actions::AdjustShipment", + "SolidusFriendlyPromotions::Actions::AdjustShipment" ] end diff --git a/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb b/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb index e433417ce48..26922dd1b64 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spree/core/environment_extension' +require "spree/core/environment_extension" module SolidusFriendlyPromotions class Configuration < Spree::Preferences::Configuration diff --git a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_order_promotion_factory.rb b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_order_promotion_factory.rb index 2361cbedae5..aefa80384f0 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_order_promotion_factory.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_order_promotion_factory.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true FactoryBot.define do - factory :friendly_order_promotion, class: 'SolidusFriendlyPromotions::OrderPromotion' do + factory :friendly_order_promotion, class: "SolidusFriendlyPromotions::OrderPromotion" do association :order, factory: :order association :promotion, factory: :friendly_promotion end diff --git a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_category_factory.rb b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_category_factory.rb index 07155b7b7fc..ea720a21492 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_category_factory.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_category_factory.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true FactoryBot.define do - factory :friendly_promotion_category, class: 'SolidusFriendlyPromotions::PromotionCategory' do - name { 'Promotion Category' } + factory :friendly_promotion_category, class: "SolidusFriendlyPromotions::PromotionCategory" do + name { "Promotion Category" } end end diff --git a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_code_factory.rb b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_code_factory.rb index dbf38774456..3de00d1eaf5 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_code_factory.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_code_factory.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true FactoryBot.define do - factory :friendly_promotion_code, class: 'SolidusFriendlyPromotions::PromotionCode' do + factory :friendly_promotion_code, class: "SolidusFriendlyPromotions::PromotionCode" do association :promotion, factory: :friendly_promotion sequence(:value) { |i| "code#{i}" } end diff --git a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_factory.rb b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_factory.rb index 244716876c8..f23dedbb355 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_factory.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_factory.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true FactoryBot.define do - factory :friendly_promotion, class: 'SolidusFriendlyPromotions::Promotion' do - name { 'Promo' } + factory :friendly_promotion, class: "SolidusFriendlyPromotions::Promotion" do + name { "Promo" } transient do code { nil } @@ -68,7 +68,7 @@ after(:create) do |promotion, evaluator| rule = SolidusFriendlyPromotions::Rules::ItemTotal.create!( promotion: promotion, - preferred_operator: 'gte', + preferred_operator: "gte", preferred_amount: evaluator.item_total_threshold_amount ) promotion.rules << rule @@ -79,7 +79,7 @@ trait :with_first_order_rule do after(:create) do |promotion, _evaluator| rule = SolidusFriendlyPromotions::Rules::FirstOrder.create!( - promotion: promotion, + promotion: promotion ) promotion.rules << rule promotion.save! diff --git a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_shipping_rate_discount_factory.rb b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_shipping_rate_discount_factory.rb index 1fbe1b7e46b..27594efc6b2 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_shipping_rate_discount_factory.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_shipping_rate_discount_factory.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true FactoryBot.define do - factory :friendly_shipping_rate_discount, class: 'SolidusFriendlyPromotions::ShippingRateDiscount' do + factory :friendly_shipping_rate_discount, class: "SolidusFriendlyPromotions::ShippingRateDiscount" do amount { BigDecimal("-4.00") } shipping_rate promotion_action { SolidusFriendlyPromotions::Actions::AdjustShipment.new } diff --git a/friendly_promotions/spec/jobs/solidus_friendly_promotions/promotion_code_batch_job_spec.rb b/friendly_promotions/spec/jobs/solidus_friendly_promotions/promotion_code_batch_job_spec.rb index 46a497b8702..148073427be 100644 --- a/friendly_promotions/spec/jobs/solidus_friendly_promotions/promotion_code_batch_job_spec.rb +++ b/friendly_promotions/spec/jobs/solidus_friendly_promotions/promotion_code_batch_job_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" RSpec.describe SolidusFriendlyPromotions::PromotionCodeBatchJob, type: :job do let(:email) { "test@email.com" } let(:code_batch) do @@ -23,8 +23,8 @@ def codes SolidusFriendlyPromotions::PromotionCode.pluck(:value) end - context 'with the default join character' do - it 'uses the default join characters', :aggregate_failures do + context "with the default join character" do + it "uses the default join characters", :aggregate_failures do subject.perform(code_batch) codes.each do |code| expect(code).to match(/^test_/) @@ -32,18 +32,18 @@ def codes end end - context 'with a custom join character' do + context "with a custom join character" do let(:code_batch) do SolidusFriendlyPromotions::PromotionCodeBatch.create!( promotion: create(:friendly_promotion), base_code: "test", number_of_codes: 10, email: email, - join_characters: '-' + join_characters: "-" ) end - it 'uses the custom join characters', :aggregate_failures do + it "uses the custom join characters", :aggregate_failures do subject.perform(code_batch) codes.each do |code| expect(code).to match(/^test-/) diff --git a/friendly_promotions/spec/lib/solidus_friendly_promotions/testing_support/factories_spec.rb b/friendly_promotions/spec/lib/solidus_friendly_promotions/testing_support/factories_spec.rb index 23ee0b5c8c8..1e46e37fe51 100644 --- a/friendly_promotions/spec/lib/solidus_friendly_promotions/testing_support/factories_spec.rb +++ b/friendly_promotions/spec/lib/solidus_friendly_promotions/testing_support/factories_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" RSpec.describe "Friendly Factories" do it "has a bunch of working factories" do diff --git a/friendly_promotions/spec/mailers/solidus_friendly_promotions/promotion_code_batch_mailer_spec.rb b/friendly_promotions/spec/mailers/solidus_friendly_promotions/promotion_code_batch_mailer_spec.rb index 9ee8ba28729..272afc5103e 100644 --- a/friendly_promotions/spec/mailers/solidus_friendly_promotions/promotion_code_batch_mailer_spec.rb +++ b/friendly_promotions/spec/mailers/solidus_friendly_promotions/promotion_code_batch_mailer_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" RSpec.describe SolidusFriendlyPromotions::PromotionCodeBatchMailer, type: :mailer do let(:promotion) { create(:friendly_promotion, name: "Promotion Test") } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_line_item_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_line_item_spec.rb index 863f4be290a..b3d8e04432d 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_line_item_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_line_item_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" RSpec.describe SolidusFriendlyPromotions::Actions::AdjustLineItem do subject(:action) { described_class.new } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_shipment_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_shipment_spec.rb index fdd07df85e0..7fc0206ea49 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_shipment_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_shipment_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" RSpec.describe SolidusFriendlyPromotions::Actions::AdjustShipment do subject(:action) { described_class.new } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/distributed_amount_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/distributed_amount_spec.rb index 9ad4690ce94..65aca311d33 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/distributed_amount_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/distributed_amount_spec.rb @@ -1,31 +1,31 @@ # frozen_string_literal: true -require 'spec_helper' -require 'shared_examples/calculator_shared_examples' +require "spec_helper" +require "shared_examples/calculator_shared_examples" RSpec.describe SolidusFriendlyPromotions::Calculators::DistributedAmount, type: :model do let(:calculator) { described_class.new(preferred_amount: 15, preferred_currency: currency) } let!(:promotion) do - create :friendly_promotion, apply_automatically: true, name: '15 spread', actions: [action], rules: rules + create :friendly_promotion, apply_automatically: true, name: "15 spread", actions: [action], rules: rules end let(:rules) { [] } let(:action) { SolidusFriendlyPromotions::Actions::AdjustLineItem.create(calculator: calculator) } let(:order) { create(:order_with_line_items, line_items_attributes: line_items_attributes) } let(:currency) { "USD" } - context 'applied to an order' do - let(:line_items_attributes) { [{ price: 20 }, { price: 30 }, { price: 100 }] } + context "applied to an order" do + let(:line_items_attributes) { [{price: 20}, {price: 30}, {price: 100}] } before do order.recalculate end - it 'correctly distributes the entire discount' do + it "correctly distributes the entire discount" do expect(order.promo_total).to eq(-15) expect(order.line_items.map(&:adjustment_total)).to eq([-2, -3, -10]) end - context 'with product promotion rule' do + context "with product promotion rule" do let(:first_product) { order.line_items.first.product } let(:rules) do [ @@ -37,7 +37,7 @@ order.recalculate end - it 'still distributes the entire discount' do + it "still distributes the entire discount" do expect(order.promo_total).to eq(-15) expect(order.line_items.map(&:adjustment_total)).to eq([-15, 0, 0]) end @@ -47,7 +47,7 @@ describe "#compute_line_item" do subject { calculator.compute_line_item(order.line_items.first) } - let(:line_items_attributes) { [{ price: 50 }, { price: 50 }, { price: 50 }] } + let(:line_items_attributes) { [{price: 50}, {price: 50}, {price: 50}] } context "when the order currency matches the store's currency" do let(:currency) { "USD" } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/flat_rate_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/flat_rate_spec.rb index e89918d7865..da44375c416 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/flat_rate_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/flat_rate_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require 'spec_helper' -require 'shared_examples/calculator_shared_examples' +require "spec_helper" +require "shared_examples/calculator_shared_examples" RSpec.describe SolidusFriendlyPromotions::Calculators::FlatRate, type: :model do subject { calculator.compute(line_item) } @@ -15,7 +15,7 @@ ) end - it_behaves_like 'a calculator with a description' + it_behaves_like "a calculator with a description" context "compute" do describe "when preferred currency matches order" do diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/flexi_rate_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/flexi_rate_spec.rb index 078cecac60d..3acd59c13d6 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/flexi_rate_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/flexi_rate_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require 'spec_helper' -require 'shared_examples/calculator_shared_examples' +require "spec_helper" +require "shared_examples/calculator_shared_examples" RSpec.describe SolidusFriendlyPromotions::Calculators::FlexiRate, type: :model do let(:calculator) do @@ -26,7 +26,7 @@ ) end - it_behaves_like 'a calculator with a description' + it_behaves_like "a calculator with a description" context "compute" do subject { calculator.compute(line_item) } @@ -178,7 +178,7 @@ end it "allows creation of new object with all the attributes" do - attributes = { preferred_first_item: 1, preferred_additional_item: 1, preferred_max_items: 1 } + attributes = {preferred_first_item: 1, preferred_additional_item: 1, preferred_max_items: 1} calculator = described_class.new(attributes) expect(calculator).to have_attributes(attributes) end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/percent_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/percent_spec.rb index 78ce5c5e59a..f71734952e3 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/percent_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/percent_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require 'spec_helper' -require 'shared_examples/calculator_shared_examples' +require "spec_helper" +require "shared_examples/calculator_shared_examples" RSpec.describe SolidusFriendlyPromotions::Calculators::Percent, type: :model do context "compute" do @@ -16,5 +16,5 @@ end end - it_behaves_like 'a calculator with a description' + it_behaves_like "a calculator with a description" end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_flat_rate_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_flat_rate_spec.rb index b476776b7e6..ee438e6c34c 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_flat_rate_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_flat_rate_spec.rb @@ -1,65 +1,65 @@ # frozen_string_literal: true -require 'spec_helper' -require 'shared_examples/calculator_shared_examples' +require "spec_helper" +require "shared_examples/calculator_shared_examples" RSpec.describe SolidusFriendlyPromotions::Calculators::TieredFlatRate, type: :model do let(:calculator) { described_class.new } - it_behaves_like 'a calculator with a description' + it_behaves_like "a calculator with a description" describe "#valid?" do subject { calculator.valid? } context "when tiers is a hash" do context "and the key is not a positive number" do - before { calculator.preferred_tiers = { "nope" => 20 } } + before { calculator.preferred_tiers = {"nope" => 20} } it { is_expected.to be false } end context "and the key is an integer" do - before { calculator.preferred_tiers = { 20 => 20 } } + before { calculator.preferred_tiers = {20 => 20} } it "converts successfully" do expect(subject).to be true - expect(calculator.preferred_tiers).to eq({ BigDecimal('20') => BigDecimal('20') }) + expect(calculator.preferred_tiers).to eq({BigDecimal("20") => BigDecimal("20")}) end end context "and the key is a float" do - before { calculator.preferred_tiers = { 20.5 => 20.5 } } + before { calculator.preferred_tiers = {20.5 => 20.5} } it "converts successfully" do expect(subject).to be true - expect(calculator.preferred_tiers).to eq({ BigDecimal('20.5') => BigDecimal('20.5') }) + expect(calculator.preferred_tiers).to eq({BigDecimal("20.5") => BigDecimal("20.5")}) end end context "and the key is a string number" do - before { calculator.preferred_tiers = { "20" => 20 } } + before { calculator.preferred_tiers = {"20" => 20} } it "converts successfully" do expect(subject).to be true - expect(calculator.preferred_tiers).to eq({ BigDecimal('20') => BigDecimal('20') }) + expect(calculator.preferred_tiers).to eq({BigDecimal("20") => BigDecimal("20")}) end end context "and the key is a numeric string with spaces" do - before { calculator.preferred_tiers = { " 20 " => 20 } } + before { calculator.preferred_tiers = {" 20 " => 20} } it "converts successfully" do expect(subject).to be true - expect(calculator.preferred_tiers).to eq({ BigDecimal('20') => BigDecimal('20') }) + expect(calculator.preferred_tiers).to eq({BigDecimal("20") => BigDecimal("20")}) end end context "and the key is a string number with decimals" do - before { calculator.preferred_tiers = { "20.5" => "20.5" } } + before { calculator.preferred_tiers = {"20.5" => "20.5"} } it "converts successfully" do expect(subject).to be true - expect(calculator.preferred_tiers).to eq({ BigDecimal('20.5') => BigDecimal('20.5') }) + expect(calculator.preferred_tiers).to eq({BigDecimal("20.5") => BigDecimal("20.5")}) end end end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_percent_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_percent_spec.rb index 73cd5fd6458..6f9bb1518b1 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_percent_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_percent_spec.rb @@ -1,12 +1,12 @@ # frozen_string_literal: true -require 'spec_helper' -require 'shared_examples/calculator_shared_examples' +require "spec_helper" +require "shared_examples/calculator_shared_examples" RSpec.describe SolidusFriendlyPromotions::Calculators::TieredPercent, type: :model do let(:calculator) { described_class.new } - it_behaves_like 'a calculator with a description' + it_behaves_like "a calculator with a description" describe "#valid?" do subject { calculator.valid? } @@ -25,59 +25,59 @@ context "when tiers is a hash" do context "and the key is not a positive number" do - before { calculator.preferred_tiers = { "nope" => 20 } } + before { calculator.preferred_tiers = {"nope" => 20} } it { is_expected.to be false } end context "and one of the values is not a percent" do - before { calculator.preferred_tiers = { 10 => 110 } } + before { calculator.preferred_tiers = {10 => 110} } it { is_expected.to be false } end context "and the key is an integer" do - before { calculator.preferred_tiers = { 20 => 20 } } + before { calculator.preferred_tiers = {20 => 20} } it "converts successfully" do expect(subject).to be true - expect(calculator.preferred_tiers).to eq({ BigDecimal('20') => BigDecimal('20') }) + expect(calculator.preferred_tiers).to eq({BigDecimal("20") => BigDecimal("20")}) end end context "and the key is a float" do - before { calculator.preferred_tiers = { 20.5 => 20.5 } } + before { calculator.preferred_tiers = {20.5 => 20.5} } it "converts successfully" do expect(subject).to be true - expect(calculator.preferred_tiers).to eq({ BigDecimal('20.5') => BigDecimal('20.5') }) + expect(calculator.preferred_tiers).to eq({BigDecimal("20.5") => BigDecimal("20.5")}) end end context "and the key is a string number" do - before { calculator.preferred_tiers = { "20" => 20 } } + before { calculator.preferred_tiers = {"20" => 20} } it "converts successfully" do expect(subject).to be true - expect(calculator.preferred_tiers).to eq({ BigDecimal('20') => BigDecimal('20') }) + expect(calculator.preferred_tiers).to eq({BigDecimal("20") => BigDecimal("20")}) end end context "and the key is a numeric string with spaces" do - before { calculator.preferred_tiers = { " 20 " => 20 } } + before { calculator.preferred_tiers = {" 20 " => 20} } it "converts successfully" do expect(subject).to be true - expect(calculator.preferred_tiers).to eq({ BigDecimal('20') => BigDecimal('20') }) + expect(calculator.preferred_tiers).to eq({BigDecimal("20") => BigDecimal("20")}) end end context "and the key is a string number with decimals" do - before { calculator.preferred_tiers = { "20.5" => "20.5" } } + before { calculator.preferred_tiers = {"20.5" => "20.5"} } it "converts successfully" do expect(subject).to be true - expect(calculator.preferred_tiers).to eq({ BigDecimal('20.5') => BigDecimal('20.5') }) + expect(calculator.preferred_tiers).to eq({BigDecimal("20.5") => BigDecimal("20.5")}) end end end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/distributed_amounts_handler_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/distributed_amounts_handler_spec.rb index 4780f71f144..f3d244902c0 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/distributed_amounts_handler_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/distributed_amounts_handler_spec.rb @@ -18,7 +18,7 @@ let(:total_amount) { 15 } context "when there is only one line item" do - let(:line_items_attributes) { [{ price: 100 }] } + let(:line_items_attributes) { [{price: 100}] } let(:line_item) { order.line_items.first } it "applies the entire amount to the line item" do @@ -28,7 +28,7 @@ context "when there are multiple line items" do let(:line_items_attributes) do - [{ price: 50 }, { price: 50 }, { price: 50 }] + [{price: 50}, {price: 50}, {price: 50}] end context "and the line items are equally priced" do @@ -63,7 +63,7 @@ context "and the line items do not have equal subtotal amounts" do let(:line_items_attributes) do - [{ price: 50, quantity: 3 }, { price: 50, quantity: 1 }, { price: 50, quantity: 2 }] + [{price: 50, quantity: 3}, {price: 50, quantity: 1}, {price: 50, quantity: 2}] end it "distributes the total amount relative to the item's price" do diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/friendly_promotion_discounter_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/friendly_promotion_discounter_spec.rb index 3574adadf0e..fb97da02a6a 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/friendly_promotion_discounter_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/friendly_promotion_discounter_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" RSpec.describe SolidusFriendlyPromotions::FriendlyPromotionDiscounter do describe "selecting promotions" do @@ -19,9 +19,9 @@ context "no promo is connected to the order" do it "checks only active promotions" do - expect(SolidusFriendlyPromotions::PromotionEligibility).to receive(:new). - with(promotable: order, possible_promotions: [active_promotion]). - and_call_original + expect(SolidusFriendlyPromotions::PromotionEligibility).to receive(:new) + .with(promotable: order, possible_promotions: [active_promotion]) + .and_call_original subject end end @@ -32,9 +32,9 @@ end it "checks active and connected promotions" do - expect(SolidusFriendlyPromotions::PromotionEligibility).to receive(:new). - with(promotable: order, possible_promotions: array_including(active_promotion, connectable_promotion)). - and_call_original + expect(SolidusFriendlyPromotions::PromotionEligibility).to receive(:new) + .with(promotable: order, possible_promotions: array_including(active_promotion, connectable_promotion)) + .and_call_original subject end end @@ -45,16 +45,16 @@ end it "does not check connected inactive promotions" do - expect(SolidusFriendlyPromotions::PromotionEligibility).to receive(:new). - with(promotable: order, possible_promotions: array_including(active_promotion)). - and_call_original + expect(SolidusFriendlyPromotions::PromotionEligibility).to receive(:new) + .with(promotable: order, possible_promotions: array_including(active_promotion)) + .and_call_original subject end end end context "promotions in the past" do - let(:order) { create(:order, completed_at: 7.days.ago) } + let(:order) { create(:order, completed_at: 7.days.ago) } let(:currently_active_promotion) { create(:friendly_promotion, :with_adjustable_action, starts_at: 1.hour.ago) } let(:past_promotion) { create(:friendly_promotion, :with_adjustable_action, starts_at: 1.year.ago, expires_at: 11.months.ago) } let(:order_promotion) { create(:friendly_promotion, :with_adjustable_action, starts_at: 8.days.ago, expires_at: 6.days.ago) } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/item_discount_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/item_discount_spec.rb index 1d53505d6e3..77f1e6824c1 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/item_discount_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/item_discount_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" RSpec.describe SolidusFriendlyPromotions::ItemDiscount do it { is_expected.to respond_to(:item) } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/order_discounter_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/order_discounter_spec.rb index 43f2262f3f2..ac9a300bc7a 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/order_discounter_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/order_discounter_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" RSpec.describe SolidusFriendlyPromotions::OrderDiscounter, type: :model do subject { described_class.new(order) } @@ -59,7 +59,7 @@ context "promotion has item total rule" do let!(:rule) do SolidusFriendlyPromotions::Rules::ItemTotal.create( - preferred_operator: 'gt', + preferred_operator: "gt", preferred_amount: 50, promotion: promotion ) diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/order_promotion_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/order_promotion_spec.rb index 2a22422e019..2843422a32c 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/order_promotion_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/order_promotion_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" RSpec.describe SolidusFriendlyPromotions::OrderPromotion do subject do diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/products_promotion_rule_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/products_promotion_rule_spec.rb index c837246e2a6..9e55f0821ef 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/products_promotion_rule_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/products_promotion_rule_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" RSpec.describe SolidusFriendlyPromotions::ProductsPromotionRule do it { is_expected.to belong_to(:product).optional } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_category_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_category_spec.rb index 50ce646d63d..c19a36457d9 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_category_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_category_spec.rb @@ -1,20 +1,20 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" RSpec.describe SolidusFriendlyPromotions::PromotionCategory, type: :model do it { is_expected.to have_many :promotions } - describe 'validation' do + describe "validation" do subject { described_class.new name: name } - let(:name) { 'Nom' } + let(:name) { "Nom" } - context 'when all required attributes are specified' do + context "when all required attributes are specified" do it { is_expected.to be_valid } end - context 'when name is missing' do + context "when name is missing" do let(:name) { nil } it { is_expected.not_to be_valid } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code_batch_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code_batch_spec.rb index 1051afbe7c1..be244f8d9a9 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code_batch_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code_batch_spec.rb @@ -31,7 +31,7 @@ before { subject.update_attribute(:state, "processing") } it "raises an error" do - expect{ subject.process }.to raise_error described_class::CantProcessStartedBatch + expect { subject.process }.to raise_error described_class::CantProcessStartedBatch end end @@ -39,7 +39,7 @@ before { subject.update_attribute(:state, "completed") } it "raises an error" do - expect{ subject.process }.to raise_error described_class::CantProcessStartedBatch + expect { subject.process }.to raise_error described_class::CantProcessStartedBatch end end @@ -47,7 +47,7 @@ before { subject.update_attribute(:state, "failed") } it "raises an error" do - expect{ subject.process }.to raise_error described_class::CantProcessStartedBatch + expect { subject.process }.to raise_error described_class::CantProcessStartedBatch end end end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code_spec.rb index 755ecb8ddea..285a4ce1235 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code_spec.rb @@ -1,59 +1,59 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" RSpec.describe SolidusFriendlyPromotions::PromotionCode do - context 'callbacks' do + context "callbacks" do subject { promotion_code.save } - describe '#normalize_code' do + describe "#normalize_code" do let(:promotion) { create(:friendly_promotion, code: code) } before { subject } - context 'when no other code with the same value exists' do + context "when no other code with the same value exists" do let(:promotion_code) { promotion.codes.first } - context 'with mixed case' do - let(:code) { 'NewCoDe' } + context "with mixed case" do + let(:code) { "NewCoDe" } - it 'downcases the value before saving' do - expect(promotion_code.value).to eq('newcode') + it "downcases the value before saving" do + expect(promotion_code.value).to eq("newcode") end end - context 'with extra spacing' do - let(:code) { ' new code ' } + context "with extra spacing" do + let(:code) { " new code " } - it 'removes surrounding whitespace' do - expect(promotion_code.value).to eq 'new code' + it "removes surrounding whitespace" do + expect(promotion_code.value).to eq "new code" end end end - context 'when another code with the same value exists' do + context "when another code with the same value exists" do let(:promotion_code) { promotion.codes.build(value: code) } - context 'with mixed case' do - let(:code) { 'NewCoDe' } + context "with mixed case" do + let(:code) { "NewCoDe" } - it 'does not save the record and marks it as invalid' do + it "does not save the record and marks it as invalid" do expect(promotion_code.valid?).to eq false expect(promotion_code.errors.messages[:value]).to contain_exactly( - 'has already been taken' + "has already been taken" ) end end - context 'with extra spacing' do - let(:code) { ' new code ' } + context "with extra spacing" do + let(:code) { " new code " } - it 'does not save the record and marks it as invalid' do + it "does not save the record and marks it as invalid" do expect(promotion_code.valid?).to eq false expect(promotion_code.errors.messages[:value]).to contain_exactly( - 'has already been taken' + "has already been taken" ) end end @@ -127,7 +127,7 @@ :friendly_promotion, :with_line_item_adjustment, code: "discount", - per_code_usage_limit: usage_limit, + per_code_usage_limit: usage_limit ) end @@ -208,7 +208,7 @@ before { order.cancel! } it { is_expected.to eq 0 } - it { expect(order.state).to eq 'canceled' } + it { expect(order.state).to eq "canceled" } end end end @@ -256,27 +256,27 @@ end it "makes the adjustment disappear" do - expect{ + expect { order.complete }.to change { order.all_adjustments.friendly_promotion }.to([]) end it "adjusts the promo_total" do - expect{ + expect { order.complete }.to change(order, :promo_total).by(10) end it "increases the total to remove the promo" do - expect{ + expect { order.complete }.to change(order, :total).from(30).to(40) end it "resets the state of the order" do - expect{ + expect { order.complete - }.to change{ order.reload.state }.from("confirm").to("address") + }.to change { order.reload.state }.from("confirm").to("address") end end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rule_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rule_spec.rb index 85e31ad9c27..90ff42a9059 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rule_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rule_spec.rb @@ -33,6 +33,6 @@ def eligible?(_promotable, _options = {}) it "generates its own partial path" do rule = TestRule.new - expect(rule.to_partial_path).to eq 'solidus_friendly_promotions/admin/promotion_rules/rules/test_rule' + expect(rule.to_partial_path).to eq "solidus_friendly_promotions/admin/promotion_rules/rules/test_rule" end end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_store_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_store_spec.rb index 02e47b4cdb5..d1507f1f0ac 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_store_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_store_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" RSpec.describe SolidusFriendlyPromotions::PromotionRulesStore do it { is_expected.to belong_to(:store).optional } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_taxon_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_taxon_spec.rb index b0c06384862..a5c767fb1af 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_taxon_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_taxon_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" RSpec.describe SolidusFriendlyPromotions::PromotionRulesTaxon do it { is_expected.to belong_to(:taxon).optional } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_user_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_user_spec.rb index 981cc07d43a..ebcd062ec1a 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_user_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_user_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" RSpec.describe SolidusFriendlyPromotions::PromotionRulesUser do it { is_expected.to belong_to(:user).optional } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb index fb8ba937086..afefc52803a 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" RSpec.describe SolidusFriendlyPromotions::Promotion, type: :model do let(:promotion) { described_class.new } @@ -55,7 +55,7 @@ end end - describe '.active' do + describe ".active" do subject { described_class.active } let(:promotion) { create(:friendly_promotion, starts_at: Date.yesterday, name: "name1") } @@ -66,15 +66,15 @@ expect(subject).to be_empty end - context 'when promotion has an action' do + context "when promotion has an action" do let(:promotion) { create(:friendly_promotion, :with_adjustable_action, starts_at: Date.yesterday, name: "name1") } - it 'returns promotion with action' do + it "returns promotion with action" do expect(subject).to match [promotion] end end - context 'when called with a time that is not current' do + context "when called with a time that is not current" do subject { described_class.active(4.days.ago) } let(:promotion) do @@ -87,13 +87,13 @@ ) end - it 'returns promotion that was active then' do + it "returns promotion that was active then" do expect(subject).to match [promotion] end end end - describe '.has_actions' do + describe ".has_actions" do subject { described_class.has_actions } let(:promotion) { create(:friendly_promotion, starts_at: Date.yesterday, name: "name1") } @@ -104,14 +104,14 @@ expect(subject).to be_empty end - context 'when promotion has two actions' do + context "when promotion has two actions" do let(:promotion) { create(:friendly_promotion, :with_adjustable_action, starts_at: Date.yesterday, name: "name1") } before do promotion.actions << SolidusFriendlyPromotions::Actions::AdjustShipment.new(calculator: SolidusFriendlyPromotions::Calculators::Percent.new) end - it 'returns distinct promotion' do + it "returns distinct promotion" do expect(subject).to match [promotion] end end @@ -268,7 +268,7 @@ before { order.cancel! } it { is_expected.to eq 0 } - it { expect(order.state).to eq 'canceled' } + it { expect(order.state).to eq "canceled" } end end end @@ -307,96 +307,96 @@ end end - describe '#not_started?' do + describe "#not_started?" do subject { promotion.not_started? } let(:promotion) { described_class.new(starts_at: starts_at) } - context 'no starts_at date' do + context "no starts_at date" do let(:starts_at) { nil } it { is_expected.to be_falsey } end - context 'when starts_at date is in the past' do + context "when starts_at date is in the past" do let(:starts_at) { Time.current - 1.day } it { is_expected.to be_falsey } end - context 'when starts_at date is not already reached' do + context "when starts_at date is not already reached" do let(:starts_at) { Time.current + 1.day } it { is_expected.to be_truthy } end end - describe '#started?' do + describe "#started?" do subject { promotion.started? } let(:promotion) { described_class.new(starts_at: starts_at) } - context 'when no starts_at date' do + context "when no starts_at date" do let(:starts_at) { nil } it { is_expected.to be_truthy } end - context 'when starts_at date is in the past' do + context "when starts_at date is in the past" do let(:starts_at) { Time.current - 1.day } it { is_expected.to be_truthy } end - context 'when starts_at date is not already reached' do + context "when starts_at date is not already reached" do let(:starts_at) { Time.current + 1.day } it { is_expected.to be_falsey } end end - describe '#expired?' do + describe "#expired?" do subject { promotion.expired? } let(:promotion) { described_class.new(expires_at: expires_at) } - context 'when no expires_at date' do + context "when no expires_at date" do let(:expires_at) { nil } it { is_expected.to be_falsey } end - context 'when expires_at date is not already reached' do + context "when expires_at date is not already reached" do let(:expires_at) { Time.current + 1.day } it { is_expected.to be_falsey } end - context 'when expires_at date is in the past' do + context "when expires_at date is in the past" do let(:expires_at) { Time.current - 1.day } it { is_expected.to be_truthy } end end - describe '#not_expired?' do + describe "#not_expired?" do subject { promotion.not_expired? } let(:promotion) { described_class.new(expires_at: expires_at) } - context 'when no expired_at date' do + context "when no expired_at date" do let(:expires_at) { nil } it { is_expected.to be_truthy } end - context 'when expires_at date is not already reached' do + context "when expires_at date is not already reached" do let(:expires_at) { Time.current + 1.day } it { is_expected.to be_truthy } end - context 'when expires_at date is in the past' do + context "when expires_at date is in the past" do let(:expires_at) { Time.current - 1.day } it { is_expected.to be_falsey } @@ -426,7 +426,7 @@ expect(promotion.active?).to eq(false) end - context 'when promotion has an action' do + context "when promotion has an action" do let(:promotion) { create(:friendly_promotion, :with_adjustable_action, name: "name1") } it "is active if it has started already" do @@ -508,7 +508,7 @@ end end - describe '#used_by?' do + describe "#used_by?" do subject { promotion.used_by? user, [excluded_order] } let(:promotion) { create :friendly_promotion, :with_adjustable_action } @@ -521,7 +521,7 @@ order.save! end - context 'when the user has used this promo' do + context "when the user has used this promo" do before do order.friendly_order_promotions.create( promotion: promotion @@ -531,10 +531,10 @@ order.save! end - context 'when the order is complete' do + context "when the order is complete" do it { is_expected.to be true } - context 'when the promotion was not eligible' do + context "when the promotion was not eligible" do let(:adjustment) { order.all_adjustments.first } before do @@ -545,14 +545,14 @@ it { is_expected.to be false } end - context 'when the only matching order is the excluded order' do + context "when the only matching order is the excluded order" do let(:excluded_order) { order } it { is_expected.to be false } end end - context 'when the order is not complete' do + context "when the order is not complete" do let(:order) { create :order, user: user } # The before clause above sets the completed at @@ -563,7 +563,7 @@ end end - context 'when the user has not used this promo' do + context "when the user has not used this promo" do it { is_expected.to be false } end end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/item_total_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/item_total_spec.rb index 38fc3a2f307..72f7dc61110 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/item_total_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/item_total_spec.rb @@ -9,7 +9,7 @@ preferred_operator: preferred_operator ) end - let(:order) { instance_double('Spree::Order', item_total: item_total, currency: order_currency) } + let(:order) { instance_double("Spree::Order", item_total: item_total, currency: order_currency) } let(:preferred_amount) { 50 } let(:order_currency) { "USD" } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/option_value_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/option_value_spec.rb index c8078873ac8..519309378d5 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/option_value_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/option_value_spec.rb @@ -9,8 +9,8 @@ subject { rule.preferred_eligible_values } it "assigns a nicely formatted hash" do - rule.preferred_eligible_values = { "5" => "1,2", "6" => "1" } - expect(subject).to eq({ 5 => [1, 2], 6 => [1] }) + rule.preferred_eligible_values = {"5" => "1,2", "6" => "1"} + expect(subject).to eq({5 => [1, 2], 6 => [1]}) end end @@ -39,9 +39,9 @@ context "when there are any applicable line items" do before do - rule.preferred_eligible_values = { line_item.product.id => [ + rule.preferred_eligible_values = {line_item.product.id => [ line_item.variant.option_values.pick(:id) - ] } + ]} end it { is_expected.to be true } @@ -49,7 +49,7 @@ context "when there are no applicable line items" do before do - rule.preferred_eligible_values = { 99 => [99] } + rule.preferred_eligible_values = {99 => [99]} end it { is_expected.to be false } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/simple_order_contents_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/simple_order_contents_spec.rb index fefa74d8db7..11c44a17c3b 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/simple_order_contents_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/simple_order_contents_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" RSpec.describe SolidusFriendlyPromotions::SimpleOrderContents, type: :model do subject(:order_contents) { described_class.new(order) } @@ -11,15 +11,15 @@ let!(:stock_location) { variant.stock_locations.first } describe "#add" do - context 'given quantity is not explicitly provided' do - it 'adds one line item' do + context "given quantity is not explicitly provided" do + it "adds one line item" do line_item = subject.add(variant) expect(line_item.quantity).to eq(1) expect(order.line_items.size).to eq(1) end end - context 'given a shipment' do + context "given a shipment" do let!(:shipment) { create(:shipment, order: order) } it "ensure shipment calls update_amounts instead of order calling check_shipments_and_restart_checkout" do @@ -51,20 +51,20 @@ end end - context 'not given a shipment' do + context "not given a shipment" do it "ensures updated shipments" do expect(subject.order).to receive(:check_shipments_and_restart_checkout) subject.add(variant) end end - it 'adds line item if one does not exist' do + it "adds line item if one does not exist" do line_item = subject.add(variant, 1) expect(line_item.quantity).to eq(1) expect(order.line_items.size).to eq(1) end - it 'updates line item if one exists' do + it "updates line item if one exists" do subject.add(variant, 1) line_item = subject.add(variant, 1) expect(line_item.quantity).to eq(2) @@ -104,14 +104,14 @@ end end - describe 'tax calculations' do + describe "tax calculations" do let!(:zone) { create(:global_zone) } let!(:tax_rate) do create(:tax_rate, zone: zone, tax_categories: [variant.tax_category]) end - context 'when the order has a taxable address' do - it 'creates a tax adjustment' do + context "when the order has a taxable address" do + it "creates a tax adjustment" do expect(order.tax_address.country_id).to be_present order_contents.add(variant) line_item = order.find_line_item_by_variant(variant) @@ -119,12 +119,12 @@ end end - context 'when the order does not have a taxable address' do + context "when the order does not have a taxable address" do before do order.update!(ship_address: nil, bill_address: nil) end - it 'creates a tax adjustment' do + it "creates a tax adjustment" do expect(order.tax_address.country_id).to be_nil order_contents.add(variant) line_item = order.find_line_item_by_variant(variant) @@ -143,8 +143,8 @@ end end - context 'given quantity is not explicitly provided' do - it 'removes one line item' do + context "given quantity is not explicitly provided" do + it "removes one line item" do line_item = subject.add(variant, 3) subject.remove(variant) @@ -152,7 +152,7 @@ end end - context 'given a shipment' do + context "given a shipment" do it "ensure shipment calls update_amounts instead of order calling check_shipments_and_restart_checkout" do subject.add(variant, 1) shipment = create(:shipment) @@ -162,7 +162,7 @@ end end - context 'not given a shipment' do + context "not given a shipment" do it "ensures updated shipments" do subject.add(variant, 1) expect(subject.order).to receive(:check_shipments_and_restart_checkout) @@ -170,21 +170,21 @@ end end - it 'reduces line_item quantity if quantity is less the line_item quantity' do + it "reduces line_item quantity if quantity is less the line_item quantity" do line_item = subject.add(variant, 3) subject.remove(variant, 1) expect(line_item.reload.quantity).to eq(2) end - it 'removes line_item if quantity matches line_item quantity' do + it "removes line_item if quantity matches line_item quantity" do subject.add(variant, 1) subject.remove(variant, 1) expect(order.reload.find_line_item_by_variant(variant)).to be_nil end - it 'removes line_item if quantity is greater than line_item quantity' do + it "removes line_item if quantity is greater than line_item quantity" do subject.add(variant, 1) subject.remove(variant, 2) @@ -207,7 +207,7 @@ end describe "#remove_line_item" do - context 'given a shipment' do + context "given a shipment" do it "ensure shipment calls update_amounts instead of order calling check_shipments_and_restart_checkout" do line_item = subject.add(variant, 1) shipment = create(:shipment) @@ -217,7 +217,7 @@ end end - context 'not given a shipment' do + context "not given a shipment" do it "ensures updated shipments" do line_item = subject.add(variant, 1) expect(subject.order).to receive(:check_shipments_and_restart_checkout) @@ -225,7 +225,7 @@ end end - it 'removes line_item' do + it "removes line_item" do line_item = subject.add(variant, 1) subject.remove_line_item(line_item) @@ -251,9 +251,9 @@ let!(:shirt) { subject.add variant, 1 } let(:params) do - { line_items_attributes: { - "0" => { id: shirt.id, quantity: 3 } - } } + {line_items_attributes: { + "0" => {id: shirt.id, quantity: 3} + }} end it "changes item quantity" do @@ -269,9 +269,9 @@ context "submits item quantity 0" do let(:params) do - { line_items_attributes: { - "0" => { id: shirt.id, quantity: 0 } - } } + {line_items_attributes: { + "0" => {id: shirt.id, quantity: 0} + }} end it "removes item from order" do @@ -290,7 +290,7 @@ context "completed order" do let(:order) do Spree::Order.create!( - state: 'complete', + state: "complete", completed_at: Time.current, email: "test@example.com" ) @@ -310,20 +310,20 @@ end describe "#approve" do - context 'when a name is supplied' do - it 'approves the order' do - order.contents.approve(name: 'Jordan') + context "when a name is supplied" do + it "approves the order" do + order.contents.approve(name: "Jordan") expect(order.approver).to be_nil - expect(order.approver_name).to eq('Jordan') + expect(order.approver_name).to eq("Jordan") expect(order.approved_at).to be_present expect(order).to be_approved end end - context 'when a user is supplied' do + context "when a user is supplied" do let(:user) { create(:user) } - it 'approves the order' do + it "approves the order" do order.contents.approve(user: user) expect(order.approver).to eq(user) expect(order.approver_name).to be_nil @@ -332,23 +332,23 @@ end end - context 'when a user and a name are supplied' do + context "when a user and a name are supplied" do let(:user) { create(:user) } - it 'approves the order' do - order.contents.approve(user: user, name: 'Jordan') + it "approves the order" do + order.contents.approve(user: user, name: "Jordan") expect(order.approver).to eq(user) - expect(order.approver_name).to eq('Jordan') + expect(order.approver_name).to eq("Jordan") expect(order.approved_at).to be_present expect(order).to be_approved end end - context 'when neither a user nor a name are supplied' do - it 'raises' do + context "when neither a user nor a name are supplied" do + it "raises" do expect { order.contents.approve - }.to raise_error(ArgumentError, 'user or name must be specified') + }.to raise_error(ArgumentError, "user or name must be specified") end end end diff --git a/friendly_promotions/spec/models/spree/shipping_rate_spec.rb b/friendly_promotions/spec/models/spree/shipping_rate_spec.rb index ccdddf887d8..05a770710eb 100644 --- a/friendly_promotions/spec/models/spree/shipping_rate_spec.rb +++ b/friendly_promotions/spec/models/spree/shipping_rate_spec.rb @@ -5,11 +5,11 @@ RSpec.describe Spree::ShippingRate do let(:subject) { build(:shipping_rate) } - describe '#display_price' do + describe "#display_price" do before { subject.amount = 5 } - it 'returns formatted amount' do - expect(subject.display_price).to eq('$5.00') + it "returns formatted amount" do + expect(subject.display_price).to eq("$5.00") end end diff --git a/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_actions_request_spec.rb b/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_actions_request_spec.rb index ec0ecd06aa5..09a979c8f0f 100644 --- a/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_actions_request_spec.rb +++ b/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_actions_request_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" describe "Admin::PromotionActions", type: :request do stub_authorization! @@ -11,7 +11,7 @@ post solidus_friendly_promotions.admin_promotion_promotion_actions_path(promotion_id: promotion.id), params: { promotion_action: { type: "SolidusFriendlyPromotions::Actions::AdjustLineItem", - calculator_attributes: { type: "SolidusFriendlyPromotions::Calculators::FlatRate" } + calculator_attributes: {type: "SolidusFriendlyPromotions::Calculators::FlatRate"} } } expect(response).to be_redirect @@ -21,7 +21,7 @@ it "can not create a promotion action of an invalid type" do post solidus_friendly_promotions.admin_promotion_promotion_actions_path(promotion_id: promotion.id), params: { - promotion_action: { type: "Spree::InvalidType" } + promotion_action: {type: "Spree::InvalidType"} } expect(response).to be_redirect expect(response).to redirect_to spree.edit_admin_promotion_path(promotion) diff --git a/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_rules_request_spec.rb b/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_rules_request_spec.rb index 75e7ae83c79..998d578c130 100644 --- a/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_rules_request_spec.rb +++ b/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_rules_request_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" describe "Admin::PromotionRules", type: :request do let!(:promotion) { create(:friendly_promotion) } @@ -12,7 +12,7 @@ it "can create a promotion rule of a valid type" do post solidus_friendly_promotions.admin_promotion_promotion_rules_path(promotion_id: promotion.id), params: { - promotion_rule: { type: "SolidusFriendlyPromotions::Rules::Product" } + promotion_rule: {type: "SolidusFriendlyPromotions::Rules::Product"} } expect(response).to be_redirect expect(response).to redirect_to solidus_friendly_promotions.edit_admin_promotion_path(promotion) @@ -21,7 +21,7 @@ it "can not create a promotion rule of an invalid type" do post solidus_friendly_promotions.admin_promotion_promotion_rules_path(promotion_id: promotion.id), params: { - promotion_rule: { type: "Spree::InvalidType" } + promotion_rule: {type: "Spree::InvalidType"} } expect(response).to be_redirect expect(response).to redirect_to solidus_friendly_promotions.edit_admin_promotion_path(promotion) @@ -32,9 +32,9 @@ context "when the user is not authorized" do it "redirects the user to login" do post solidus_friendly_promotions.admin_promotion_promotion_rules_path(promotion_id: promotion.id), params: { - promotion_rule: { type: "SolidusFriendlyPromotions::Rules::Product" } + promotion_rule: {type: "SolidusFriendlyPromotions::Rules::Product"} } - expect(response).to redirect_to('/admin/login') + expect(response).to redirect_to("/admin/login") end end end diff --git a/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotions_request_spec.rb b/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotions_request_spec.rb index 9c98a0df6e4..a90662c13cf 100644 --- a/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotions_request_spec.rb +++ b/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotions_request_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" RSpec.describe "Admin::Promotions", type: :request do describe "GET /index" do diff --git a/friendly_promotions/spec/shared_examples/calculator_shared_examples.rb b/friendly_promotions/spec/shared_examples/calculator_shared_examples.rb index b4318576f91..0eb256ce669 100644 --- a/friendly_promotions/spec/shared_examples/calculator_shared_examples.rb +++ b/friendly_promotions/spec/shared_examples/calculator_shared_examples.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.shared_examples_for 'a calculator with a description' do +RSpec.shared_examples_for "a calculator with a description" do describe ".description" do subject { described_class.description } diff --git a/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotion_categories_spec.rb b/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotion_categories_spec.rb index 206b5bb2752..a93ec955173 100644 --- a/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotion_categories_spec.rb +++ b/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotion_categories_spec.rb @@ -1,14 +1,14 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" -describe 'Promotion Categories', type: :system do +describe "Promotion Categories", type: :system do stub_authorization! context "index" do before do - create(:friendly_promotion_category, name: 'name1', code: 'code1') - create(:friendly_promotion_category, name: 'name2', code: 'code2') + create(:friendly_promotion_category, name: "name1", code: "code1") + create(:friendly_promotion_category, name: "name2", code: "code2") visit solidus_friendly_promotions.admin_promotion_categories_path end @@ -50,7 +50,7 @@ context "edit" do before do - create(:friendly_promotion_category, name: 'name1') + create(:friendly_promotion_category, name: "name1") visit solidus_friendly_promotions.admin_promotion_categories_path within_row(1) { click_icon :edit } end @@ -71,7 +71,7 @@ context "delete" do before do - create(:friendly_promotion_category, name: 'name1') + create(:friendly_promotion_category, name: "name1") visit solidus_friendly_promotions.admin_promotion_categories_path end diff --git a/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotions_spec.rb b/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotions_spec.rb index 3d6da8a3fbd..705e18847e2 100644 --- a/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotions_spec.rb +++ b/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotions_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" RSpec.describe "Promotions admin", type: :system do stub_authorization! @@ -41,31 +41,31 @@ context "search" do it "pages results" do - visit solidus_friendly_promotions.admin_promotions_path(per_page: '1') + visit solidus_friendly_promotions.admin_promotions_path(per_page: "1") expect(page).to have_content(promotion3.name) expect(page).not_to have_content(promotion1.name) end it "filters by name" do - visit solidus_friendly_promotions.admin_promotions_path(q: { name_cont: promotion1.name }) + visit solidus_friendly_promotions.admin_promotions_path(q: {name_cont: promotion1.name}) expect(page).to have_content(promotion1.name) expect(page).not_to have_content(promotion2.name) end it "filters by code" do - visit solidus_friendly_promotions.admin_promotions_path(q: { codes_value_cont: promotion1.codes.first.value }) + visit solidus_friendly_promotions.admin_promotions_path(q: {codes_value_cont: promotion1.codes.first.value}) expect(page).to have_content(promotion1.name) expect(page).not_to have_content(promotion2.name) end it "filters by path" do - visit solidus_friendly_promotions.admin_promotions_path(q: { path_cont: promotion1.path }) + visit solidus_friendly_promotions.admin_promotions_path(q: {path_cont: promotion1.path}) expect(page).to have_content(promotion1.name) expect(page).not_to have_content(promotion2.name) end it "filters by active" do - visit solidus_friendly_promotions.admin_promotions_path(q: { active: true }) + visit solidus_friendly_promotions.admin_promotions_path(q: {active: true}) expect(page).to have_content(promotion1.name) expect(page).to have_content(promotion2.name) expect(page).not_to have_content(promotion3.name) From 9638e8ebdf6a60ba00fcd385a81bbc309c5e510a Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 27 Sep 2023 17:27:20 +0200 Subject: [PATCH 331/834] Remove duplicate method --- .../app/models/solidus_friendly_promotions/promotion.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb index 02189238141..208f0d572d7 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb @@ -97,10 +97,6 @@ def inactive?(time = Time.current) !active?(time) end - def not_expired?(time = Time.current) - !expired?(time) - end - def expired?(time = Time.current) expires_at.present? && expires_at < time end From b0546fa516b35d4fd15b10be4036046eb03bcb4f Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 27 Sep 2023 17:27:32 +0200 Subject: [PATCH 332/834] Simplify hash generation syntax in distributed amounts handler --- .../solidus_friendly_promotions/distributed_amounts_handler.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/distributed_amounts_handler.rb b/friendly_promotions/app/models/solidus_friendly_promotions/distributed_amounts_handler.rb index 1129e91da7c..6e0c0630c0d 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/distributed_amounts_handler.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/distributed_amounts_handler.rb @@ -21,7 +21,7 @@ def amount(line_item) # @return [Hash] a hash of line item IDs and their # corresponding weighted adjustments def distributed_amounts - Hash[line_item_ids.zip(allocated_amounts)] + line_item_ids.zip(allocated_amounts).to_h end def line_item_ids From 93cdb3bd81ab6ceae0f79321f725315960074e46 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 28 Sep 2023 11:46:13 +0200 Subject: [PATCH 333/834] Add "lane" enum to promotions In order to implement stacked promotions, we need some way for the user to specify the order in which promotions run. It's really hard to imagine a UI where all promotions are sorted, so we're opting here for a coarse ordering via promotion "lanes". We imagine three lanes, "pre", "post" and "default", ordered via their enum integer. The system should first evaluate "pre" promotions, then it should evaluate "default" promotions, and then it should evaluate "post" promotions. --- .../app/models/solidus_friendly_promotions/promotion.rb | 6 ++++++ ..._add_lane_to_solidus_friendly_promotions_promotions.rb | 5 +++++ .../models/solidus_friendly_promotions/promotion_spec.rb | 8 ++++++++ 3 files changed, 19 insertions(+) create mode 100644 friendly_promotions/db/migrate/20230928093138_add_lane_to_solidus_friendly_promotions_promotions.rb diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb index 208f0d572d7..df4c7d36de8 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb @@ -29,6 +29,12 @@ class Promotion < Spree::Base joins(:actions).distinct end + enum lane: { + pre: 0, + default: 1, + post: 2 + } + self.allowed_ransackable_associations = ["codes"] self.allowed_ransackable_attributes = %w[name path promotion_category_id] self.allowed_ransackable_scopes = %i[active] diff --git a/friendly_promotions/db/migrate/20230928093138_add_lane_to_solidus_friendly_promotions_promotions.rb b/friendly_promotions/db/migrate/20230928093138_add_lane_to_solidus_friendly_promotions_promotions.rb new file mode 100644 index 00000000000..7961d469bd2 --- /dev/null +++ b/friendly_promotions/db/migrate/20230928093138_add_lane_to_solidus_friendly_promotions_promotions.rb @@ -0,0 +1,5 @@ +class AddLaneToSolidusFriendlyPromotionsPromotions < ActiveRecord::Migration[7.0] + def change + add_column :friendly_promotions, :lane, :integer, null: false, default: 1 + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb index afefc52803a..e486cd6279b 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb @@ -8,6 +8,14 @@ it { is_expected.to belong_to(:category).optional } it { is_expected.to have_many :rules } + describe "lane" do + it { is_expected.to respond_to(:lane) } + + it "is default be default" do + expect(subject.lane).to eq("default") + end + end + describe "validations" do before do @valid_promotion = described_class.new name: "A promotion" From 78d34f3dbcf5bb15181f22771338d9661333b27e Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 28 Sep 2023 12:04:04 +0200 Subject: [PATCH 334/834] Add a lane select to promotions form Every promotion now belongs to a lane, and users can configure that lane on the promotions form. --- .../models/solidus_friendly_promotions/promotion.rb | 10 ++++++++++ .../admin/promotions/_form.html.erb | 8 ++++++++ friendly_promotions/config/locales/en.yml | 5 +++++ 3 files changed, 23 insertions(+) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb index df4c7d36de8..322f0cc14cc 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb @@ -35,6 +35,16 @@ class Promotion < Spree::Base post: 2 } + def self.human_enum_name(enum_name, enum_value) + I18n.t("activerecord.attributes.#{model_name.i18n_key}.#{enum_name.to_s.pluralize}.#{enum_value}") + end + + def self.lane_options + lanes.map do |lane_name, _index| + [human_enum_name(:lane, lane_name), lane_name] + end + end + self.allowed_ransackable_associations = ["codes"] self.allowed_ransackable_attributes = %w[name path promotion_category_id] self.allowed_ransackable_scopes = %i[active] diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_form.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_form.html.erb index 307210a9ee3..e73c7a5f91b 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_form.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_form.html.erb @@ -68,6 +68,14 @@ data: { :'enable-time' => true, :'default-hour' => 0 } %>
    +
    + <%= f.field_container :lane do %> + <%= f.label :lane %>
    + <%= + f.select(:lane, SolidusFriendlyPromotions::Promotion.lane_options, {}, { class: 'custom-select fullwidth' }) + %> + <% end %> +
    diff --git a/friendly_promotions/config/locales/en.yml b/friendly_promotions/config/locales/en.yml index 33920c0e76a..87fef33be7f 100644 --- a/friendly_promotions/config/locales/en.yml +++ b/friendly_promotions/config/locales/en.yml @@ -112,6 +112,11 @@ en: solidus_friendly_promotions/rules/user_logged_in: User Logged In solidus_friendly_promotions/rules/user_role: User Role(s) attributes: + solidus_friendly_promotions/promotion: + lanes: + pre: Pre + default: Default + post: Post solidus_friendly_promotions/actions/adjust_line_item: description: Creates a promotion credit on matching line items solidus_friendly_promotions/actions/adjust_shipment: From 23a690dcedf8f7ea1c298e2830a094cec0ac4c4a Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 28 Sep 2023 12:17:50 +0200 Subject: [PATCH 335/834] Add "ordered_lanes" method We need the lanes to be sorted by enum integer value, and we don't want that to rely on the enum declaration. --- .../app/models/solidus_friendly_promotions/promotion.rb | 6 +++++- .../models/solidus_friendly_promotions/promotion_spec.rb | 6 ++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb index 322f0cc14cc..793f66c9c08 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb @@ -40,11 +40,15 @@ def self.human_enum_name(enum_name, enum_value) end def self.lane_options - lanes.map do |lane_name, _index| + ordered_lanes.map do |lane_name, _index| [human_enum_name(:lane, lane_name), lane_name] end end + def self.ordered_lanes + lanes.sort_by(&:last).to_h + end + self.allowed_ransackable_associations = ["codes"] self.allowed_ransackable_attributes = %w[name path promotion_category_id] self.allowed_ransackable_scopes = %i[active] diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb index e486cd6279b..ba7437bd244 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb @@ -16,6 +16,12 @@ end end + describe ".ordered_lanes" do + subject { described_class.ordered_lanes } + + it { is_expected.to eq({"pre" => 0, "default" => 1, "post" => 2}) } + end + describe "validations" do before do @valid_promotion = described_class.new name: "A promotion" From 7e428f709f3f124107b9fcfeb44223366aa7dcc0 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 29 Sep 2023 12:44:30 +0200 Subject: [PATCH 336/834] Add Facade objects for orders For calculating and retrieving intermediate amounts on line items, orders, shipments, and shipping rates, we need some kind of value object that holds a reference to the order and behaves more or less like the real things. I've considered using Ruby Refinements instead of Delegators for this, but they are clunky in that they need to be referenced in every file using them. --- .../discountable/line_item.rb | 23 ++++++++++ .../discountable/order.rb | 19 ++++++++ .../discountable/shipment.rb | 24 ++++++++++ .../discountable/shipping_rate.rb | 23 ++++++++++ .../discountable/line_item_spec.rb | 34 ++++++++++++++ .../discountable/order_spec.rb | 39 ++++++++++++++++ .../discountable/shipment_spec.rb | 45 +++++++++++++++++++ .../discountable/shipping_rate_spec.rb | 35 +++++++++++++++ 8 files changed, 242 insertions(+) create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/discountable/line_item.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/discountable/order.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/discountable/shipment.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/discountable/shipping_rate.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/discountable/line_item_spec.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/discountable/order_spec.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/discountable/shipment_spec.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/discountable/shipping_rate_spec.rb diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/discountable/line_item.rb b/friendly_promotions/app/models/solidus_friendly_promotions/discountable/line_item.rb new file mode 100644 index 00000000000..d196c55386a --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/discountable/line_item.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module Discountable + class LineItem < SimpleDelegator + attr_reader :discounts, :order + + def initialize(line_item, order:) + super(line_item) + @order = order + @discounts = [] + end + + def line_item + __getobj__ + end + + def discounted_amount + amount + discounts.sum(&:amount) + end + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/discountable/order.rb b/friendly_promotions/app/models/solidus_friendly_promotions/discountable/order.rb new file mode 100644 index 00000000000..ef2bd8c110a --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/discountable/order.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module Discountable + class Order < SimpleDelegator + attr_reader :line_items, :shipments + + def initialize(order) + super + @line_items = order.line_items.map { |line_item| LineItem.new(line_item, order: self) } + @shipments = order.shipments.map { |shipment| Shipment.new(shipment, order: self) } + end + + def order + __getobj__ + end + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/discountable/shipment.rb b/friendly_promotions/app/models/solidus_friendly_promotions/discountable/shipment.rb new file mode 100644 index 00000000000..e84a22a787b --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/discountable/shipment.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module Discountable + class Shipment < SimpleDelegator + attr_reader :discounts, :shipping_rates, :order + + def initialize(shipment, order:) + super(shipment) + @order = order + @discounts = [] + @shipping_rates = shipment.shipping_rates.map { |shipping_rate| ShippingRate.new(shipping_rate, shipment: self) } + end + + def shipment + __getobj__ + end + + def discounted_amount + amount + discounts.sum(&:amount) + end + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/discountable/shipping_rate.rb b/friendly_promotions/app/models/solidus_friendly_promotions/discountable/shipping_rate.rb new file mode 100644 index 00000000000..bc938e696e3 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/discountable/shipping_rate.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module Discountable + class ShippingRate < SimpleDelegator + attr_reader :discounts, :shipment + + def initialize(shipping_rate, shipment:) + super(shipping_rate) + @shipment = shipment + @discounts = [] + end + + def shipping_rate + __getobj__ + end + + def discounted_amount + amount + discounts.sum(&:amount) + end + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/line_item_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/line_item_spec.rb new file mode 100644 index 00000000000..2778f52243d --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/line_item_spec.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe SolidusFriendlyPromotions::Discountable::LineItem do + let(:discountable_order) { double(SolidusFriendlyPromotions::Discountable::Order) } + let(:spree_line_item) { build(:line_item, price: 10, quantity: 2) } + + subject(:discountable_line_item) { described_class.new(spree_line_item, order: discountable_order) } + + describe "#order" do + subject { discountable_line_item.order } + + it { is_expected.to eq(discountable_order) } + end + + describe "#discounted_amount" do + subject(:discounted_amount) { discountable_line_item.discounted_amount } + + context "with no discounts" do + it { is_expected.to eq(20) } + end + + context "with discounts" do + let(:discount) { SolidusFriendlyPromotions::ItemDiscount.new(amount: -4) } + + before do + discountable_line_item.discounts << discount + end + + it { is_expected.to eq(16) } + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/order_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/order_spec.rb new file mode 100644 index 00000000000..637b8a050cd --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/order_spec.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe SolidusFriendlyPromotions::Discountable::Order do + subject(:discountable_order) { described_class.new(spree_order) } + + let(:spree_order) { Spree::Order.new } + + describe "#line_items" do + let(:spree_order) { create(:order_with_line_items) } + subject(:line_items) { discountable_order.line_items } + + specify "are converted into Discountable Line Items" do + line_items.each do |line_item| + expect(line_item).to be_a(SolidusFriendlyPromotions::Discountable::LineItem) + end + end + end + + describe "#shipments" do + let(:spree_order) { create(:order_ready_to_ship) } + subject(:shipments) { discountable_order.shipments } + + specify "are converted into Discountable Shipments" do + shipments.each do |shipment| + expect(shipment).to be_a(SolidusFriendlyPromotions::Discountable::Shipment) + end + end + end + + describe "delegation" do + let(:spree_order) { Spree::Order.new(email: "yoda@example.com") } + + it "forwards order attributes" do + expect(subject.email).to eq("yoda@example.com") + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/shipment_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/shipment_spec.rb new file mode 100644 index 00000000000..3c963f7dbab --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/shipment_spec.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe SolidusFriendlyPromotions::Discountable::Shipment do + let(:discountable_order) { double(SolidusFriendlyPromotions::Discountable::Order) } + + let(:spree_shipment) { build(:shipment, amount: 20) } + + subject(:discountable_shipment) { described_class.new(spree_shipment, order: discountable_order) } + + describe "#order" do + subject { discountable_shipment.order } + + it { is_expected.to eq(discountable_order) } + end + + describe "#discounted_amount" do + subject(:discounted_amount) { discountable_shipment.discounted_amount } + + context "with no discounts" do + it { is_expected.to eq(20) } + end + + context "with discounts" do + let(:discount) { SolidusFriendlyPromotions::ItemDiscount.new(amount: -4) } + + before do + discountable_shipment.discounts << discount + end + + it { is_expected.to eq(16) } + end + end + + describe "#shipping_rates" do + subject(:shipping_rates) { discountable_shipment.shipping_rates } + + specify "are converted into Discountable Shipments" do + shipping_rates.each do |shipping_rate| + expect(shipping_rate).to be_a(SolidusFriendlyPromotions::Discountable::ShippingRate) + end + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/shipping_rate_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/shipping_rate_spec.rb new file mode 100644 index 00000000000..25cbaf6a747 --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/shipping_rate_spec.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe SolidusFriendlyPromotions::Discountable::ShippingRate do + let(:discountable_shipment) { double(SolidusFriendlyPromotions::Discountable::Shipment) } + + let(:spree_shipping_rate) { build(:shipping_rate, amount: 20) } + + subject(:discountable_shipping_rate) { described_class.new(spree_shipping_rate, shipment: discountable_shipment) } + + describe "#shipment" do + subject { discountable_shipping_rate.shipment } + + it { is_expected.to eq(discountable_shipment) } + end + + describe "#discounted_amount" do + subject(:discounted_amount) { discountable_shipping_rate.discounted_amount } + + context "with no discounts" do + it { is_expected.to eq(20) } + end + + context "with discounts" do + let(:discount) { SolidusFriendlyPromotions::ItemDiscount.new(amount: -4) } + + before do + discountable_shipping_rate.discounts << discount + end + + it { is_expected.to eq(16) } + end + end +end From 326a0287d38727164c48eefbbce58ec3d57e6cc8 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 29 Sep 2023 12:53:57 +0200 Subject: [PATCH 337/834] Add integration spec for stacking promotions --- .../spec/models/promotion/integration_spec.rb | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/friendly_promotions/spec/models/promotion/integration_spec.rb b/friendly_promotions/spec/models/promotion/integration_spec.rb index 8ab354e7607..5d9d1ab1115 100644 --- a/friendly_promotions/spec/models/promotion/integration_spec.rb +++ b/friendly_promotions/spec/models/promotion/integration_spec.rb @@ -51,6 +51,49 @@ end end + context "with two promotions that should stack", :pending do + let(:shirt) { create(:product, name: "Shirt", price: 30) } + let(:pants) { create(:product, name: "Pants", price: 40) } + + let!(:distributed_amount_promo) do + create(:friendly_promotion, + :with_adjustable_action, + preferred_amount: 10.0, + apply_automatically: true, + lane: :post, + calculator_class: SolidusFriendlyPromotions::Calculators::DistributedAmount) + end + let(:shirts_rule) { SolidusFriendlyPromotions::Rules::LineItemProduct.new(products: [shirt]) } + let(:shirts_calculator) { SolidusFriendlyPromotions::Calculators::Percent.new(preferred_percent: 20) } + let(:shirts_action) { SolidusFriendlyPromotions::Actions::AdjustLineItem.new(calculator: shirts_calculator) } + let!(:shirts_promotion) do + create( + :friendly_promotion, + rules: [shirts_rule], + actions: [shirts_action], + name: "20% off shirts", + apply_automatically: true + ) + end + let(:order) { create(:order) } + + before do + order.contents.add(shirt.master, 1) + order.contents.add(pants.master, 1) + end + + it "does all the right things" do + expect(order.adjustments).to be_empty + # shirt: 30 USD - 20% = 24 USD + # Remaining total: 64 USD + # 10 USD distributed off: 54 USD + expect(order.total).to eq(54.00) + expect(order.item_total).to eq(70.00) + expect(order.item_total_before_tax).to eq(54) + expect(order.line_items.flat_map(&:adjustments).length).to eq(3) + end + end + context "with a shipment-level rule" do let!(:address) { create(:address) } let(:shipping_zone) { create(:global_zone) } From 2090add315fe10b4a5a4c2bbe7ff4876f2f2f4a3 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 29 Sep 2023 13:25:05 +0200 Subject: [PATCH 338/834] Refactor system to use Discountable/{Order, Shipment, Lineitem} We need a way to store intermediate amounts on the order we adjust. This accomplishes this in part by storing the discounts on custom delegators to the Solidus objects. --- .../actions/adjust_line_item.rb | 2 +- .../actions/adjust_shipment.rb | 2 +- .../friendly_promotion_discounter.rb | 12 +++++------- .../order_discounter.rb | 18 +++++------------- .../promotion_action.rb | 2 +- .../rules/first_order.rb | 2 +- .../rules/first_repeat_purchase_since.rb | 2 +- .../rules/item_total.rb | 2 +- .../rules/line_item_option_value.rb | 2 +- .../rules/line_item_product.rb | 2 +- .../rules/line_item_taxon.rb | 2 +- .../rules/nth_order.rb | 2 +- .../rules/one_use_per_user.rb | 2 +- .../rules/option_value.rb | 2 +- .../rules/product.rb | 2 +- .../rules/shipping_method.rb | 2 +- .../solidus_friendly_promotions/rules/store.rb | 2 +- .../solidus_friendly_promotions/rules/taxon.rb | 2 +- .../solidus_friendly_promotions/rules/user.rb | 2 +- .../rules/user_logged_in.rb | 2 +- .../rules/user_role.rb | 2 +- .../actions/adjust_shipment_spec.rb | 6 +++--- .../promotion_action_spec.rb | 13 +++++++------ .../rules/first_repeat_purchase_since_spec.rb | 4 ++-- .../rules/nth_order_spec.rb | 2 +- .../rules/option_value_spec.rb | 4 ++-- 26 files changed, 44 insertions(+), 53 deletions(-) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb index 394d0b660cd..0e2bc9ce9ba 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb @@ -4,7 +4,7 @@ module SolidusFriendlyPromotions module Actions class AdjustLineItem < PromotionAction def can_discount?(object) - object.is_a? Spree::LineItem + object.is_a? Discountable::LineItem end def available_calculators diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb index 82d73533943..d52354ab0d2 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb @@ -4,7 +4,7 @@ module SolidusFriendlyPromotions module Actions class AdjustShipment < PromotionAction def can_discount?(object) - object.is_a?(Spree::Shipment) || object.is_a?(Spree::ShippingRate) + object.is_a?(Discountable::Shipment) || object.is_a?(Discountable::ShippingRate) end def available_calculators diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb b/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb index 1401bd811e2..db0310b3717 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb @@ -5,7 +5,7 @@ class FriendlyPromotionDiscounter attr_reader :order, :promotions, :item_discounter def initialize(order) - @order = order + @order = Discountable::Order.new(order) @promotions = PromotionEligibility.new(promotable: order, possible_promotions: possible_promotions).call @item_discounter = ItemDiscounter.new(promotions: promotions) end @@ -13,12 +13,10 @@ def initialize(order) def call return nil if order.shipped? - OrderDiscounts.new( - order_id: order.id, - line_item_discounts: adjust_line_items, - shipment_discounts: adjust_shipments, - shipping_rate_discounts: adjust_shipping_rates - ) + adjust_line_items + adjust_shipments + adjust_shipping_rates + order end private diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb b/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb index cc693364741..7e60bf031e2 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb @@ -7,31 +7,23 @@ def initialize(order) end def call - all_order_discounts = SolidusFriendlyPromotions.config.discounters.filter_map do |discounter| - discounter.new(order).call - end + @order = FriendlyPromotionDiscounter.new(order).call @order.line_items.each do |item| - all_line_item_discounts = all_order_discounts.flat_map(&:line_item_discounts) - item_discounts = all_line_item_discounts.select { |element| element.item == item } - chosen_item_discounts = SolidusFriendlyPromotions.config.discount_chooser_class.new(item).call(item_discounts) + chosen_item_discounts = SolidusFriendlyPromotions.config.discount_chooser_class.new(item).call(item.discounts) update_adjustments(item, chosen_item_discounts) end @order.shipments.each do |item| - all_shipment_discounts = all_order_discounts.flat_map(&:shipment_discounts) - item_discounts = all_shipment_discounts.select { |element| element.item == item } - chosen_item_discounts = SolidusFriendlyPromotions.config.discount_chooser_class.new(item).call(item_discounts) + chosen_item_discounts = SolidusFriendlyPromotions.config.discount_chooser_class.new(item).call(item.discounts) update_adjustments(item, chosen_item_discounts) end @order.shipments.flat_map(&:shipping_rates).each do |item| - all_item_discounts = all_order_discounts.flat_map(&:shipping_rate_discounts) - item_discounts = all_item_discounts.select { |element| element.item == item } - chosen_item_discounts = SolidusFriendlyPromotions.config.discount_chooser_class.new(item).call(item_discounts) + chosen_item_discounts = SolidusFriendlyPromotions.config.discount_chooser_class.new(item).call(item.discounts) item.discounts = chosen_item_discounts.map do |discount| SolidusFriendlyPromotions::ShippingRateDiscount.new( - shipping_rate: item, + shipping_rate: item.shipping_rate, amount: discount.amount, label: discount.label ) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb index 27eb152b3b2..6f4998ceba7 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb @@ -27,7 +27,7 @@ def can_discount?(object) end def discount(adjustable) - ItemDiscount.new( + adjustable.discounts << ItemDiscount.new( item: adjustable, label: adjustment_label(adjustable), amount: compute_amount(adjustable), diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_order.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_order.rb index 65ccd9921de..9d6f81f9e99 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_order.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_order.rb @@ -6,7 +6,7 @@ class FirstOrder < PromotionRule attr_reader :user, :email def applicable?(promotable) - promotable.is_a?(Spree::Order) + promotable.is_a?(Discountable::Order) end def eligible?(order, options = {}) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb index dfca964597d..2f64e45448a 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb @@ -8,7 +8,7 @@ class FirstRepeatPurchaseSince < PromotionRule # This promotion is applicable to orders only. def applicable?(promotable) - promotable.is_a?(Spree::Order) + promotable.is_a?(Discountable::Order) end # This is never eligible if the order does not have a user, and that user does not have any previous completed orders. diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb index 12df85239e9..55fc42ec782 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb @@ -27,7 +27,7 @@ def self.operator_options end def applicable?(promotable) - promotable.is_a?(Spree::Order) + promotable.is_a?(Discountable::Order) end def eligible?(order, _options = {}) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_option_value.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_option_value.rb index b8252820c6e..a914702a4d9 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_option_value.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_option_value.rb @@ -6,7 +6,7 @@ class LineItemOptionValue < PromotionRule preference :eligible_values, :hash def applicable?(promotable) - promotable.is_a?(Spree::LineItem) + promotable.is_a?(Discountable::LineItem) end def eligible?(line_item, _options = {}) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb index b24e8e55a38..fb16c2fd645 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb @@ -17,7 +17,7 @@ class LineItemProduct < PromotionRule preference :match_policy, :string, default: MATCH_POLICIES.first def applicable?(promotable) - promotable.is_a?(Spree::LineItem) + promotable.is_a?(Discountable::LineItem) end def eligible?(line_item, _options = {}) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb index e0fd27b693f..204a12e220c 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb @@ -13,7 +13,7 @@ class LineItemTaxon < PromotionRule preference :match_policy, :string, default: MATCH_POLICIES.first def applicable?(promotable) - promotable.is_a?(Spree::LineItem) + promotable.is_a?(Discountable::LineItem) end def eligible?(line_item, _options = {}) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb index 04cb592bd6c..041cbc538ba 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb @@ -10,7 +10,7 @@ class NthOrder < PromotionRule # This promotion is applicable to orders only. def applicable?(promotable) - promotable.is_a?(Spree::Order) + promotable.is_a?(Discountable::Order) end # This is never eligible if the order does not have a user, and that user does not have any previous completed orders. diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb index 5c9d13a89ef..8393c1ffddf 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb @@ -4,7 +4,7 @@ module SolidusFriendlyPromotions module Rules class OneUsePerUser < PromotionRule def applicable?(promotable) - promotable.is_a?(Spree::Order) + promotable.is_a?(Discountable::Order) end def eligible?(order, _options = {}) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/option_value.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/option_value.rb index f8b25aa040e..9cd76948052 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/option_value.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/option_value.rb @@ -6,7 +6,7 @@ class OptionValue < PromotionRule preference :eligible_values, :hash def applicable?(promotable) - promotable.is_a?(Spree::Order) + promotable.is_a?(Discountable::Order) end def eligible?(order, _options = {}) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb index c7a5d83732e..02fadb6945d 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb @@ -29,7 +29,7 @@ def eligible_products end def applicable?(promotable) - promotable.is_a?(Spree::Order) + promotable.is_a?(Discountable::Order) end def eligible?(order, _options = {}) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/shipping_method.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/shipping_method.rb index eb745f7605e..dce6d5afad8 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/shipping_method.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/shipping_method.rb @@ -6,7 +6,7 @@ class ShippingMethod < PromotionRule preference :shipping_method_ids, type: :array, default: [] def applicable?(promotable) - promotable.is_a?(Spree::Shipment) || promotable.is_a?(Spree::ShippingRate) + promotable.is_a?(Discountable::Shipment) || promotable.is_a?(Discountable::ShippingRate) end def eligible?(promotable) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb index 3c08784975c..182e9f4928c 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb @@ -13,7 +13,7 @@ def preload_relations end def applicable?(promotable) - promotable.is_a?(Spree::Order) + promotable.is_a?(Discountable::Order) end def eligible?(order, _options = {}) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb index 9130cadcfe2..9f20c285791 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb @@ -17,7 +17,7 @@ def preload_relations preference :match_policy, :string, default: MATCH_POLICIES.first def applicable?(promotable) - promotable.is_a?(Spree::Order) + promotable.is_a?(Discountable::Order) end def eligible?(order, _options = {}) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb index 1cb7e9fd9da..7c5a170f88c 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb @@ -14,7 +14,7 @@ def preload_relations end def applicable?(promotable) - promotable.is_a?(Spree::Order) + promotable.is_a?(Discountable::Order) end def eligible?(order, _options = {}) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb index 5be6e2ea802..a866884b841 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb @@ -4,7 +4,7 @@ module SolidusFriendlyPromotions module Rules class UserLoggedIn < PromotionRule def applicable?(promotable) - promotable.is_a?(Spree::Order) + promotable.is_a?(Discountable::Order) end def eligible?(order, _options = {}) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_role.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_role.rb index e8df12f71ba..08354c58daf 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_role.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_role.rb @@ -9,7 +9,7 @@ class UserRole < PromotionRule preference :match_policy, default: MATCH_POLICIES.first def applicable?(promotable) - promotable.is_a?(Spree::Order) + promotable.is_a?(Discountable::Order) end def eligible?(order, _options = {}) diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_shipment_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_shipment_spec.rb index 7fc0206ea49..887349b32f1 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_shipment_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_shipment_spec.rb @@ -15,19 +15,19 @@ subject { action.can_discount?(promotable) } context "with a line item" do - let(:promotable) { Spree::LineItem.new } + let(:promotable) { SolidusFriendlyPromotions::Discountable::LineItem.new(Spree::Order.new, order: double) } it { is_expected.to be false } end context "with a shipment" do - let(:promotable) { Spree::Shipment.new } + let(:promotable) { SolidusFriendlyPromotions::Discountable::Shipment.new(Spree::Shipment.new, order: double) } it { is_expected.to be true } end context "with a shipping rate" do - let(:promotable) { Spree::ShippingRate.new } + let(:promotable) { SolidusFriendlyPromotions::Discountable::ShippingRate.new(Spree::ShippingRate.new, shipment: double) } it { is_expected.to be true } end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_action_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_action_spec.rb index 20f580a0331..93b1cf90cbb 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_action_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_action_spec.rb @@ -18,11 +18,12 @@ end describe "#discount" do - subject { action.discount(adjustable) } + subject { action.discount(discountable) } let(:variant) { create(:variant) } let(:order) { create(:order) } - let(:adjustable) { Spree::LineItem.new(order: order, variant: variant, price: 10) } + let(:discountable) { SolidusFriendlyPromotions::Discountable::LineItem.new(line_item, order: SolidusFriendlyPromotions::Discountable::Order.new(order)) } + let(:line_item) { Spree::LineItem.new(order: order, variant: variant, price: 10) } let(:promotion) { SolidusFriendlyPromotions::Promotion.new(name: "20 Perzent off") } let(:action) { described_class.new(promotion: promotion) } @@ -30,15 +31,15 @@ allow(action).to receive(:compute_amount).and_return(-1) end - it "returs an discount to the adjustable" do - expect(subject).to eq( + it "adds a discount to the discountable" do + expect { subject }.to change { discountable.discounts }.from([]).to([ SolidusFriendlyPromotions::ItemDiscount.new( - item: adjustable, + item: discountable, label: "Promotion (20 Perzent off)", source: action, amount: -1 ) - ) + ]) end end end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_repeat_purchase_since_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_repeat_purchase_since_spec.rb index e23fd17e3a3..8edd838e559 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_repeat_purchase_since_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_repeat_purchase_since_spec.rb @@ -7,13 +7,13 @@ subject { described_class.new.applicable?(promotable) } context "when the promotable is an order" do - let(:promotable) { Spree::Order.new } + let(:promotable) { SolidusFriendlyPromotions::Discountable::Order.new(Spree::Order.new) } it { is_expected.to be true } end context "when the promotable is not a order" do - let(:promotable) { Spree::LineItem.new } + let(:promotable) { SolidusFriendlyPromotions::Discountable::LineItem.new(Spree::LineItem.new, order: double) } it { is_expected.to be false } end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/nth_order_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/nth_order_spec.rb index d2464d35286..a85d4feccc1 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/nth_order_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/nth_order_spec.rb @@ -7,7 +7,7 @@ subject { described_class.new.applicable?(promotable) } context "when the promotable is an order" do - let(:promotable) { Spree::Order.new } + let(:promotable) { SolidusFriendlyPromotions::Discountable::Order.new(Spree::Order.new) } it { is_expected.to be true } end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/option_value_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/option_value_spec.rb index 519309378d5..4e29b4c16ce 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/option_value_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/option_value_spec.rb @@ -18,13 +18,13 @@ subject { rule.applicable?(promotable) } context "when promotable is an order" do - let(:promotable) { Spree::Order.new } + let(:promotable) { SolidusFriendlyPromotions::Discountable::Order.new(Spree::Order.new) } it { is_expected.to be true } end context "when promotable is not an order" do - let(:promotable) { Spree::LineItem.new } + let(:promotable) { SolidusFriendlyPromotions::Discountable::LineItem.new(Spree::LineItem.new, order: double) } it { is_expected.to be false } end From 889fd135abcec5fa8ae245ede81a31280ce6ca31 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 29 Sep 2023 13:45:18 +0200 Subject: [PATCH 339/834] Remove unused OrderDiscounts class The responsibilities for this class are now in the Discountable::* classes. --- .../order_discounts.rb | 18 ------------------ .../order_discounts_spec.rb | 8 -------- 2 files changed, 26 deletions(-) delete mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/order_discounts.rb delete mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/order_discounts_spec.rb diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/order_discounts.rb b/friendly_promotions/app/models/solidus_friendly_promotions/order_discounts.rb deleted file mode 100644 index df340659703..00000000000 --- a/friendly_promotions/app/models/solidus_friendly_promotions/order_discounts.rb +++ /dev/null @@ -1,18 +0,0 @@ -# frozen_string_literal: true - -module SolidusFriendlyPromotions - # Simple object to pass back discount data from a promoter. - # - # Will be used by {SolidusFriendlyPromotions::OrderDiscounter} to create or update promotion - # adjustments on an order. - # - # @attr_reader [Integer] order_id the {Spree::Order} these discounts apply to - # @attr_reader [Array] line_item_discounts an array of - # discount data for order's line items - # @attr_reader [Array] shipment_discounts an array of - # discount data for the order's shipments - class OrderDiscounts - include ActiveModel::Model - attr_accessor :order_id, :line_item_discounts, :shipment_discounts, :shipping_rate_discounts - end -end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/order_discounts_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/order_discounts_spec.rb deleted file mode 100644 index 9fe907d0f5a..00000000000 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/order_discounts_spec.rb +++ /dev/null @@ -1,8 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe SolidusFriendlyPromotions::OrderDiscounts do - it { is_expected.to respond_to :order_id } - it { is_expected.to respond_to :line_item_discounts } - it { is_expected.to respond_to :shipment_discounts } - it { is_expected.to respond_to :shipping_rate_discounts } -end From e8f83194c44c11aa4284032b0a8db6b33c20f6d2 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 29 Sep 2023 13:56:18 +0200 Subject: [PATCH 340/834] Apply promotions by lane --- .../friendly_promotion_discounter.rb | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb b/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb index db0310b3717..ac743555828 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb @@ -2,36 +2,40 @@ module SolidusFriendlyPromotions class FriendlyPromotionDiscounter - attr_reader :order, :promotions, :item_discounter + attr_reader :order, :promotions def initialize(order) @order = Discountable::Order.new(order) @promotions = PromotionEligibility.new(promotable: order, possible_promotions: possible_promotions).call - @item_discounter = ItemDiscounter.new(promotions: promotions) end def call return nil if order.shipped? - adjust_line_items - adjust_shipments - adjust_shipping_rates + SolidusFriendlyPromotions::Promotion.ordered_lanes.each do |lane, _index| + lane_promotions = promotions.select { |promotion| promotion.lane == lane } + item_discounter = ItemDiscounter.new(promotions: lane_promotions) + adjust_line_items(item_discounter) + adjust_shipments(item_discounter) + adjust_shipping_rates(item_discounter) + end + order end private - def adjust_line_items + def adjust_line_items(item_discounter) order.line_items.select do |line_item| line_item.variant.product.promotionable? end.flat_map { |line_item| item_discounter.call(line_item) } end - def adjust_shipments + def adjust_shipments(item_discounter) order.shipments.flat_map { |shipment| item_discounter.call(shipment) } end - def adjust_shipping_rates + def adjust_shipping_rates(item_discounter) order.shipments.flat_map(&:shipping_rates).flat_map { |rate| item_discounter.call(rate) } end From 2e3458054c9449d4b257ced8b2279ed4b8f8e603 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 29 Sep 2023 14:00:58 +0200 Subject: [PATCH 341/834] Add item discounts in Promotion Discounter We want to run the promotion chooser on all discounts eligible for an item by lane. For that we need a collection of discounts for that item on that lane. This separates creating the discount and adding it, giving us space to do the selection. --- .../friendly_promotion_discounter.rb | 15 ++++++++++++--- .../promotion_action.rb | 2 +- .../promotion_action_spec.rb | 6 +++--- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb b/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb index ac743555828..98a1676d639 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb @@ -28,15 +28,24 @@ def call def adjust_line_items(item_discounter) order.line_items.select do |line_item| line_item.variant.product.promotionable? - end.flat_map { |line_item| item_discounter.call(line_item) } + end.flat_map do |line_item| + discounts = item_discounter.call(line_item) + line_item.discounts.concat(discounts) + end end def adjust_shipments(item_discounter) - order.shipments.flat_map { |shipment| item_discounter.call(shipment) } + order.shipments.flat_map do |shipment| + discounts = item_discounter.call(shipment) + shipment.discounts.concat(discounts) + end end def adjust_shipping_rates(item_discounter) - order.shipments.flat_map(&:shipping_rates).flat_map { |rate| item_discounter.call(rate) } + order.shipments.flat_map(&:shipping_rates).flat_map do |rate| + discounts = item_discounter.call(rate) + rate.discounts.concat(discounts) + end end def possible_promotions diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb index 6f4998ceba7..27eb152b3b2 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb @@ -27,7 +27,7 @@ def can_discount?(object) end def discount(adjustable) - adjustable.discounts << ItemDiscount.new( + ItemDiscount.new( item: adjustable, label: adjustment_label(adjustable), amount: compute_amount(adjustable), diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_action_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_action_spec.rb index 93b1cf90cbb..940f806027a 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_action_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_action_spec.rb @@ -31,15 +31,15 @@ allow(action).to receive(:compute_amount).and_return(-1) end - it "adds a discount to the discountable" do - expect { subject }.to change { discountable.discounts }.from([]).to([ + it "returns an discount to the discountable" do + expect(subject).to eq( SolidusFriendlyPromotions::ItemDiscount.new( item: discountable, label: "Promotion (20 Perzent off)", source: action, amount: -1 ) - ]) + ) end end end From b7659b7e844c7c3a4f26e8ac58a58309076f2dfb Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 29 Sep 2023 14:09:21 +0200 Subject: [PATCH 342/834] Move discount choosing to FriendlyPromotionDiscounter We need the discount choosing to happen per lane, and that can't happen in the OrderDiscounter. --- .../friendly_promotion_discounter.rb | 9 ++++++--- .../solidus_friendly_promotions/order_discounter.rb | 13 +++++-------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb b/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb index 98a1676d639..07a062969bd 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb @@ -30,21 +30,24 @@ def adjust_line_items(item_discounter) line_item.variant.product.promotionable? end.flat_map do |line_item| discounts = item_discounter.call(line_item) - line_item.discounts.concat(discounts) + chosen_item_discounts = SolidusFriendlyPromotions.config.discount_chooser_class.new(line_item).call(discounts) + line_item.discounts.concat(chosen_item_discounts) end end def adjust_shipments(item_discounter) order.shipments.flat_map do |shipment| discounts = item_discounter.call(shipment) - shipment.discounts.concat(discounts) + chosen_item_discounts = SolidusFriendlyPromotions.config.discount_chooser_class.new(shipment).call(discounts) + shipment.discounts.concat(chosen_item_discounts) end end def adjust_shipping_rates(item_discounter) order.shipments.flat_map(&:shipping_rates).flat_map do |rate| discounts = item_discounter.call(rate) - rate.discounts.concat(discounts) + chosen_item_discounts = SolidusFriendlyPromotions.config.discount_chooser_class.new(rate).call(discounts) + rate.discounts.concat(chosen_item_discounts) end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb b/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb index 7e60bf031e2..cdb8734227a 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb @@ -10,20 +10,17 @@ def call @order = FriendlyPromotionDiscounter.new(order).call @order.line_items.each do |item| - chosen_item_discounts = SolidusFriendlyPromotions.config.discount_chooser_class.new(item).call(item.discounts) - update_adjustments(item, chosen_item_discounts) + update_adjustments(item.line_item, item.discounts) end @order.shipments.each do |item| - chosen_item_discounts = SolidusFriendlyPromotions.config.discount_chooser_class.new(item).call(item.discounts) - update_adjustments(item, chosen_item_discounts) + update_adjustments(item.shipment, item.discounts) end - @order.shipments.flat_map(&:shipping_rates).each do |item| - chosen_item_discounts = SolidusFriendlyPromotions.config.discount_chooser_class.new(item).call(item.discounts) - item.discounts = chosen_item_discounts.map do |discount| + @order.shipments.flat_map(&:shipping_rates).each do |shipping_rate| + shipping_rate.shipping_rate.discounts = shipping_rate.discounts.map do |discount| SolidusFriendlyPromotions::ShippingRateDiscount.new( - shipping_rate: item.shipping_rate, + shipping_rate: shipping_rate.shipping_rate, amount: discount.amount, label: discount.label ) From a5e7f81cb34d07de6008883ebce14a9b99f5287d Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 29 Sep 2023 14:14:51 +0200 Subject: [PATCH 343/834] Refactor order discounter This has no functional changes, but it renames some variables in this class so things don't seem as confusing. --- .../order_discounter.rb | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb b/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb index cdb8734227a..d0cfb2046cc 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb @@ -7,28 +7,29 @@ def initialize(order) end def call - @order = FriendlyPromotionDiscounter.new(order).call + discountable_order = FriendlyPromotionDiscounter.new(order).call - @order.line_items.each do |item| - update_adjustments(item.line_item, item.discounts) + discountable_order.line_items.each do |discountable_line_item| + update_adjustments(discountable_line_item.line_item, discountable_line_item.discounts) end - @order.shipments.each do |item| - update_adjustments(item.shipment, item.discounts) + discountable_order.shipments.each do |discountable_shipment| + update_adjustments(discountable_shipment.shipment, discountable_shipment.discounts) end - @order.shipments.flat_map(&:shipping_rates).each do |shipping_rate| - shipping_rate.shipping_rate.discounts = shipping_rate.discounts.map do |discount| + discountable_order.shipments.flat_map(&:shipping_rates).each do |discountable_shipping_rate| + spree_shipping_rate = discountable_shipping_rate.shipping_rate + spree_shipping_rate.discounts = discountable_shipping_rate.discounts.map do |discount| SolidusFriendlyPromotions::ShippingRateDiscount.new( - shipping_rate: shipping_rate.shipping_rate, + shipping_rate: spree_shipping_rate, amount: discount.amount, label: discount.label ) end end - @order.promo_total = (order.line_items + order.shipments).sum(&:promo_total) - @order + order.promo_total = (order.line_items + order.shipments).sum(&:promo_total) + order end private From 6f81c89560c6b89f670772b1cba72a29e9069e0a Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 29 Sep 2023 14:26:17 +0200 Subject: [PATCH 344/834] Remove unused "discounters" configuration option This was used in the order discounter anticipating features we don't have. --- .../install/templates/initializer.rb | 4 ---- .../lib/solidus_friendly_promotions/configuration.rb | 1 - 2 files changed, 5 deletions(-) diff --git a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb index 75343fc0892..95a31d53772 100644 --- a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb +++ b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb @@ -51,10 +51,6 @@ # How many promotions should be displayed on the index page in the admin. config.promotions_per_page = 25 - config.discounters = [ - "SolidusFriendlyPromotions::FriendlyPromotionDiscounter" - ] - config.shipment_discount_calculators = [ "SolidusFriendlyPromotions::Calculators::FlatRate", "SolidusFriendlyPromotions::Calculators::FlexiRate", diff --git a/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb b/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb index 26922dd1b64..7ec7bd16c92 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb @@ -14,7 +14,6 @@ class Configuration < Spree::Preferences::Configuration add_class_set :shipment_rules add_class_set :actions - add_class_set :discounters class_name_attribute :discount_chooser_class, default: "SolidusFriendlyPromotions::DiscountChooser" class_name_attribute :promotion_code_batch_mailer_class, From 69509fdd944baeef0ee3799b400e20bf4ccdedf6 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 29 Sep 2023 15:05:04 +0200 Subject: [PATCH 345/834] Add discounts to items after calculation Otherwise we get discounted amounts from discounts from the current lane (which we don't want). --- .../friendly_promotion_discounter.rb | 21 +++++++++++-------- .../spec/models/promotion/integration_spec.rb | 2 +- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb b/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb index 07a062969bd..e3945e1eefc 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb @@ -15,9 +15,12 @@ def call SolidusFriendlyPromotions::Promotion.ordered_lanes.each do |lane, _index| lane_promotions = promotions.select { |promotion| promotion.lane == lane } item_discounter = ItemDiscounter.new(promotions: lane_promotions) - adjust_line_items(item_discounter) - adjust_shipments(item_discounter) - adjust_shipping_rates(item_discounter) + line_item_discounts = adjust_line_items(item_discounter) + shipment_discounts = adjust_shipments(item_discounter) + shipping_rate_discounts = adjust_shipping_rates(item_discounter) + (line_item_discounts + shipment_discounts + shipping_rate_discounts).each do |item, chosen_discounts| + item.discounts.concat(chosen_discounts) + end end order @@ -28,26 +31,26 @@ def call def adjust_line_items(item_discounter) order.line_items.select do |line_item| line_item.variant.product.promotionable? - end.flat_map do |line_item| + end.map do |line_item| discounts = item_discounter.call(line_item) chosen_item_discounts = SolidusFriendlyPromotions.config.discount_chooser_class.new(line_item).call(discounts) - line_item.discounts.concat(chosen_item_discounts) + [line_item, chosen_item_discounts] end end def adjust_shipments(item_discounter) - order.shipments.flat_map do |shipment| + order.shipments.map do |shipment| discounts = item_discounter.call(shipment) chosen_item_discounts = SolidusFriendlyPromotions.config.discount_chooser_class.new(shipment).call(discounts) - shipment.discounts.concat(chosen_item_discounts) + [shipment, chosen_item_discounts] end end def adjust_shipping_rates(item_discounter) - order.shipments.flat_map(&:shipping_rates).flat_map do |rate| + order.shipments.flat_map(&:shipping_rates).map do |rate| discounts = item_discounter.call(rate) chosen_item_discounts = SolidusFriendlyPromotions.config.discount_chooser_class.new(rate).call(discounts) - rate.discounts.concat(chosen_item_discounts) + [rate, chosen_item_discounts] end end diff --git a/friendly_promotions/spec/models/promotion/integration_spec.rb b/friendly_promotions/spec/models/promotion/integration_spec.rb index 5d9d1ab1115..b51dc38d034 100644 --- a/friendly_promotions/spec/models/promotion/integration_spec.rb +++ b/friendly_promotions/spec/models/promotion/integration_spec.rb @@ -51,7 +51,7 @@ end end - context "with two promotions that should stack", :pending do + context "with two promotions that should stack" do let(:shirt) { create(:product, name: "Shirt", price: 30) } let(:pants) { create(:product, name: "Pants", price: 40) } From ebcebcc75662c3553a2fe3de4e7b905367c938ed Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 29 Sep 2023 14:48:47 +0200 Subject: [PATCH 346/834] Change calculators to use #discountable_amount This change allows use to actually use the "lanes" feature in our calculators. --- .../solidus_friendly_promotions/calculators/percent.rb | 2 +- .../calculators/tiered_flat_rate.rb | 2 +- .../solidus_friendly_promotions/discountable/line_item.rb | 2 +- .../solidus_friendly_promotions/discountable/shipment.rb | 2 +- .../discountable/shipping_rate.rb | 2 +- .../distributed_amounts_handler.rb | 2 +- .../models/solidus_friendly_promotions/promotion_action.rb | 2 +- .../calculators/distributed_amount_spec.rb | 3 ++- .../solidus_friendly_promotions/calculators/percent_spec.rb | 2 +- .../calculators/tiered_flat_rate_spec.rb | 5 +++-- .../discountable/line_item_spec.rb | 4 ++-- .../discountable/shipment_spec.rb | 4 ++-- .../discountable/shipping_rate_spec.rb | 4 ++-- .../distributed_amounts_handler_spec.rb | 6 +++++- 14 files changed, 24 insertions(+), 18 deletions(-) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/percent.rb b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/percent.rb index 8864904f254..50d51225450 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/percent.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/percent.rb @@ -10,7 +10,7 @@ class Percent < Spree::Calculator def compute(object) preferred_currency = object.order.currency currency_exponent = ::Money::Currency.find(preferred_currency).exponent - (object.amount * preferred_percent / 100).round(currency_exponent) + (object.discountable_amount * preferred_percent / 100).round(currency_exponent) end end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_flat_rate.rb b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_flat_rate.rb index 1fda3e91a05..f6cda6cc226 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_flat_rate.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_flat_rate.rb @@ -22,7 +22,7 @@ class TieredFlatRate < Spree::Calculator def compute_item(object) _base, amount = preferred_tiers.sort.reverse.detect do |value, _| - object.amount >= value + object.discountable_amount >= value end if preferred_currency.casecmp(object.currency).zero? diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/discountable/line_item.rb b/friendly_promotions/app/models/solidus_friendly_promotions/discountable/line_item.rb index d196c55386a..f732710d534 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/discountable/line_item.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/discountable/line_item.rb @@ -15,7 +15,7 @@ def line_item __getobj__ end - def discounted_amount + def discountable_amount amount + discounts.sum(&:amount) end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/discountable/shipment.rb b/friendly_promotions/app/models/solidus_friendly_promotions/discountable/shipment.rb index e84a22a787b..e16c5a96dc7 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/discountable/shipment.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/discountable/shipment.rb @@ -16,7 +16,7 @@ def shipment __getobj__ end - def discounted_amount + def discountable_amount amount + discounts.sum(&:amount) end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/discountable/shipping_rate.rb b/friendly_promotions/app/models/solidus_friendly_promotions/discountable/shipping_rate.rb index bc938e696e3..77a6ca46ea6 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/discountable/shipping_rate.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/discountable/shipping_rate.rb @@ -15,7 +15,7 @@ def shipping_rate __getobj__ end - def discounted_amount + def discountable_amount amount + discounts.sum(&:amount) end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/distributed_amounts_handler.rb b/friendly_promotions/app/models/solidus_friendly_promotions/distributed_amounts_handler.rb index 6e0c0630c0d..485b8b22574 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/distributed_amounts_handler.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/distributed_amounts_handler.rb @@ -29,7 +29,7 @@ def line_item_ids end def elligible_amounts - line_items.map(&:amount) + line_items.map(&:discountable_amount) end def subtotal diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb index 27eb152b3b2..9ce0c9df2d9 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb @@ -38,7 +38,7 @@ def discount(adjustable) # Ensure a negative amount which does not exceed the object's amount def compute_amount(adjustable) promotion_amount = calculator.compute(adjustable) || BigDecimal("0") - [adjustable.amount, promotion_amount.abs].min * -1 + [adjustable.discountable_amount, promotion_amount.abs].min * -1 end def adjustment_label(adjustable) diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/distributed_amount_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/distributed_amount_spec.rb index 65aca311d33..1f4cc9200b0 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/distributed_amount_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/distributed_amount_spec.rb @@ -10,7 +10,8 @@ end let(:rules) { [] } let(:action) { SolidusFriendlyPromotions::Actions::AdjustLineItem.create(calculator: calculator) } - let(:order) { create(:order_with_line_items, line_items_attributes: line_items_attributes) } + let(:spree_order) { create(:order_with_line_items, line_items_attributes: line_items_attributes) } + let(:order) { SolidusFriendlyPromotions::Discountable::Order.new(spree_order) } let(:currency) { "USD" } context "applied to an order" do diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/percent_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/percent_spec.rb index f71734952e3..fe98125d7dc 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/percent_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/percent_spec.rb @@ -7,7 +7,7 @@ context "compute" do let(:currency) { "USD" } let(:order) { double(currency: currency) } - let(:line_item) { double("LineItem", amount: 100, order: order) } + let(:line_item) { double("SolidusFriendlyPromotions::Discountable::LineItem", discountable_amount: 100, order: order) } before { subject.preferred_percent = 15 } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_flat_rate_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_flat_rate_spec.rb index ee438e6c34c..490dac04247 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_flat_rate_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_flat_rate_spec.rb @@ -75,7 +75,7 @@ line_items_price: amount ) end - let(:line_item) { order.line_items.first } + let(:line_item) { SolidusFriendlyPromotions::Discountable::LineItem.new(order.line_items.first, order: order) } let(:preferred_currency) { "USD" } before do @@ -109,7 +109,8 @@ context "with a shipment" do subject { calculator.compute(shipment) } - let(:shipment) { Spree::Shipment.new(order: order, amount: shipping_cost) } + let(:spree_shipment) { Spree::Shipment.new(order: order, amount: shipping_cost) } + let(:shipment) { SolidusFriendlyPromotions::Discountable::Shipment.new(spree_shipment, order: order) } let(:line_item_count) { 1 } let(:amount) { 10 } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/line_item_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/line_item_spec.rb index 2778f52243d..c107b8211fa 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/line_item_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/line_item_spec.rb @@ -14,8 +14,8 @@ it { is_expected.to eq(discountable_order) } end - describe "#discounted_amount" do - subject(:discounted_amount) { discountable_line_item.discounted_amount } + describe "#discountable_amount" do + subject(:discountable_amount) { discountable_line_item.discountable_amount } context "with no discounts" do it { is_expected.to eq(20) } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/shipment_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/shipment_spec.rb index 3c963f7dbab..96e58f1fe1a 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/shipment_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/shipment_spec.rb @@ -15,8 +15,8 @@ it { is_expected.to eq(discountable_order) } end - describe "#discounted_amount" do - subject(:discounted_amount) { discountable_shipment.discounted_amount } + describe "#discountable_amount" do + subject(:discountable_amount) { discountable_shipment.discountable_amount } context "with no discounts" do it { is_expected.to eq(20) } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/shipping_rate_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/shipping_rate_spec.rb index 25cbaf6a747..f6c6d7781c3 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/shipping_rate_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/shipping_rate_spec.rb @@ -15,8 +15,8 @@ it { is_expected.to eq(discountable_shipment) } end - describe "#discounted_amount" do - subject(:discounted_amount) { discountable_shipping_rate.discounted_amount } + describe "#discountable_amount" do + subject(:discountable_amount) { discountable_shipping_rate.discountable_amount } context "with no discounts" do it { is_expected.to eq(20) } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/distributed_amounts_handler_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/distributed_amounts_handler_spec.rb index f3d244902c0..ee267f901c6 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/distributed_amounts_handler_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/distributed_amounts_handler_spec.rb @@ -3,13 +3,17 @@ require "spec_helper" RSpec.describe SolidusFriendlyPromotions::DistributedAmountsHandler, type: :model do - let(:order) do + let(:spree_order) do FactoryBot.create( :order_with_line_items, line_items_attributes: line_items_attributes ) end + let(:order) do + SolidusFriendlyPromotions::Discountable::Order.new(spree_order) + end + let(:handler) { described_class.new(order.line_items, total_amount) } From 7499b173ea6c46c55c5641b283e8cc8c808ab787 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Mon, 2 Oct 2023 15:10:00 +0200 Subject: [PATCH 347/834] Add Spree::{LineItem,Shipment,ShippingRate}#discountable_amount This little module allows us to remove the delegators in a future commit. They are quite clunky to work with. --- .../line_item_decorator.rb | 7 ++++ .../shipment_decorator.rb | 7 ++++ .../shipping_rate_decorator.rb | 5 ++- .../discountable_amount.rb | 21 +++++++++++ .../spec/models/spree/line_item_spec.rb | 37 +++++++++++++++++++ .../spec/models/spree/shipment_spec.rb | 24 ++++++++++++ .../spec/models/spree/shipping_rate_spec.rb | 32 ++++++++++++++++ 7 files changed, 131 insertions(+), 2 deletions(-) create mode 100644 friendly_promotions/app/decorators/models/solidus_friendly_promotions/line_item_decorator.rb create mode 100644 friendly_promotions/app/decorators/models/solidus_friendly_promotions/shipment_decorator.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/discountable_amount.rb create mode 100644 friendly_promotions/spec/models/spree/line_item_spec.rb create mode 100644 friendly_promotions/spec/models/spree/shipment_spec.rb diff --git a/friendly_promotions/app/decorators/models/solidus_friendly_promotions/line_item_decorator.rb b/friendly_promotions/app/decorators/models/solidus_friendly_promotions/line_item_decorator.rb new file mode 100644 index 00000000000..686d0686f48 --- /dev/null +++ b/friendly_promotions/app/decorators/models/solidus_friendly_promotions/line_item_decorator.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module LineItemDecorator + Spree::LineItem.prepend SolidusFriendlyPromotions::DiscountableAmount + end +end diff --git a/friendly_promotions/app/decorators/models/solidus_friendly_promotions/shipment_decorator.rb b/friendly_promotions/app/decorators/models/solidus_friendly_promotions/shipment_decorator.rb new file mode 100644 index 00000000000..8772f6d53ae --- /dev/null +++ b/friendly_promotions/app/decorators/models/solidus_friendly_promotions/shipment_decorator.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module ShipmentDecorator + Spree::Shipment.prepend SolidusFriendlyPromotions::DiscountableAmount + end +end diff --git a/friendly_promotions/app/decorators/models/solidus_friendly_promotions/shipping_rate_decorator.rb b/friendly_promotions/app/decorators/models/solidus_friendly_promotions/shipping_rate_decorator.rb index 66cf3cf5cbf..b39a4c40a64 100644 --- a/friendly_promotions/app/decorators/models/solidus_friendly_promotions/shipping_rate_decorator.rb +++ b/friendly_promotions/app/decorators/models/solidus_friendly_promotions/shipping_rate_decorator.rb @@ -22,7 +22,8 @@ def total_before_tax def promo_total discounts.sum(&:amount) end + + Spree::ShippingRate.prepend SolidusFriendlyPromotions::DiscountableAmount + Spree::ShippingRate.prepend self end end - -Spree::ShippingRate.prepend(SolidusFriendlyPromotions::ShippingRateDecorator) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/discountable_amount.rb b/friendly_promotions/app/models/solidus_friendly_promotions/discountable_amount.rb new file mode 100644 index 00000000000..e67e60d6ee2 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/discountable_amount.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module DiscountableAmount + def discountable_amount + amount + current_discounts.sum(&:amount) + end + + def current_discounts + @current_discounts ||= [] + end + + def current_discounts=(args) + @current_discounts = args + end + + def reset_current_discounts + @current_discounts = [] + end + end +end diff --git a/friendly_promotions/spec/models/spree/line_item_spec.rb b/friendly_promotions/spec/models/spree/line_item_spec.rb new file mode 100644 index 00000000000..44fc87336c9 --- /dev/null +++ b/friendly_promotions/spec/models/spree/line_item_spec.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe Spree::LineItem do + describe "#discountable_amount" do + let(:discounts) { [] } + let(:line_item) { Spree::LineItem.new(price: 10, quantity: 2, current_discounts: discounts) } + + subject(:discountable_amount) { line_item.discountable_amount } + + it { is_expected.to eq(20) } + + context "with a proposed discount" do + let(:discounts) do + [ + SolidusFriendlyPromotions::ItemDiscount.new(item: double, amount: -2, label: "Foo", source: double) + ] + end + + it { is_expected.to eq(18) } + end + end + + describe "#reset_current_discounts" do + let(:line_item) { Spree::LineItem.new } + + subject { line_item.reset_current_discounts } + before do + line_item.current_discounts << SolidusFriendlyPromotions::ItemDiscount.new(item: double, amount: -2, label: "Foo", source: double) + end + + it "resets the current discounts to an empty array" do + expect { subject }.to change { line_item.current_discounts.length }.from(1).to(0) + end + end +end diff --git a/friendly_promotions/spec/models/spree/shipment_spec.rb b/friendly_promotions/spec/models/spree/shipment_spec.rb new file mode 100644 index 00000000000..65133bd1c70 --- /dev/null +++ b/friendly_promotions/spec/models/spree/shipment_spec.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe Spree::Shipment do + describe "#discountable_amount" do + let(:discounts) { [] } + let(:shipment) { Spree::Shipment.new(amount: 20, current_discounts: discounts) } + + subject(:discountable_amount) { shipment.discountable_amount } + + it { is_expected.to eq(20) } + + context "with a proposed discount" do + let(:discounts) do + [ + SolidusFriendlyPromotions::ItemDiscount.new(item: double, amount: -2, label: "Foo", source: double) + ] + end + + it { is_expected.to eq(18) } + end + end +end diff --git a/friendly_promotions/spec/models/spree/shipping_rate_spec.rb b/friendly_promotions/spec/models/spree/shipping_rate_spec.rb index 05a770710eb..63a12db3d92 100644 --- a/friendly_promotions/spec/models/spree/shipping_rate_spec.rb +++ b/friendly_promotions/spec/models/spree/shipping_rate_spec.rb @@ -43,4 +43,36 @@ it { is_expected.to eq(Spree::Money.new("0")) } end + + describe "#discountable_amount" do + let(:discounts) { [] } + let(:shipping_rate) { Spree::ShippingRate.new(amount: 20, current_discounts: discounts) } + + subject(:discountable_amount) { shipping_rate.discountable_amount } + + it { is_expected.to eq(20) } + + context "with a proposed discount" do + let(:discounts) do + [ + SolidusFriendlyPromotions::ItemDiscount.new(item: double, amount: -2, label: "Foo", source: double) + ] + end + + it { is_expected.to eq(18) } + end + end + + describe "#reset_current_discounts" do + let(:shipping_rate) { Spree::ShippingRate.new } + + subject { shipping_rate.reset_current_discounts } + before do + shipping_rate.current_discounts << SolidusFriendlyPromotions::ItemDiscount.new(item: double, amount: -2, label: "Foo", source: double) + end + + it "resets the current discounts to an empty array" do + expect { subject }.to change { shipping_rate.current_discounts.length }.from(1).to(0) + end + end end From 06573c4b452af1e18f80818ed34ae19cd0a8eedb Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Mon, 2 Oct 2023 20:04:47 +0200 Subject: [PATCH 348/834] Provide API for resetting order discounts on order and shipment The order can be called to reset discounts on all leaf items. This is what does it. --- .../order_decorator.rb | 5 +++++ .../shipment_decorator.rb | 7 +++++++ .../spec/models/spree/order_spec.rb | 14 ++++++++++++++ .../spec/models/spree/shipment_spec.rb | 15 +++++++++++++++ 4 files changed, 41 insertions(+) diff --git a/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_decorator.rb b/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_decorator.rb index 23e13c39abc..a70817c8a4e 100644 --- a/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_decorator.rb +++ b/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_decorator.rb @@ -20,6 +20,11 @@ def ensure_promotions_eligible super end + def reset_current_discounts + line_items.each(&:reset_current_discounts) + shipments.each(&:reset_current_discounts) + end + Spree::Order.prepend self end end diff --git a/friendly_promotions/app/decorators/models/solidus_friendly_promotions/shipment_decorator.rb b/friendly_promotions/app/decorators/models/solidus_friendly_promotions/shipment_decorator.rb index 8772f6d53ae..cca74442c8b 100644 --- a/friendly_promotions/app/decorators/models/solidus_friendly_promotions/shipment_decorator.rb +++ b/friendly_promotions/app/decorators/models/solidus_friendly_promotions/shipment_decorator.rb @@ -3,5 +3,12 @@ module SolidusFriendlyPromotions module ShipmentDecorator Spree::Shipment.prepend SolidusFriendlyPromotions::DiscountableAmount + + def reset_current_discounts + super + shipping_rates.each(&:reset_current_discounts) + end + + Spree::Shipment.prepend self end end diff --git a/friendly_promotions/spec/models/spree/order_spec.rb b/friendly_promotions/spec/models/spree/order_spec.rb index 51bfa5af2b9..15ee8d76024 100644 --- a/friendly_promotions/spec/models/spree/order_spec.rb +++ b/friendly_promotions/spec/models/spree/order_spec.rb @@ -5,4 +5,18 @@ RSpec.describe Spree::Order do it { is_expected.to have_many :friendly_promotions } it { is_expected.to have_many :friendly_order_promotions } + + describe "#reset_current_discounts" do + let(:line_item) { Spree::LineItem.new } + let(:shipment) { Spree::Shipment.new } + let(:order) { Spree::Order.new(shipments: [shipment], line_items: [line_item]) } + + subject { order.reset_current_discounts } + + it "resets the current discounts on all line items and shipments" do + expect(line_item).to receive(:reset_current_discounts) + expect(shipment).to receive(:reset_current_discounts) + subject + end + end end diff --git a/friendly_promotions/spec/models/spree/shipment_spec.rb b/friendly_promotions/spec/models/spree/shipment_spec.rb index 65133bd1c70..06a2f3df8f8 100644 --- a/friendly_promotions/spec/models/spree/shipment_spec.rb +++ b/friendly_promotions/spec/models/spree/shipment_spec.rb @@ -20,5 +20,20 @@ it { is_expected.to eq(18) } end + + describe "#reset_current_discounts" do + let(:shipping_rate) { Spree::ShippingRate.new } + let(:shipment) { Spree::Shipment.new(shipping_rates: [shipping_rate]) } + + subject { shipment.reset_current_discounts } + before do + shipment.current_discounts << SolidusFriendlyPromotions::ItemDiscount.new(item: double, amount: -2, label: "Foo", source: double) + end + + it "resets the current discounts to an empty array and resets current discounts on all shipping rates" do + expect(shipping_rate).to receive(:reset_current_discounts) + expect { subject }.to change { shipment.current_discounts.length }.from(1).to(0) + end + end end end From 038f918d27ef17e300ec7e051370dcd3404c7e9d Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Mon, 2 Oct 2023 20:05:31 +0200 Subject: [PATCH 349/834] Use Spree::{Order;LineItem;ShippingRate} in all models It's less change to all rules and actions if we just use the core Solidus models to store discounts. --- .../actions/adjust_line_item.rb | 2 +- .../actions/adjust_shipment.rb | 2 +- .../friendly_promotion_discounter.rb | 6 +++-- .../order_discounter.rb | 24 +++++++++---------- .../rules/first_order.rb | 2 +- .../rules/first_repeat_purchase_since.rb | 2 +- .../rules/item_total.rb | 2 +- .../rules/line_item_option_value.rb | 2 +- .../rules/line_item_product.rb | 2 +- .../rules/line_item_taxon.rb | 2 +- .../rules/nth_order.rb | 2 +- .../rules/one_use_per_user.rb | 2 +- .../rules/option_value.rb | 2 +- .../rules/product.rb | 2 +- .../rules/shipping_method.rb | 2 +- .../rules/store.rb | 2 +- .../rules/taxon.rb | 2 +- .../solidus_friendly_promotions/rules/user.rb | 2 +- .../rules/user_logged_in.rb | 2 +- .../rules/user_role.rb | 2 +- .../actions/adjust_shipment_spec.rb | 6 ++--- .../calculators/distributed_amount_spec.rb | 3 +-- .../calculators/percent_spec.rb | 2 +- .../calculators/tiered_flat_rate_spec.rb | 5 ++-- .../distributed_amounts_handler_spec.rb | 6 +---- .../promotion_action_spec.rb | 3 +-- .../rules/first_repeat_purchase_since_spec.rb | 4 ++-- .../rules/nth_order_spec.rb | 2 +- .../rules/option_value_spec.rb | 4 ++-- 29 files changed, 48 insertions(+), 53 deletions(-) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb index 0e2bc9ce9ba..394d0b660cd 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb @@ -4,7 +4,7 @@ module SolidusFriendlyPromotions module Actions class AdjustLineItem < PromotionAction def can_discount?(object) - object.is_a? Discountable::LineItem + object.is_a? Spree::LineItem end def available_calculators diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb index d52354ab0d2..82d73533943 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb @@ -4,7 +4,7 @@ module SolidusFriendlyPromotions module Actions class AdjustShipment < PromotionAction def can_discount?(object) - object.is_a?(Discountable::Shipment) || object.is_a?(Discountable::ShippingRate) + object.is_a?(Spree::Shipment) || object.is_a?(Spree::ShippingRate) end def available_calculators diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb b/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb index e3945e1eefc..9c1d7b4e978 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb @@ -5,13 +5,15 @@ class FriendlyPromotionDiscounter attr_reader :order, :promotions def initialize(order) - @order = Discountable::Order.new(order) + @order = order @promotions = PromotionEligibility.new(promotable: order, possible_promotions: possible_promotions).call end def call return nil if order.shipped? + order.reset_current_discounts + SolidusFriendlyPromotions::Promotion.ordered_lanes.each do |lane, _index| lane_promotions = promotions.select { |promotion| promotion.lane == lane } item_discounter = ItemDiscounter.new(promotions: lane_promotions) @@ -19,7 +21,7 @@ def call shipment_discounts = adjust_shipments(item_discounter) shipping_rate_discounts = adjust_shipping_rates(item_discounter) (line_item_discounts + shipment_discounts + shipping_rate_discounts).each do |item, chosen_discounts| - item.discounts.concat(chosen_discounts) + item.current_discounts.concat(chosen_discounts) end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb b/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb index d0cfb2046cc..7792c65afed 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb @@ -9,19 +9,18 @@ def initialize(order) def call discountable_order = FriendlyPromotionDiscounter.new(order).call - discountable_order.line_items.each do |discountable_line_item| - update_adjustments(discountable_line_item.line_item, discountable_line_item.discounts) + discountable_order.line_items.each do |line_item| + update_adjustments(line_item, line_item.current_discounts) end - discountable_order.shipments.each do |discountable_shipment| - update_adjustments(discountable_shipment.shipment, discountable_shipment.discounts) + discountable_order.shipments.each do |shipment| + update_adjustments(shipment, shipment.current_discounts) end - discountable_order.shipments.flat_map(&:shipping_rates).each do |discountable_shipping_rate| - spree_shipping_rate = discountable_shipping_rate.shipping_rate - spree_shipping_rate.discounts = discountable_shipping_rate.discounts.map do |discount| + discountable_order.shipments.flat_map(&:shipping_rates).each do |shipping_rate| + shipping_rate.discounts = shipping_rate.current_discounts.map do |discount| SolidusFriendlyPromotions::ShippingRateDiscount.new( - shipping_rate: spree_shipping_rate, + shipping_rate: shipping_rate, amount: discount.amount, label: discount.label ) @@ -42,17 +41,18 @@ def call # # @private # @param [#adjustments] item a {Spree::LineItem} or {Spree::Shipment} - # @param [Array] taxed_items a list of calculated discounts for an item + # @param [Array] item_discounts a list of calculated discounts for an item # @return [void] - def update_adjustments(item, taxed_items) + def update_adjustments(item, item_discounts) promotion_adjustments = item.adjustments.select(&:friendly_promotion?) - active_adjustments = taxed_items.map do |tax_item| - update_adjustment(item, tax_item) + active_adjustments = item_discounts.map do |item_discount| + update_adjustment(item, item_discount) end item.update(promo_total: active_adjustments.sum(&:amount)) # Remove any tax adjustments tied to rates which no longer match. unmatched_adjustments = promotion_adjustments - active_adjustments + item.adjustments.destroy(unmatched_adjustments) end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_order.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_order.rb index 9d6f81f9e99..65ccd9921de 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_order.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_order.rb @@ -6,7 +6,7 @@ class FirstOrder < PromotionRule attr_reader :user, :email def applicable?(promotable) - promotable.is_a?(Discountable::Order) + promotable.is_a?(Spree::Order) end def eligible?(order, options = {}) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb index 2f64e45448a..dfca964597d 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb @@ -8,7 +8,7 @@ class FirstRepeatPurchaseSince < PromotionRule # This promotion is applicable to orders only. def applicable?(promotable) - promotable.is_a?(Discountable::Order) + promotable.is_a?(Spree::Order) end # This is never eligible if the order does not have a user, and that user does not have any previous completed orders. diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb index 55fc42ec782..12df85239e9 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb @@ -27,7 +27,7 @@ def self.operator_options end def applicable?(promotable) - promotable.is_a?(Discountable::Order) + promotable.is_a?(Spree::Order) end def eligible?(order, _options = {}) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_option_value.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_option_value.rb index a914702a4d9..b8252820c6e 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_option_value.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_option_value.rb @@ -6,7 +6,7 @@ class LineItemOptionValue < PromotionRule preference :eligible_values, :hash def applicable?(promotable) - promotable.is_a?(Discountable::LineItem) + promotable.is_a?(Spree::LineItem) end def eligible?(line_item, _options = {}) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb index fb16c2fd645..b24e8e55a38 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb @@ -17,7 +17,7 @@ class LineItemProduct < PromotionRule preference :match_policy, :string, default: MATCH_POLICIES.first def applicable?(promotable) - promotable.is_a?(Discountable::LineItem) + promotable.is_a?(Spree::LineItem) end def eligible?(line_item, _options = {}) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb index 204a12e220c..e0fd27b693f 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb @@ -13,7 +13,7 @@ class LineItemTaxon < PromotionRule preference :match_policy, :string, default: MATCH_POLICIES.first def applicable?(promotable) - promotable.is_a?(Discountable::LineItem) + promotable.is_a?(Spree::LineItem) end def eligible?(line_item, _options = {}) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb index 041cbc538ba..04cb592bd6c 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb @@ -10,7 +10,7 @@ class NthOrder < PromotionRule # This promotion is applicable to orders only. def applicable?(promotable) - promotable.is_a?(Discountable::Order) + promotable.is_a?(Spree::Order) end # This is never eligible if the order does not have a user, and that user does not have any previous completed orders. diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb index 8393c1ffddf..5c9d13a89ef 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb @@ -4,7 +4,7 @@ module SolidusFriendlyPromotions module Rules class OneUsePerUser < PromotionRule def applicable?(promotable) - promotable.is_a?(Discountable::Order) + promotable.is_a?(Spree::Order) end def eligible?(order, _options = {}) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/option_value.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/option_value.rb index 9cd76948052..f8b25aa040e 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/option_value.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/option_value.rb @@ -6,7 +6,7 @@ class OptionValue < PromotionRule preference :eligible_values, :hash def applicable?(promotable) - promotable.is_a?(Discountable::Order) + promotable.is_a?(Spree::Order) end def eligible?(order, _options = {}) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb index 02fadb6945d..c7a5d83732e 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb @@ -29,7 +29,7 @@ def eligible_products end def applicable?(promotable) - promotable.is_a?(Discountable::Order) + promotable.is_a?(Spree::Order) end def eligible?(order, _options = {}) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/shipping_method.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/shipping_method.rb index dce6d5afad8..eb745f7605e 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/shipping_method.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/shipping_method.rb @@ -6,7 +6,7 @@ class ShippingMethod < PromotionRule preference :shipping_method_ids, type: :array, default: [] def applicable?(promotable) - promotable.is_a?(Discountable::Shipment) || promotable.is_a?(Discountable::ShippingRate) + promotable.is_a?(Spree::Shipment) || promotable.is_a?(Spree::ShippingRate) end def eligible?(promotable) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb index 182e9f4928c..3c08784975c 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb @@ -13,7 +13,7 @@ def preload_relations end def applicable?(promotable) - promotable.is_a?(Discountable::Order) + promotable.is_a?(Spree::Order) end def eligible?(order, _options = {}) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb index 9f20c285791..9130cadcfe2 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb @@ -17,7 +17,7 @@ def preload_relations preference :match_policy, :string, default: MATCH_POLICIES.first def applicable?(promotable) - promotable.is_a?(Discountable::Order) + promotable.is_a?(Spree::Order) end def eligible?(order, _options = {}) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb index 7c5a170f88c..1cb7e9fd9da 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb @@ -14,7 +14,7 @@ def preload_relations end def applicable?(promotable) - promotable.is_a?(Discountable::Order) + promotable.is_a?(Spree::Order) end def eligible?(order, _options = {}) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb index a866884b841..5be6e2ea802 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb @@ -4,7 +4,7 @@ module SolidusFriendlyPromotions module Rules class UserLoggedIn < PromotionRule def applicable?(promotable) - promotable.is_a?(Discountable::Order) + promotable.is_a?(Spree::Order) end def eligible?(order, _options = {}) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_role.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_role.rb index 08354c58daf..e8df12f71ba 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_role.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_role.rb @@ -9,7 +9,7 @@ class UserRole < PromotionRule preference :match_policy, default: MATCH_POLICIES.first def applicable?(promotable) - promotable.is_a?(Discountable::Order) + promotable.is_a?(Spree::Order) end def eligible?(order, _options = {}) diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_shipment_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_shipment_spec.rb index 887349b32f1..eb1709dea0b 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_shipment_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_shipment_spec.rb @@ -15,19 +15,19 @@ subject { action.can_discount?(promotable) } context "with a line item" do - let(:promotable) { SolidusFriendlyPromotions::Discountable::LineItem.new(Spree::Order.new, order: double) } + let(:promotable) { Spree::Order.new } it { is_expected.to be false } end context "with a shipment" do - let(:promotable) { SolidusFriendlyPromotions::Discountable::Shipment.new(Spree::Shipment.new, order: double) } + let(:promotable) { Spree::Shipment.new } it { is_expected.to be true } end context "with a shipping rate" do - let(:promotable) { SolidusFriendlyPromotions::Discountable::ShippingRate.new(Spree::ShippingRate.new, shipment: double) } + let(:promotable) { Spree::ShippingRate.new } it { is_expected.to be true } end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/distributed_amount_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/distributed_amount_spec.rb index 1f4cc9200b0..65aca311d33 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/distributed_amount_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/distributed_amount_spec.rb @@ -10,8 +10,7 @@ end let(:rules) { [] } let(:action) { SolidusFriendlyPromotions::Actions::AdjustLineItem.create(calculator: calculator) } - let(:spree_order) { create(:order_with_line_items, line_items_attributes: line_items_attributes) } - let(:order) { SolidusFriendlyPromotions::Discountable::Order.new(spree_order) } + let(:order) { create(:order_with_line_items, line_items_attributes: line_items_attributes) } let(:currency) { "USD" } context "applied to an order" do diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/percent_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/percent_spec.rb index fe98125d7dc..025c3578a65 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/percent_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/percent_spec.rb @@ -7,7 +7,7 @@ context "compute" do let(:currency) { "USD" } let(:order) { double(currency: currency) } - let(:line_item) { double("SolidusFriendlyPromotions::Discountable::LineItem", discountable_amount: 100, order: order) } + let(:line_item) { double("Spree::LineItem", discountable_amount: 100, order: order) } before { subject.preferred_percent = 15 } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_flat_rate_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_flat_rate_spec.rb index 490dac04247..ee438e6c34c 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_flat_rate_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_flat_rate_spec.rb @@ -75,7 +75,7 @@ line_items_price: amount ) end - let(:line_item) { SolidusFriendlyPromotions::Discountable::LineItem.new(order.line_items.first, order: order) } + let(:line_item) { order.line_items.first } let(:preferred_currency) { "USD" } before do @@ -109,8 +109,7 @@ context "with a shipment" do subject { calculator.compute(shipment) } - let(:spree_shipment) { Spree::Shipment.new(order: order, amount: shipping_cost) } - let(:shipment) { SolidusFriendlyPromotions::Discountable::Shipment.new(spree_shipment, order: order) } + let(:shipment) { Spree::Shipment.new(order: order, amount: shipping_cost) } let(:line_item_count) { 1 } let(:amount) { 10 } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/distributed_amounts_handler_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/distributed_amounts_handler_spec.rb index ee267f901c6..f3d244902c0 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/distributed_amounts_handler_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/distributed_amounts_handler_spec.rb @@ -3,17 +3,13 @@ require "spec_helper" RSpec.describe SolidusFriendlyPromotions::DistributedAmountsHandler, type: :model do - let(:spree_order) do + let(:order) do FactoryBot.create( :order_with_line_items, line_items_attributes: line_items_attributes ) end - let(:order) do - SolidusFriendlyPromotions::Discountable::Order.new(spree_order) - end - let(:handler) { described_class.new(order.line_items, total_amount) } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_action_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_action_spec.rb index 940f806027a..64f039db186 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_action_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_action_spec.rb @@ -22,8 +22,7 @@ let(:variant) { create(:variant) } let(:order) { create(:order) } - let(:discountable) { SolidusFriendlyPromotions::Discountable::LineItem.new(line_item, order: SolidusFriendlyPromotions::Discountable::Order.new(order)) } - let(:line_item) { Spree::LineItem.new(order: order, variant: variant, price: 10) } + let(:discountable) { Spree::LineItem.new(order: order, variant: variant, price: 10) } let(:promotion) { SolidusFriendlyPromotions::Promotion.new(name: "20 Perzent off") } let(:action) { described_class.new(promotion: promotion) } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_repeat_purchase_since_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_repeat_purchase_since_spec.rb index 8edd838e559..e23fd17e3a3 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_repeat_purchase_since_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_repeat_purchase_since_spec.rb @@ -7,13 +7,13 @@ subject { described_class.new.applicable?(promotable) } context "when the promotable is an order" do - let(:promotable) { SolidusFriendlyPromotions::Discountable::Order.new(Spree::Order.new) } + let(:promotable) { Spree::Order.new } it { is_expected.to be true } end context "when the promotable is not a order" do - let(:promotable) { SolidusFriendlyPromotions::Discountable::LineItem.new(Spree::LineItem.new, order: double) } + let(:promotable) { Spree::LineItem.new } it { is_expected.to be false } end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/nth_order_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/nth_order_spec.rb index a85d4feccc1..d2464d35286 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/nth_order_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/nth_order_spec.rb @@ -7,7 +7,7 @@ subject { described_class.new.applicable?(promotable) } context "when the promotable is an order" do - let(:promotable) { SolidusFriendlyPromotions::Discountable::Order.new(Spree::Order.new) } + let(:promotable) { Spree::Order.new } it { is_expected.to be true } end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/option_value_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/option_value_spec.rb index 4e29b4c16ce..519309378d5 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/option_value_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/option_value_spec.rb @@ -18,13 +18,13 @@ subject { rule.applicable?(promotable) } context "when promotable is an order" do - let(:promotable) { SolidusFriendlyPromotions::Discountable::Order.new(Spree::Order.new) } + let(:promotable) { Spree::Order.new } it { is_expected.to be true } end context "when promotable is not an order" do - let(:promotable) { SolidusFriendlyPromotions::Discountable::LineItem.new(Spree::LineItem.new, order: double) } + let(:promotable) { Spree::LineItem.new } it { is_expected.to be false } end From 320d8d74cdcb6dae76414b668d9824c0f32bfd9c Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Mon, 2 Oct 2023 20:06:56 +0200 Subject: [PATCH 350/834] Remove Delegators We don't need these delegators, as they are somewhat unwieldy. --- .../discountable/line_item.rb | 23 ---------- .../discountable/order.rb | 19 -------- .../discountable/shipment.rb | 24 ---------- .../discountable/shipping_rate.rb | 23 ---------- .../discountable/line_item_spec.rb | 34 -------------- .../discountable/order_spec.rb | 39 ---------------- .../discountable/shipment_spec.rb | 45 ------------------- .../discountable/shipping_rate_spec.rb | 35 --------------- 8 files changed, 242 deletions(-) delete mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/discountable/line_item.rb delete mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/discountable/order.rb delete mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/discountable/shipment.rb delete mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/discountable/shipping_rate.rb delete mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/discountable/line_item_spec.rb delete mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/discountable/order_spec.rb delete mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/discountable/shipment_spec.rb delete mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/discountable/shipping_rate_spec.rb diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/discountable/line_item.rb b/friendly_promotions/app/models/solidus_friendly_promotions/discountable/line_item.rb deleted file mode 100644 index f732710d534..00000000000 --- a/friendly_promotions/app/models/solidus_friendly_promotions/discountable/line_item.rb +++ /dev/null @@ -1,23 +0,0 @@ -# frozen_string_literal: true - -module SolidusFriendlyPromotions - module Discountable - class LineItem < SimpleDelegator - attr_reader :discounts, :order - - def initialize(line_item, order:) - super(line_item) - @order = order - @discounts = [] - end - - def line_item - __getobj__ - end - - def discountable_amount - amount + discounts.sum(&:amount) - end - end - end -end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/discountable/order.rb b/friendly_promotions/app/models/solidus_friendly_promotions/discountable/order.rb deleted file mode 100644 index ef2bd8c110a..00000000000 --- a/friendly_promotions/app/models/solidus_friendly_promotions/discountable/order.rb +++ /dev/null @@ -1,19 +0,0 @@ -# frozen_string_literal: true - -module SolidusFriendlyPromotions - module Discountable - class Order < SimpleDelegator - attr_reader :line_items, :shipments - - def initialize(order) - super - @line_items = order.line_items.map { |line_item| LineItem.new(line_item, order: self) } - @shipments = order.shipments.map { |shipment| Shipment.new(shipment, order: self) } - end - - def order - __getobj__ - end - end - end -end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/discountable/shipment.rb b/friendly_promotions/app/models/solidus_friendly_promotions/discountable/shipment.rb deleted file mode 100644 index e16c5a96dc7..00000000000 --- a/friendly_promotions/app/models/solidus_friendly_promotions/discountable/shipment.rb +++ /dev/null @@ -1,24 +0,0 @@ -# frozen_string_literal: true - -module SolidusFriendlyPromotions - module Discountable - class Shipment < SimpleDelegator - attr_reader :discounts, :shipping_rates, :order - - def initialize(shipment, order:) - super(shipment) - @order = order - @discounts = [] - @shipping_rates = shipment.shipping_rates.map { |shipping_rate| ShippingRate.new(shipping_rate, shipment: self) } - end - - def shipment - __getobj__ - end - - def discountable_amount - amount + discounts.sum(&:amount) - end - end - end -end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/discountable/shipping_rate.rb b/friendly_promotions/app/models/solidus_friendly_promotions/discountable/shipping_rate.rb deleted file mode 100644 index 77a6ca46ea6..00000000000 --- a/friendly_promotions/app/models/solidus_friendly_promotions/discountable/shipping_rate.rb +++ /dev/null @@ -1,23 +0,0 @@ -# frozen_string_literal: true - -module SolidusFriendlyPromotions - module Discountable - class ShippingRate < SimpleDelegator - attr_reader :discounts, :shipment - - def initialize(shipping_rate, shipment:) - super(shipping_rate) - @shipment = shipment - @discounts = [] - end - - def shipping_rate - __getobj__ - end - - def discountable_amount - amount + discounts.sum(&:amount) - end - end - end -end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/line_item_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/line_item_spec.rb deleted file mode 100644 index c107b8211fa..00000000000 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/line_item_spec.rb +++ /dev/null @@ -1,34 +0,0 @@ -# frozen_string_literal: true - -require "spec_helper" - -RSpec.describe SolidusFriendlyPromotions::Discountable::LineItem do - let(:discountable_order) { double(SolidusFriendlyPromotions::Discountable::Order) } - let(:spree_line_item) { build(:line_item, price: 10, quantity: 2) } - - subject(:discountable_line_item) { described_class.new(spree_line_item, order: discountable_order) } - - describe "#order" do - subject { discountable_line_item.order } - - it { is_expected.to eq(discountable_order) } - end - - describe "#discountable_amount" do - subject(:discountable_amount) { discountable_line_item.discountable_amount } - - context "with no discounts" do - it { is_expected.to eq(20) } - end - - context "with discounts" do - let(:discount) { SolidusFriendlyPromotions::ItemDiscount.new(amount: -4) } - - before do - discountable_line_item.discounts << discount - end - - it { is_expected.to eq(16) } - end - end -end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/order_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/order_spec.rb deleted file mode 100644 index 637b8a050cd..00000000000 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/order_spec.rb +++ /dev/null @@ -1,39 +0,0 @@ -# frozen_string_literal: true - -require "spec_helper" - -RSpec.describe SolidusFriendlyPromotions::Discountable::Order do - subject(:discountable_order) { described_class.new(spree_order) } - - let(:spree_order) { Spree::Order.new } - - describe "#line_items" do - let(:spree_order) { create(:order_with_line_items) } - subject(:line_items) { discountable_order.line_items } - - specify "are converted into Discountable Line Items" do - line_items.each do |line_item| - expect(line_item).to be_a(SolidusFriendlyPromotions::Discountable::LineItem) - end - end - end - - describe "#shipments" do - let(:spree_order) { create(:order_ready_to_ship) } - subject(:shipments) { discountable_order.shipments } - - specify "are converted into Discountable Shipments" do - shipments.each do |shipment| - expect(shipment).to be_a(SolidusFriendlyPromotions::Discountable::Shipment) - end - end - end - - describe "delegation" do - let(:spree_order) { Spree::Order.new(email: "yoda@example.com") } - - it "forwards order attributes" do - expect(subject.email).to eq("yoda@example.com") - end - end -end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/shipment_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/shipment_spec.rb deleted file mode 100644 index 96e58f1fe1a..00000000000 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/shipment_spec.rb +++ /dev/null @@ -1,45 +0,0 @@ -# frozen_string_literal: true - -require "spec_helper" - -RSpec.describe SolidusFriendlyPromotions::Discountable::Shipment do - let(:discountable_order) { double(SolidusFriendlyPromotions::Discountable::Order) } - - let(:spree_shipment) { build(:shipment, amount: 20) } - - subject(:discountable_shipment) { described_class.new(spree_shipment, order: discountable_order) } - - describe "#order" do - subject { discountable_shipment.order } - - it { is_expected.to eq(discountable_order) } - end - - describe "#discountable_amount" do - subject(:discountable_amount) { discountable_shipment.discountable_amount } - - context "with no discounts" do - it { is_expected.to eq(20) } - end - - context "with discounts" do - let(:discount) { SolidusFriendlyPromotions::ItemDiscount.new(amount: -4) } - - before do - discountable_shipment.discounts << discount - end - - it { is_expected.to eq(16) } - end - end - - describe "#shipping_rates" do - subject(:shipping_rates) { discountable_shipment.shipping_rates } - - specify "are converted into Discountable Shipments" do - shipping_rates.each do |shipping_rate| - expect(shipping_rate).to be_a(SolidusFriendlyPromotions::Discountable::ShippingRate) - end - end - end -end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/shipping_rate_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/shipping_rate_spec.rb deleted file mode 100644 index f6c6d7781c3..00000000000 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/shipping_rate_spec.rb +++ /dev/null @@ -1,35 +0,0 @@ -# frozen_string_literal: true - -require "spec_helper" - -RSpec.describe SolidusFriendlyPromotions::Discountable::ShippingRate do - let(:discountable_shipment) { double(SolidusFriendlyPromotions::Discountable::Shipment) } - - let(:spree_shipping_rate) { build(:shipping_rate, amount: 20) } - - subject(:discountable_shipping_rate) { described_class.new(spree_shipping_rate, shipment: discountable_shipment) } - - describe "#shipment" do - subject { discountable_shipping_rate.shipment } - - it { is_expected.to eq(discountable_shipment) } - end - - describe "#discountable_amount" do - subject(:discountable_amount) { discountable_shipping_rate.discountable_amount } - - context "with no discounts" do - it { is_expected.to eq(20) } - end - - context "with discounts" do - let(:discount) { SolidusFriendlyPromotions::ItemDiscount.new(amount: -4) } - - before do - discountable_shipping_rate.discounts << discount - end - - it { is_expected.to eq(16) } - end - end -end From 4ba8c8d7baa4639fc3e2c3d3852396b73f21dd6f Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 4 Oct 2023 13:48:07 +0200 Subject: [PATCH 351/834] Add dependent: :destroy to Spree::Order#friendly_promotions This is the same change as in https://github.com/solidusio/solidus/pull/5411 --- .../solidus_friendly_promotions/order_decorator.rb | 1 + .../spec/models/spree/order_spec.rb | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_decorator.rb b/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_decorator.rb index a70817c8a4e..f6130deff41 100644 --- a/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_decorator.rb +++ b/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_decorator.rb @@ -5,6 +5,7 @@ module OrderDecorator def self.prepended(base) base.has_many :friendly_order_promotions, class_name: "SolidusFriendlyPromotions::OrderPromotion", + dependent: :destroy, inverse_of: :order base.has_many :friendly_promotions, through: :friendly_order_promotions, source: :promotion end diff --git a/friendly_promotions/spec/models/spree/order_spec.rb b/friendly_promotions/spec/models/spree/order_spec.rb index 15ee8d76024..6be2cbff349 100644 --- a/friendly_promotions/spec/models/spree/order_spec.rb +++ b/friendly_promotions/spec/models/spree/order_spec.rb @@ -19,4 +19,18 @@ subject end end + + describe "order deletion" do + let(:order) { create(:order) } + let(:promotion) { create(:friendly_promotion) } + + subject { order.destroy } + before do + order.friendly_promotions << promotion + end + + it "deletes join table entries when deleting an order" do + expect { subject }.to change { SolidusFriendlyPromotions::OrderPromotion.count }.from(1).to(0) + end + end end From 3a7955c6e6e804b9cb5d514a84597c6ea7f33f4a Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 3 Oct 2023 11:39:11 +0200 Subject: [PATCH 352/834] Extract promotion loading from promo discounter --- .../friendly_promotion_discounter.rb | 38 +--------- .../promotion_loader.rb | 50 ++++++++++++++ .../friendly_promotion_discounter_spec.rb | 69 ------------------- .../promotion_loader_spec.rb | 66 ++++++++++++++++++ 4 files changed, 117 insertions(+), 106 deletions(-) create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/promotion_loader.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/promotion_loader_spec.rb diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb b/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb index 9c1d7b4e978..05e4d39776f 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb @@ -6,6 +6,7 @@ class FriendlyPromotionDiscounter def initialize(order) @order = order + possible_promotions = PromotionLoader.new(order: order).call @promotions = PromotionEligibility.new(promotable: order, possible_promotions: possible_promotions).call end @@ -55,42 +56,5 @@ def adjust_shipping_rates(item_discounter) [rate, chosen_item_discounts] end end - - def possible_promotions - promos = connected_order_promotions | sale_promotions - promos.flat_map(&:actions).group_by(&:preload_relations).each do |preload_relations, actions| - preload(records: actions, associations: preload_relations) - end - promos.flat_map(&:rules).group_by(&:preload_relations).each do |preload_relations, rules| - preload(records: rules, associations: preload_relations) - end - promos.reject { |promotion| promotion.usage_limit_exceeded?(excluded_orders: [order]) } - end - - def preload(records:, associations:) - ActiveRecord::Associations::Preloader.new(records: records, associations: associations).call - end - - def connected_order_promotions - eligible_connected_promotion_ids = order.friendly_order_promotions.select do |order_promotion| - order_promotion.promotion_code.nil? || !order_promotion.promotion_code.usage_limit_exceeded?(excluded_orders: [order]) - end.map(&:promotion_id) - order.friendly_promotions.active(reference_time).where(id: eligible_connected_promotion_ids).includes(promotion_includes) - end - - def sale_promotions - SolidusFriendlyPromotions::Promotion.where(apply_automatically: true).active(reference_time).includes(promotion_includes) - end - - def reference_time - order.completed_at || Time.current - end - - def promotion_includes - [ - :rules, - :actions - ] - end end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_loader.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_loader.rb new file mode 100644 index 00000000000..6e767f041b8 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_loader.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + class PromotionLoader + def initialize(order:) + @order = order + end + + def call + promos = connected_order_promotions | sale_promotions + promos.flat_map(&:actions).group_by(&:preload_relations).each do |preload_relations, actions| + preload(records: actions, associations: preload_relations) + end + promos.flat_map(&:rules).group_by(&:preload_relations).each do |preload_relations, rules| + preload(records: rules, associations: preload_relations) + end + promos.reject { |promotion| promotion.usage_limit_exceeded?(excluded_orders: [order]) } + end + + private + + attr_reader :order + + def preload(records:, associations:) + ActiveRecord::Associations::Preloader.new(records: records, associations: associations).call + end + + def connected_order_promotions + eligible_connected_promotion_ids = order.friendly_order_promotions.select do |order_promotion| + order_promotion.promotion_code.nil? || !order_promotion.promotion_code.usage_limit_exceeded?(excluded_orders: [order]) + end.map(&:promotion_id) + order.friendly_promotions.active(reference_time).where(id: eligible_connected_promotion_ids).includes(promotion_includes) + end + + def sale_promotions + SolidusFriendlyPromotions::Promotion.where(apply_automatically: true).active(reference_time).includes(promotion_includes) + end + + def reference_time + order.completed_at || Time.current + end + + def promotion_includes + [ + :rules, + :actions + ] + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/friendly_promotion_discounter_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/friendly_promotion_discounter_spec.rb index fb97da02a6a..f766f83140b 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/friendly_promotion_discounter_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/friendly_promotion_discounter_spec.rb @@ -3,75 +3,6 @@ require "spec_helper" RSpec.describe SolidusFriendlyPromotions::FriendlyPromotionDiscounter do - describe "selecting promotions" do - subject { described_class.new(order) } - - let(:order) { create(:order) } - - let!(:active_promotion) { create(:friendly_promotion, :with_adjustable_action, apply_automatically: true) } - let!(:inactive_promotion) do - create(:friendly_promotion, :with_adjustable_action, expires_at: 2.days.ago, apply_automatically: true) - end - let!(:connectable_promotion) { create(:friendly_promotion, :with_adjustable_action) } - let!(:connectable_inactive_promotion) do - create(:friendly_promotion, :with_adjustable_action, expires_at: 2.days.ago) - end - - context "no promo is connected to the order" do - it "checks only active promotions" do - expect(SolidusFriendlyPromotions::PromotionEligibility).to receive(:new) - .with(promotable: order, possible_promotions: [active_promotion]) - .and_call_original - subject - end - end - - context "an active promo is connected to the order" do - before do - order.friendly_promotions << connectable_promotion - end - - it "checks active and connected promotions" do - expect(SolidusFriendlyPromotions::PromotionEligibility).to receive(:new) - .with(promotable: order, possible_promotions: array_including(active_promotion, connectable_promotion)) - .and_call_original - subject - end - end - - context "an inactive promo is connected to the order" do - before do - order.friendly_promotions << connectable_inactive_promotion - end - - it "does not check connected inactive promotions" do - expect(SolidusFriendlyPromotions::PromotionEligibility).to receive(:new) - .with(promotable: order, possible_promotions: array_including(active_promotion)) - .and_call_original - subject - end - end - end - - context "promotions in the past" do - let(:order) { create(:order, completed_at: 7.days.ago) } - let(:currently_active_promotion) { create(:friendly_promotion, :with_adjustable_action, starts_at: 1.hour.ago) } - let(:past_promotion) { create(:friendly_promotion, :with_adjustable_action, starts_at: 1.year.ago, expires_at: 11.months.ago) } - let(:order_promotion) { create(:friendly_promotion, :with_adjustable_action, starts_at: 8.days.ago, expires_at: 6.days.ago) } - - before do - order.friendly_promotions << past_promotion - order.friendly_promotions << order_promotion - order.friendly_promotions << currently_active_promotion - end - - subject { described_class.new(order) } - - it "only evaluates the past promotion that was active when the order was completed" do - expect(subject.promotions).to eq([order_promotion]) - end - end - context "shipped orders" do let(:order) { create(:order, shipment_state: "shipped") } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_loader_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_loader_spec.rb new file mode 100644 index 00000000000..0b0ca6ebee9 --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_loader_spec.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe SolidusFriendlyPromotions::PromotionLoader do + describe "selecting promotions" do + subject { described_class.new(order: order).call } + + let(:order) { create(:order) } + + let!(:active_promotion) { create(:friendly_promotion, :with_adjustable_action, apply_automatically: true) } + let!(:inactive_promotion) do + create(:friendly_promotion, :with_adjustable_action, expires_at: 2.days.ago, apply_automatically: true) + end + let!(:connectable_promotion) { create(:friendly_promotion, :with_adjustable_action) } + let!(:connectable_inactive_promotion) do + create(:friendly_promotion, :with_adjustable_action, expires_at: 2.days.ago) + end + + context "no promo is connected to the order" do + it "returns only active promotions" do + expect(subject).to eq([active_promotion]) + end + end + + context "an active promo is connected to the order" do + before do + order.friendly_promotions << connectable_promotion + end + + it "checks active and connected promotions" do + expect(subject).to include(active_promotion, connectable_promotion) + end + end + + context "an inactive promo is connected to the order" do + before do + order.friendly_promotions << connectable_inactive_promotion + end + + it "does not check connected inactive promotions" do + expect(subject).not_to include(connectable_inactive_promotion) + expect(subject).to eq([active_promotion]) + end + end + end + + context "promotions in the past" do + let(:order) { create(:order, completed_at: 7.days.ago) } + let(:currently_active_promotion) { create(:friendly_promotion, :with_adjustable_action, starts_at: 1.hour.ago) } + let(:past_promotion) { create(:friendly_promotion, :with_adjustable_action, starts_at: 1.year.ago, expires_at: 11.months.ago) } + let(:order_promotion) { create(:friendly_promotion, :with_adjustable_action, starts_at: 8.days.ago, expires_at: 6.days.ago) } + + before do + order.friendly_promotions << past_promotion + order.friendly_promotions << order_promotion + order.friendly_promotions << currently_active_promotion + end + + subject { described_class.new(order: order).call } + + it "only evaluates the past promotion that was active when the order was completed" do + expect(subject).to eq([order_promotion]) + end + end +end From 6b75dcf4e2e41539c42721e9e3156c4053262f86 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 29 Sep 2023 17:53:40 +0200 Subject: [PATCH 353/834] Fix standardrb error in promotion rule spec --- .../promotion_rule_spec.rb | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rule_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rule_spec.rb index 90ff42a9059..e2959e5d2a4 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rule_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rule_spec.rb @@ -3,11 +3,16 @@ require "spec_helper" RSpec.describe SolidusFriendlyPromotions::PromotionRule do - class BadTestRule < SolidusFriendlyPromotions::PromotionRule; end - - class TestRule < SolidusFriendlyPromotions::PromotionRule - def eligible?(_promotable, _options = {}) - true + let(:bad_test_rule_class) { Class.new(SolidusFriendlyPromotions::PromotionRule) } + let(:test_rule_class) do + Class.new(SolidusFriendlyPromotions::PromotionRule) do + def self.model_name + ActiveModel::Name.new(self, nil, "test_rule") + end + + def eligible?(_promotable, _options = {}) + true + end end end @@ -18,21 +23,22 @@ def eligible?(_promotable, _options = {}) end it "forces developer to implement eligible? method" do - expect { BadTestRule.new.eligible?("promotable") }.to raise_error NotImplementedError + expect { bad_test_rule_class.new.eligible?("promotable") }.to raise_error NotImplementedError + expect { test_rule_class.new.eligible?("promotable") }.not_to raise_error NotImplementedError end it "validates unique rules for a promotion" do - promotion_one = TestRule.new + promotion_one = test_rule_class.new promotion_one.promotion_id = 1 promotion_one.save - promotion_two = TestRule.new + promotion_two = test_rule_class.new promotion_two.promotion_id = 1 expect(promotion_two).not_to be_valid end it "generates its own partial path" do - rule = TestRule.new + rule = test_rule_class.new expect(rule.to_partial_path).to eq "solidus_friendly_promotions/admin/promotion_rules/rules/test_rule" end end From 277a552fcbac0a94d02948d4696f888ff756d46a Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 4 Oct 2023 15:41:58 +0200 Subject: [PATCH 354/834] Namespace routes to our controllers For the transition process between the two promotion systems, we want the admins to still have access to the legacy promotions. Prior to this commit, this access was thwarted as we used the exact same paths as core Solidus. This adds an extra `friendly/` to the path, so that the paths can be distinguished. This in turn revealed we have to use the right routing proxy for the promotions controller, otherwise we'd be sent to actions in the Solidus world. --- .../admin/promotions_controller.rb | 4 ++++ friendly_promotions/config/routes.rb | 16 +++++++++------- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb index ef5bb8bbdbf..65957c9cb62 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb @@ -59,6 +59,10 @@ def load_data def location_after_save solidus_friendly_promotions.edit_admin_promotion_url(@promotion) end + + def routes_proxy + solidus_friendly_promotions + end end end end diff --git a/friendly_promotions/config/routes.rb b/friendly_promotions/config/routes.rb index 911a8da73f4..74792e801b2 100644 --- a/friendly_promotions/config/routes.rb +++ b/friendly_promotions/config/routes.rb @@ -2,14 +2,16 @@ SolidusFriendlyPromotions::Engine.routes.draw do namespace :admin do - resources :promotion_categories, except: [:show] + scope :friendly do + resources :promotion_categories, except: [:show] - resources :promotions do - resources :promotion_rules - resources :promotion_actions - resources :promotion_codes, only: [:index, :new, :create] - resources :promotion_code_batches, only: [:index, :new, :create] do - get "/download", to: "promotion_code_batches#download", defaults: {format: "csv"} + resources :promotions do + resources :promotion_rules + resources :promotion_actions + resources :promotion_codes, only: [:index, :new, :create] + resources :promotion_code_batches, only: [:index, :new, :create] do + get "/download", to: "promotion_code_batches#download", defaults: {format: "csv"} + end end end end From 5e61d4653fe5f78d76b77c9a5356ea79f1bef358 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 4 Oct 2023 15:46:16 +0200 Subject: [PATCH 355/834] Use our own objects and translations, add link to legacy promotions This commit isn't super clear cut but really small: It adds a link to the top of the promotions index to the legacy Solidus promotions overview, and it also switches out some of the uses of the legacy system for our system. --- .../admin/promotions/index.html.erb | 11 +++++++---- friendly_promotions/config/locales/en.yml | 1 + .../admin/promotion_actions_request_spec.rb | 4 ++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/index.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/index.html.erb index 8395681c812..0910ed70b23 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/index.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/index.html.erb @@ -1,9 +1,12 @@ -<% admin_breadcrumb(plural_resource_name(Spree::Promotion)) %> +<% admin_breadcrumb(plural_resource_name(SolidusFriendlyPromotions::Promotion)) %> <% content_for :page_actions do %> - <% if can? :create, Spree::Promotion %> + <% if can? :create, SolidusFriendlyPromotions::Promotion %>
  • - <%= link_to t('spree.new_promotion'), spree.new_admin_promotion_path, class: 'btn btn-primary' %> + <%= link_to t('solidus_friendly_promotions.new_promotion'), solidus_friendly_promotions.new_admin_promotion_path, class: 'btn btn-primary' %> +
  • +
  • + <%= link_to t('solidus_friendly_promotions.legacy_promotions'), spree.admin_promotions_path, class: 'btn btn-primary' %>
  • <% end %> <% end %> @@ -118,7 +121,7 @@ <% else %>
    <%= render 'spree/admin/shared/no_objects_found', - resource: Spree::Promotion, + resource: SolidusFriendlyPromotions::Promotion, new_resource_url: new_object_url %>
    <% end %> diff --git a/friendly_promotions/config/locales/en.yml b/friendly_promotions/config/locales/en.yml index 87fef33be7f..8d9070d2f41 100644 --- a/friendly_promotions/config/locales/en.yml +++ b/friendly_promotions/config/locales/en.yml @@ -25,6 +25,7 @@ en: new_promotion: New Promotion new_promotion_category: New Promotion Category new_promotion_code_batch: New Promotion Code Batch + legacy_promotions: Legacy Promotions no_rules_addes: No Rules Added promotion_successfully_created: Promotion has been successfully created! promotion_total_changed_before_complete: One or more of the promotions on your order have become ineligible and were removed. Please check the new order amounts and try again. diff --git a/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_actions_request_spec.rb b/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_actions_request_spec.rb index 09a979c8f0f..1067952c44e 100644 --- a/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_actions_request_spec.rb +++ b/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_actions_request_spec.rb @@ -15,7 +15,7 @@ } } expect(response).to be_redirect - expect(response).to redirect_to spree.edit_admin_promotion_path(promotion) + expect(response).to redirect_to solidus_friendly_promotions.edit_admin_promotion_path(promotion) expect(promotion.actions.count).to eq(1) end @@ -24,7 +24,7 @@ promotion_action: {type: "Spree::InvalidType"} } expect(response).to be_redirect - expect(response).to redirect_to spree.edit_admin_promotion_path(promotion) + expect(response).to redirect_to solidus_friendly_promotions.edit_admin_promotion_path(promotion) expect(promotion.actions.count).to eq(0) end end From 34ba08c07c32f431caeeb2853080510f24a7cc2a Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 5 Oct 2023 13:20:19 +0200 Subject: [PATCH 356/834] Add custom base controller The controller code we were using relied on a patch to Solidus[1] that will only be released with Solidus 4.2. We want to be compatible with Solidus 4 though, so we're incorporating that patch into our own BaseController and use that instead of Solidus' ResourceController. [1] https://github.com/solidusio/solidus/pull/5219 --- .../admin/base_controller.rb | 54 +++++++++++++++++++ .../admin/promotion_categories_controller.rb | 6 +-- .../promotion_code_batches_controller.rb | 2 +- .../admin/promotion_codes_controller.rb | 2 +- .../admin/promotions_controller.rb | 6 +-- 5 files changed, 58 insertions(+), 12 deletions(-) create mode 100644 friendly_promotions/app/controllers/solidus_friendly_promotions/admin/base_controller.rb diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/base_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/base_controller.rb new file mode 100644 index 00000000000..d87386bb3e6 --- /dev/null +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/base_controller.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module Admin + class BaseController < Spree::Admin::ResourceController + def parent + @parent ||= self.class.parent_data[:model_class] + .includes(self.class.parent_data[:includes]) + .find_by!(self.class.parent_data[:find_by] => params["#{parent_model_name}_id"]) + instance_variable_set("@#{parent_model_name}", @parent) + rescue ActiveRecord::RecordNotFound => e + resource_not_found(flash_class: e.model.constantize, redirect_url: routes_proxy.polymorphic_url([:admin, parent_model_name.pluralize.to_sym])) + end + + def new_object_url(options = {}) + if parent? + routes_proxy.new_polymorphic_url([:admin, parent, model_class], options) + else + routes_proxy.new_polymorphic_url([:admin, model_class], options) + end + end + + def edit_object_url(object, options = {}) + if parent? + routes_proxy.polymorphic_url([:edit, :admin, parent, object], options) + else + routes_proxy.polymorphic_url([:edit, :admin, object], options) + end + end + + def object_url(object = nil, options = {}) + target = object || @object + + if parent? + routes_proxy.polymorphic_url([:admin, parent, target], options) + else + routes_proxy.polymorphic_url([:admin, target], options) + end + end + + def collection_url(options = {}) + if parent? + routes_proxy.polymorphic_url([:admin, parent, model_class], options) + else + routes_proxy.polymorphic_url([:admin, model_class], options) + end + end + + def routes_proxy + solidus_friendly_promotions + end + end + end +end diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_categories_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_categories_controller.rb index a9861ca3693..53a1e1f35ef 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_categories_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_categories_controller.rb @@ -2,16 +2,12 @@ module SolidusFriendlyPromotions module Admin - class PromotionCategoriesController < Spree::Admin::ResourceController + class PromotionCategoriesController < BaseController private def model_class SolidusFriendlyPromotions::PromotionCategory end - - def routes_proxy - solidus_friendly_promotions - end end end end diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_code_batches_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_code_batches_controller.rb index 24d5be4ddc8..7361d860cca 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_code_batches_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_code_batches_controller.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Admin - class PromotionCodeBatchesController < Spree::Admin::ResourceController + class PromotionCodeBatchesController < BaseController belongs_to "solidus_friendly_promotions/promotion" create.after :build_promotion_code_batch diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_codes_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_codes_controller.rb index 291e3d3b315..d95bc9baeec 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_codes_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_codes_controller.rb @@ -4,7 +4,7 @@ module SolidusFriendlyPromotions module Admin - class PromotionCodesController < Spree::Admin::ResourceController + class PromotionCodesController < BaseController before_action :load_promotion def index diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb index 65957c9cb62..bd838a23821 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Admin - class PromotionsController < ::Spree::Admin::ResourceController + class PromotionsController < BaseController before_action :load_data helper "solidus_friendly_promotions/admin/promotion_rules" @@ -59,10 +59,6 @@ def load_data def location_after_save solidus_friendly_promotions.edit_admin_promotion_url(@promotion) end - - def routes_proxy - solidus_friendly_promotions - end end end end From 6d7e864216eae4f5e2fbb562ce5388a83738c129 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 6 Oct 2023 11:43:24 +0200 Subject: [PATCH 357/834] Change references to legacy promotion system in views There were many references to the promotion system in solidus core in our views folder still. This commit changes them. --- .../admin/promotion_categories/edit.html.erb | 4 ++-- .../admin/promotion_categories/new.html.erb | 4 ++-- .../admin/promotion_codes/index.html.erb | 14 +++++++------- .../admin/promotion_codes/new.html.erb | 4 ++-- .../admin/promotion_rules/_promotion_rule.html.erb | 2 +- .../admin/promotion_rules/new.html.erb | 2 +- .../rules/_first_repeat_purchase_since.html.erb | 2 +- .../promotion_rules/rules/_nth_order.html.erb | 2 +- .../admin/promotion_rules/rules/_product.html.erb | 2 +- .../admin/promotion_rules/rules/_taxon.html.erb | 2 +- .../promotion_rules/rules/_user_role.html.erb | 2 +- .../admin/promotions/_activations_new.html.erb | 2 +- .../admin/promotions/edit.html.erb | 8 ++++---- .../admin/promotions/index.html.erb | 14 +++++++------- .../admin/promotions/new.html.erb | 2 +- 15 files changed, 33 insertions(+), 33 deletions(-) diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_categories/edit.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_categories/edit.html.erb index f6b3136f964..e5112ac90b2 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_categories/edit.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_categories/edit.html.erb @@ -1,5 +1,5 @@ -<% admin_breadcrumb(link_to plural_resource_name(Spree::Promotion), solidus_friendly_promotions.admin_promotions_path) %> -<% admin_breadcrumb(link_to plural_resource_name(Spree::PromotionCategory), solidus_friendly_promotions.admin_promotion_categories_path) %> +<% admin_breadcrumb(link_to plural_resource_name(SolidusFriendlyPromotions::Promotion), solidus_friendly_promotions.admin_promotions_path) %> +<% admin_breadcrumb(link_to plural_resource_name(SolidusFriendlyPromotions::PromotionCategory), solidus_friendly_promotions.admin_promotion_categories_path) %> <% admin_breadcrumb(@promotion_category.name) %> <%= form_for @promotion_category, url: object_url, method: :put do |f| %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_categories/new.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_categories/new.html.erb index 7b557fc61c2..f0bdf88de64 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_categories/new.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_categories/new.html.erb @@ -1,5 +1,5 @@ -<% admin_breadcrumb(link_to plural_resource_name(Spree::Promotion), solidus_friendly_promotions.admin_promotions_path) %> -<% admin_breadcrumb(link_to plural_resource_name(Spree::PromotionCategory), solidus_friendly_promotions.admin_promotion_categories_path) %> +<% admin_breadcrumb(link_to plural_resource_name(SolidusFriendlyPromotions::Promotion), solidus_friendly_promotions.admin_promotions_path) %> +<% admin_breadcrumb(link_to plural_resource_name(SolidusFriendlyPromotions::PromotionCategory), solidus_friendly_promotions.admin_promotion_categories_path) %> <% admin_breadcrumb(t('spree.new_promotion_category')) %> <%= form_for :promotion_category, url: collection_url do |f| %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_codes/index.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_codes/index.html.erb index 75e00969544..300ef013e2d 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_codes/index.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_codes/index.html.erb @@ -1,14 +1,14 @@ -<% admin_breadcrumb link_to plural_resource_name(Spree::Promotion), spree.admin_promotions_path %> -<% admin_breadcrumb link_to(@promotion.name, spree.edit_admin_promotion_path(@promotion)) %> -<% admin_breadcrumb plural_resource_name(Spree::PromotionCode) %> +<% admin_breadcrumb link_to plural_resource_name(SolidusFriendlyPromotions::Promotion), solidus_friendly_promotions.admin_promotions_path %> +<% admin_breadcrumb link_to(@promotion.name, solidus_friendly_promotions.edit_admin_promotion_path(@promotion)) %> +<% admin_breadcrumb plural_resource_name(SolidusFriendlyPromotions::PromotionCode) %> <% content_for :page_actions do %>
  • - <% if can?(:create, Spree::PromotionCode) && !@promotion.apply_automatically? %> - <%= link_to t('spree.create_promotion_code'), new_admin_promotion_promotion_code_path(promotion_id: @promotion.id), class: 'btn btn-primary' %> + <% if can?(:create, SolidusFriendlyPromotions::PromotionCode) && !@promotion.apply_automatically? %> + <%= link_to t('spree.create_promotion_code'), solidus_friendly_promotions.new_admin_promotion_promotion_code_path(promotion_id: @promotion.id), class: 'btn btn-primary' %> <% end %> - <%= link_to t('spree.download_promotion_codes_list'), admin_promotion_promotion_codes_path(promotion_id: @promotion.id, format: :csv), class: 'btn btn-primary' %> + <%= link_to t('spree.download_promotion_codes_list'), solidus_friendly_promotions.admin_promotion_promotion_codes_path(promotion_id: @promotion.id, format: :csv), class: 'btn btn-primary' %>
  • <% end %> @@ -18,7 +18,7 @@ - + <% @promotion_codes.each do |promotion_code| %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_codes/new.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_codes/new.html.erb index 894df09e29e..5b82b40d333 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_codes/new.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_codes/new.html.erb @@ -1,6 +1,6 @@ -<% admin_breadcrumb link_to plural_resource_name(Spree::Promotion), solidus_friendly_promotions.admin_promotions_path %> +<% admin_breadcrumb link_to plural_resource_name(SolidusFriendlyPromotions::Promotion), solidus_friendly_promotions.admin_promotions_path %> <% admin_breadcrumb link_to(@promotion.name, solidus_friendly_promotions.edit_admin_promotion_path(@promotion)) %> -<% admin_breadcrumb plural_resource_name(Spree::PromotionCode) %> +<% admin_breadcrumb plural_resource_name(SolidusFriendlyPromotions::PromotionCode) %> <% content_for :page_actions do %>
  • diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/_promotion_rule.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/_promotion_rule.html.erb index e1c55fcec50..493d6ddd582 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/_promotion_rule.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/_promotion_rule.html.erb @@ -3,7 +3,7 @@ <%= form_with model: promotion_rule, scope: :promotion_rule, url: solidus_friendly_promotions.admin_promotion_promotion_rule_path(@promotion, promotion_rule), method: :patch do |form| %>
    <%= promotion_rule.class.model_name.human %>
    <% if can?(:destroy, promotion_rule) %> - <%= link_to_with_icon 'trash', '', spree.admin_promotion_promotion_rule_path(@promotion, promotion_rule), method: :delete, class: 'delete' %> + <%= link_to_with_icon 'trash', '', solidus_friendly_promotions.admin_promotion_promotion_rule_path(@promotion, promotion_rule), method: :delete, class: 'delete' %> <% end %>

    <%= promotion_rule.class.human_attribute_name(:description) %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/new.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/new.html.erb index ad0174a57bd..f65d4ce7b64 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/new.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/new.html.erb @@ -1,7 +1,7 @@ <%= turbo_frame_tag @promotion, "new_#{@level}_promotion_rule" do %>

    <%= t(:add_rule, scope: :solidus_friendly_promotions) %>
    - <%= link_to_with_icon 'trash', '', spree.edit_admin_promotion_path(@promotion), class: 'delete' %> + <%= link_to_with_icon 'trash', '', solidus_friendly_promotions.edit_admin_promotion_path(@promotion), class: 'delete' %> <%= render 'type_select', level: @level %> <% flash.each do |severity, message| %> <%= content_tag(:div, "", data: { controller: :flash, severity: severity, message: message }) %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_first_repeat_purchase_since.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_first_repeat_purchase_since.html.erb index 8e2e587aab7..c1de9b240d4 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_first_repeat_purchase_since.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_first_repeat_purchase_since.html.erb @@ -1,7 +1,7 @@
    - <%= Spree::Promotion::Rules::FirstRepeatPurchaseSince.human_attribute_name(:form_text) %> + <%= SolidusFriendlyPromotions::Rules::FirstRepeatPurchaseSince.human_attribute_name(:form_text) %>
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_nth_order.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_nth_order.html.erb index 0a37ce60cf1..6e2a0b20382 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_nth_order.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_nth_order.html.erb @@ -1,7 +1,7 @@
    - <%= Spree::Promotion::Rules::NthOrder.human_attribute_name(:form_text) %> + <%= SolidusFriendlyPromotions::Rules::NthOrder.human_attribute_name(:form_text) %>
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_product.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_product.html.erb index 2d11f098832..1b1e3465ef3 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_product.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_product.html.erb @@ -8,7 +8,7 @@
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_taxon.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_taxon.html.erb index bec9eb7e9c3..13a0e124e94 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_taxon.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_taxon.html.erb @@ -4,6 +4,6 @@
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_user_role.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_user_role.html.erb index e078526d08f..5a136ba6b63 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_user_role.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_user_role.html.erb @@ -7,6 +7,6 @@
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_activations_new.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_activations_new.html.erb index 47cf2da5283..51646009c1b 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_activations_new.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_activations_new.html.erb @@ -28,7 +28,7 @@
    - <%= label_tag :single_code, Spree::PromotionCode.model_name.human, class: "required" %> + <%= label_tag :single_code, SolidusFriendlyPromotions::PromotionCode.model_name.human, class: "required" %> <%= text_field_tag :single_code, @promotion.codes.first.try!(:value), class: "fullwidth", required: true %>
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/edit.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/edit.html.erb index 34402a4f4cf..57f3cc7a3e4 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/edit.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/edit.html.erb @@ -1,6 +1,6 @@ <% admin_layout "full-width" %> -<% admin_breadcrumb(link_to plural_resource_name(Spree::Promotion), solidus_friendly_promotions.admin_promotions_path) %> +<% admin_breadcrumb(link_to plural_resource_name(SolidusFriendlyPromotions::Promotion), solidus_friendly_promotions.admin_promotions_path) %> <% admin_breadcrumb(@promotion.name) %> <%= content_for :head do %> @@ -9,14 +9,14 @@ <% content_for :page_actions do %>
  • - <% if can?(:show, Spree::PromotionCode) %> + <% if can?(:show, SolidusFriendlyPromotions::PromotionCode) %> <%= link_to t('solidus_friendly_promotions.view_promotion_codes_list'), solidus_friendly_promotions.admin_promotion_promotion_codes_path(promotion_id: @promotion.id), class: 'btn btn-primary' %> <%= link_to t('solidus_friendly_promotions.download_promotion_codes_list'), solidus_friendly_promotions.admin_promotion_promotion_codes_path(promotion_id: @promotion.id, format: :csv), class: 'btn btn-primary' %> <% end %> - <% if can?(:show, Spree::PromotionCodeBatch) %> - <%= link_to plural_resource_name(Spree::PromotionCodeBatch), solidus_friendly_promotions.admin_promotion_promotion_code_batches_path(promotion_id: @promotion.id), class: 'btn btn-primary' %> + <% if can?(:show, SolidusFriendlyPromotions::PromotionCodeBatch) %> + <%= link_to plural_resource_name(SolidusFriendlyPromotions::PromotionCodeBatch), solidus_friendly_promotions.admin_promotion_promotion_code_batches_path(promotion_id: @promotion.id), class: 'btn btn-primary' %> <% end %>
  • <% end %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/index.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/index.html.erb index 0910ed70b23..a54f02f5e5a 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/index.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/index.html.erb @@ -72,13 +72,13 @@
    <%= Spree::PromotionCode.human_attribute_name :value %><%= SolidusFriendlyPromotions::PromotionCode.human_attribute_name :value %>
    - - - - - - - + + + + + + + diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/new.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/new.html.erb index d9302bd67cb..cac42cebc53 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/new.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/new.html.erb @@ -1,6 +1,6 @@ <% admin_layout "full-width" %> -<% admin_breadcrumb(link_to plural_resource_name(Spree::Promotion), solidus_friendly_promotions.admin_promotions_path) %> +<% admin_breadcrumb(link_to plural_resource_name(SolidusFriendlyPromotions::Promotion), solidus_friendly_promotions.admin_promotions_path) %> <% admin_breadcrumb(t('solidus_friendly_promotions.new_promotion')) %> <%= form_for @promotion, url: collection_url do |f| %> From a6b8beb5dfdd97adda60a9d6a2809164ce2e7b0e Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 6 Oct 2023 12:10:56 +0200 Subject: [PATCH 358/834] Reference our own I18n namespace in views Prior to this commit, we were still using the translations in core Solidus. --- .../tiered_flat_rate/_fields.html.erb | 5 +- .../tiered_percent/_fields.html.erb | 5 +- .../admin/promotion_categories/new.html.erb | 2 +- .../admin/promotion_codes/index.html.erb | 4 +- .../promotion_rules/_type_select.html.erb | 2 +- .../promotion_rules/rules/_product.html.erb | 4 +- .../promotion_rules/rules/_store.html.erb | 2 +- .../promotion_rules/rules/_taxon.html.erb | 4 +- .../promotion_rules/rules/_user.html.erb | 2 +- .../promotion_rules/rules/_user_role.html.erb | 4 +- .../admin/promotions/_form.html.erb | 2 +- .../admin/promotions/index.html.erb | 10 ++-- friendly_promotions/config/locales/en.yml | 52 ++++++++++++------- 13 files changed, 57 insertions(+), 41 deletions(-) diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/calculators/tiered_flat_rate/_fields.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/calculators/tiered_flat_rate/_fields.html.erb index 05e86e4a52d..66938ff44d7 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/calculators/tiered_flat_rate/_fields.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/calculators/tiered_flat_rate/_fields.html.erb @@ -1,6 +1,7 @@ <%= render "spree/admin/shared/preference_fields/#{calculator.preference_type(:base_amount)}", name: "#{prefix}[calculator_attributes][preferred_base_amount]", - value: calculator.preferred_base_amount, label: t('spree.base_amount') %> + value: calculator.preferred_base_amount, + label: calculator.class.human_attribute_name(:preferred_base_amount) %>
    <%= label_tag( @@ -18,7 +19,7 @@
    - <%= label_tag nil, t('spree.tiers') %> + <%= label_tag nil, calculator.class.human_attribute_name(:tiers) %>
    <%= Spree::Promotion.human_attribute_name(:name) %><%= Spree::Promotion.human_attribute_name(:code) %><%= Spree::Promotion.human_attribute_name(:status) %><%= Spree::Promotion.human_attribute_name(:usage_limit) %><%= Spree::Promotion.human_attribute_name(:uses) %><%= Spree::Promotion.human_attribute_name(:starts_at) %><%= Spree::Promotion.human_attribute_name(:expires_at) %><%= SolidusFriendlyPromotions::Promotion.human_attribute_name(:name) %><%= SolidusFriendlyPromotions::Promotion.human_attribute_name(:code) %><%= SolidusFriendlyPromotions::Promotion.human_attribute_name(:status) %><%= SolidusFriendlyPromotions::Promotion.human_attribute_name(:usage_limit) %><%= SolidusFriendlyPromotions::Promotion.human_attribute_name(:uses) %><%= SolidusFriendlyPromotions::Promotion.human_attribute_name(:starts_at) %><%= SolidusFriendlyPromotions::Promotion.human_attribute_name(:expires_at) %>