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? %> <% 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"