diff --git a/.circleci/config.yml b/.circleci/config.yml
index 1e753503a88..e07188dacd3 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -232,27 +232,6 @@ commands:
bundle exec rake -rrails -rspree/testing_support/extension_rake -e'Rake::Task["extension:test_app"].invoke'
jobs:
- lint_code:
- docker:
- - image: cimg/ruby:3.2-node
- environment:
- BUNDLE_ONLY: "lint"
- ESLINT_USE_FLAT_CONFIG: false
- steps:
- - checkout
- - run: "bundle install"
- - run:
- name: Check Ruby
- command: "bin/rake lint:rb"
- - run:
- name: Check ERB
- command: "bin/rake lint:erb"
- - run:
- name: Check JavaScript
- command: "bin/rake lint:js"
- - store_test_results:
- path: test-results
-
solidus_installer:
executor:
name: sqlite
@@ -362,7 +341,6 @@ jobs:
workflows:
build:
jobs:
- - lint_code
- solidus_installer
- test_solidus_with_coverage # Only test with coverage support with the default versions
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
new file mode 100644
index 00000000000..98d53406039
--- /dev/null
+++ b/.github/workflows/lint.yml
@@ -0,0 +1,62 @@
+name: Lint
+
+on: [pull_request]
+
+concurrency:
+ group: lint-${{ github.ref_name }}
+ cancel-in-progress: ${{ github.ref_name != 'main' }}
+
+permissions:
+ contents: read
+
+env:
+ BUNDLE_ONLY: "lint"
+
+jobs:
+ ruby:
+ name: Check Ruby
+ runs-on: ubuntu-22.04
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v3
+ - name: Install Ruby and gems
+ uses: ruby/setup-ruby@v1
+ with:
+ ruby-version: "3.2"
+ bundler-cache: true
+ - name: Lint Ruby files
+ run: bin/rake lint:rb
+ - name: Store test results
+ uses: actions/upload-artifact@v4
+ with:
+ name: rubocop-results
+ path: test-results
+
+ erb:
+ name: Check ERB
+ runs-on: ubuntu-22.04
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v3
+ - name: Install Ruby and gems
+ uses: ruby/setup-ruby@v1
+ with:
+ ruby-version: "3.2"
+ bundler-cache: true
+ - name: Lint ERB files
+ run: bin/rake lint:erb
+
+ javascript:
+ name: Check JavaScript
+ runs-on: ubuntu-22.04
+ env:
+ ESLINT_USE_FLAT_CONFIG: false
+ steps:
+ - uses: actions/checkout@v3
+ - name: Install Ruby and gems
+ uses: ruby/setup-ruby@v1
+ with:
+ ruby-version: "3.2"
+ bundler-cache: true
+ - name: Lint JS files
+ run: bin/rake lint:js
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f965db5d616..60b772e438d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,68 @@
+## Solidus v4.4.2 (2024-12-09)
+
+
+
+## Solidus Core
+
+* [v4.4] [FIX] Remove spacing at top of OrderShipping#ship method by @github-actions[bot] in https://github.com/solidusio/solidus/pull/5974
+* [v4.4] Backend: Add missing error translation by @github-actions[bot] in https://github.com/solidusio/solidus/pull/5986
+* [v4.4] Test app task: Allow passing in user class by @github-actions[bot] in https://github.com/solidusio/solidus/pull/5961
+* [v4.4] Add show all results to en.yml by @github-actions[bot] in https://github.com/solidusio/solidus/pull/5996
+* [v4.4] Do not constantize Spree.user_class in UserClassHandle by @github-actions[bot] in https://github.com/solidusio/solidus/pull/6004
+
+## Solidus Admin
+
+* [v4.4] test: Wait for modal to open before testing its content by @github-actions[bot] in https://github.com/solidusio/solidus/pull/5994
+* [v4.4] [specs] Wait for modal before testing its content by @github-actions[bot] in https://github.com/solidusio/solidus/pull/6005
+* [v4.4] Use Order#email to show the order's email in new admin by @github-actions[bot] in https://github.com/solidusio/solidus/pull/6006
+* [v4.4] tests: Give even more dialogs more time to open in tests by @github-actions[bot] in https://github.com/solidusio/solidus/pull/6018
+
+## Solidus Backend
+
+* [v4.4] Backend: Add missing error translation by @github-actions[bot] in https://github.com/solidusio/solidus/pull/5986
+
+## Solidus Promotions
+
+* [v4.4] test: Wait for modal to open before testing its content by @github-actions[bot] in https://github.com/solidusio/solidus/pull/5994
+* [v4.4] [FIX] A few small tweaks for the new promotion admin by @github-actions[bot] in https://github.com/solidusio/solidus/pull/6021
+* [v4.4] Fix(promotions): Validate benefits on save by @github-actions[bot] in https://github.com/solidusio/solidus/pull/6023
+* [v4.4] Add can apply to promotions by @github-actions[bot] in https://github.com/solidusio/solidus/pull/6024
+* [v4.4] Fix admin promotions controller by @github-actions[bot] in https://github.com/solidusio/solidus/pull/6020
+
+**Full Changelog**: https://github.com/solidusio/solidus/compare/v4.4.1...v4.4.2
+
+
+## Solidus v4.4.1 (2024-11-18)
+
+
+
+## Solidus Core
+
+* [v4.4] Move Line Item Actions to solidus_legacy_promotions by @github-actions[bot] in https://github.com/solidusio/solidus/pull/5919
+* [v4.4] Remove rails binstubs from built gems by @github-actions[bot] in https://github.com/solidusio/solidus/pull/5922
+
+## Solidus Admin
+
+* [v4.4] Remove rails binstubs from built gems by @github-actions[bot] in https://github.com/solidusio/solidus/pull/5922
+* [v4.4] Fix component translation scopes by @github-actions[bot] in https://github.com/solidusio/solidus/pull/5930
+* [v4.4] Feat(Admin): Dynamic routing proxies by @github-actions[bot] in https://github.com/solidusio/solidus/pull/5937
+
+## Solidus Backend
+
+* [v4.4] Remove rails binstubs from built gems by @github-actions[bot] in https://github.com/solidusio/solidus/pull/5922
+
+## Solidus API
+
+* [v4.4] Remove rails binstubs from built gems by @github-actions[bot] in https://github.com/solidusio/solidus/pull/5922
+
+## Solidus Promotions
+
+* [v4.4] Fix component translation scopes by @github-actions[bot] in https://github.com/solidusio/solidus/pull/5930
+* [v4.4] Better promotion menus by @tvdeyen in https://github.com/solidusio/solidus/pull/5938
+
+**Full Changelog**: https://github.com/solidusio/solidus/compare/v4.4.0...v4.4.1
+
+
## Solidus v4.4.0 (2024-11-12)
diff --git a/admin/app/components/solidus_admin/base_component.rb b/admin/app/components/solidus_admin/base_component.rb
index 7bfe1b52852..e3adf62c02f 100644
--- a/admin/app/components/solidus_admin/base_component.rb
+++ b/admin/app/components/solidus_admin/base_component.rb
@@ -25,6 +25,10 @@ def missing_translation(key, options)
end
end
+ def self.i18n_scope
+ @i18n_scope ||= name.underscore.tr("/", ".")
+ end
+
def self.stimulus_id
@stimulus_id ||= name.underscore
.sub(/^solidus_admin\/(.*)\/component$/, '\1')
@@ -34,12 +38,22 @@ def self.stimulus_id
delegate :stimulus_id, to: :class
- def spree
- @spree ||= Spree::Core::Engine.routes.url_helpers
+ class << self
+ private
+
+ def engines_with_routes
+ Rails::Engine.subclasses.map(&:instance).reject do |engine|
+ engine.routes.empty?
+ end
+ end
end
- def solidus_admin
- @solidus_admin ||= SolidusAdmin::Engine.routes.url_helpers
+ # For each engine with routes, define a method that returns the routes proxy.
+ # This allows us to use the routes in the context of a component class.
+ engines_with_routes.each do |engine|
+ define_method(engine.engine_name) do
+ engine.routes.url_helpers
+ end
end
end
end
diff --git a/admin/app/components/solidus_admin/layout/navigation/item/component.html.erb b/admin/app/components/solidus_admin/layout/navigation/item/component.html.erb
index d3270a08c07..575872b242b 100644
--- a/admin/app/components/solidus_admin/layout/navigation/item/component.html.erb
+++ b/admin/app/components/solidus_admin/layout/navigation/item/component.html.erb
@@ -1,7 +1,7 @@
">
"
+ aria-current="<%= @item.current?(self, @fullpath) ? "page" : "false" %>"
class="
flex gap-3 items-center
py-1 px-3 rounded
@@ -20,7 +20,7 @@
<% if @item.children? %>
">
- <%= render self.class.with_collection(@item.children, url_helpers: @url_helpers, fullpath: @fullpath) %>
+ <%= render self.class.with_collection(@item.children, fullpath: @fullpath) %>
<% end %>
diff --git a/admin/app/components/solidus_admin/layout/navigation/item/component.rb b/admin/app/components/solidus_admin/layout/navigation/item/component.rb
index 17ac8a62f74..f05c95a47bb 100644
--- a/admin/app/components/solidus_admin/layout/navigation/item/component.rb
+++ b/admin/app/components/solidus_admin/layout/navigation/item/component.rb
@@ -6,22 +6,19 @@ class SolidusAdmin::Layout::Navigation::Item::Component < SolidusAdmin::BaseComp
# @param item [SolidusAdmin::MenuItem]
# @param fullpath [String] the current path
- # @param url_helpers [#solidus_admin, #spree] context for generating paths
def initialize(
item:,
- fullpath: "#",
- url_helpers: Struct.new(:spree, :solidus_admin).new(spree, solidus_admin)
+ fullpath: "#"
)
@item = item
- @url_helpers = url_helpers
@fullpath = fullpath
end
def path
- @item.path(@url_helpers)
+ @item.path(self)
end
def active?
- @item.active?(@url_helpers, @fullpath)
+ @item.active?(self, @fullpath)
end
end
diff --git a/admin/app/components/solidus_admin/orders/index/component.rb b/admin/app/components/solidus_admin/orders/index/component.rb
index 2c8641926f5..f90e25653b3 100644
--- a/admin/app/components/solidus_admin/orders/index/component.rb
+++ b/admin/app/components/solidus_admin/orders/index/component.rb
@@ -143,7 +143,7 @@ def customer_column
col: { class: "w-[400px]" },
header: :customer,
data: ->(order) do
- customer_email = order.user&.email
+ customer_email = order.email
content_tag :div, String(customer_email)
end
}
diff --git a/admin/lib/solidus_admin/testing_support/component_helpers.rb b/admin/lib/solidus_admin/testing_support/component_helpers.rb
index 58f530eb1fd..f56960561dd 100644
--- a/admin/lib/solidus_admin/testing_support/component_helpers.rb
+++ b/admin/lib/solidus_admin/testing_support/component_helpers.rb
@@ -12,15 +12,9 @@ module ComponentHelpers
# "Rendered"
# end
# end
- def mock_component(&definition)
- location = caller(1, 1).first
- component_class = Class.new(SolidusAdmin::BaseComponent)
- # ViewComponent will complain if we don't fake a class name:
- # @see https://github.com/ViewComponent/view_component/blob/5decd07842c48cbad82527daefa3fe9c65a4226a/lib/view_component/base.rb#L371
- component_class.define_singleton_method(:name) { "Foo" }
- component_class.define_singleton_method(:to_s) { "#{name} (#{location})" }
- component_class.class_eval(&definition) if definition
- component_class
+ def mock_component(class_name = "Foo::Component", &definition)
+ component_class = stub_const(class_name, Class.new(described_class, &definition))
+ component_class.new
end
end
end
diff --git a/admin/solidus_admin.gemspec b/admin/solidus_admin.gemspec
index f15f2c9ce47..ce348c7b33d 100644
--- a/admin/solidus_admin.gemspec
+++ b/admin/solidus_admin.gemspec
@@ -22,7 +22,7 @@ Gem::Specification.new do |s|
s.metadata["changelog_uri"] = "https://github.com/solidusio/solidus/releases?q=%22solidus_admin%2Fv0%22&expanded=true"
s.files = `git ls-files -z`.split("\x0").reject do |f|
- f.match(%r{^(spec|script)/})
+ f.match(%r{^(spec|bin)/})
end + ["app/assets/builds/solidus_admin/tailwind.css"]
s.required_ruby_version = '>= 3.1.0'
diff --git a/admin/spec/components/solidus_admin/base_component_spec.rb b/admin/spec/components/solidus_admin/base_component_spec.rb
index 2c95f2c9fd3..8ae121dce20 100644
--- a/admin/spec/components/solidus_admin/base_component_spec.rb
+++ b/admin/spec/components/solidus_admin/base_component_spec.rb
@@ -9,7 +9,7 @@
def call
icon_tag("user-line")
end
- end.new
+ end
render_inline(component)
@@ -42,7 +42,7 @@ def call
describe ".stimulus_id" do
it "returns the stimulus id for the component" do
- stub_const("SolidusAdmin::Foo::Bar::Component", Class.new(described_class))
+ mock_component("SolidusAdmin::Foo::Bar::Component") { erb_template "" }
expect(SolidusAdmin::Foo::Bar::Component.stimulus_id).to eq("foo--bar")
expect(SolidusAdmin::Foo::Bar::Component.new.stimulus_id).to eq("foo--bar")
@@ -55,8 +55,7 @@ def call
allow(Rails.logger).to receive(:debug) { debug_logs << _1 }
- component_class = stub_const("Foo::Component", Class.new(described_class){ erb_template "" })
- component = component_class.new
+ component = mock_component { erb_template "" }
render_inline(component)
translation = component.translate("foo.bar.baz")
diff --git a/admin/spec/features/adjustment_reasons_spec.rb b/admin/spec/features/adjustment_reasons_spec.rb
index 8af9a711ea0..615dfa234fa 100644
--- a/admin/spec/features/adjustment_reasons_spec.rb
+++ b/admin/spec/features/adjustment_reasons_spec.rb
@@ -26,14 +26,14 @@
before do
visit "/admin/adjustment_reasons#{query}"
click_on "Add new"
+ expect(page).to have_selector("dialog", wait: 5)
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")
+ it "closing the modal keeps query params" do
within("dialog") { click_on "Cancel" }
- expect(page).not_to have_selector("dialog")
+ expect(page).not_to have_selector("dialog", wait: 5)
expect(page.current_url).to include(query)
end
@@ -69,14 +69,14 @@
Spree::AdjustmentReason.create(name: "Good Reason", code: 5999)
visit "/admin/adjustment_reasons#{query}"
find_row("Good Reason").click
+ expect(page).to have_selector("dialog", wait: 5)
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")
+ it "closing the modal keeps query params" do
within("dialog") { click_on "Cancel" }
- expect(page).not_to have_selector("dialog")
+ expect(page).not_to have_selector("dialog", wait: 5)
expect(page.current_url).to include(query)
end
diff --git a/admin/spec/features/orders/index_spec.rb b/admin/spec/features/orders/index_spec.rb
index 474edfe9439..45cb7cdc0bf 100644
--- a/admin/spec/features/orders/index_spec.rb
+++ b/admin/spec/features/orders/index_spec.rb
@@ -11,6 +11,7 @@
visit "/admin/orders"
click_on "In Progress"
+ expect(page).to have_content("admin@example.com")
expect(page).to have_content("R123456789")
expect(page).to have_content("$19.99")
expect(page).to be_axe_clean
diff --git a/admin/spec/features/orders/show_spec.rb b/admin/spec/features/orders/show_spec.rb
index 7b8aebd5479..986acbac250 100644
--- a/admin/spec/features/orders/show_spec.rb
+++ b/admin/spec/features/orders/show_spec.rb
@@ -48,7 +48,7 @@
expect(page).to have_content("Order R123456789")
open_customer_menu
click_on "Edit billing address"
- expect(page).to have_css("dialog", wait: 30)
+ expect(page).to have_css("dialog", wait: 5)
within("dialog") do
fill_in "Name", with: "John Doe"
@@ -74,7 +74,7 @@
open_customer_menu
click_on "Edit shipping address"
- expect(page).to have_css("dialog", wait: 30)
+ expect(page).to have_css("dialog", wait: 5)
within("dialog") do
fill_in "Name", with: "Jane Doe"
@@ -119,18 +119,18 @@
expect(Spree::Order.last.line_items.count).to eq(0)
find("[aria-selected]", text: "Just another product").click
- expect(page).to have_content("Variant added to cart successfully", wait: 30)
+ expect(page).to have_content("Variant added to cart successfully", wait: 5)
expect(Spree::Order.last.line_items.count).to eq(1)
expect(Spree::Order.last.line_items.last.quantity).to eq(1)
fill_in "line_item[quantity]", with: 4
- expect(page).to have_content("Quantity updated successfully", wait: 30)
+ expect(page).to have_content("Quantity updated successfully", wait: 5)
expect(Spree::Order.last.line_items.last.quantity).to eq(4)
accept_confirm("Are you sure?") { click_on "Delete" }
- expect(page).to have_content("Line item removed successfully", wait: 30)
+ expect(page).to have_content("Line item removed successfully", wait: 5)
expect(Spree::Order.last.line_items.count).to eq(0)
expect(page).to be_axe_clean
diff --git a/admin/spec/features/refund_reasons_spec.rb b/admin/spec/features/refund_reasons_spec.rb
index ae5a52663eb..19f83464914 100644
--- a/admin/spec/features/refund_reasons_spec.rb
+++ b/admin/spec/features/refund_reasons_spec.rb
@@ -26,14 +26,14 @@
before do
visit "/admin/refund_reasons/#{query}"
click_on "Add new"
+ expect(page).to have_css("dialog", wait: 5)
expect(page).to have_content("New Refund Reason")
expect(page).to be_axe_clean
end
- it "opens a modal" do
- expect(page).to have_selector("dialog")
+ it "closing the modal keeps query params" do
within("dialog") { click_on "Cancel" }
- expect(page).not_to have_selector("dialog")
+ expect(page).not_to have_selector("dialog", wait: 5)
expect(page.current_url).to include(query)
end
@@ -66,14 +66,14 @@
Spree::RefundReason.create(name: "Return process")
visit "/admin/refund_reasons#{query}"
find_row("Return process").click
+ expect(page).to have_css("dialog", wait: 5)
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")
+ it "closing the modal keeps query params" do
within("dialog") { click_on "Cancel" }
- expect(page).not_to have_selector("dialog")
+ expect(page).not_to have_selector("dialog", wait: 5)
expect(page.current_url).to include(query)
end
diff --git a/admin/spec/features/return_reasons_spec.rb b/admin/spec/features/return_reasons_spec.rb
index 5932101bf41..41c978da8de 100644
--- a/admin/spec/features/return_reasons_spec.rb
+++ b/admin/spec/features/return_reasons_spec.rb
@@ -26,14 +26,14 @@
before do
visit "/admin/return_reasons#{query}"
click_on "Add new"
+ expect(page).to have_selector("dialog", wait: 5)
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")
+ it "closing the modal keeps query params" do
within("dialog") { click_on "Cancel" }
- expect(page).not_to have_selector("dialog")
+ expect(page).not_to have_selector("dialog", wait: 5)
expect(page.current_url).to include(query)
end
@@ -68,14 +68,14 @@
Spree::ReturnReason.create(name: "Good Reason")
visit "/admin/return_reasons#{query}"
find_row("Good Reason").click
+ expect(page).to have_selector("dialog", wait: 5)
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")
+ it "closing the modal keeps query params" do
within("dialog") { click_on "Cancel" }
- expect(page).not_to have_selector("dialog")
+ expect(page).not_to have_selector("dialog", wait: 5)
expect(page.current_url).to include(query)
end
diff --git a/admin/spec/features/roles_spec.rb b/admin/spec/features/roles_spec.rb
index 0132e2ed205..625d048e2a9 100644
--- a/admin/spec/features/roles_spec.rb
+++ b/admin/spec/features/roles_spec.rb
@@ -54,14 +54,14 @@
before do
visit "/admin/roles#{query}"
click_on "Add new"
+ expect(page).to have_selector("dialog", wait: 5)
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")
+ it "closing the modal keeps query params" do
within("dialog") { click_on "Cancel" }
- expect(page).not_to have_selector("dialog")
+ expect(page).not_to have_selector("dialog", wait: 5)
expect(page.current_url).to include(query)
end
@@ -121,16 +121,16 @@
Spree::Role.create(name: "Reviewer", permission_sets: [settings_edit_permission])
visit "/admin/roles#{query}"
find_row("Reviewer").click
+ expect(page).to have_selector("dialog", wait: 5)
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
- expect(page).to have_selector("dialog")
+ it "closing the modal keeps query params" do
within("dialog") { click_on "Cancel" }
- expect(page).not_to have_selector("dialog")
+ expect(page).not_to have_selector("dialog", wait: 5)
expect(page.current_url).to include(query)
end
diff --git a/admin/spec/features/shipping_categories_spec.rb b/admin/spec/features/shipping_categories_spec.rb
index 674ce76a00d..09ad72338fd 100644
--- a/admin/spec/features/shipping_categories_spec.rb
+++ b/admin/spec/features/shipping_categories_spec.rb
@@ -26,14 +26,14 @@
before do
visit "/admin/shipping_categories#{query}"
click_on "Add new"
+ expect(page).to have_css("dialog", wait: 5)
expect(page).to have_content("New Shipping Category")
expect(page).to be_axe_clean
end
- it "opens a modal" do
- expect(page).to have_selector("dialog")
+ it "closing the modal keeps query params" do
within("dialog") { click_on "Cancel" }
- expect(page).not_to have_selector("dialog")
+ expect(page).not_to have_selector("dialog", wait: 5)
expect(page.current_url).to include(query)
end
@@ -66,14 +66,14 @@
Spree::ShippingCategory.create(name: "Letter Mail")
visit "/admin/shipping_categories#{query}"
find_row("Letter Mail").click
+ expect(page).to have_css("dialog", wait: 5)
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")
+ it "closing the modal keeps query params" do
within("dialog") { click_on "Cancel" }
- expect(page).not_to have_selector("dialog")
+ expect(page).not_to have_selector("dialog", wait: 5)
expect(page.current_url).to include(query)
end
diff --git a/admin/spec/features/store_credit_reasons_spec.rb b/admin/spec/features/store_credit_reasons_spec.rb
index b4b2c46aff4..fcbe4bbea6c 100644
--- a/admin/spec/features/store_credit_reasons_spec.rb
+++ b/admin/spec/features/store_credit_reasons_spec.rb
@@ -26,14 +26,14 @@
before do
visit "/admin/store_credit_reasons#{query}"
click_on "Add new"
+ expect(page).to have_selector("dialog", wait: 5)
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")
+ it "closing the modal keeps query params" do
within("dialog") { click_on "Cancel" }
- expect(page).not_to have_selector("dialog")
+ expect(page).not_to have_selector("dialog", wait: 5)
expect(page.current_url).to include(query)
end
@@ -66,14 +66,14 @@
Spree::StoreCreditReason.create(name: "New Customer Reward")
visit "/admin/store_credit_reasons#{query}"
find_row("New Customer Reward").click
+ expect(page).to have_selector("dialog", wait: 5)
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")
+ it "closing the modal keeps query params" do
within("dialog") { click_on "Cancel" }
- expect(page).not_to have_selector("dialog")
+ expect(page).not_to have_selector("dialog", wait: 5)
expect(page.current_url).to include(query)
end
diff --git a/admin/spec/features/tax_categories_spec.rb b/admin/spec/features/tax_categories_spec.rb
index b91a24256dc..463f673bfdc 100644
--- a/admin/spec/features/tax_categories_spec.rb
+++ b/admin/spec/features/tax_categories_spec.rb
@@ -28,14 +28,14 @@
before do
visit "/admin/tax_categories#{query}"
click_on "Add new"
+ expect(page).to have_selector("dialog", wait: 5)
expect(page).to have_content("New Tax Category")
expect(page).to be_axe_clean
end
- it "opens a modal" do
- expect(page).to have_selector("dialog")
+ it "closing the modal keeps query params" do
within("dialog") { click_on "Cancel" }
- expect(page).not_to have_selector("dialog")
+ expect(page).not_to have_selector("dialog", wait: 5)
expect(page.current_url).to include(query)
end
diff --git a/api/script/rails b/api/script/rails
deleted file mode 100755
index f4f3cbf4353..00000000000
--- a/api/script/rails
+++ /dev/null
@@ -1,10 +0,0 @@
-#!/usr/bin/env ruby
-# frozen_string_literal: true
-
-# This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
-
-ENGINE_ROOT = File.expand_path('..', __dir__)
-ENGINE_PATH = File.expand_path('../lib/spree/api/engine', __dir__)
-
-require 'rails/all'
-require 'rails/engine/commands'
diff --git a/api/solidus_api.gemspec b/api/solidus_api.gemspec
index 90885c28ebd..f9ef877d116 100644
--- a/api/solidus_api.gemspec
+++ b/api/solidus_api.gemspec
@@ -17,7 +17,7 @@ Gem::Specification.new do |s|
s.metadata['rubygems_mfa_required'] = 'true'
s.files = `git ls-files -z`.split("\x0").reject do |f|
- f.match(%r{^(spec|script)/})
+ f.match(%r{^(spec|bin)/})
end
s.required_ruby_version = '>= 3.1.0'
diff --git a/backend/script/rails b/backend/script/rails
deleted file mode 100755
index d8e68052dd2..00000000000
--- a/backend/script/rails
+++ /dev/null
@@ -1,10 +0,0 @@
-#!/usr/bin/env ruby
-# frozen_string_literal: true
-
-# This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
-
-ENGINE_ROOT = File.expand_path('..', __dir__)
-ENGINE_PATH = File.expand_path('../lib/spree/backend/engine', __dir__)
-
-require 'rails/all'
-require 'rails/engine/commands'
diff --git a/backend/solidus_backend.gemspec b/backend/solidus_backend.gemspec
index 7d0fcd2b8cb..15783c7061d 100644
--- a/backend/solidus_backend.gemspec
+++ b/backend/solidus_backend.gemspec
@@ -17,7 +17,7 @@ Gem::Specification.new do |s|
s.metadata['rubygems_mfa_required'] = 'true'
s.files = `git ls-files -z`.split("\x0").reject do |f|
- f.match(%r{^(spec|script)/})
+ f.match(%r{^(spec|bin)/})
end
s.required_ruby_version = '>= 3.1.0'
diff --git a/backend/spec/controllers/spree/admin/users_controller_spec.rb b/backend/spec/controllers/spree/admin/users_controller_spec.rb
index d4e3a3e420e..c8ad0d41f61 100644
--- a/backend/spec/controllers/spree/admin/users_controller_spec.rb
+++ b/backend/spec/controllers/spree/admin/users_controller_spec.rb
@@ -505,7 +505,7 @@ def user
it "cannot be destroyed" do
is_expected.to be_forbidden
- expect(subject.body).to eq I18n.t("spree.error_user_destroy_with_orders")
+ expect(subject.body).to eq("Cannot delete a user with orders")
end
end
end
diff --git a/core/app/models/spree/line_item.rb b/core/app/models/spree/line_item.rb
index 8130c0ba8a9..396bee150ad 100644
--- a/core/app/models/spree/line_item.rb
+++ b/core/app/models/spree/line_item.rb
@@ -19,9 +19,6 @@ class LineItem < Spree::Base
has_many :adjustments, as: :adjustable, inverse_of: :adjustable, dependent: :destroy
has_many :inventory_units, inverse_of: :line_item
- has_many :line_item_actions, dependent: :destroy
- has_many :actions, through: :line_item_actions
-
before_validation :normalize_quantity
before_validation :set_required_attributes
diff --git a/core/app/models/spree/order_shipping.rb b/core/app/models/spree/order_shipping.rb
index e70ccd2b3bc..bfab5ecb910 100644
--- a/core/app/models/spree/order_shipping.rb
+++ b/core/app/models/spree/order_shipping.rb
@@ -44,7 +44,6 @@ def ship_shipment(shipment, external_number: nil, tracking_number: nil, suppress
# @return The carton created.
def ship(inventory_units:, stock_location:, address:, shipping_method:,
shipped_at: Time.current, external_number: nil, tracking_number: nil, suppress_mailer: false)
-
carton = nil
Spree::InventoryUnit.transaction do
diff --git a/core/config/locales/en.yml b/core/config/locales/en.yml
index 5968fab8ad5..02a8ca8aa07 100644
--- a/core/config/locales/en.yml
+++ b/core/config/locales/en.yml
@@ -1516,6 +1516,7 @@ en:
end: End
ending_in: Ending in
error: error
+ error_user_destroy_with_orders: Cannot delete a user with orders
errors:
messages:
cannot_delete_finalized_stock_location: Stock Location cannot be destroyed if you have open stock transfers.
@@ -2096,6 +2097,7 @@ en:
scope: Scope
search: Search
search_results: Search results for '%{keywords}'
+ search_results_all: Show all results for '%{keywords}'
searching: Searching
secure_connection_type: Secure Connection Type
security_settings: Security Settings
diff --git a/core/db/migrate/20160101010000_solidus_one_four.rb b/core/db/migrate/20160101010000_solidus_one_four.rb
index 317e9c99d5c..fe5e54facc8 100644
--- a/core/db/migrate/20160101010000_solidus_one_four.rb
+++ b/core/db/migrate/20160101010000_solidus_one_four.rb
@@ -197,16 +197,6 @@ def up
t.index ["variant_id"], name: "index_inventory_units_on_variant_id"
end
- create_table "spree_line_item_actions", force: :cascade do |t|
- t.integer "line_item_id", null: false
- t.integer "action_id", null: false
- t.integer "quantity", default: 0
- t.datetime "created_at", precision: 6
- t.datetime "updated_at", precision: 6
- t.index ["action_id"], name: "index_spree_line_item_actions_on_action_id"
- t.index ["line_item_id"], name: "index_spree_line_item_actions_on_line_item_id"
- end
-
create_table "spree_line_items", force: :cascade do |t|
t.integer "variant_id"
t.integer "order_id"
diff --git a/core/lib/spree/core.rb b/core/lib/spree/core.rb
index c9ca87e1198..e5c3fe42794 100644
--- a/core/lib/spree/core.rb
+++ b/core/lib/spree/core.rb
@@ -39,6 +39,10 @@ def self.user_class
end
end
+ def self.user_class_name
+ @@user_class
+ end
+
# Load the same version defaults for all available Solidus components
#
# @see Spree::Preferences::Configuration#load_defaults
diff --git a/core/lib/spree/core/version.rb b/core/lib/spree/core/version.rb
index c7121286cc1..6917d34eb11 100644
--- a/core/lib/spree/core/version.rb
+++ b/core/lib/spree/core/version.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module Spree
- VERSION = "4.4.0"
+ VERSION = "4.4.3.dev"
def self.solidus_version
VERSION
diff --git a/core/lib/spree/preferences/persistable.rb b/core/lib/spree/preferences/persistable.rb
index 252f6e296ed..0d78ae2b01a 100644
--- a/core/lib/spree/preferences/persistable.rb
+++ b/core/lib/spree/preferences/persistable.rb
@@ -8,7 +8,7 @@ module Persistable
included do
include Spree::Preferences::Preferable
- if method(:serialize).parameters.include?([:key, :type]) # Rails 7.1+
+ if Rails.gem_version >= Gem::Version.new('7.1')
serialize :preferences, type: Hash, coder: YAML
else
serialize :preferences, Hash, coder: YAML
diff --git a/core/lib/spree/testing_support/extension_rake.rb b/core/lib/spree/testing_support/extension_rake.rb
index f03d87ce527..4597fa4465a 100644
--- a/core/lib/spree/testing_support/extension_rake.rb
+++ b/core/lib/spree/testing_support/extension_rake.rb
@@ -4,8 +4,8 @@
desc "Generates a dummy app for testing an extension"
namespace :extension do
- task :test_app, [:user_class] do |_t, _args|
+ task :test_app, [:user_class] do |_t, args|
Spree::DummyGeneratorHelper.inject_extension_requirements = true
- Rake::Task['common:test_app'].invoke
+ Rake::Task['common:test_app'].invoke(args[:user_class])
end
end
diff --git a/core/lib/spree/user_class_handle.rb b/core/lib/spree/user_class_handle.rb
index 9df1da303a2..8e01cd83446 100644
--- a/core/lib/spree/user_class_handle.rb
+++ b/core/lib/spree/user_class_handle.rb
@@ -21,8 +21,8 @@ class UserClassHandle
# @return [String] the name of the user class as a string.
# @raise [RuntimeError] if Spree.user_class is nil
def to_s
- fail "'Spree.user_class' has not been set yet." unless Spree.user_class
- "::#{Spree.user_class}"
+ fail "'Spree.user_class' has not been set yet." unless Spree.user_class_name
+ "::#{Spree.user_class_name}"
end
end
end
diff --git a/core/script/rails b/core/script/rails
deleted file mode 100755
index 05c3838f737..00000000000
--- a/core/script/rails
+++ /dev/null
@@ -1,10 +0,0 @@
-#!/usr/bin/env ruby
-# frozen_string_literal: true
-
-# This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
-
-ENGINE_ROOT = File.expand_path('..', __dir__)
-ENGINE_PATH = File.expand_path('../lib/spree/core/engine', __dir__)
-
-require 'rails/all'
-require 'rails/engine/commands'
diff --git a/core/solidus_core.gemspec b/core/solidus_core.gemspec
index 330e7f81306..3d4a7f5c108 100644
--- a/core/solidus_core.gemspec
+++ b/core/solidus_core.gemspec
@@ -17,7 +17,7 @@ Gem::Specification.new do |s|
s.metadata['rubygems_mfa_required'] = 'true'
s.files = `git ls-files -z`.split("\x0").reject do |f|
- f.match(%r{^(spec|script)/})
+ f.match(%r{^(spec|bin)/})
end
s.required_ruby_version = '>= 3.1.0'
diff --git a/core/spec/lib/spree/user_class_handle_spec.rb b/core/spec/lib/spree/user_class_handle_spec.rb
new file mode 100644
index 00000000000..b9055b9062d
--- /dev/null
+++ b/core/spec/lib/spree/user_class_handle_spec.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+require "spec_helper"
+require "spree/core"
+require "spree/user_class_handle"
+
+RSpec.describe Spree::UserClassHandle do
+ describe "#to_s" do
+ around do |example|
+ @prev_user_class = Spree.user_class_name
+ example.run
+ Spree.user_class = @prev_user_class
+ end
+
+ subject { described_class.new.to_s }
+
+ context "when Spree.user_class is nil" do
+ before do
+ Spree.user_class = nil
+ end
+
+ it "is expected to fail" do
+ expect { subject }.to raise_error(RuntimeError, "'Spree.user_class' has not been set yet.")
+ end
+ end
+
+ context "when Spree.user_class is not nil" do
+ before do
+ Spree.user_class = "Spree::User"
+ end
+
+ it "is expected to return the user class as a string" do
+ expect(subject).to eq("::Spree::User")
+ end
+ end
+ end
+end
diff --git a/legacy_promotions/app/assets/stylesheets/solidus_legacy_promotions/promotions.scss b/legacy_promotions/app/assets/stylesheets/solidus_legacy_promotions/promotions.scss
index eee8b872043..4a37ef631b1 100644
--- a/legacy_promotions/app/assets/stylesheets/solidus_legacy_promotions/promotions.scss
+++ b/legacy_promotions/app/assets/stylesheets/solidus_legacy_promotions/promotions.scss
@@ -1,3 +1,3 @@
-@import "spree/backend/themes/classic";
+@import "spree/backend/globals/variables";
@import "solidus_legacy_promotions/promotions/edit";
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
index f7d76204e41..58147729943 100644
--- 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
@@ -2,6 +2,11 @@
module SolidusLegacyPromotions
module SpreeLineItemDecorator
+ def self.prepended(base)
+ base.has_many :line_item_actions, dependent: :destroy
+ base.has_many :actions, through: :line_item_actions
+ end
+
def total_before_tax
amount + adjustments.select { |value| !value.tax? && value.eligible? }.sum(&:amount)
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
deleted file mode 100644
index fdb8c85454b..00000000000
--- a/legacy_promotions/app/decorators/solidus_legacy_promotions/models/spree_promotion_code_batch_decorator.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# 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/app/models/spree/line_item_action.rb b/legacy_promotions/app/models/spree/line_item_action.rb
similarity index 100%
rename from core/app/models/spree/line_item_action.rb
rename to legacy_promotions/app/models/spree/line_item_action.rb
diff --git a/legacy_promotions/app/models/spree/promotion_code_batch.rb b/legacy_promotions/app/models/spree/promotion_code_batch.rb
index 66cbcc50d6a..85a17be14be 100644
--- a/legacy_promotions/app/models/spree/promotion_code_batch.rb
+++ b/legacy_promotions/app/models/spree/promotion_code_batch.rb
@@ -14,5 +14,14 @@ 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/config/locales/en.yml b/legacy_promotions/config/locales/en.yml
index 6a6b00b220b..906570b727c 100644
--- a/legacy_promotions/config/locales/en.yml
+++ b/legacy_promotions/config/locales/en.yml
@@ -1,4 +1,8 @@
en:
+ solidus_admin:
+ menu_item:
+ legacy_promotions: Promotions
+ legacy_promotion_categories: Promotion Categories
spree:
admin:
promotion_status:
@@ -23,8 +27,8 @@ en:
general: General
starts_at_placeholder: Immediately
tab:
- promotion_categories: Promotion Categories
- promotions: Promotions
+ legacy_promotions: Promotions
+ legacy_promotion_categories: Promotion Categories
back_to_promotion_categories_list: Back To Promotions Categories List
back_to_promotions_list: Back To Promotions List
base_amount: Base Amount
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 645019b44b3..733f9e01ccf 100644
--- a/legacy_promotions/db/migrate/20160101010001_solidus_one_four_promotions.rb
+++ b/legacy_promotions/db/migrate/20160101010001_solidus_one_four_promotions.rb
@@ -122,5 +122,17 @@ def up
t.index ["starts_at"], name: "index_spree_promotions_on_starts_at"
end
end
+
+ unless table_exists?(:spree_line_item_actions)
+ create_table "spree_line_item_actions", force: :cascade do |t|
+ t.integer "line_item_id", null: false
+ t.integer "action_id", null: false
+ t.integer "quantity", default: 0
+ t.datetime "created_at", precision: 6
+ t.datetime "updated_at", precision: 6
+ t.index ["action_id"], name: "index_spree_line_item_actions_on_action_id"
+ t.index ["line_item_id"], name: "index_spree_line_item_actions_on_line_item_id"
+ end
+ end
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
index 454b31bbdb7..3829db2d825 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,20 +1,5 @@
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
diff --git a/legacy_promotions/lib/solidus_legacy_promotions/engine.rb b/legacy_promotions/lib/solidus_legacy_promotions/engine.rb
index b1526f4eb42..ba7fe49583e 100644
--- a/legacy_promotions/lib/solidus_legacy_promotions/engine.rb
+++ b/legacy_promotions/lib/solidus_legacy_promotions/engine.rb
@@ -9,7 +9,7 @@ class Engine < ::Rails::Engine
initializer "solidus_legacy_promotions.add_backend_menu_item" do
if SolidusSupport.backend_available?
promotions_menu_item = Spree::BackendConfiguration::MenuItem.new(
- label: :promotions,
+ label: :legacy_promotions,
icon: Spree::Backend::Config.admin_updated_navbar ? "ri-megaphone-line" : "bullhorn",
partial: "spree/admin/shared/promotion_sub_menu",
condition: -> { can?(:admin, Spree::Promotion) },
@@ -17,12 +17,14 @@ class Engine < ::Rails::Engine
data_hook: :admin_promotion_sub_tabs,
children: [
Spree::BackendConfiguration::MenuItem.new(
- label: :promotions,
- condition: -> { can?(:admin, Spree::Promotion) }
+ label: :legacy_promotions,
+ condition: -> { can?(:admin, Spree::Promotion) },
+ url: :admin_promotions_path
),
Spree::BackendConfiguration::MenuItem.new(
- label: :promotion_categories,
- condition: -> { can?(:admin, Spree::PromotionCategory) }
+ label: :legacy_promotion_categories,
+ condition: -> { can?(:admin, Spree::PromotionCategory) },
+ url: -> { Spree::Core::Engine.routes.url_helpers.admin_promotion_categories_path },
)
]
)
@@ -41,10 +43,22 @@ class Engine < ::Rails::Engine
if SolidusSupport.admin_available?
SolidusAdmin::Config.configure do |config|
config.menu_items << {
- key: "promotions",
+ key: "legacy_promotions",
route: -> { spree.admin_promotions_path },
icon: "megaphone-line",
- position: 30
+ position: 1.5,
+ children: [
+ {
+ key: "legacy_promotions",
+ route: -> { spree.admin_promotions_path },
+ position: 1
+ },
+ {
+ key: "legacy_promotion_categories",
+ route: -> { spree.admin_promotion_categories_path },
+ position: 2
+ }
+ ]
}
end
end
@@ -86,12 +100,17 @@ class Engine < ::Rails::Engine
end
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"
+ # Only set these if there is no promotion configuration set. In this case,
+ # we're running on a store without the new `solidus_promotions` gem and we
+ # need to set the configuration to the legacy one.
+ if Spree::Config.promotions.is_a?(Spree::Core::NullPromotionConfiguration)
+ Spree::Config.order_contents_class = "Spree::OrderContents"
+ Spree::Config.promotions = SolidusLegacyPromotions::Configuration.new
+ end
Spree::Api::Config.adjustment_attributes << :promotion_code_id
Spree::Api::Config.adjustment_attributes << :eligible
+ Spree::Config.adjustment_promotion_source_types << "Spree::PromotionAction"
end
end
end
diff --git a/legacy_promotions/solidus_legacy_promotions.gemspec b/legacy_promotions/solidus_legacy_promotions.gemspec
index 8f5586e6839..fdd1bcddaac 100644
--- a/legacy_promotions/solidus_legacy_promotions.gemspec
+++ b/legacy_promotions/solidus_legacy_promotions.gemspec
@@ -17,7 +17,7 @@ Gem::Specification.new do |s|
s.metadata['rubygems_mfa_required'] = 'true'
s.files = `git ls-files -z`.split("\x0").reject do |f|
- f.match(%r{^(spec|script)/})
+ f.match(%r{^(spec|bin)/})
end
s.required_ruby_version = '>= 3.1.0'
diff --git a/legacy_promotions/spec/features/solidus_admin/promotions_spec.rb b/legacy_promotions/spec/features/solidus_admin/promotions_spec.rb
index 16f1439ace7..922846e2a51 100644
--- a/legacy_promotions/spec/features/solidus_admin/promotions_spec.rb
+++ b/legacy_promotions/spec/features/solidus_admin/promotions_spec.rb
@@ -14,13 +14,13 @@
visit "/admin/promotions"
expect(page).to have_content("My active Promotion")
click_on "Draft"
- expect(page).to have_content("My draft Promotion", wait: 30)
+ expect(page).to have_content("My draft Promotion", wait: 5)
click_on "Future"
- expect(page).to have_content("My future Promotion", wait: 30)
+ expect(page).to have_content("My future Promotion", wait: 5)
click_on "Expired"
- expect(page).to have_content("My expired Promotion", wait: 30)
+ expect(page).to have_content("My expired Promotion", wait: 5)
click_on "All"
- expect(page).to have_content("My active Promotion", wait: 30)
+ expect(page).to have_content("My active Promotion", wait: 5)
expect(page).to have_content("My draft Promotion")
expect(page).to have_content("My future Promotion")
expect(page).to have_content("My expired Promotion")
diff --git a/promotions/README.md b/promotions/README.md
index 4103f90b2c1..03fc841984d 100644
--- a/promotions/README.md
+++ b/promotions/README.md
@@ -2,17 +2,22 @@
This gem contains Solidus' recommended promotion system. It is slated to replace the promotion system in the `legacy_promotions` gem.
-The basic architecture is very similar to the legacy promotion system, but with a few decisive tweaks, which I'll explain in the coming sections.
+The basic architecture is very similar to the legacy promotion system, but with a few decisive tweaks, which are explained in the subsequent sections.
+
+> [!IMPORTANT]
+> If you are upgrading from a previous version of Solidus, it is advised that you update also the promotion system to the new gem. Please consult the [Migration Guide](./MIGRATING.md).
+> While the current version of Solidus still installs the legacy promotion system, we advise a migration at the ealiest convinience to avoid having to rush the migration in the future.
+
## Architecture
-This extension centralizes promotion handling in the order updater. A service class, the `SolidusPromotions::OrderAdjuster` applies the current promotion configuration to the order, adjusting or removing adjustments as necessary.
+This extension centralizes promotion handling in the order updater significantly improving the performance of cart calculations. A service class, the `SolidusPromotions::OrderAdjuster` applies the current promotion configuration to the order, adjusting or removing adjustments as necessary.
`SolidusPromotions::Promotion` objects have benefits, and benefits have conditions. For example, a promotion that is "20% off shirts" would have a benefit of type "AdjustLineItem", and that benefit would have a condition of type "LineItemTaxon" that makes sure only line items with the "shirts" taxon will get the benefit.
### Promotion lanes
-Promotions get applied by "lane". Promotions within a lane conflict with each other, whereas promotions that do not share a lane will apply sequentially in the order of the lanes. By default these are "pre", "default" and "post", but you can configure this using the SolidusPromotions initializer:
+Promotions get applied by "lane". Promotions within a lane conflict with each other, whereas promotions that do not share a lane will apply sequentially in the order of the lanes. By default these are "pre", "default" and "post", but they can be configured using the SolidusPromotions initializer:
```rb
SolidusPromotions.configure do |config|
@@ -25,11 +30,9 @@ SolidusPromotions.configure do |config|
end
```
-### Benefits
-
-Solidus Friendly Promotions ships with only three benefit types by default that should cover most use cases: `AdjustLineItem`, `AdjustShipment` and `CreateDiscountedItem`. There is no benefit that creates order-level adjustments, as this feature of Solidus' legacy promotions system 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` benefit with the `DistributedAmount` calculator.
+### Types of Benefits
-Alle benefits are calculable. By setting their `calculator` to one of the classes provided, a great range of discounts is possible.
+The new Solidus Promotions Systems ships with three benefit types: `AdjustLineItem`, `AdjustShipment` and `CreateDiscountedItem`. To allow more efficient processing of returns and reduce the complexity for bookkeping and support departmentes fixed discount to all line items in an order can now be given through the `AdjustLineItem` benefit through the `DistributedAmount` calculator significantly reducing amministration issues with refunds.
#### `AdjustLineItem`
diff --git a/promotions/app/models/solidus_promotions/order_adjuster.rb b/promotions/app/models/solidus_promotions/order_adjuster.rb
index 8769670be57..ea16b895166 100644
--- a/promotions/app/models/solidus_promotions/order_adjuster.rb
+++ b/promotions/app/models/solidus_promotions/order_adjuster.rb
@@ -13,7 +13,7 @@ def initialize(order, dry_run_promotion: nil)
def call
order.reset_current_discounts
- return order if (!SolidusPromotions.config.recalculate_complete_orders && order.complete?) || order.shipped?
+ return order unless SolidusPromotions::Promotion.order_activatable?(order)
discounted_order = DiscountOrder.new(order, promotions, dry_run: dry_run).call
diff --git a/promotions/app/models/solidus_promotions/promotion.rb b/promotions/app/models/solidus_promotions/promotion.rb
index e36015facac..fced9b24d75 100644
--- a/promotions/app/models/solidus_promotions/promotion.rb
+++ b/promotions/app/models/solidus_promotions/promotion.rb
@@ -2,6 +2,8 @@
module SolidusPromotions
class Promotion < Spree::Base
+ UNACTIVATABLE_ORDER_STATES = ["awaiting_return", "returned", "canceled"]
+
include Spree::SoftDeletable
belongs_to :category, class_name: "SolidusPromotions::PromotionCategory",
@@ -9,7 +11,7 @@ class Promotion < Spree::Base
belongs_to :original_promotion, class_name: "Spree::Promotion", optional: true
has_many :benefits, class_name: "SolidusPromotions::Benefit", dependent: :destroy
has_many :conditions, through: :benefits
- has_many :codes, class_name: "SolidusPromotions::PromotionCode", dependent: :destroy
+ has_many :codes, class_name: "SolidusPromotions::PromotionCode", dependent: :destroy, inverse_of: :promotion
has_many :code_batches, class_name: "SolidusPromotions::PromotionCodeBatch", dependent: :destroy
has_many :order_promotions, class_name: "SolidusPromotions::OrderPromotion", dependent: :destroy
@@ -59,6 +61,14 @@ def self.ordered_lanes
lanes.sort_by(&:last).to_h
end
+ def self.order_activatable?(order)
+ return false if UNACTIVATABLE_ORDER_STATES.include?(order.state)
+ return false if order.shipped?
+ return false if order.complete? && !SolidusPromotions.config.recalculate_complete_orders
+
+ true
+ end
+
self.allowed_ransackable_associations = ["codes"]
self.allowed_ransackable_attributes = %w[name customer_label path promotion_category_id lane updated_at]
self.allowed_ransackable_scopes = %i[active with_discarded]
diff --git a/promotions/app/models/solidus_promotions/promotion_handler/coupon.rb b/promotions/app/models/solidus_promotions/promotion_handler/coupon.rb
index 48db345a41e..99fe4918b66 100644
--- a/promotions/app/models/solidus_promotions/promotion_handler/coupon.rb
+++ b/promotions/app/models/solidus_promotions/promotion_handler/coupon.rb
@@ -26,6 +26,10 @@ def apply
self
end
+ def can_apply?
+ SolidusPromotions::Promotion.order_activatable?(order)
+ end
+
def remove
if promotion.blank?
set_error_code :coupon_code_not_found
diff --git a/promotions/config/locales/en.yml b/promotions/config/locales/en.yml
index 19b07d81f2f..d4b56d3ccb8 100644
--- a/promotions/config/locales/en.yml
+++ b/promotions/config/locales/en.yml
@@ -2,13 +2,15 @@
# See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points.
en:
+ solidus_admin:
+ menu_item:
+ promotions: Promotions (new)
+ promotion_categories: Promotion Categories (new)
spree:
admin:
tab:
- promotions: Promotions
- promotion_categories: Promotion Categories
- legacy_promotions: Legacy Promotions
- legacy_promotion_categories: Legacy Promotion Categories
+ promotions: Promotions (new)
+ promotion_categories: Promotion Categories (new)
hints:
solidus_promotions/promotion:
expires_at: This determines when the promotion expires.
If no value is specified, the promotion will never expire.
diff --git a/promotions/lib/components/admin/solidus_admin/orders/show/adjustments/index/source/solidus_promotions_benefit/component.rb b/promotions/lib/components/admin/solidus_admin/orders/show/adjustments/index/source/solidus_promotions_benefit/component.rb
index 3f24c7c478f..0ceee3408cf 100644
--- a/promotions/lib/components/admin/solidus_admin/orders/show/adjustments/index/source/solidus_promotions_benefit/component.rb
+++ b/promotions/lib/components/admin/solidus_admin/orders/show/adjustments/index/source/solidus_promotions_benefit/component.rb
@@ -10,8 +10,4 @@ def detail
def promotion_name
source.promotion.name
end
-
- def solidus_promotions
- @solidus_promotions ||= SolidusPromotions::Engine.routes.url_helpers
- end
end
diff --git a/promotions/lib/components/admin/solidus_promotions/orders/index/component.yml b/promotions/lib/components/admin/solidus_promotions/orders/index/component.yml
index 95af3f495d4..3829db2d825 100644
--- a/promotions/lib/components/admin/solidus_promotions/orders/index/component.yml
+++ b/promotions/lib/components/admin/solidus_promotions/orders/index/component.yml
@@ -1,3 +1,5 @@
en:
filters:
promotions: Promotions
+ scopes:
+ promotions: Promotions
diff --git a/promotions/lib/components/admin/solidus_promotions/promotions/index/component.rb b/promotions/lib/components/admin/solidus_promotions/promotions/index/component.rb
index 19cf4b79772..8416e5d343c 100644
--- a/promotions/lib/components/admin/solidus_promotions/promotions/index/component.rb
+++ b/promotions/lib/components/admin/solidus_promotions/promotions/index/component.rb
@@ -14,7 +14,7 @@ def search_url
end
def row_url(promotion)
- solidus_promotions.admin_promotion_path(promotion)
+ solidus_promotions.edit_admin_promotion_path(promotion)
end
def page_actions
@@ -63,14 +63,16 @@ def columns
{
header: :name,
data: ->(promotion) do
- content_tag :div, promotion.name
+ link_to promotion.name, row_url(promotion)
end
},
{
header: :code,
data: ->(promotion) do
- count = promotion.codes.count
- (count == 1) ? promotion.codes.pick(:value) : t("spree.number_of_codes", count: count)
+ link_to solidus_promotions.admin_promotion_promotion_codes_path(promotion), title: t(".codes") do
+ count = promotion.codes.count
+ (count == 1) ? promotion.codes.pick(:value) : t("spree.number_of_codes", count: count)
+ end
end
},
{
@@ -101,8 +103,4 @@ def columns
}
]
end
-
- def solidus_promotions
- @solidus_promotions ||= SolidusPromotions::Engine.routes.url_helpers
- end
end
diff --git a/promotions/lib/components/admin/solidus_promotions/promotions/index/component.yml b/promotions/lib/components/admin/solidus_promotions/promotions/index/component.yml
index 7ba70160390..cf53ebf0d54 100644
--- a/promotions/lib/components/admin/solidus_promotions/promotions/index/component.yml
+++ b/promotions/lib/components/admin/solidus_promotions/promotions/index/component.yml
@@ -8,3 +8,4 @@ en:
status:
active: Active
inactive: Inactive
+ codes: Codes
diff --git a/promotions/lib/controllers/admin/solidus_promotions/promotion_categories_controller.rb b/promotions/lib/controllers/admin/solidus_promotions/promotion_categories_controller.rb
index f27166e38d5..0218e3a94c2 100644
--- a/promotions/lib/controllers/admin/solidus_promotions/promotion_categories_controller.rb
+++ b/promotions/lib/controllers/admin/solidus_promotions/promotion_categories_controller.rb
@@ -13,7 +13,7 @@ def index
set_page_and_extract_portion_from(promotion_categories)
respond_to do |format|
- format.html { render component("promotion_categories/index").new(page: @page) }
+ format.html { render component("solidus_promotions/categories/index").new(page: @page) }
end
end
@@ -25,5 +25,11 @@ def destroy
flash[:notice] = t(".success")
redirect_back_or_to solidus_promotions.promotion_categories_path, status: :see_other
end
+
+ private
+
+ def authorization_subject
+ SolidusPromotions::PromotionCategory
+ end
end
end
diff --git a/promotions/lib/controllers/admin/solidus_promotions/promotions_controller.rb b/promotions/lib/controllers/admin/solidus_promotions/promotions_controller.rb
index de2fcf25f78..62b856006a0 100644
--- a/promotions/lib/controllers/admin/solidus_promotions/promotions_controller.rb
+++ b/promotions/lib/controllers/admin/solidus_promotions/promotions_controller.rb
@@ -19,7 +19,7 @@ def index
set_page_and_extract_portion_from(promotions)
respond_to do |format|
- format.html { render component("promotions/index").new(page: @page) }
+ format.html { render component("solidus_promotions/promotions/index").new(page: @page) }
end
end
@@ -42,5 +42,9 @@ def load_promotion
def promotion_params
params.require(:promotion).permit(:user_id, permitted_promotion_attributes)
end
+
+ def authorization_subject
+ SolidusPromotions::Promotion
+ end
end
end
diff --git a/promotions/lib/controllers/backend/solidus_promotions/admin/benefits_controller.rb b/promotions/lib/controllers/backend/solidus_promotions/admin/benefits_controller.rb
index cd0f40da056..354a37ac8ba 100644
--- a/promotions/lib/controllers/backend/solidus_promotions/admin/benefits_controller.rb
+++ b/promotions/lib/controllers/backend/solidus_promotions/admin/benefits_controller.rb
@@ -14,12 +14,12 @@ def new
def create
@benefit = @benefit_type.new(benefit_params)
@benefit.promotion = @promotion
- if @benefit.save(validate: false)
+ if @benefit.save
flash[:success] =
t("spree.successfully_created", resource: SolidusPromotions::Benefit.model_name.human)
redirect_to location_after_save, format: :html
else
- render :new, layout: false
+ render :new, layout: false, status: :unprocessable_entity
end
end
@@ -28,7 +28,7 @@ def edit
if params.dig(:benefit, :calculator_type)
@benefit.calculator_type = params[:benefit][:calculator_type]
end
- render layout: false
+ render layout: false, status: :unprocessable_entity
end
def update
@@ -39,7 +39,7 @@ def update
t("spree.successfully_updated", resource: SolidusPromotions::Benefit.model_name.human)
redirect_to location_after_save, format: :html
else
- render :edit
+ render :edit, status: :unprocessable_entity
end
end
diff --git a/promotions/lib/controllers/backend/solidus_promotions/admin/promotion_codes_controller.rb b/promotions/lib/controllers/backend/solidus_promotions/admin/promotion_codes_controller.rb
index ee23a457469..0310fc54ceb 100644
--- a/promotions/lib/controllers/backend/solidus_promotions/admin/promotion_codes_controller.rb
+++ b/promotions/lib/controllers/backend/solidus_promotions/admin/promotion_codes_controller.rb
@@ -48,6 +48,10 @@ def create
private
+ def model_class
+ SolidusPromotions::PromotionCode
+ end
+
def load_promotion
@promotion = SolidusPromotions::Promotion
.accessible_by(current_ability, :show)
diff --git a/promotions/lib/solidus_promotions/engine.rb b/promotions/lib/solidus_promotions/engine.rb
index 97330073d1b..29df050ba6d 100644
--- a/promotions/lib/solidus_promotions/engine.rb
+++ b/promotions/lib/solidus_promotions/engine.rb
@@ -52,64 +52,71 @@ class Engine < Rails::Engine
end
end
- initializer "solidus_promotions.add_admin_order_index_component", after: "solidus_legacy_promotions.add_admin_order_index_component" do
+ initializer "solidus_promotions.add_admin_order_index_component", after: "spree.load_config_initializers" do
if SolidusSupport.admin_available?
- SolidusAdmin::Config.components["orders/index"] = "SolidusPromotions::Orders::Index::Component"
- SolidusAdmin::Config.components["promotions/index"] = "SolidusPromotions::Promotions::Index::Component"
- SolidusAdmin::Config.components["promotion_categories/index"] = "SolidusPromotions::PromotionCategories::Index::Component"
+ if Spree::Config.promotions.is_a?(SolidusPromotions::Configuration)
+ SolidusAdmin::Config.components["orders/index"] = "SolidusPromotions::Orders::Index::Component"
+ end
+
+ SolidusAdmin::Config.components["solidus_promotions/promotions/index"] = "SolidusPromotions::Promotions::Index::Component"
+ SolidusAdmin::Config.components["solidus_promotions/categories/index"] = "SolidusPromotions::PromotionCategories::Index::Component"
end
end
- initializer "solidus_promotions.add_backend_menus", after: "spree.backend.environment" do
- if SolidusSupport.backend_available?
- # Replace the promotions menu from core with ours
- Spree::Backend::Config.configure do |config|
- config.menu_items = config.menu_items.flat_map do |item|
- next item unless item.label.to_sym == :promotions
-
- [
- Spree::BackendConfiguration::MenuItem.new(
- label: :promotions,
- icon: config.admin_updated_navbar ? "ri-megaphone-line" : "bullhorn",
- condition: -> { can?(:admin, SolidusPromotions::Promotion) },
- url: -> { SolidusPromotions::Engine.routes.url_helpers.admin_promotions_path },
- data_hook: :admin_solidus_promotion_sub_tabs,
- children: [
- Spree::BackendConfiguration::MenuItem.new(
- label: :promotions,
- url: -> { SolidusPromotions::Engine.routes.url_helpers.admin_promotions_path },
- condition: -> { can?(:admin, SolidusPromotions::Promotion) }
- ),
- Spree::BackendConfiguration::MenuItem.new(
- label: :promotion_categories,
- url: -> { SolidusPromotions::Engine.routes.url_helpers.admin_promotion_categories_path },
- condition: -> { can?(:admin, SolidusPromotions::PromotionCategory) }
- )
- ]
- ),
- Spree::BackendConfiguration::MenuItem.new(
- label: :legacy_promotions,
- icon: config.admin_updated_navbar ? "ri-megaphone-line" : "bullhorn",
- condition: -> { can?(:admin, SolidusPromotions::Promotion) },
- url: -> { Spree::Core::Engine.routes.url_helpers.admin_promotions_path },
- data_hook: :admin_promotion_sub_tabs,
- children: [
- Spree::BackendConfiguration::MenuItem.new(
- label: :legacy_promotions,
- condition: -> { can?(:admin, Spree::Promotion && Spree::Promotion.any?) },
- url: -> { Spree::Core::Engine.routes.url_helpers.admin_promotions_path },
- ),
- Spree::BackendConfiguration::MenuItem.new(
- label: :legacy_promotion_categories,
- condition: -> { can?(:admin, Spree::PromotionCategory && Spree::Promotion.any?) },
- url: -> { Spree::Core::Engine.routes.url_helpers.admin_promotion_categories_path },
- )
- ]
- )
+ initializer "solidus_promotions.add_solidus_admin_menu_items", after: "spree.load_config_initializers" do
+ if SolidusSupport.admin_available?
+ SolidusAdmin::Config.configure do |config|
+ config.menu_items << {
+ key: "promotions",
+ route: -> { solidus_promotions.admin_promotions_path },
+ icon: "megaphone-line",
+ position: 1.6,
+ children: [
+ {
+ key: "promotions",
+ route: -> { solidus_promotions.admin_promotions_path },
+ position: 1
+ },
+ {
+ key: "promotion_categories",
+ route: -> { solidus_promotions.admin_promotion_categories_path },
+ position: 1
+ }
]
- end
+ }
end
end
end
+
+ initializer "solidus_promotions.add_backend_menus", after: "spree.backend.environment" do
+ if SolidusSupport.backend_available?
+ promotions_menu_item = Spree::BackendConfiguration::MenuItem.new(
+ label: :promotions,
+ icon: Spree::Backend::Config.admin_updated_navbar ? "ri-megaphone-line" : "bullhorn",
+ condition: -> { can?(:admin, SolidusPromotions::Promotion) },
+ url: -> { SolidusPromotions::Engine.routes.url_helpers.admin_promotions_path },
+ data_hook: :admin_solidus_promotion_sub_tabs,
+ children: [
+ Spree::BackendConfiguration::MenuItem.new(
+ label: :promotions,
+ url: -> { SolidusPromotions::Engine.routes.url_helpers.admin_promotions_path },
+ condition: -> { can?(:admin, SolidusPromotions::Promotion) }
+ ),
+ Spree::BackendConfiguration::MenuItem.new(
+ label: :promotion_categories,
+ url: -> { SolidusPromotions::Engine.routes.url_helpers.admin_promotion_categories_path },
+ condition: -> { can?(:admin, SolidusPromotions::PromotionCategory) }
+ )
+ ]
+ )
+
+ # We want to appear after the legacy promotions menu item if it exists, otherwise after the products menu item
+ product_menu_item_index = Spree::Backend::Config.menu_items.find_index { |item| item.label == :products }
+ legacy_promotions_menu_item = Spree::Backend::Config.menu_items.find_index { |item| item.label == :legacy_promotions }
+ promotions_menu_index = [product_menu_item_index, legacy_promotions_menu_item].compact.max + 1
+
+ Spree::Backend::Config.menu_items.insert(promotions_menu_index, promotions_menu_item)
+ end
+ end
end
end
diff --git a/promotions/solidus_promotions.gemspec b/promotions/solidus_promotions.gemspec
index 7f67644ee32..fd3f6a35348 100644
--- a/promotions/solidus_promotions.gemspec
+++ b/promotions/solidus_promotions.gemspec
@@ -17,12 +17,12 @@ Gem::Specification.new do |spec|
spec.metadata["homepage_uri"] = spec.homepage
- spec.required_ruby_version = ">= 3.0.0"
+ spec.required_ruby_version = ">= 3.1.0"
# 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.files = files.grep_v(%r{^(spec|bin)/})
spec.add_dependency "importmap-rails", "~> 1.2"
spec.add_dependency "ransack-enum", "~> 1.0"
diff --git a/promotions/spec/models/solidus_promotions/promotion_handler/coupon_spec.rb b/promotions/spec/models/solidus_promotions/promotion_handler/coupon_spec.rb
index 0e82d6c8bdc..7b6df6178de 100644
--- a/promotions/spec/models/solidus_promotions/promotion_handler/coupon_spec.rb
+++ b/promotions/spec/models/solidus_promotions/promotion_handler/coupon_spec.rb
@@ -452,4 +452,15 @@ def expect_adjustment_creation(adjustable:, promotion:)
)
end
end
+
+ describe "#can_apply?" do
+ let(:order) { double("Order").as_null_object }
+
+ subject { described_class.new(order).can_apply? }
+
+ it "forwards to SolidusPromotions::Promotion.order_activatable?" do
+ expect(SolidusPromotions::Promotion).to receive(:order_activatable?).with(order)
+ subject
+ end
+ end
end
diff --git a/promotions/spec/models/solidus_promotions/promotion_spec.rb b/promotions/spec/models/solidus_promotions/promotion_spec.rb
index e2fea9fad2a..6d941eabf6b 100644
--- a/promotions/spec/models/solidus_promotions/promotion_spec.rb
+++ b/promotions/spec/models/solidus_promotions/promotion_spec.rb
@@ -686,4 +686,56 @@
expect(subject).to be_nil
end
end
+
+ describe ".order_activatable" do
+ let(:order) { create :order }
+
+ subject { described_class.order_activatable?(order) }
+
+ it "is true" do
+ expect(subject).to be true
+ end
+
+ context "when the order is in the cart state" do
+ let(:order) { create :order, state: "cart" }
+
+ it { is_expected.to be true }
+ end
+
+ context "when the order is shipped" do
+ let(:order) { create :order, state: "complete", shipment_state: "shipped" }
+
+ it { is_expected.to be false }
+ end
+
+ context "when the order is completed but not shipped" do
+ let(:order) { create :order, state: "complete", shipment_state: "ready" }
+
+ it { is_expected.to be true }
+
+ context "when the promotion system is configured to prohibit applying promotions to completed orders" do
+ before { stub_spree_preferences(SolidusPromotions.configuration, recalculate_complete_orders: false) }
+
+ it { is_expected.to be false }
+ end
+ end
+
+ context "when the order is canceled" do
+ let(:order) { create :order, state: "canceled" }
+
+ it { is_expected.to be false }
+ end
+
+ context "when the order is awaiting return" do
+ let(:order) { create :order, state: "awaiting_return" }
+
+ it { is_expected.to be false }
+ end
+
+ context "when the order is returned" do
+ let(:order) { create :order, state: "returned" }
+
+ it { is_expected.to be false }
+ end
+ end
end
diff --git a/promotions/spec/system/solidus_promotions/admin/orders/index_spec.rb b/promotions/spec/system/solidus_promotions/admin/orders/index_spec.rb
index dc6304eb355..159ef8a0a31 100644
--- a/promotions/spec/system/solidus_promotions/admin/orders/index_spec.rb
+++ b/promotions/spec/system/solidus_promotions/admin/orders/index_spec.rb
@@ -9,6 +9,12 @@
before { sign_in create(:admin_user, email: "admin@example.com") }
+ around do |example|
+ SolidusAdmin::Config.components["orders/index"] = "SolidusPromotions::Orders::Index::Component"
+ example.run
+ SolidusAdmin::Config.components["orders/index"] = "SolidusAdmin::Orders::Index::Component"
+ end
+
it "lists products", :js, :flaky do
visit "/admin/orders"
diff --git a/promotions/spec/system/solidus_promotions/admin/promotions_spec.rb b/promotions/spec/system/solidus_promotions/admin/promotions_spec.rb
index b49bbd94d37..c02a8066084 100644
--- a/promotions/spec/system/solidus_promotions/admin/promotions_spec.rb
+++ b/promotions/spec/system/solidus_promotions/admin/promotions_spec.rb
@@ -14,13 +14,13 @@
visit "/admin/solidus/promotions"
expect(page).to have_content("My active Promotion")
click_on "Draft"
- expect(page).to have_content("My draft Promotion", wait: 30)
+ expect(page).to have_content("My draft Promotion", wait: 5)
click_on "Future"
- expect(page).to have_content("My future Promotion", wait: 30)
+ expect(page).to have_content("My future Promotion", wait: 5)
click_on "Expired"
- expect(page).to have_content("My expired Promotion", wait: 30)
+ expect(page).to have_content("My expired Promotion", wait: 5)
click_on "All"
- expect(page).to have_content("My active Promotion", wait: 30)
+ expect(page).to have_content("My active Promotion", wait: 5)
expect(page).to have_content("My draft Promotion")
expect(page).to have_content("My future Promotion")
expect(page).to have_content("My expired Promotion")
@@ -32,5 +32,9 @@
expect(page).to have_content("Promotions were successfully removed.")
expect(page).not_to have_content("My active Promotion")
expect(SolidusPromotions::Promotion.count).to eq(3)
+
+ click_link("My future Promotion")
+ expect(page).to have_content("My future Promotion")
+ expect(page).to have_content("Starts at")
end
end
diff --git a/promotions/spec/system/solidus_promotions/backend/main_menu_spec.rb b/promotions/spec/system/solidus_promotions/backend/main_menu_spec.rb
index 73021eba6aa..788365fcc98 100644
--- a/promotions/spec/system/solidus_promotions/backend/main_menu_spec.rb
+++ b/promotions/spec/system/solidus_promotions/backend/main_menu_spec.rb
@@ -12,10 +12,10 @@
end
it "should have a link to promotions" do
- expect(page).to have_link("Promotions", href: solidus_promotions.admin_promotions_path, count: 2)
+ expect(page).to have_link("Promotions (new)", href: solidus_promotions.admin_promotions_path, count: 2)
end
it "should have a link to legacy promotions" do
- expect(page).to have_link("Legacy Promotions", href: spree.admin_promotions_path, count: 2)
+ expect(page).to have_link("Promotions", href: spree.admin_promotions_path, count: 2)
end
end
@@ -25,11 +25,11 @@
end
it "should have a link to promotions" do
- within(".selected .admin-subnav") { expect(page).to have_link("Promotions", href: solidus_promotions.admin_promotions_path) }
+ within(".selected .admin-subnav") { expect(page).to have_link("Promotions (new)", href: solidus_promotions.admin_promotions_path) }
end
it "should have a link to promotion categories" do
- within(".selected .admin-subnav") { expect(page).to have_link("Promotion Categories", href: solidus_promotions.admin_promotion_categories_path) }
+ within(".selected .admin-subnav") { expect(page).to have_link("Promotion Categories (new)", href: solidus_promotions.admin_promotion_categories_path) }
end
end
@@ -39,11 +39,11 @@
end
it "should have a link to promotions" do
- within(".selected .admin-subnav") { expect(page).to have_link("Legacy Promotions", href: spree.admin_promotions_path) }
+ within(".selected .admin-subnav") { expect(page).to have_link("Promotions", href: spree.admin_promotions_path) }
end
it "should have a link to promotion categories" do
- within(".selected .admin-subnav") { expect(page).to have_link("Legacy Promotion Categories", href: spree.admin_promotion_categories_path) }
+ within(".selected .admin-subnav") { expect(page).to have_link("Promotion Categories", href: spree.admin_promotion_categories_path) }
end
end
end
diff --git a/promotions/spec/system/solidus_promotions/backend/orders/adjustments_spec.rb b/promotions/spec/system/solidus_promotions/backend/orders/adjustments_spec.rb
new file mode 100644
index 00000000000..d861d498fa2
--- /dev/null
+++ b/promotions/spec/system/solidus_promotions/backend/orders/adjustments_spec.rb
@@ -0,0 +1,83 @@
+# frozen_string_literal: true
+
+require "rails_helper"
+
+RSpec.describe "Adjustments", type: :feature do
+ stub_authorization!
+
+ let!(:ship_address) { create(:address) }
+ let!(:tax_zone) { create(:global_zone) } # will include the above address
+ let!(:tax_rate) { create(:tax_rate, name: "Sales Tax", amount: 0.20, zone: tax_zone, tax_categories: [tax_category]) }
+
+ let!(:line_item) { order.line_items[0] }
+
+ let(:tax_category) { create(:tax_category) }
+ let(:variant) { create(:variant, tax_category:) }
+ let(:preferences) { {} }
+
+ before(:each) do
+ stub_spree_preferences(SolidusPromotions.configuration, preferences)
+ order.recalculate
+
+ visit spree.admin_path
+ click_link "Orders"
+ uncheck "Only show complete orders"
+ click_button "Filter Results"
+ within_row(1) { click_icon :edit }
+ click_link "Adjustments"
+ end
+
+ let!(:order) { create(:order, line_items_attributes: [{ price: 10, variant: }]) }
+
+ context "when the order is completed" do
+ let!(:order) do
+ create(
+ :completed_order_with_totals,
+ line_items_attributes: [{ price: 10, variant: }],
+ ship_address:
+ )
+ end
+
+ let!(:adjustment) { order.adjustments.create!(order:, label: "Rebate", amount: 10) }
+
+ it "shows adjustments" do
+ expect(page).to have_content("Adjustments")
+ end
+
+ context "when the promotion system is configured to allow applying promotions to completed orders" do
+ it "shows input field for promotion code" do
+ expect(page).to have_content("Adjustments")
+ expect(page).to have_field("coupon_code")
+ end
+ end
+
+ context "when the promotion system is configured to not allow applying promotions to completed orders" do
+ let(:preferences) { { recalculate_complete_orders: false } }
+
+ it "does not show input field for promotion code" do
+ expect(page).to have_content("Adjustments")
+ expect(page).not_to have_field("coupon_code")
+ end
+ end
+ end
+
+ it "shows the input field for applying a promotion" do
+ expect(page).to have_field("coupon_code")
+ end
+
+ context "creating a manual adjustment" do
+ let!(:adjustment_reason) { create(:adjustment_reason, name: "Friendly customer") }
+ before do
+ click_link "New Adjustment"
+ end
+
+ it "creates a new adjustment" do
+ fill_in "adjustment_amount", with: "5"
+ fill_in "adjustment_label", with: "Test Adjustment"
+ select "Friendly customer", from: "Reason"
+ click_button "Continue"
+ expect(page).to have_content("Adjustment has been successfully created!")
+ expect(page).to have_content("Test Adjustment")
+ end
+ end
+end
diff --git a/tasks/releasing.rake b/tasks/releasing.rake
index 4336ea0f3ce..098192672c5 100644
--- a/tasks/releasing.rake
+++ b/tasks/releasing.rake
@@ -2,7 +2,7 @@
require 'bundler/gem_tasks'
-SOLIDUS_GEM_NAMES = %w[core api backend sample promotions]
+SOLIDUS_GEM_NAMES = %w[core api backend sample promotions legacy_promotions]
%w[build install].each do |task_name|
desc "Run rake #{task} for each Solidus gem"
diff --git a/tasks/testing.rake b/tasks/testing.rake
index 213fab55d26..e3c62f0eb10 100644
--- a/tasks/testing.rake
+++ b/tasks/testing.rake
@@ -17,16 +17,15 @@ def subproject_task(project, task, title: project, task_name: nil)
end
end
-SOLIDUS_GEM_NAMES = %w[core api backend frontend sample promotions]
-
%w[spec db:drop db:create db:migrate db:reset].each do |task|
- SOLIDUS_GEM_NAMES.each do |project|
+ solidus_gem_names = %w[core api backend sample promotions legacy_promotions]
+ solidus_gem_names.each do |project|
desc "Run specs for #{project}" if task == 'spec'
subproject_task(project, task)
end
desc "Run rake #{task} for each Solidus engine"
- task task => SOLIDUS_GEM_NAMES.map { |p| "#{task}:#{p}" }
+ task task => solidus_gem_names.map { |p| "#{task}:#{p}" }
end
desc "Run backend JS specs"