From 593ec3c0757fd2587528c17e880d8d818025e488 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 26 May 2023 10:41:08 +0200 Subject: [PATCH 001/524] Initial commit --- friendly_promotions/.circleci/config.yml | 53 +++++++++++++ friendly_promotions/.gem_release.yml | 5 ++ friendly_promotions/.github/stale.yml | 1 + .../.github_changelog_generator | 2 + friendly_promotions/.gitignore | 21 +++++ friendly_promotions/.rspec | 2 + friendly_promotions/.rubocop.yml | 5 ++ friendly_promotions/CHANGELOG.md | 1 + friendly_promotions/Gemfile | 43 ++++++++++ friendly_promotions/LICENSE | 26 +++++++ friendly_promotions/README.md | 73 +++++++++++++++++ friendly_promotions/Rakefile | 6 ++ .../backend/solidus_friendly_promotions.js | 2 + .../frontend/solidus_friendly_promotions.js | 2 + .../backend/solidus_friendly_promotions.css | 4 + .../frontend/solidus_friendly_promotions.css | 4 + friendly_promotions/bin/console | 17 ++++ friendly_promotions/bin/rails | 7 ++ friendly_promotions/bin/rails-engine | 13 ++++ friendly_promotions/bin/rails-sandbox | 16 ++++ friendly_promotions/bin/rake | 7 ++ friendly_promotions/bin/sandbox | 78 +++++++++++++++++++ friendly_promotions/bin/setup | 8 ++ friendly_promotions/config/locales/en.yml | 5 ++ friendly_promotions/config/routes.rb | 5 ++ .../install/install_generator.rb | 41 ++++++++++ .../install/templates/initializer.rb | 6 ++ .../lib/solidus_friendly_promotions.rb | 5 ++ .../configuration.rb | 21 +++++ .../lib/solidus_friendly_promotions/engine.rb | 19 +++++ .../testing_support/factories.rb | 4 + .../solidus_friendly_promotions/version.rb | 5 ++ .../solidus_friendly_promotions.gemspec | 36 +++++++++ friendly_promotions/spec/spec_helper.rb | 32 ++++++++ 34 files changed, 575 insertions(+) create mode 100644 friendly_promotions/.circleci/config.yml create mode 100644 friendly_promotions/.gem_release.yml create mode 100644 friendly_promotions/.github/stale.yml create mode 100644 friendly_promotions/.github_changelog_generator create mode 100644 friendly_promotions/.gitignore create mode 100644 friendly_promotions/.rspec create mode 100644 friendly_promotions/.rubocop.yml create mode 100644 friendly_promotions/CHANGELOG.md create mode 100644 friendly_promotions/Gemfile create mode 100644 friendly_promotions/LICENSE create mode 100644 friendly_promotions/README.md create mode 100644 friendly_promotions/Rakefile create mode 100644 friendly_promotions/app/assets/javascripts/spree/backend/solidus_friendly_promotions.js create mode 100644 friendly_promotions/app/assets/javascripts/spree/frontend/solidus_friendly_promotions.js create mode 100644 friendly_promotions/app/assets/stylesheets/spree/backend/solidus_friendly_promotions.css create mode 100644 friendly_promotions/app/assets/stylesheets/spree/frontend/solidus_friendly_promotions.css create mode 100755 friendly_promotions/bin/console create mode 100755 friendly_promotions/bin/rails create mode 100755 friendly_promotions/bin/rails-engine create mode 100755 friendly_promotions/bin/rails-sandbox create mode 100755 friendly_promotions/bin/rake create mode 100755 friendly_promotions/bin/sandbox create mode 100755 friendly_promotions/bin/setup create mode 100644 friendly_promotions/config/locales/en.yml create mode 100644 friendly_promotions/config/routes.rb create mode 100644 friendly_promotions/lib/generators/solidus_friendly_promotions/install/install_generator.rb create mode 100644 friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb create mode 100644 friendly_promotions/lib/solidus_friendly_promotions.rb create mode 100644 friendly_promotions/lib/solidus_friendly_promotions/configuration.rb create mode 100644 friendly_promotions/lib/solidus_friendly_promotions/engine.rb create mode 100644 friendly_promotions/lib/solidus_friendly_promotions/testing_support/factories.rb create mode 100644 friendly_promotions/lib/solidus_friendly_promotions/version.rb create mode 100644 friendly_promotions/solidus_friendly_promotions.gemspec create mode 100644 friendly_promotions/spec/spec_helper.rb diff --git a/friendly_promotions/.circleci/config.yml b/friendly_promotions/.circleci/config.yml new file mode 100644 index 00000000000..28d70ebd500 --- /dev/null +++ b/friendly_promotions/.circleci/config.yml @@ -0,0 +1,53 @@ +version: 2.1 + +orbs: + # Required for feature specs. + browser-tools: circleci/browser-tools@1.1 + + # Always take the latest version of the orb, this allows us to + # run specs against Solidus supported versions only without the need + # to change this configuration every time a Solidus version is released + # or goes EOL. + solidusio_extensions: solidusio/extensions@volatile + +jobs: + run-specs-with-sqlite: + executor: solidusio_extensions/sqlite + steps: + - browser-tools/install-chrome + - solidusio_extensions/run-tests + run-specs-with-postgres: + executor: solidusio_extensions/postgres + steps: + - browser-tools/install-chrome + - solidusio_extensions/run-tests + run-specs-with-mysql: + executor: solidusio_extensions/mysql + steps: + - browser-tools/install-chrome + - solidusio_extensions/run-tests + lint-code: + executor: solidusio_extensions/sqlite-memory + steps: + - solidusio_extensions/lint-code + +workflows: + "Run specs on supported Solidus versions": + jobs: + - run-specs-with-sqlite + - run-specs-with-postgres + - run-specs-with-mysql + - lint-code + + "Weekly run specs against master": + triggers: + - schedule: + cron: "0 0 * * 4" # every Thursday + filters: + branches: + only: + - master + jobs: + - run-specs-with-sqlite + - run-specs-with-postgres + - run-specs-with-mysql diff --git a/friendly_promotions/.gem_release.yml b/friendly_promotions/.gem_release.yml new file mode 100644 index 00000000000..8c2591b0dba --- /dev/null +++ b/friendly_promotions/.gem_release.yml @@ -0,0 +1,5 @@ +bump: + recurse: false + file: 'lib/solidus_friendly_promotions/version.rb' + message: Bump SolidusFriendlyPromotions to %{version} + tag: true diff --git a/friendly_promotions/.github/stale.yml b/friendly_promotions/.github/stale.yml new file mode 100644 index 00000000000..0d0b1c994dd --- /dev/null +++ b/friendly_promotions/.github/stale.yml @@ -0,0 +1 @@ +_extends: .github diff --git a/friendly_promotions/.github_changelog_generator b/friendly_promotions/.github_changelog_generator new file mode 100644 index 00000000000..eac0962107b --- /dev/null +++ b/friendly_promotions/.github_changelog_generator @@ -0,0 +1,2 @@ +issues=false +exclude-labels=infrastructure diff --git a/friendly_promotions/.gitignore b/friendly_promotions/.gitignore new file mode 100644 index 00000000000..1ba20966542 --- /dev/null +++ b/friendly_promotions/.gitignore @@ -0,0 +1,21 @@ +*.gem +\#* +*~ +.#* +.DS_Store +.idea +.project +.sass-cache +coverage +Gemfile.lock +Gemfile-local +tmp +nbproject +pkg +*.swp +spec/dummy +spec/examples.txt +/sandbox +.rvmrc +.ruby-version +.ruby-gemset diff --git a/friendly_promotions/.rspec b/friendly_promotions/.rspec new file mode 100644 index 00000000000..83e16f80447 --- /dev/null +++ b/friendly_promotions/.rspec @@ -0,0 +1,2 @@ +--color +--require spec_helper diff --git a/friendly_promotions/.rubocop.yml b/friendly_promotions/.rubocop.yml new file mode 100644 index 00000000000..b075a8f6826 --- /dev/null +++ b/friendly_promotions/.rubocop.yml @@ -0,0 +1,5 @@ +require: + - solidus_dev_support/rubocop + +AllCops: + NewCops: disable diff --git a/friendly_promotions/CHANGELOG.md b/friendly_promotions/CHANGELOG.md new file mode 100644 index 00000000000..825c32f0d03 --- /dev/null +++ b/friendly_promotions/CHANGELOG.md @@ -0,0 +1 @@ +# Changelog diff --git a/friendly_promotions/Gemfile b/friendly_promotions/Gemfile new file mode 100644 index 00000000000..2f2a55c68e6 --- /dev/null +++ b/friendly_promotions/Gemfile @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +source 'https://rubygems.org' +git_source(:github) { |repo| "https://github.com/#{repo}.git" } + +branch = ENV.fetch('SOLIDUS_BRANCH', 'master') +gem 'solidus', github: 'solidusio/solidus', branch: branch + +# The solidus_frontend gem has been pulled out since v3.2 +gem 'solidus_frontend', github: 'solidusio/solidus_frontend' if branch == 'master' +gem 'solidus_frontend' if branch >= 'v3.2' # rubocop:disable Bundler/DuplicatedGem + +# Needed to help Bundler figure out how to resolve dependencies, +# otherwise it takes forever to resolve them. +# See https://github.com/bundler/bundler/issues/6677 +gem 'rails', '>0.a' + + +# Provides basic authentication functionality for testing parts of your engine +gem 'solidus_auth_devise' + +case ENV.fetch('DB', nil) +when 'mysql' + gem 'mysql2' +when 'postgresql' + gem 'pg' +else + gem 'sqlite3' +end + +# While we still support Ruby < 3 we need to workaround a limitation in +# the 'async' gem that relies on the latest ruby, since RubyGems doesn't +# resolve gems based on the required ruby version. +gem 'async', '< 3' if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('3') + +gemspec + +# Use a local Gemfile to include development dependencies that might not be +# relevant for the project or for other contributors, e.g. pry-byebug. +# +# We use `send` instead of calling `eval_gemfile` to work around an issue with +# how Dependabot parses projects: https://github.com/dependabot/dependabot-core/issues/1658. +send(:eval_gemfile, 'Gemfile-local') if File.exist? 'Gemfile-local' diff --git a/friendly_promotions/LICENSE b/friendly_promotions/LICENSE new file mode 100644 index 00000000000..375c0dd192b --- /dev/null +++ b/friendly_promotions/LICENSE @@ -0,0 +1,26 @@ +Copyright (c) 2023 Martin Meyerhoff +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name Solidus nor the names of its contributors may be used to + endorse or promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/friendly_promotions/README.md b/friendly_promotions/README.md new file mode 100644 index 00000000000..950e71818ea --- /dev/null +++ b/friendly_promotions/README.md @@ -0,0 +1,73 @@ +# Solidus Friendly Promotions + +[![CircleCI](https://circleci.com/gh/solidusio-contrib/solidus_friendly_promotions.svg?style=shield)](https://circleci.com/gh/solidusio-contrib/solidus_friendly_promotions) +[![codecov](https://codecov.io/gh/solidusio-contrib/solidus_friendly_promotions/branch/master/graph/badge.svg)](https://codecov.io/gh/solidusio-contrib/solidus_friendly_promotions) + + + +## Installation + +Add solidus_friendly_promotions to your Gemfile: + +```ruby +gem 'solidus_friendly_promotions' +``` + +Bundle your dependencies and run the installation generator: + +```shell +bin/rails generate solidus_friendly_promotions:install +``` + +## Usage + + + +## Development + +### Testing the extension + +First bundle your dependencies, then run `bin/rake`. `bin/rake` will default to building the dummy +app if it does not exist, then it will run specs. The dummy app can be regenerated by using +`bin/rake extension:test_app`. + +```shell +bin/rake +``` + +To run [Rubocop](https://github.com/bbatsov/rubocop) static code analysis run + +```shell +bundle exec rubocop +``` + +When testing your application's integration with this extension you may use its factories. +You can load Solidus core factories along with this extension's factories using this statement: + +```ruby +SolidusDevSupport::TestingSupport::Factories.load_for(SolidusFriendlyPromotions::Engine) +``` + +### Running the sandbox + +To run this extension in a sandboxed Solidus application, you can run `bin/sandbox`. The path for +the sandbox app is `./sandbox` and `bin/rails` will forward any Rails commands to +`sandbox/bin/rails`. + +Here's an example: + +``` +$ bin/rails server +=> Booting Puma +=> Rails 6.0.2.1 application starting in development +* Listening on tcp://127.0.0.1:3000 +Use Ctrl-C to stop +``` + +### Releasing new versions + +Please refer to the [dedicated page](https://github.com/solidusio/solidus/wiki/How-to-release-extensions) in the Solidus wiki. + +## License + +Copyright (c) 2023 Martin Meyerhoff, released under the New BSD License. diff --git a/friendly_promotions/Rakefile b/friendly_promotions/Rakefile new file mode 100644 index 00000000000..c08aa46816c --- /dev/null +++ b/friendly_promotions/Rakefile @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +require 'solidus_dev_support/rake_tasks' +SolidusDevSupport::RakeTasks.install + +task default: 'extension:specs' diff --git a/friendly_promotions/app/assets/javascripts/spree/backend/solidus_friendly_promotions.js b/friendly_promotions/app/assets/javascripts/spree/backend/solidus_friendly_promotions.js new file mode 100644 index 00000000000..8aa3b014409 --- /dev/null +++ b/friendly_promotions/app/assets/javascripts/spree/backend/solidus_friendly_promotions.js @@ -0,0 +1,2 @@ +// Placeholder manifest file. +// the installer will append this file to the app vendored assets here: vendor/assets/javascripts/spree/backend/all.js' \ No newline at end of file diff --git a/friendly_promotions/app/assets/javascripts/spree/frontend/solidus_friendly_promotions.js b/friendly_promotions/app/assets/javascripts/spree/frontend/solidus_friendly_promotions.js new file mode 100644 index 00000000000..a79f2e948dd --- /dev/null +++ b/friendly_promotions/app/assets/javascripts/spree/frontend/solidus_friendly_promotions.js @@ -0,0 +1,2 @@ +// Placeholder manifest file. +// the installer will append this file to the app vendored assets here: vendor/assets/javascripts/spree/frontend/all.js' \ No newline at end of file diff --git a/friendly_promotions/app/assets/stylesheets/spree/backend/solidus_friendly_promotions.css b/friendly_promotions/app/assets/stylesheets/spree/backend/solidus_friendly_promotions.css new file mode 100644 index 00000000000..e3c236629e3 --- /dev/null +++ b/friendly_promotions/app/assets/stylesheets/spree/backend/solidus_friendly_promotions.css @@ -0,0 +1,4 @@ +/* +Placeholder manifest file. +the installer will append this file to the app vendored assets here: 'vendor/assets/stylesheets/spree/backend/all.css' +*/ diff --git a/friendly_promotions/app/assets/stylesheets/spree/frontend/solidus_friendly_promotions.css b/friendly_promotions/app/assets/stylesheets/spree/frontend/solidus_friendly_promotions.css new file mode 100644 index 00000000000..da236237c52 --- /dev/null +++ b/friendly_promotions/app/assets/stylesheets/spree/frontend/solidus_friendly_promotions.css @@ -0,0 +1,4 @@ +/* +Placeholder manifest file. +the installer will append this file to the app vendored assets here: 'vendor/assets/stylesheets/spree/frontend/all.css' +*/ diff --git a/friendly_promotions/bin/console b/friendly_promotions/bin/console new file mode 100755 index 00000000000..5170bfc2daa --- /dev/null +++ b/friendly_promotions/bin/console @@ -0,0 +1,17 @@ +#!/usr/bin/env ruby + +# frozen_string_literal: true + +require "bundler/setup" +require "solidus_friendly_promotions" + +# You can add fixtures and/or initialization code here to make experimenting +# with your gem easier. You can also use a different console, if you like. +$LOAD_PATH.unshift(*Dir["#{__dir__}/../app/*"]) + +# (If you use this, don't forget to add pry to your Gemfile!) +# require "pry" +# Pry.start + +require "irb" +IRB.start(__FILE__) diff --git a/friendly_promotions/bin/rails b/friendly_promotions/bin/rails new file mode 100755 index 00000000000..6dbbbc36e77 --- /dev/null +++ b/friendly_promotions/bin/rails @@ -0,0 +1,7 @@ +#!/usr/bin/env ruby + +if %w[g generate].include? ARGV.first + exec "#{__dir__}/rails-engine", *ARGV +else + exec "#{__dir__}/rails-sandbox", *ARGV +end diff --git a/friendly_promotions/bin/rails-engine b/friendly_promotions/bin/rails-engine new file mode 100755 index 00000000000..00cd82aeace --- /dev/null +++ b/friendly_promotions/bin/rails-engine @@ -0,0 +1,13 @@ +#!/usr/bin/env ruby +# This command will automatically be run when you run "rails" with Rails gems +# installed from the root of your application. + +ENGINE_ROOT = File.expand_path('..', __dir__) +ENGINE_PATH = File.expand_path('../lib/solidus_friendly_promotions/engine', __dir__) + +# Set up gems listed in the Gemfile. +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) +require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) + +require 'rails/all' +require 'rails/engine/commands' diff --git a/friendly_promotions/bin/rails-sandbox b/friendly_promotions/bin/rails-sandbox new file mode 100755 index 00000000000..ad2df04d0e7 --- /dev/null +++ b/friendly_promotions/bin/rails-sandbox @@ -0,0 +1,16 @@ +#!/usr/bin/env ruby + +app_root = 'sandbox' + +unless File.exist? "#{app_root}/bin/rails" + warn 'Creating the sandbox app...' + Dir.chdir "#{__dir__}/.." do + system "#{__dir__}/sandbox" or begin + warn 'Automatic creation of the sandbox app failed' + exit 1 + end + end +end + +Dir.chdir app_root +exec 'bin/rails', *ARGV diff --git a/friendly_promotions/bin/rake b/friendly_promotions/bin/rake new file mode 100755 index 00000000000..1e6eacd34e8 --- /dev/null +++ b/friendly_promotions/bin/rake @@ -0,0 +1,7 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require "rubygems" +require "bundler/setup" + +load Gem.bin_path("rake", "rake") diff --git a/friendly_promotions/bin/sandbox b/friendly_promotions/bin/sandbox new file mode 100755 index 00000000000..bf577b65c23 --- /dev/null +++ b/friendly_promotions/bin/sandbox @@ -0,0 +1,78 @@ +#!/usr/bin/env bash + +set -e +test -z "${DEBUG+empty_string}" || set -x + +test "$DB" = "sqlite" && export DB="sqlite3" + +if [ -z "$SOLIDUS_BRANCH" ] +then + echo "~~> Use 'export SOLIDUS_BRANCH=[master|v3.2|...]' to control the Solidus branch" + SOLIDUS_BRANCH="master" +fi +echo "~~> Using branch $SOLIDUS_BRANCH of solidus" + +if [ -z "$SOLIDUS_FRONTEND" ] +then + echo "~~> Use 'export SOLIDUS_FRONTEND=[solidus_frontend|solidus_starter_frontend]' to control the Solidus frontend" + SOLIDUS_FRONTEND="solidus_frontend" +fi +echo "~~> Using branch $SOLIDUS_FRONTEND as the solidus frontend" + +extension_name="solidus_friendly_promotions" + +# Stay away from the bundler env of the containing extension. +function unbundled { + ruby -rbundler -e'b = proc {system *ARGV}; Bundler.respond_to?(:with_unbundled_env) ? Bundler.with_unbundled_env(&b) : Bundler.with_clean_env(&b)' -- $@ +} + +rm -rf ./sandbox +unbundled bundle exec rails new sandbox \ + --database="${DB:-sqlite3}" \ + --skip-bundle \ + --skip-git \ + --skip-keeps \ + --skip-rc \ + --skip-spring \ + --skip-test \ + --skip-javascript + +if [ ! -d "sandbox" ]; then + echo 'sandbox rails application failed' + exit 1 +fi + +cd ./sandbox +cat <> Gemfile +gem 'solidus', github: 'solidusio/solidus', branch: '$SOLIDUS_BRANCH' +gem 'rails-i18n' +gem 'solidus_i18n' + +gem '$extension_name', path: '..' + +group :test, :development do + platforms :mri do + gem 'pry-byebug' + end +end +RUBY + +unbundled bundle install --gemfile Gemfile + +unbundled bundle exec rake db:drop db:create + +unbundled bundle exec rails generate solidus:install \ + --auto-accept \ + --user_class=Spree::User \ + --enforce_available_locales=true \ + --with-authentication=true \ + --payment-method=none \ + --frontend=${SOLIDUS_FRONTEND} \ + $@ + +unbundled bundle exec rails generate solidus:auth:install --auto-run-migrations +unbundled bundle exec rails generate ${extension_name}:install --auto-run-migrations + +echo +echo "๐Ÿš€ Sandbox app successfully created for $extension_name!" +echo "๐Ÿงช This app is intended for test purposes." diff --git a/friendly_promotions/bin/setup b/friendly_promotions/bin/setup new file mode 100755 index 00000000000..67d919320aa --- /dev/null +++ b/friendly_promotions/bin/setup @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -euo pipefail +IFS=$'\n\t' +set -vx + +gem install bundler --conservative +bundle update +bin/rake clobber diff --git a/friendly_promotions/config/locales/en.yml b/friendly_promotions/config/locales/en.yml new file mode 100644 index 00000000000..f341cf450bc --- /dev/null +++ b/friendly_promotions/config/locales/en.yml @@ -0,0 +1,5 @@ +# Sample localization file for English. Add more files in this directory for other locales. +# See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. + +en: + hello: Hello world diff --git a/friendly_promotions/config/routes.rb b/friendly_promotions/config/routes.rb new file mode 100644 index 00000000000..59d02443fbd --- /dev/null +++ b/friendly_promotions/config/routes.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +Spree::Core::Engine.routes.draw do + # Add your extension routes here +end diff --git a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/install_generator.rb b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/install_generator.rb new file mode 100644 index 00000000000..31ac0a7c3d7 --- /dev/null +++ b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/install_generator.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module Generators + class InstallGenerator < Rails::Generators::Base + class_option :auto_run_migrations, type: :boolean, default: false + source_root File.expand_path('templates', __dir__) + + def self.exit_on_failure? + true + end + + def copy_initializer + template 'initializer.rb', 'config/initializers/solidus_friendly_promotions.rb' + end + + def add_javascripts + append_file 'vendor/assets/javascripts/spree/frontend/all.js', "//= require spree/frontend/solidus_friendly_promotions\n" + append_file 'vendor/assets/javascripts/spree/backend/all.js', "//= require spree/backend/solidus_friendly_promotions\n" + end + + def add_stylesheets + inject_into_file 'vendor/assets/stylesheets/spree/frontend/all.css', " *= require spree/frontend/solidus_friendly_promotions\n", before: %r{\*/}, verbose: true # rubocop:disable Layout/LineLength + inject_into_file 'vendor/assets/stylesheets/spree/backend/all.css', " *= require spree/backend/solidus_friendly_promotions\n", before: %r{\*/}, verbose: true # rubocop:disable Layout/LineLength + end + + def add_migrations + run 'bin/rails railties:install:migrations FROM=solidus_friendly_promotions' + end + + def run_migrations + run_migrations = options[:auto_run_migrations] || ['', 'y', 'Y'].include?(ask('Would you like to run the migrations now? [Y/n]')) # rubocop:disable Layout/LineLength + if run_migrations + run 'bin/rails db:migrate' + else + puts 'Skipping bin/rails db:migrate, don\'t forget to run it!' # rubocop:disable Rails/Output + end + end + end + end +end diff --git a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb new file mode 100644 index 00000000000..79e8c8e6990 --- /dev/null +++ b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +SolidusFriendlyPromotions.configure do |config| + # TODO: Remember to change this with the actual preferences you have implemented! + # config.sample_preference = 'sample_value' +end diff --git a/friendly_promotions/lib/solidus_friendly_promotions.rb b/friendly_promotions/lib/solidus_friendly_promotions.rb new file mode 100644 index 00000000000..5ae3dc6fcc7 --- /dev/null +++ b/friendly_promotions/lib/solidus_friendly_promotions.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +require 'solidus_friendly_promotions/configuration' +require 'solidus_friendly_promotions/version' +require 'solidus_friendly_promotions/engine' diff --git a/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb b/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb new file mode 100644 index 00000000000..d00757a289a --- /dev/null +++ b/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + class Configuration + # Define here the settings for this extension, e.g.: + # + # attr_accessor :my_setting + end + + class << self + def configuration + @configuration ||= Configuration.new + end + + alias config configuration + + def configure + yield configuration + end + end +end diff --git a/friendly_promotions/lib/solidus_friendly_promotions/engine.rb b/friendly_promotions/lib/solidus_friendly_promotions/engine.rb new file mode 100644 index 00000000000..1cf24dc3e2c --- /dev/null +++ b/friendly_promotions/lib/solidus_friendly_promotions/engine.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +require 'solidus_core' +require 'solidus_support' + +module SolidusFriendlyPromotions + class Engine < Rails::Engine + include SolidusSupport::EngineExtensions + + isolate_namespace ::Spree + + engine_name 'solidus_friendly_promotions' + + # use rspec for tests + config.generators do |g| + g.test_framework :rspec + end + end +end diff --git a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/factories.rb b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/factories.rb new file mode 100644 index 00000000000..745a01e4c27 --- /dev/null +++ b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/factories.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true + +FactoryBot.define do +end diff --git a/friendly_promotions/lib/solidus_friendly_promotions/version.rb b/friendly_promotions/lib/solidus_friendly_promotions/version.rb new file mode 100644 index 00000000000..eb53a9e9496 --- /dev/null +++ b/friendly_promotions/lib/solidus_friendly_promotions/version.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + VERSION = '0.0.1' +end diff --git a/friendly_promotions/solidus_friendly_promotions.gemspec b/friendly_promotions/solidus_friendly_promotions.gemspec new file mode 100644 index 00000000000..b26ed10cbb0 --- /dev/null +++ b/friendly_promotions/solidus_friendly_promotions.gemspec @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require_relative 'lib/solidus_friendly_promotions/version' + +Gem::Specification.new do |spec| + spec.name = 'solidus_friendly_promotions' + spec.version = SolidusFriendlyPromotions::VERSION + spec.authors = ['Martin Meyerhoff'] + spec.email = 'mamhoff@gmail.com' + + spec.summary = 'A replacement for Solidus\' promotion system' + spec.description = 'Experimental replacement for the promotion system in Solidus' + spec.homepage = 'https://github.com/solidusio-contrib/solidus_friendly_promotions#readme' + spec.license = 'BSD-3-Clause' + + spec.metadata['homepage_uri'] = spec.homepage + spec.metadata['source_code_uri'] = 'https://github.com/solidusio-contrib/solidus_friendly_promotions' + spec.metadata['changelog_uri'] = 'https://github.com/solidusio-contrib/solidus_friendly_promotions/blob/master/CHANGELOG.md' + + spec.required_ruby_version = Gem::Requirement.new('>= 2.5', '< 4') + + # Specify which files should be added to the gem when it is released. + # The `git ls-files -z` loads the files in the RubyGem that have been added into git. + files = Dir.chdir(__dir__) { `git ls-files -z`.split("\x0") } + + spec.files = files.grep_v(%r{^(test|spec|features)/}) + spec.test_files = files.grep(%r{^(test|spec|features)/}) + spec.bindir = "exe" + spec.executables = files.grep(%r{^exe/}) { |f| File.basename(f) } + spec.require_paths = ["lib"] + + spec.add_dependency 'solidus_core', ['>= 3.0.0', '< 5'] + spec.add_dependency 'solidus_support', '~> 0.5' + + spec.add_development_dependency 'solidus_dev_support', '~> 2.6' +end diff --git a/friendly_promotions/spec/spec_helper.rb b/friendly_promotions/spec/spec_helper.rb new file mode 100644 index 00000000000..62c1bab47b0 --- /dev/null +++ b/friendly_promotions/spec/spec_helper.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +# Configure Rails Environment +ENV['RAILS_ENV'] = 'test' + +# Run Coverage report +require 'solidus_dev_support/rspec/coverage' + +# Create the dummy app if it's still missing. +dummy_env = "#{__dir__}/dummy/config/environment.rb" +system 'bin/rake extension:test_app' unless File.exist? dummy_env +require dummy_env + +# Requires factories and other useful helpers defined in spree_core. +require 'solidus_dev_support/rspec/feature_helper' + +# Requires supporting ruby files with custom matchers and macros, etc, +# in spec/support/ and its subdirectories. +Dir["#{__dir__}/support/**/*.rb"].sort.each { |f| require f } + +# Requires factories defined in Solidus core and this extension. +# See: lib/solidus_friendly_promotions/testing_support/factories.rb +SolidusDevSupport::TestingSupport::Factories.load_for(SolidusFriendlyPromotions::Engine) + +RSpec.configure do |config| + config.infer_spec_type_from_file_location! + config.use_transactional_fixtures = false + + if Spree.solidus_gem_version < Gem::Version.new('2.11') + config.extend Spree::TestingSupport::AuthorizationHelpers::Request, type: :system + end +end From a7ed882f3252a34c26ceaeb6d09e48797561353e Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 26 May 2023 10:47:09 +0200 Subject: [PATCH 002/524] Add integration spec This integration spec comes from the experimental PR https://github.com/solidusio/solidus/pull/4296. That PR will very probably not be merged, so I'm moving it here as a vantage point. --- .../spec/models/promotion/integration_spec.rb | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 friendly_promotions/spec/models/promotion/integration_spec.rb diff --git a/friendly_promotions/spec/models/promotion/integration_spec.rb b/friendly_promotions/spec/models/promotion/integration_spec.rb new file mode 100644 index 00000000000..555e3992668 --- /dev/null +++ b/friendly_promotions/spec/models/promotion/integration_spec.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe "Promotion System" do + context "A promotion that creates line item adjustments" do + let(:shirt) { create(:product) } + let(:pants) { create(:product) } + let(:promotion) { create(:promotion, name: "20% off Shirts", apply_automatically: true) } + let(:order) { create(:order) } + + before do + promotion.rules << rule + promotion.actions << action + order.contents.add(shirt.master, 1) + order.contents.add(pants.master, 1) + end + + context "with an order-level rule" do + let(:rule) { Spree::Promotion::Rules::Product.new(products: [shirt]) } + + context "with an order level action" do + let(:calculator) { Spree::Calculator::FlatPercentItemTotal.new(preferred_flat_percent: 20) } + let(:action) { Spree::Promotion::Actions::CreateAdjustment.new(calculator: calculator) } + + it "creates one order-level adjustment" do + expect(order.adjustments.length).to eq(1) + expect(order.total).to eq(31.98) + expect(order.item_total).to eq(39.98) + # This is wrong! But order level adjustments can't work any other way + expect(order.item_total_before_tax).to eq(39.98) + expect(order.line_items.flat_map(&:adjustments)).to be_empty + end + end + + context "with an line item level action" do + let(:calculator) { Spree::Calculator::PercentOnLineItem.new(preferred_percent: 20) } + let(:action) { Spree::Promotion::Actions::CreateItemAdjustments.new(calculator: calculator) } + + it "creates one order-level adjustment" do + pending + expect(order.adjustments).to be_empty + expect(order.total).to eq(31.98) + expect(order.item_total).to eq(39.98) + expect(order.item_total_before_tax).to eq(31.98) + expect(order.line_items.flat_map(&:adjustments).length).to eq(2) + end + end + end + + context "with a line-item level rule" do + let(:rule) { Spree::Promotion::Rules::LineItemProduct.new(products: [shirt]) } + + context "with an order level action" do + let(:calculator) { Spree::Calculator::FlatPercentItemTotal.new(preferred_flat_percent: 20) } + let(:action) { Spree::Promotion::Actions::CreateAdjustment.new(calculator: calculator) } + + it "creates one order-level adjustment" do + pending + # Whoops - this works because line item level rules don't affect order-level actions :( + expect(order.adjustments.length).to eq(1) + expect(order.total).to eq(31.98) + expect(order.item_total).to eq(39.98) + # This is wrong! But order level adjustments can't work any other way + expect(order.item_total_before_tax).to eq(39.98) + expect(order.line_items.flat_map(&:adjustments)).to be_empty + end + end + + context "with an line item level action" do + let(:calculator) { Spree::Calculator::PercentOnLineItem.new(preferred_percent: 20) } + let(:action) { Spree::Promotion::Actions::CreateItemAdjustments.new(calculator: calculator) } + + it "creates one line item level adjustment" do + pending + expect(order.adjustments).to be_empty + expect(order.total).to eq(35.98) + expect(order.item_total).to eq(39.98) + expect(order.item_total_before_tax).to eq(35.98) + expect(order.line_items.flat_map(&:adjustments).length).to eq(1) + end + end + end + end +end From ff8f57cec1b708dfa71267e7fc597ccf8518f129 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 26 May 2023 11:53:31 +0200 Subject: [PATCH 003/524] Use Solidus main branch No weird errors! Much good! --- friendly_promotions/Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/friendly_promotions/Gemfile b/friendly_promotions/Gemfile index 2f2a55c68e6..0a229872832 100644 --- a/friendly_promotions/Gemfile +++ b/friendly_promotions/Gemfile @@ -3,7 +3,7 @@ source 'https://rubygems.org' git_source(:github) { |repo| "https://github.com/#{repo}.git" } -branch = ENV.fetch('SOLIDUS_BRANCH', 'master') +branch = ENV.fetch('SOLIDUS_BRANCH', 'main') gem 'solidus', github: 'solidusio/solidus', branch: branch # The solidus_frontend gem has been pulled out since v3.2 From 419be4b35a9b6b3a03ebb699bce503b5f5d46be4 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 31 May 2023 12:45:59 +0200 Subject: [PATCH 004/524] Add RSpec ActiveModel Mocks These are needed to import the promotion rule specs. --- friendly_promotions/solidus_friendly_promotions.gemspec | 1 + friendly_promotions/spec/spec_helper.rb | 3 +++ 2 files changed, 4 insertions(+) diff --git a/friendly_promotions/solidus_friendly_promotions.gemspec b/friendly_promotions/solidus_friendly_promotions.gemspec index b26ed10cbb0..05cb6b99c9a 100644 --- a/friendly_promotions/solidus_friendly_promotions.gemspec +++ b/friendly_promotions/solidus_friendly_promotions.gemspec @@ -33,4 +33,5 @@ Gem::Specification.new do |spec| spec.add_dependency 'solidus_support', '~> 0.5' spec.add_development_dependency 'solidus_dev_support', '~> 2.6' + spec.add_development_dependency 'rspec-activemodel-mocks', '~> 1.0' end diff --git a/friendly_promotions/spec/spec_helper.rb b/friendly_promotions/spec/spec_helper.rb index 62c1bab47b0..1cb2698e589 100644 --- a/friendly_promotions/spec/spec_helper.rb +++ b/friendly_promotions/spec/spec_helper.rb @@ -14,6 +14,9 @@ # Requires factories and other useful helpers defined in spree_core. require 'solidus_dev_support/rspec/feature_helper' +# Explicitly load activemodel mocks +require 'rspec-activemodel-mocks' + # Requires supporting ruby files with custom matchers and macros, etc, # in spec/support/ and its subdirectories. Dir["#{__dir__}/support/**/*.rb"].sort.each { |f| require f } From 06e73ce07e196294dc0bea44411d01dd4ea83a67 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 31 May 2023 12:50:56 +0200 Subject: [PATCH 005/524] Import Solidus Promotion Rules Since `Spree::Promotion` is an STI class, we can redefine all Solidus promotion rules in another place and not touch any of the core Solidus things. --- .../rules/first_order.rb | 36 +++++ .../rules/first_repeat_purchase_since.rb | 34 +++++ .../rules/item_total.rb | 95 ++++++++++++ .../rules/line_item_option_value.rb | 39 +++++ .../rules/line_item_product.rb | 46 ++++++ .../rules/line_item_taxon.rb | 52 +++++++ .../rules/nth_order.rb | 44 ++++++ .../rules/one_use_per_user.rb | 23 +++ .../rules/option_value.rb | 28 ++++ .../rules/product.rb | 73 +++++++++ .../rules/store.rb | 24 +++ .../rules/taxon.rb | 73 +++++++++ .../solidus_friendly_promotions/rules/user.rb | 32 ++++ .../rules/user_logged_in.rb | 18 +++ .../rules/user_role.rb | 43 ++++++ .../spec/models/promotion/integration_spec.rb | 6 +- .../rules/first_order_spec.rb | 87 +++++++++++ .../rules/first_repeat_purchase_since_spec.rb | 71 +++++++++ .../rules/item_total_spec.rb | 128 ++++++++++++++++ .../rules/line_item_product_spec.rb | 47 ++++++ .../rules/line_item_taxon_spec.rb | 92 +++++++++++ .../rules/nth_order_spec.rb | 72 +++++++++ .../rules/one_use_per_user_spec.rb | 54 +++++++ .../rules/option_value_spec.rb | 48 ++++++ .../rules/product_spec.rb | 127 +++++++++++++++ .../rules/store_spec.rb | 35 +++++ .../rules/taxon_spec.rb | 144 ++++++++++++++++++ .../rules/user_logged_in_spec.rb | 33 ++++ .../rules/user_role_spec.rb | 88 +++++++++++ .../rules/user_spec.rb | 39 +++++ 30 files changed, 1727 insertions(+), 4 deletions(-) create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/rules/first_order.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_option_value.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/rules/option_value.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/rules/user_role.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_order_spec.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_repeat_purchase_since_spec.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/rules/item_total_spec.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/rules/line_item_product_spec.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/rules/line_item_taxon_spec.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/rules/nth_order_spec.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/rules/one_use_per_user_spec.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/rules/option_value_spec.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/rules/product_spec.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/rules/store_spec.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/rules/taxon_spec.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_logged_in_spec.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_role_spec.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_spec.rb diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_order.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_order.rb new file mode 100644 index 00000000000..5b92a0cf9a4 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_order.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module Rules + class FirstOrder < ::Spree::PromotionRule + attr_reader :user, :email + + def applicable?(promotable) + promotable.is_a?(Spree::Order) + end + + def eligible?(order, options = {}) + @user = order.try(:user) || options[:user] + @email = order.email + + if user || email + if !completed_orders.blank? && completed_orders.first != order + eligibility_errors.add(:base, eligibility_error_message(:not_first_order), error_code: :not_first_order) + end + end + + eligibility_errors.empty? + end + + private + + def completed_orders + user ? user.orders.complete : orders_by_email + end + + def orders_by_email + Spree::Order.where(email: email).complete + end + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb new file mode 100644 index 00000000000..eab1e40fa42 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module Rules + class FirstRepeatPurchaseSince < ::Spree::PromotionRule + preference :days_ago, :integer, default: 365 + validates :preferred_days_ago, numericality: { only_integer: true, greater_than: 0 } + + # This promotion is applicable to orders only. + def applicable?(promotable) + promotable.is_a?(Spree::Order) + end + + # This is never eligible if the order does not have a user, and that user does not have any previous completed orders. + # + # This is eligible if the user's most recently completed order is more than the preferred days ago + # @param order [Spree::Order] + def eligible?(order, _options = {}) + return false unless order.user + + last_order = last_completed_order(order.user) + return false unless last_order + + last_order.completed_at < preferred_days_ago.days.ago + end + + private + + def last_completed_order(user) + user.orders.complete.order(:completed_at).last + end + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb new file mode 100644 index 00000000000..153134ee604 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb @@ -0,0 +1,95 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module Rules + # A rule to apply to an order greater than (or greater than or equal to) + # a specific amount + # + # To add extra operators please override `self.operators_map` or any other helper method. + # To customize the error message you can also override `ineligible_message`. + class ItemTotal < ::Spree::PromotionRule + include ActiveSupport::Deprecation::DeprecatedConstantAccessor + + preference :amount, :decimal, default: 100.00 + preference :currency, :string, default: ->{ Spree::Config[:currency] } + preference :operator, :string, default: 'gt' + + # The list of allowed operators names mapped to their symbols. + def self.operators_map + { + gte: :>=, + gt: :>, + } + end + + def self.operator_options + operators_map.map do |name, _method| + [I18n.t(name, scope: 'spree.item_total_rule.operators'), name] + end + end + + # @deprecated + OPERATORS = operators_map.keys.map(&:to_s) + deprecate_constant( + :OPERATORS, + :operators_map, + message: "OPERATORS is deprecated! Use `operators_map.keys.map(&:to_s)` instead.", + deprecator: Spree::Deprecation, + ) + + def applicable?(promotable) + promotable.is_a?(Spree::Order) + end + + def eligible?(order, _options = {}) + return false unless order.currency == preferred_currency + + unless total_for_order(order).send(operator, threshold) + eligibility_errors.add(:base, ineligible_message, error_code: ineligible_error_code) + end + + eligibility_errors.empty? + end + + private + + def operator + self.class.operators_map.fetch( + preferred_operator.to_sym, + preferred_operator_default, + ) + end + + def total_for_order(order) + order.item_total + end + + def threshold + BigDecimal(preferred_amount.to_s) + end + + def formatted_amount + Spree::Money.new(preferred_amount, currency: preferred_currency).to_s + end + + def ineligible_message + case preferred_operator.to_s + when 'gte' + eligibility_error_message(:item_total_less_than, amount: formatted_amount) + when 'gt' + eligibility_error_message(:item_total_less_than_or_equal, amount: formatted_amount) + else + eligibility_error_message(:item_total_doesnt_match_with_operator, amount: formatted_amount, operator: preferred_operator) + end + end + + def ineligible_error_code + if preferred_operator == 'gte' + :item_total_less_than + else + :item_total_less_than_or_equal + end + end + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_option_value.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_option_value.rb new file mode 100644 index 00000000000..ceff257c286 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_option_value.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module Rules + class LineItemOptionValue < ::Spree::PromotionRule + preference :eligible_values, :hash + + def applicable?(promotable) + promotable.is_a?(Spree::LineItem) + end + + def eligible?(line_item, _options = {}) + pid = line_item.product.id + ovids = line_item.variant.option_values.pluck(:id) + + product_ids.include?(pid) && (value_ids(pid) & ovids).present? + end + + def preferred_eligible_values + values = preferences[:eligible_values] || {} + Hash[values.keys.map(&:to_i).zip( + values.values.map do |value| + (value.is_a?(Array) ? value : value.split(",")).map(&:to_i) + end + )] + end + + private + + def product_ids + preferred_eligible_values.keys + end + + def value_ids(product_id) + preferred_eligible_values[product_id] + end + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb new file mode 100644 index 00000000000..d930e6f5681 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module Rules + # A rule to apply a promotion only to line items with or without a chosen product + class LineItemProduct < Spree::PromotionRule + MATCH_POLICIES = %w(include exclude) + + has_many :product_promotion_rules, + dependent: :destroy, + foreign_key: :promotion_rule_id, + class_name: "Spree::ProductPromotionRule" + has_many :products, + class_name: "Spree::Product", + through: :product_promotion_rules + + preference :match_policy, :string, default: MATCH_POLICIES.first + + def applicable?(promotable) + promotable.is_a?(Spree::LineItem) + end + + def eligible?(line_item, _options = {}) + if inverse? + !product_ids.include?(line_item.variant.product_id) + else + product_ids.include?(line_item.variant.product_id) + end + end + + def product_ids_string + product_ids.join(",") + end + + def product_ids_string=(product_ids) + self.product_ids = product_ids.to_s.split(",").map(&:strip) + end + + private + + def inverse? + preferred_match_policy == "exclude" + end + end + end + end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb new file mode 100644 index 00000000000..ae6683c6f4b --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module Rules + class LineItemTaxon < ::Spree::PromotionRule + has_many :promotion_rule_taxons, class_name: 'Spree::PromotionRuleTaxon', foreign_key: :promotion_rule_id, + dependent: :destroy + has_many :taxons, through: :promotion_rule_taxons, class_name: 'Spree::Taxon' + + MATCH_POLICIES = %w(include exclude) + + validates_inclusion_of :preferred_match_policy, in: MATCH_POLICIES + + preference :match_policy, :string, default: MATCH_POLICIES.first + def applicable?(promotable) + promotable.is_a?(Spree::LineItem) + end + + def eligible?(line_item, _options = {}) + found = Spree::Classification.where( + product_id: line_item.variant.product_id, + taxon_id: rule_taxon_ids_with_children + ).exists? + + case preferred_match_policy + when 'include' + found + when 'exclude' + !found + else + raise "unexpected match policy: #{preferred_match_policy.inspect}" + end + end + + def taxon_ids_string + taxons.pluck(:id).join(',') + end + + def taxon_ids_string=(taxon_ids) + taxon_ids = taxon_ids.to_s.split(',').map(&:strip) + self.taxons = Spree::Taxon.find(taxon_ids) + end + + private + + # ids of taxons rules and taxons rules children + def rule_taxon_ids_with_children + taxons.flat_map { |taxon| taxon.self_and_descendants.ids }.uniq + end + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb new file mode 100644 index 00000000000..3c8d94ca5cf --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + + module Rules + class NthOrder < ::Spree::PromotionRule + preference :nth_order, :integer, default: 2 + # It does not make sense to have this apply to the first order using preferred_nth_order == 1 + # Instead we could use the first_order rule + validates :preferred_nth_order, numericality: { only_integer: true, greater_than: 1 } + + # This promotion is applicable to orders only. + def applicable?(promotable) + promotable.is_a?(Spree::Order) + end + + # This is never eligible if the order does not have a user, and that user does not have any previous completed orders. + # + # Use the first order rule if you want a promotion to be applied to the first order for a user. + # @param order [Spree::Order] + def eligible?(order, _options = {}) + return false unless order.user + + nth_order?(order) + end + + private + + def completed_order_count(order) + order. + user. + orders. + complete. + where(Spree::Order.arel_table[:completed_at].lt(order.completed_at || Time.current)). + count + end + + def nth_order?(order) + count = completed_order_count(order) + 1 + count == preferred_nth_order + end + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb new file mode 100644 index 00000000000..2b174a8c9c7 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module Rules + class OneUsePerUser < ::Spree::PromotionRule + def applicable?(promotable) + promotable.is_a?(Spree::Order) + end + + def eligible?(order, _options = {}) + if order.user.present? + if promotion.used_by?(order.user, [order]) + eligibility_errors.add(:base, eligibility_error_message(:limit_once_per_user), error_code: :limit_once_per_user) + end + else + eligibility_errors.add(:base, eligibility_error_message(:no_user_specified), error_code: :no_user_specified) + end + + eligibility_errors.empty? + end + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/option_value.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/option_value.rb new file mode 100644 index 00000000000..26e1cffba9a --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/option_value.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module Rules + class OptionValue < ::Spree::PromotionRule + preference :eligible_values, :hash + + def applicable?(promotable) + promotable.is_a?(Spree::Order) + end + + def eligible?(order, _options = {}) + order.line_items.any? do |item| + LineItemOptionValue.new(preferred_eligible_values: preferred_eligible_values).eligible?(item) + end + end + + def preferred_eligible_values + values = preferences[:eligible_values] || {} + Hash[values.keys.map(&:to_i).zip( + values.values.map do |value| + (value.is_a?(Array) ? value : value.split(",")).map(&:to_i) + end + )] + end + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb new file mode 100644 index 00000000000..f6b0ce019da --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module Rules + # A rule to limit a promotion based on products in the order. Can + # require all or any of the products to be present. Valid products + # either come from assigned product group or are assingned directly to + # the rule. + class Product < ::Spree::PromotionRule + has_many :product_promotion_rules, dependent: :destroy, foreign_key: :promotion_rule_id, + class_name: 'Spree::ProductPromotionRule' + has_many :products, class_name: 'Spree::Product', through: :product_promotion_rules + + def preload_relations + [:products] + end + + MATCH_POLICIES = %w(any all none) + + validates_inclusion_of :preferred_match_policy, in: MATCH_POLICIES + + preference :match_policy, :string, default: MATCH_POLICIES.first + + # scope/association that is used to test eligibility + def eligible_products + products + end + + def applicable?(promotable) + promotable.is_a?(Spree::Order) + end + + def eligible?(order, _options = {}) + return true if eligible_products.empty? + + case preferred_match_policy + when "all" + unless eligible_products.all? { |product| order_products(order).include?(product) } + eligibility_errors.add(:base, eligibility_error_message(:missing_product), error_code: :missing_product) + end + when "any" + unless order_products(order).any? { |product| eligible_products.include?(product) } + eligibility_errors.add(:base, eligibility_error_message(:no_applicable_products), + error_code: :no_applicable_products) + end + when "none" + unless order_products(order).none? { |product| eligible_products.include?(product) } + eligibility_errors.add(:base, eligibility_error_message(:has_excluded_product), + error_code: :has_excluded_product) + end + else + raise "unexpected match policy: #{preferred_match_policy.inspect}" + end + + eligibility_errors.empty? + end + + def product_ids_string + product_ids.join(',') + end + + def product_ids_string=(product_ids) + self.product_ids = product_ids.to_s.split(',').map(&:strip) + end + + private + + def order_products(order) + order.line_items.map(&:variant).map(&:product) + end + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb new file mode 100644 index 00000000000..a0fe76bf694 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module Rules + class Store < ::Spree::PromotionRule + has_many :promotion_rule_stores, class_name: "Spree::PromotionRuleStore", + foreign_key: :promotion_rule_id, + dependent: :destroy + has_many :stores, through: :promotion_rule_stores, class_name: "Spree::Store" + + def preload_relations + [:stores] + end + + def applicable?(promotable) + promotable.is_a?(Spree::Order) + end + + def eligible?(order, _options = {}) + stores.none? || stores.include?(order.store) + end + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb new file mode 100644 index 00000000000..6ab8658ad58 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module Rules + class Taxon < ::Spree::PromotionRule + has_many :promotion_rule_taxons, class_name: 'Spree::PromotionRuleTaxon', foreign_key: :promotion_rule_id, + dependent: :destroy + has_many :taxons, through: :promotion_rule_taxons, class_name: 'Spree::Taxon' + + def preload_relations + [:taxons] + end + + MATCH_POLICIES = %w(any all none) + + validates_inclusion_of :preferred_match_policy, in: MATCH_POLICIES + + preference :match_policy, :string, default: MATCH_POLICIES.first + def applicable?(promotable) + promotable.is_a?(Spree::Order) + end + + def eligible?(order, _options = {}) + order_taxons = taxons_in_order(order) + + case preferred_match_policy + when 'all' + matches_all = taxons.all? do |rule_taxon| + order_taxons.where(id: rule_taxon.self_and_descendants.ids).exists? + end + + unless matches_all + eligibility_errors.add(:base, eligibility_error_message(:missing_taxon), error_code: :missing_taxon) + end + when 'any' + unless order_taxons.where(id: rule_taxon_ids_with_children).exists? + eligibility_errors.add(:base, eligibility_error_message(:no_matching_taxons), error_code: :no_matching_taxons) + end + when 'none' + if order_taxons.where(id: rule_taxon_ids_with_children).exists? + eligibility_errors.add(:base, eligibility_error_message(:has_excluded_taxon), error_code: :has_excluded_taxon) + end + else + raise "unexpected match policy: #{preferred_match_policy.inspect}" + end + + eligibility_errors.empty? + end + + def taxon_ids_string + taxons.pluck(:id).join(',') + end + + def taxon_ids_string=(taxon_ids) + taxon_ids = taxon_ids.to_s.split(',').map(&:strip) + self.taxons = Spree::Taxon.find(taxon_ids) + end + + private + + # All taxons in an order + def taxons_in_order(order) + Spree::Taxon.joins(products: { variants_including_master: :line_items }) + .where(spree_line_items: { order_id: order.id }).distinct + end + + # ids of taxons rules and taxons rules children + def rule_taxon_ids_with_children + taxons.flat_map { |taxon| taxon.self_and_descendants.ids }.uniq + end + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb new file mode 100644 index 00000000000..1a0f08ad861 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module Rules + class User < ::Spree::PromotionRule + has_many :promotion_rule_users, class_name: 'Spree::PromotionRuleUser', + foreign_key: :promotion_rule_id, + dependent: :destroy + has_many :users, through: :promotion_rule_users, class_name: Spree::UserClassHandle.new + + def preload_relations + [:users] + end + + def applicable?(promotable) + promotable.is_a?(Spree::Order) + end + + def eligible?(order, _options = {}) + users.include?(order.user) + end + + def user_ids_string + user_ids.join(',') + end + + def user_ids_string=(user_ids) + self.user_ids = user_ids.to_s.split(',').map(&:strip) + end + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb new file mode 100644 index 00000000000..1f7f02afd3b --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module Rules + class UserLoggedIn < ::Spree::PromotionRule + def applicable?(promotable) + promotable.is_a?(Spree::Order) + end + + def eligible?(order, _options = {}) + unless order.user.present? + eligibility_errors.add(:base, eligibility_error_message(:no_user_specified), error_code: :no_user_specified) + end + eligibility_errors.empty? + end + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_role.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_role.rb new file mode 100644 index 00000000000..94fe8a65adf --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_role.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module Rules + class UserRole < ::Spree::PromotionRule + preference :role_ids, :array, default: [] + + MATCH_POLICIES = %w(any all) + preference :match_policy, default: MATCH_POLICIES.first + + def applicable?(promotable) + promotable.is_a?(Spree::Order) + end + + def eligible?(order, _options = {}) + return false unless order.user + if all_match_policy? + match_all_roles?(order) + else + match_any_roles?(order) + end + end + + private + + def all_match_policy? + preferred_match_policy == 'all' && preferred_role_ids.present? + end + + def user_roles(order) + order.user.spree_roles.where(id: preferred_role_ids) + end + + def match_all_roles?(order) + user_roles(order).count == preferred_role_ids.count + end + + def match_any_roles?(order) + user_roles(order).exists? + end + end + end +end diff --git a/friendly_promotions/spec/models/promotion/integration_spec.rb b/friendly_promotions/spec/models/promotion/integration_spec.rb index 555e3992668..4d065132bd0 100644 --- a/friendly_promotions/spec/models/promotion/integration_spec.rb +++ b/friendly_promotions/spec/models/promotion/integration_spec.rb @@ -17,7 +17,7 @@ end context "with an order-level rule" do - let(:rule) { Spree::Promotion::Rules::Product.new(products: [shirt]) } + let(:rule) { SolidusFriendlyPromotions::Rules::Product.new(products: [shirt]) } context "with an order level action" do let(:calculator) { Spree::Calculator::FlatPercentItemTotal.new(preferred_flat_percent: 20) } @@ -38,7 +38,6 @@ let(:action) { Spree::Promotion::Actions::CreateItemAdjustments.new(calculator: calculator) } it "creates one order-level adjustment" do - pending expect(order.adjustments).to be_empty expect(order.total).to eq(31.98) expect(order.item_total).to eq(39.98) @@ -49,14 +48,13 @@ end context "with a line-item level rule" do - let(:rule) { Spree::Promotion::Rules::LineItemProduct.new(products: [shirt]) } + let(:rule) { SolidusFriendlyPromotions::Rules::LineItemProduct.new(products: [shirt]) } context "with an order level action" do let(:calculator) { Spree::Calculator::FlatPercentItemTotal.new(preferred_flat_percent: 20) } let(:action) { Spree::Promotion::Actions::CreateAdjustment.new(calculator: calculator) } it "creates one order-level adjustment" do - pending # Whoops - this works because line item level rules don't affect order-level actions :( expect(order.adjustments.length).to eq(1) expect(order.total).to eq(31.98) diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_order_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_order_spec.rb new file mode 100644 index 00000000000..0a958413e3c --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_order_spec.rb @@ -0,0 +1,87 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SolidusFriendlyPromotions::Rules::FirstOrder, type: :model do + let(:rule) { SolidusFriendlyPromotions::Rules::FirstOrder.new } + let(:order) { mock_model(Spree::Order, user: nil, email: nil) } + let(:user) { mock_model(Spree::LegacyUser) } + + context "without a user or email" do + it { expect(rule).to be_eligible(order) } + it "does not set an error message" do + rule.eligible?(order) + expect(rule.eligibility_errors.full_messages.first). + to be_nil + end + end + + context "first order" do + context "for a signed user" do + context "with no completed orders" do + before(:each) do + allow(user).to receive_message_chain(:orders, complete: []) + end + + specify do + allow(order).to receive_messages(user: user) + expect(rule).to be_eligible(order) + end + + it "should be eligible when user passed in payload data" do + expect(rule).to be_eligible(order, user: user) + end + end + + context "with completed orders" do + before(:each) do + allow(order).to receive_messages(user: user) + end + + it "should be eligible when checked against first completed order" do + allow(user).to receive_message_chain(:orders, complete: [order]) + expect(rule).to be_eligible(order) + end + + context "with another order" do + before { allow(user).to receive_message_chain(:orders, complete: [mock_model(Spree::Order)]) } + it { expect(rule).not_to be_eligible(order) } + it "sets an error message" do + rule.eligible?(order) + expect(rule.eligibility_errors.full_messages.first). + to eq "This coupon code can only be applied to your first order." + end + it "sets an error code" do + rule.eligible?(order) + expect(rule.eligibility_errors.details[:base].first[:error_code]). + to eq :not_first_order + end + end + end + end + + context "for a guest user" do + let(:email) { 'user@solidus.io' } + before { allow(order).to receive_messages email: 'user@solidus.io' } + + context "with no other orders" do + it { expect(rule).to be_eligible(order) } + end + + context "with another order" do + before { allow(rule).to receive_messages(orders_by_email: [mock_model(Spree::Order)]) } + it { expect(rule).not_to be_eligible(order) } + it "sets an error message" do + rule.eligible?(order) + expect(rule.eligibility_errors.full_messages.first). + to eq "This coupon code can only be applied to your first order." + end + it "sets an error code" do + rule.eligible?(order) + expect(rule.eligibility_errors.details[:base].first[:error_code]). + to eq :not_first_order + end + end + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_repeat_purchase_since_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_repeat_purchase_since_spec.rb new file mode 100644 index 00000000000..a827a300f75 --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_repeat_purchase_since_spec.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SolidusFriendlyPromotions::Rules::FirstRepeatPurchaseSince do + describe "#applicable?" do + subject { described_class.new.applicable?(promotable) } + + context "when the promotable is an order" do + let(:promotable) { Spree::Order.new } + + it { is_expected.to be true } + end + + context "when the promotable is not a order" do + let(:promotable) { Spree::LineItem.new } + + it { is_expected.to be false } + end + end + + describe "eligible?" do + let(:instance) { described_class.new } + subject { instance.eligible?(order) } + + before do + instance.preferred_days_ago = 365 + end + + context "when the order does not have a user" do + let(:order) { Spree::Order.new } + + it { is_expected.to be false } + end + + context "when the order has a user" do + let(:order) { create :order } + let(:user) { order.user } + + context "when the user has completed orders" do + let(:order_completion_date_1) { 1.day.ago } + let(:order_completion_date_2) { 1.day.ago } + before do + old_order_1 = create :completed_order_with_totals, user: user + old_order_1.update(completed_at: order_completion_date_1) + + old_order_2 = create :completed_order_with_totals, user: user + old_order_2.update(completed_at: order_completion_date_2) + end + + context "the last completed order was greater than the preferred days ago" do + let(:order_completion_date_1) { 14.months.ago } + let(:order_completion_date_2) { 13.months.ago } + + it { is_expected.to be true } + end + + context "the last completed order was less than the preferred days ago" do + let(:order_completion_date_1) { 14.months.ago } + let(:order_completion_date_2) { 11.months.ago } + + it { is_expected.to be false } + end + end + + context "when the user has no completed orders " do + it { is_expected.to be false } + end + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/item_total_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/item_total_spec.rb new file mode 100644 index 00000000000..f1c6444f98f --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/item_total_spec.rb @@ -0,0 +1,128 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SolidusFriendlyPromotions::Rules::ItemTotal, type: :model do + let(:rule) do + SolidusFriendlyPromotions::Rules::ItemTotal.new( + preferred_amount: preferred_amount, + preferred_operator: preferred_operator + ) + end + let(:order) { double(:order, item_total: item_total, currency: order_currency) } + let(:preferred_amount) { 50 } + let(:order_currency) { 'USD' } + + context "preferred operator set to gt" do + let(:preferred_operator) { 'gt' } + + context "item total is greater than preferred amount" do + let(:item_total) { 51 } + + it "should be eligible when item total is greater than preferred amount" do + expect(rule).to be_eligible(order) + end + + context "when the order is a different currency" do + let(:order_currency) { "CAD" } + + it "is not eligible" do + expect(rule).not_to be_eligible(order) + end + end + end + + context "when item total is equal to preferred amount" do + let(:item_total) { 50 } + + it "is not eligible" do + expect(rule).not_to be_eligible(order) + end + + it "set an error message" do + rule.eligible?(order) + expect(rule.eligibility_errors.full_messages.first). + to eq "This coupon code can't be applied to orders less than or equal to $50.00." + end + it "sets an error code" do + rule.eligible?(order) + expect(rule.eligibility_errors.details[:base].first[:error_code]). + to eq :item_total_less_than_or_equal + end + end + + context "when item total is lower than preferred amount" do + let(:item_total) { 49 } + + it "is not eligible" do + expect(rule).not_to be_eligible(order) + end + + it "set an error message" do + rule.eligible?(order) + expect(rule.eligibility_errors.full_messages.first). + to eq "This coupon code can't be applied to orders less than or equal to $50.00." + end + it "sets an error code" do + rule.eligible?(order) + expect(rule.eligibility_errors.details[:base].first[:error_code]). + to eq :item_total_less_than_or_equal + end + end + end + + context "preferred operator set to gte" do + let(:preferred_operator) { 'gte' } + + context "total is greater than preferred amount" do + let(:item_total) { 51 } + + it "should be eligible when item total is greater than preferred amount" do + expect(rule).to be_eligible(order) + end + + context "when the order is a different currency" do + let(:order_currency) { "CAD" } + + it "is not eligible" do + expect(rule).not_to be_eligible(order) + end + end + end + + context "item total is equal to preferred amount" do + let(:item_total) { 50 } + + it "should be eligible" do + expect(rule).to be_eligible(order) + end + + context "when the order is a different currency" do + let(:order_currency) { "CAD" } + + it "is not eligible" do + expect(rule).not_to be_eligible(order) + end + end + end + + context "when item total is lower than preferred amount" do + let(:item_total) { 49 } + + it "is not eligible" do + expect(rule).not_to be_eligible(order) + end + + it "set an error message" do + rule.eligible?(order) + expect(rule.eligibility_errors.full_messages.first). + to eq "This coupon code can't be applied to orders less than $50.00." + end + it "sets an error code" do + rule.eligible?(order) + expect(rule.eligibility_errors.details[:base].first[:error_code]). + to eq :item_total_less_than + end + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/line_item_product_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/line_item_product_spec.rb new file mode 100644 index 00000000000..ee5d634c574 --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/line_item_product_spec.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe SolidusFriendlyPromotions::Rules::LineItemProduct, type: :model do + let(:rule) { described_class.new(rule_options) } + let(:rule_options) { {} } + + context "#eligible?(line_item)" do + let(:rule_line_item) { Spree::LineItem.new(product: rule_product) } + let(:other_line_item) { Spree::LineItem.new(product: other_product) } + + let(:rule_options) { super().merge(products: [rule_product]) } + let(:rule_product) { mock_model(Spree::Product) } + let(:other_product) { mock_model(Spree::Product) } + + it "should be eligible if there are no products" do + expect(rule).to be_eligible(rule_line_item) + end + + subject { rule.eligible?(line_item, {}) } + + context "for product in rule" do + let(:line_item) { rule_line_item } + it { is_expected.to be_truthy } + end + + context "for product not in rule" do + let(:line_item) { other_line_item } + it { is_expected.to be_falsey } + end + + context "if match policy is inverse" do + let(:rule_options) { super().merge(preferred_match_policy: "exclude") } + + context "for product in rule" do + let(:line_item) { rule_line_item } + it { is_expected.to be_falsey } + end + + context "for product not in rule" do + let(:line_item) { other_line_item } + it { is_expected.to be_truthy } + end + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/line_item_taxon_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/line_item_taxon_spec.rb new file mode 100644 index 00000000000..de67486218c --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/line_item_taxon_spec.rb @@ -0,0 +1,92 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SolidusFriendlyPromotions::Rules::LineItemTaxon, type: :model do + let(:taxon) { create :taxon, name: 'first' } + let(:taxon2) { create :taxon, name: 'second' } + let(:order) { create :order_with_line_items } + let(:product) { order.products.first } + + let(:rule) do + described_class.create!(promotion: create(:promotion)) + end + + describe '#eligible?' do + let(:line_item) { order.line_items.first! } + let(:order) { create :order_with_line_items } + let(:taxon) { create :taxon, name: 'first' } + + context 'with an invalid match policy' do + before do + rule.preferred_match_policy = 'invalid' + rule.save!(validate: false) + line_item.product.taxons << taxon + rule.taxons << taxon + end + + it 'raises' do + expect { + rule.eligible?(line_item) + }.to raise_error('unexpected match policy: "invalid"') + end + end + + context 'when a product has a taxon of a taxon rule' do + before do + product.taxons << taxon + rule.taxons << taxon + rule.save! + end + + it 'is eligible' do + expect(rule).to be_eligible(line_item) + end + end + + context 'when a product has a taxon child of a taxon rule' do + before do + taxon.children << taxon2 + product.taxons << taxon2 + rule.taxons << taxon + rule.save! + end + + it 'is eligible' do + expect(rule).to be_eligible(line_item) + end + + context "with 'exclude' match policy" do + before do + rule.update(preferred_match_policy: :exclude) + end + + it "is not eligible" do + expect(rule).not_to be_eligible(line_item) + end + end + end + + context 'when a product does not have taxon or child taxon of a taxon rule' do + before do + product.taxons << taxon2 + rule.taxons << taxon + rule.save! + end + + it 'is not eligible' do + expect(rule).not_to be_eligible(line_item) + end + + context "with 'exclude' match policy" do + before do + rule.update(preferred_match_policy: :exclude) + end + + it "is not eligible" do + expect(rule).to be_eligible(line_item) + end + end + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/nth_order_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/nth_order_spec.rb new file mode 100644 index 00000000000..483a685038d --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/nth_order_spec.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SolidusFriendlyPromotions::Rules::NthOrder do + describe "#applicable?" do + subject { described_class.new.applicable?(promotable) } + + context "when the promotable is an order" do + let(:promotable) { Spree::Order.new } + + it { is_expected.to be true } + end + + context "when the promotable is not a order" do + let(:promotable) { "not an order" } + + it { is_expected.to be false } + end + end + + describe "eligible?" do + let(:instance) { described_class.new } + subject { instance.eligible?(order) } + + before do + instance.preferred_nth_order = 2 + end + + context "when the order does not have a user" do + let(:order) { Spree::Order.new } + + it { is_expected.to be false } + end + + context "when the order has a user" do + let(:order) { create :order } + let(:user) { order.user } + + context "when the user has completed orders" do + before do + old_order = create :completed_order_with_totals, user: user + old_order.update(completed_at: 1.day.ago) + end + + context "when this order will be the 'nth' order" do + it { is_expected.to be true } + end + + context "when this order is completed and is still the 'nth' order" do + before do + order.update(completed_at: Time.current) + end + + it { is_expected.to be true } + end + + context "when this order will not be the 'nth' order" do + before do + instance.preferred_nth_order = 100 + end + + it { is_expected.to be false } + end + end + + context "when the user has no completed orders " do + it { is_expected.to be false } + end + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/one_use_per_user_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/one_use_per_user_spec.rb new file mode 100644 index 00000000000..35ef09ce1a0 --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/one_use_per_user_spec.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SolidusFriendlyPromotions::Rules::OneUsePerUser, type: :model do + let(:rule) { described_class.new } + + describe '#eligible?(order)' do + subject { rule.eligible?(order) } + let(:order) { double Spree::Order, user: user } + let(:user) { double Spree::LegacyUser } + let(:promotion) { stub_model Spree::Promotion, used_by?: used_by } + let(:used_by) { false } + + before { rule.promotion = promotion } + + context 'when the order is assigned to a user' do + context 'when the user has used this promotion before' do + let(:used_by) { true } + + it { is_expected.to be false } + it "sets an error message" do + subject + expect(rule.eligibility_errors.full_messages.first). + to eq "This coupon code can only be used once per user." + end + it "sets an error code" do + rule.eligible?(order) + expect(rule.eligibility_errors.details[:base].first[:error_code]). + to eq :limit_once_per_user + end + end + + context 'when the user has not used this promotion before' do + it { is_expected.to be true } + end + end + + context 'when the order is not assigned to a user' do + let(:user) { nil } + it { is_expected.to be false } + it "sets an error message" do + subject + expect(rule.eligibility_errors.full_messages.first). + to eq "You need to login before applying this coupon code." + end + it "sets an error code" do + rule.eligible?(order) + expect(rule.eligibility_errors.details[:base].first[:error_code]). + to eq :no_user_specified + end + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/option_value_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/option_value_spec.rb new file mode 100644 index 00000000000..fdf4e3441fb --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/option_value_spec.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SolidusFriendlyPromotions::Rules::OptionValue do + let(:rule) { SolidusFriendlyPromotions::Rules::OptionValue.new } + + describe "#preferred_eligible_values" do + subject { rule.preferred_eligible_values } + it "assigns a nicely formatted hash" do + rule.preferred_eligible_values = Hash["5" => "1,2", "6" => "1"] + expect(subject).to eq Hash[5 => [1, 2], 6 => [1]] + end + end + + describe "#applicable?" do + subject { rule.applicable?(promotable) } + context "when promotable is an order" do + let(:promotable) { Spree::Order.new } + it { is_expected.to be true } + end + context "when promotable is not an order" do + let(:promotable) { Spree::LineItem.new } + it { is_expected.to be false } + end + end + + describe "#eligible?" do + let(:variant) { create :variant } + let(:line_item) { create :line_item, variant: variant } + let(:promotable) { line_item.order } + subject { rule.eligible?(promotable) } + context "when there are any applicable line items" do + before do + rule.preferred_eligible_values = Hash[line_item.product.id => [ + line_item.variant.option_values.pluck(:id).first + ]] + end + it { is_expected.to be true } + end + context "when there are no applicable line items" do + before do + rule.preferred_eligible_values = Hash[99 => [99]] + end + it { is_expected.to be false } + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/product_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/product_spec.rb new file mode 100644 index 00000000000..2a2e30b53c6 --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/product_spec.rb @@ -0,0 +1,127 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SolidusFriendlyPromotions::Rules::Product, type: :model do + let(:rule) { SolidusFriendlyPromotions::Rules::Product.new(rule_options) } + let(:rule_options) { {} } + + context "#eligible?(order)" do + let(:order) { Spree::Order.new } + + it "should be eligible if there are no products" do + allow(rule).to receive_messages(eligible_products: []) + expect(rule).to be_eligible(order) + end + + before do + 3.times { |i| instance_variable_set("@product#{i}", mock_model(Spree::Product)) } + end + + context "with 'any' match policy" do + let(:rule_options) { super().merge(preferred_match_policy: "any") } + + it "should be eligible if any of the products is in eligible products" do + allow(rule).to receive_messages(order_products: [@product1, @product2]) + allow(rule).to receive_messages(eligible_products: [@product2, @product3]) + expect(rule).to be_eligible(order) + end + + context "when none of the products are eligible products" do + before do + allow(rule).to receive_messages(order_products: [@product1]) + allow(rule).to receive_messages(eligible_products: [@product2, @product3]) + end + it { expect(rule).not_to be_eligible(order) } + it "sets an error message" do + rule.eligible?(order) + expect(rule.eligibility_errors.full_messages.first). + to eq "You need to add an applicable product before applying this coupon code." + end + it "sets an error code" do + rule.eligible?(order) + expect(rule.eligibility_errors.details[:base].first[:error_code]). + to eq :no_applicable_products + end + end + end + + context "with 'all' match policy" do + let(:rule_options) { super().merge(preferred_match_policy: "all") } + + it "should be eligible if all of the eligible products are ordered" do + allow(rule).to receive_messages(order_products: [@product3, @product2, @product1]) + allow(rule).to receive_messages(eligible_products: [@product2, @product3]) + expect(rule).to be_eligible(order) + end + + context "when any of the eligible products is not ordered" do + before do + allow(rule).to receive_messages(order_products: [@product1, @product2]) + allow(rule).to receive_messages(eligible_products: [@product1, @product2, @product3]) + end + it { expect(rule).not_to be_eligible(order) } + it "sets an error message" do + rule.eligible?(order) + expect(rule.eligibility_errors.full_messages.first). + to eq "This coupon code can't be applied because you don't have all of the necessary products in your cart." + end + it "sets an error code" do + rule.eligible?(order) + expect(rule.eligibility_errors.details[:base].first[:error_code]). + to eq :missing_product + end + end + end + + context "with 'none' match policy" do + let(:rule_options) { super().merge(preferred_match_policy: "none") } + + it "should be eligible if none of the order's products are in eligible products" do + allow(rule).to receive_messages(order_products: [@product1]) + allow(rule).to receive_messages(eligible_products: [@product2, @product3]) + expect(rule).to be_eligible(order) + end + + context "when any of the order's products are in eligible products" do + before do + allow(rule).to receive_messages(order_products: [@product1, @product2]) + allow(rule).to receive_messages(eligible_products: [@product2, @product3]) + end + it { expect(rule).not_to be_eligible(order) } + it "sets an error message" do + rule.eligible?(order) + expect(rule.eligibility_errors.full_messages.first). + to eq "Your cart contains a product that prevents this coupon code from being applied." + end + it "sets an error code" do + rule.eligible?(order) + expect(rule.eligibility_errors.details[:base].first[:error_code]). + to eq :has_excluded_product + end + end + end + + context "with an invalid match policy" do + let(:rule) do + SolidusFriendlyPromotions::Rules::Product.create!( + promotion: create(:promotion), + product_promotion_rules: [ + Spree::ProductPromotionRule.new(product: product), + ], + ).tap do |rule| + rule.preferred_match_policy = 'invalid' + rule.save!(validate: false) + end + end + let(:product) { order.line_items.first!.product } + let(:order) { create(:order_with_line_items, line_items_count: 1) } + + it 'raises' do + expect { + rule.eligible?(order) + }.to raise_error('unexpected match policy: "invalid"') + end + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/store_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/store_spec.rb new file mode 100644 index 00000000000..f7e3b558bc9 --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/store_spec.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe SolidusFriendlyPromotions::Rules::Store, type: :model do + let(:rule) { described_class.new } + + context "#eligible?(order)" do + let(:order) { Spree::Order.new } + + it "is eligible if no stores are provided" do + expect(rule).to be_eligible(order) + end + + it "is eligible if stores include the order's store" do + default_store = Spree::Store.new(name: "Default") + other_store = Spree::Store.new(name: "Other") + + rule.stores = [default_store, other_store] + order.store = default_store + + expect(rule).to be_eligible(order) + end + + it "is not eligible if order is placed in a different store" do + default_store = Spree::Store.new(name: "Default") + other_store = Spree::Store.new(name: "Other") + + rule.stores = [other_store] + order.store = default_store + + expect(rule).not_to be_eligible(order) + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/taxon_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/taxon_spec.rb new file mode 100644 index 00000000000..a75e8728b64 --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/taxon_spec.rb @@ -0,0 +1,144 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SolidusFriendlyPromotions::Rules::Taxon, type: :model do + let(:taxon) { create :taxon, name: 'first' } + let(:taxon2) { create :taxon, name: 'second' } + let(:order) { create :order_with_line_items } + let(:product) { order.products.first } + + let(:rule) do + SolidusFriendlyPromotions::Rules::Taxon.create!(promotion: create(:promotion)) + end + + context '#eligible?(order)' do + context 'with any match policy' do + before do + rule.update!(preferred_match_policy: 'any') + end + + it 'is eligible if order does have any prefered taxon' do + product.taxons << taxon + rule.taxons << taxon + expect(rule).to be_eligible(order) + end + + context "when order does not have any prefered taxon" do + before { rule.taxons << taxon2 } + it { expect(rule).not_to be_eligible(order) } + it "sets an error message" do + rule.eligible?(order) + expect(rule.eligibility_errors.full_messages.first). + to eq "You need to add a product from an applicable category before applying this coupon code." + end + it "sets an error code" do + rule.eligible?(order) + expect(rule.eligibility_errors.details[:base].first[:error_code]). + to eq :no_matching_taxons + end + end + + context 'when a product has a taxon child of a taxon rule' do + before do + taxon.children << taxon2 + product.taxons << taxon2 + rule.taxons << taxon + end + + it{ expect(rule).to be_eligible(order) } + end + end + + context 'with all match policy' do + before do + rule.update!(preferred_match_policy: 'all') + end + + it 'is eligible order has all prefered taxons' do + product.taxons << taxon2 + order.products.last.taxons << taxon + + rule.taxons = [taxon, taxon2] + + expect(rule).to be_eligible(order) + end + + context "when order does not have all prefered taxons" do + before { rule.taxons << taxon } + it { expect(rule).not_to be_eligible(order) } + it "sets an error message" do + rule.eligible?(order) + expect(rule.eligibility_errors.full_messages.first). + to eq "You need to add a product from all applicable categories before applying this coupon code." + end + it "sets an error code" do + rule.eligible?(order) + expect(rule.eligibility_errors.details[:base].first[:error_code]). + to eq :missing_taxon + end + end + + context 'when a product has a taxon child of a taxon rule' do + let(:taxon3) { create :taxon } + + before do + taxon.children << taxon2 + taxon.save! + taxon.reload + + product.taxons = [taxon2, taxon3] + rule.taxons = [taxon, taxon3] + end + + it { expect(rule).to be_eligible(order) } + end + end + + context 'with none match policy' do + before do + rule.preferred_match_policy = 'none' + end + + context "none of the order's products are in listed taxon" do + before { rule.taxons << taxon2 } + it { expect(rule).to be_eligible(order) } + end + + context "one of the order's products is in a listed taxon" do + before do + order.products.first.taxons << taxon + rule.taxons << taxon + end + it "should not be eligible" do + expect(rule).not_to be_eligible(order) + end + it "sets an error message" do + rule.eligible?(order) + expect(rule.eligibility_errors.full_messages.first). + to eq "Your cart contains a product from an excluded category that prevents this coupon code from being applied." + end + it "sets an error code" do + rule.eligible?(order) + expect(rule.eligibility_errors.details[:base].first[:error_code]). + to eq :has_excluded_taxon + end + end + end + + context 'with an invalid match policy' do + before do + order.products.first.taxons << taxon + rule.taxons << taxon + rule.preferred_match_policy = 'invalid' + rule.save!(validate: false) + end + + it 'raises' do + expect { + rule.eligible?(order) + }.to raise_error('unexpected match policy: "invalid"') + end + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_logged_in_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_logged_in_spec.rb new file mode 100644 index 00000000000..7c6191ef504 --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_logged_in_spec.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SolidusFriendlyPromotions::Rules::UserLoggedIn, type: :model do + let(:rule) { SolidusFriendlyPromotions::Rules::UserLoggedIn.new } + + context "#eligible?(order)" do + let(:order) { Spree::Order.new } + + it "should be eligible if order has an associated user" do + user = double('User') + allow(order).to receive_messages(user: user) + + expect(rule).to be_eligible(order) + end + + context "when user is not logged in" do + before { allow(order).to receive_messages(user: nil) } # better to be explicit here + it { expect(rule).not_to be_eligible(order) } + it "sets an error message" do + rule.eligible?(order) + expect(rule.eligibility_errors.full_messages.first). + to eq "You need to login before applying this coupon code." + end + it "sets an error code" do + rule.eligible?(order) + expect(rule.eligibility_errors.details[:base].first[:error_code]). + to eq :no_user_specified + end + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_role_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_role_spec.rb new file mode 100644 index 00000000000..beae7821091 --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_role_spec.rb @@ -0,0 +1,88 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SolidusFriendlyPromotions::Rules::UserRole, type: :model do + let(:rule) { described_class.new(preferred_role_ids: roles_for_rule.map(&:id)) } + let(:user) { create(:user, spree_roles: roles_for_user) } + let(:roles_for_rule) { [] } + let(:roles_for_user) { [] } + + subject { rule } + + shared_examples 'eligibility' do + context 'no roles on rule' do + let(:roles_for_user) { [create(:role)] } + it 'should not be eligible' do + expect(rule).to_not be_eligible(order) + end + end + + context 'no roles on user' do + let(:roles_for_rule) { [create(:role)] } + it 'should not be eligible' do + expect(rule).to_not be_eligible(order) + end + end + + context 'mismatched roles' do + let(:roles_for_user) { [create(:role)] } + let(:roles_for_rule) { [create(:role)] } + it 'should not be eligible' do + expect(rule).to_not be_eligible(order) + end + end + + context 'matching all roles' do + let(:roles_for_user) { [create(:role), create(:role)] } + let(:roles_for_rule) { roles_for_user } + it 'should be eligible' do + expect(rule).to be_eligible(order) + end + end + end + + context '#eligible?(order)' do + context 'order with no user' do + let(:order) { Spree::Order.new } + + it 'should not be eligible' do + expect(rule).to_not be_eligible(order) + end + end + + context 'order with user' do + let(:order) { Spree::Order.new(user: user) } + + context 'with any match policy' do + before { rule.preferred_match_policy = 'any' } + + include_examples 'eligibility' + + context 'one shared role' do + let(:shared_role) { create(:role) } + let(:roles_for_user) { [create(:role), shared_role] } + let(:roles_for_rule) { [create(:role), shared_role] } + it 'should be eligible' do + expect(rule).to be_eligible(order) + end + end + end + + context 'with all match policy' do + before { rule.preferred_match_policy = 'all' } + + include_examples 'eligibility' + + context 'one shared role' do + let(:shared_role) { create(:role) } + let(:roles_for_user) { [create(:role), shared_role] } + let(:roles_for_rule) { [create(:role), shared_role] } + it 'should not be eligible' do + expect(rule).to_not be_eligible(order) + end + end + end + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_spec.rb new file mode 100644 index 00000000000..c1df8349180 --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_spec.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SolidusFriendlyPromotions::Rules::User, type: :model do + let(:rule) { SolidusFriendlyPromotions::Rules::User.new } + + context "#eligible?(order)" do + let(:order) { Spree::Order.new } + + it "should not be eligible if users are not provided" do + expect(rule).not_to be_eligible(order) + end + + it "should be eligible if users include user placing the order" do + user = mock_model(Spree::LegacyUser) + users = [user, mock_model(Spree::LegacyUser)] + allow(rule).to receive_messages(users: users) + allow(order).to receive_messages(user: user) + + expect(rule).to be_eligible(order) + end + + it "should not be eligible if user placing the order is not listed" do + allow(order).to receive_messages(user: mock_model(Spree::LegacyUser)) + users = [mock_model(Spree::LegacyUser), mock_model(Spree::LegacyUser)] + allow(rule).to receive_messages(users: users) + + expect(rule).not_to be_eligible(order) + end + + # Regression test for https://github.com/spree/spree/issues/3885 + it "can assign to user_ids" do + user1 = Spree::LegacyUser.create!(email: "test1@example.com") + user2 = Spree::LegacyUser.create!(email: "test2@example.com") + rule.user_ids = "#{user1.id}, #{user2.id}" + end + end +end From 4f172c192dac6fe0bd3faedc27bddb3293870d1a Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 31 May 2023 12:56:01 +0200 Subject: [PATCH 006/524] Standard Ruby --- friendly_promotions/Gemfile | 31 +++-- friendly_promotions/Rakefile | 4 +- .../rules/first_repeat_purchase_since.rb | 2 +- .../rules/item_total.rb | 18 +-- .../rules/line_item_option_value.rb | 4 +- .../rules/line_item_product.rb | 66 +++++------ .../rules/line_item_taxon.rb | 74 ++++++------ .../rules/nth_order.rb | 65 ++++++----- .../rules/one_use_per_user.rb | 28 ++--- .../rules/option_value.rb | 36 +++--- .../rules/product.rb | 106 +++++++++--------- .../rules/store.rb | 30 ++--- .../rules/taxon.rb | 106 +++++++++--------- .../solidus_friendly_promotions/rules/user.rb | 42 +++---- .../rules/user_logged_in.rb | 20 ++-- .../rules/user_role.rb | 56 ++++----- .../install/install_generator.rb | 20 ++-- .../lib/solidus_friendly_promotions.rb | 6 +- .../configuration.rb | 2 +- .../lib/solidus_friendly_promotions/engine.rb | 6 +- .../solidus_friendly_promotions/version.rb | 2 +- .../solidus_friendly_promotions.gemspec | 33 +++--- .../rules/first_order_spec.rb | 26 ++--- .../rules/first_repeat_purchase_since_spec.rb | 2 +- .../rules/item_total_spec.rb | 32 +++--- .../rules/line_item_taxon_spec.rb | 30 ++--- .../rules/nth_order_spec.rb | 2 +- .../rules/one_use_per_user_spec.rb | 28 ++--- .../rules/option_value_spec.rb | 12 +- .../rules/product_spec.rb | 34 +++--- .../rules/taxon_spec.rb | 62 +++++----- .../rules/user_logged_in_spec.rb | 12 +- .../rules/user_role_spec.rb | 48 ++++---- .../rules/user_spec.rb | 2 +- friendly_promotions/spec/spec_helper.rb | 12 +- 35 files changed, 528 insertions(+), 531 deletions(-) diff --git a/friendly_promotions/Gemfile b/friendly_promotions/Gemfile index 0a229872832..5b90455db84 100644 --- a/friendly_promotions/Gemfile +++ b/friendly_promotions/Gemfile @@ -1,37 +1,36 @@ # frozen_string_literal: true -source 'https://rubygems.org' +source "https://rubygems.org" git_source(:github) { |repo| "https://github.com/#{repo}.git" } -branch = ENV.fetch('SOLIDUS_BRANCH', 'main') -gem 'solidus', github: 'solidusio/solidus', branch: branch +branch = ENV.fetch("SOLIDUS_BRANCH", "main") +gem "solidus", github: "solidusio/solidus", branch: branch # The solidus_frontend gem has been pulled out since v3.2 -gem 'solidus_frontend', github: 'solidusio/solidus_frontend' if branch == 'master' -gem 'solidus_frontend' if branch >= 'v3.2' # rubocop:disable Bundler/DuplicatedGem +gem "solidus_frontend", github: "solidusio/solidus_frontend" if branch == "master" +gem "solidus_frontend" if branch >= "v3.2" # rubocop:disable Bundler/DuplicatedGem # Needed to help Bundler figure out how to resolve dependencies, # otherwise it takes forever to resolve them. # See https://github.com/bundler/bundler/issues/6677 -gem 'rails', '>0.a' - +gem "rails", ">0.a" # Provides basic authentication functionality for testing parts of your engine -gem 'solidus_auth_devise' +gem "solidus_auth_devise" -case ENV.fetch('DB', nil) -when 'mysql' - gem 'mysql2' -when 'postgresql' - gem 'pg' +case ENV.fetch("DB", nil) +when "mysql" + gem "mysql2" +when "postgresql" + gem "pg" else - gem 'sqlite3' + gem "sqlite3" end # While we still support Ruby < 3 we need to workaround a limitation in # the 'async' gem that relies on the latest ruby, since RubyGems doesn't # resolve gems based on the required ruby version. -gem 'async', '< 3' if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('3') +gem "async", "< 3" if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("3") gemspec @@ -40,4 +39,4 @@ gemspec # # We use `send` instead of calling `eval_gemfile` to work around an issue with # how Dependabot parses projects: https://github.com/dependabot/dependabot-core/issues/1658. -send(:eval_gemfile, 'Gemfile-local') if File.exist? 'Gemfile-local' +send(:eval_gemfile, "Gemfile-local") if File.exist? "Gemfile-local" diff --git a/friendly_promotions/Rakefile b/friendly_promotions/Rakefile index c08aa46816c..50635fc88d1 100644 --- a/friendly_promotions/Rakefile +++ b/friendly_promotions/Rakefile @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'solidus_dev_support/rake_tasks' +require "solidus_dev_support/rake_tasks" SolidusDevSupport::RakeTasks.install -task default: 'extension:specs' +task default: "extension:specs" diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb index eab1e40fa42..409b2f1ea99 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb @@ -4,7 +4,7 @@ module SolidusFriendlyPromotions module Rules class FirstRepeatPurchaseSince < ::Spree::PromotionRule preference :days_ago, :integer, default: 365 - validates :preferred_days_ago, numericality: { only_integer: true, greater_than: 0 } + validates :preferred_days_ago, numericality: {only_integer: true, greater_than: 0} # This promotion is applicable to orders only. def applicable?(promotable) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb index 153134ee604..b80c3d45357 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb @@ -11,20 +11,20 @@ class ItemTotal < ::Spree::PromotionRule include ActiveSupport::Deprecation::DeprecatedConstantAccessor preference :amount, :decimal, default: 100.00 - preference :currency, :string, default: ->{ Spree::Config[:currency] } - preference :operator, :string, default: 'gt' + preference :currency, :string, default: -> { Spree::Config[:currency] } + preference :operator, :string, default: "gt" # The list of allowed operators names mapped to their symbols. def self.operators_map { gte: :>=, - gt: :>, + gt: :> } end def self.operator_options operators_map.map do |name, _method| - [I18n.t(name, scope: 'spree.item_total_rule.operators'), name] + [I18n.t(name, scope: "spree.item_total_rule.operators"), name] end end @@ -34,7 +34,7 @@ def self.operator_options :OPERATORS, :operators_map, message: "OPERATORS is deprecated! Use `operators_map.keys.map(&:to_s)` instead.", - deprecator: Spree::Deprecation, + deprecator: Spree::Deprecation ) def applicable?(promotable) @@ -56,7 +56,7 @@ def eligible?(order, _options = {}) def operator self.class.operators_map.fetch( preferred_operator.to_sym, - preferred_operator_default, + preferred_operator_default ) end @@ -74,9 +74,9 @@ def formatted_amount def ineligible_message case preferred_operator.to_s - when 'gte' + when "gte" eligibility_error_message(:item_total_less_than, amount: formatted_amount) - when 'gt' + when "gt" eligibility_error_message(:item_total_less_than_or_equal, amount: formatted_amount) else eligibility_error_message(:item_total_doesnt_match_with_operator, amount: formatted_amount, operator: preferred_operator) @@ -84,7 +84,7 @@ def ineligible_message end def ineligible_error_code - if preferred_operator == 'gte' + if preferred_operator == "gte" :item_total_less_than else :item_total_less_than_or_equal diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_option_value.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_option_value.rb index ceff257c286..27a2eb46ba3 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_option_value.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_option_value.rb @@ -18,11 +18,11 @@ def eligible?(line_item, _options = {}) def preferred_eligible_values values = preferences[:eligible_values] || {} - Hash[values.keys.map(&:to_i).zip( + values.keys.map(&:to_i).zip( values.values.map do |value| (value.is_a?(Array) ? value : value.split(",")).map(&:to_i) end - )] + ).to_h end private diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb index d930e6f5681..ad969ce628c 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb @@ -2,45 +2,45 @@ module SolidusFriendlyPromotions module Rules - # A rule to apply a promotion only to line items with or without a chosen product - class LineItemProduct < Spree::PromotionRule - MATCH_POLICIES = %w(include exclude) - - has_many :product_promotion_rules, - dependent: :destroy, - foreign_key: :promotion_rule_id, - class_name: "Spree::ProductPromotionRule" - has_many :products, - class_name: "Spree::Product", - through: :product_promotion_rules - - preference :match_policy, :string, default: MATCH_POLICIES.first - - def applicable?(promotable) - promotable.is_a?(Spree::LineItem) - end + # A rule to apply a promotion only to line items with or without a chosen product + class LineItemProduct < Spree::PromotionRule + MATCH_POLICIES = %w[include exclude] + + has_many :product_promotion_rules, + dependent: :destroy, + foreign_key: :promotion_rule_id, + class_name: "Spree::ProductPromotionRule" + has_many :products, + class_name: "Spree::Product", + through: :product_promotion_rules + + preference :match_policy, :string, default: MATCH_POLICIES.first + + def applicable?(promotable) + promotable.is_a?(Spree::LineItem) + end - def eligible?(line_item, _options = {}) - if inverse? - !product_ids.include?(line_item.variant.product_id) - else - product_ids.include?(line_item.variant.product_id) - end + def eligible?(line_item, _options = {}) + if inverse? + !product_ids.include?(line_item.variant.product_id) + else + product_ids.include?(line_item.variant.product_id) end + end - def product_ids_string - product_ids.join(",") - end + def product_ids_string + product_ids.join(",") + end - def product_ids_string=(product_ids) - self.product_ids = product_ids.to_s.split(",").map(&:strip) - end + def product_ids_string=(product_ids) + self.product_ids = product_ids.to_s.split(",").map(&:strip) + end - private + private - def inverse? - preferred_match_policy == "exclude" - end + def inverse? + preferred_match_policy == "exclude" end end end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb index ae6683c6f4b..45caa7fc53f 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb @@ -1,52 +1,52 @@ # frozen_string_literal: true module SolidusFriendlyPromotions - module Rules - class LineItemTaxon < ::Spree::PromotionRule - has_many :promotion_rule_taxons, class_name: 'Spree::PromotionRuleTaxon', foreign_key: :promotion_rule_id, - dependent: :destroy - has_many :taxons, through: :promotion_rule_taxons, class_name: 'Spree::Taxon' + module Rules + class LineItemTaxon < ::Spree::PromotionRule + has_many :promotion_rule_taxons, class_name: "Spree::PromotionRuleTaxon", foreign_key: :promotion_rule_id, + dependent: :destroy + has_many :taxons, through: :promotion_rule_taxons, class_name: "Spree::Taxon" - MATCH_POLICIES = %w(include exclude) + MATCH_POLICIES = %w[include exclude] - validates_inclusion_of :preferred_match_policy, in: MATCH_POLICIES + validates_inclusion_of :preferred_match_policy, in: MATCH_POLICIES - preference :match_policy, :string, default: MATCH_POLICIES.first - def applicable?(promotable) - promotable.is_a?(Spree::LineItem) - end + preference :match_policy, :string, default: MATCH_POLICIES.first + def applicable?(promotable) + promotable.is_a?(Spree::LineItem) + end - def eligible?(line_item, _options = {}) - found = Spree::Classification.where( - product_id: line_item.variant.product_id, - taxon_id: rule_taxon_ids_with_children - ).exists? - - case preferred_match_policy - when 'include' - found - when 'exclude' - !found - else - raise "unexpected match policy: #{preferred_match_policy.inspect}" - end + def eligible?(line_item, _options = {}) + found = Spree::Classification.where( + product_id: line_item.variant.product_id, + taxon_id: rule_taxon_ids_with_children + ).exists? + + case preferred_match_policy + when "include" + found + when "exclude" + !found + else + raise "unexpected match policy: #{preferred_match_policy.inspect}" end + end - def taxon_ids_string - taxons.pluck(:id).join(',') - end + def taxon_ids_string + taxons.pluck(:id).join(",") + end - def taxon_ids_string=(taxon_ids) - taxon_ids = taxon_ids.to_s.split(',').map(&:strip) - self.taxons = Spree::Taxon.find(taxon_ids) - end + def taxon_ids_string=(taxon_ids) + taxon_ids = taxon_ids.to_s.split(",").map(&:strip) + self.taxons = Spree::Taxon.find(taxon_ids) + end - private + private - # ids of taxons rules and taxons rules children - def rule_taxon_ids_with_children - taxons.flat_map { |taxon| taxon.self_and_descendants.ids }.uniq - end + # ids of taxons rules and taxons rules children + def rule_taxon_ids_with_children + taxons.flat_map { |taxon| taxon.self_and_descendants.ids }.uniq end end + end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb index 3c8d94ca5cf..a8ceb652431 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb @@ -1,44 +1,43 @@ # frozen_string_literal: true module SolidusFriendlyPromotions + module Rules + class NthOrder < ::Spree::PromotionRule + preference :nth_order, :integer, default: 2 + # It does not make sense to have this apply to the first order using preferred_nth_order == 1 + # Instead we could use the first_order rule + validates :preferred_nth_order, numericality: {only_integer: true, greater_than: 1} + + # This promotion is applicable to orders only. + def applicable?(promotable) + promotable.is_a?(Spree::Order) + end - module Rules - class NthOrder < ::Spree::PromotionRule - preference :nth_order, :integer, default: 2 - # It does not make sense to have this apply to the first order using preferred_nth_order == 1 - # Instead we could use the first_order rule - validates :preferred_nth_order, numericality: { only_integer: true, greater_than: 1 } - - # This promotion is applicable to orders only. - def applicable?(promotable) - promotable.is_a?(Spree::Order) - end - - # This is never eligible if the order does not have a user, and that user does not have any previous completed orders. - # - # Use the first order rule if you want a promotion to be applied to the first order for a user. - # @param order [Spree::Order] - def eligible?(order, _options = {}) - return false unless order.user + # This is never eligible if the order does not have a user, and that user does not have any previous completed orders. + # + # Use the first order rule if you want a promotion to be applied to the first order for a user. + # @param order [Spree::Order] + def eligible?(order, _options = {}) + return false unless order.user - nth_order?(order) - end + nth_order?(order) + end - private + private - def completed_order_count(order) - order. - user. - orders. - complete. - where(Spree::Order.arel_table[:completed_at].lt(order.completed_at || Time.current)). - count - end + def completed_order_count(order) + order + .user + .orders + .complete + .where(Spree::Order.arel_table[:completed_at].lt(order.completed_at || Time.current)) + .count + end - def nth_order?(order) - count = completed_order_count(order) + 1 - count == preferred_nth_order - end + def nth_order?(order) + count = completed_order_count(order) + 1 + count == preferred_nth_order end + end end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb index 2b174a8c9c7..8cbc905d02a 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb @@ -1,23 +1,23 @@ # frozen_string_literal: true module SolidusFriendlyPromotions - module Rules - class OneUsePerUser < ::Spree::PromotionRule - def applicable?(promotable) - promotable.is_a?(Spree::Order) - end + module Rules + class OneUsePerUser < ::Spree::PromotionRule + def applicable?(promotable) + promotable.is_a?(Spree::Order) + end - def eligible?(order, _options = {}) - if order.user.present? - if promotion.used_by?(order.user, [order]) - eligibility_errors.add(:base, eligibility_error_message(:limit_once_per_user), error_code: :limit_once_per_user) - end - else - eligibility_errors.add(:base, eligibility_error_message(:no_user_specified), error_code: :no_user_specified) + def eligible?(order, _options = {}) + if order.user.present? + if promotion.used_by?(order.user, [order]) + eligibility_errors.add(:base, eligibility_error_message(:limit_once_per_user), error_code: :limit_once_per_user) end - - eligibility_errors.empty? + else + eligibility_errors.add(:base, eligibility_error_message(:no_user_specified), error_code: :no_user_specified) end + + eligibility_errors.empty? end end + end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/option_value.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/option_value.rb index 26e1cffba9a..84f79875b90 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/option_value.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/option_value.rb @@ -1,28 +1,28 @@ # frozen_string_literal: true module SolidusFriendlyPromotions - module Rules - class OptionValue < ::Spree::PromotionRule - preference :eligible_values, :hash + module Rules + class OptionValue < ::Spree::PromotionRule + preference :eligible_values, :hash - def applicable?(promotable) - promotable.is_a?(Spree::Order) - end + def applicable?(promotable) + promotable.is_a?(Spree::Order) + end - def eligible?(order, _options = {}) - order.line_items.any? do |item| - LineItemOptionValue.new(preferred_eligible_values: preferred_eligible_values).eligible?(item) - end + def eligible?(order, _options = {}) + order.line_items.any? do |item| + LineItemOptionValue.new(preferred_eligible_values: preferred_eligible_values).eligible?(item) end + end - def preferred_eligible_values - values = preferences[:eligible_values] || {} - Hash[values.keys.map(&:to_i).zip( - values.values.map do |value| - (value.is_a?(Array) ? value : value.split(",")).map(&:to_i) - end - )] - end + def preferred_eligible_values + values = preferences[:eligible_values] || {} + values.keys.map(&:to_i).zip( + values.values.map do |value| + (value.is_a?(Array) ? value : value.split(",")).map(&:to_i) + end + ).to_h end end + end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb index f6b0ce019da..e257adc952a 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb @@ -1,73 +1,73 @@ # frozen_string_literal: true module SolidusFriendlyPromotions - module Rules - # A rule to limit a promotion based on products in the order. Can - # require all or any of the products to be present. Valid products - # either come from assigned product group or are assingned directly to - # the rule. - class Product < ::Spree::PromotionRule - has_many :product_promotion_rules, dependent: :destroy, foreign_key: :promotion_rule_id, - class_name: 'Spree::ProductPromotionRule' - has_many :products, class_name: 'Spree::Product', through: :product_promotion_rules + module Rules + # A rule to limit a promotion based on products in the order. Can + # require all or any of the products to be present. Valid products + # either come from assigned product group or are assingned directly to + # the rule. + class Product < ::Spree::PromotionRule + has_many :product_promotion_rules, dependent: :destroy, foreign_key: :promotion_rule_id, + class_name: "Spree::ProductPromotionRule" + has_many :products, class_name: "Spree::Product", through: :product_promotion_rules - def preload_relations - [:products] - end + def preload_relations + [:products] + end - MATCH_POLICIES = %w(any all none) + MATCH_POLICIES = %w[any all none] - validates_inclusion_of :preferred_match_policy, in: MATCH_POLICIES + validates_inclusion_of :preferred_match_policy, in: MATCH_POLICIES - preference :match_policy, :string, default: MATCH_POLICIES.first + preference :match_policy, :string, default: MATCH_POLICIES.first - # scope/association that is used to test eligibility - def eligible_products - products - end + # scope/association that is used to test eligibility + def eligible_products + products + end - def applicable?(promotable) - promotable.is_a?(Spree::Order) - end + def applicable?(promotable) + promotable.is_a?(Spree::Order) + end - def eligible?(order, _options = {}) - return true if eligible_products.empty? + def eligible?(order, _options = {}) + return true if eligible_products.empty? - case preferred_match_policy - when "all" - unless eligible_products.all? { |product| order_products(order).include?(product) } - eligibility_errors.add(:base, eligibility_error_message(:missing_product), error_code: :missing_product) - end - when "any" - unless order_products(order).any? { |product| eligible_products.include?(product) } - eligibility_errors.add(:base, eligibility_error_message(:no_applicable_products), - error_code: :no_applicable_products) - end - when "none" - unless order_products(order).none? { |product| eligible_products.include?(product) } - eligibility_errors.add(:base, eligibility_error_message(:has_excluded_product), - error_code: :has_excluded_product) - end - else - raise "unexpected match policy: #{preferred_match_policy.inspect}" + case preferred_match_policy + when "all" + unless eligible_products.all? { |product| order_products(order).include?(product) } + eligibility_errors.add(:base, eligibility_error_message(:missing_product), error_code: :missing_product) end - - eligibility_errors.empty? + when "any" + unless order_products(order).any? { |product| eligible_products.include?(product) } + eligibility_errors.add(:base, eligibility_error_message(:no_applicable_products), + error_code: :no_applicable_products) + end + when "none" + unless order_products(order).none? { |product| eligible_products.include?(product) } + eligibility_errors.add(:base, eligibility_error_message(:has_excluded_product), + error_code: :has_excluded_product) + end + else + raise "unexpected match policy: #{preferred_match_policy.inspect}" end - def product_ids_string - product_ids.join(',') - end + eligibility_errors.empty? + end - def product_ids_string=(product_ids) - self.product_ids = product_ids.to_s.split(',').map(&:strip) - end + def product_ids_string + product_ids.join(",") + end + + def product_ids_string=(product_ids) + self.product_ids = product_ids.to_s.split(",").map(&:strip) + end - private + private - def order_products(order) - order.line_items.map(&:variant).map(&:product) - end + def order_products(order) + order.line_items.map(&:variant).map(&:product) end + end end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb index a0fe76bf694..680600542ff 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb @@ -1,24 +1,24 @@ # frozen_string_literal: true module SolidusFriendlyPromotions - module Rules - class Store < ::Spree::PromotionRule - has_many :promotion_rule_stores, class_name: "Spree::PromotionRuleStore", - foreign_key: :promotion_rule_id, - dependent: :destroy - has_many :stores, through: :promotion_rule_stores, class_name: "Spree::Store" + module Rules + class Store < ::Spree::PromotionRule + has_many :promotion_rule_stores, class_name: "Spree::PromotionRuleStore", + foreign_key: :promotion_rule_id, + dependent: :destroy + has_many :stores, through: :promotion_rule_stores, class_name: "Spree::Store" - def preload_relations - [:stores] - end + def preload_relations + [:stores] + end - def applicable?(promotable) - promotable.is_a?(Spree::Order) - end + def applicable?(promotable) + promotable.is_a?(Spree::Order) + end - def eligible?(order, _options = {}) - stores.none? || stores.include?(order.store) - end + def eligible?(order, _options = {}) + stores.none? || stores.include?(order.store) end end + end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb index 6ab8658ad58..6b67fea88cf 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb @@ -1,73 +1,73 @@ # frozen_string_literal: true module SolidusFriendlyPromotions - module Rules - class Taxon < ::Spree::PromotionRule - has_many :promotion_rule_taxons, class_name: 'Spree::PromotionRuleTaxon', foreign_key: :promotion_rule_id, - dependent: :destroy - has_many :taxons, through: :promotion_rule_taxons, class_name: 'Spree::Taxon' + module Rules + class Taxon < ::Spree::PromotionRule + has_many :promotion_rule_taxons, class_name: "Spree::PromotionRuleTaxon", foreign_key: :promotion_rule_id, + dependent: :destroy + has_many :taxons, through: :promotion_rule_taxons, class_name: "Spree::Taxon" - def preload_relations - [:taxons] - end + def preload_relations + [:taxons] + end - MATCH_POLICIES = %w(any all none) + MATCH_POLICIES = %w[any all none] - validates_inclusion_of :preferred_match_policy, in: MATCH_POLICIES + validates_inclusion_of :preferred_match_policy, in: MATCH_POLICIES - preference :match_policy, :string, default: MATCH_POLICIES.first - def applicable?(promotable) - promotable.is_a?(Spree::Order) - end - - def eligible?(order, _options = {}) - order_taxons = taxons_in_order(order) + preference :match_policy, :string, default: MATCH_POLICIES.first + def applicable?(promotable) + promotable.is_a?(Spree::Order) + end - case preferred_match_policy - when 'all' - matches_all = taxons.all? do |rule_taxon| - order_taxons.where(id: rule_taxon.self_and_descendants.ids).exists? - end + def eligible?(order, _options = {}) + order_taxons = taxons_in_order(order) - unless matches_all - eligibility_errors.add(:base, eligibility_error_message(:missing_taxon), error_code: :missing_taxon) - end - when 'any' - unless order_taxons.where(id: rule_taxon_ids_with_children).exists? - eligibility_errors.add(:base, eligibility_error_message(:no_matching_taxons), error_code: :no_matching_taxons) - end - when 'none' - if order_taxons.where(id: rule_taxon_ids_with_children).exists? - eligibility_errors.add(:base, eligibility_error_message(:has_excluded_taxon), error_code: :has_excluded_taxon) - end - else - raise "unexpected match policy: #{preferred_match_policy.inspect}" + case preferred_match_policy + when "all" + matches_all = taxons.all? do |rule_taxon| + order_taxons.where(id: rule_taxon.self_and_descendants.ids).exists? end - eligibility_errors.empty? + unless matches_all + eligibility_errors.add(:base, eligibility_error_message(:missing_taxon), error_code: :missing_taxon) + end + when "any" + unless order_taxons.where(id: rule_taxon_ids_with_children).exists? + eligibility_errors.add(:base, eligibility_error_message(:no_matching_taxons), error_code: :no_matching_taxons) + end + when "none" + if order_taxons.where(id: rule_taxon_ids_with_children).exists? + eligibility_errors.add(:base, eligibility_error_message(:has_excluded_taxon), error_code: :has_excluded_taxon) + end + else + raise "unexpected match policy: #{preferred_match_policy.inspect}" end - def taxon_ids_string - taxons.pluck(:id).join(',') - end + eligibility_errors.empty? + end - def taxon_ids_string=(taxon_ids) - taxon_ids = taxon_ids.to_s.split(',').map(&:strip) - self.taxons = Spree::Taxon.find(taxon_ids) - end + def taxon_ids_string + taxons.pluck(:id).join(",") + end + + def taxon_ids_string=(taxon_ids) + taxon_ids = taxon_ids.to_s.split(",").map(&:strip) + self.taxons = Spree::Taxon.find(taxon_ids) + end - private + private - # All taxons in an order - def taxons_in_order(order) - Spree::Taxon.joins(products: { variants_including_master: :line_items }) - .where(spree_line_items: { order_id: order.id }).distinct - end + # All taxons in an order + def taxons_in_order(order) + Spree::Taxon.joins(products: {variants_including_master: :line_items}) + .where(spree_line_items: {order_id: order.id}).distinct + end - # ids of taxons rules and taxons rules children - def rule_taxon_ids_with_children - taxons.flat_map { |taxon| taxon.self_and_descendants.ids }.uniq - end + # ids of taxons rules and taxons rules children + def rule_taxon_ids_with_children + taxons.flat_map { |taxon| taxon.self_and_descendants.ids }.uniq end end + end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb index 1a0f08ad861..7eee578248b 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb @@ -1,32 +1,32 @@ # frozen_string_literal: true module SolidusFriendlyPromotions - module Rules - class User < ::Spree::PromotionRule - has_many :promotion_rule_users, class_name: 'Spree::PromotionRuleUser', - foreign_key: :promotion_rule_id, - dependent: :destroy - has_many :users, through: :promotion_rule_users, class_name: Spree::UserClassHandle.new + module Rules + class User < ::Spree::PromotionRule + has_many :promotion_rule_users, class_name: "Spree::PromotionRuleUser", + foreign_key: :promotion_rule_id, + dependent: :destroy + has_many :users, through: :promotion_rule_users, class_name: Spree::UserClassHandle.new - def preload_relations - [:users] - end + def preload_relations + [:users] + end - def applicable?(promotable) - promotable.is_a?(Spree::Order) - end + def applicable?(promotable) + promotable.is_a?(Spree::Order) + end - def eligible?(order, _options = {}) - users.include?(order.user) - end + def eligible?(order, _options = {}) + users.include?(order.user) + end - def user_ids_string - user_ids.join(',') - end + def user_ids_string + user_ids.join(",") + end - def user_ids_string=(user_ids) - self.user_ids = user_ids.to_s.split(',').map(&:strip) - end + def user_ids_string=(user_ids) + self.user_ids = user_ids.to_s.split(",").map(&:strip) end end + end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb index 1f7f02afd3b..bec0ae0b101 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb @@ -1,18 +1,18 @@ # frozen_string_literal: true module SolidusFriendlyPromotions - module Rules - class UserLoggedIn < ::Spree::PromotionRule - def applicable?(promotable) - promotable.is_a?(Spree::Order) - end + module Rules + class UserLoggedIn < ::Spree::PromotionRule + def applicable?(promotable) + promotable.is_a?(Spree::Order) + end - def eligible?(order, _options = {}) - unless order.user.present? - eligibility_errors.add(:base, eligibility_error_message(:no_user_specified), error_code: :no_user_specified) - end - eligibility_errors.empty? + def eligible?(order, _options = {}) + unless order.user.present? + eligibility_errors.add(:base, eligibility_error_message(:no_user_specified), error_code: :no_user_specified) end + eligibility_errors.empty? end end + end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_role.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_role.rb index 94fe8a65adf..42c1bb07f57 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_role.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_role.rb @@ -1,43 +1,43 @@ # frozen_string_literal: true module SolidusFriendlyPromotions - module Rules - class UserRole < ::Spree::PromotionRule - preference :role_ids, :array, default: [] + module Rules + class UserRole < ::Spree::PromotionRule + preference :role_ids, :array, default: [] - MATCH_POLICIES = %w(any all) - preference :match_policy, default: MATCH_POLICIES.first + MATCH_POLICIES = %w[any all] + preference :match_policy, default: MATCH_POLICIES.first - def applicable?(promotable) - promotable.is_a?(Spree::Order) - end + def applicable?(promotable) + promotable.is_a?(Spree::Order) + end - def eligible?(order, _options = {}) - return false unless order.user - if all_match_policy? - match_all_roles?(order) - else - match_any_roles?(order) - end + def eligible?(order, _options = {}) + return false unless order.user + if all_match_policy? + match_all_roles?(order) + else + match_any_roles?(order) end + end - private + private - def all_match_policy? - preferred_match_policy == 'all' && preferred_role_ids.present? - end + def all_match_policy? + preferred_match_policy == "all" && preferred_role_ids.present? + end - def user_roles(order) - order.user.spree_roles.where(id: preferred_role_ids) - end + def user_roles(order) + order.user.spree_roles.where(id: preferred_role_ids) + end - def match_all_roles?(order) - user_roles(order).count == preferred_role_ids.count - end + def match_all_roles?(order) + user_roles(order).count == preferred_role_ids.count + end - def match_any_roles?(order) - user_roles(order).exists? - end + def match_any_roles?(order) + user_roles(order).exists? end end + end end diff --git a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/install_generator.rb b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/install_generator.rb index 31ac0a7c3d7..cf402d0bd68 100644 --- a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/install_generator.rb +++ b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/install_generator.rb @@ -4,36 +4,36 @@ module SolidusFriendlyPromotions module Generators class InstallGenerator < Rails::Generators::Base class_option :auto_run_migrations, type: :boolean, default: false - source_root File.expand_path('templates', __dir__) + source_root File.expand_path("templates", __dir__) def self.exit_on_failure? true end def copy_initializer - template 'initializer.rb', 'config/initializers/solidus_friendly_promotions.rb' + template "initializer.rb", "config/initializers/solidus_friendly_promotions.rb" end def add_javascripts - append_file 'vendor/assets/javascripts/spree/frontend/all.js', "//= require spree/frontend/solidus_friendly_promotions\n" - append_file 'vendor/assets/javascripts/spree/backend/all.js', "//= require spree/backend/solidus_friendly_promotions\n" + append_file "vendor/assets/javascripts/spree/frontend/all.js", "//= require spree/frontend/solidus_friendly_promotions\n" + append_file "vendor/assets/javascripts/spree/backend/all.js", "//= require spree/backend/solidus_friendly_promotions\n" end def add_stylesheets - inject_into_file 'vendor/assets/stylesheets/spree/frontend/all.css', " *= require spree/frontend/solidus_friendly_promotions\n", before: %r{\*/}, verbose: true # rubocop:disable Layout/LineLength - inject_into_file 'vendor/assets/stylesheets/spree/backend/all.css', " *= require spree/backend/solidus_friendly_promotions\n", before: %r{\*/}, verbose: true # rubocop:disable Layout/LineLength + inject_into_file "vendor/assets/stylesheets/spree/frontend/all.css", " *= require spree/frontend/solidus_friendly_promotions\n", before: %r{\*/}, verbose: true # rubocop:disable Layout/LineLength + inject_into_file "vendor/assets/stylesheets/spree/backend/all.css", " *= require spree/backend/solidus_friendly_promotions\n", before: %r{\*/}, verbose: true # rubocop:disable Layout/LineLength end def add_migrations - run 'bin/rails railties:install:migrations FROM=solidus_friendly_promotions' + run "bin/rails railties:install:migrations FROM=solidus_friendly_promotions" end def run_migrations - run_migrations = options[:auto_run_migrations] || ['', 'y', 'Y'].include?(ask('Would you like to run the migrations now? [Y/n]')) # rubocop:disable Layout/LineLength + run_migrations = options[:auto_run_migrations] || ["", "y", "Y"].include?(ask("Would you like to run the migrations now? [Y/n]")) # rubocop:disable Layout/LineLength if run_migrations - run 'bin/rails db:migrate' + run "bin/rails db:migrate" else - puts 'Skipping bin/rails db:migrate, don\'t forget to run it!' # rubocop:disable Rails/Output + puts "Skipping bin/rails db:migrate, don't forget to run it!" # rubocop:disable Rails/Output end end end diff --git a/friendly_promotions/lib/solidus_friendly_promotions.rb b/friendly_promotions/lib/solidus_friendly_promotions.rb index 5ae3dc6fcc7..bf6950e2e15 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require 'solidus_friendly_promotions/configuration' -require 'solidus_friendly_promotions/version' -require 'solidus_friendly_promotions/engine' +require "solidus_friendly_promotions/configuration" +require "solidus_friendly_promotions/version" +require "solidus_friendly_promotions/engine" diff --git a/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb b/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb index d00757a289a..bb9d78a8c19 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb @@ -12,7 +12,7 @@ def configuration @configuration ||= Configuration.new end - alias config configuration + alias_method :config, :configuration def configure yield configuration diff --git a/friendly_promotions/lib/solidus_friendly_promotions/engine.rb b/friendly_promotions/lib/solidus_friendly_promotions/engine.rb index 1cf24dc3e2c..47a740fd133 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions/engine.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions/engine.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require 'solidus_core' -require 'solidus_support' +require "solidus_core" +require "solidus_support" module SolidusFriendlyPromotions class Engine < Rails::Engine @@ -9,7 +9,7 @@ class Engine < Rails::Engine isolate_namespace ::Spree - engine_name 'solidus_friendly_promotions' + engine_name "solidus_friendly_promotions" # use rspec for tests config.generators do |g| diff --git a/friendly_promotions/lib/solidus_friendly_promotions/version.rb b/friendly_promotions/lib/solidus_friendly_promotions/version.rb index eb53a9e9496..ab1978d5a44 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions/version.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module SolidusFriendlyPromotions - VERSION = '0.0.1' + VERSION = "0.0.1" end diff --git a/friendly_promotions/solidus_friendly_promotions.gemspec b/friendly_promotions/solidus_friendly_promotions.gemspec index 05cb6b99c9a..5180cdf27a8 100644 --- a/friendly_promotions/solidus_friendly_promotions.gemspec +++ b/friendly_promotions/solidus_friendly_promotions.gemspec @@ -1,37 +1,36 @@ # frozen_string_literal: true -require_relative 'lib/solidus_friendly_promotions/version' +require_relative "lib/solidus_friendly_promotions/version" Gem::Specification.new do |spec| - spec.name = 'solidus_friendly_promotions' + spec.name = "solidus_friendly_promotions" spec.version = SolidusFriendlyPromotions::VERSION - spec.authors = ['Martin Meyerhoff'] - spec.email = 'mamhoff@gmail.com' + spec.authors = ["Martin Meyerhoff"] + spec.email = "mamhoff@gmail.com" - spec.summary = 'A replacement for Solidus\' promotion system' - spec.description = 'Experimental replacement for the promotion system in Solidus' - spec.homepage = 'https://github.com/solidusio-contrib/solidus_friendly_promotions#readme' - spec.license = 'BSD-3-Clause' + spec.summary = "A replacement for Solidus' promotion system" + spec.description = "Experimental replacement for the promotion system in Solidus" + spec.homepage = "https://github.com/solidusio-contrib/solidus_friendly_promotions#readme" + spec.license = "BSD-3-Clause" - spec.metadata['homepage_uri'] = spec.homepage - spec.metadata['source_code_uri'] = 'https://github.com/solidusio-contrib/solidus_friendly_promotions' - spec.metadata['changelog_uri'] = 'https://github.com/solidusio-contrib/solidus_friendly_promotions/blob/master/CHANGELOG.md' + spec.metadata["homepage_uri"] = spec.homepage + spec.metadata["source_code_uri"] = "https://github.com/solidusio-contrib/solidus_friendly_promotions" + spec.metadata["changelog_uri"] = "https://github.com/solidusio-contrib/solidus_friendly_promotions/blob/master/CHANGELOG.md" - spec.required_ruby_version = Gem::Requirement.new('>= 2.5', '< 4') + spec.required_ruby_version = Gem::Requirement.new(">= 2.5", "< 4") # Specify which files should be added to the gem when it is released. # The `git ls-files -z` loads the files in the RubyGem that have been added into git. files = Dir.chdir(__dir__) { `git ls-files -z`.split("\x0") } spec.files = files.grep_v(%r{^(test|spec|features)/}) - spec.test_files = files.grep(%r{^(test|spec|features)/}) spec.bindir = "exe" spec.executables = files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] - spec.add_dependency 'solidus_core', ['>= 3.0.0', '< 5'] - spec.add_dependency 'solidus_support', '~> 0.5' + spec.add_dependency "solidus_core", [">= 3.0.0", "< 5"] + spec.add_dependency "solidus_support", "~> 0.5" - spec.add_development_dependency 'solidus_dev_support', '~> 2.6' - spec.add_development_dependency 'rspec-activemodel-mocks', '~> 1.0' + spec.add_development_dependency "solidus_dev_support", "~> 2.6" + spec.add_development_dependency "rspec-activemodel-mocks", "~> 1.0" end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_order_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_order_spec.rb index 0a958413e3c..74e95b91a54 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_order_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_order_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" RSpec.describe SolidusFriendlyPromotions::Rules::FirstOrder, type: :model do let(:rule) { SolidusFriendlyPromotions::Rules::FirstOrder.new } @@ -11,8 +11,8 @@ it { expect(rule).to be_eligible(order) } it "does not set an error message" do rule.eligible?(order) - expect(rule.eligibility_errors.full_messages.first). - to be_nil + expect(rule.eligibility_errors.full_messages.first) + .to be_nil end end @@ -48,21 +48,21 @@ it { expect(rule).not_to be_eligible(order) } it "sets an error message" do rule.eligible?(order) - expect(rule.eligibility_errors.full_messages.first). - to eq "This coupon code can only be applied to your first order." + expect(rule.eligibility_errors.full_messages.first) + .to eq "This coupon code can only be applied to your first order." end it "sets an error code" do rule.eligible?(order) - expect(rule.eligibility_errors.details[:base].first[:error_code]). - to eq :not_first_order + expect(rule.eligibility_errors.details[:base].first[:error_code]) + .to eq :not_first_order end end end end context "for a guest user" do - let(:email) { 'user@solidus.io' } - before { allow(order).to receive_messages email: 'user@solidus.io' } + let(:email) { "user@solidus.io" } + before { allow(order).to receive_messages email: "user@solidus.io" } context "with no other orders" do it { expect(rule).to be_eligible(order) } @@ -73,13 +73,13 @@ it { expect(rule).not_to be_eligible(order) } it "sets an error message" do rule.eligible?(order) - expect(rule.eligibility_errors.full_messages.first). - to eq "This coupon code can only be applied to your first order." + expect(rule.eligibility_errors.full_messages.first) + .to eq "This coupon code can only be applied to your first order." end it "sets an error code" do rule.eligible?(order) - expect(rule.eligibility_errors.details[:base].first[:error_code]). - to eq :not_first_order + expect(rule.eligibility_errors.details[:base].first[:error_code]) + .to eq :not_first_order end end end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_repeat_purchase_since_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_repeat_purchase_since_spec.rb index a827a300f75..33dd9487c7f 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_repeat_purchase_since_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_repeat_purchase_since_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" RSpec.describe SolidusFriendlyPromotions::Rules::FirstRepeatPurchaseSince do describe "#applicable?" do diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/item_total_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/item_total_spec.rb index f1c6444f98f..4c396723103 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/item_total_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/item_total_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" RSpec.describe SolidusFriendlyPromotions::Rules::ItemTotal, type: :model do let(:rule) do @@ -11,10 +11,10 @@ end let(:order) { double(:order, item_total: item_total, currency: order_currency) } let(:preferred_amount) { 50 } - let(:order_currency) { 'USD' } + let(:order_currency) { "USD" } context "preferred operator set to gt" do - let(:preferred_operator) { 'gt' } + let(:preferred_operator) { "gt" } context "item total is greater than preferred amount" do let(:item_total) { 51 } @@ -41,13 +41,13 @@ it "set an error message" do rule.eligible?(order) - expect(rule.eligibility_errors.full_messages.first). - to eq "This coupon code can't be applied to orders less than or equal to $50.00." + expect(rule.eligibility_errors.full_messages.first) + .to eq "This coupon code can't be applied to orders less than or equal to $50.00." end it "sets an error code" do rule.eligible?(order) - expect(rule.eligibility_errors.details[:base].first[:error_code]). - to eq :item_total_less_than_or_equal + expect(rule.eligibility_errors.details[:base].first[:error_code]) + .to eq :item_total_less_than_or_equal end end @@ -60,19 +60,19 @@ it "set an error message" do rule.eligible?(order) - expect(rule.eligibility_errors.full_messages.first). - to eq "This coupon code can't be applied to orders less than or equal to $50.00." + expect(rule.eligibility_errors.full_messages.first) + .to eq "This coupon code can't be applied to orders less than or equal to $50.00." end it "sets an error code" do rule.eligible?(order) - expect(rule.eligibility_errors.details[:base].first[:error_code]). - to eq :item_total_less_than_or_equal + expect(rule.eligibility_errors.details[:base].first[:error_code]) + .to eq :item_total_less_than_or_equal end end end context "preferred operator set to gte" do - let(:preferred_operator) { 'gte' } + let(:preferred_operator) { "gte" } context "total is greater than preferred amount" do let(:item_total) { 51 } @@ -115,13 +115,13 @@ it "set an error message" do rule.eligible?(order) - expect(rule.eligibility_errors.full_messages.first). - to eq "This coupon code can't be applied to orders less than $50.00." + expect(rule.eligibility_errors.full_messages.first) + .to eq "This coupon code can't be applied to orders less than $50.00." end it "sets an error code" do rule.eligible?(order) - expect(rule.eligibility_errors.details[:base].first[:error_code]). - to eq :item_total_less_than + expect(rule.eligibility_errors.details[:base].first[:error_code]) + .to eq :item_total_less_than end end end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/line_item_taxon_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/line_item_taxon_spec.rb index de67486218c..c65fe5e792a 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/line_item_taxon_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/line_item_taxon_spec.rb @@ -1,50 +1,50 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" RSpec.describe SolidusFriendlyPromotions::Rules::LineItemTaxon, type: :model do - let(:taxon) { create :taxon, name: 'first' } - let(:taxon2) { create :taxon, name: 'second' } - let(:order) { create :order_with_line_items } + let(:taxon) { create :taxon, name: "first" } + let(:taxon2) { create :taxon, name: "second" } + let(:order) { create :order_with_line_items } let(:product) { order.products.first } let(:rule) do described_class.create!(promotion: create(:promotion)) end - describe '#eligible?' do + describe "#eligible?" do let(:line_item) { order.line_items.first! } let(:order) { create :order_with_line_items } - let(:taxon) { create :taxon, name: 'first' } + let(:taxon) { create :taxon, name: "first" } - context 'with an invalid match policy' do + context "with an invalid match policy" do before do - rule.preferred_match_policy = 'invalid' + rule.preferred_match_policy = "invalid" rule.save!(validate: false) line_item.product.taxons << taxon rule.taxons << taxon end - it 'raises' do + it "raises" do expect { rule.eligible?(line_item) }.to raise_error('unexpected match policy: "invalid"') end end - context 'when a product has a taxon of a taxon rule' do + context "when a product has a taxon of a taxon rule" do before do product.taxons << taxon rule.taxons << taxon rule.save! end - it 'is eligible' do + it "is eligible" do expect(rule).to be_eligible(line_item) end end - context 'when a product has a taxon child of a taxon rule' do + context "when a product has a taxon child of a taxon rule" do before do taxon.children << taxon2 product.taxons << taxon2 @@ -52,7 +52,7 @@ rule.save! end - it 'is eligible' do + it "is eligible" do expect(rule).to be_eligible(line_item) end @@ -67,14 +67,14 @@ end end - context 'when a product does not have taxon or child taxon of a taxon rule' do + context "when a product does not have taxon or child taxon of a taxon rule" do before do product.taxons << taxon2 rule.taxons << taxon rule.save! end - it 'is not eligible' do + it "is not eligible" do expect(rule).not_to be_eligible(line_item) end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/nth_order_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/nth_order_spec.rb index 483a685038d..e478a3630cc 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/nth_order_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/nth_order_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" RSpec.describe SolidusFriendlyPromotions::Rules::NthOrder do describe "#applicable?" do diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/one_use_per_user_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/one_use_per_user_spec.rb index 35ef09ce1a0..d95968fff81 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/one_use_per_user_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/one_use_per_user_spec.rb @@ -1,11 +1,11 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" RSpec.describe SolidusFriendlyPromotions::Rules::OneUsePerUser, type: :model do let(:rule) { described_class.new } - describe '#eligible?(order)' do + describe "#eligible?(order)" do subject { rule.eligible?(order) } let(:order) { double Spree::Order, user: user } let(:user) { double Spree::LegacyUser } @@ -14,40 +14,40 @@ before { rule.promotion = promotion } - context 'when the order is assigned to a user' do - context 'when the user has used this promotion before' do + context "when the order is assigned to a user" do + context "when the user has used this promotion before" do let(:used_by) { true } it { is_expected.to be false } it "sets an error message" do subject - expect(rule.eligibility_errors.full_messages.first). - to eq "This coupon code can only be used once per user." + expect(rule.eligibility_errors.full_messages.first) + .to eq "This coupon code can only be used once per user." end it "sets an error code" do rule.eligible?(order) - expect(rule.eligibility_errors.details[:base].first[:error_code]). - to eq :limit_once_per_user + expect(rule.eligibility_errors.details[:base].first[:error_code]) + .to eq :limit_once_per_user end end - context 'when the user has not used this promotion before' do + context "when the user has not used this promotion before" do it { is_expected.to be true } end end - context 'when the order is not assigned to a user' do + context "when the order is not assigned to a user" do let(:user) { nil } it { is_expected.to be false } it "sets an error message" do subject - expect(rule.eligibility_errors.full_messages.first). - to eq "You need to login before applying this coupon code." + expect(rule.eligibility_errors.full_messages.first) + .to eq "You need to login before applying this coupon code." end it "sets an error code" do rule.eligible?(order) - expect(rule.eligibility_errors.details[:base].first[:error_code]). - to eq :no_user_specified + expect(rule.eligibility_errors.details[:base].first[:error_code]) + .to eq :no_user_specified end end end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/option_value_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/option_value_spec.rb index fdf4e3441fb..913944f2bee 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/option_value_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/option_value_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" RSpec.describe SolidusFriendlyPromotions::Rules::OptionValue do let(:rule) { SolidusFriendlyPromotions::Rules::OptionValue.new } @@ -8,8 +8,8 @@ describe "#preferred_eligible_values" do subject { rule.preferred_eligible_values } it "assigns a nicely formatted hash" do - rule.preferred_eligible_values = Hash["5" => "1,2", "6" => "1"] - expect(subject).to eq Hash[5 => [1, 2], 6 => [1]] + rule.preferred_eligible_values= {"5" => "1,2", "6" => "1"} + expect(subject).to eq({5 => [1, 2], 6 => [1]}) end end @@ -32,15 +32,15 @@ subject { rule.eligible?(promotable) } context "when there are any applicable line items" do before do - rule.preferred_eligible_values = Hash[line_item.product.id => [ + rule.preferred_eligible_values= {line_item.product.id => [ line_item.variant.option_values.pluck(:id).first - ]] + ]} end it { is_expected.to be true } end context "when there are no applicable line items" do before do - rule.preferred_eligible_values = Hash[99 => [99]] + rule.preferred_eligible_values= {99 => [99]} end it { is_expected.to be false } end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/product_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/product_spec.rb index 2a2e30b53c6..192f20531df 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/product_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/product_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" RSpec.describe SolidusFriendlyPromotions::Rules::Product, type: :model do let(:rule) { SolidusFriendlyPromotions::Rules::Product.new(rule_options) } @@ -35,13 +35,13 @@ it { expect(rule).not_to be_eligible(order) } it "sets an error message" do rule.eligible?(order) - expect(rule.eligibility_errors.full_messages.first). - to eq "You need to add an applicable product before applying this coupon code." + expect(rule.eligibility_errors.full_messages.first) + .to eq "You need to add an applicable product before applying this coupon code." end it "sets an error code" do rule.eligible?(order) - expect(rule.eligibility_errors.details[:base].first[:error_code]). - to eq :no_applicable_products + expect(rule.eligibility_errors.details[:base].first[:error_code]) + .to eq :no_applicable_products end end end @@ -63,13 +63,13 @@ it { expect(rule).not_to be_eligible(order) } it "sets an error message" do rule.eligible?(order) - expect(rule.eligibility_errors.full_messages.first). - to eq "This coupon code can't be applied because you don't have all of the necessary products in your cart." + expect(rule.eligibility_errors.full_messages.first) + .to eq "This coupon code can't be applied because you don't have all of the necessary products in your cart." end it "sets an error code" do rule.eligible?(order) - expect(rule.eligibility_errors.details[:base].first[:error_code]). - to eq :missing_product + expect(rule.eligibility_errors.details[:base].first[:error_code]) + .to eq :missing_product end end end @@ -91,13 +91,13 @@ it { expect(rule).not_to be_eligible(order) } it "sets an error message" do rule.eligible?(order) - expect(rule.eligibility_errors.full_messages.first). - to eq "Your cart contains a product that prevents this coupon code from being applied." + expect(rule.eligibility_errors.full_messages.first) + .to eq "Your cart contains a product that prevents this coupon code from being applied." end it "sets an error code" do rule.eligible?(order) - expect(rule.eligibility_errors.details[:base].first[:error_code]). - to eq :has_excluded_product + expect(rule.eligibility_errors.details[:base].first[:error_code]) + .to eq :has_excluded_product end end end @@ -107,17 +107,17 @@ SolidusFriendlyPromotions::Rules::Product.create!( promotion: create(:promotion), product_promotion_rules: [ - Spree::ProductPromotionRule.new(product: product), - ], + Spree::ProductPromotionRule.new(product: product) + ] ).tap do |rule| - rule.preferred_match_policy = 'invalid' + rule.preferred_match_policy = "invalid" rule.save!(validate: false) end end let(:product) { order.line_items.first!.product } let(:order) { create(:order_with_line_items, line_items_count: 1) } - it 'raises' do + it "raises" do expect { rule.eligible?(order) }.to raise_error('unexpected match policy: "invalid"') diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/taxon_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/taxon_spec.rb index a75e8728b64..0e24cec66cd 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/taxon_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/taxon_spec.rb @@ -1,24 +1,24 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" RSpec.describe SolidusFriendlyPromotions::Rules::Taxon, type: :model do - let(:taxon) { create :taxon, name: 'first' } - let(:taxon2) { create :taxon, name: 'second' } - let(:order) { create :order_with_line_items } + let(:taxon) { create :taxon, name: "first" } + let(:taxon2) { create :taxon, name: "second" } + let(:order) { create :order_with_line_items } let(:product) { order.products.first } let(:rule) do SolidusFriendlyPromotions::Rules::Taxon.create!(promotion: create(:promotion)) end - context '#eligible?(order)' do - context 'with any match policy' do + context "#eligible?(order)" do + context "with any match policy" do before do - rule.update!(preferred_match_policy: 'any') + rule.update!(preferred_match_policy: "any") end - it 'is eligible if order does have any prefered taxon' do + it "is eligible if order does have any prefered taxon" do product.taxons << taxon rule.taxons << taxon expect(rule).to be_eligible(order) @@ -29,33 +29,33 @@ it { expect(rule).not_to be_eligible(order) } it "sets an error message" do rule.eligible?(order) - expect(rule.eligibility_errors.full_messages.first). - to eq "You need to add a product from an applicable category before applying this coupon code." + expect(rule.eligibility_errors.full_messages.first) + .to eq "You need to add a product from an applicable category before applying this coupon code." end it "sets an error code" do rule.eligible?(order) - expect(rule.eligibility_errors.details[:base].first[:error_code]). - to eq :no_matching_taxons + expect(rule.eligibility_errors.details[:base].first[:error_code]) + .to eq :no_matching_taxons end end - context 'when a product has a taxon child of a taxon rule' do + context "when a product has a taxon child of a taxon rule" do before do taxon.children << taxon2 product.taxons << taxon2 rule.taxons << taxon end - it{ expect(rule).to be_eligible(order) } + it { expect(rule).to be_eligible(order) } end end - context 'with all match policy' do + context "with all match policy" do before do - rule.update!(preferred_match_policy: 'all') + rule.update!(preferred_match_policy: "all") end - it 'is eligible order has all prefered taxons' do + it "is eligible order has all prefered taxons" do product.taxons << taxon2 order.products.last.taxons << taxon @@ -69,17 +69,17 @@ it { expect(rule).not_to be_eligible(order) } it "sets an error message" do rule.eligible?(order) - expect(rule.eligibility_errors.full_messages.first). - to eq "You need to add a product from all applicable categories before applying this coupon code." + expect(rule.eligibility_errors.full_messages.first) + .to eq "You need to add a product from all applicable categories before applying this coupon code." end it "sets an error code" do rule.eligible?(order) - expect(rule.eligibility_errors.details[:base].first[:error_code]). - to eq :missing_taxon + expect(rule.eligibility_errors.details[:base].first[:error_code]) + .to eq :missing_taxon end end - context 'when a product has a taxon child of a taxon rule' do + context "when a product has a taxon child of a taxon rule" do let(:taxon3) { create :taxon } before do @@ -95,9 +95,9 @@ end end - context 'with none match policy' do + context "with none match policy" do before do - rule.preferred_match_policy = 'none' + rule.preferred_match_policy = "none" end context "none of the order's products are in listed taxon" do @@ -115,26 +115,26 @@ end it "sets an error message" do rule.eligible?(order) - expect(rule.eligibility_errors.full_messages.first). - to eq "Your cart contains a product from an excluded category that prevents this coupon code from being applied." + expect(rule.eligibility_errors.full_messages.first) + .to eq "Your cart contains a product from an excluded category that prevents this coupon code from being applied." end it "sets an error code" do rule.eligible?(order) - expect(rule.eligibility_errors.details[:base].first[:error_code]). - to eq :has_excluded_taxon + expect(rule.eligibility_errors.details[:base].first[:error_code]) + .to eq :has_excluded_taxon end end end - context 'with an invalid match policy' do + context "with an invalid match policy" do before do order.products.first.taxons << taxon rule.taxons << taxon - rule.preferred_match_policy = 'invalid' + rule.preferred_match_policy = "invalid" rule.save!(validate: false) end - it 'raises' do + it "raises" do expect { rule.eligible?(order) }.to raise_error('unexpected match policy: "invalid"') diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_logged_in_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_logged_in_spec.rb index 7c6191ef504..4147c5d4bd0 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_logged_in_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_logged_in_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" RSpec.describe SolidusFriendlyPromotions::Rules::UserLoggedIn, type: :model do let(:rule) { SolidusFriendlyPromotions::Rules::UserLoggedIn.new } @@ -9,7 +9,7 @@ let(:order) { Spree::Order.new } it "should be eligible if order has an associated user" do - user = double('User') + user = double("User") allow(order).to receive_messages(user: user) expect(rule).to be_eligible(order) @@ -20,13 +20,13 @@ it { expect(rule).not_to be_eligible(order) } it "sets an error message" do rule.eligible?(order) - expect(rule.eligibility_errors.full_messages.first). - to eq "You need to login before applying this coupon code." + expect(rule.eligibility_errors.full_messages.first) + .to eq "You need to login before applying this coupon code." end it "sets an error code" do rule.eligible?(order) - expect(rule.eligibility_errors.details[:base].first[:error_code]). - to eq :no_user_specified + expect(rule.eligibility_errors.details[:base].first[:error_code]) + .to eq :no_user_specified end end end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_role_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_role_spec.rb index beae7821091..49c6f0060bc 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_role_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_role_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" RSpec.describe SolidusFriendlyPromotions::Rules::UserRole, type: :model do let(:rule) { described_class.new(preferred_role_ids: roles_for_rule.map(&:id)) } @@ -10,75 +10,75 @@ subject { rule } - shared_examples 'eligibility' do - context 'no roles on rule' do + shared_examples "eligibility" do + context "no roles on rule" do let(:roles_for_user) { [create(:role)] } - it 'should not be eligible' do + it "should not be eligible" do expect(rule).to_not be_eligible(order) end end - context 'no roles on user' do + context "no roles on user" do let(:roles_for_rule) { [create(:role)] } - it 'should not be eligible' do + it "should not be eligible" do expect(rule).to_not be_eligible(order) end end - context 'mismatched roles' do + context "mismatched roles" do let(:roles_for_user) { [create(:role)] } let(:roles_for_rule) { [create(:role)] } - it 'should not be eligible' do + it "should not be eligible" do expect(rule).to_not be_eligible(order) end end - context 'matching all roles' do + context "matching all roles" do let(:roles_for_user) { [create(:role), create(:role)] } let(:roles_for_rule) { roles_for_user } - it 'should be eligible' do + it "should be eligible" do expect(rule).to be_eligible(order) end end end - context '#eligible?(order)' do - context 'order with no user' do + context "#eligible?(order)" do + context "order with no user" do let(:order) { Spree::Order.new } - it 'should not be eligible' do + it "should not be eligible" do expect(rule).to_not be_eligible(order) end end - context 'order with user' do + context "order with user" do let(:order) { Spree::Order.new(user: user) } - context 'with any match policy' do - before { rule.preferred_match_policy = 'any' } + context "with any match policy" do + before { rule.preferred_match_policy = "any" } - include_examples 'eligibility' + include_examples "eligibility" - context 'one shared role' do + context "one shared role" do let(:shared_role) { create(:role) } let(:roles_for_user) { [create(:role), shared_role] } let(:roles_for_rule) { [create(:role), shared_role] } - it 'should be eligible' do + it "should be eligible" do expect(rule).to be_eligible(order) end end end - context 'with all match policy' do - before { rule.preferred_match_policy = 'all' } + context "with all match policy" do + before { rule.preferred_match_policy = "all" } - include_examples 'eligibility' + include_examples "eligibility" - context 'one shared role' do + context "one shared role" do let(:shared_role) { create(:role) } let(:roles_for_user) { [create(:role), shared_role] } let(:roles_for_rule) { [create(:role), shared_role] } - it 'should not be eligible' do + it "should not be eligible" do expect(rule).to_not be_eligible(order) end end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_spec.rb index c1df8349180..56bf4b4a97f 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" RSpec.describe SolidusFriendlyPromotions::Rules::User, type: :model do let(:rule) { SolidusFriendlyPromotions::Rules::User.new } diff --git a/friendly_promotions/spec/spec_helper.rb b/friendly_promotions/spec/spec_helper.rb index 1cb2698e589..0c2fae49a39 100644 --- a/friendly_promotions/spec/spec_helper.rb +++ b/friendly_promotions/spec/spec_helper.rb @@ -1,21 +1,21 @@ # frozen_string_literal: true # Configure Rails Environment -ENV['RAILS_ENV'] = 'test' +ENV["RAILS_ENV"] = "test" # Run Coverage report -require 'solidus_dev_support/rspec/coverage' +require "solidus_dev_support/rspec/coverage" # Create the dummy app if it's still missing. dummy_env = "#{__dir__}/dummy/config/environment.rb" -system 'bin/rake extension:test_app' unless File.exist? dummy_env +system "bin/rake extension:test_app" unless File.exist? dummy_env require dummy_env # Requires factories and other useful helpers defined in spree_core. -require 'solidus_dev_support/rspec/feature_helper' +require "solidus_dev_support/rspec/feature_helper" # Explicitly load activemodel mocks -require 'rspec-activemodel-mocks' +require "rspec-activemodel-mocks" # Requires supporting ruby files with custom matchers and macros, etc, # in spec/support/ and its subdirectories. @@ -29,7 +29,7 @@ config.infer_spec_type_from_file_location! config.use_transactional_fixtures = false - if Spree.solidus_gem_version < Gem::Version.new('2.11') + if Spree.solidus_gem_version < Gem::Version.new("2.11") config.extend Spree::TestingSupport::AuthorizationHelpers::Request, type: :system end end From dbc3c2ef60e1e88e6e2da59c4aa78611fddcc6a0 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 31 May 2023 12:59:23 +0200 Subject: [PATCH 007/524] Remove deprecated code --- .../solidus_friendly_promotions/rules/item_total.rb | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb index b80c3d45357..c865830c642 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb @@ -8,8 +8,6 @@ module Rules # To add extra operators please override `self.operators_map` or any other helper method. # To customize the error message you can also override `ineligible_message`. class ItemTotal < ::Spree::PromotionRule - include ActiveSupport::Deprecation::DeprecatedConstantAccessor - preference :amount, :decimal, default: 100.00 preference :currency, :string, default: -> { Spree::Config[:currency] } preference :operator, :string, default: "gt" @@ -28,15 +26,6 @@ def self.operator_options end end - # @deprecated - OPERATORS = operators_map.keys.map(&:to_s) - deprecate_constant( - :OPERATORS, - :operators_map, - message: "OPERATORS is deprecated! Use `operators_map.keys.map(&:to_s)` instead.", - deprecator: Spree::Deprecation - ) - def applicable?(promotable) promotable.is_a?(Spree::Order) end From 0a1081a3078498c0284049daedd93b57885a66ff Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 6 Jun 2023 11:16:57 +0200 Subject: [PATCH 008/524] Minimal working version This removes promotion stuff from `Spree::OrderContents` and adds services classes that handle adjustments in-memory. I'm still debating with myself whether to have separate tables for discounts, as that will make a lot of that much easier to work with. --- .../order_contents_decorator.rb | 16 +++++ .../actions/adjust_line_item.rb | 11 ++++ .../actions/adjust_shipment.rb | 11 ++++ .../actions/base.rb | 43 +++++++++++++ .../line_item_adjuster.rb | 31 ++++++++++ .../order_promotion_adjuster.rb | 61 +++++++++++++++++++ .../promotion_adjustment_chooser.rb | 19 ++++++ .../promotion_eligibility.rb | 24 ++++++++ .../shipment_adjuster.rb | 31 ++++++++++ .../spec/models/promotion/integration_spec.rb | 42 +++---------- .../actions/base_spec.rb | 34 +++++++++++ .../order_promotion_adjuster_spec.rb | 0 12 files changed, 288 insertions(+), 35 deletions(-) create mode 100644 friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_contents_decorator.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/actions/base.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/line_item_adjuster.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/order_promotion_adjuster.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/promotion_adjustment_chooser.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/promotion_eligibility.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/shipment_adjuster.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/actions/base_spec.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/order_promotion_adjuster_spec.rb diff --git a/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_contents_decorator.rb b/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_contents_decorator.rb new file mode 100644 index 00000000000..029fa3447f4 --- /dev/null +++ b/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_contents_decorator.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module OrderContentsDecorator + private + + def after_add_or_remove(line_item, options = {}) + shipment = options[:shipment] + shipment.present? ? shipment.update_amounts : order.ensure_updated_shipments + reload_totals + line_item + end + + Spree::OrderContents.prepend self + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb new file mode 100644 index 00000000000..73eaa92b863 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module Actions + class AdjustLineItem < Base + def can_adjust?(object) + object.is_a? Spree::LineItem + end + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb new file mode 100644 index 00000000000..bcc0a03bdec --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module Actions + class AdjustShipment < Base + def can_adjust?(object) + object.is_a? Spree::Shipment + end + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/actions/base.rb b/friendly_promotions/app/models/solidus_friendly_promotions/actions/base.rb new file mode 100644 index 00000000000..e26f14ab339 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/actions/base.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module Actions + class Base < ::Spree::PromotionAction + include Spree::CalculatedAdjustments + include Spree::AdjustmentSource + + has_many :adjustments, as: :source + + def preload_relations + [:calculator] + end + + def can_adjust?(object) + raise NotImplementedError + end + + def adjust(adjustable) + adjustment = adjustable.adjustments.detect do |adjustment| + adjustment.source == self + end || adjustable.adjustments.build(source: self) + adjustment.label = adjustment_label(adjustable) + adjustment.amount = compute_amount(adjustable) + adjustment + end + + # Ensure a negative amount which does not exceed the object's amount + def compute_amount(adjustable) + promotion_amount = calculator.compute(adjustable) || BigDecimal(0) + [adjustable.amount, promotion_amount.abs].min * -1 + end + + def adjustment_label(adjustable) + I18n.t( + "spree.adjustment_labels.#{adjustable.class.name.demodulize.underscore}", + promotion: Spree::Promotion.model_name.human, + promotion_name: promotion.name, + ) + end + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/line_item_adjuster.rb b/friendly_promotions/app/models/solidus_friendly_promotions/line_item_adjuster.rb new file mode 100644 index 00000000000..e60b92a2c71 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/line_item_adjuster.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + class LineItemAdjuster + attr_reader :promotions + + def initialize(promotions:) + @promotions = promotions + end + + def call(line_item) + return unless line_item.variant.product.promotionable? + non_promotion_adjustments = line_item.adjustments.reject(&:promotion?) + + eligible_promotions = PromotionEligibility.new(promotable: line_item, possible_promotions: promotions).call + + possible_adjustments = eligible_promotions.flat_map do |promotion| + promotion.actions.select do |action| + action.can_adjust?(line_item) + end.map do |action| + action.adjust(line_item) + end + end + + chosen_adjustments = Spree::Config.promotion_chooser_class.new(line_item).call(possible_adjustments) + + line_item.promo_total = chosen_adjustments.sum(&:amount) + line_item.adjustments = non_promotion_adjustments + chosen_adjustments + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/order_promotion_adjuster.rb b/friendly_promotions/app/models/solidus_friendly_promotions/order_promotion_adjuster.rb new file mode 100644 index 00000000000..c9277510810 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/order_promotion_adjuster.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + class OrderPromotionAdjuster + attr_reader :order, :promotions + + def initialize(order) + @order = order + @promotions = PromotionEligibility.new(promotable: order, possible_promotions: possible_promotions).call + end + + def call + adjust_line_items + adjust_shipments + order.promo_total = (order.line_items + order.shipments).sum { |item| item.promo_total } + order + end + + private + + def adjust_line_items + line_item_adjuster = LineItemAdjuster.new(promotions: promotions) + order.line_items.each { |line_item| line_item_adjuster.call(line_item) } + end + + def adjust_shipments + shipment_adjuster = ShipmentAdjuster.new(promotions: promotions) + order.shipments.each { |shipment| shipment_adjuster.call(shipment) } + end + + def possible_promotions + promos = connected_order_promotions | sale_promotions + promos.flat_map(&:promotion_actions).group_by(&:preload_relations).each do |preload_relations, actions| + preload(records: actions, associations: preload_relations) + end + promos.flat_map(&:promotion_rules).group_by(&:preload_relations).each do |preload_relations, rules| + preload(records: rules, associations: preload_relations) + end + promos.reject { |promotion| promotion.usage_limit_exceeded?(excluded_orders: [order]) } + end + + def preload(records:, associations:) + ActiveRecord::Associations::Preloader.new(records: records, associations: associations).call + end + + def connected_order_promotions + order.promotions.active.includes(promotion_includes) + end + + def sale_promotions + Spree::Promotion.where(apply_automatically: true).active.includes(promotion_includes) + end + + def promotion_includes + [ + :promotion_rules, + :promotion_actions, + ] + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_adjustment_chooser.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_adjustment_chooser.rb new file mode 100644 index 00000000000..4408a1d5f95 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_adjustment_chooser.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + class PromotionAdjustmentChooser + attr_reader :adjustable + + def initialize(adjustable) + @adjustable = adjustable + end + + def call(adjustments) + Array.wrap( + adjustments.select(&:eligible?).min_by do |adjustment| + [adjustment.amount, -adjustment.id.to_i] + end + ) + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_eligibility.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_eligibility.rb new file mode 100644 index 00000000000..ef6c4d7df18 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_eligibility.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + class PromotionEligibility + attr_reader :promotable, :possible_promotions + + def initialize(promotable:, possible_promotions:) + @promotable = promotable + @possible_promotions = possible_promotions + end + + def call + possible_promotions.select do |candidate| + applicable_rules = candidate.rules.select do |rule| + rule.applicable?(promotable) + end + + applicable_rules.all? do |applicable_rule| + applicable_rule.eligible?(promotable) + end + end + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/shipment_adjuster.rb b/friendly_promotions/app/models/solidus_friendly_promotions/shipment_adjuster.rb new file mode 100644 index 00000000000..5f43851ac97 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/shipment_adjuster.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + class ShipmentAdjuster + attr_reader :promotions + + def initialize(promotions:) + @promotions = promotions + end + + def call(shipment) + non_promotion_adjustments = shipment.adjustments.reject(&:promotion?) + + eligible_promotions = promotions.select do |promotion| + PromotionEligibility.new(promotable: shipment, possible_promotions: promotions).call + end + + possible_adjustments = eligible_promotions.flat_map do |promotion| + promotion.actions.select do |action| + action.can_adjust?(shipment) + end.map do |action| + action.adjust(shipment) + end + end + + chosen_adjustments = Spree::Config.promotion_chooser_class.new(shipment).call(possible_adjustments) + shipment.promo_total = chosen_adjustments.sum(&:amount) + shipment.adjustments = non_promotion_adjustments + chosen_adjustments + end + end +end diff --git a/friendly_promotions/spec/models/promotion/integration_spec.rb b/friendly_promotions/spec/models/promotion/integration_spec.rb index 4d065132bd0..d6d8dc1ac5a 100644 --- a/friendly_promotions/spec/models/promotion/integration_spec.rb +++ b/friendly_promotions/spec/models/promotion/integration_spec.rb @@ -4,12 +4,14 @@ RSpec.describe "Promotion System" do context "A promotion that creates line item adjustments" do - let(:shirt) { create(:product) } - let(:pants) { create(:product) } + let(:shirt) { create(:product, name: "Shirt") } + let(:pants) { create(:product, name: "Pants") } let(:promotion) { create(:promotion, name: "20% off Shirts", apply_automatically: true) } let(:order) { create(:order) } before do + Spree::Config.promotion_adjuster_class = "SolidusFriendlyPromotions::OrderPromotionAdjuster" + Spree::Config.promotion_chooser_class = "SolidusFriendlyPromotions::PromotionAdjustmentChooser" promotion.rules << rule promotion.actions << action order.contents.add(shirt.master, 1) @@ -19,25 +21,11 @@ context "with an order-level rule" do let(:rule) { SolidusFriendlyPromotions::Rules::Product.new(products: [shirt]) } - context "with an order level action" do - let(:calculator) { Spree::Calculator::FlatPercentItemTotal.new(preferred_flat_percent: 20) } - let(:action) { Spree::Promotion::Actions::CreateAdjustment.new(calculator: calculator) } - - it "creates one order-level adjustment" do - expect(order.adjustments.length).to eq(1) - expect(order.total).to eq(31.98) - expect(order.item_total).to eq(39.98) - # This is wrong! But order level adjustments can't work any other way - expect(order.item_total_before_tax).to eq(39.98) - expect(order.line_items.flat_map(&:adjustments)).to be_empty - end - end - context "with an line item level action" do let(:calculator) { Spree::Calculator::PercentOnLineItem.new(preferred_percent: 20) } - let(:action) { Spree::Promotion::Actions::CreateItemAdjustments.new(calculator: calculator) } + let(:action) { SolidusFriendlyPromotions::Actions::AdjustLineItem.new(calculator: calculator) } - it "creates one order-level adjustment" do + it "creates one line item level adjustment" do expect(order.adjustments).to be_empty expect(order.total).to eq(31.98) expect(order.item_total).to eq(39.98) @@ -50,27 +38,11 @@ context "with a line-item level rule" do let(:rule) { SolidusFriendlyPromotions::Rules::LineItemProduct.new(products: [shirt]) } - context "with an order level action" do - let(:calculator) { Spree::Calculator::FlatPercentItemTotal.new(preferred_flat_percent: 20) } - let(:action) { Spree::Promotion::Actions::CreateAdjustment.new(calculator: calculator) } - - it "creates one order-level adjustment" do - # Whoops - this works because line item level rules don't affect order-level actions :( - expect(order.adjustments.length).to eq(1) - expect(order.total).to eq(31.98) - expect(order.item_total).to eq(39.98) - # This is wrong! But order level adjustments can't work any other way - expect(order.item_total_before_tax).to eq(39.98) - expect(order.line_items.flat_map(&:adjustments)).to be_empty - end - end - context "with an line item level action" do let(:calculator) { Spree::Calculator::PercentOnLineItem.new(preferred_percent: 20) } - let(:action) { Spree::Promotion::Actions::CreateItemAdjustments.new(calculator: calculator) } + let(:action) { SolidusFriendlyPromotions::Actions::AdjustLineItem.new(calculator: calculator) } it "creates one line item level adjustment" do - pending expect(order.adjustments).to be_empty expect(order.total).to eq(35.98) expect(order.item_total).to eq(39.98) diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/actions/base_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/actions/base_spec.rb new file mode 100644 index 00000000000..7e967b4aef9 --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/actions/base_spec.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe SolidusFriendlyPromotions::Actions::Base do + it { is_expected.to respond_to :adjust } + it { is_expected.to respond_to :can_adjust? } + + describe "#can_adjust?" do + subject { described_class.new.can_adjust?(double) } + + it "raises a NotImplementedError" do + expect { subject }.to raise_exception(NotImplementedError) + end + end + + describe "#adjust" do + let(:variant) { create(:variant) } + let(:order) { create(:order) } + let(:adjustable) { Spree::LineItem.new(order: order, variant: variant, price: 10)} + let(:promotion) { Spree::Promotion.new(name: "20 Perzent off") } + let(:action) { described_class.new(promotion: promotion)} + before do + allow(action).to receive(:compute_amount).and_return(-1) + end + + subject { action.adjust(adjustable) } + + it "adds an adjustment to the adjustable" do + expect { subject }.to change { adjustable.adjustments.length }.by(1) + expect(adjustable.adjustments.first.label).to eq("Promotion (20 Perzent off)") + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/order_promotion_adjuster_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/order_promotion_adjuster_spec.rb new file mode 100644 index 00000000000..e69de29bb2d From 88a4e1c97cca15943e505ef9b57e54aee3b8d6ec Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 13 Jun 2023 09:14:16 +0200 Subject: [PATCH 009/524] Add order promotion adjuster spec --- .../order_promotion_adjuster_spec.rb | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/order_promotion_adjuster_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/order_promotion_adjuster_spec.rb index e69de29bb2d..75ac9adfa99 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/order_promotion_adjuster_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/order_promotion_adjuster_spec.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SolidusFriendlyPromotions::OrderPromotionAdjuster, type: :model do + let(:line_item) { create(:line_item) } + let(:order) { line_item.order } + let(:promotion) { create(:promotion, apply_automatically: true) } + let(:calculator) { Spree::Calculator::FlatPercentItemTotal.new(preferred_flat_percent: 10) } + + subject { described_class.new(order) } + + context "adjusting line items" do + let!(:action) { SolidusFriendlyPromotions::Actions::AdjustLineItem.create(promotion: promotion, calculator: calculator) } + let(:adjustable) { line_item } + + context "promotion with no rules" do + context "creates the adjustment" do + it "creates the adjustment" do + expect { + subject.call + }.to change { adjustable.adjustments.length }.by(1) + end + end + + context "for a non-sale promotion" do + let(:promotion) { create(:promotion, apply_automatically: false) } + + it "doesn't connect the promotion to the order" do + expect { + subject.call + }.to change { order.promotions.length }.by(0) + end + + it "doesn't create an adjustment" do + expect { + subject.call + }.to change { adjustable.adjustments.length }.by(0) + end + end + end + + context "promotion includes item involved" do + let!(:rule) { SolidusFriendlyPromotions::Rules::Product.create(products: [line_item.product], promotion: promotion) } + + context "creates the adjustment" do + it "creates the adjustment" do + expect { + subject.call + }.to change { adjustable.adjustments.length }.by(1) + end + end + end + + context "promotion has item total rule" do + let!(:rule) { SolidusFriendlyPromotions::Rules::ItemTotal.create(preferred_operator: 'gt', preferred_amount: 50, promotion: promotion) } + + before do + # Makes the order eligible for this promotion + order.item_total = 100 + order.save + end + + context "creates the adjustment" do + it "creates the adjustment" do + expect { + subject.call + }.to change { adjustable.adjustments.length }.by(1) + end + end + end + end +end From 2a8ca6f9307cbfc757db484959f26fe80f2d7d9d Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 6 Jun 2023 12:02:27 +0200 Subject: [PATCH 010/524] Replace OrderContents decorator with configurable class This also imports the Solidus specs and revealed an omission in the Actions base class. --- .../order_contents_decorator.rb | 16 - .../actions/base.rb | 1 + .../line_item_adjuster.rb | 1 + .../simple_order_contents.rb | 28 ++ .../simple_order_contents_spec.rb | 361 ++++++++++++++++++ friendly_promotions/spec/spec_helper.rb | 4 + 6 files changed, 395 insertions(+), 16 deletions(-) delete mode 100644 friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_contents_decorator.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/simple_order_contents.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/simple_order_contents_spec.rb diff --git a/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_contents_decorator.rb b/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_contents_decorator.rb deleted file mode 100644 index 029fa3447f4..00000000000 --- a/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_contents_decorator.rb +++ /dev/null @@ -1,16 +0,0 @@ -# frozen_string_literal: true - -module SolidusFriendlyPromotions - module OrderContentsDecorator - private - - def after_add_or_remove(line_item, options = {}) - shipment = options[:shipment] - shipment.present? ? shipment.update_amounts : order.ensure_updated_shipments - reload_totals - line_item - end - - Spree::OrderContents.prepend self - end -end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/actions/base.rb b/friendly_promotions/app/models/solidus_friendly_promotions/actions/base.rb index e26f14ab339..e30fd21c711 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/actions/base.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/actions/base.rb @@ -22,6 +22,7 @@ def adjust(adjustable) end || adjustable.adjustments.build(source: self) adjustment.label = adjustment_label(adjustable) adjustment.amount = compute_amount(adjustable) + adjustment.order = adjustable.order adjustment end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/line_item_adjuster.rb b/friendly_promotions/app/models/solidus_friendly_promotions/line_item_adjuster.rb index e60b92a2c71..6de84fa0e52 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/line_item_adjuster.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/line_item_adjuster.rb @@ -26,6 +26,7 @@ def call(line_item) line_item.promo_total = chosen_adjustments.sum(&:amount) line_item.adjustments = non_promotion_adjustments + chosen_adjustments + line_item end end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/simple_order_contents.rb b/friendly_promotions/app/models/solidus_friendly_promotions/simple_order_contents.rb new file mode 100644 index 00000000000..e2dabf06327 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/simple_order_contents.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + class SimpleOrderContents < Spree::OrderContents + + def update_cart(params) + if order.update(params) + unless order.completed? + order.line_items = order.line_items.select { |li| li.quantity > 0 } + order.ensure_updated_shipments + end + reload_totals + true + else + false + end + end + + private + + def after_add_or_remove(line_item, options = {}) + shipment = options[:shipment] + shipment.present? ? shipment.update_amounts : order.ensure_updated_shipments + reload_totals + line_item + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/simple_order_contents_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/simple_order_contents_spec.rb new file mode 100644 index 00000000000..a10b2396c3b --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/simple_order_contents_spec.rb @@ -0,0 +1,361 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SolidusFriendlyPromotions::SimpleOrderContents, type: :model do + let!(:store) { create :store } + let(:order) { create(:order) } + let(:variant) { create(:variant) } + let!(:stock_location) { variant.stock_locations.first } + let(:stock_location_2) { create(:stock_location) } + + subject(:order_contents) { described_class.new(order) } + + context "#add" do + context 'given quantity is not explicitly provided' do + it 'should add one line item' do + line_item = subject.add(variant) + expect(line_item.quantity).to eq(1) + expect(order.line_items.size).to eq(1) + end + end + + context 'given a shipment' do + let!(:shipment) { create(:shipment, order: order) } + + it "ensure shipment calls update_amounts instead of order calling ensure_updated_shipments" do + expect(subject.order).to_not receive(:ensure_updated_shipments) + expect(shipment).to receive(:update_amounts).at_least(:once) + subject.add(variant, 1, shipment: shipment) + end + + context "with quantity=1" do + it "creates correct inventory" do + subject.add(variant, 1, shipment: shipment) + expect(order.inventory_units.count).to eq(1) + end + end + + context "with quantity=2" do + it "creates correct inventory" do + subject.add(variant, 2, shipment: shipment) + expect(order.inventory_units.count).to eq(2) + end + end + + context "called multiple times" do + it "creates correct inventory" do + subject.add(variant, 1, shipment: shipment) + subject.add(variant, 1, shipment: shipment) + expect(order.inventory_units.count).to eq(2) + end + end + end + + context 'not given a shipment' do + it "ensures updated shipments" do + expect(subject.order).to receive(:ensure_updated_shipments) + subject.add(variant) + end + end + + it 'should add line item if one does not exist' do + line_item = subject.add(variant, 1) + expect(line_item.quantity).to eq(1) + expect(order.line_items.size).to eq(1) + end + + it 'should update line item if one exists' do + subject.add(variant, 1) + line_item = subject.add(variant, 1) + expect(line_item.quantity).to eq(2) + expect(order.line_items.size).to eq(1) + end + + it "should update order totals" do + expect(order.item_total.to_f).to eq(0.00) + expect(order.total.to_f).to eq(0.00) + + subject.add(variant, 1) + + expect(order.item_total.to_f).to eq(19.99) + expect(order.total.to_f).to eq(19.99) + end + + context "running promotions" do + let(:promotion) { create(:promotion, apply_automatically: true) } + let(:calculator) { Spree::Calculator::FlatRate.new(preferred_amount: 10) } + + before do + Spree::Config.promotion_adjuster_class = "SolidusFriendlyPromotions::OrderPromotionAdjuster" + Spree::Config.promotion_chooser_class = "SolidusFriendlyPromotions::PromotionAdjustmentChooser" + end + + context "one active line item promotion" do + let!(:action) { SolidusFriendlyPromotions::Actions::AdjustLineItem.create(promotion: promotion, calculator: calculator) } + + it "creates valid discount on order" do + subject.add(variant, 1) + subject.order.line_item_adjustments.reset + expect(subject.order.line_item_adjustments.to_a.sum(&:amount)).not_to eq 0 + end + + context "discount changes order total" do + before { subject.add(variant, 1) } + it { expect(subject.order.total).not_to eq variant.price } + end + end + end + + describe 'tax calculations' do + let!(:zone) { create(:global_zone) } + let!(:tax_rate) do + create(:tax_rate, zone: zone, tax_categories: [variant.tax_category]) + end + + context 'when the order has a taxable address' do + before do + expect(order.tax_address.country_id).to be_present + end + + it 'creates a tax adjustment' do + order_contents.add(variant) + line_item = order.find_line_item_by_variant(variant) + expect(line_item.adjustments.tax.count).to eq(1) + end + end + + context 'when the order does not have a taxable address' do + before do + order.update!(ship_address: nil, bill_address: nil) + expect(order.tax_address.country_id).to be_nil + end + + it 'creates a tax adjustment' do + order_contents.add(variant) + line_item = order.find_line_item_by_variant(variant) + expect(line_item.adjustments.tax.count).to eq(0) + end + end + end + end + + context "#remove" do + context "given an invalid variant" do + it "raises an exception" do + expect { + subject.remove(variant, 1) + }.to raise_error(ActiveRecord::RecordNotFound) + end + end + + context 'given quantity is not explicitly provided' do + it 'should remove one line item' do + line_item = subject.add(variant, 3) + subject.remove(variant) + + expect(line_item.reload.quantity).to eq(2) + end + end + + context 'given a shipment' do + it "ensure shipment calls update_amounts instead of order calling ensure_updated_shipments" do + subject.add(variant, 1) + shipment = create(:shipment) + expect(subject.order).to_not receive(:ensure_updated_shipments) + expect(shipment).to receive(:update_amounts) + subject.remove(variant, 1, shipment: shipment) + end + end + + context 'not given a shipment' do + it "ensures updated shipments" do + subject.add(variant, 1) + expect(subject.order).to receive(:ensure_updated_shipments) + subject.remove(variant) + end + end + + it 'should reduce line_item quantity if quantity is less the line_item quantity' do + line_item = subject.add(variant, 3) + subject.remove(variant, 1) + + expect(line_item.reload.quantity).to eq(2) + end + + it 'should remove line_item if quantity matches line_item quantity' do + subject.add(variant, 1) + subject.remove(variant, 1) + + expect(order.reload.find_line_item_by_variant(variant)).to be_nil + end + + it 'should remove line_item if quantity is greater than line_item quantity' do + subject.add(variant, 1) + subject.remove(variant, 2) + + expect(order.reload.find_line_item_by_variant(variant)).to be_nil + end + + it "should update order totals" do + expect(order.item_total.to_f).to eq(0.00) + expect(order.total.to_f).to eq(0.00) + + subject.add(variant, 2) + + expect(order.item_total.to_f).to eq(39.98) + expect(order.total.to_f).to eq(39.98) + + subject.remove(variant, 1) + expect(order.item_total.to_f).to eq(19.99) + expect(order.total.to_f).to eq(19.99) + end + end + + context "#remove_line_item" do + context 'given a shipment' do + it "ensure shipment calls update_amounts instead of order calling ensure_updated_shipments" do + line_item = subject.add(variant, 1) + shipment = create(:shipment) + expect(subject.order).to_not receive(:ensure_updated_shipments) + expect(shipment).to receive(:update_amounts) + subject.remove_line_item(line_item, shipment: shipment) + end + end + + context 'not given a shipment' do + it "ensures updated shipments" do + line_item = subject.add(variant, 1) + expect(subject.order).to receive(:ensure_updated_shipments) + subject.remove_line_item(line_item) + end + end + + it 'should remove line_item' do + line_item = subject.add(variant, 1) + subject.remove_line_item(line_item) + + expect(order.reload.line_items).to_not include(line_item) + end + + it "should update order totals" do + expect(order.item_total.to_f).to eq(0.00) + expect(order.total.to_f).to eq(0.00) + + line_item = subject.add(variant, 2) + + expect(order.item_total.to_f).to eq(39.98) + expect(order.total.to_f).to eq(39.98) + + subject.remove_line_item(line_item) + expect(order.item_total.to_f).to eq(0.00) + expect(order.total.to_f).to eq(0.00) + end + end + + context "update cart" do + let!(:shirt) { subject.add variant, 1 } + + let(:params) do + { line_items_attributes: { + "0" => { id: shirt.id, quantity: 3 } + } } + end + + it "changes item quantity" do + subject.update_cart params + expect(shirt.reload.quantity).to eq 3 + end + + it "updates order totals" do + expect { + subject.update_cart params + }.to change { subject.order.total } + end + + context "submits item quantity 0" do + let(:params) do + { line_items_attributes: { + "0" => { id: shirt.id, quantity: 0 } + } } + end + + it "removes item from order" do + expect { + subject.update_cart params + }.to change { subject.order.line_items.count } + end + end + + it "ensures updated shipments" do + expect(subject.order).to receive(:ensure_updated_shipments) + subject.update_cart params + end + end + + context "completed order" do + let(:order) do + Spree::Order.create!( + state: 'complete', + completed_at: Time.current, + email: "test@example.com" + ) + end + + before { order.shipments.create! stock_location_id: variant.stock_location_ids.first } + + it "updates order payment state" do + expect { + subject.add variant + }.to change { order.payment_state } + + expect { + subject.remove variant + }.to change { order.payment_state } + end + end + + describe "#approve" do + context 'when a name is supplied' do + it 'approves the order' do + order.contents.approve(name: 'Jordan') + expect(order.approver).to be_nil + expect(order.approver_name).to eq('Jordan') + expect(order.approved_at).to be_present + expect(order.approved?).to be_truthy + end + end + + context 'when a user is supplied' do + let(:user) { create(:user) } + + it 'approves the order' do + order.contents.approve(user: user) + expect(order.approver).to eq(user) + expect(order.approver_name).to be_nil + expect(order.approved_at).to be_present + expect(order.approved?).to be_truthy + end + end + + context 'when a user and a name are supplied' do + let(:user) { create(:user) } + + it 'approves the order' do + order.contents.approve(user: user, name: 'Jordan') + expect(order.approver).to eq(user) + expect(order.approver_name).to eq('Jordan') + expect(order.approved_at).to be_present + expect(order.approved?).to be_truthy + end + end + + context 'when neither a user nor a name are supplied' do + it 'raises' do + expect { + order.contents.approve + }.to raise_error(ArgumentError, 'user or name must be specified') + end + end + end +end diff --git a/friendly_promotions/spec/spec_helper.rb b/friendly_promotions/spec/spec_helper.rb index 0c2fae49a39..d17e8ccd2b4 100644 --- a/friendly_promotions/spec/spec_helper.rb +++ b/friendly_promotions/spec/spec_helper.rb @@ -32,4 +32,8 @@ if Spree.solidus_gem_version < Gem::Version.new("2.11") config.extend Spree::TestingSupport::AuthorizationHelpers::Request, type: :system end + + config.before do + Spree::Config.order_contents_class = "SolidusFriendlyPromotions::SimpleOrderContents" + end end From a846ff323d1f478a9841227d487cfc08fd9b8f2c Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 13 Jun 2023 09:10:19 +0200 Subject: [PATCH 011/524] Set order when creating adjustments We don't have to re-set the order every time an adjustment changes, it's enough the first time around. --- .../app/models/solidus_friendly_promotions/actions/base.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/actions/base.rb b/friendly_promotions/app/models/solidus_friendly_promotions/actions/base.rb index e30fd21c711..bd1574b1715 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/actions/base.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/actions/base.rb @@ -19,10 +19,9 @@ def can_adjust?(object) def adjust(adjustable) adjustment = adjustable.adjustments.detect do |adjustment| adjustment.source == self - end || adjustable.adjustments.build(source: self) + end || adjustable.adjustments.build(source: self, order: adjustable.order) adjustment.label = adjustment_label(adjustable) adjustment.amount = compute_amount(adjustable) - adjustment.order = adjustable.order adjustment end From 8e3e56d4c5c0e0d8e5759110e7a05952a06345a5 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 13 Jun 2023 09:11:26 +0200 Subject: [PATCH 012/524] Add a promotion chooser class configuration option The interface here is slightly different than the one in core Solidus. This is because we want the class to work in-memory rather than at the DB level. --- .../line_item_adjuster.rb | 2 +- .../shipment_adjuster.rb | 4 +++- .../configuration.rb | 6 ++---- .../configuration_spec.rb | 21 +++++++++++++++++++ 4 files changed, 27 insertions(+), 6 deletions(-) create mode 100644 friendly_promotions/spec/lib/solidus_friendly_promotions/configuration_spec.rb diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/line_item_adjuster.rb b/friendly_promotions/app/models/solidus_friendly_promotions/line_item_adjuster.rb index 6de84fa0e52..ef748830aa2 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/line_item_adjuster.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/line_item_adjuster.rb @@ -22,7 +22,7 @@ def call(line_item) end end - chosen_adjustments = Spree::Config.promotion_chooser_class.new(line_item).call(possible_adjustments) + chosen_adjustments = SolidusFriendlyPromotions.config.promotion_chooser_class.new(line_item).call(possible_adjustments) line_item.promo_total = chosen_adjustments.sum(&:amount) line_item.adjustments = non_promotion_adjustments + chosen_adjustments diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/shipment_adjuster.rb b/friendly_promotions/app/models/solidus_friendly_promotions/shipment_adjuster.rb index 5f43851ac97..73bab73e396 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/shipment_adjuster.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/shipment_adjuster.rb @@ -23,9 +23,11 @@ def call(shipment) end end - chosen_adjustments = Spree::Config.promotion_chooser_class.new(shipment).call(possible_adjustments) + chosen_adjustments = SolidusFriendlyPromotions.config.promotion_chooser_class.new(shipment).call(possible_adjustments) + shipment.promo_total = chosen_adjustments.sum(&:amount) shipment.adjustments = non_promotion_adjustments + chosen_adjustments + shipment end end end diff --git a/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb b/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb index bb9d78a8c19..2813c447f15 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb @@ -1,10 +1,8 @@ # frozen_string_literal: true module SolidusFriendlyPromotions - class Configuration - # Define here the settings for this extension, e.g.: - # - # attr_accessor :my_setting + class Configuration < Spree::Preferences::Configuration + class_name_attribute :promotion_chooser_class, default: "SolidusFriendlyPromotions::PromotionAdjustmentChooser" end class << self diff --git a/friendly_promotions/spec/lib/solidus_friendly_promotions/configuration_spec.rb b/friendly_promotions/spec/lib/solidus_friendly_promotions/configuration_spec.rb new file mode 100644 index 00000000000..92df9f120e3 --- /dev/null +++ b/friendly_promotions/spec/lib/solidus_friendly_promotions/configuration_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe SolidusFriendlyPromotions::Configuration do + subject { SolidusFriendlyPromotions.config } + + it "has a nice accessor" do + expect(subject).to be_a(described_class) + end + + it "is an instance of Spree::Configuration" do + expect(subject).to be_a(Spree::Preferences::Configuration) + end + + describe ".promotion_chooser_class" do + it "is the promotion chooser" do + expect(subject.promotion_chooser_class).to eq(SolidusFriendlyPromotions::PromotionAdjustmentChooser) + end + end +end From 4b9f2b78ce56c7e3bb0f17a6421fdcc86ac52b5d Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 13 Jun 2023 09:24:45 +0200 Subject: [PATCH 013/524] Require at least Solidus 4 We're not developing for the past here. --- friendly_promotions/solidus_friendly_promotions.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/friendly_promotions/solidus_friendly_promotions.gemspec b/friendly_promotions/solidus_friendly_promotions.gemspec index 5180cdf27a8..d066341da4e 100644 --- a/friendly_promotions/solidus_friendly_promotions.gemspec +++ b/friendly_promotions/solidus_friendly_promotions.gemspec @@ -28,7 +28,7 @@ Gem::Specification.new do |spec| spec.executables = files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] - spec.add_dependency "solidus_core", [">= 3.0.0", "< 5"] + spec.add_dependency "solidus_core", [">= 4.0.0", "< 5"] spec.add_dependency "solidus_support", "~> 0.5" spec.add_development_dependency "solidus_dev_support", "~> 2.6" From 7bf478b6c211f7557a09356968c2aa401dfb61ff Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 13 Jun 2023 11:10:57 +0200 Subject: [PATCH 014/524] Remove Solidus promotions tab from menu --- .../config/initializers/solidus_friendly_promotions.rb | 1 + 1 file changed, 1 insertion(+) create mode 100644 friendly_promotions/config/initializers/solidus_friendly_promotions.rb diff --git a/friendly_promotions/config/initializers/solidus_friendly_promotions.rb b/friendly_promotions/config/initializers/solidus_friendly_promotions.rb new file mode 100644 index 00000000000..472576bf09f --- /dev/null +++ b/friendly_promotions/config/initializers/solidus_friendly_promotions.rb @@ -0,0 +1 @@ +Spree::Backend::Config.menu_items.delete_if { |menu_item| menu_item.url == :admin_promotions_path } From 388842173ca061249714f42d333f3494155636ed Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 13 Jun 2023 13:50:38 +0200 Subject: [PATCH 015/524] Import TieredPercent Calculator from Solidus --- .../calculators/tiered_percent.rb | 64 ++++++ .../calculators/tiered_percent_spec.rb | 197 ++++++++++++++++++ .../calculator_shared_examples.rb | 10 + 3 files changed, 271 insertions(+) create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_percent.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_percent_spec.rb create mode 100644 friendly_promotions/spec/shared_examples/calculator_shared_examples.rb diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_percent.rb b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_percent.rb new file mode 100644 index 00000000000..9ab212f24bf --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_percent.rb @@ -0,0 +1,64 @@ +# frozen_string_literal: true + +require_dependency 'spree/calculator' + +module SolidusFriendlyPromotions + module Calculators + class TieredPercent < Spree::Calculator + preference :base_percent, :decimal, default: 0 + preference :tiers, :hash, default: {} + preference :currency, :string, default: -> { Spree::Config[:currency] } + + before_validation do + # Convert tier values to decimals. Strings don't do us much good. + if preferred_tiers.is_a?(Hash) + self.preferred_tiers = preferred_tiers.map do |key, value| + [cast_to_d(key.to_s), cast_to_d(value.to_s)] + end.to_h + end + end + + validates :preferred_base_percent, numericality: { + greater_than_or_equal_to: 0, + less_than_or_equal_to: 100 + } + validate :preferred_tiers_content + + def compute(object) + order = object.is_a?(Spree::Order) ? object : object.order + + _base, percent = preferred_tiers.sort.reverse.detect do |value, _| + order.item_total >= value + end + + if preferred_currency.casecmp(order.currency).zero? + currency_exponent = ::Money::Currency.find(preferred_currency).exponent + (object.amount * (percent || preferred_base_percent) / 100).round(currency_exponent) + else + 0 + end + end + + private + + def cast_to_d(value) + value.to_s.to_d + rescue ArgumentError + BigDecimal(0) + end + + def preferred_tiers_content + if preferred_tiers.is_a? Hash + unless preferred_tiers.keys.all?{ |key| key.is_a?(Numeric) && key > 0 } + errors.add(:base, :keys_should_be_positive_number) + end + unless preferred_tiers.values.all?{ |key| key.is_a?(Numeric) && key >= 0 && key <= 100 } + errors.add(:base, :values_should_be_percent) + end + else + errors.add(:preferred_tiers, :should_be_hash) + end + end + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_percent_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_percent_spec.rb new file mode 100644 index 00000000000..7d8cacde5ee --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_percent_spec.rb @@ -0,0 +1,197 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'shared_examples/calculator_shared_examples' + +RSpec.describe SolidusFriendlyPromotions::Calculators::TieredPercent, type: :model do + let(:calculator) { described_class.new } + + it_behaves_like 'a calculator with a description' + + describe "#valid?" do + subject { calculator.valid? } + + context "when base percent is less than zero" do + before { calculator.preferred_base_percent = -1 } + it { is_expected.to be false } + end + + context "when base percent is greater than 100" do + before { calculator.preferred_base_percent = 110 } + it { is_expected.to be false } + end + + context "when tiers is a hash" do + context "and the key is not a positive number" do + before { calculator.preferred_tiers = { "nope" => 20 } } + it { is_expected.to be false } + end + + context "and one of the values is not a percent" do + before { calculator.preferred_tiers = { 10 => 110 } } + it { is_expected.to be false } + end + + context "and the key is an integer" do + before { calculator.preferred_tiers = { 20 => 20 } } + it "converts successfully" do + is_expected.to be true + expect(calculator.preferred_tiers).to eq({ BigDecimal('20') => BigDecimal('20') }) + end + end + + context "and the key is a float" do + before { calculator.preferred_tiers = { 20.5 => 20.5 } } + it "converts successfully" do + is_expected.to be true + expect(calculator.preferred_tiers).to eq({ BigDecimal('20.5') => BigDecimal('20.5') }) + end + end + + context "and the key is a string number" do + before { calculator.preferred_tiers = { "20" => 20 } } + it "converts successfully" do + is_expected.to be true + expect(calculator.preferred_tiers).to eq({ BigDecimal('20') => BigDecimal('20') }) + end + end + + context "and the key is a numeric string with spaces" do + before { calculator.preferred_tiers = { " 20 " => 20 } } + it "converts successfully" do + is_expected.to be true + expect(calculator.preferred_tiers).to eq({ BigDecimal('20') => BigDecimal('20') }) + end + end + + context "and the key is a string number with decimals" do + before { calculator.preferred_tiers = { "20.5" => "20.5" } } + it "converts successfully" do + is_expected.to be true + expect(calculator.preferred_tiers).to eq({ BigDecimal('20.5') => BigDecimal('20.5') }) + end + end + end + end + + describe "#compute" do + let(:order) do + create( + :order_with_line_items, + line_items_count: line_item_count, + line_items_price: price + ) + end + let(:price) { 10 } + let(:preferred_currency) { "USD" } + + before do + calculator.preferred_base_percent = 10 + calculator.preferred_tiers = { + 20 => 15, + 30 => 20 + } + calculator.preferred_currency = preferred_currency + end + + context "with a line item" do + let(:line_item) { order.line_items.first } + subject { calculator.compute(line_item) } + + context "for multiple line items" do + context "when amount falls within the first tier" do + let(:line_item_count) { 1 } + it { is_expected.to eq 1.0 } + end + + context "when amount falls within the second tier" do + let(:line_item_count) { 2 } + it { is_expected.to eq 1.5 } + end + + context "when amount falls within the third tier" do + let(:line_item_count) { 3 } + it { is_expected.to eq 2.0 } + end + end + + context "for a single line item" do + let(:line_item_count) { 1 } + + context "when amount falls within the first tier" do + let(:price) { 10 } + it { is_expected.to eq 1.0 } + end + + context "when amount falls within the second tier" do + let(:price) { 20 } + it { is_expected.to eq 3.0 } + end + + context "when amount falls within the third tier" do + let(:price) { 30 } + it { is_expected.to eq 6.0 } + end + end + + context "when the order's currency does not match the calculator" do + let(:preferred_currency) { "JPY" } + let(:line_item_count) { 1 } + let(:price) { 15 } + it { is_expected.to eq 0 } + + it "rounds based on currency" do + allow(order).to receive_messages currency: "JPY" + expect(subject).to eq(2) + end + end + end + + context "with an order" do + subject { calculator.compute(order) } + + let(:line_item_count) { 1 } + + context "for multiple line items" do + context "when amount falls within the first tier" do + let(:line_item_count) { 1 } + it { is_expected.to eq 1.0 } + end + + context "when amount falls within the second tier" do + let(:line_item_count) { 2 } + it { is_expected.to eq 3.0 } + end + + context "when amount falls within the third tier" do + let(:line_item_count) { 3 } + it { is_expected.to eq 6.0 } + end + end + + context "for a single line item" do + let(:line_item_count) { 1 } + + context "when amount falls within the first tier" do + let(:price) { 10 } + it { is_expected.to eq 1.0 } + end + + context "when amount falls within the second tier" do + let(:price) { 20 } + it { is_expected.to eq 3.0 } + end + + context "when amount falls within the third tier" do + let(:price) { 30 } + it { is_expected.to eq 6.0 } + end + end + + context "when the order's currency does not match the calculator" do + let(:preferred_currency) { "CAD" } + it { is_expected.to eq 0 } + end + end + end +end diff --git a/friendly_promotions/spec/shared_examples/calculator_shared_examples.rb b/friendly_promotions/spec/shared_examples/calculator_shared_examples.rb new file mode 100644 index 00000000000..c41b3c7e4e6 --- /dev/null +++ b/friendly_promotions/spec/shared_examples/calculator_shared_examples.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +RSpec.shared_examples_for 'a calculator with a description' do + describe ".description" do + subject { described_class.description } + it "has a description" do + expect(subject.size).to be > 0 + end + end +end From b60b51306f704503263f9c1e9e73d04d12cf8abc Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 13 Jun 2023 14:05:39 +0200 Subject: [PATCH 016/524] Adapt tiered calculator spec to shipments We want to be able to calculate shipments with this calculator, not orders. --- .../calculators/tiered_percent.rb | 6 ++++-- .../calculators/tiered_percent_spec.rb | 13 +++++++++---- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_percent.rb b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_percent.rb index 9ab212f24bf..7e3f75d1005 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_percent.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_percent.rb @@ -24,8 +24,8 @@ class TieredPercent < Spree::Calculator } validate :preferred_tiers_content - def compute(object) - order = object.is_a?(Spree::Order) ? object : object.order + def compute_item(object) + order = object.order _base, percent = preferred_tiers.sort.reverse.detect do |value, _| order.item_total >= value @@ -38,6 +38,8 @@ def compute(object) 0 end end + alias_method :compute_shipment, :compute_item + alias_method :compute_line_item, :compute_item private diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_percent_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_percent_spec.rb index 7d8cacde5ee..02db8f2bfbe 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_percent_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_percent_spec.rb @@ -147,8 +147,11 @@ end end - context "with an order" do - subject { calculator.compute(order) } + context "with a shipment" do + let(:shipment) { Spree::Shipment.new(order: order, amount: shipping_cost) } + let(:shipping_cost) { 10 } + + subject { calculator.compute(shipment) } let(:line_item_count) { 1 } @@ -160,12 +163,12 @@ context "when amount falls within the second tier" do let(:line_item_count) { 2 } - it { is_expected.to eq 3.0 } + it { is_expected.to eq 1.5 } end context "when amount falls within the third tier" do let(:line_item_count) { 3 } - it { is_expected.to eq 6.0 } + it { is_expected.to eq 2.0 } end end @@ -179,11 +182,13 @@ context "when amount falls within the second tier" do let(:price) { 20 } + let(:shipping_cost) { 20 } it { is_expected.to eq 3.0 } end context "when amount falls within the third tier" do let(:price) { 30 } + let(:shipping_cost) { 30 } it { is_expected.to eq 6.0 } end end From f6b029ebe1fd6549a6c57902c771dc752ef08e73 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 14 Jun 2023 11:57:53 +0200 Subject: [PATCH 017/524] Add TieredFlatRate calculator Interestingly, this calculator works differently to the `TieredPercent` calculator: `TieredPercent` gets the tier from the order's item total, where as this calculator gets it from the line item or shipment being calculated. --- .../calculators/tiered_flat_rate.rb | 56 ++++++++ .../calculators/tiered_flat_rate_spec.rb | 126 ++++++++++++++++++ 2 files changed, 182 insertions(+) create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_flat_rate.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_flat_rate_spec.rb diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_flat_rate.rb b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_flat_rate.rb new file mode 100644 index 00000000000..77e04246fe7 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_flat_rate.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +require_dependency 'spree/calculator' + +module SolidusFriendlyPromotions + module Calculators + class TieredFlatRate < Spree::Calculator + preference :base_amount, :decimal, default: 0 + preference :tiers, :hash, default: {} + preference :currency, :string, default: -> { Spree::Config[:currency] } + + before_validation do + # Convert tier values to decimals. Strings don't do us much good. + if preferred_tiers.is_a?(Hash) + self.preferred_tiers = preferred_tiers.map do |key, value| + [cast_to_d(key.to_s), cast_to_d(value.to_s)] + end.to_h + end + end + + validate :preferred_tiers_content + + def compute_item(object) + _base, amount = preferred_tiers.sort.reverse.detect do |value, _| + object.amount >= value + end + + if preferred_currency.casecmp(object.currency).zero? + amount || preferred_base_amount + else + 0 + end + end + alias_method :compute_shipment, :compute_item + alias_method :compute_line_item, :compute_item + + private + + def cast_to_d(value) + value.to_s.to_d + rescue ArgumentError + BigDecimal(0) + end + + def preferred_tiers_content + if preferred_tiers.is_a? Hash + unless preferred_tiers.keys.all?{ |key| key.is_a?(Numeric) && key > 0 } + errors.add(:base, :keys_should_be_positive_number) + end + else + errors.add(:preferred_tiers, :should_be_hash) + end + end + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_flat_rate_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_flat_rate_spec.rb new file mode 100644 index 00000000000..c42b5c34cf4 --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_flat_rate_spec.rb @@ -0,0 +1,126 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'shared_examples/calculator_shared_examples' + +RSpec.describe SolidusFriendlyPromotions::Calculators::TieredFlatRate, type: :model do + let(:calculator) { described_class.new } + + it_behaves_like 'a calculator with a description' + + describe "#valid?" do + subject { calculator.valid? } + context "when tiers is a hash" do + context "and the key is not a positive number" do + before { calculator.preferred_tiers = { "nope" => 20 } } + it { is_expected.to be false } + end + + context "and the key is an integer" do + before { calculator.preferred_tiers = { 20 => 20 } } + it "converts successfully" do + is_expected.to be true + expect(calculator.preferred_tiers).to eq({ BigDecimal('20') => BigDecimal('20') }) + end + end + + context "and the key is a float" do + before { calculator.preferred_tiers = { 20.5 => 20.5 } } + it "converts successfully" do + is_expected.to be true + expect(calculator.preferred_tiers).to eq({ BigDecimal('20.5') => BigDecimal('20.5') }) + end + end + + context "and the key is a string number" do + before { calculator.preferred_tiers = { "20" => 20 } } + it "converts successfully" do + is_expected.to be true + expect(calculator.preferred_tiers).to eq({ BigDecimal('20') => BigDecimal('20') }) + end + end + + context "and the key is a numeric string with spaces" do + before { calculator.preferred_tiers = { " 20 " => 20 } } + it "converts successfully" do + is_expected.to be true + expect(calculator.preferred_tiers).to eq({ BigDecimal('20') => BigDecimal('20') }) + end + end + + context "and the key is a string number with decimals" do + before { calculator.preferred_tiers = { "20.5" => "20.5" } } + it "converts successfully" do + is_expected.to be true + expect(calculator.preferred_tiers).to eq({ BigDecimal('20.5') => BigDecimal('20.5') }) + end + end + end + end + + describe "#compute" do + let(:order) do + create( + :order_with_line_items, + line_items_count: 1, + line_items_price: amount + ) + end + let(:line_item) { order.line_items.first } + let(:preferred_currency) { "USD" } + + before do + calculator.preferred_base_amount = 10 + calculator.preferred_tiers = { + 100 => 15, + 200 => 20 + } + calculator.preferred_currency = preferred_currency + end + + subject { calculator.compute(line_item) } + + context "when amount falls within the first tier" do + let(:amount) { 50 } + it { is_expected.to eq 10 } + end + + context "when amount falls within the second tier" do + let(:amount) { 150 } + it { is_expected.to eq 15 } + end + + context "when the order's currency does not match the calculator" do + let(:preferred_currency) { "CAD" } + let(:amount) { 50 } + it { is_expected.to eq 0 } + end + + context "with a shipment" do + let(:shipment) { Spree::Shipment.new(order: order, amount: shipping_cost) } + let(:amount) { 10 } + + subject { calculator.compute(shipment) } + + let(:line_item_count) { 1 } + + context "for multiple line items" do + context "when amount falls within the first tier" do + let(:shipping_cost) { 110 } + it { is_expected.to eq 15 } + end + + context "when amount falls within the second tier" do + let(:shipping_cost) { 210 } + it { is_expected.to eq 20 } + end + + context "when the order's currency does not match the calculator" do + let(:preferred_currency) { "CAD" } + let(:shipping_cost) { 110 } + it { is_expected.to eq 0 } + end + end + end + end +end From 7084f89ee8bba7a428ab12c90d49229eab39c0bf Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 14 Jun 2023 12:15:17 +0200 Subject: [PATCH 018/524] Add "Percent" calculator Over time, it turned out that Solidus core has two calculators doing the same thing, `FlatPercentItemTotal` as well as `PercentOnLineItem`. This calculator has all the features of both (except for the protection against overpromoting items, which the adjuster classes do). --- .../calculators/percent.rb | 17 ++++++++++++++++ .../calculators/percent_spec.rb | 20 +++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/calculators/percent.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/calculators/percent_spec.rb diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/percent.rb b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/percent.rb new file mode 100644 index 00000000000..1ab796788e9 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/percent.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require_dependency 'spree/calculator' + +module SolidusFriendlyPromotions + module Calculators + class Percent < Spree::Calculator + preference :percent, :decimal, default: 0 + + def compute(object) + preferred_currency = object.order.currency + currency_exponent = ::Money::Currency.find(preferred_currency).exponent + (object.amount * preferred_percent / 100).round(currency_exponent) + end + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/percent_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/percent_spec.rb new file mode 100644 index 00000000000..78ce5c5e59a --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/percent_spec.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'shared_examples/calculator_shared_examples' + +RSpec.describe SolidusFriendlyPromotions::Calculators::Percent, type: :model do + context "compute" do + let(:currency) { "USD" } + let(:order) { double(currency: currency) } + let(:line_item) { double("LineItem", amount: 100, order: order) } + + before { subject.preferred_percent = 15 } + + it "computes based on item price and quantity" do + expect(subject.compute(line_item)).to eq 15 + end + end + + it_behaves_like 'a calculator with a description' +end From 4745ccea47517f27f9133a95eab19fecbb4d6b8e Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 14 Jun 2023 13:09:00 +0200 Subject: [PATCH 019/524] Add Flat Rate calculator --- .../calculators/flat_rate.rb | 21 ++++++++ .../calculators/flat_rate_spec.rb | 49 +++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/calculators/flat_rate.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/calculators/flat_rate_spec.rb diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/flat_rate.rb b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/flat_rate.rb new file mode 100644 index 00000000000..0b6bab74482 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/flat_rate.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require_dependency 'spree/calculator' + +module SolidusFriendlyPromotions + module Calculators + class FlatRate < Spree::Calculator + preference :amount, :decimal, default: 0 + preference :currency, :string, default: ->{ Spree::Config[:currency] } + + def compute(object = nil) + currency = object.order.currency + if object && preferred_currency.casecmp(currency).zero? + preferred_amount + else + 0 + end + end + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/flat_rate_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/flat_rate_spec.rb new file mode 100644 index 00000000000..d893989e153 --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/flat_rate_spec.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'shared_examples/calculator_shared_examples' + +RSpec.describe SolidusFriendlyPromotions::Calculators::FlatRate, type: :model do + it_behaves_like 'a calculator with a description' + + let(:calculator) do + described_class.new( + preferred_amount: preferred_amount, + preferred_currency: preferred_currency + ) + end + let(:order) { mock_model(Spree::Order, currency: order_currency) } + let(:line_item) { mock_model(Spree::LineItem, order: order) } + + subject { calculator.compute(line_item) } + + context "compute" do + describe "when preferred currency matches order" do + let(:preferred_currency) { "GBP" } + let(:order_currency) { "GBP" } + let(:preferred_amount) { 25 } + it { is_expected.to eq(25.0) } + end + + describe "when preferred currency does not match order" do + let(:preferred_currency) { "GBP" } + let(:order_currency) { "USD" } + let(:preferred_amount) { 25 } + it { is_expected.to be_zero } + end + + describe "when preferred currency does not match order" do + let(:preferred_currency) { "" } + let(:order_currency) { "USD" } + let(:preferred_amount) { 25 } + it { is_expected.to be_zero } + end + + describe "when preferred currency and order currency use different casing" do + let(:preferred_currency) { "gbP" } + let(:order_currency) { "GBP" } + let(:preferred_amount) { 25 } + it { is_expected.to eq(25.0) } + end + end +end From ef36907e7e8f3c227fb8169caf8d66a01b5cd94a Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 14 Jun 2023 14:16:26 +0200 Subject: [PATCH 020/524] Add Distributed Amounts Calculator --- .../calculators/distributed_amount.rb | 38 +++++++++++ .../calculators/distributed_amount_spec.rb | 66 +++++++++++++++++++ 2 files changed, 104 insertions(+) create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/calculators/distributed_amount.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/calculators/distributed_amount_spec.rb diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/distributed_amount.rb b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/distributed_amount.rb new file mode 100644 index 00000000000..5403d548e74 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/distributed_amount.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require_dependency 'spree/calculator' + +# This is a calculator for line item adjustment actions. It accepts a line item +# and calculates its weighted adjustment amount based on the value of the +# preferred amount and the price of the other line items. More expensive line +# items will receive a greater share of the preferred amount. +module SolidusFriendlyPromotions + module Calculators + class DistributedAmount < Spree::Calculator + preference :amount, :decimal, default: 0 + preference :currency, :string, default: -> { Spree::Config[:currency] } + + def compute_line_item(line_item) + return 0 unless line_item + return 0 unless preferred_currency.casecmp(line_item.currency).zero? + distributable_line_items = eligible_line_items(line_item.order) + return 0 unless line_item.in?(distributable_line_items) + Spree::DistributedAmountsHandler.new( + distributable_line_items, + preferred_amount + ).amount(line_item) + end + + private + + def eligible_line_items(order) + order.line_items.reject do |line_item| + SolidusFriendlyPromotions::PromotionEligibility.new( + promotable: line_item, + possible_promotions: [calculable.promotion] + ).call.empty? + end + end + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/distributed_amount_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/distributed_amount_spec.rb new file mode 100644 index 00000000000..3d1eeed0dbd --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/distributed_amount_spec.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'shared_examples/calculator_shared_examples' + +RSpec.describe SolidusFriendlyPromotions::Calculators::DistributedAmount, type: :model do + let(:calculator) { described_class.new(preferred_amount: 15, preferred_currency: currency) } + let!(:promotion) { create :promotion, apply_automatically: true, name: '15 spread', promotion_actions: [action], promotion_rules: rules } + let(:rules) { [] } + let(:action) { SolidusFriendlyPromotions::Actions::AdjustLineItem.create(calculator: calculator) } + let(:order) { create(:order_with_line_items, line_items_attributes: line_items_attributes) } + let(:currency) { "USD" } + + before do + Spree::Config.promotion_adjuster_class = "SolidusFriendlyPromotions::OrderPromotionAdjuster" + Spree::Config.promotion_chooser_class = "SolidusFriendlyPromotions::PromotionAdjustmentChooser" + end + + context 'applied to an order' do + let(:line_items_attributes) { [{ price: 20 }, { price: 30 }, { price: 100 }] } + before do + order.recalculate + end + + it 'correctly distributes the entire discount' do + expect(order.promo_total).to eq(-15) + expect(order.line_items.map(&:adjustment_total)).to eq([-2, -3, -10]) + end + + context 'with product promotion rule' do + let(:first_product) { order.line_items.first.product } + let(:rules) do + [ + SolidusFriendlyPromotions::Rules::LineItemProduct.new(products: [first_product]) + ] + end + + before do + order.recalculate + end + + it 'still distributes the entire discount' do + expect(order.promo_total).to eq(-15) + expect(order.line_items.map(&:adjustment_total)).to eq([-15, 0, 0]) + end + end + end + + describe "#compute_line_item" do + let(:line_items_attributes) { [{ price: 50 }, { price: 50 }, { price: 50 }] } + + subject { calculator.compute_line_item(order.line_items.first) } + + + context "when the order currency matches the store's currency" do + let(:currency) { "USD" } + it { is_expected.to eq 5 } + it { is_expected.to be_a BigDecimal } + end + + context "when the order currency does not match the store's currency" do + let(:currency) { "CAD" } + it { is_expected.to eq 0 } + end + end +end From de1adc26155f728ab37e8091566581eafafd70f5 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 14 Jun 2023 14:22:14 +0200 Subject: [PATCH 021/524] Add DistributedAmountsHandler This class is only used for the DistributedAmountsCalculator which we have also imported. --- .../calculators/distributed_amount.rb | 2 +- .../distributed_amounts_handler.rb | 43 ++++++++++ .../distributed_amounts_handler_spec.rb | 83 +++++++++++++++++++ 3 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/distributed_amounts_handler.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/distributed_amounts_handler_spec.rb diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/distributed_amount.rb b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/distributed_amount.rb index 5403d548e74..5513190d90e 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/distributed_amount.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/distributed_amount.rb @@ -17,7 +17,7 @@ def compute_line_item(line_item) return 0 unless preferred_currency.casecmp(line_item.currency).zero? distributable_line_items = eligible_line_items(line_item.order) return 0 unless line_item.in?(distributable_line_items) - Spree::DistributedAmountsHandler.new( + DistributedAmountsHandler.new( distributable_line_items, preferred_amount ).amount(line_item) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/distributed_amounts_handler.rb b/friendly_promotions/app/models/solidus_friendly_promotions/distributed_amounts_handler.rb new file mode 100644 index 00000000000..1129e91da7c --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/distributed_amounts_handler.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + class DistributedAmountsHandler + attr_reader :line_items, :total_amount + + def initialize(line_items, total_amount) + @line_items = line_items + @total_amount = total_amount + end + + # @param line_item [LineItem] one of the line_items distributed over + # @return [BigDecimal] the weighted adjustment for this line_item + def amount(line_item) + distributed_amounts[line_item.id].to_d + end + + private + + # @private + # @return [Hash] a hash of line item IDs and their + # corresponding weighted adjustments + def distributed_amounts + Hash[line_item_ids.zip(allocated_amounts)] + end + + def line_item_ids + line_items.map(&:id) + end + + def elligible_amounts + line_items.map(&:amount) + end + + def subtotal + elligible_amounts.sum + end + + def allocated_amounts + total_amount.to_money.allocate(elligible_amounts).map(&:to_money) + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/distributed_amounts_handler_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/distributed_amounts_handler_spec.rb new file mode 100644 index 00000000000..4780f71f144 --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/distributed_amounts_handler_spec.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe SolidusFriendlyPromotions::DistributedAmountsHandler, type: :model do + let(:order) do + FactoryBot.create( + :order_with_line_items, + line_items_attributes: line_items_attributes + ) + end + + let(:handler) { + described_class.new(order.line_items, total_amount) + } + + describe "#amount" do + let(:total_amount) { 15 } + + context "when there is only one line item" do + let(:line_items_attributes) { [{ price: 100 }] } + let(:line_item) { order.line_items.first } + + it "applies the entire amount to the line item" do + expect(handler.amount(line_item)).to eq(15) + end + end + + context "when there are multiple line items" do + let(:line_items_attributes) do + [{ price: 50 }, { price: 50 }, { price: 50 }] + end + + context "and the line items are equally priced" do + it "evenly distributes the total amount" do + expect( + [ + handler.amount(order.line_items[0]), + handler.amount(order.line_items[1]), + handler.amount(order.line_items[2]) + ] + ).to eq( + [5, 5, 5] + ) + end + + context "and the total amount cannot be equally distributed" do + let(:total_amount) { 10 } + + it "applies the remainder of the total amount to the last item" do + expect( + [ + handler.amount(order.line_items[0]), + handler.amount(order.line_items[1]), + handler.amount(order.line_items[2]) + ] + ).to match_array( + [3.33, 3.33, 3.34] + ) + end + end + end + + context "and the line items do not have equal subtotal amounts" do + let(:line_items_attributes) do + [{ price: 50, quantity: 3 }, { price: 50, quantity: 1 }, { price: 50, quantity: 2 }] + end + + it "distributes the total amount relative to the item's price" do + expect( + [ + handler.amount(order.line_items[0]), + handler.amount(order.line_items[1]), + handler.amount(order.line_items[2]) + ] + ).to eq( + [7.5, 2.5, 5] + ) + end + end + end + end +end From 16743bd44a87325258cb7ca5be84830b7917727e Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 14 Jun 2023 14:25:13 +0200 Subject: [PATCH 022/524] Make sure Solidus is around when running `bin/rake` --- friendly_promotions/lib/solidus_friendly_promotions.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/friendly_promotions/lib/solidus_friendly_promotions.rb b/friendly_promotions/lib/solidus_friendly_promotions.rb index bf6950e2e15..3d288c4212b 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true +require "spree" require "solidus_friendly_promotions/configuration" require "solidus_friendly_promotions/version" require "solidus_friendly_promotions/engine" From 1a69d7d55e66331916b3060db31cea96d5b3bdef Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 14 Jun 2023 15:18:34 +0200 Subject: [PATCH 023/524] Add model name and description translations --- friendly_promotions/config/locales/en.yml | 68 ++++++++++++++++++- .../actions/adjust_line_item_spec.rb | 13 ++++ .../actions/adjust_shipment_spec.rb | 13 ++++ 3 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_line_item_spec.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_shipment_spec.rb diff --git a/friendly_promotions/config/locales/en.yml b/friendly_promotions/config/locales/en.yml index f341cf450bc..8ffb28a35ab 100644 --- a/friendly_promotions/config/locales/en.yml +++ b/friendly_promotions/config/locales/en.yml @@ -2,4 +2,70 @@ # See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. en: - hello: Hello world + activerecord: + models: + solidus_friendly_promotions/actions/adjust_shipment: Discount matching shipments + solidus_friendly_promotions/actions/adjust_line_item: Discount matching line items + solidus_friendly_promotions/calculators/distributed_amount: Distributed Amount + solidus_friendly_promotions/calculators/percent: Flat Percent + solidus_friendly_promotions/calculators/flat_rate: Flat Rate + solidus_friendly_promotions/calculators/flexi_rate: Flexible Rate + solidus_friendly_promotions/calculators/tiered_flat_rate: Tiered Flat Rate + solidus_friendly_promotions/calculators/tiered_percent: Tiered Percent + solidus_friendly_promotions/rules/first_order: First Order + solidus_friendly_promotions/rules/first_repeat_purchase_since: First Repeat Purchase Since + solidus_friendly_promotions/rules/item_total: Item Total + solidus_friendly_promotions/rules/landing_page: Landing Page + solidus_friendly_promotions/rules/nth_order: Nth Order + solidus_friendly_promotions/rules/one_use_per_user: One Use Per User + solidus_friendly_promotions/rules/option_value: Option Value(s) + solidus_friendly_promotions/rules/line_item_option_value: Line Item Option Value(s) + solidus_friendly_promotions/rules/product: Order Product(s) + solidus_friendly_promotions/rules/line_item_product: Line Item Product(s) + solidus_friendly_promotions/rules/taxon: Order Taxon(s) + solidus_friendly_promotions/rules/line_item_taxon: Line Item Taxon(s) + solidus_friendly_promotions/rules/user: User + solidus_friendly_promotions/rules/user_logged_in: User Logged In + solidus_friendly_promotions/rules/user_role: User Role(s) + attributes: + solidus_friendly_promotions/actions/adjust_line_item: + description: Creates a promotion credit on matching line items + solidus_friendly_promotions/actions/adjust_shipment: + description: Creates a promotion credit on matching shipments + solidus_friendly_promotions/rules/first_order: + description: Must be the customer's first order + solidus_friendly_promotions/rules/first_repeat_purchase_since: + description: Available only to user who have not purchased in a while + form_text: 'Apply this promotion to users whose last order was more than X + days ago: ' + solidus_friendly_promotions/rules/item_total: + description: Order total meets these criteria + solidus_friendly_promotions/rules/landing_page: + description: Customer must have visited the specified page + solidus_friendly_promotions/rules/nth_order: + description: Apply a promotion to every nth order a user has completed. + form_text: 'Apply this promotion on the users Nth order: ' + solidus_friendly_promotions/rules/one_use_per_user: + description: Only one use per user + solidus_friendly_promotions/rules/option_value: + description: Order includes specified product(s) with matching option value(s) + solidus_friendly_promotions/rules/line_item_option_value: + description: Line Item has specified product with matching option value + solidus_friendly_promotions/rules/product: + description: Order includes specified product(s) + solidus_friendly_promotions/rules/line_item_product: + description: Line item matches specified product(s) + preferred_match_policy: Match Policy + solidus_friendly_promotions/rules/store: + description: Available only to the specified stores + solidus_friendly_promotions/rules/taxon: + description: Order includes products with specified taxon(s) + solidus_friendly_promotions/rules/line_item_taxon: + description: Line Item has product with specified taxon(s) + preferred_match_policy: Match Policy + solidus_friendly_promotions/rules/user: + description: Available only to the specified users + solidus_friendly_promotions/rules/user_logged_in: + description: Available only to logged in users + solidus_friendly_promotions/rules/user_role: + description: Order includes User with specified Role(s) diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_line_item_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_line_item_spec.rb new file mode 100644 index 00000000000..45214a5d818 --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_line_item_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SolidusFriendlyPromotions::Actions::AdjustLineItem do + subject(:action) { described_class.new } + + describe "name" do + subject(:name) { action.model_name.human } + + it { is_expected.to eq("Discount matching line items") } + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_shipment_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_shipment_spec.rb new file mode 100644 index 00000000000..5495a3eb512 --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_shipment_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SolidusFriendlyPromotions::Actions::AdjustShipment do + subject(:action) { described_class.new } + + describe "name" do + subject(:name) { action.model_name.human } + + it { is_expected.to eq("Discount matching shipments") } + end +end From 428c2c2ee7e45a353e2f2a53923ab2ecaf5a5045 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 14 Jun 2023 18:10:26 +0200 Subject: [PATCH 024/524] Add FlexRate calculcator --- .../calculators/flexi_rate.rb | 24 +++ .../calculators/flexi_rate_spec.rb | 158 ++++++++++++++++++ 2 files changed, 182 insertions(+) create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/calculators/flexi_rate.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/calculators/flexi_rate_spec.rb diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/flexi_rate.rb b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/flexi_rate.rb new file mode 100644 index 00000000000..2ab4ed53063 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/flexi_rate.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require_dependency 'spree/calculator' + +module SolidusFriendlyPromotions + module Calculators + class FlexiRate < Spree::Calculator + preference :first_item, :decimal, default: 0 + preference :additional_item, :decimal, default: 0 + preference :max_items, :integer, default: 0 + preference :currency, :string, default: ->{ Spree::Config[:currency] } + + def compute(object) + items_count = object.quantity + items_count = [items_count, preferred_max_items].min unless preferred_max_items.zero? + + return BigDecimal(0) if items_count == 0 + + additional_items_count = items_count - 1 + preferred_first_item + preferred_additional_item * additional_items_count + end + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/flexi_rate_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/flexi_rate_spec.rb new file mode 100644 index 00000000000..e1c2056b27d --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/flexi_rate_spec.rb @@ -0,0 +1,158 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'shared_examples/calculator_shared_examples' + +RSpec.describe SolidusFriendlyPromotions::Calculators::FlexiRate, type: :model do + let(:calculator) do + described_class.new( + preferred_first_item: first_item, + preferred_additional_item: additional_item, + preferred_max_items: max_items + ) + end + let(:first_item) { 0 } + let(:additional_item) { 0 } + let(:max_items) { 0 } + + it_behaves_like 'a calculator with a description' + + let(:line_item) do + mock_model( + Spree::LineItem, quantity: quantity + ) + end + + context "compute" do + subject { calculator.compute(line_item) } + context "with all amounts 0" do + context "with quantity 0" do + let(:quantity) { 0 } + it { should eq 0 } + end + + context "with quantity 1" do + let(:quantity) { 1 } + it { should eq 0 } + end + + context "with quantity 2" do + let(:quantity) { 2 } + it { should eq 0 } + end + + context "with quantity 10" do + let(:quantity) { 10 } + it { should eq 0 } + end + end + + context "when first_item has a value" do + let(:first_item) { 1.23 } + + context "with quantity 0" do + let(:quantity) { 0 } + it { should eq 0 } + end + + context "with quantity 1" do + let(:quantity) { 1 } + it { should eq 1.23 } + end + + context "with quantity 2" do + let(:quantity) { 2 } + it { should eq 1.23 } + end + + context "with quantity 10" do + let(:quantity) { 10 } + it { should eq 1.23 } + end + end + + context "when additional_items has a value" do + let(:additional_item) { 1.23 } + + context "with quantity 0" do + let(:quantity) { 0 } + it { should eq 0 } + end + + context "with quantity 1" do + let(:quantity) { 1 } + it { should eq 0 } + end + + context "with quantity 2" do + let(:quantity) { 2 } + it { should eq 1.23 } + end + + context "with quantity 10" do + let(:quantity) { 10 } + it { should eq 11.07 } + end + end + + context "when first_item and additional_items has a value" do + let(:first_item) { 1.13 } + let(:additional_item) { 2.11 } + + context "with quantity 0" do + let(:quantity) { 0 } + it { should eq 0 } + end + + context "with quantity 1" do + let(:quantity) { 1 } + it { should eq 1.13 } + end + + context "with quantity 2" do + let(:quantity) { 2 } + it { should eq 3.24 } + end + + context "with quantity 10" do + let(:quantity) { 10 } + it { should eq 20.12 } + end + + context "with max_items 5" do + let(:max_items) { 5 } + + context "with quantity 0" do + let(:quantity) { 0 } + it { should eq 0 } + end + + context "with quantity 1" do + let(:quantity) { 1 } + it { should eq 1.13 } + end + + context "with quantity 2" do + let(:quantity) { 2 } + it { should eq 3.24 } + end + + context "with quantity 5" do + let(:quantity) { 5 } + it { should eq 9.57 } + end + + context "with quantity 10" do + let(:quantity) { 10 } + it { should eq 9.57 } + end + end + end + end + + it "should allow creation of new object with all the attributes" do + attributes = { preferred_first_item: 1, preferred_additional_item: 1, preferred_max_items: 1 } + calculator = Spree::Calculator::FlexiRate.new(attributes) + expect(calculator).to have_attributes(attributes) + end +end From 5708e14e76119feb50448b9f6fec4407db75f110 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 14 Jun 2023 18:12:20 +0200 Subject: [PATCH 025/524] Add available calculators endpoint on AdjustShipment action --- .../actions/adjust_shipment.rb | 2 ++ .../install/templates/initializer.rb | 10 ++++++++++ .../actions/adjust_shipment_spec.rb | 14 ++++++++++++++ 3 files changed, 26 insertions(+) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb index bcc0a03bdec..da8117b3216 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb @@ -3,6 +3,8 @@ module SolidusFriendlyPromotions module Actions class AdjustShipment < Base + class_attribute :available_calculators, default: [] + def can_adjust?(object) object.is_a? Spree::Shipment end diff --git a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb index 79e8c8e6990..1946eac2042 100644 --- a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb +++ b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb @@ -4,3 +4,13 @@ # TODO: Remember to change this with the actual preferences you have implemented! # config.sample_preference = 'sample_value' end + +Rails.application.config.to_prepare do + SolidusFriendlyPromotions::Actions::AdjustShipment.available_calculators += [ + SolidusFriendlyPromotions::Calculators::FlatRate, + SolidusFriendlyPromotions::Calculators::FlexiRate, + SolidusFriendlyPromotions::Calculators::Percent, + SolidusFriendlyPromotions::Calculators::TieredFlatRate, + SolidusFriendlyPromotions::Calculators::TieredPercent, + ] +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_shipment_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_shipment_spec.rb index 5495a3eb512..b9d9fd9d0c5 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_shipment_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_shipment_spec.rb @@ -10,4 +10,18 @@ it { is_expected.to eq("Discount matching shipments") } end + + describe ".available_calculators" do + subject { described_class.available_calculators } + + it do + is_expected.to contain_exactly( + SolidusFriendlyPromotions::Calculators::FlatRate, + SolidusFriendlyPromotions::Calculators::FlexiRate, + SolidusFriendlyPromotions::Calculators::Percent, + SolidusFriendlyPromotions::Calculators::TieredFlatRate, + SolidusFriendlyPromotions::Calculators::TieredPercent, + ) + end + end end From f68e0400dcc6f3a5f64fa07958bcb641e9e8c742 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 14 Jun 2023 18:14:38 +0200 Subject: [PATCH 026/524] Allow configuring available calculators for line item --- .../actions/adjust_line_item.rb | 2 ++ .../install/templates/initializer.rb | 8 ++++++++ .../actions/adjust_line_item_spec.rb | 15 +++++++++++++++ 3 files changed, 25 insertions(+) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb index 73eaa92b863..e96cb769749 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb @@ -3,6 +3,8 @@ module SolidusFriendlyPromotions module Actions class AdjustLineItem < Base + class_attribute :available_calculators, default: [] + def can_adjust?(object) object.is_a? Spree::LineItem end diff --git a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb index 1946eac2042..4b9e223db55 100644 --- a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb +++ b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb @@ -13,4 +13,12 @@ SolidusFriendlyPromotions::Calculators::TieredFlatRate, SolidusFriendlyPromotions::Calculators::TieredPercent, ] + SolidusFriendlyPromotions::Actions::AdjustLineItem.available_calculators += [ + SolidusFriendlyPromotions::Calculators::DistributedAmount, + SolidusFriendlyPromotions::Calculators::FlatRate, + SolidusFriendlyPromotions::Calculators::FlexiRate, + SolidusFriendlyPromotions::Calculators::Percent, + SolidusFriendlyPromotions::Calculators::TieredFlatRate, + SolidusFriendlyPromotions::Calculators::TieredPercent, + ] end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_line_item_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_line_item_spec.rb index 45214a5d818..6ea291929df 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_line_item_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_line_item_spec.rb @@ -10,4 +10,19 @@ it { is_expected.to eq("Discount matching line items") } end + + describe ".available_calculators" do + subject { described_class.available_calculators } + + it do + is_expected.to contain_exactly( + SolidusFriendlyPromotions::Calculators::DistributedAmount, + SolidusFriendlyPromotions::Calculators::FlatRate, + SolidusFriendlyPromotions::Calculators::FlexiRate, + SolidusFriendlyPromotions::Calculators::Percent, + SolidusFriendlyPromotions::Calculators::TieredFlatRate, + SolidusFriendlyPromotions::Calculators::TieredPercent, + ) + end + end end From 9d65f405f7796d2bceab4f3f68fc397cbee205e9 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 15 Jun 2023 10:33:40 +0200 Subject: [PATCH 027/524] Remove frontend stuff from generator We don't support the old frontend. --- .../solidus_friendly_promotions/install/install_generator.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/install_generator.rb b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/install_generator.rb index cf402d0bd68..1e2de6828d5 100644 --- a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/install_generator.rb +++ b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/install_generator.rb @@ -15,12 +15,10 @@ def copy_initializer end def add_javascripts - append_file "vendor/assets/javascripts/spree/frontend/all.js", "//= require spree/frontend/solidus_friendly_promotions\n" append_file "vendor/assets/javascripts/spree/backend/all.js", "//= require spree/backend/solidus_friendly_promotions\n" end def add_stylesheets - inject_into_file "vendor/assets/stylesheets/spree/frontend/all.css", " *= require spree/frontend/solidus_friendly_promotions\n", before: %r{\*/}, verbose: true # rubocop:disable Layout/LineLength inject_into_file "vendor/assets/stylesheets/spree/backend/all.css", " *= require spree/backend/solidus_friendly_promotions\n", before: %r{\*/}, verbose: true # rubocop:disable Layout/LineLength end From e7c63950d896d75f30940c8f0c7987acc732e7c8 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 15 Jun 2023 10:41:52 +0200 Subject: [PATCH 028/524] Fix sandbox command --- friendly_promotions/bin/sandbox | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/friendly_promotions/bin/sandbox b/friendly_promotions/bin/sandbox index bf577b65c23..6fc4884c492 100755 --- a/friendly_promotions/bin/sandbox +++ b/friendly_promotions/bin/sandbox @@ -8,7 +8,7 @@ test "$DB" = "sqlite" && export DB="sqlite3" if [ -z "$SOLIDUS_BRANCH" ] then echo "~~> Use 'export SOLIDUS_BRANCH=[master|v3.2|...]' to control the Solidus branch" - SOLIDUS_BRANCH="master" + SOLIDUS_BRANCH="main" fi echo "~~> Using branch $SOLIDUS_BRANCH of solidus" @@ -47,6 +47,7 @@ cat <> Gemfile gem 'solidus', github: 'solidusio/solidus', branch: '$SOLIDUS_BRANCH' gem 'rails-i18n' gem 'solidus_i18n' +gem 'solidus_auth_devise' gem '$extension_name', path: '..' From d2ceaeafd276b4d5f01322b0d18602821cf74abf Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 15 Jun 2023 13:59:36 +0200 Subject: [PATCH 029/524] Move configuration for available calculators to SFP.config --- .../actions/adjust_line_item.rb | 2 -- .../actions/adjust_shipment.rb | 2 -- .../install/templates/initializer.rb | 29 +++++++++---------- .../configuration.rb | 7 +++++ .../configuration_spec.rb | 14 ++++++++- .../actions/adjust_line_item_spec.rb | 15 ---------- .../actions/adjust_shipment_spec.rb | 14 --------- 7 files changed, 33 insertions(+), 50 deletions(-) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb index e96cb769749..73eaa92b863 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb @@ -3,8 +3,6 @@ module SolidusFriendlyPromotions module Actions class AdjustLineItem < Base - class_attribute :available_calculators, default: [] - def can_adjust?(object) object.is_a? Spree::LineItem end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb index da8117b3216..bcc0a03bdec 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb @@ -3,8 +3,6 @@ module SolidusFriendlyPromotions module Actions class AdjustShipment < Base - class_attribute :available_calculators, default: [] - def can_adjust?(object) object.is_a? Spree::Shipment end diff --git a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb index 4b9e223db55..ca957c682aa 100644 --- a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb +++ b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb @@ -3,22 +3,19 @@ SolidusFriendlyPromotions.configure do |config| # TODO: Remember to change this with the actual preferences you have implemented! # config.sample_preference = 'sample_value' -end - -Rails.application.config.to_prepare do - SolidusFriendlyPromotions::Actions::AdjustShipment.available_calculators += [ - SolidusFriendlyPromotions::Calculators::FlatRate, - SolidusFriendlyPromotions::Calculators::FlexiRate, - SolidusFriendlyPromotions::Calculators::Percent, - SolidusFriendlyPromotions::Calculators::TieredFlatRate, - SolidusFriendlyPromotions::Calculators::TieredPercent, + config.shipment_discount_calculators = [ + "SolidusFriendlyPromotions::Calculators::FlatRate", + "SolidusFriendlyPromotions::Calculators::FlexiRate", + "SolidusFriendlyPromotions::Calculators::Percent", + "SolidusFriendlyPromotions::Calculators::TieredFlatRate", + "SolidusFriendlyPromotions::Calculators::TieredPercent", ] - SolidusFriendlyPromotions::Actions::AdjustLineItem.available_calculators += [ - SolidusFriendlyPromotions::Calculators::DistributedAmount, - SolidusFriendlyPromotions::Calculators::FlatRate, - SolidusFriendlyPromotions::Calculators::FlexiRate, - SolidusFriendlyPromotions::Calculators::Percent, - SolidusFriendlyPromotions::Calculators::TieredFlatRate, - SolidusFriendlyPromotions::Calculators::TieredPercent, + config.line_item_discount_calculators = [ + "SolidusFriendlyPromotions::Calculators::DistributedAmount", + "SolidusFriendlyPromotions::Calculators::FlatRate", + "SolidusFriendlyPromotions::Calculators::FlexiRate", + "SolidusFriendlyPromotions::Calculators::Percent", + "SolidusFriendlyPromotions::Calculators::TieredFlatRate", + "SolidusFriendlyPromotions::Calculators::TieredPercent", ] end diff --git a/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb b/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb index 2813c447f15..45e22bbd073 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb @@ -1,7 +1,14 @@ # frozen_string_literal: true +require 'spree/core/environment_extension' + module SolidusFriendlyPromotions class Configuration < Spree::Preferences::Configuration + include Spree::Core::EnvironmentExtension + + add_class_set :line_item_discount_calculators + add_class_set :shipment_discount_calculators + class_name_attribute :promotion_chooser_class, default: "SolidusFriendlyPromotions::PromotionAdjustmentChooser" end diff --git a/friendly_promotions/spec/lib/solidus_friendly_promotions/configuration_spec.rb b/friendly_promotions/spec/lib/solidus_friendly_promotions/configuration_spec.rb index 92df9f120e3..cd1155944a0 100644 --- a/friendly_promotions/spec/lib/solidus_friendly_promotions/configuration_spec.rb +++ b/friendly_promotions/spec/lib/solidus_friendly_promotions/configuration_spec.rb @@ -3,7 +3,7 @@ require "spec_helper" RSpec.describe SolidusFriendlyPromotions::Configuration do - subject { SolidusFriendlyPromotions.config } + subject(:config) { SolidusFriendlyPromotions.config } it "has a nice accessor" do expect(subject).to be_a(described_class) @@ -18,4 +18,16 @@ expect(subject.promotion_chooser_class).to eq(SolidusFriendlyPromotions::PromotionAdjustmentChooser) end end + + describe ".shipment_discount_calculators" do + subject { config.shipment_discount_calculators } + + it { is_expected.to be_a(Spree::Core::ClassConstantizer::Set) } + end + + describe ".line_item_discount_calculators" do + subject { config.line_item_discount_calculators } + + it { is_expected.to be_a(Spree::Core::ClassConstantizer::Set) } + end end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_line_item_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_line_item_spec.rb index 6ea291929df..45214a5d818 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_line_item_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_line_item_spec.rb @@ -10,19 +10,4 @@ it { is_expected.to eq("Discount matching line items") } end - - describe ".available_calculators" do - subject { described_class.available_calculators } - - it do - is_expected.to contain_exactly( - SolidusFriendlyPromotions::Calculators::DistributedAmount, - SolidusFriendlyPromotions::Calculators::FlatRate, - SolidusFriendlyPromotions::Calculators::FlexiRate, - SolidusFriendlyPromotions::Calculators::Percent, - SolidusFriendlyPromotions::Calculators::TieredFlatRate, - SolidusFriendlyPromotions::Calculators::TieredPercent, - ) - end - end end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_shipment_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_shipment_spec.rb index b9d9fd9d0c5..5495a3eb512 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_shipment_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_shipment_spec.rb @@ -10,18 +10,4 @@ it { is_expected.to eq("Discount matching shipments") } end - - describe ".available_calculators" do - subject { described_class.available_calculators } - - it do - is_expected.to contain_exactly( - SolidusFriendlyPromotions::Calculators::FlatRate, - SolidusFriendlyPromotions::Calculators::FlexiRate, - SolidusFriendlyPromotions::Calculators::Percent, - SolidusFriendlyPromotions::Calculators::TieredFlatRate, - SolidusFriendlyPromotions::Calculators::TieredPercent, - ) - end - end end From c7bb2818afc82d4edd8a114929e9d7155b56b7ff Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 15 Jun 2023 14:01:57 +0200 Subject: [PATCH 030/524] Add line item, shipment, and order rules to configuration --- .../configuration.rb | 4 ++++ .../configuration_spec.rb | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb b/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb index 45e22bbd073..21fe9c8f71d 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb @@ -9,6 +9,10 @@ class Configuration < Spree::Preferences::Configuration add_class_set :line_item_discount_calculators add_class_set :shipment_discount_calculators + add_class_set :order_rules + add_class_set :line_item_rules + add_class_set :shipment_rules + class_name_attribute :promotion_chooser_class, default: "SolidusFriendlyPromotions::PromotionAdjustmentChooser" end diff --git a/friendly_promotions/spec/lib/solidus_friendly_promotions/configuration_spec.rb b/friendly_promotions/spec/lib/solidus_friendly_promotions/configuration_spec.rb index cd1155944a0..3a14b9c824c 100644 --- a/friendly_promotions/spec/lib/solidus_friendly_promotions/configuration_spec.rb +++ b/friendly_promotions/spec/lib/solidus_friendly_promotions/configuration_spec.rb @@ -30,4 +30,22 @@ it { is_expected.to be_a(Spree::Core::ClassConstantizer::Set) } end + + describe ".order_rules" do + subject { config.order_rules } + + it { is_expected.to be_a(Spree::Core::ClassConstantizer::Set) } + end + + describe ".line_item_rules" do + subject { config.line_item_rules } + + it { is_expected.to be_a(Spree::Core::ClassConstantizer::Set) } + end + + describe ".shipment_rules" do + subject { config.line_item_rules } + + it { is_expected.to be_a(Spree::Core::ClassConstantizer::Set) } + end end From e75c0408fea2887c4125be668ed41f74a851f8f1 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 15 Jun 2023 14:07:16 +0200 Subject: [PATCH 031/524] Add Rules sets to initializer --- .../install/templates/initializer.rb | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb index ca957c682aa..fa9924c9270 100644 --- a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb +++ b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb @@ -18,4 +18,25 @@ "SolidusFriendlyPromotions::Calculators::TieredFlatRate", "SolidusFriendlyPromotions::Calculators::TieredPercent", ] + + config.order_rules = [ + "SolidusFriendlyPromotions::Rules::FirstOrder", + "SolidusFriendlyPromotions::Rules::FirstRepeatPurchaseSince", + "SolidusFriendlyPromotions::Rules::ItemTotal", + "SolidusFriendlyPromotions::Rules::NthOrder", + "SolidusFriendlyPromotions::Rules::OneUsePerUser", + "SolidusFriendlyPromotions::Rules::OptionValue", + "SolidusFriendlyPromotions::Rules::Product", + "SolidusFriendlyPromotions::Rules::Store", + "SolidusFriendlyPromotions::Rules::Taxon", + "SolidusFriendlyPromotions::Rules::UserLoggedIn", + "SolidusFriendlyPromotions::Rules::UserRole", + "SolidusFriendlyPromotions::Rules::User", + ] + config.line_item_rules = [ + "SolidusFriendlyPromotions::Rules::LineItemOptionValue", + "SolidusFriendlyPromotions::Rules::LineItemProduct", + "SolidusFriendlyPromotions::Rules::LineItemTaxon", + ] + config.shipment_rules = [] end From 8217f81854ce86f7bed18321b3e3c975589abdd9 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 15 Jun 2023 17:35:59 +0200 Subject: [PATCH 032/524] No-op admin promotions controller This just adds a controller which we can fill with functionality from Solidus core. --- .../admin/promotions_controller.rb | 10 ++ .../admin/promotions/index.html.erb | 124 ++++++++++++++++++ friendly_promotions/config/routes.rb | 6 +- .../install/install_generator.rb | 7 + .../lib/solidus_friendly_promotions/engine.rb | 2 +- .../admin/promotions_spec.rb | 14 ++ friendly_promotions/spec/spec_helper.rb | 2 + 7 files changed, 162 insertions(+), 3 deletions(-) create mode 100644 friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/index.html.erb create mode 100644 friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotions_spec.rb diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb new file mode 100644 index 00000000000..2596c80457a --- /dev/null +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module Admin + class PromotionsController < ::Spree::Admin::ResourceController + def index + end + end + end +end diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/index.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/index.html.erb new file mode 100644 index 00000000000..4c35e9d766c --- /dev/null +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/index.html.erb @@ -0,0 +1,124 @@ +<% admin_breadcrumb(plural_resource_name(Spree::Promotion)) %> + +<% content_for :page_actions do %> + <% if can? :create, Spree::Promotion %> +
  • + <%= link_to t('spree.new_promotion'), spree.new_admin_promotion_path, class: 'btn btn-primary' %> +
  • + <% end %> +<% end %> + +<% content_for :table_filter_title do %> + <%= t('spree.search') %> +<% end %> + +<% content_for :table_filter do %> +
    + <%= search_form_for [:admin, @search] do |f| %> +
    +
    +
    + <%= label_tag :q_name_cont, Spree::Promotion.human_attribute_name(:name) %> + <%= f.text_field :name_cont, tabindex: 1 %> +
    +
    + +
    +
    + <%= label_tag :q_codes_value_cont, Spree::Promotion.human_attribute_name(:code) %> + <%= f.text_field :codes_value_cont, tabindex: 1 %> +
    +
    + +
    +
    + <%= label_tag :q_path_cont, Spree::Promotion.human_attribute_name(:path) %> + <%= f.text_field :path_cont, tabindex: 1 %> +
    +
    + +
    +
    + <%= label_tag :q_promotion_category_id_eq, Spree::PromotionCategory.model_name.human %>
    + <%= f.collection_select(:promotion_category_id_eq, @promotion_categories, :id, :name, { include_blank: t('spree.match_choices.all') }, { class: 'custom-select fullwidth' }) %> +
    +
    + +
    +
    + <%= label_tag :active, t('spree.active') %>
    + <%= f.check_box :active, label: false, as: :boolean, checked_value: true %> +
    +
    +
    + +
    + +
    +
    + <%= button_tag t('spree.filter_results'), class: 'btn btn-primary' %> +
    +
    + <% end %> +
    +<% end %> + +<%= paginate @promotions, theme: "solidus_admin" %> + +<% if @promotions.length > 0 %> + + + + + + + + + + + + + + + <% @promotions.each do |promotion| %> + + + + + + + + + + + <% end %> + +
    <%= Spree::Promotion.human_attribute_name(:name) %><%= Spree::Promotion.human_attribute_name(:code) %><%= Spree::Promotion.human_attribute_name(:status) %><%= Spree::Promotion.human_attribute_name(:usage_limit) %><%= Spree::Promotion.human_attribute_name(:uses) %><%= Spree::Promotion.human_attribute_name(:starts_at) %><%= Spree::Promotion.human_attribute_name(:expires_at) %>
    <%= promotion.name %> + <%= (promotion.codes.size == 1) ? promotion.codes.pluck(:value).first : t('spree.number_of_codes', count: promotion.codes.size) %> + + + <%= t(admin_promotion_status(promotion), scope: 'spree.admin.promotion_status') %> + + + <%= promotion.usage_limit.nil? ? "โˆž" : promotion.usage_limit %> + + <%= promotion.usage_count %> + + <%= promotion.starts_at.to_fs(:long) if promotion.starts_at %> + + <%= promotion.expires_at.to_fs(:long) if promotion.expires_at %> + + <% if can?(:edit, promotion) %> + <%= link_to_edit promotion, no_text: true %> + <% end %> + <% if can?(:destroy, promotion) %> + <%= link_to_delete promotion, no_text: true %> + <% end %> +
    +<% else %> +
    + <%= render 'spree/admin/shared/no_objects_found', + resource: Spree::Promotion, + new_resource_url: new_object_url %> +
    +<% end %> diff --git a/friendly_promotions/config/routes.rb b/friendly_promotions/config/routes.rb index 59d02443fbd..ded5869ba85 100644 --- a/friendly_promotions/config/routes.rb +++ b/friendly_promotions/config/routes.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true -Spree::Core::Engine.routes.draw do - # Add your extension routes here +SolidusFriendlyPromotions::Engine.routes.draw do + namespace :admin do + resources :promotions, only: :index + end end diff --git a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/install_generator.rb b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/install_generator.rb index 1e2de6828d5..3c8d47d459a 100644 --- a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/install_generator.rb +++ b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/install_generator.rb @@ -26,6 +26,13 @@ def add_migrations run "bin/rails railties:install:migrations FROM=solidus_friendly_promotions" end + def mount_engine + inject_into_file "config/routes.rb", + " mount SolidusFriendlyPromotions::Engine => '/'\n", + before: %r{ mount Spree::Core::Engine.*}, + verbose: true + end + def run_migrations run_migrations = options[:auto_run_migrations] || ["", "y", "Y"].include?(ask("Would you like to run the migrations now? [Y/n]")) # rubocop:disable Layout/LineLength if run_migrations diff --git a/friendly_promotions/lib/solidus_friendly_promotions/engine.rb b/friendly_promotions/lib/solidus_friendly_promotions/engine.rb index 47a740fd133..b4fef4a447a 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions/engine.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions/engine.rb @@ -7,7 +7,7 @@ module SolidusFriendlyPromotions class Engine < Rails::Engine include SolidusSupport::EngineExtensions - isolate_namespace ::Spree + isolate_namespace ::SolidusFriendlyPromotions engine_name "solidus_friendly_promotions" diff --git a/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotions_spec.rb b/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotions_spec.rb new file mode 100644 index 00000000000..9c98a0df6e4 --- /dev/null +++ b/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotions_spec.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe "Admin::Promotions", type: :request do + describe "GET /index" do + stub_authorization! + + it "is successful" do + get admin_promotions_path + expect(response).to be_successful + end + end +end diff --git a/friendly_promotions/spec/spec_helper.rb b/friendly_promotions/spec/spec_helper.rb index d17e8ccd2b4..bd47c3aa520 100644 --- a/friendly_promotions/spec/spec_helper.rb +++ b/friendly_promotions/spec/spec_helper.rb @@ -33,6 +33,8 @@ config.extend Spree::TestingSupport::AuthorizationHelpers::Request, type: :system end + config.include SolidusFriendlyPromotions::Engine.routes.url_helpers, type: :request + config.before do Spree::Config.order_contents_class = "SolidusFriendlyPromotions::SimpleOrderContents" end From 35ca8897fc35cb9b2821e36b042cbc0e2126e732 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Mon, 19 Jun 2023 14:48:54 +0200 Subject: [PATCH 033/524] Fix sandbox generation --- friendly_promotions/bin/sandbox | 38 +++++++++++++++------------------ 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/friendly_promotions/bin/sandbox b/friendly_promotions/bin/sandbox index 6fc4884c492..9266d651af3 100755 --- a/friendly_promotions/bin/sandbox +++ b/friendly_promotions/bin/sandbox @@ -5,49 +5,50 @@ test -z "${DEBUG+empty_string}" || set -x test "$DB" = "sqlite" && export DB="sqlite3" -if [ -z "$SOLIDUS_BRANCH" ] +if [ -z "$PAYMENT_METHOD" ] then - echo "~~> Use 'export SOLIDUS_BRANCH=[master|v3.2|...]' to control the Solidus branch" - SOLIDUS_BRANCH="main" + PAYMENT_METHOD="none" fi -echo "~~> Using branch $SOLIDUS_BRANCH of solidus" -if [ -z "$SOLIDUS_FRONTEND" ] +if [ -z "$SOLIDUS_BRANCH" ] then - echo "~~> Use 'export SOLIDUS_FRONTEND=[solidus_frontend|solidus_starter_frontend]' to control the Solidus frontend" - SOLIDUS_FRONTEND="solidus_frontend" + echo "~~> Use 'export SOLIDUS_BRANCH=[main|v4.0|...]' to control the Solidus branch" + SOLIDUS_BRANCH="main" fi -echo "~~> Using branch $SOLIDUS_FRONTEND as the solidus frontend" +echo "~~> Using branch $SOLIDUS_BRANCH of solidus" extension_name="solidus_friendly_promotions" # Stay away from the bundler env of the containing extension. function unbundled { - ruby -rbundler -e'b = proc {system *ARGV}; Bundler.respond_to?(:with_unbundled_env) ? Bundler.with_unbundled_env(&b) : Bundler.with_clean_env(&b)' -- $@ + ruby -rbundler -e' + Bundler.with_unbundled_env {system *ARGV}' -- \ + env BUNDLE_SUPPRESS_INSTALL_USING_MESSAGES=true $@ } +echo "~~~> Removing the old sandbox" rm -rf ./sandbox -unbundled bundle exec rails new sandbox \ + +echo "~~~> Creating a pristine Rails app" +rails new sandbox \ --database="${DB:-sqlite3}" \ - --skip-bundle \ --skip-git \ --skip-keeps \ --skip-rc \ - --skip-spring \ - --skip-test \ - --skip-javascript + --skip-bootsnap \ + --skip-test if [ ! -d "sandbox" ]; then echo 'sandbox rails application failed' exit 1 fi +echo "~~~> Adding solidus (with i18n and authentication) and your gem to the Gemfile" cd ./sandbox cat <> Gemfile gem 'solidus', github: 'solidusio/solidus', branch: '$SOLIDUS_BRANCH' gem 'rails-i18n' gem 'solidus_i18n' -gem 'solidus_auth_devise' gem '$extension_name', path: '..' @@ -62,13 +63,8 @@ unbundled bundle install --gemfile Gemfile unbundled bundle exec rake db:drop db:create -unbundled bundle exec rails generate solidus:install \ +unbundled bin/rails generate solidus:install \ --auto-accept \ - --user_class=Spree::User \ - --enforce_available_locales=true \ - --with-authentication=true \ - --payment-method=none \ - --frontend=${SOLIDUS_FRONTEND} \ $@ unbundled bundle exec rails generate solidus:auth:install --auto-run-migrations From c7620996a8ec23881223b01aff7b70303dd7fc97 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Mon, 19 Jun 2023 17:25:30 +0200 Subject: [PATCH 034/524] Import index action from Solidus With feature specs. --- .../admin/promotions_controller.rb | 27 +++++++- .../solidus_friendly_promotions.rb | 1 - .../install/templates/initializer.rb | 15 +++++ friendly_promotions/spec/spec_helper.rb | 2 +- .../admin/promotions_spec.rb | 61 +++++++++++++++++++ 5 files changed, 103 insertions(+), 3 deletions(-) create mode 100644 friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotions_spec.rb diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb index 2596c80457a..7a29b5f946c 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb @@ -3,7 +3,32 @@ module SolidusFriendlyPromotions module Admin class PromotionsController < ::Spree::Admin::ResourceController - def index + before_action :load_data + + private + + def collection + return @collection if @collection + params[:q] ||= HashWithIndifferentAccess.new + params[:q][:s] ||= 'id desc' + + @collection = super + @search = @collection.ransack(params[:q]) + @collection = @search.result(distinct: true). + includes(promotion_includes). + page(params[:page]). + per(params[:per_page] || Spree::Config[:promotions_per_page]) + + @collection + end + + def promotion_includes + [:promotion_actions] + end + + def load_data + @calculators = Rails.application.config.spree.calculators.promotion_actions_create_adjustments + @promotion_categories = Spree::PromotionCategory.order(:name) end end end diff --git a/friendly_promotions/config/initializers/solidus_friendly_promotions.rb b/friendly_promotions/config/initializers/solidus_friendly_promotions.rb index 472576bf09f..e69de29bb2d 100644 --- a/friendly_promotions/config/initializers/solidus_friendly_promotions.rb +++ b/friendly_promotions/config/initializers/solidus_friendly_promotions.rb @@ -1 +0,0 @@ -Spree::Backend::Config.menu_items.delete_if { |menu_item| menu_item.url == :admin_promotions_path } diff --git a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb index fa9924c9270..743e89ef52d 100644 --- a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb +++ b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb @@ -1,5 +1,20 @@ # frozen_string_literal: true +# Replace the promotions menu from core with ours +Spree::Backend::Config.configure do |config| + config.menu_items = Spree::Backend::Config.menu_items.map do |item| + next item unless item.url == :admin_promotions_path + Spree::BackendConfiguration::MenuItem.new( + [:promotions, :promotion_categories], + 'bullhorn', + partial: 'spree/admin/shared/promotion_sub_menu', + condition: -> { can?(:admin, Spree::Promotion) }, + url: -> { SolidusFriendlyPromotions::Engine.routes.url_helpers.admin_promotions_path }, + position: 2 + ) + end +end + SolidusFriendlyPromotions.configure do |config| # TODO: Remember to change this with the actual preferences you have implemented! # config.sample_preference = 'sample_value' diff --git a/friendly_promotions/spec/spec_helper.rb b/friendly_promotions/spec/spec_helper.rb index bd47c3aa520..36851bd3aa9 100644 --- a/friendly_promotions/spec/spec_helper.rb +++ b/friendly_promotions/spec/spec_helper.rb @@ -27,7 +27,7 @@ RSpec.configure do |config| config.infer_spec_type_from_file_location! - config.use_transactional_fixtures = false + config.use_transactional_fixtures = true if Spree.solidus_gem_version < Gem::Version.new("2.11") config.extend Spree::TestingSupport::AuthorizationHelpers::Request, type: :system diff --git a/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotions_spec.rb b/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotions_spec.rb new file mode 100644 index 00000000000..00af683f4d6 --- /dev/null +++ b/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotions_spec.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe "Promotions admin", type: :system do + stub_authorization! + + describe "#index" do + let!(:promotion1) { create(:promotion, :with_action, name: "name1", code: "code1", path: "path1") } + let!(:promotion2) { create(:promotion, :with_action, name: "name2", code: "code2", path: "path2") } + let!(:promotion3) do + create(:promotion, :with_action, name: "name3", code: "code3", path: "path3", expires_at: Date.yesterday) + end + let!(:category) { create :promotion_category } + + it "succeeds" do + visit solidus_friendly_promotions.admin_promotions_path + [promotion3, promotion2, promotion1].map(&:name).each do |promotion_name| + expect(page).to have_content promotion_name + end + end + + it "shows promotion categories" do + visit solidus_friendly_promotions.admin_promotions_path + expect(page).to have_select(Spree::PromotionCategory.model_name.human, options: ["All", category.name]) + end + + context "search" do + it "pages results" do + visit solidus_friendly_promotions.admin_promotions_path(per_page: '1') + expect(page).to have_content(promotion3.name) + expect(page).not_to have_content(promotion1.name) + end + + it "filters by name" do + visit solidus_friendly_promotions.admin_promotions_path(q: { name_cont: promotion1.name }) + expect(page).to have_content(promotion1.name) + expect(page).not_to have_content(promotion2.name) + end + + it "filters by code" do + visit solidus_friendly_promotions.admin_promotions_path(q: { codes_value_cont: promotion1.codes.first.value }) + expect(page).to have_content(promotion1.name) + expect(page).not_to have_content(promotion2.name) + end + + it "filters by path" do + visit solidus_friendly_promotions.admin_promotions_path(q: { path_cont: promotion1.path }) + expect(page).to have_content(promotion1.name) + expect(page).not_to have_content(promotion2.name) + end + + it "filters by active" do + visit solidus_friendly_promotions.admin_promotions_path(q: { active: true }) + expect(page).to have_content(promotion1.name) + expect(page).to have_content(promotion2.name) + expect(page).not_to have_content(promotion3.name) + end + end + end +end From 1827b22e12ce2def12f9d8e332b25aa1731071fd Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 20 Jun 2023 10:50:32 +0200 Subject: [PATCH 035/524] Add promotion form This also imports a bunch of translations in order to keep the interaction with Solidus down to just things that are related to the resources controller. --- .../promotions/_activations_edit.html.erb | 22 +++++ .../promotions/_activations_new.html.erb | 43 ++++++++++ .../admin/promotions/_form.html.erb | 83 +++++++++++++++++++ .../admin/promotions/new.html.erb | 9 ++ friendly_promotions/config/locales/en.yml | 20 +++++ friendly_promotions/config/routes.rb | 2 +- .../admin/promotions_spec.rb | 17 ++++ 7 files changed, 195 insertions(+), 1 deletion(-) create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_activations_edit.html.erb create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_activations_new.html.erb create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_form.html.erb create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/new.html.erb diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_activations_edit.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_activations_edit.html.erb new file mode 100644 index 00000000000..8f82fe4accf --- /dev/null +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_activations_edit.html.erb @@ -0,0 +1,22 @@ +<% if @promotion.apply_automatically? %> +

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

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

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

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

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

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

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

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

    + +

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

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

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

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

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

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

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

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

    - <%= form_tag solidus_friendly_promotions.admin_promotion_promotion_rules_path(@promotion), data: { turbo: false } do %> + <%= form_with model: @promotion_rule, scope: "promotion_rule", url: solidus_friendly_promotions.admin_promotion_promotion_rules_path(@promotion), data: { turbo: false } do |form| %> <%= hidden_field_tag :level, @level %> <%= hidden_field_tag "promotion_rule[type]", @promotion_rule.class.name %> - <%= render partial: @promotion_rule.to_partial_path, locals: { promotion_rule: @promotion_rule, param_prefix: "promotion_rule" } %> + <%= render @promotion_rule, promotion_rule: @promotion_rule, param_prefix: "promotion_rule", form: form %>
    <%= button_tag "Add", class: "btn btn-secondary float-right", data: { "turbo-frame" => "_top" } %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_option_value.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_option_value.html.erb index 99ca90d34af..49d25160eca 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_option_value.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_option_value.html.erb @@ -5,8 +5,17 @@
    <%= label_tag nil, plural_resource_name(Spree::OptionValue) %>
    - <%= content_tag :div, nil, class: "js-promo-rule-option-values", - data: { :'original-option-values' => promotion_rule.preferred_eligible_values } %> - - +
    +
    + + <% form.object.preferred_eligible_values.each do |product_option_values| %> + <%= render "solidus_friendly_promotions/admin/promotions/rules/line_item_option_value/option_value_fields", product_option_values: product_option_values, form: form %> + <% end %> +
    + <%= link_to "Add Row", "#", class: "btn btn-outline-primary", data: { action: "click->product-option-values#add_row" } %> +
    +
    +
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/line_item_option_value/_option_value_fields.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/line_item_option_value/_option_value_fields.html.erb new file mode 100644 index 00000000000..26d117035a5 --- /dev/null +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/line_item_option_value/_option_value_fields.html.erb @@ -0,0 +1,17 @@ +
    +
    +
    + +
    +
    + "> +
    +
    + +
    +
    From 7786cd21f5a5fcb99c53fd8eeb21f42dd1ab2533 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Mon, 3 Jul 2023 10:42:42 +0200 Subject: [PATCH 066/524] Add level to promotion rules controller We need to know which rules to show --- .../admin/promotion_actions_controller.rb | 11 +++++++++++ .../admin/promotion_actions/new.html.erb | 2 +- .../admin/promotions/edit.html.erb | 2 +- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb index 083334d4bd4..7a2603281b8 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb @@ -4,6 +4,7 @@ module SolidusFriendlyPromotions module Admin class PromotionActionsController < Spree::Admin::ResourceController before_action :load_promotion + before_action :validate_level, only: :new before_action :validate_promotion_action_type, only: [:create] helper 'solidus_friendly_promotions/admin/promotion_actions' @@ -68,6 +69,16 @@ def load_promotion @promotion = Spree::Promotion.find(params[:promotion_id]) end + def validate_level + requested_level = params[:level].to_s + if requested_level.in?(["line_item", "shipment"]) + @level = requested_level + else + @level = "line_item" + flash.now[:error] = t(:invalid_promotion_rule_level, scope: :solidus_friendly_promotions) + end + end + def validate_promotion_action_type requested_type = params[:promotion_action].delete(:type) promotion_action_types = SolidusFriendlyPromotions.config.actions diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/new.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/new.html.erb index f7ea1bd4cdb..b95fe080550 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/new.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/new.html.erb @@ -1,4 +1,4 @@ -<%= turbo_frame_tag @promotion, "new_promotion_action" do %> +<%= turbo_frame_tag @promotion, "new_#{@level}_promotion_action" do %>
    <%= t(:add_action, scope: :solidus_friendly_promotions) %>
    <%= link_to_with_icon 'trash', '', spree.edit_admin_promotion_path(@promotion), class: 'delete' %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/edit.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/edit.html.erb index f40de46bebb..97ed6162bf6 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/edit.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/edit.html.erb @@ -48,7 +48,7 @@ <%= render partial: 'solidus_friendly_promotions/admin/promotion_actions/promotion_action', collection: promotion_actions_by_level(@promotion, level), locals: {} %> <%= turbo_frame_tag @promotion, "new_#{level}_promotion_action" do %> - <%= link_to t(:add_action, scope: :solidus_friendly_promotions), solidus_friendly_promotions.new_admin_promotion_promotion_action_path(@promotion), class: 'btn btn-secondary' %> + <%= link_to t(:add_action, scope: :solidus_friendly_promotions), solidus_friendly_promotions.new_admin_promotion_promotion_action_path(@promotion, level: level), class: 'btn btn-secondary' %> <% end %>
    From 6eddd87f10b4871151c4535b2cdb7379f52baf67 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Mon, 3 Jul 2023 10:50:36 +0200 Subject: [PATCH 067/524] Import JQuery option value picker This isn't used anywhere else in Solidus, so let's import it. --- .../solidus_friendly_promotions/manifest.js | 1 + .../javascript/solidus_friendly_promotions.js | 1 + .../jquery/option_value_picker.js | 44 +++++++++++++++++++ friendly_promotions/config/importmap.rb | 1 + 4 files changed, 47 insertions(+) create mode 100644 friendly_promotions/app/javascript/solidus_friendly_promotions/jquery/option_value_picker.js diff --git a/friendly_promotions/app/assets/config/solidus_friendly_promotions/manifest.js b/friendly_promotions/app/assets/config/solidus_friendly_promotions/manifest.js index 46a78bb3b35..cffce0f1866 100644 --- a/friendly_promotions/app/assets/config/solidus_friendly_promotions/manifest.js +++ b/friendly_promotions/app/assets/config/solidus_friendly_promotions/manifest.js @@ -4,3 +4,4 @@ //= link solidus_friendly_promotions/controllers/calculator_tiers_controller.js //= link solidus_friendly_promotions/controllers/flash_controller.js //= link solidus_friendly_promotions/controllers/product_option_values_controller.js +//= link solidus_friendly_promotions/jquery/option_value_picker.js diff --git a/friendly_promotions/app/javascript/solidus_friendly_promotions.js b/friendly_promotions/app/javascript/solidus_friendly_promotions.js index 1c46292c453..5a6d4506083 100644 --- a/friendly_promotions/app/javascript/solidus_friendly_promotions.js +++ b/friendly_promotions/app/javascript/solidus_friendly_promotions.js @@ -1,5 +1,6 @@ import "@hotwired/turbo-rails"; import "solidus_friendly_promotions/controllers"; +import "solidus_friendly_promotions/jquery/option_value_picker" Turbo.session.drive = false; diff --git a/friendly_promotions/app/javascript/solidus_friendly_promotions/jquery/option_value_picker.js b/friendly_promotions/app/javascript/solidus_friendly_promotions/jquery/option_value_picker.js new file mode 100644 index 00000000000..17b4c71eeb3 --- /dev/null +++ b/friendly_promotions/app/javascript/solidus_friendly_promotions/jquery/option_value_picker.js @@ -0,0 +1,44 @@ +$.fn.optionValueAutocomplete = function (options) { + 'use strict'; + + // Default options + options = options || {} + var multiple = typeof(options['multiple']) !== 'undefined' ? options['multiple'] : true; + var productSelect = options['productSelect']; + + function formatOptionValue(option_value) { + return Select2.util.escapeMarkup(option_value.name); + } + + this.select2({ + minimumInputLength: 3, + multiple: multiple, + initSelection: function (element, callback) { + $.get(Spree.pathFor('api/option_values'), { + ids: element.val().split(','), + token: Spree.api_key + }, function (data) { + callback(multiple ? data : data[0]); + }); + }, + ajax: { + url: Spree.pathFor('api/option_values'), + datatype: 'json', + data: function (term, page) { + var productId = typeof(productSelect) !== 'undefined' ? $(productSelect).select2('val') : null; + return { + q: { + name_cont: term, + variants_product_id_eq: productId + }, + token: Spree.api_key + }; + }, + results: function (data, page) { + return { results: data }; + } + }, + formatResult: formatOptionValue, + formatSelection: formatOptionValue + }); +}; diff --git a/friendly_promotions/config/importmap.rb b/friendly_promotions/config/importmap.rb index de0cba41fce..f0ffda37249 100644 --- a/friendly_promotions/config/importmap.rb +++ b/friendly_promotions/config/importmap.rb @@ -3,5 +3,6 @@ pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true pin_all_from SolidusFriendlyPromotions::Engine.root.join("app/javascript/solidus_friendly_promotions/controllers"), under: "solidus_friendly_promotions/controllers" +pin_all_from SolidusFriendlyPromotions::Engine.root.join("app/javascript/solidus_friendly_promotions/jquery"), under: "solidus_friendly_promotions/jquery" pin "solidus_friendly_promotions", to: "solidus_friendly_promotions.js", preload: true From d209a5fa9205a1b0f6ac89ac9ed234659ebcd8e8 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Mon, 3 Jul 2023 10:52:28 +0200 Subject: [PATCH 068/524] Fix Order option value rule This can use the same JS and templates as the line item option value rule. --- .../promotions/rules/_option_value.html.erb | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_option_value.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_option_value.html.erb index 093148c1cfe..49d25160eca 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_option_value.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_option_value.html.erb @@ -5,9 +5,17 @@
    <%= label_tag nil, plural_resource_name(Spree::OptionValue) %>
    - <%= content_tag :div, nil, class: "js-promo-rule-option-values", - data: { :'original-option-values' => promotion_rule.preferred_eligible_values } %> - - - +
    +
    + + <% form.object.preferred_eligible_values.each do |product_option_values| %> + <%= render "solidus_friendly_promotions/admin/promotions/rules/line_item_option_value/option_value_fields", product_option_values: product_option_values, form: form %> + <% end %> +
    + <%= link_to "Add Row", "#", class: "btn btn-outline-primary", data: { action: "click->product-option-values#add_row" } %> +
    +
    +
    From 947519608eddeb1f518e50efe886593e6e5d944f Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Mon, 3 Jul 2023 10:58:09 +0200 Subject: [PATCH 069/524] Move rule form submit button into partials It's cleaner to have the button inside the partials in order to not give a button to rules that have no preferences or relations. --- .../admin/promotion_rules/_promotion_rule.html.erb | 5 ----- .../admin/promotion_rules/new.html.erb | 5 ----- .../promotions/rules/_first_repeat_purchase_since.html.erb | 6 ++++++ .../admin/promotions/rules/_item_total.html.erb | 6 ++++++ .../admin/promotions/rules/_line_item_option_value.html.erb | 6 ++++++ .../admin/promotions/rules/_line_item_product.html.erb | 6 ++++++ .../admin/promotions/rules/_line_item_taxon.html.erb | 6 ++++++ .../admin/promotions/rules/_nth_order.html.erb | 6 ++++++ .../admin/promotions/rules/_option_value.html.erb | 6 ++++++ .../admin/promotions/rules/_product.html.erb | 6 ++++++ .../admin/promotions/rules/_store.html.erb | 6 ++++++ .../admin/promotions/rules/_taxon.html.erb | 6 ++++++ .../admin/promotions/rules/_user.html.erb | 6 ++++++ .../admin/promotions/rules/_user_role.html.erb | 6 ++++++ 14 files changed, 72 insertions(+), 10 deletions(-) diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/_promotion_rule.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/_promotion_rule.html.erb index bc7accac723..a2564b48829 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/_promotion_rule.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/_promotion_rule.html.erb @@ -12,10 +12,5 @@ <%= hidden_field_tag "#{param_prefix}[id]", promotion_rule.id %> <%= render partial: "spree/shared/error_messages", locals: { target: promotion_rule } %> <%= render promotion_rule, promotion_rule: promotion_rule, param_prefix: "promotion_rule", form: form %> -
    -
    - <%= button_tag "Update", class: "btn btn-secondary float-right" if promotion_rule.preferences.any? %> -
    -
    <% end %>
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/new.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/new.html.erb index 01ebdd15db5..022c47c3e7a 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/new.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/new.html.erb @@ -14,11 +14,6 @@ <%= hidden_field_tag :level, @level %> <%= hidden_field_tag "promotion_rule[type]", @promotion_rule.class.name %> <%= render @promotion_rule, promotion_rule: @promotion_rule, param_prefix: "promotion_rule", form: form %> -
    -
    - <%= button_tag "Add", class: "btn btn-secondary float-right", data: { "turbo-frame" => "_top" } %> -
    -
    <% end %> <% end %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_first_repeat_purchase_since.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_first_repeat_purchase_since.html.erb index 8e2e587aab7..a19c540b415 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_first_repeat_purchase_since.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_first_repeat_purchase_since.html.erb @@ -11,3 +11,9 @@ + +
    +
    + <%= form.submit(t((form.object.persisted? ? :update : :add), scope: [:solidus_friendly_promotions, :crud]), class: "btn btn-secondary float-right") %> +
    +
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_item_total.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_item_total.html.erb index 1b0767b2ea9..90c9d17c967 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_item_total.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_item_total.html.erb @@ -12,3 +12,9 @@ + +
    +
    + <%= form.submit(t((form.object.persisted? ? :update : :add), scope: [:solidus_friendly_promotions, :crud]), class: "btn btn-secondary float-right") %> +
    +
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_option_value.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_option_value.html.erb index 49d25160eca..94414312519 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_option_value.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_option_value.html.erb @@ -19,3 +19,9 @@ + +
    +
    + <%= form.submit(t((form.object.persisted? ? :update : :add), scope: [:solidus_friendly_promotions, :crud]), class: "btn btn-secondary float-right") %> +
    +
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_product.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_product.html.erb index 2c6f7dd5288..e2d7ac86d95 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_product.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_product.html.erb @@ -16,3 +16,9 @@ + +
    +
    + <%= form.submit(t((form.object.persisted? ? :update : :add), scope: [:solidus_friendly_promotions, :crud]), class: "btn btn-secondary float-right") %> +
    +
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_taxon.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_taxon.html.erb index 1a2dbcf7e48..14db1d05fc8 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_taxon.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_taxon.html.erb @@ -12,3 +12,9 @@ ) %> + +
    +
    + <%= form.submit(t((form.object.persisted? ? :update : :add), scope: [:solidus_friendly_promotions, :crud]), class: "btn btn-secondary float-right") %> +
    +
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_nth_order.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_nth_order.html.erb index 0a37ce60cf1..2ccb5f0eebc 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_nth_order.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_nth_order.html.erb @@ -10,3 +10,9 @@ + +
    +
    + <%= form.submit(t((form.object.persisted? ? :update : :add), scope: [:solidus_friendly_promotions, :crud]), class: "btn btn-secondary float-right") %> +
    +
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_option_value.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_option_value.html.erb index 49d25160eca..94414312519 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_option_value.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_option_value.html.erb @@ -19,3 +19,9 @@ + +
    +
    + <%= form.submit(t((form.object.persisted? ? :update : :add), scope: [:solidus_friendly_promotions, :crud]), class: "btn btn-secondary float-right") %> +
    +
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_product.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_product.html.erb index 2d11f098832..c0ec650e450 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_product.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_product.html.erb @@ -13,3 +13,9 @@ + +
    +
    + <%= form.submit(t((form.object.persisted? ? :update : :add), scope: [:solidus_friendly_promotions, :crud]), class: "btn btn-secondary float-right") %> +
    +
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_store.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_store.html.erb index cce8ac2e37f..1018cccf30d 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_store.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_store.html.erb @@ -4,3 +4,9 @@ options_from_collection_for_select(Spree::Store.all, :id, :name, promotion_rule.store_ids), multiple: true, class: "select2 fullwidth" %> + +
    +
    + <%= form.submit(t((form.object.persisted? ? :update : :add), scope: [:solidus_friendly_promotions, :crud]), class: "btn btn-secondary float-right") %> +
    +
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_taxon.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_taxon.html.erb index bec9eb7e9c3..3df5bf3bde5 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_taxon.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_taxon.html.erb @@ -7,3 +7,9 @@ <%= t('spree.taxon_rule.label', select: select_tag("#{param_prefix}[preferred_match_policy]", options_for_select(Spree::Promotion::Rules::Taxon::MATCH_POLICIES.map{|s| [t("spree.taxon_rule.match_#{s}"),s] }, promotion_rule.preferred_match_policy), {class: 'select_taxon custom-select'})).html_safe %> + +
    +
    + <%= form.submit(t((form.object.persisted? ? :update : :add), scope: [:solidus_friendly_promotions, :crud]), class: "btn btn-secondary float-right") %> +
    +
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_user.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_user.html.erb index 91391a18fd0..1f76adf4a52 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_user.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_user.html.erb @@ -2,3 +2,9 @@
    + +
    +
    + <%= form.submit(t((form.object.persisted? ? :update : :add), scope: [:solidus_friendly_promotions, :crud]), class: "btn btn-secondary float-right") %> +
    +
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_user_role.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_user_role.html.erb index e078526d08f..cd82a42faa1 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_user_role.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_user_role.html.erb @@ -10,3 +10,9 @@ <%= t('spree.user_role_rule.label', select: select_tag("#{param_prefix}[preferred_match_policy]", options_for_select(Spree::Promotion::Rules::UserRole::MATCH_POLICIES.map{ |s| [t("spree.user_role_rule.match_#{s}"),s] }, promotion_rule.preferred_match_policy), { class: 'custom-select'})).html_safe %> + +
    +
    + <%= form.submit(t((form.object.persisted? ? :update : :add), scope: [:solidus_friendly_promotions, :crud]), class: "btn btn-secondary float-right") %> +
    +
    From 00fa3a479b390367fecdb582558f25026ef1c8f1 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Mon, 3 Jul 2023 11:07:18 +0200 Subject: [PATCH 070/524] Move promotion rule partials into app/views/admin/promotion_rules This is the way. --- .../app/models/solidus_friendly_promotions/rules/base.rb | 2 +- .../rules/_first_order.html.erb | 0 .../rules/_first_repeat_purchase_since.html.erb | 0 .../rules/_item_total.html.erb | 0 .../rules/_line_item_option_value.html.erb | 4 ++-- .../rules/_line_item_product.html.erb | 0 .../rules/_line_item_taxon.html.erb | 0 .../{promotions => promotion_rules}/rules/_nth_order.html.erb | 0 .../rules/_one_use_per_user.html.erb | 0 .../rules/_option_value.html.erb | 4 ++-- .../{promotions => promotion_rules}/rules/_product.html.erb | 0 .../{promotions => promotion_rules}/rules/_store.html.erb | 0 .../{promotions => promotion_rules}/rules/_taxon.html.erb | 0 .../{promotions => promotion_rules}/rules/_user.html.erb | 0 .../rules/_user_logged_in.html.erb | 0 .../{promotions => promotion_rules}/rules/_user_role.html.erb | 0 .../line_item_option_value/_option_value_fields.html.erb | 0 .../solidus_friendly_promotions/rules/first_order_spec.rb | 2 +- 18 files changed, 6 insertions(+), 6 deletions(-) rename friendly_promotions/app/views/solidus_friendly_promotions/admin/{promotions => promotion_rules}/rules/_first_order.html.erb (100%) rename friendly_promotions/app/views/solidus_friendly_promotions/admin/{promotions => promotion_rules}/rules/_first_repeat_purchase_since.html.erb (100%) rename friendly_promotions/app/views/solidus_friendly_promotions/admin/{promotions => promotion_rules}/rules/_item_total.html.erb (100%) rename friendly_promotions/app/views/solidus_friendly_promotions/admin/{promotions => promotion_rules}/rules/_line_item_option_value.html.erb (74%) rename friendly_promotions/app/views/solidus_friendly_promotions/admin/{promotions => promotion_rules}/rules/_line_item_product.html.erb (100%) rename friendly_promotions/app/views/solidus_friendly_promotions/admin/{promotions => promotion_rules}/rules/_line_item_taxon.html.erb (100%) rename friendly_promotions/app/views/solidus_friendly_promotions/admin/{promotions => promotion_rules}/rules/_nth_order.html.erb (100%) rename friendly_promotions/app/views/solidus_friendly_promotions/admin/{promotions => promotion_rules}/rules/_one_use_per_user.html.erb (100%) rename friendly_promotions/app/views/solidus_friendly_promotions/admin/{promotions => promotion_rules}/rules/_option_value.html.erb (74%) rename friendly_promotions/app/views/solidus_friendly_promotions/admin/{promotions => promotion_rules}/rules/_product.html.erb (100%) rename friendly_promotions/app/views/solidus_friendly_promotions/admin/{promotions => promotion_rules}/rules/_store.html.erb (100%) rename friendly_promotions/app/views/solidus_friendly_promotions/admin/{promotions => promotion_rules}/rules/_taxon.html.erb (100%) rename friendly_promotions/app/views/solidus_friendly_promotions/admin/{promotions => promotion_rules}/rules/_user.html.erb (100%) rename friendly_promotions/app/views/solidus_friendly_promotions/admin/{promotions => promotion_rules}/rules/_user_logged_in.html.erb (100%) rename friendly_promotions/app/views/solidus_friendly_promotions/admin/{promotions => promotion_rules}/rules/_user_role.html.erb (100%) rename friendly_promotions/app/views/solidus_friendly_promotions/admin/{promotions => promotion_rules}/rules/line_item_option_value/_option_value_fields.html.erb (100%) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/base.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/base.rb index 4fa28c8f554..e511e305fc6 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/base.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/base.rb @@ -4,7 +4,7 @@ module SolidusFriendlyPromotions module Rules class Base < Spree::PromotionRule def to_partial_path - "solidus_friendly_promotions/admin/promotions/rules/#{model_name.element}" + "solidus_friendly_promotions/admin/promotion_rules/rules/#{model_name.element}" end end end diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_first_order.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_first_order.html.erb similarity index 100% rename from friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_first_order.html.erb rename to friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_first_order.html.erb diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_first_repeat_purchase_since.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_first_repeat_purchase_since.html.erb similarity index 100% rename from friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_first_repeat_purchase_since.html.erb rename to friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_first_repeat_purchase_since.html.erb diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_item_total.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_item_total.html.erb similarity index 100% rename from friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_item_total.html.erb rename to friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_item_total.html.erb diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_option_value.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_line_item_option_value.html.erb similarity index 74% rename from friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_option_value.html.erb rename to friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_line_item_option_value.html.erb index 94414312519..bedd2b5cb63 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_option_value.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_line_item_option_value.html.erb @@ -8,10 +8,10 @@
    <% form.object.preferred_eligible_values.each do |product_option_values| %> - <%= render "solidus_friendly_promotions/admin/promotions/rules/line_item_option_value/option_value_fields", product_option_values: product_option_values, form: form %> + <%= render "solidus_friendly_promotions/admin/promotion_rules/rules/line_item_option_value/option_value_fields", product_option_values: product_option_values, form: form %> <% end %>
    <%= link_to "Add Row", "#", class: "btn btn-outline-primary", data: { action: "click->product-option-values#add_row" } %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_product.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_line_item_product.html.erb similarity index 100% rename from friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_product.html.erb rename to friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_line_item_product.html.erb diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_taxon.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_line_item_taxon.html.erb similarity index 100% rename from friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_line_item_taxon.html.erb rename to friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_line_item_taxon.html.erb diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_nth_order.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_nth_order.html.erb similarity index 100% rename from friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_nth_order.html.erb rename to friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_nth_order.html.erb diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_one_use_per_user.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_one_use_per_user.html.erb similarity index 100% rename from friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_one_use_per_user.html.erb rename to friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_one_use_per_user.html.erb diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_option_value.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_option_value.html.erb similarity index 74% rename from friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_option_value.html.erb rename to friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_option_value.html.erb index 94414312519..bedd2b5cb63 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_option_value.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_option_value.html.erb @@ -8,10 +8,10 @@
    <% form.object.preferred_eligible_values.each do |product_option_values| %> - <%= render "solidus_friendly_promotions/admin/promotions/rules/line_item_option_value/option_value_fields", product_option_values: product_option_values, form: form %> + <%= render "solidus_friendly_promotions/admin/promotion_rules/rules/line_item_option_value/option_value_fields", product_option_values: product_option_values, form: form %> <% end %>
    <%= link_to "Add Row", "#", class: "btn btn-outline-primary", data: { action: "click->product-option-values#add_row" } %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_product.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_product.html.erb similarity index 100% rename from friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_product.html.erb rename to friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_product.html.erb diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_store.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_store.html.erb similarity index 100% rename from friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_store.html.erb rename to friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_store.html.erb diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_taxon.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_taxon.html.erb similarity index 100% rename from friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_taxon.html.erb rename to friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_taxon.html.erb diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_user.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_user.html.erb similarity index 100% rename from friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_user.html.erb rename to friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_user.html.erb diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_user_logged_in.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_user_logged_in.html.erb similarity index 100% rename from friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_user_logged_in.html.erb rename to friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_user_logged_in.html.erb diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_user_role.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_user_role.html.erb similarity index 100% rename from friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/_user_role.html.erb rename to friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_user_role.html.erb diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/line_item_option_value/_option_value_fields.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/line_item_option_value/_option_value_fields.html.erb similarity index 100% rename from friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/rules/line_item_option_value/_option_value_fields.html.erb rename to friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/line_item_option_value/_option_value_fields.html.erb diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_order_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_order_spec.rb index 8be4c2fcbe7..bc6c2e82ba8 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_order_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_order_spec.rb @@ -9,7 +9,7 @@ describe ".to_partial_path" do subject { rule.to_partial_path } - it { is_expected.to eq("solidus_friendly_promotions/admin/promotions/rules/first_order") } + it { is_expected.to eq("solidus_friendly_promotions/admin/promotion_rules/rules/first_order") } end context "without a user or email" do From 735e81dfcf2dea833cf05a26786d832c37c448a1 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Mon, 3 Jul 2023 14:49:13 +0200 Subject: [PATCH 071/524] Fix promotion actions request spec --- .../admin/promotion_actions_request_spec.rb | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_actions_request_spec.rb b/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_actions_request_spec.rb index ad25638effb..63fd5325357 100644 --- a/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_actions_request_spec.rb +++ b/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_actions_request_spec.rb @@ -8,14 +8,19 @@ let!(:promotion) { create(:promotion) } it "can create a promotion action of a valid type" do - post solidus_friendly_promotions.admin_promotion_promotion_actions_path(promotion_id: promotion.id, action_type: "Spree::Promotion::Actions::CreateAdjustment") + post solidus_friendly_promotions.admin_promotion_promotion_actions_path(promotion_id: promotion.id), params: { + promotion_action: { + type: "SolidusFriendlyPromotions::Actions::AdjustLineItem", + calculator_attributes: { type: "SolidusFriendlyPromotions::Calculators::FlatRate" } + } + } expect(response).to be_redirect expect(response).to redirect_to spree.edit_admin_promotion_path(promotion) expect(promotion.actions.count).to eq(1) end it "can not create a promotion action of an invalid type" do - post solidus_friendly_promotions.admin_promotion_promotion_actions_path(promotion_id: promotion.id, action_type: "Spree::InvalidType") + post solidus_friendly_promotions.admin_promotion_promotion_actions_path(promotion_id: promotion.id, promotion_action: { type: "Spree::InvalidType" }) expect(response).to be_redirect expect(response).to redirect_to spree.edit_admin_promotion_path(promotion) expect(promotion.rules.count).to eq(0) From 1033a9ff19f3dca7cddaf378cca32aae95a1dac0 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Mon, 3 Jul 2023 14:56:45 +0200 Subject: [PATCH 072/524] Fix Action Path spec --- .../actions/adjust_line_item_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_line_item_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_line_item_spec.rb index d5ee10e2ae4..8d8ab9ae105 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_line_item_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_line_item_spec.rb @@ -13,6 +13,6 @@ describe ".to_partial_path" do subject { described_class.new.to_partial_path } - it { is_expected.to eq("solidus_friendly_promotions/admin/promotions/actions/adjust_line_item") } + it { is_expected.to eq("solidus_friendly_promotions/admin/promotion_actions/actions/adjust_line_item") } end end From 73af3c5ab67a45c936526d9931ce23b13629261b Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Mon, 3 Jul 2023 14:58:59 +0200 Subject: [PATCH 073/524] Fix promotion rules creation spec --- .../admin/promotion_rules_request_spec.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_rules_request_spec.rb b/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_rules_request_spec.rb index a555188e8c6..ac71afa53c6 100644 --- a/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_rules_request_spec.rb +++ b/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_rules_request_spec.rb @@ -11,7 +11,9 @@ end it "can create a promotion rule of a valid type" do - post solidus_friendly_promotions.admin_promotion_promotion_rules_path(promotion_id: promotion.id, promotion_rule: { type: "Spree::Promotion::Rules::Product" } ) + post solidus_friendly_promotions.admin_promotion_promotion_rules_path(promotion_id: promotion.id), params: { + promotion_rule: { type: "SolidusFriendlyPromotions::Rules::Product" } + } expect(response).to be_redirect expect(response).to redirect_to solidus_friendly_promotions.edit_admin_promotion_path(promotion) expect(promotion.rules.count).to eq(1) From 8d354b9eac6ae356906d73eefcafc1a36d5c6a91 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Mon, 3 Jul 2023 15:06:50 +0200 Subject: [PATCH 074/524] Solidus 4.1: Fix deprecation warning in Simple Order Updater --- .../simple_order_contents.rb | 4 ++-- .../simple_order_contents_spec.rb | 20 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/simple_order_contents.rb b/friendly_promotions/app/models/solidus_friendly_promotions/simple_order_contents.rb index e2dabf06327..c66dfa8c979 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/simple_order_contents.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/simple_order_contents.rb @@ -7,7 +7,7 @@ def update_cart(params) if order.update(params) unless order.completed? order.line_items = order.line_items.select { |li| li.quantity > 0 } - order.ensure_updated_shipments + order.check_shipments_and_restart_checkout end reload_totals true @@ -20,7 +20,7 @@ def update_cart(params) def after_add_or_remove(line_item, options = {}) shipment = options[:shipment] - shipment.present? ? shipment.update_amounts : order.ensure_updated_shipments + shipment.present? ? shipment.update_amounts : order.check_shipments_and_restart_checkout reload_totals line_item end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/simple_order_contents_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/simple_order_contents_spec.rb index a10b2396c3b..a101a7a9c66 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/simple_order_contents_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/simple_order_contents_spec.rb @@ -23,8 +23,8 @@ context 'given a shipment' do let!(:shipment) { create(:shipment, order: order) } - it "ensure shipment calls update_amounts instead of order calling ensure_updated_shipments" do - expect(subject.order).to_not receive(:ensure_updated_shipments) + it "ensure shipment calls update_amounts instead of order calling check_shipments_and_restart_checkout" do + expect(subject.order).to_not receive(:check_shipments_and_restart_checkout) expect(shipment).to receive(:update_amounts).at_least(:once) subject.add(variant, 1, shipment: shipment) end @@ -54,7 +54,7 @@ context 'not given a shipment' do it "ensures updated shipments" do - expect(subject.order).to receive(:ensure_updated_shipments) + expect(subject.order).to receive(:check_shipments_and_restart_checkout) subject.add(variant) end end @@ -159,10 +159,10 @@ end context 'given a shipment' do - it "ensure shipment calls update_amounts instead of order calling ensure_updated_shipments" do + it "ensure shipment calls update_amounts instead of order calling check_shipments_and_restart_checkout" do subject.add(variant, 1) shipment = create(:shipment) - expect(subject.order).to_not receive(:ensure_updated_shipments) + expect(subject.order).to_not receive(:check_shipments_and_restart_checkout) expect(shipment).to receive(:update_amounts) subject.remove(variant, 1, shipment: shipment) end @@ -171,7 +171,7 @@ context 'not given a shipment' do it "ensures updated shipments" do subject.add(variant, 1) - expect(subject.order).to receive(:ensure_updated_shipments) + expect(subject.order).to receive(:check_shipments_and_restart_checkout) subject.remove(variant) end end @@ -214,10 +214,10 @@ context "#remove_line_item" do context 'given a shipment' do - it "ensure shipment calls update_amounts instead of order calling ensure_updated_shipments" do + it "ensure shipment calls update_amounts instead of order calling check_shipments_and_restart_checkout" do line_item = subject.add(variant, 1) shipment = create(:shipment) - expect(subject.order).to_not receive(:ensure_updated_shipments) + expect(subject.order).to_not receive(:check_shipments_and_restart_checkout) expect(shipment).to receive(:update_amounts) subject.remove_line_item(line_item, shipment: shipment) end @@ -226,7 +226,7 @@ context 'not given a shipment' do it "ensures updated shipments" do line_item = subject.add(variant, 1) - expect(subject.order).to receive(:ensure_updated_shipments) + expect(subject.order).to receive(:check_shipments_and_restart_checkout) subject.remove_line_item(line_item) end end @@ -288,7 +288,7 @@ end it "ensures updated shipments" do - expect(subject.order).to receive(:ensure_updated_shipments) + expect(subject.order).to receive(:check_shipments_and_restart_checkout) subject.update_cart params end end From c2ad7a8d6947c13d08bdd788833bffd536443304 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Mon, 3 Jul 2023 14:33:36 +0200 Subject: [PATCH 075/524] Create Promotion class, table and spec --- .../solidus_friendly_promotions/promotion.rb | 11 +++++++ .../20230703101637_create_promotions.rb | 16 ++++++++++ .../promotion_spec.rb | 30 +++++++++++++++++++ 3 files changed, 57 insertions(+) create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb create mode 100644 friendly_promotions/db/migrate/20230703101637_create_promotions.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb new file mode 100644 index 00000000000..b47d5a2c9d0 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + class Promotion < Spree::Base + validates :name, presence: true + validates :path, uniqueness: { allow_blank: true, case_sensitive: true } + validates :usage_limit, numericality: { greater_than: 0, allow_nil: true } + validates :per_code_usage_limit, numericality: { greater_than_or_equal_to: 0, allow_nil: true } + validates :description, length: { maximum: 255 } + end +end diff --git a/friendly_promotions/db/migrate/20230703101637_create_promotions.rb b/friendly_promotions/db/migrate/20230703101637_create_promotions.rb new file mode 100644 index 00000000000..b0c98b1d077 --- /dev/null +++ b/friendly_promotions/db/migrate/20230703101637_create_promotions.rb @@ -0,0 +1,16 @@ +class CreatePromotions < ActiveRecord::Migration[7.0] + def change + create_table :solidus_friendly_promotions_promotions do |t| + t.string :description + t.datetime :expires_at, precision: nil + t.datetime :starts_at, precision: nil + t.string :name + t.integer :usage_limit + t.boolean :advertise, default: false + t.string :path + t.integer :per_code_usage_limit + t.boolean :apply_automatically, default: false + t.timestamps + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb new file mode 100644 index 00000000000..2026db2c59e --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SolidusFriendlyPromotions::Promotion, type: :model do + let(:promotion) { SolidusFriendlyPromotions::Promotion.new } + + describe "validations" do + before :each do + @valid_promotion = SolidusFriendlyPromotions::Promotion.new name: "A promotion" + end + + it "valid_promotion is valid" do + expect(@valid_promotion).to be_valid + end + + it "validates usage limit" do + @valid_promotion.usage_limit = -1 + expect(@valid_promotion).not_to be_valid + + @valid_promotion.usage_limit = 100 + expect(@valid_promotion).to be_valid + end + + it "validates name" do + @valid_promotion.name = nil + expect(@valid_promotion).not_to be_valid + end + end +end From a8c9d78fa560fc9771162bdb7087e9cf0a4f16a1 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Mon, 3 Jul 2023 16:35:12 +0200 Subject: [PATCH 076/524] Add shoulda-matchers gem Good for testing associations --- friendly_promotions/solidus_friendly_promotions.gemspec | 1 + friendly_promotions/spec/spec_helper.rb | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/friendly_promotions/solidus_friendly_promotions.gemspec b/friendly_promotions/solidus_friendly_promotions.gemspec index fb6b5946c32..af999ef9f5d 100644 --- a/friendly_promotions/solidus_friendly_promotions.gemspec +++ b/friendly_promotions/solidus_friendly_promotions.gemspec @@ -35,4 +35,5 @@ Gem::Specification.new do |spec| spec.add_development_dependency "solidus_dev_support", "~> 2.6" spec.add_development_dependency "importmap-rails", "~> 1.2" spec.add_development_dependency "rspec-activemodel-mocks", "~> 1.0" + spec.add_development_dependency "shoulda-matchers", "~> 5.3" end diff --git a/friendly_promotions/spec/spec_helper.rb b/friendly_promotions/spec/spec_helper.rb index 36851bd3aa9..3413d8d94b9 100644 --- a/friendly_promotions/spec/spec_helper.rb +++ b/friendly_promotions/spec/spec_helper.rb @@ -13,7 +13,7 @@ # Requires factories and other useful helpers defined in spree_core. require "solidus_dev_support/rspec/feature_helper" - +require "shoulda-matchers" # Explicitly load activemodel mocks require "rspec-activemodel-mocks" @@ -39,3 +39,10 @@ Spree::Config.order_contents_class = "SolidusFriendlyPromotions::SimpleOrderContents" end end + +Shoulda::Matchers.configure do |config| + config.integrate do |with| + with.test_framework :rspec + with.library :rails + end +end From 9ff28d772c5921191f7fcb4e1b908b887fbf294f Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Mon, 3 Jul 2023 16:35:29 +0200 Subject: [PATCH 077/524] Add promotion categories --- .../solidus_friendly_promotions/category.rb | 9 ++++++++ .../solidus_friendly_promotions/promotion.rb | 1 + ...30703141116_create_promotion_categories.rb | 11 ++++++++++ .../category_spec.rb | 21 +++++++++++++++++++ .../promotion_spec.rb | 6 ++++-- 5 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/category.rb create mode 100644 friendly_promotions/db/migrate/20230703141116_create_promotion_categories.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/category_spec.rb diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/category.rb b/friendly_promotions/app/models/solidus_friendly_promotions/category.rb new file mode 100644 index 00000000000..b800d746abb --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/category.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + class Category < Spree::Base + has_many :promotions + + validates :name, presence: true + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb index b47d5a2c9d0..31fd745f16b 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb @@ -2,6 +2,7 @@ module SolidusFriendlyPromotions class Promotion < Spree::Base + belongs_to :category, optional: true validates :name, presence: true validates :path, uniqueness: { allow_blank: true, case_sensitive: true } validates :usage_limit, numericality: { greater_than: 0, allow_nil: true } diff --git a/friendly_promotions/db/migrate/20230703141116_create_promotion_categories.rb b/friendly_promotions/db/migrate/20230703141116_create_promotion_categories.rb new file mode 100644 index 00000000000..e0a84bfc5c1 --- /dev/null +++ b/friendly_promotions/db/migrate/20230703141116_create_promotion_categories.rb @@ -0,0 +1,11 @@ +class CreatePromotionCategories < ActiveRecord::Migration[7.0] + def change + create_table :solidus_friendly_promotions_categories do |t| + t.string :name + + t.timestamps + end + + add_reference :solidus_friendly_promotions_promotions, :category, foreign_key: { to_table: :solidus_friendly_promotions_categories } + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/category_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/category_spec.rb new file mode 100644 index 00000000000..9fda2e04a9d --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/category_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SolidusFriendlyPromotions::Category, type: :model do + it { is_expected.to have_many :promotions } + + describe 'validation' do + let(:name) { 'Nom' } + subject { SolidusFriendlyPromotions::Category.new name: name } + + context 'when all required attributes are specified' do + it { is_expected.to be_valid } + end + + context 'when name is missing' do + let(:name) { nil } + it { is_expected.not_to be_valid } + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb index 2026db2c59e..3396ec5f0e6 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb @@ -3,11 +3,13 @@ require 'spec_helper' RSpec.describe SolidusFriendlyPromotions::Promotion, type: :model do - let(:promotion) { SolidusFriendlyPromotions::Promotion.new } + let(:promotion) { described_class.new } + + it { is_expected.to belong_to(:category).optional } describe "validations" do before :each do - @valid_promotion = SolidusFriendlyPromotions::Promotion.new name: "A promotion" + @valid_promotion = described_class.new name: "A promotion" end it "valid_promotion is valid" do From 1a0ab45f8e9f6cef21b91335a68b4134832848ee Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Mon, 3 Jul 2023 16:59:47 +0200 Subject: [PATCH 078/524] Add our own promotion rule base class --- .../solidus_friendly_promotions/promotion.rb | 2 + .../solidus_friendly_promotions/rule.rb | 47 +++++++++++++++++++ .../solidus_friendly_promotions/rules/base.rb | 11 ----- .../rules/first_order.rb | 2 +- .../rules/first_repeat_purchase_since.rb | 2 +- .../rules/item_total.rb | 2 +- .../rules/line_item_option_value.rb | 2 +- .../rules/line_item_product.rb | 2 +- .../rules/line_item_taxon.rb | 2 +- .../rules/nth_order.rb | 2 +- .../rules/one_use_per_user.rb | 2 +- .../rules/option_value.rb | 2 +- .../rules/product.rb | 2 +- .../rules/store.rb | 2 +- .../rules/taxon.rb | 2 +- .../solidus_friendly_promotions/rules/user.rb | 2 +- .../rules/user_logged_in.rb | 2 +- .../rules/user_role.rb | 2 +- .../db/migrate/20230703143943_create_rules.rb | 11 +++++ .../promotion_spec.rb | 1 + .../solidus_friendly_promotions/rule_spec.rb | 38 +++++++++++++++ 21 files changed, 114 insertions(+), 26 deletions(-) create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/rule.rb delete mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/rules/base.rb create mode 100644 friendly_promotions/db/migrate/20230703143943_create_rules.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/rule_spec.rb diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb index 31fd745f16b..28fa0c05c81 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb @@ -3,6 +3,8 @@ module SolidusFriendlyPromotions class Promotion < Spree::Base belongs_to :category, optional: true + has_many :rules + validates :name, presence: true validates :path, uniqueness: { allow_blank: true, case_sensitive: true } validates :usage_limit, numericality: { greater_than: 0, allow_nil: true } diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rule.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rule.rb new file mode 100644 index 00000000000..86efa637f7f --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rule.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require 'spree/preferences/persistable' + +module SolidusFriendlyPromotions + class Rule < Spree::Base + include Spree::Preferences::Persistable + + belongs_to :promotion + + scope :of_type, ->(type) { where(type: type) } + + validate :unique_per_promotion, on: :create + + def preload_relations + [] + end + + def applicable?(_promotable) + raise NotImplementedError, "applicable? should be implemented in a sub-class of SolidusFriendlyPromotions::Rule" + end + + def eligible?(_promotable, _options = {}) + raise NotImplementedError, "eligible? should be implemented in a sub-class of SolidusFriendlyPromotions::Rule" + end + + def eligibility_errors + @eligibility_errors ||= ActiveModel::Errors.new(self) + end + + def to_partial_path + "solidus_friendly_promotions/admin/promotion_rules/rules/#{model_name.element}" + end + + private + + def unique_per_promotion + if self.class.exists?(promotion_id: promotion_id, type: self.class.name) + errors[:base] << "Promotion already contains this rule type" + end + end + + def eligibility_error_message(key, options = {}) + I18n.t(key, **{ scope: [:spree, :eligibility_errors, :messages] }.merge(options)) + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/base.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/base.rb deleted file mode 100644 index e511e305fc6..00000000000 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/base.rb +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true - -module SolidusFriendlyPromotions - module Rules - class Base < Spree::PromotionRule - def to_partial_path - "solidus_friendly_promotions/admin/promotion_rules/rules/#{model_name.element}" - end - end - end -end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_order.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_order.rb index 81f00186b20..ca90c5b3f2e 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_order.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_order.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Rules - class FirstOrder < Base + class FirstOrder < Rule attr_reader :user, :email def applicable?(promotable) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb index 264c262623e..087f6314e8f 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Rules - class FirstRepeatPurchaseSince < Base + class FirstRepeatPurchaseSince < Rule preference :days_ago, :integer, default: 365 validates :preferred_days_ago, numericality: {only_integer: true, greater_than: 0} diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb index 0722f106a8c..d2af73b3772 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb @@ -7,7 +7,7 @@ module Rules # # To add extra operators please override `self.operators_map` or any other helper method. # To customize the error message you can also override `ineligible_message`. - class ItemTotal < Base + class ItemTotal < Rule preference :amount, :decimal, default: 100.00 preference :currency, :string, default: -> { Spree::Config[:currency] } preference :operator, :string, default: "gt" diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_option_value.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_option_value.rb index ac8ef3cb28f..f57bfb17d42 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_option_value.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_option_value.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Rules - class LineItemOptionValue < Base + class LineItemOptionValue < Rule preference :eligible_values, :hash def applicable?(promotable) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb index eff1ab54792..f74933a1676 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb @@ -3,7 +3,7 @@ module SolidusFriendlyPromotions module Rules # A rule to apply a promotion only to line items with or without a chosen product - class LineItemProduct < Base + class LineItemProduct < Rule MATCH_POLICIES = %w[include exclude] has_many :product_promotion_rules, diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb index 858d57a680a..48b73009922 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Rules - class LineItemTaxon < Base + class LineItemTaxon < Rule has_many :promotion_rule_taxons, class_name: "Spree::PromotionRuleTaxon", foreign_key: :promotion_rule_id, dependent: :destroy has_many :taxons, through: :promotion_rule_taxons, class_name: "Spree::Taxon" diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb index 8adba7157c1..bee23a67e22 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Rules - class NthOrder < Base + class NthOrder < Rule preference :nth_order, :integer, default: 2 # It does not make sense to have this apply to the first order using preferred_nth_order == 1 # Instead we could use the first_order rule diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb index 20edae46d7d..2cefe588199 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Rules - class OneUsePerUser < Base + class OneUsePerUser < Rule def applicable?(promotable) promotable.is_a?(Spree::Order) end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/option_value.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/option_value.rb index a5004233dee..4d5451a1dcb 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/option_value.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/option_value.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Rules - class OptionValue < Base + class OptionValue < Rule preference :eligible_values, :hash def applicable?(promotable) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb index 6de81662aa8..34847df0ba7 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb @@ -6,7 +6,7 @@ module Rules # require all or any of the products to be present. Valid products # either come from assigned product group or are assingned directly to # the rule. - class Product < Base + class Product < Rule has_many :product_promotion_rules, dependent: :destroy, foreign_key: :promotion_rule_id, class_name: "Spree::ProductPromotionRule" has_many :products, class_name: "Spree::Product", through: :product_promotion_rules diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb index 0ce363c3acd..baab2f1666d 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Rules - class Store < Base + class Store < Rule has_many :promotion_rule_stores, class_name: "Spree::PromotionRuleStore", foreign_key: :promotion_rule_id, dependent: :destroy diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb index 6e7f5b0077c..ea49627dfa0 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Rules - class Taxon < Base + class Taxon < Rule has_many :promotion_rule_taxons, class_name: "Spree::PromotionRuleTaxon", foreign_key: :promotion_rule_id, dependent: :destroy has_many :taxons, through: :promotion_rule_taxons, class_name: "Spree::Taxon" diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb index 1be7a6f74d3..2fcadd2e989 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Rules - class User < Base + class User < Rule has_many :promotion_rule_users, class_name: "Spree::PromotionRuleUser", foreign_key: :promotion_rule_id, dependent: :destroy diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb index 27a62132408..7c109cc956a 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Rules - class UserLoggedIn < Base + class UserLoggedIn < Rule def applicable?(promotable) promotable.is_a?(Spree::Order) end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_role.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_role.rb index 1b75e130a7a..5f5c5ac9d93 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_role.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_role.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Rules - class UserRole < Base + class UserRole < Rule preference :role_ids, :array, default: [] MATCH_POLICIES = %w[any all] diff --git a/friendly_promotions/db/migrate/20230703143943_create_rules.rb b/friendly_promotions/db/migrate/20230703143943_create_rules.rb new file mode 100644 index 00000000000..674672f9536 --- /dev/null +++ b/friendly_promotions/db/migrate/20230703143943_create_rules.rb @@ -0,0 +1,11 @@ +class CreateRules < ActiveRecord::Migration[7.0] + def change + create_table :solidus_friendly_promotions_rules do |t| + t.references :promotion, foreign_key: { to_table: :solidus_friendly_promotions_promotions } + t.string :type + t.text :preferences + + t.timestamps + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb index 3396ec5f0e6..7a0e1ce116d 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb @@ -6,6 +6,7 @@ let(:promotion) { described_class.new } it { is_expected.to belong_to(:category).optional } + it { is_expected.to have_many :rules } describe "validations" do before :each do diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rule_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rule_spec.rb new file mode 100644 index 00000000000..57d8bda464b --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rule_spec.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe SolidusFriendlyPromotions::Rule do + class BadTestRule < SolidusFriendlyPromotions::Rule; end + + class TestRule < SolidusFriendlyPromotions::Rule + def eligible?(_promotable, _options = {}) + true + end + end + + describe "preferences" do + subject { described_class.new.preferences } + + it { is_expected.to be_a(Hash) } + end + + it "forces developer to implement eligible? method" do + expect { BadTestRule.new.eligible?("promotable") }.to raise_error NotImplementedError + end + + it "validates unique rules for a promotion" do + promotion_one = TestRule.new + promotion_one.promotion_id = 1 + promotion_one.save + + promotion_two = TestRule.new + promotion_two.promotion_id = 1 + expect(promotion_two).not_to be_valid + end + + it "generates its own partial path" do + rule = TestRule.new + expect(rule.to_partial_path).to eq 'solidus_friendly_promotions/admin/promotion_rules/rules/test_rule' + end +end From 1a021772a2d6d5897dc3695b95fb247401b5537d Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 4 Jul 2023 11:35:04 +0200 Subject: [PATCH 079/524] Add join table between rules and Solidus models This is four join tables, for the product, store, taxon, and user rules. --- .../products_rule.rb | 8 +++++ .../rules/product.rb | 5 ++- .../rules/store.rb | 6 ++-- .../rules/taxon.rb | 5 ++- .../solidus_friendly_promotions/rules/user.rb | 6 ++-- .../rules_store.rb | 8 +++++ .../rules_taxon.rb | 8 +++++ .../solidus_friendly_promotions/rules_user.rb | 8 +++++ .../migrate/20230704083830_add_rule_tables.rb | 31 +++++++++++++++++++ .../products_rule_spec.rb | 8 +++++ .../rules/product_spec.rb | 4 ++- .../rules/store_spec.rb | 2 ++ .../rules/taxon_spec.rb | 2 ++ .../rules/user_spec.rb | 3 +- .../rules_store_spec.rb | 8 +++++ .../rules_taxon_spec.rb | 8 +++++ .../rules_user_spec.rb | 8 +++++ 17 files changed, 112 insertions(+), 16 deletions(-) create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/products_rule.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/rules_store.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/rules_taxon.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/rules_user.rb create mode 100644 friendly_promotions/db/migrate/20230704083830_add_rule_tables.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/products_rule_spec.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/rules_store_spec.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/rules_taxon_spec.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/rules_user_spec.rb diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/products_rule.rb b/friendly_promotions/app/models/solidus_friendly_promotions/products_rule.rb new file mode 100644 index 00000000000..d33f5970fa6 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/products_rule.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + class ProductsRule < Spree::Base + belongs_to :product, class_name: "Spree::Product" + belongs_to :rule + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb index 34847df0ba7..1c35ffd0ec4 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb @@ -7,9 +7,8 @@ module Rules # either come from assigned product group or are assingned directly to # the rule. class Product < Rule - has_many :product_promotion_rules, dependent: :destroy, foreign_key: :promotion_rule_id, - class_name: "Spree::ProductPromotionRule" - has_many :products, class_name: "Spree::Product", through: :product_promotion_rules + has_many :products_rules, inverse_of: :rule, dependent: :destroy + has_many :products, class_name: "Spree::Product", through: :products_rules def preload_relations [:products] diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb index baab2f1666d..4c3ed6446c4 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb @@ -3,10 +3,8 @@ module SolidusFriendlyPromotions module Rules class Store < Rule - has_many :promotion_rule_stores, class_name: "Spree::PromotionRuleStore", - foreign_key: :promotion_rule_id, - dependent: :destroy - has_many :stores, through: :promotion_rule_stores, class_name: "Spree::Store" + has_many :rules_stores, inverse_of: :rule, dependent: :destroy + has_many :stores, through: :rules_stores, class_name: "Spree::Store" def preload_relations [:stores] diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb index ea49627dfa0..811f7998295 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb @@ -3,9 +3,8 @@ module SolidusFriendlyPromotions module Rules class Taxon < Rule - has_many :promotion_rule_taxons, class_name: "Spree::PromotionRuleTaxon", foreign_key: :promotion_rule_id, - dependent: :destroy - has_many :taxons, through: :promotion_rule_taxons, class_name: "Spree::Taxon" + has_many :rules_taxons, inverse_of: :rule, dependent: :destroy + has_many :taxons, through: :rules_taxons, class_name: "Spree::Taxon" def preload_relations [:taxons] diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb index 2fcadd2e989..023b1b9e92c 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb @@ -3,10 +3,8 @@ module SolidusFriendlyPromotions module Rules class User < Rule - has_many :promotion_rule_users, class_name: "Spree::PromotionRuleUser", - foreign_key: :promotion_rule_id, - dependent: :destroy - has_many :users, through: :promotion_rule_users, class_name: Spree::UserClassHandle.new + has_many :rules_users, inverse_of: :rule, dependent: :destroy + has_many :users, through: :rules_users, class_name: Spree::UserClassHandle.new def preload_relations [:users] diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules_store.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules_store.rb new file mode 100644 index 00000000000..a4ab6dae755 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules_store.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + class RulesStore < Spree::Base + belongs_to :rule + belongs_to :store, class_name: "Spree::Store" + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules_taxon.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules_taxon.rb new file mode 100644 index 00000000000..f9858940773 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules_taxon.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + class RulesTaxon < Spree::Base + belongs_to :rule + belongs_to :taxon, class_name: "Spree::Taxon" + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules_user.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules_user.rb new file mode 100644 index 00000000000..3283ce6f8a4 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules_user.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + class RulesUser < Spree::Base + belongs_to :rule + belongs_to :user, class_name: Spree::UserClassHandle.new + end +end diff --git a/friendly_promotions/db/migrate/20230704083830_add_rule_tables.rb b/friendly_promotions/db/migrate/20230704083830_add_rule_tables.rb new file mode 100644 index 00000000000..89e49b35e37 --- /dev/null +++ b/friendly_promotions/db/migrate/20230704083830_add_rule_tables.rb @@ -0,0 +1,31 @@ +class AddRuleTables < ActiveRecord::Migration[7.0] + def change + create_table :solidus_friendly_promotions_products_rules, force: :cascade do |t| + t.references :product, index: true, null: false, foreign_key: { to_table: :spree_products } + t.references :rule, index: true, null: false, foreign_key: { to_table: :solidus_friendly_promotions_rules } + + t.timestamps + end + + create_table :solidus_friendly_promotions_rules_taxons, force: :cascade do |t| + t.references :taxon, index: true, null: false, foreign_key: { to_table: :spree_taxons } + t.references :rule, index: true, null: false, foreign_key: { to_table: :solidus_friendly_promotions_rules } + + t.timestamps + end + + create_table :solidus_friendly_promotions_rules_users, force: :cascade do |t| + t.references :user, index: true, null: false, foreign_key: { to_table: Spree.user_class.table_name } + t.references :rule, index: true, null: false, foreign_key: { to_table: :solidus_friendly_promotions_rules } + + t.timestamps + end + + create_table :solidus_friendly_promotions_rules_stores do |t| + t.references :store, index: true, null: false, foreign_key: { to_table: :spree_users } + t.references :rule, index: true, null: false + + t.timestamps + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/products_rule_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/products_rule_spec.rb new file mode 100644 index 00000000000..3d8f7a01044 --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/products_rule_spec.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SolidusFriendlyPromotions::ProductsRule do + it { is_expected.to belong_to(:product) } + it { is_expected.to belong_to(:rule) } +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/product_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/product_spec.rb index 192f20531df..19ec4525fd5 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/product_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/product_spec.rb @@ -3,7 +3,9 @@ require "spec_helper" RSpec.describe SolidusFriendlyPromotions::Rules::Product, type: :model do - let(:rule) { SolidusFriendlyPromotions::Rules::Product.new(rule_options) } + it { is_expected.to have_many(:products) } + + let(:rule) { described_class.new(rule_options) } let(:rule_options) { {} } context "#eligible?(order)" do diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/store_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/store_spec.rb index f7e3b558bc9..50120abb572 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/store_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/store_spec.rb @@ -3,6 +3,8 @@ require "spec_helper" RSpec.describe SolidusFriendlyPromotions::Rules::Store, type: :model do + it { is_expected.to have_many(:stores) } + let(:rule) { described_class.new } context "#eligible?(order)" do diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/taxon_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/taxon_spec.rb index 0e24cec66cd..40d692b5855 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/taxon_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/taxon_spec.rb @@ -3,6 +3,8 @@ require "spec_helper" RSpec.describe SolidusFriendlyPromotions::Rules::Taxon, type: :model do + it { is_expected.to have_many(:taxons) } + let(:taxon) { create :taxon, name: "first" } let(:taxon2) { create :taxon, name: "second" } let(:order) { create :order_with_line_items } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_spec.rb index 56bf4b4a97f..0b78257bb31 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_spec.rb @@ -3,7 +3,8 @@ require "spec_helper" RSpec.describe SolidusFriendlyPromotions::Rules::User, type: :model do - let(:rule) { SolidusFriendlyPromotions::Rules::User.new } + it { is_expected.to have_many(:users) } + let(:rule) { described_class.new } context "#eligible?(order)" do let(:order) { Spree::Order.new } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules_store_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules_store_spec.rb new file mode 100644 index 00000000000..c0b2635a37e --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules_store_spec.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SolidusFriendlyPromotions::RulesStore do + it { is_expected.to belong_to(:store) } + it { is_expected.to belong_to(:rule) } +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules_taxon_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules_taxon_spec.rb new file mode 100644 index 00000000000..cceff860d66 --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules_taxon_spec.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SolidusFriendlyPromotions::RulesTaxon do + it { is_expected.to belong_to(:taxon) } + it { is_expected.to belong_to(:rule) } +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules_user_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules_user_spec.rb new file mode 100644 index 00000000000..0fdb7b479ce --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules_user_spec.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SolidusFriendlyPromotions::RulesUser do + it { is_expected.to belong_to(:user) } + it { is_expected.to belong_to(:rule) } +end From f0a02203f47bc38b983335cad30e97cb04025792 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 4 Jul 2023 11:53:40 +0200 Subject: [PATCH 080/524] Add Action base class This combines the previous "Base" class from this gem and the "PromotionAction" base class from Solidus. --- .../solidus_friendly_promotions/action.rb | 59 +++++++++++++++++++ .../actions/adjust_line_item.rb | 2 +- .../actions/adjust_shipment.rb | 2 +- .../actions/base.rb | 51 ---------------- .../solidus_friendly_promotions/promotion.rb | 1 + .../migrate/20230704093625_create_actions.rb | 14 +++++ .../{actions/base_spec.rb => action_spec.rb} | 5 +- 7 files changed, 80 insertions(+), 54 deletions(-) create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/action.rb delete mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/actions/base.rb create mode 100644 friendly_promotions/db/migrate/20230704093625_create_actions.rb rename friendly_promotions/spec/models/solidus_friendly_promotions/{actions/base_spec.rb => action_spec.rb} (87%) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/action.rb b/friendly_promotions/app/models/solidus_friendly_promotions/action.rb new file mode 100644 index 00000000000..14195714c51 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/action.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true +require 'spree/preferences/persistable' + +module SolidusFriendlyPromotions + # Base class for all types of promotion action. + # + # PromotionActions perform the necessary tasks when a promotion is activated + # by an event and determined to be eligible. + class Action < Spree::Base + include Spree::Preferences::Persistable + include Spree::SoftDeletable + include Spree::CalculatedAdjustments + include Spree::AdjustmentSource + + belongs_to :promotion, inverse_of: :actions + has_many :adjustments, as: :source + + scope :of_type, ->(type) { where(type: Array.wrap(type).map(&:to_s)) } + + def preload_relations + [:calculator] + end + + def can_adjust?(object) + raise NotImplementedError + end + + def adjust(adjustable) + adjustment = adjustable.adjustments.detect do |adjustment| + adjustment.source == self + end || adjustable.adjustments.build(source: self, order: adjustable.order) + adjustment.label = adjustment_label(adjustable) + adjustment.amount = compute_amount(adjustable) + adjustment + end + + # Ensure a negative amount which does not exceed the object's amount + def compute_amount(adjustable) + promotion_amount = calculator.compute(adjustable) || BigDecimal(0) + [adjustable.amount, promotion_amount.abs].min * -1 + end + + def adjustment_label(adjustable) + I18n.t( + "spree.adjustment_labels.#{adjustable.class.name.demodulize.underscore}", + promotion: Spree::Promotion.model_name.human, + promotion_name: promotion.name, + ) + end + + def to_partial_path + "solidus_friendly_promotions/admin/promotion_actions/actions/#{model_name.element}" + end + + def available_calculators + raise NotImplementedError + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb index d345eb551b8..b106f84af22 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Actions - class AdjustLineItem < Base + class AdjustLineItem < Action def can_adjust?(object) object.is_a? Spree::LineItem end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb index 6d19427113e..a14c953c7d0 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Actions - class AdjustShipment < Base + class AdjustShipment < Action def can_adjust?(object) object.is_a? Spree::Shipment end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/actions/base.rb b/friendly_promotions/app/models/solidus_friendly_promotions/actions/base.rb deleted file mode 100644 index 949a061c58d..00000000000 --- a/friendly_promotions/app/models/solidus_friendly_promotions/actions/base.rb +++ /dev/null @@ -1,51 +0,0 @@ -# frozen_string_literal: true - -module SolidusFriendlyPromotions - module Actions - class Base < ::Spree::PromotionAction - include Spree::CalculatedAdjustments - include Spree::AdjustmentSource - - has_many :adjustments, as: :source - - def preload_relations - [:calculator] - end - - def can_adjust?(object) - raise NotImplementedError - end - - def adjust(adjustable) - adjustment = adjustable.adjustments.detect do |adjustment| - adjustment.source == self - end || adjustable.adjustments.build(source: self, order: adjustable.order) - adjustment.label = adjustment_label(adjustable) - adjustment.amount = compute_amount(adjustable) - adjustment - end - - # Ensure a negative amount which does not exceed the object's amount - def compute_amount(adjustable) - promotion_amount = calculator.compute(adjustable) || BigDecimal(0) - [adjustable.amount, promotion_amount.abs].min * -1 - end - - def adjustment_label(adjustable) - I18n.t( - "spree.adjustment_labels.#{adjustable.class.name.demodulize.underscore}", - promotion: Spree::Promotion.model_name.human, - promotion_name: promotion.name, - ) - end - - def to_partial_path - "solidus_friendly_promotions/admin/promotion_actions/actions/#{model_name.element}" - end - - def available_calculators - raise NotImplementedError - end - end - end -end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb index 28fa0c05c81..13c1d23c690 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb @@ -4,6 +4,7 @@ module SolidusFriendlyPromotions class Promotion < Spree::Base belongs_to :category, optional: true has_many :rules + has_many :actions validates :name, presence: true validates :path, uniqueness: { allow_blank: true, case_sensitive: true } diff --git a/friendly_promotions/db/migrate/20230704093625_create_actions.rb b/friendly_promotions/db/migrate/20230704093625_create_actions.rb new file mode 100644 index 00000000000..41e7cb21120 --- /dev/null +++ b/friendly_promotions/db/migrate/20230704093625_create_actions.rb @@ -0,0 +1,14 @@ +class CreateActions < ActiveRecord::Migration[7.0] + def change + create_table :solidus_friendly_promotions_actions do |t| + t.references :promotion, index: true, null: false, foreign_key: { to_table: :solidus_friendly_promotions_promotions } + t.string :type + t.datetime :deleted_at, precision: nil + t.text :preferences + t.index [:deleted_at], name: :index_solidus_friendly_promotions_actions_on_deleted_at + t.index [:id, :type], name: :index_solidus_friendly_promotions_actions_on_id_and_type + + t.timestamps + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/actions/base_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/action_spec.rb similarity index 87% rename from friendly_promotions/spec/models/solidus_friendly_promotions/actions/base_spec.rb rename to friendly_promotions/spec/models/solidus_friendly_promotions/action_spec.rb index 7e967b4aef9..63217e6b1a3 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/actions/base_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/action_spec.rb @@ -2,7 +2,10 @@ require "spec_helper" -RSpec.describe SolidusFriendlyPromotions::Actions::Base do +RSpec.describe SolidusFriendlyPromotions::Action do + it { is_expected.to belong_to(:promotion) } + it { is_expected.to have_one(:calculator) } + it { is_expected.to respond_to :adjust } it { is_expected.to respond_to :can_adjust? } From cbf9241100520f060e61f16eb61c4371e2b5e51f Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 4 Jul 2023 12:10:59 +0200 Subject: [PATCH 081/524] Add Friendly Promotion Factory --- .../testing_support/factories.rb | 2 + .../friendly_promotion_factory.rb | 100 ++++++++++++++++++ .../testing_support/factories_spec.rb | 18 ++++ 3 files changed, 120 insertions(+) create mode 100644 friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_factory.rb create mode 100644 friendly_promotions/spec/lib/solidus_friendly_promotions/testing_support/factories_spec.rb diff --git a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/factories.rb b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/factories.rb index 745a01e4c27..0b97b8fd914 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/factories.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/factories.rb @@ -1,4 +1,6 @@ # frozen_string_literal: true +require "solidus_friendly_promotions/testing_support/friendly_promotion_factory" + FactoryBot.define do end diff --git a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_factory.rb b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_factory.rb new file mode 100644 index 00000000000..b48f67d86ad --- /dev/null +++ b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_factory.rb @@ -0,0 +1,100 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :friendly_promotion, class: 'SolidusFriendlyPromotions::Promotion' do + name { 'Promo' } + + transient do + code { nil } + end + before(:create) do |promotion, evaluator| + if evaluator.code + promotion.codes << build(:friendly_promotion_code, promotion: promotion, value: evaluator.code) + end + end + + trait :with_action do + transient do + promotion_action_class { SolidusFriendlyPromotions::Actions::AdjustLineItem } + end + + after(:create) do |promotion, evaluator| + promotion.actions << evaluator.promotion_action_class.new + end + end + + trait :with_adjustable_action do + transient do + preferred_amount { 10 } + calculator_class { SolidusFriendlyPromotions::Calculators::FlatRate } + promotion_action_class { SolidusFriendlyPromotions::Actions::AdjustLineItem } + end + + after(:create) do |promotion, evaluator| + calculator = evaluator.calculator_class.new + calculator.preferred_amount = evaluator.preferred_amount + evaluator.promotion_action_class.create!(calculator: calculator, promotion: promotion) + end + end + + factory :friendly_promotion_with_action_adjustment, traits: [:with_adjustable_action] + + trait :with_line_item_adjustment do + transient do + adjustment_rate { 10 } + end + + with_adjustable_action + preferred_amount { adjustment_rate } + end + + factory :friendly_promotion_with_item_adjustment, traits: [:with_line_item_adjustment] + + trait :with_free_shipping do + after(:create) do |promotion| + calculator = SolidusFriendlyPromotions::Calculators::Percent.new(preferred_percent: 100) + + SolidusFriendlyPromotions::Actions::AdjustShipment.create!(promotion: promotion, calculator: calculator) + end + end + + trait :with_order_adjustment do + transient do + weighted_order_adjustment_amount { 10 } + end + + with_adjustable_action + preferred_amount { weighted_order_adjustment_amount } + calculator_class { SolidusFriendlyPromotions::Calculators::DistributedAmount } + end + + factory :friendly_promotion_with_order_adjustment, traits: [:with_order_adjustment] + + trait :with_item_total_rule do + transient do + item_total_threshold_amount { 10 } + end + + after(:create) do |promotion, evaluator| + rule = SolidusFriendlyPromotions::Rules::ItemTotal.create!( + promotion: promotion, + preferred_operator: 'gte', + preferred_amount: evaluator.item_total_threshold_amount + ) + promotion.rules << rule + promotion.save! + end + end + factory :friendly_promotion_with_item_total_rule, traits: [:with_item_total_rule] + trait :with_first_order_rule do + after(:create) do |promotion, _evaluator| + rule = SolidusFriendlyPromotions::Rules::FirstOrder.create!( + promotion: promotion, + ) + promotion.rules << rule + promotion.save! + end + end + factory :friendly_promotion_with_first_order_rule, traits: [:with_first_order_rule] + end +end diff --git a/friendly_promotions/spec/lib/solidus_friendly_promotions/testing_support/factories_spec.rb b/friendly_promotions/spec/lib/solidus_friendly_promotions/testing_support/factories_spec.rb new file mode 100644 index 00000000000..23ee0b5c8c8 --- /dev/null +++ b/friendly_promotions/spec/lib/solidus_friendly_promotions/testing_support/factories_spec.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe "Friendly Factories" do + it "has a bunch of working factories" do + [ + :friendly_promotion, + :friendly_promotion_with_action_adjustment, + :friendly_promotion_with_first_order_rule, + :friendly_promotion_with_item_adjustment, + :friendly_promotion_with_item_total_rule, + :friendly_promotion_with_order_adjustment + ].each do |factory| + expect { FactoryBot.create(factory) }.not_to raise_exception + end + end +end From eb2cc5ce7dd7b4b9b850bb6f74932d1fbf2a96cb Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 4 Jul 2023 12:16:55 +0200 Subject: [PATCH 082/524] Use friendly promotion in specs --- .../rules/line_item_taxon_spec.rb | 2 +- .../rules/one_use_per_user_spec.rb | 2 +- .../solidus_friendly_promotions/rules/product_spec.rb | 6 +++--- .../models/solidus_friendly_promotions/rules/taxon_spec.rb | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/line_item_taxon_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/line_item_taxon_spec.rb index c65fe5e792a..fa2fe051259 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/line_item_taxon_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/line_item_taxon_spec.rb @@ -9,7 +9,7 @@ let(:product) { order.products.first } let(:rule) do - described_class.create!(promotion: create(:promotion)) + described_class.create!(promotion: create(:friendly_promotion)) end describe "#eligible?" do diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/one_use_per_user_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/one_use_per_user_spec.rb index d95968fff81..d04bf2c16d5 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/one_use_per_user_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/one_use_per_user_spec.rb @@ -9,7 +9,7 @@ subject { rule.eligible?(order) } let(:order) { double Spree::Order, user: user } let(:user) { double Spree::LegacyUser } - let(:promotion) { stub_model Spree::Promotion, used_by?: used_by } + let(:promotion) { stub_model SolidusFriendlyPromotions::Promotion, used_by?: used_by } let(:used_by) { false } before { rule.promotion = promotion } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/product_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/product_spec.rb index 19ec4525fd5..aa2ab511209 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/product_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/product_spec.rb @@ -107,9 +107,9 @@ context "with an invalid match policy" do let(:rule) do SolidusFriendlyPromotions::Rules::Product.create!( - promotion: create(:promotion), - product_promotion_rules: [ - Spree::ProductPromotionRule.new(product: product) + promotion: create(:friendly_promotion), + products_rules: [ + SolidusFriendlyPromotions::ProductsRule.new(product: product) ] ).tap do |rule| rule.preferred_match_policy = "invalid" diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/taxon_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/taxon_spec.rb index 40d692b5855..c3108065d28 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/taxon_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/taxon_spec.rb @@ -11,7 +11,7 @@ let(:product) { order.products.first } let(:rule) do - SolidusFriendlyPromotions::Rules::Taxon.create!(promotion: create(:promotion)) + SolidusFriendlyPromotions::Rules::Taxon.create!(promotion: create(:friendly_promotion)) end context "#eligible?(order)" do From 236f105fe230672402e2fdbfd8077ca46c8f441f Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 4 Jul 2023 12:24:26 +0200 Subject: [PATCH 083/524] Use SFP Promotions in our admin This doesn't work yet because codes are missing. --- .../admin/promotions_controller.rb | 8 ++++++-- .../app/models/solidus_friendly_promotions/promotion.rb | 4 ++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb index 57af8f7bc7a..c5a5d43d02e 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb @@ -44,11 +44,15 @@ def collection end def promotion_includes - [:promotion_actions] + [:actions] + end + + def model_class + SolidusFriendlyPromotions::Promotion end def load_data - @promotion_categories = Spree::PromotionCategory.order(:name) + @promotion_categories = Category.order(:name) end def location_after_save diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb index 13c1d23c690..8c1d07a04ed 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb @@ -11,5 +11,9 @@ class Promotion < Spree::Base validates :usage_limit, numericality: { greater_than: 0, allow_nil: true } validates :per_code_usage_limit, numericality: { greater_than_or_equal_to: 0, allow_nil: true } validates :description, length: { maximum: 255 } + + self.allowed_ransackable_associations = ['codes'] + self.allowed_ransackable_attributes = %w[name path promotion_category_id] + self.allowed_ransackable_scopes = %i[active] end end From dd582842dcd729fc89a5ed05cbaad6ec28916ffa Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 4 Jul 2023 14:11:22 +0200 Subject: [PATCH 084/524] Add promotion code, code batches and related classes This chunky commit adds a lot of code from Solidus core. --- .../code_batch_job.rb | 26 ++ .../code_batch_mailer.rb | 15 ++ .../solidus_friendly_promotions/code.rb | 55 ++++ .../code/batch_builder.rb | 68 +++++ .../solidus_friendly_promotions/code_batch.rb | 27 ++ .../solidus_friendly_promotions/promotion.rb | 2 + .../code_batch_errored.text.erb | 2 + .../code_batch_finished.text.erb | 2 + friendly_promotions/config/locales/en.yml | 13 + .../db/migrate/20230704102444_create_codes.rb | 11 + .../20230704102656_create_code_batches.rb | 39 +++ .../configuration.rb | 1 + .../code_batch_job_spec.rb | 96 +++++++ .../code_batch_mailer_spec.rb | 48 ++++ .../code/batch_builder_spec.rb | 107 ++++++++ .../code_batch_spec.rb | 54 ++++ .../solidus_friendly_promotions/code_spec.rb | 250 ++++++++++++++++++ 17 files changed, 816 insertions(+) create mode 100644 friendly_promotions/app/jobs/solidus_friendly_promotions/code_batch_job.rb create mode 100644 friendly_promotions/app/mailers/solidus_friendly_promotions/code_batch_mailer.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/code.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/code/batch_builder.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/code_batch.rb create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/code_batch_mailer/code_batch_errored.text.erb create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/code_batch_mailer/code_batch_finished.text.erb create mode 100644 friendly_promotions/db/migrate/20230704102444_create_codes.rb create mode 100644 friendly_promotions/db/migrate/20230704102656_create_code_batches.rb create mode 100644 friendly_promotions/spec/jobs/solidus_friendly_promotions/code_batch_job_spec.rb create mode 100644 friendly_promotions/spec/mailers/solidus_friendly_promotions/code_batch_mailer_spec.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/code/batch_builder_spec.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/code_batch_spec.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/code_spec.rb diff --git a/friendly_promotions/app/jobs/solidus_friendly_promotions/code_batch_job.rb b/friendly_promotions/app/jobs/solidus_friendly_promotions/code_batch_job.rb new file mode 100644 index 00000000000..d30dbda0e25 --- /dev/null +++ b/friendly_promotions/app/jobs/solidus_friendly_promotions/code_batch_job.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + class CodeBatchJob < ActiveJob::Base + queue_as :default + + def perform(code_batch) + Code::BatchBuilder.new( + code_batch + ).build_promotion_codes + + if code_batch.email? + SolidusFriendlyPromotions.config.code_batch_mailer_class + .code_batch_finished(code_batch) + .deliver_now + end + rescue StandardError => error + if code_batch.email? + SolidusFriendlyPromotions.config.code_batch_mailer_class + .code_batch_errored(code_batch) + .deliver_now + end + raise error + end + end +end diff --git a/friendly_promotions/app/mailers/solidus_friendly_promotions/code_batch_mailer.rb b/friendly_promotions/app/mailers/solidus_friendly_promotions/code_batch_mailer.rb new file mode 100644 index 00000000000..11078ae985a --- /dev/null +++ b/friendly_promotions/app/mailers/solidus_friendly_promotions/code_batch_mailer.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + class CodeBatchMailer < Spree::BaseMailer + def code_batch_finished(code_batch) + @code_batch = code_batch + mail(to: code_batch.email) + end + + def code_batch_errored(code_batch) + @code_batch = code_batch + mail(to: code_batch.email) + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/code.rb b/friendly_promotions/app/models/solidus_friendly_promotions/code.rb new file mode 100644 index 00000000000..eec5fbe4a90 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/code.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + class Code < Spree::Base + belongs_to :promotion, inverse_of: :codes + belongs_to :code_batch, inverse_of: :codes, optional: true + has_many :adjustments + + before_validation :normalize_code + + validates :value, presence: true, uniqueness: { allow_blank: true, case_sensitive: true } + validate :promotion_not_apply_automatically, on: :create + + self.allowed_ransackable_attributes = ['value'] + + # Whether the promotion code has exceeded its usage restrictions + # + # @param excluded_orders [Array] Orders to exclude from usage limit + # @return true or false + def usage_limit_exceeded?(excluded_orders: []) + if usage_limit + usage_count(excluded_orders: excluded_orders) >= usage_limit + end + end + + # Number of times the code has been used overall + # + # @param excluded_orders [Array] Orders to exclude from usage count + # @return [Integer] usage count + def usage_count(excluded_orders: []) + promotion. + discounted_orders. + complete. + where.not(spree_orders: { state: :canceled }). + joins(:order_promotions). + where(spree_orders_promotions: { promotion_code_id: self.id }). + where.not(id: excluded_orders.map(&:id)). + count + end + + def usage_limit + promotion.per_code_usage_limit + end + + def promotion_not_apply_automatically + errors.add(:base, :disallowed_with_apply_automatically) if promotion.apply_automatically + end + + private + + def normalize_code + self.value = value.downcase.strip + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/code/batch_builder.rb b/friendly_promotions/app/models/solidus_friendly_promotions/code/batch_builder.rb new file mode 100644 index 00000000000..5ef85e50ce9 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/code/batch_builder.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + class Code < Spree::Base + class BatchBuilder + attr_reader :code_batch, :options + delegate :promotion, :number_of_codes, :base_code, to: :code_batch + + DEFAULT_OPTIONS = { + random_code_length: 6, + batch_size: 1000, + sample_characters: ('a'..'z').to_a + (2..9).to_a.map(&:to_s) + } + + def initialize(code_batch, options = {}) + @code_batch = code_batch + options.assert_valid_keys(*DEFAULT_OPTIONS.keys) + @options = DEFAULT_OPTIONS.merge(options) + end + + def build_promotion_codes + generate_random_codes + code_batch.update!(state: "completed") + rescue StandardError => error + code_batch.update!( + error: error.inspect, + state: "failed" + ) + raise error + end + + private + + def generate_random_codes + created_codes = 0 + batch_size = @options[:batch_size] + + while created_codes < number_of_codes + max_codes_to_generate = [batch_size, number_of_codes - created_codes].min + + new_codes = Array.new(max_codes_to_generate) { generate_random_code }.uniq + codes_for_current_batch = get_unique_codes(new_codes) + + codes_for_current_batch.each do |value| + Code.create!( + value: value, + promotion: promotion, + code_batch: code_batch + ) + end + created_codes += codes_for_current_batch.size + end + end + + def generate_random_code + suffix = Array.new(@options[:random_code_length]) do + @options[:sample_characters].sample + end.join + + "#{base_code}#{@code_batch.join_characters}#{suffix}" + end + + def get_unique_codes(code_set) + code_set - Code.where(value: code_set.to_a).pluck(:value) + end + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/code_batch.rb b/friendly_promotions/app/models/solidus_friendly_promotions/code_batch.rb new file mode 100644 index 00000000000..0d47e7ea970 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/code_batch.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + class CodeBatch < Spree::Base + class CantProcessStartedBatch < StandardError + end + + belongs_to :promotion + has_many :codes, dependent: :destroy + + validates :number_of_codes, numericality: { greater_than: 0 } + validates_presence_of :base_code, :number_of_codes + + def finished? + state == "completed" + end + + def process + if state == "pending" + update!(state: "processing") + CodeBatchJob.perform_later(self) + else + raise CantProcessStartedBatch.new("Batch #{id} already started") + end + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb index 8c1d07a04ed..f995272b5ae 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb @@ -5,6 +5,8 @@ class Promotion < Spree::Base belongs_to :category, optional: true has_many :rules has_many :actions + has_many :codes + has_many :code_batches validates :name, presence: true validates :path, uniqueness: { allow_blank: true, case_sensitive: true } diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/code_batch_mailer/code_batch_errored.text.erb b/friendly_promotions/app/views/solidus_friendly_promotions/code_batch_mailer/code_batch_errored.text.erb new file mode 100644 index 00000000000..21aea738112 --- /dev/null +++ b/friendly_promotions/app/views/solidus_friendly_promotions/code_batch_mailer/code_batch_errored.text.erb @@ -0,0 +1,2 @@ +<%= t(".message", error: @code_batch.error) %> +<%= @code_batch.promotion.name %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/code_batch_mailer/code_batch_finished.text.erb b/friendly_promotions/app/views/solidus_friendly_promotions/code_batch_mailer/code_batch_finished.text.erb new file mode 100644 index 00000000000..872138b0c1b --- /dev/null +++ b/friendly_promotions/app/views/solidus_friendly_promotions/code_batch_mailer/code_batch_finished.text.erb @@ -0,0 +1,2 @@ +<%= t(".message", number_of_codes: 10) %> +<%= @code_batch.promotion.name %> diff --git a/friendly_promotions/config/locales/en.yml b/friendly_promotions/config/locales/en.yml index 5d0b0fc91d8..62331987703 100644 --- a/friendly_promotions/config/locales/en.yml +++ b/friendly_promotions/config/locales/en.yml @@ -69,6 +69,13 @@ en: starts_at_placeholder: Immediately edit: order_rules: Order Rules + code_batch_mailer: + code_batch_errored: + message: 'Promotion code batch errored (%{error}) for promotion: ' + subject: Promotion code batch errored + code_batch_finished: + message: 'All %{number_of_codes} codes have been created for promotion: ' + subject: Promotion code batch finished activerecord: models: solidus_friendly_promotions/actions/adjust_shipment: Discount matching shipments @@ -136,3 +143,9 @@ en: description: Available only to logged in users solidus_friendly_promotions/rules/user_role: description: Order includes User with specified Role(s) + errors: + models: + solidus_friendly_promotions/code: + attributes: + base: + disallowed_with_apply_automatically: Could not create promotion code on promotion that apply automatically diff --git a/friendly_promotions/db/migrate/20230704102444_create_codes.rb b/friendly_promotions/db/migrate/20230704102444_create_codes.rb new file mode 100644 index 00000000000..022eed3aa3f --- /dev/null +++ b/friendly_promotions/db/migrate/20230704102444_create_codes.rb @@ -0,0 +1,11 @@ +class CreateCodes < ActiveRecord::Migration[7.0] + def change + create_table :solidus_friendly_promotions_codes, force: :cascade do |t| + t.references :promotion, null: false, index: true, foreign_key: { to_table: :solidus_friendly_promotions_promotions } + t.string :value, null: false + t.timestamps + + t.index ["value"], name: "index_solidus_friendly_promotions_codes_on_value", unique: true + end + end +end diff --git a/friendly_promotions/db/migrate/20230704102656_create_code_batches.rb b/friendly_promotions/db/migrate/20230704102656_create_code_batches.rb new file mode 100644 index 00000000000..f70db0ec4ff --- /dev/null +++ b/friendly_promotions/db/migrate/20230704102656_create_code_batches.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +class CreateCodeBatches < ActiveRecord::Migration[7.0] + def change + create_table :solidus_friendly_promotions_code_batches do |t| + t.references :promotion, null: false, index: true, foreign_key: { to_table: :solidus_friendly_promotions_promotions } + t.string :base_code, null: false + t.integer :number_of_codes, null: false + t.string :join_characters, null: false, default: "_" + t.string :email + t.string :error + t.string :state, default: "pending" + t.timestamps precision: 6 + end + + add_foreign_key( + :solidus_friendly_promotions_code_batches, + :solidus_friendly_promotions_promotions, + column: :promotion_id + ) + + add_column( + :solidus_friendly_promotions_codes, + :code_batch_id, + :integer + ) + + add_foreign_key( + :solidus_friendly_promotions_codes, + :solidus_friendly_promotions_code_batches, + column: :code_batch_id + ) + + add_index( + :solidus_friendly_promotions_codes, + :code_batch_id + ) + end +end diff --git a/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb b/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb index 825bca57453..32d5dcaac8c 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb @@ -16,6 +16,7 @@ class Configuration < Spree::Preferences::Configuration add_class_set :actions class_name_attribute :promotion_chooser_class, default: "SolidusFriendlyPromotions::PromotionAdjustmentChooser" + class_name_attribute :code_batch_mailer_class, default: "SolidusFriendlyPromotions::CodeBatchMailer" end class << self diff --git a/friendly_promotions/spec/jobs/solidus_friendly_promotions/code_batch_job_spec.rb b/friendly_promotions/spec/jobs/solidus_friendly_promotions/code_batch_job_spec.rb new file mode 100644 index 00000000000..bad0f6c2744 --- /dev/null +++ b/friendly_promotions/spec/jobs/solidus_friendly_promotions/code_batch_job_spec.rb @@ -0,0 +1,96 @@ +# frozen_string_literal: true + +require 'spec_helper' +RSpec.describe SolidusFriendlyPromotions::CodeBatchJob, type: :job do + let(:email) { "test@email.com" } + let(:code_batch) do + SolidusFriendlyPromotions::CodeBatch.create!( + promotion: create(:friendly_promotion), + base_code: "test", + number_of_codes: 10, + email: email + ) + end + context "with a successful build" do + before do + allow(SolidusFriendlyPromotions::CodeBatchMailer) + .to receive(:code_batch_finished) + .and_call_original + end + + def codes + SolidusFriendlyPromotions::Code.pluck(:value) + end + + context 'with the default join character' do + it 'uses the default join characters', :aggregate_failures do + subject.perform(code_batch) + codes.each do |code| + expect(code).to match(/^test_/) + end + end + end + context 'with a custom join character' do + let(:code_batch) do + SolidusFriendlyPromotions::CodeBatch.create!( + promotion: create(:friendly_promotion), + base_code: "test", + number_of_codes: 10, + email: email, + join_characters: '-' + ) + end + it 'uses the custom join characters', :aggregate_failures do + subject.perform(code_batch) + codes.each do |code| + expect(code).to match(/^test-/) + end + end + end + context "with an email address" do + it "sends an email" do + subject.perform(code_batch) + expect(SolidusFriendlyPromotions::CodeBatchMailer) + .to have_received(:code_batch_finished) + end + end + context "with no email address" do + let(:email) { nil } + it "sends an email" do + subject.perform(code_batch) + expect(SolidusFriendlyPromotions::CodeBatchMailer) + .to_not have_received(:code_batch_finished) + end + end + end + + context "with a failed build" do + before do + allow_any_instance_of(SolidusFriendlyPromotions::Code::BatchBuilder) + .to receive(:build_promotion_codes) + .and_raise("Error") + + allow(SolidusFriendlyPromotions::CodeBatchMailer) + .to receive(:code_batch_errored) + .and_call_original + + expect { subject.perform(code_batch) } + .to raise_error RuntimeError + end + + context "with an email address" do + it "sends an email" do + expect(SolidusFriendlyPromotions::CodeBatchMailer) + .to have_received(:code_batch_errored) + end + end + + context "with no email address" do + let(:email) { nil } + it "sends an email" do + expect(SolidusFriendlyPromotions::CodeBatchMailer) + .to_not have_received(:code_batch_errored) + end + end + end +end diff --git a/friendly_promotions/spec/mailers/solidus_friendly_promotions/code_batch_mailer_spec.rb b/friendly_promotions/spec/mailers/solidus_friendly_promotions/code_batch_mailer_spec.rb new file mode 100644 index 00000000000..129cd5a19e7 --- /dev/null +++ b/friendly_promotions/spec/mailers/solidus_friendly_promotions/code_batch_mailer_spec.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SolidusFriendlyPromotions::CodeBatchMailer, type: :mailer do + let(:promotion) { create(:friendly_promotion, name: "Promotion Test") } + let(:code_batch) do + SolidusFriendlyPromotions::CodeBatch.create!( + promotion_id: promotion.id, + base_code: "test", + number_of_codes: 10, + email: "test@email.com" + ) + end + + describe "#code_batch_finished" do + subject { described_class.code_batch_finished(code_batch) } + + it "sends the email to the email attached to the promotion code batch " do + expect(subject.to).to eq([code_batch.email]) + end + + it "contains the number of codes created" do + expect(subject.body).to include("All 10 codes have been created") + end + + it "contains the name of the promotion" do + expect(subject.body).to include(promotion.name) + end + end + + describe "#code_batch_errored" do + before { code_batch.update(error: "Test error") } + subject { described_class.code_batch_errored(code_batch) } + + it "sends the email to the email attached to the promotion code batch " do + expect(subject.to).to eq([code_batch.email]) + end + + it "contains the error" do + expect(subject.body).to include("Test error") + end + + it "contains the name of the promotion" do + expect(subject.body).to include(promotion.name) + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/code/batch_builder_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/code/batch_builder_spec.rb new file mode 100644 index 00000000000..4d0e3156a71 --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/code/batch_builder_spec.rb @@ -0,0 +1,107 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe SolidusFriendlyPromotions::Code::BatchBuilder do + let(:promotion) { create(:friendly_promotion) } + let(:base_code) { "abc" } + let(:options) { {} } + let(:number_of_codes) { 10 } + let(:code_batch) do + SolidusFriendlyPromotions::CodeBatch.create!( + promotion_id: promotion.id, + base_code: base_code, + number_of_codes: number_of_codes, + email: "test@email.com" + ) + end + + subject { described_class.new(code_batch, options) } + + describe "#build_promotion_codes" do + context "with a failed build" do + before do + allow(subject).to receive(:generate_random_codes).and_raise "Error" + + expect { subject.build_promotion_codes }.to raise_error RuntimeError + end + + it "updates the error and state on promotion batch" do + expect(code_batch.reload.error).to eq("#") + expect(code_batch.reload.state).to eq("failed") + end + end + + context "with a successful build" do + before do + allow(Spree::PromotionCodeBatchMailer) + .to receive(:code_batch_finished) + .and_call_original + + subject.build_promotion_codes + end + + it "update the promotion codes count for the batch" do + expect(code_batch.codes.count).to eq(10) + end + + it "builds the correct number of codes" do + expect(subject.promotion.codes.size).to eq(10) + end + + it "builds codes with distinct values" do + expect(subject.promotion.codes.map(&:value).uniq.size).to eq(10) + end + + it "updates the promotion code batch state to completed" do + expect(code_batch.state).to eq("completed") + end + end + + context "with likely code contention" do + let(:number_of_codes) { 50 } + let(:options) do + { + batch_size: 10, + sample_characters: (0..9).to_a.map(&:to_s), + random_code_length: 2 + } + end + + it "creates the correct number of codes" do + subject.build_promotion_codes + expect(promotion.codes.size).to eq(number_of_codes) + end + end + end + + describe "#join_character" do + context "with the default join charachter _" do + it "builds codes with the same base prefix" do + subject.build_promotion_codes + + values = subject.promotion.codes.map(&:value) + expect(values.all? { |val| val.starts_with?("#{base_code}_") }).to be true + end + end + + context "with a custom join separator" do + let(:code_batch) do + SolidusFriendlyPromotions::CodeBatch.create!( + promotion_id: promotion.id, + base_code: base_code, + number_of_codes: number_of_codes, + email: "test@email.com", + join_characters: "x" + ) + end + + it "builds codes with the same base prefix" do + subject.build_promotion_codes + + values = subject.promotion.codes.map(&:value) + expect(values.all? { |val| val.starts_with?("#{base_code}x") }).to be true + end + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/code_batch_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/code_batch_spec.rb new file mode 100644 index 00000000000..b4b23104249 --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/code_batch_spec.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe SolidusFriendlyPromotions::CodeBatch, type: :model do + subject do + described_class.create!( + promotion_id: create(:friendly_promotion).id, + base_code: "abc", + number_of_codes: 1, + error: nil, + email: "test@email.com" + ) + end + + describe "#process" do + context "with a pending code batch" do + it "should call the worker" do + expect { subject.process } + .to have_enqueued_job(SolidusFriendlyPromotions::CodeBatchJob) + end + + it "should update the state to processing" do + subject.process + + expect(subject.state).to eq("processing") + end + end + + context "with a processing batch" do + before { subject.update_attribute(:state, "processing") } + + it "should raise an error" do + expect{ subject.process }.to raise_error SolidusFriendlyPromotions::CodeBatch::CantProcessStartedBatch + end + end + + context "with a completed batch" do + before { subject.update_attribute(:state, "completed") } + + it "should raise an error" do + expect{ subject.process }.to raise_error SolidusFriendlyPromotions::CodeBatch::CantProcessStartedBatch + end + end + + context "with a failed batch" do + before { subject.update_attribute(:state, "failed") } + + it "should raise an error" do + expect{ subject.process }.to raise_error SolidusFriendlyPromotions::CodeBatch::CantProcessStartedBatch + end + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/code_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/code_spec.rb new file mode 100644 index 00000000000..f80cbbcf661 --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/code_spec.rb @@ -0,0 +1,250 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SolidusFriendlyPromotions::Code do + context 'callbacks' do + subject { promotion_code.save } + + describe '#normalize_code' do + let(:promotion) { create(:promotion, code: code) } + + before { subject } + + context 'when no other code with the same value exists' do + let(:promotion_code) { promotion.codes.first } + + context 'with mixed case' do + let(:code) { 'NewCoDe' } + + it 'downcases the value before saving' do + expect(promotion_code.value).to eq('newcode') + end + end + + context 'with extra spacing' do + let(:code) { ' new code ' } + + it 'removes surrounding whitespace' do + expect(promotion_code.value).to eq 'new code' + end + end + end + + context 'when another code with the same value exists' do + let(:promotion_code) { promotion.codes.build(value: code) } + + context 'with mixed case' do + let(:code) { 'NewCoDe' } + + it 'does not save the record and marks it as invalid' do + expect(promotion_code.valid?).to eq false + + expect(promotion_code.errors.messages[:value]).to contain_exactly( + 'has already been taken' + ) + end + end + + context 'with extra spacing' do + let(:code) { ' new code ' } + + it 'does not save the record and marks it as invalid' do + expect(promotion_code.valid?).to eq false + + expect(promotion_code.errors.messages[:value]).to contain_exactly( + 'has already been taken' + ) + end + end + end + end + end + + describe "#usage_limit_exceeded?" do + subject { code.usage_limit_exceeded? } + + shared_examples "it should" do + context "when there is a usage limit" do + context "and the limit is not exceeded" do + let(:usage_limit) { 10 } + it { is_expected.to be_falsy } + end + context "and the limit is exceeded" do + let(:usage_limit) { 1 } + context "on a different order" do + before do + FactoryBot.create( + :completed_order_with_promotion, + promotion: promotion + ) + code.adjustments.update_all(eligible: true) + end + it { is_expected.to be_truthy } + end + context "on the same order" do + it { is_expected.to be_falsy } + end + end + end + context "when there is no usage limit" do + let(:usage_limit) { nil } + it { is_expected.to be_falsy } + end + end + + let(:code) { promotion.codes.first } + + context "with an order-level adjustment" do + let(:promotion) do + FactoryBot.create( + :promotion, + :with_order_adjustment, + code: "discount", + per_code_usage_limit: usage_limit + ) + end + let(:promotable) do + FactoryBot.create( + :completed_order_with_promotion, + promotion: promotion + ) + end + it_behaves_like "it should" + end + + context "with an item-level adjustment" do + let(:promotion) do + FactoryBot.create( + :promotion, + :with_line_item_adjustment, + code: "discount", + per_code_usage_limit: usage_limit + ) + end + before do + promotion.actions.first.perform({ + order: order, + promotion: promotion, + promotion_code: code + }) + end + context "when there are multiple line items" do + let(:order) { FactoryBot.create(:order_with_line_items, line_items_count: 2) } + describe "the first item" do + let(:promotable) { order.line_items.first } + it_behaves_like "it should" + end + describe "the second item" do + let(:promotable) { order.line_items.last } + it_behaves_like "it should" + end + end + context "when there is a single line item" do + let(:order) { FactoryBot.create(:order_with_line_items) } + let(:promotable) { order.line_items.first } + it_behaves_like "it should" + end + end + end + + describe "#usage_count" do + let(:promotion) do + FactoryBot.create( + :promotion, + :with_order_adjustment, + code: "discount" + ) + end + let(:code) { promotion.codes.first } + + subject { code.usage_count } + + context "when the code is applied to a non-complete order" do + let(:order) { FactoryBot.create(:order_with_line_items) } + before { promotion.activate(order: order, promotion_code: code) } + it { is_expected.to eq 0 } + end + context "when the code is applied to a complete order" do + let!(:order) do + FactoryBot.create( + :completed_order_with_promotion, + promotion: promotion + ) + end + context "and the promo is eligible" do + it { is_expected.to eq 1 } + end + context "and the promo is ineligible" do + before { order.adjustments.promotion.update_all(eligible: false) } + it { is_expected.to eq 0 } + end + context "and the order is canceled" do + before { order.cancel! } + it { is_expected.to eq 0 } + it { expect(order.state).to eq 'canceled' } + end + end + end + + describe "completing multiple orders with the same code", slow: true do + let(:promotion) do + FactoryBot.create( + :promotion, + :with_order_adjustment, + code: "discount", + per_code_usage_limit: 1, + weighted_order_adjustment_amount: 10 + ) + end + let(:code) { promotion.codes.first } + let(:order) do + FactoryBot.create(:order_with_line_items, line_items_price: 40, shipment_cost: 0).tap do |order| + FactoryBot.create(:payment, amount: 30, order: order) + promotion.activate(order: order, promotion_code: code) + end + end + let(:promo_adjustment) { order.adjustments.promotion.first } + before do + order.next! until order.can_complete? + + FactoryBot.create(:order_with_line_items, line_items_price: 40, shipment_cost: 0).tap do |order| + FactoryBot.create(:payment, amount: 30, order: order) + promotion.activate(order: order, promotion_code: code) + order.next! until order.can_complete? + order.complete! + end + end + + it "makes the promotion ineligible" do + expect{ + order.complete + }.to change{ promo_adjustment.reload.eligible }.to(false) + end + + it "adjusts the promo_total" do + expect{ + order.complete + }.to change(order, :promo_total).by(10) + end + + it "increases the total to remove the promo" do + expect{ + order.complete + }.to change(order, :total).from(30).to(40) + end + + it "resets the state of the order" do + expect{ + order.complete + }.to change{ order.reload.state }.from("confirm").to("address") + end + end + + it "cannot create promotion code on apply automatically promotion" do + promotion = create(:promotion, apply_automatically: true) + expect { + create(:promotion_code, promotion: promotion) + }.to raise_error ActiveRecord::RecordInvalid, "Validation failed: Could not create promotion code on promotion that apply automatically" + end +end From a704fc124b27e828bc1a4fb9f0ee1f873a624b16 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 5 Jul 2023 10:31:19 +0200 Subject: [PATCH 085/524] Rename Category to PromotionCategory Things turn really funky in Rails routing otherwise. --- .../admin/promotion_categories_controller.rb | 9 +++++++ .../admin/promotions_controller.rb | 2 +- .../solidus_friendly_promotions/promotion.rb | 2 +- .../{category.rb => promotion_category.rb} | 2 +- .../admin/promotion_categories/index.html.erb | 26 +++++++++---------- .../admin/promotions/_form.html.erb | 4 +-- friendly_promotions/config/routes.rb | 3 ++- ...30703141116_create_promotion_categories.rb | 8 ++++-- ...ory_spec.rb => promotion_category_spec.rb} | 4 +-- 9 files changed, 37 insertions(+), 23 deletions(-) rename friendly_promotions/app/models/solidus_friendly_promotions/{category.rb => promotion_category.rb} (77%) rename friendly_promotions/spec/models/solidus_friendly_promotions/{category_spec.rb => promotion_category_spec.rb} (73%) diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_categories_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_categories_controller.rb index 02050100ba8..a9861ca3693 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_categories_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_categories_controller.rb @@ -3,6 +3,15 @@ module SolidusFriendlyPromotions module Admin class PromotionCategoriesController < Spree::Admin::ResourceController + private + + def model_class + SolidusFriendlyPromotions::PromotionCategory + end + + def routes_proxy + solidus_friendly_promotions + end end end end diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb index c5a5d43d02e..d42806b4f1d 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb @@ -52,7 +52,7 @@ def model_class end def load_data - @promotion_categories = Category.order(:name) + @promotion_categories = PromotionCategory.order(:name) end def location_after_save diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb index f995272b5ae..7b152b56c74 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions class Promotion < Spree::Base - belongs_to :category, optional: true + belongs_to :category, class_name: "SolidusFriendlyPromotions::PromotionCategory", foreign_key: :promotion_category_id, optional: true has_many :rules has_many :actions has_many :codes diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/category.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_category.rb similarity index 77% rename from friendly_promotions/app/models/solidus_friendly_promotions/category.rb rename to friendly_promotions/app/models/solidus_friendly_promotions/promotion_category.rb index b800d746abb..b7130ec00bc 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/category.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_category.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module SolidusFriendlyPromotions - class Category < Spree::Base + class PromotionCategory < Spree::Base has_many :promotions validates :name, presence: true diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_categories/index.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_categories/index.html.erb index f9c93f64a1a..ad0196414e8 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_categories/index.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_categories/index.html.erb @@ -1,8 +1,8 @@ -<% admin_breadcrumb(link_to plural_resource_name(Spree::Promotion), solidus_friendly_promotions.admin_promotions_path) %> -<% admin_breadcrumb(plural_resource_name(Spree::PromotionCategory)) %> +<% admin_breadcrumb(link_to plural_resource_name(SolidusFriendlyPromotions::Promotion), solidus_friendly_promotions.admin_promotions_path) %> +<% admin_breadcrumb(plural_resource_name(SolidusFriendlyPromotions::PromotionCategory)) %> <% content_for :page_actions do %> - <% if can?(:create, Spree::PromotionCategory) %> + <% if can?(:create, SolidusFriendlyPromotions::PromotionCategory) %>
  • <%= link_to t('solidus_friendly_promotions.new_promotion_category'), solidus_friendly_promotions.new_admin_promotion_category_path, class: 'btn btn-primary' %>
  • @@ -17,21 +17,21 @@ - <%= Spree::PromotionCategory.human_attribute_name :name %> - <%= Spree::PromotionCategory.human_attribute_name :code %> + <%= SolidusFriendlyPromotions::PromotionCategory.human_attribute_name :name %> + <%= SolidusFriendlyPromotions::PromotionCategory.human_attribute_name :code %> - <% @promotion_categories.each do |promotion_category| %> + <% @promotion_categories.each do |category| %> - <%= promotion_category.name %> - <%= promotion_category.code %> + <%= category.name %> + <%= category.code %> - <% if can?(:update, promotion_category) %> - <%= link_to_edit promotion_category, no_text: true %> + <% if can?(:update, category) %> + <%= link_to_edit category, no_text: true %> <% end %> - <% if can?(:destroy, promotion_category) %> - <%= link_to_delete promotion_category, no_text: true %> + <% if can?(:destroy, category) %> + <%= link_to_delete category, no_text: true %> <% end %> @@ -41,7 +41,7 @@ <% else %>
    <%= render 'spree/admin/shared/no_objects_found', - resource: Spree::PromotionCategory, + resource: SolidusFriendlyPromotions::PromotionCategory, new_resource_url: new_object_url %>
    <% end %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_form.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_form.html.erb index 405f88c6d01..5f89e10c3ca 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_form.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_form.html.erb @@ -18,9 +18,9 @@ <% end %> <%= f.field_container :category do %> - <%= f.label :promotion_category_id, Spree::PromotionCategory.model_name.human %>
    + <%= f.label :category_id, Spree::PromotionCategory.model_name.human %>
    <%= - f.collection_select(:promotion_category_id, @promotion_categories, :id, :name, { include_blank: t('spree.match_choices.none') }, + f.collection_select(:category_id, @promotion_categories, :id, :name, { include_blank: t('spree.match_choices.none') }, { class: 'custom-select fullwidth' }) %> <% end %> diff --git a/friendly_promotions/config/routes.rb b/friendly_promotions/config/routes.rb index 3ad87c0cbe9..1dd211b9c62 100644 --- a/friendly_promotions/config/routes.rb +++ b/friendly_promotions/config/routes.rb @@ -2,6 +2,8 @@ SolidusFriendlyPromotions::Engine.routes.draw do namespace :admin do + resources :promotion_categories, except: [:show] + resources :promotions do resources :promotion_rules resources :promotion_actions @@ -10,6 +12,5 @@ get '/download', to: "promotion_code_batches#download", defaults: { format: "csv" } end end - resources :promotion_categories, except: [:show] end end diff --git a/friendly_promotions/db/migrate/20230703141116_create_promotion_categories.rb b/friendly_promotions/db/migrate/20230703141116_create_promotion_categories.rb index e0a84bfc5c1..f31f9a2ed90 100644 --- a/friendly_promotions/db/migrate/20230703141116_create_promotion_categories.rb +++ b/friendly_promotions/db/migrate/20230703141116_create_promotion_categories.rb @@ -1,11 +1,15 @@ class CreatePromotionCategories < ActiveRecord::Migration[7.0] def change - create_table :solidus_friendly_promotions_categories do |t| + create_table :solidus_friendly_promotions_promotion_categories do |t| t.string :name + t.string :code t.timestamps end - add_reference :solidus_friendly_promotions_promotions, :category, foreign_key: { to_table: :solidus_friendly_promotions_categories } + add_reference :solidus_friendly_promotions_promotions, + :promotion_category, + foreign_key: { to_table: :solidus_friendly_promotions_promotion_categories }, + index: { name: :index_solidus_friendly_promotions_promotions_categories } end end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/category_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_category_spec.rb similarity index 73% rename from friendly_promotions/spec/models/solidus_friendly_promotions/category_spec.rb rename to friendly_promotions/spec/models/solidus_friendly_promotions/promotion_category_spec.rb index 9fda2e04a9d..25d2a5bfbb1 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/category_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_category_spec.rb @@ -2,12 +2,12 @@ require 'spec_helper' -RSpec.describe SolidusFriendlyPromotions::Category, type: :model do +RSpec.describe SolidusFriendlyPromotions::PromotionCategory, type: :model do it { is_expected.to have_many :promotions } describe 'validation' do let(:name) { 'Nom' } - subject { SolidusFriendlyPromotions::Category.new name: name } + subject { described_class.new name: name } context 'when all required attributes are specified' do it { is_expected.to be_valid } From 9819932c3fa758b0d8e42428924edfc53b95438f Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 5 Jul 2023 10:32:03 +0200 Subject: [PATCH 086/524] Make promotions index page work --- .../admin/promotions_controller.rb | 8 +++--- .../solidus_friendly_promotions/promotion.rb | 26 +++++++++++++++++++ 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb index d42806b4f1d..b6584c09bcf 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb @@ -9,15 +9,15 @@ class PromotionsController < ::Spree::Admin::ResourceController helper 'solidus_friendly_promotions/admin/promotion_actions' def create - @promotion = Spree::Promotion.new(permitted_resource_params) + @promotion = model_class.new(permitted_resource_params) @promotion.codes.new(value: params[:single_code]) if params[:single_code].present? - if params[:promotion_code_batch] - @promotion_code_batch = @promotion.promotion_code_batches.new(promotion_code_batch_params) + if params[:code_batch] + @code_batch = @promotion.code_batches.new(code_batch_params) end if @promotion.save - @promotion_code_batch.process if @promotion_code_batch + @code_batch.process if @code_batch flash[:success] = t('solidus_friendly_promotions.promotion_successfully_created') redirect_to location_after_save else diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb index 7b152b56c74..c3a3b9da2b0 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb @@ -17,5 +17,31 @@ class Promotion < Spree::Base self.allowed_ransackable_associations = ['codes'] self.allowed_ransackable_attributes = %w[name path promotion_category_id] self.allowed_ransackable_scopes = %i[active] + + + # All orders that have been discounted using this promotion + def discounted_orders + Spree::Order. + joins(:all_adjustments). + where( + spree_adjustments: { + source_type: "SolidusFriendlyPromotions::Action", + source_id: actions.map(&:id), + eligible: true + } + ).distinct + end + + # Number of times the code has been used overall + # + # @param excluded_orders [Array] Orders to exclude from usage count + # @return [Integer] usage count + def usage_count(excluded_orders: []) + discounted_orders. + complete. + where.not(id: [excluded_orders.map(&:id)]). + where.not(spree_orders: { state: :canceled }). + count + end end end From 73e504c7e16240879665c001a504e707531fd24f Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 5 Jul 2023 10:32:29 +0200 Subject: [PATCH 087/524] Use friendly promotion factories in specs --- .../testing_support/factories.rb | 2 ++ .../friendly_promotion_category_factory.rb | 7 +++++++ .../friendly_promotion_code_factory.rb | 8 ++++++++ .../calculators/distributed_amount_spec.rb | 2 +- .../solidus_friendly_promotions/code_spec.rb | 14 +++++++------- .../order_promotion_adjuster_spec.rb | 4 ++-- .../simple_order_contents_spec.rb | 4 ++-- .../admin/promotion_actions_request_spec.rb | 2 +- .../admin/promotion_rules_request_spec.rb | 7 ++++--- .../admin/promotion_categories_spec.rb | 8 ++++---- .../admin/promotions_spec.rb | 10 +++++----- 11 files changed, 43 insertions(+), 25 deletions(-) create mode 100644 friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_category_factory.rb create mode 100644 friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_code_factory.rb diff --git a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/factories.rb b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/factories.rb index 0b97b8fd914..9ffd7e6ee9c 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/factories.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/factories.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require "solidus_friendly_promotions/testing_support/friendly_promotion_code_factory" +require "solidus_friendly_promotions/testing_support/friendly_promotion_category_factory" require "solidus_friendly_promotions/testing_support/friendly_promotion_factory" FactoryBot.define do diff --git a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_category_factory.rb b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_category_factory.rb new file mode 100644 index 00000000000..07155b7b7fc --- /dev/null +++ b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_category_factory.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :friendly_promotion_category, class: 'SolidusFriendlyPromotions::PromotionCategory' do + name { 'Promotion Category' } + end +end diff --git a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_code_factory.rb b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_code_factory.rb new file mode 100644 index 00000000000..267b8139d95 --- /dev/null +++ b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_code_factory.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :friendly_promotion_code, class: 'SolidusFriendlyPromotions::Code' do + promotion factory: :friendly_promotion + sequence(:value) { |i| "code#{i}" } + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/distributed_amount_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/distributed_amount_spec.rb index 3d1eeed0dbd..e43050cf6d9 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/distributed_amount_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/distributed_amount_spec.rb @@ -5,7 +5,7 @@ RSpec.describe SolidusFriendlyPromotions::Calculators::DistributedAmount, type: :model do let(:calculator) { described_class.new(preferred_amount: 15, preferred_currency: currency) } - let!(:promotion) { create :promotion, apply_automatically: true, name: '15 spread', promotion_actions: [action], promotion_rules: rules } + let!(:promotion) { create :friendly_promotion, apply_automatically: true, name: '15 spread', actions: [action], rules: rules } let(:rules) { [] } let(:action) { SolidusFriendlyPromotions::Actions::AdjustLineItem.create(calculator: calculator) } let(:order) { create(:order_with_line_items, line_items_attributes: line_items_attributes) } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/code_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/code_spec.rb index f80cbbcf661..7ed0a8cacd2 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/code_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/code_spec.rb @@ -7,7 +7,7 @@ subject { promotion_code.save } describe '#normalize_code' do - let(:promotion) { create(:promotion, code: code) } + let(:promotion) { create(:friendly_promotion, code: code) } before { subject } @@ -98,7 +98,7 @@ context "with an order-level adjustment" do let(:promotion) do FactoryBot.create( - :promotion, + :friendly_promotion, :with_order_adjustment, code: "discount", per_code_usage_limit: usage_limit @@ -116,7 +116,7 @@ context "with an item-level adjustment" do let(:promotion) do FactoryBot.create( - :promotion, + :friendly_promotion, :with_line_item_adjustment, code: "discount", per_code_usage_limit: usage_limit @@ -151,7 +151,7 @@ describe "#usage_count" do let(:promotion) do FactoryBot.create( - :promotion, + :friendly_promotion, :with_order_adjustment, code: "discount" ) @@ -190,7 +190,7 @@ describe "completing multiple orders with the same code", slow: true do let(:promotion) do FactoryBot.create( - :promotion, + :friendly_promotion, :with_order_adjustment, code: "discount", per_code_usage_limit: 1, @@ -242,9 +242,9 @@ end it "cannot create promotion code on apply automatically promotion" do - promotion = create(:promotion, apply_automatically: true) + promotion = create(:friendly_promotion, apply_automatically: true) expect { - create(:promotion_code, promotion: promotion) + create(:friendly_promotion_code, promotion: promotion) }.to raise_error ActiveRecord::RecordInvalid, "Validation failed: Could not create promotion code on promotion that apply automatically" end end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/order_promotion_adjuster_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/order_promotion_adjuster_spec.rb index 75ac9adfa99..3b6b948f4f4 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/order_promotion_adjuster_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/order_promotion_adjuster_spec.rb @@ -5,7 +5,7 @@ RSpec.describe SolidusFriendlyPromotions::OrderPromotionAdjuster, type: :model do let(:line_item) { create(:line_item) } let(:order) { line_item.order } - let(:promotion) { create(:promotion, apply_automatically: true) } + let(:promotion) { create(:friendly_promotion, apply_automatically: true) } let(:calculator) { Spree::Calculator::FlatPercentItemTotal.new(preferred_flat_percent: 10) } subject { described_class.new(order) } @@ -24,7 +24,7 @@ end context "for a non-sale promotion" do - let(:promotion) { create(:promotion, apply_automatically: false) } + let(:promotion) { create(:friendly_promotion, apply_automatically: false) } it "doesn't connect the promotion to the order" do expect { diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/simple_order_contents_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/simple_order_contents_spec.rb index a101a7a9c66..c6e68a392bd 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/simple_order_contents_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/simple_order_contents_spec.rb @@ -83,8 +83,8 @@ end context "running promotions" do - let(:promotion) { create(:promotion, apply_automatically: true) } - let(:calculator) { Spree::Calculator::FlatRate.new(preferred_amount: 10) } + let(:promotion) { create(:friendly_promotion, apply_automatically: true) } + let(:calculator) { SolidusFriendlyPromotions::Calculators::FlatRate.new(preferred_amount: 10) } before do Spree::Config.promotion_adjuster_class = "SolidusFriendlyPromotions::OrderPromotionAdjuster" diff --git a/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_actions_request_spec.rb b/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_actions_request_spec.rb index 63fd5325357..bec5489a4e9 100644 --- a/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_actions_request_spec.rb +++ b/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_actions_request_spec.rb @@ -5,7 +5,7 @@ describe SolidusFriendlyPromotions::Admin::PromotionActionsController, type: :request do stub_authorization! - let!(:promotion) { create(:promotion) } + let!(:promotion) { create(:friendly_promotion) } it "can create a promotion action of a valid type" do post solidus_friendly_promotions.admin_promotion_promotion_actions_path(promotion_id: promotion.id), params: { diff --git a/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_rules_request_spec.rb b/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_rules_request_spec.rb index ac71afa53c6..d4067f55ace 100644 --- a/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_rules_request_spec.rb +++ b/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_rules_request_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe SolidusFriendlyPromotions::Admin::PromotionRulesController, type: :request do - let!(:promotion) { create(:promotion) } + let!(:promotion) { create(:friendly_promotion) } context "when the user is authorized" do stub_authorization! do |_u| @@ -29,8 +29,9 @@ context "when the user is not authorized" do it "redirects the user to login" do - post solidus_friendly_promotions.admin_promotion_promotion_rules_path(promotion_id: promotion.id, promotion_rule: { type: "Spree::Promotion::Rules::Product" }) - + post solidus_friendly_promotions.admin_promotion_promotion_rules_path(promotion_id: promotion.id), params: { + promotion_rule: { type: "SolidusFriendlyPromotions::Rules::Product" } + } expect(response).to redirect_to('/admin/login') end end diff --git a/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotion_categories_spec.rb b/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotion_categories_spec.rb index 96a72be5c50..34c534ae262 100644 --- a/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotion_categories_spec.rb +++ b/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotion_categories_spec.rb @@ -7,8 +7,8 @@ context "index" do before do - create(:promotion_category, name: 'name1', code: 'code1') - create(:promotion_category, name: 'name2', code: 'code2') + create(:friendly_promotion_category, name: 'name1', code: 'code1') + create(:friendly_promotion_category, name: 'name2', code: 'code2') visit solidus_friendly_promotions.admin_promotion_categories_path end @@ -50,7 +50,7 @@ context "edit" do before(:each) do - create(:promotion_category, name: 'name1') + create(:friendly_promotion_category, name: 'name1') visit solidus_friendly_promotions.admin_promotion_categories_path within_row(1) { click_icon :edit } end @@ -71,7 +71,7 @@ context "delete" do before(:each) do - create(:promotion_category, name: 'name1') + create(:friendly_promotion_category, name: 'name1') visit solidus_friendly_promotions.admin_promotion_categories_path end diff --git a/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotions_spec.rb b/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotions_spec.rb index 909c02e1e55..d73fc22bd13 100644 --- a/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotions_spec.rb +++ b/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotions_spec.rb @@ -6,12 +6,12 @@ stub_authorization! describe "#index" do - let!(:promotion1) { create(:promotion, :with_action, name: "name1", code: "code1", path: "path1") } - let!(:promotion2) { create(:promotion, :with_action, name: "name2", code: "code2", path: "path2") } + let!(:promotion1) { create(:friendly_promotion, :with_action, name: "name1", code: "code1", path: "path1") } + let!(:promotion2) { create(:friendly_promotion, :with_action, name: "name2", code: "code2", path: "path2") } let!(:promotion3) do - create(:promotion, :with_action, name: "name3", code: "code3", path: "path3", expires_at: Date.yesterday) + create(:friendly_promotion, :with_action, name: "name3", code: "code3", path: "path3", expires_at: Date.yesterday) end - let!(:category) { create :promotion_category } + let!(:category) { create :friendly_promotion_category } it "succeeds" do visit solidus_friendly_promotions.admin_promotions_path @@ -22,7 +22,7 @@ it "shows promotion categories" do visit solidus_friendly_promotions.admin_promotions_path - expect(page).to have_select(Spree::PromotionCategory.model_name.human, options: ["All", category.name]) + expect(page).to have_select(SolidusFriendlyPromotions::PromotionCategory.model_name.human, options: ["All", category.name]) end context "search" do From 21395b1f22a737c38e1b37d92c748e925c4048ff Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 5 Jul 2023 14:35:38 +0200 Subject: [PATCH 088/524] Fix Order Promotion Adjuster to work with new models --- .../order_promotion_adjuster.rb | 10 ++++---- .../solidus_friendly_promotions/promotion.rb | 23 ++++++++++++++++++- .../spec/models/promotion/integration_spec.rb | 2 +- 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/order_promotion_adjuster.rb b/friendly_promotions/app/models/solidus_friendly_promotions/order_promotion_adjuster.rb index c9277510810..c58da2c46e9 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/order_promotion_adjuster.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/order_promotion_adjuster.rb @@ -30,10 +30,10 @@ def adjust_shipments def possible_promotions promos = connected_order_promotions | sale_promotions - promos.flat_map(&:promotion_actions).group_by(&:preload_relations).each do |preload_relations, actions| + promos.flat_map(&:actions).group_by(&:preload_relations).each do |preload_relations, actions| preload(records: actions, associations: preload_relations) end - promos.flat_map(&:promotion_rules).group_by(&:preload_relations).each do |preload_relations, rules| + promos.flat_map(&:rules).group_by(&:preload_relations).each do |preload_relations, rules| preload(records: rules, associations: preload_relations) end promos.reject { |promotion| promotion.usage_limit_exceeded?(excluded_orders: [order]) } @@ -48,13 +48,13 @@ def connected_order_promotions end def sale_promotions - Spree::Promotion.where(apply_automatically: true).active.includes(promotion_includes) + SolidusFriendlyPromotions::Promotion.where(apply_automatically: true).active.includes(promotion_includes) end def promotion_includes [ - :promotion_rules, - :promotion_actions, + :rules, + :actions, ] end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb index c3a3b9da2b0..4f609db64a2 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb @@ -14,11 +14,22 @@ class Promotion < Spree::Base validates :per_code_usage_limit, numericality: { greater_than_or_equal_to: 0, allow_nil: true } validates :description, length: { maximum: 255 } + scope :active, -> { has_actions.started_and_unexpired } + scope :started_and_unexpired, -> do + table = arel_table + time = Time.current + + where(table[:starts_at].eq(nil).or(table[:starts_at].lt(time))). + where(table[:expires_at].eq(nil).or(table[:expires_at].gt(time))) + end + scope :has_actions, -> do + joins(:actions).distinct + end + self.allowed_ransackable_associations = ['codes'] self.allowed_ransackable_attributes = %w[name path promotion_category_id] self.allowed_ransackable_scopes = %i[active] - # All orders that have been discounted using this promotion def discounted_orders Spree::Order. @@ -43,5 +54,15 @@ def usage_count(excluded_orders: []) where.not(spree_orders: { state: :canceled }). count end + + # Whether the promotion has exceeded its usage restrictions. + # + # @param excluded_orders [Array] Orders to exclude from usage limit + # @return true or false + def usage_limit_exceeded?(excluded_orders: []) + if usage_limit + usage_count(excluded_orders: excluded_orders) >= usage_limit + end + end end end diff --git a/friendly_promotions/spec/models/promotion/integration_spec.rb b/friendly_promotions/spec/models/promotion/integration_spec.rb index d6d8dc1ac5a..d03c7c8b42e 100644 --- a/friendly_promotions/spec/models/promotion/integration_spec.rb +++ b/friendly_promotions/spec/models/promotion/integration_spec.rb @@ -6,7 +6,7 @@ context "A promotion that creates line item adjustments" do let(:shirt) { create(:product, name: "Shirt") } let(:pants) { create(:product, name: "Pants") } - let(:promotion) { create(:promotion, name: "20% off Shirts", apply_automatically: true) } + let(:promotion) { create(:friendly_promotion, name: "20% off Shirts", apply_automatically: true) } let(:order) { create(:order) } before do From 30470c932512a7408a418b26ae5580cf17cf3e1f Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 6 Jul 2023 11:28:01 +0200 Subject: [PATCH 089/524] Giant commit to be taken apart later --- .../admin/promotion_actions_controller.rb | 27 ++-------- .../admin/promotion_rules_controller.rb | 11 ++++ .../order_decorator.rb | 9 ++++ ...tch_job.rb => promotion_code_batch_job.rb} | 4 +- ...iler.rb => promotion_code_batch_mailer.rb} | 6 +-- .../app/models/solidus_friendly_promotions.rb | 5 ++ .../actions/adjust_line_item.rb | 2 +- .../actions/adjust_shipment.rb | 2 +- .../order_promotion.rb | 23 +++++++++ .../order_promotion_adjuster.rb | 2 +- ...cts_rule.rb => products_promotion_rule.rb} | 4 +- .../solidus_friendly_promotions/promotion.rb | 8 +-- .../{action.rb => promotion_action.rb} | 2 +- .../{code.rb => promotion_code.rb} | 4 +- .../{code => promotion_code}/batch_builder.rb | 22 ++++---- ...{code_batch.rb => promotion_code_batch.rb} | 6 +-- .../{rule.rb => promotion_rule.rb} | 2 +- ...ules_store.rb => promotion_rules_store.rb} | 4 +- ...ules_taxon.rb => promotion_rules_taxon.rb} | 4 +- ...{rules_user.rb => promotion_rules_user.rb} | 4 +- .../rules/first_order.rb | 2 +- .../rules/first_repeat_purchase_since.rb | 2 +- .../rules/item_total.rb | 2 +- .../rules/line_item_option_value.rb | 2 +- .../rules/line_item_product.rb | 2 +- .../rules/line_item_taxon.rb | 2 +- .../rules/nth_order.rb | 2 +- .../rules/one_use_per_user.rb | 2 +- .../rules/option_value.rb | 2 +- .../rules/product.rb | 6 +-- .../rules/store.rb | 6 +-- .../rules/taxon.rb | 6 +-- .../solidus_friendly_promotions/rules/user.rb | 6 +-- .../rules/user_logged_in.rb | 2 +- .../rules/user_role.rb | 2 +- .../promotion_code_batch_errored.text.erb} | 0 .../promotion_code_batch_finished.text.erb} | 0 friendly_promotions/config/locales/en.yml | 2 +- .../20230703101637_create_promotions.rb | 2 +- ...30703141116_create_promotion_categories.rb | 7 ++- .../20230703143943_create_promotion_rules.rb | 12 +++++ .../db/migrate/20230703143943_create_rules.rb | 11 ---- .../migrate/20230704083830_add_rule_tables.rb | 16 +++--- .../migrate/20230704093625_create_actions.rb | 14 ------ ...20230704093625_create_promotion_actions.rb | 14 ++++++ .../db/migrate/20230704102444_create_codes.rb | 11 ---- .../20230704102444_create_promotion_codes.rb | 11 ++++ ...04102656_create_promotion_code_batches.rb} | 24 ++++----- ...171556_create_friendly_order_promotions.rb | 11 ++++ .../lib/solidus_friendly_promotions.rb | 1 - .../testing_support/factories.rb | 6 +++ .../friendly_order_promotion_factory.rb | 8 +++ .../friendly_promotion_code_factory.rb | 4 +- .../order_promotion_spec.rb | 50 +++++++++++++++++++ .../products_promotion_rule_spec.rb | 8 +++ .../products_rule_spec.rb | 8 --- ...ction_spec.rb => promotion_action_spec.rb} | 4 +- .../batch_builder_spec.rb | 8 +-- ...h_spec.rb => promotion_code_batch_spec.rb} | 10 ++-- .../{code_spec.rb => promotion_code_spec.rb} | 2 +- .../{rule_spec.rb => promotion_rule_spec.rb} | 6 +-- .../promotion_rules_store_spec.rb | 8 +++ .../promotion_rules_taxon_spec.rb | 8 +++ .../promotion_rules_user_spec.rb | 8 +++ .../rules/product_spec.rb | 4 +- .../rules_store_spec.rb | 8 --- .../rules_taxon_spec.rb | 8 --- .../rules_user_spec.rb | 8 --- .../spec/models/spree/order_spec.rb | 8 +++ 69 files changed, 309 insertions(+), 198 deletions(-) create mode 100644 friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_decorator.rb rename friendly_promotions/app/jobs/solidus_friendly_promotions/{code_batch_job.rb => promotion_code_batch_job.rb} (86%) rename friendly_promotions/app/mailers/solidus_friendly_promotions/{code_batch_mailer.rb => promotion_code_batch_mailer.rb} (59%) create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/order_promotion.rb rename friendly_promotions/app/models/solidus_friendly_promotions/{products_rule.rb => products_promotion_rule.rb} (63%) rename friendly_promotions/app/models/solidus_friendly_promotions/{action.rb => promotion_action.rb} (97%) rename friendly_promotions/app/models/solidus_friendly_promotions/{code.rb => promotion_code.rb} (92%) rename friendly_promotions/app/models/solidus_friendly_promotions/{code => promotion_code}/batch_builder.rb (69%) rename friendly_promotions/app/models/solidus_friendly_promotions/{code_batch.rb => promotion_code_batch.rb} (78%) rename friendly_promotions/app/models/solidus_friendly_promotions/{rule.rb => promotion_rule.rb} (97%) rename friendly_promotions/app/models/solidus_friendly_promotions/{rules_store.rb => promotion_rules_store.rb} (62%) rename friendly_promotions/app/models/solidus_friendly_promotions/{rules_taxon.rb => promotion_rules_taxon.rb} (62%) rename friendly_promotions/app/models/solidus_friendly_promotions/{rules_user.rb => promotion_rules_user.rb} (65%) rename friendly_promotions/app/views/solidus_friendly_promotions/{code_batch_mailer/code_batch_errored.text.erb => promotion_code_batch_mailer/promotion_code_batch_errored.text.erb} (100%) rename friendly_promotions/app/views/solidus_friendly_promotions/{code_batch_mailer/code_batch_finished.text.erb => promotion_code_batch_mailer/promotion_code_batch_finished.text.erb} (100%) create mode 100644 friendly_promotions/db/migrate/20230703143943_create_promotion_rules.rb delete mode 100644 friendly_promotions/db/migrate/20230703143943_create_rules.rb delete mode 100644 friendly_promotions/db/migrate/20230704093625_create_actions.rb create mode 100644 friendly_promotions/db/migrate/20230704093625_create_promotion_actions.rb delete mode 100644 friendly_promotions/db/migrate/20230704102444_create_codes.rb create mode 100644 friendly_promotions/db/migrate/20230704102444_create_promotion_codes.rb rename friendly_promotions/db/migrate/{20230704102656_create_code_batches.rb => 20230704102656_create_promotion_code_batches.rb} (50%) create mode 100644 friendly_promotions/db/migrate/20230705171556_create_friendly_order_promotions.rb create mode 100644 friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_order_promotion_factory.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/order_promotion_spec.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/products_promotion_rule_spec.rb delete mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/products_rule_spec.rb rename friendly_promotions/spec/models/solidus_friendly_promotions/{action_spec.rb => promotion_action_spec.rb} (87%) rename friendly_promotions/spec/models/solidus_friendly_promotions/{code => promotion_code}/batch_builder_spec.rb (91%) rename friendly_promotions/spec/models/solidus_friendly_promotions/{code_batch_spec.rb => promotion_code_batch_spec.rb} (68%) rename friendly_promotions/spec/models/solidus_friendly_promotions/{code_spec.rb => promotion_code_spec.rb} (99%) rename friendly_promotions/spec/models/solidus_friendly_promotions/{rule_spec.rb => promotion_rule_spec.rb} (81%) create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_store_spec.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_taxon_spec.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_user_spec.rb delete mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/rules_store_spec.rb delete mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/rules_taxon_spec.rb delete mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/rules_user_spec.rb create mode 100644 friendly_promotions/spec/models/spree/order_spec.rb diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb index 7a2603281b8..0d592ff055d 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb @@ -2,37 +2,18 @@ module SolidusFriendlyPromotions module Admin - class PromotionActionsController < Spree::Admin::ResourceController - before_action :load_promotion - before_action :validate_level, only: :new - before_action :validate_promotion_action_type, only: [:create] - - helper 'solidus_friendly_promotions/admin/promotion_actions' + class PromotionActionsController < Spree::Admin::BaseController + before_action :load_promotion, only: [:create, :destroy, :new] + before_action :validate_promotion_action_type, only: :create def new - if params.dig(:promotion_action, :type) - validate_promotion_action_type - end - @promotion_action = @promotion.promotion_actions.build( - type: @promotion_action_type, - ) - if @promotion_action.respond_to?(:calculator_type) && params.dig(:promotion_action, :calculator_type) - @promotion_action.calculator_type = params.dig(:promotion_action, :calculator_type) - end - render layout: false - end - - def edit - if @promotion_action.calculator.class.name != params.dig(:promotion_action, :calculator_type) - @promotion_action.calculator = permitted_resource_params[:calculator_type].constantize.new - end render layout: false end def create @promotion_action = @promotion_action_type.new(permitted_resource_params) @promotion_action.promotion = @promotion - if @promotion_action.save + if @promotion_action.save(validate: false) flash[:success] = t('spree.successfully_created', resource: t('spree.promotion_action')) redirect_to location_after_save, format: :html else diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb index 1c237425034..3cfe1f55c63 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb @@ -85,6 +85,17 @@ def validate_level def promotion_rule_params params[:promotion_rule].permit! end + + def promotion_rule_types + case params.dig(:level) + when "order" + SolidusFriendlyPromotions.config.order_rules + when "line_item" + SolidusFriendlyPromotions.config.line_item_rules + when "shipment" + SolidusFriendlyPromotions.config.shipment_rules + end + end end end end diff --git a/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_decorator.rb b/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_decorator.rb new file mode 100644 index 00000000000..0604d5237d7 --- /dev/null +++ b/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_decorator.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions::OrderDecorator + def self.prepended(base) + base.has_many :friendly_order_promotions, class_name: "SolidusFriendlyPromotions::OrderPromotion", inverse_of: :order + base.has_many :friendly_promotions, through: :friendly_order_promotions, source: :promotion + end + Spree::Order.prepend self + end diff --git a/friendly_promotions/app/jobs/solidus_friendly_promotions/code_batch_job.rb b/friendly_promotions/app/jobs/solidus_friendly_promotions/promotion_code_batch_job.rb similarity index 86% rename from friendly_promotions/app/jobs/solidus_friendly_promotions/code_batch_job.rb rename to friendly_promotions/app/jobs/solidus_friendly_promotions/promotion_code_batch_job.rb index d30dbda0e25..7d2f8010928 100644 --- a/friendly_promotions/app/jobs/solidus_friendly_promotions/code_batch_job.rb +++ b/friendly_promotions/app/jobs/solidus_friendly_promotions/promotion_code_batch_job.rb @@ -1,11 +1,11 @@ # frozen_string_literal: true module SolidusFriendlyPromotions - class CodeBatchJob < ActiveJob::Base + class PromotionCodeBatchJob < ActiveJob::Base queue_as :default def perform(code_batch) - Code::BatchBuilder.new( + PromotionCode::BatchBuilder.new( code_batch ).build_promotion_codes diff --git a/friendly_promotions/app/mailers/solidus_friendly_promotions/code_batch_mailer.rb b/friendly_promotions/app/mailers/solidus_friendly_promotions/promotion_code_batch_mailer.rb similarity index 59% rename from friendly_promotions/app/mailers/solidus_friendly_promotions/code_batch_mailer.rb rename to friendly_promotions/app/mailers/solidus_friendly_promotions/promotion_code_batch_mailer.rb index 11078ae985a..4ad53b93e92 100644 --- a/friendly_promotions/app/mailers/solidus_friendly_promotions/code_batch_mailer.rb +++ b/friendly_promotions/app/mailers/solidus_friendly_promotions/promotion_code_batch_mailer.rb @@ -1,13 +1,13 @@ # frozen_string_literal: true module SolidusFriendlyPromotions - class CodeBatchMailer < Spree::BaseMailer - def code_batch_finished(code_batch) + class PromotionCodeBatchMailer < Spree::BaseMailer + def promotion_code_batch_finished(code_batch) @code_batch = code_batch mail(to: code_batch.email) end - def code_batch_errored(code_batch) + def promotion_code_batch_errored(code_batch) @code_batch = code_batch mail(to: code_batch.email) end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions.rb b/friendly_promotions/app/models/solidus_friendly_promotions.rb new file mode 100644 index 00000000000..56b3908fe0c --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions.rb @@ -0,0 +1,5 @@ +module SolidusFriendlyPromotions + def self.table_name_prefix + 'friendly_' + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb index b106f84af22..0870be2522a 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Actions - class AdjustLineItem < Action + class AdjustLineItem < PromotionAction def can_adjust?(object) object.is_a? Spree::LineItem end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb index a14c953c7d0..e5c8502eca6 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Actions - class AdjustShipment < Action + class AdjustShipment < PromotionAction def can_adjust?(object) object.is_a? Spree::Shipment end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/order_promotion.rb b/friendly_promotions/app/models/solidus_friendly_promotions/order_promotion.rb new file mode 100644 index 00000000000..623cf9154e2 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/order_promotion.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + # SolidusFriendlyPromotions::OrderPromotion represents the relationship between: + # + # 1. A promotion that a user attempted to apply to their order + # 2. The specific code that they used + class OrderPromotion < Spree::Base + belongs_to :order, class_name: 'Spree::Order' + belongs_to :promotion, class_name: 'SolidusFriendlyPromotions::Promotion' + belongs_to :promotion_code, class_name: 'SolidusFriendlyPromotions::PromotionCode', optional: true + + validates :promotion_code, presence: true, if: :require_promotion_code? + + self.allowed_ransackable_associations = %w[promotion_code] + + private + + def require_promotion_code? + promotion && !promotion.apply_automatically && promotion.codes.any? + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/order_promotion_adjuster.rb b/friendly_promotions/app/models/solidus_friendly_promotions/order_promotion_adjuster.rb index c58da2c46e9..8edb21594d5 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/order_promotion_adjuster.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/order_promotion_adjuster.rb @@ -44,7 +44,7 @@ def preload(records:, associations:) end def connected_order_promotions - order.promotions.active.includes(promotion_includes) + order.friendly_promotions.active.includes(promotion_includes) end def sale_promotions diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/products_rule.rb b/friendly_promotions/app/models/solidus_friendly_promotions/products_promotion_rule.rb similarity index 63% rename from friendly_promotions/app/models/solidus_friendly_promotions/products_rule.rb rename to friendly_promotions/app/models/solidus_friendly_promotions/products_promotion_rule.rb index d33f5970fa6..b4091a1e3ad 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/products_rule.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/products_promotion_rule.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true module SolidusFriendlyPromotions - class ProductsRule < Spree::Base + class ProductsPromotionRule < Spree::Base belongs_to :product, class_name: "Spree::Product" - belongs_to :rule + belongs_to :promotion_rule end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb index 4f609db64a2..bca33c18ac3 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb @@ -3,10 +3,10 @@ module SolidusFriendlyPromotions class Promotion < Spree::Base belongs_to :category, class_name: "SolidusFriendlyPromotions::PromotionCategory", foreign_key: :promotion_category_id, optional: true - has_many :rules - has_many :actions - has_many :codes - has_many :code_batches + has_many :rules, class_name: "SolidusFriendlyPromotions::PromotionRule" + has_many :actions, class_name: "SolidusFriendlyPromotions::PromotionAction" + has_many :codes, class_name: "SolidusFriendlyPromotions::PromotionCode" + has_many :code_batches, class_name: "SolidusFriendlyPromotions::PromotionCodeBatch" validates :name, presence: true validates :path, uniqueness: { allow_blank: true, case_sensitive: true } diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/action.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb similarity index 97% rename from friendly_promotions/app/models/solidus_friendly_promotions/action.rb rename to friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb index 14195714c51..265a42c4ee0 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/action.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb @@ -6,7 +6,7 @@ module SolidusFriendlyPromotions # # PromotionActions perform the necessary tasks when a promotion is activated # by an event and determined to be eligible. - class Action < Spree::Base + class PromotionAction < Spree::Base include Spree::Preferences::Persistable include Spree::SoftDeletable include Spree::CalculatedAdjustments diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/code.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code.rb similarity index 92% rename from friendly_promotions/app/models/solidus_friendly_promotions/code.rb rename to friendly_promotions/app/models/solidus_friendly_promotions/promotion_code.rb index eec5fbe4a90..23a1a546149 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/code.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true module SolidusFriendlyPromotions - class Code < Spree::Base + class PromotionCode < Spree::Base belongs_to :promotion, inverse_of: :codes - belongs_to :code_batch, inverse_of: :codes, optional: true + belongs_to :promotion_code_batch, inverse_of: :promotion_codes, optional: true has_many :adjustments before_validation :normalize_code diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/code/batch_builder.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code/batch_builder.rb similarity index 69% rename from friendly_promotions/app/models/solidus_friendly_promotions/code/batch_builder.rb rename to friendly_promotions/app/models/solidus_friendly_promotions/promotion_code/batch_builder.rb index 5ef85e50ce9..37dc95aead3 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/code/batch_builder.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code/batch_builder.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true module SolidusFriendlyPromotions - class Code < Spree::Base + class PromotionCode < Spree::Base class BatchBuilder - attr_reader :code_batch, :options - delegate :promotion, :number_of_codes, :base_code, to: :code_batch + attr_reader :promotion_code_batch, :options + delegate :promotion, :number_of_codes, :base_code, to: :promotion_code_batch DEFAULT_OPTIONS = { random_code_length: 6, @@ -12,17 +12,17 @@ class BatchBuilder sample_characters: ('a'..'z').to_a + (2..9).to_a.map(&:to_s) } - def initialize(code_batch, options = {}) - @code_batch = code_batch + def initialize(promotion_code_batch, options = {}) + @promotion_code_batch = promotion_code_batch options.assert_valid_keys(*DEFAULT_OPTIONS.keys) @options = DEFAULT_OPTIONS.merge(options) end def build_promotion_codes generate_random_codes - code_batch.update!(state: "completed") + promotion_code_batch.update!(state: "completed") rescue StandardError => error - code_batch.update!( + promotion_code_batch.update!( error: error.inspect, state: "failed" ) @@ -42,10 +42,10 @@ def generate_random_codes codes_for_current_batch = get_unique_codes(new_codes) codes_for_current_batch.each do |value| - Code.create!( + PromotionCode.create!( value: value, promotion: promotion, - code_batch: code_batch + promotion_code_batch: promotion_code_batch ) end created_codes += codes_for_current_batch.size @@ -57,11 +57,11 @@ def generate_random_code @options[:sample_characters].sample end.join - "#{base_code}#{@code_batch.join_characters}#{suffix}" + "#{base_code}#{@promotion_code_batch.join_characters}#{suffix}" end def get_unique_codes(code_set) - code_set - Code.where(value: code_set.to_a).pluck(:value) + code_set - PromotionCode.where(value: code_set.to_a).pluck(:value) end end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/code_batch.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code_batch.rb similarity index 78% rename from friendly_promotions/app/models/solidus_friendly_promotions/code_batch.rb rename to friendly_promotions/app/models/solidus_friendly_promotions/promotion_code_batch.rb index 0d47e7ea970..135fbb8998f 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/code_batch.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code_batch.rb @@ -1,12 +1,12 @@ # frozen_string_literal: true module SolidusFriendlyPromotions - class CodeBatch < Spree::Base + class PromotionCodeBatch < Spree::Base class CantProcessStartedBatch < StandardError end belongs_to :promotion - has_many :codes, dependent: :destroy + has_many :promotion_codes, dependent: :destroy validates :number_of_codes, numericality: { greater_than: 0 } validates_presence_of :base_code, :number_of_codes @@ -18,7 +18,7 @@ def finished? def process if state == "pending" update!(state: "processing") - CodeBatchJob.perform_later(self) + PromotionCodeBatchJob.perform_later(self) else raise CantProcessStartedBatch.new("Batch #{id} already started") end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rule.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rule.rb similarity index 97% rename from friendly_promotions/app/models/solidus_friendly_promotions/rule.rb rename to friendly_promotions/app/models/solidus_friendly_promotions/promotion_rule.rb index 86efa637f7f..9e083d1bc07 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rule.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rule.rb @@ -3,7 +3,7 @@ require 'spree/preferences/persistable' module SolidusFriendlyPromotions - class Rule < Spree::Base + class PromotionRule < Spree::Base include Spree::Preferences::Persistable belongs_to :promotion diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules_store.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rules_store.rb similarity index 62% rename from friendly_promotions/app/models/solidus_friendly_promotions/rules_store.rb rename to friendly_promotions/app/models/solidus_friendly_promotions/promotion_rules_store.rb index a4ab6dae755..2f6dd6c8a5c 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules_store.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rules_store.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true module SolidusFriendlyPromotions - class RulesStore < Spree::Base - belongs_to :rule + class PromotionRulesStore < Spree::Base + belongs_to :promotion_rule belongs_to :store, class_name: "Spree::Store" end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules_taxon.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rules_taxon.rb similarity index 62% rename from friendly_promotions/app/models/solidus_friendly_promotions/rules_taxon.rb rename to friendly_promotions/app/models/solidus_friendly_promotions/promotion_rules_taxon.rb index f9858940773..d020bdaeb04 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules_taxon.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rules_taxon.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true module SolidusFriendlyPromotions - class RulesTaxon < Spree::Base - belongs_to :rule + class PromotionRulesTaxon < Spree::Base + belongs_to :promotion_rule belongs_to :taxon, class_name: "Spree::Taxon" end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules_user.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rules_user.rb similarity index 65% rename from friendly_promotions/app/models/solidus_friendly_promotions/rules_user.rb rename to friendly_promotions/app/models/solidus_friendly_promotions/promotion_rules_user.rb index 3283ce6f8a4..91f2f209be8 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules_user.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rules_user.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true module SolidusFriendlyPromotions - class RulesUser < Spree::Base - belongs_to :rule + class PromotionRulesUser < Spree::Base + belongs_to :promotion_rule belongs_to :user, class_name: Spree::UserClassHandle.new end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_order.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_order.rb index ca90c5b3f2e..bd4df181558 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_order.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_order.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Rules - class FirstOrder < Rule + class FirstOrder < PromotionRule attr_reader :user, :email def applicable?(promotable) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb index 087f6314e8f..dfca964597d 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Rules - class FirstRepeatPurchaseSince < Rule + class FirstRepeatPurchaseSince < PromotionRule preference :days_ago, :integer, default: 365 validates :preferred_days_ago, numericality: {only_integer: true, greater_than: 0} diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb index d2af73b3772..a1573067ea8 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb @@ -7,7 +7,7 @@ module Rules # # To add extra operators please override `self.operators_map` or any other helper method. # To customize the error message you can also override `ineligible_message`. - class ItemTotal < Rule + class ItemTotal < PromotionRule preference :amount, :decimal, default: 100.00 preference :currency, :string, default: -> { Spree::Config[:currency] } preference :operator, :string, default: "gt" diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_option_value.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_option_value.rb index f57bfb17d42..b8252820c6e 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_option_value.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_option_value.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Rules - class LineItemOptionValue < Rule + class LineItemOptionValue < PromotionRule preference :eligible_values, :hash def applicable?(promotable) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb index f74933a1676..732a008c6aa 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb @@ -3,7 +3,7 @@ module SolidusFriendlyPromotions module Rules # A rule to apply a promotion only to line items with or without a chosen product - class LineItemProduct < Rule + class LineItemProduct < PromotionRule MATCH_POLICIES = %w[include exclude] has_many :product_promotion_rules, diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb index 48b73009922..13af307926a 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Rules - class LineItemTaxon < Rule + class LineItemTaxon < PromotionRule has_many :promotion_rule_taxons, class_name: "Spree::PromotionRuleTaxon", foreign_key: :promotion_rule_id, dependent: :destroy has_many :taxons, through: :promotion_rule_taxons, class_name: "Spree::Taxon" diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb index bee23a67e22..04cb592bd6c 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Rules - class NthOrder < Rule + class NthOrder < PromotionRule preference :nth_order, :integer, default: 2 # It does not make sense to have this apply to the first order using preferred_nth_order == 1 # Instead we could use the first_order rule diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb index 2cefe588199..770d964371f 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Rules - class OneUsePerUser < Rule + class OneUsePerUser < PromotionRule def applicable?(promotable) promotable.is_a?(Spree::Order) end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/option_value.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/option_value.rb index 4d5451a1dcb..f8b25aa040e 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/option_value.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/option_value.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Rules - class OptionValue < Rule + class OptionValue < PromotionRule preference :eligible_values, :hash def applicable?(promotable) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb index 1c35ffd0ec4..c8906689faf 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb @@ -6,9 +6,9 @@ module Rules # require all or any of the products to be present. Valid products # either come from assigned product group or are assingned directly to # the rule. - class Product < Rule - has_many :products_rules, inverse_of: :rule, dependent: :destroy - has_many :products, class_name: "Spree::Product", through: :products_rules + class Product < PromotionRule + has_many :products_promotion_rules, inverse_of: :promotion_rule, dependent: :destroy + has_many :products, class_name: "Spree::Product", through: :products_promotion_rules def preload_relations [:products] diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb index 4c3ed6446c4..70da23578a6 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb @@ -2,9 +2,9 @@ module SolidusFriendlyPromotions module Rules - class Store < Rule - has_many :rules_stores, inverse_of: :rule, dependent: :destroy - has_many :stores, through: :rules_stores, class_name: "Spree::Store" + class Store < PromotionRule + has_many :promotion_rules_stores, inverse_of: :promotion_rule, dependent: :destroy + has_many :stores, through: :promotion_rules_stores, class_name: "Spree::Store" def preload_relations [:stores] diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb index 811f7998295..a2d4750af0d 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb @@ -2,9 +2,9 @@ module SolidusFriendlyPromotions module Rules - class Taxon < Rule - has_many :rules_taxons, inverse_of: :rule, dependent: :destroy - has_many :taxons, through: :rules_taxons, class_name: "Spree::Taxon" + class Taxon < PromotionRule + has_many :promotion_rules_taxons, inverse_of: :promotion_rule, dependent: :destroy + has_many :taxons, through: :promotion_rules_taxons, class_name: "Spree::Taxon" def preload_relations [:taxons] diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb index 023b1b9e92c..44dd285880b 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb @@ -2,9 +2,9 @@ module SolidusFriendlyPromotions module Rules - class User < Rule - has_many :rules_users, inverse_of: :rule, dependent: :destroy - has_many :users, through: :rules_users, class_name: Spree::UserClassHandle.new + class User < PromotionRule + has_many :promotion_rules_users, inverse_of: :promotion_rule, dependent: :destroy + has_many :users, through: :promotion_rules_users, class_name: Spree::UserClassHandle.new def preload_relations [:users] diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb index 7c109cc956a..f43e85083c9 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Rules - class UserLoggedIn < Rule + class UserLoggedIn < PromotionRule def applicable?(promotable) promotable.is_a?(Spree::Order) end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_role.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_role.rb index 5f5c5ac9d93..7763145e979 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_role.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_role.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Rules - class UserRole < Rule + class UserRole < PromotionRule preference :role_ids, :array, default: [] MATCH_POLICIES = %w[any all] diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/code_batch_mailer/code_batch_errored.text.erb b/friendly_promotions/app/views/solidus_friendly_promotions/promotion_code_batch_mailer/promotion_code_batch_errored.text.erb similarity index 100% rename from friendly_promotions/app/views/solidus_friendly_promotions/code_batch_mailer/code_batch_errored.text.erb rename to friendly_promotions/app/views/solidus_friendly_promotions/promotion_code_batch_mailer/promotion_code_batch_errored.text.erb diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/code_batch_mailer/code_batch_finished.text.erb b/friendly_promotions/app/views/solidus_friendly_promotions/promotion_code_batch_mailer/promotion_code_batch_finished.text.erb similarity index 100% rename from friendly_promotions/app/views/solidus_friendly_promotions/code_batch_mailer/code_batch_finished.text.erb rename to friendly_promotions/app/views/solidus_friendly_promotions/promotion_code_batch_mailer/promotion_code_batch_finished.text.erb diff --git a/friendly_promotions/config/locales/en.yml b/friendly_promotions/config/locales/en.yml index 62331987703..81d13a6461e 100644 --- a/friendly_promotions/config/locales/en.yml +++ b/friendly_promotions/config/locales/en.yml @@ -145,7 +145,7 @@ en: description: Order includes User with specified Role(s) errors: models: - solidus_friendly_promotions/code: + solidus_friendly_promotions/promotion_code: attributes: base: disallowed_with_apply_automatically: Could not create promotion code on promotion that apply automatically diff --git a/friendly_promotions/db/migrate/20230703101637_create_promotions.rb b/friendly_promotions/db/migrate/20230703101637_create_promotions.rb index b0c98b1d077..84c0c2c0ca6 100644 --- a/friendly_promotions/db/migrate/20230703101637_create_promotions.rb +++ b/friendly_promotions/db/migrate/20230703101637_create_promotions.rb @@ -1,6 +1,6 @@ class CreatePromotions < ActiveRecord::Migration[7.0] def change - create_table :solidus_friendly_promotions_promotions do |t| + create_table :friendly_promotions do |t| t.string :description t.datetime :expires_at, precision: nil t.datetime :starts_at, precision: nil diff --git a/friendly_promotions/db/migrate/20230703141116_create_promotion_categories.rb b/friendly_promotions/db/migrate/20230703141116_create_promotion_categories.rb index f31f9a2ed90..97b1499d7ab 100644 --- a/friendly_promotions/db/migrate/20230703141116_create_promotion_categories.rb +++ b/friendly_promotions/db/migrate/20230703141116_create_promotion_categories.rb @@ -1,15 +1,14 @@ class CreatePromotionCategories < ActiveRecord::Migration[7.0] def change - create_table :solidus_friendly_promotions_promotion_categories do |t| + create_table :friendly_promotion_categories do |t| t.string :name t.string :code t.timestamps end - add_reference :solidus_friendly_promotions_promotions, + add_reference :friendly_promotions, :promotion_category, - foreign_key: { to_table: :solidus_friendly_promotions_promotion_categories }, - index: { name: :index_solidus_friendly_promotions_promotions_categories } + foreign_key: { to_table: :friendly_promotion_categories } end end diff --git a/friendly_promotions/db/migrate/20230703143943_create_promotion_rules.rb b/friendly_promotions/db/migrate/20230703143943_create_promotion_rules.rb new file mode 100644 index 00000000000..980ac111d56 --- /dev/null +++ b/friendly_promotions/db/migrate/20230703143943_create_promotion_rules.rb @@ -0,0 +1,12 @@ +class CreatePromotionRules < ActiveRecord::Migration[7.0] + def change + create_table :friendly_promotion_rules do |t| + t.references :promotion, + foreign_key: { to_table: :friendly_promotions } + t.string :type + t.text :preferences + + t.timestamps + end + end +end diff --git a/friendly_promotions/db/migrate/20230703143943_create_rules.rb b/friendly_promotions/db/migrate/20230703143943_create_rules.rb deleted file mode 100644 index 674672f9536..00000000000 --- a/friendly_promotions/db/migrate/20230703143943_create_rules.rb +++ /dev/null @@ -1,11 +0,0 @@ -class CreateRules < ActiveRecord::Migration[7.0] - def change - create_table :solidus_friendly_promotions_rules do |t| - t.references :promotion, foreign_key: { to_table: :solidus_friendly_promotions_promotions } - t.string :type - t.text :preferences - - t.timestamps - end - end -end diff --git a/friendly_promotions/db/migrate/20230704083830_add_rule_tables.rb b/friendly_promotions/db/migrate/20230704083830_add_rule_tables.rb index 89e49b35e37..314b2d8cf4d 100644 --- a/friendly_promotions/db/migrate/20230704083830_add_rule_tables.rb +++ b/friendly_promotions/db/migrate/20230704083830_add_rule_tables.rb @@ -1,29 +1,29 @@ class AddRuleTables < ActiveRecord::Migration[7.0] def change - create_table :solidus_friendly_promotions_products_rules, force: :cascade do |t| + create_table :friendly_products_promotion_rules, force: :cascade do |t| t.references :product, index: true, null: false, foreign_key: { to_table: :spree_products } - t.references :rule, index: true, null: false, foreign_key: { to_table: :solidus_friendly_promotions_rules } + t.references :promotion_rule, index: true, null: false, foreign_key: { to_table: :friendly_promotion_rules } t.timestamps end - create_table :solidus_friendly_promotions_rules_taxons, force: :cascade do |t| + create_table :friendly_promotion_rules_taxons, force: :cascade do |t| t.references :taxon, index: true, null: false, foreign_key: { to_table: :spree_taxons } - t.references :rule, index: true, null: false, foreign_key: { to_table: :solidus_friendly_promotions_rules } + t.references :promotion_rule, index: true, null: false, foreign_key: { to_table: :friendly_promotion_rules } t.timestamps end - create_table :solidus_friendly_promotions_rules_users, force: :cascade do |t| + create_table :friendly_promotion_rules_users, force: :cascade do |t| t.references :user, index: true, null: false, foreign_key: { to_table: Spree.user_class.table_name } - t.references :rule, index: true, null: false, foreign_key: { to_table: :solidus_friendly_promotions_rules } + t.references :promotion_rule, index: true, null: false, foreign_key: { to_table: :friendly_promotion_rules } t.timestamps end - create_table :solidus_friendly_promotions_rules_stores do |t| + create_table :friendly_promotion_rules_stores do |t| t.references :store, index: true, null: false, foreign_key: { to_table: :spree_users } - t.references :rule, index: true, null: false + t.references :promotion_rule, index: true, null: false t.timestamps end diff --git a/friendly_promotions/db/migrate/20230704093625_create_actions.rb b/friendly_promotions/db/migrate/20230704093625_create_actions.rb deleted file mode 100644 index 41e7cb21120..00000000000 --- a/friendly_promotions/db/migrate/20230704093625_create_actions.rb +++ /dev/null @@ -1,14 +0,0 @@ -class CreateActions < ActiveRecord::Migration[7.0] - def change - create_table :solidus_friendly_promotions_actions do |t| - t.references :promotion, index: true, null: false, foreign_key: { to_table: :solidus_friendly_promotions_promotions } - t.string :type - t.datetime :deleted_at, precision: nil - t.text :preferences - t.index [:deleted_at], name: :index_solidus_friendly_promotions_actions_on_deleted_at - t.index [:id, :type], name: :index_solidus_friendly_promotions_actions_on_id_and_type - - t.timestamps - end - end -end diff --git a/friendly_promotions/db/migrate/20230704093625_create_promotion_actions.rb b/friendly_promotions/db/migrate/20230704093625_create_promotion_actions.rb new file mode 100644 index 00000000000..731ee6abd44 --- /dev/null +++ b/friendly_promotions/db/migrate/20230704093625_create_promotion_actions.rb @@ -0,0 +1,14 @@ +class CreatePromotionActions < ActiveRecord::Migration[7.0] + def change + create_table :friendly_promotion_actions do |t| + t.references :promotion, index: true, null: false, foreign_key: { to_table: :friendly_promotions } + t.string :type + t.datetime :deleted_at, precision: nil + t.text :preferences + t.index [:deleted_at], name: :index_friendly_promotion_actions_on_deleted_at + t.index [:id, :type], name: :index_friendly_promotion_actions_on_id_and_type + + t.timestamps + end + end +end diff --git a/friendly_promotions/db/migrate/20230704102444_create_codes.rb b/friendly_promotions/db/migrate/20230704102444_create_codes.rb deleted file mode 100644 index 022eed3aa3f..00000000000 --- a/friendly_promotions/db/migrate/20230704102444_create_codes.rb +++ /dev/null @@ -1,11 +0,0 @@ -class CreateCodes < ActiveRecord::Migration[7.0] - def change - create_table :solidus_friendly_promotions_codes, force: :cascade do |t| - t.references :promotion, null: false, index: true, foreign_key: { to_table: :solidus_friendly_promotions_promotions } - t.string :value, null: false - t.timestamps - - t.index ["value"], name: "index_solidus_friendly_promotions_codes_on_value", unique: true - end - end -end diff --git a/friendly_promotions/db/migrate/20230704102444_create_promotion_codes.rb b/friendly_promotions/db/migrate/20230704102444_create_promotion_codes.rb new file mode 100644 index 00000000000..9168098cb96 --- /dev/null +++ b/friendly_promotions/db/migrate/20230704102444_create_promotion_codes.rb @@ -0,0 +1,11 @@ +class CreatePromotionCodes < ActiveRecord::Migration[7.0] + def change + create_table :friendly_promotion_codes, force: :cascade do |t| + t.references :promotion, null: false, index: true, foreign_key: { to_table: :friendly_promotions } + t.string :value, null: false + t.timestamps + + t.index ["value"], name: "index_friendly_promotion_codes_on_value", unique: true + end + end +end diff --git a/friendly_promotions/db/migrate/20230704102656_create_code_batches.rb b/friendly_promotions/db/migrate/20230704102656_create_promotion_code_batches.rb similarity index 50% rename from friendly_promotions/db/migrate/20230704102656_create_code_batches.rb rename to friendly_promotions/db/migrate/20230704102656_create_promotion_code_batches.rb index f70db0ec4ff..0a1cd5aba53 100644 --- a/friendly_promotions/db/migrate/20230704102656_create_code_batches.rb +++ b/friendly_promotions/db/migrate/20230704102656_create_promotion_code_batches.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true -class CreateCodeBatches < ActiveRecord::Migration[7.0] +class CreatePromotionCodeBatches < ActiveRecord::Migration[7.0] def change - create_table :solidus_friendly_promotions_code_batches do |t| - t.references :promotion, null: false, index: true, foreign_key: { to_table: :solidus_friendly_promotions_promotions } + create_table :friendly_promotion_code_batches do |t| + t.references :promotion, null: false, index: true, foreign_key: { to_table: :friendly_promotions } t.string :base_code, null: false t.integer :number_of_codes, null: false t.string :join_characters, null: false, default: "_" @@ -14,26 +14,26 @@ def change end add_foreign_key( - :solidus_friendly_promotions_code_batches, - :solidus_friendly_promotions_promotions, + :friendly_promotion_code_batches, + :friendly_promotions, column: :promotion_id ) add_column( - :solidus_friendly_promotions_codes, - :code_batch_id, + :friendly_promotion_codes, + :promotion_code_batch_id, :integer ) add_foreign_key( - :solidus_friendly_promotions_codes, - :solidus_friendly_promotions_code_batches, - column: :code_batch_id + :friendly_promotion_codes, + :friendly_promotion_code_batches, + column: :promotion_code_batch_id ) add_index( - :solidus_friendly_promotions_codes, - :code_batch_id + :friendly_promotion_codes, + :promotion_code_batch_id ) end end diff --git a/friendly_promotions/db/migrate/20230705171556_create_friendly_order_promotions.rb b/friendly_promotions/db/migrate/20230705171556_create_friendly_order_promotions.rb new file mode 100644 index 00000000000..a04e320aa0b --- /dev/null +++ b/friendly_promotions/db/migrate/20230705171556_create_friendly_order_promotions.rb @@ -0,0 +1,11 @@ +class CreateFriendlyOrderPromotions < ActiveRecord::Migration[7.0] + def change + create_table :friendly_order_promotions do |t| + t.references :order, index: true, null: false, foreign_key: { to_table: :spree_orders } + t.references :promotion, index: true, null: false, foreign_key: { to_table: :friendly_promotions } + t.references :promotion_code, index: true, null: true, foreign_key: { to_table: :friendly_promotion_codes } + + t.timestamps + end + end +end diff --git a/friendly_promotions/lib/solidus_friendly_promotions.rb b/friendly_promotions/lib/solidus_friendly_promotions.rb index bcf6fb40006..5a91e257a3e 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions.rb @@ -8,7 +8,6 @@ require "solidus_friendly_promotions/engine" module SolidusFriendlyPromotions - # JS Importmap instance singleton_class.attr_accessor :importmap self.importmap = Importmap::Map.new diff --git a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/factories.rb b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/factories.rb index 9ffd7e6ee9c..0d01d1d037a 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/factories.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/factories.rb @@ -1,8 +1,14 @@ # frozen_string_literal: true +module SolidusFriendlyPromotions + def self.table_name_prefix + "friendly_" + end +end require "solidus_friendly_promotions/testing_support/friendly_promotion_code_factory" require "solidus_friendly_promotions/testing_support/friendly_promotion_category_factory" require "solidus_friendly_promotions/testing_support/friendly_promotion_factory" +require "solidus_friendly_promotions/testing_support/friendly_order_promotion_factory" FactoryBot.define do end diff --git a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_order_promotion_factory.rb b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_order_promotion_factory.rb new file mode 100644 index 00000000000..2361cbedae5 --- /dev/null +++ b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_order_promotion_factory.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :friendly_order_promotion, class: 'SolidusFriendlyPromotions::OrderPromotion' do + association :order, factory: :order + association :promotion, factory: :friendly_promotion + end +end diff --git a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_code_factory.rb b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_code_factory.rb index 267b8139d95..dbf38774456 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_code_factory.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_code_factory.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true FactoryBot.define do - factory :friendly_promotion_code, class: 'SolidusFriendlyPromotions::Code' do - promotion factory: :friendly_promotion + factory :friendly_promotion_code, class: 'SolidusFriendlyPromotions::PromotionCode' do + association :promotion, factory: :friendly_promotion sequence(:value) { |i| "code#{i}" } end end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/order_promotion_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/order_promotion_spec.rb new file mode 100644 index 00000000000..b1d79ff4910 --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/order_promotion_spec.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SolidusFriendlyPromotions::OrderPromotion do + subject do + order_promotion + end + let(:promotion) { build(:friendly_promotion) } + let(:order_promotion) { build(:friendly_order_promotion, promotion: promotion) } + + describe "promotion code presence error" do + subject do + order_promotion.valid? + order_promotion.errors[:promotion_code] + end + + context "when the promotion does not have a code" do + it { is_expected.to be_blank } + end + + context "when the promotion has a code" do + let!(:promotion_code) do + promotion.codes << build(:friendly_promotion_code, promotion: promotion) + end + + it { is_expected.to include("can't be blank") } + end + end + + describe "promotion code presence error on promotion that apply automatically" do + subject do + order_promotion.promotion.apply_automatically = true + order_promotion.valid? + order_promotion.errors[:promotion_code] + end + + context "when the promotion does not have a code" do + it { is_expected.to be_blank } + end + + context "when the promotion has a code" do + let!(:promotion_code) do + promotion.codes << build(:friendly_promotion_code, promotion: promotion) + end + + it { is_expected.to be_blank } + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/products_promotion_rule_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/products_promotion_rule_spec.rb new file mode 100644 index 00000000000..fff40dd1c28 --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/products_promotion_rule_spec.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SolidusFriendlyPromotions::ProductsPromotionRule do + it { is_expected.to belong_to(:product) } + it { is_expected.to belong_to(:promotion_rule) } +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/products_rule_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/products_rule_spec.rb deleted file mode 100644 index 3d8f7a01044..00000000000 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/products_rule_spec.rb +++ /dev/null @@ -1,8 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe SolidusFriendlyPromotions::ProductsRule do - it { is_expected.to belong_to(:product) } - it { is_expected.to belong_to(:rule) } -end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/action_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_action_spec.rb similarity index 87% rename from friendly_promotions/spec/models/solidus_friendly_promotions/action_spec.rb rename to friendly_promotions/spec/models/solidus_friendly_promotions/promotion_action_spec.rb index 63217e6b1a3..3099a5d20d6 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/action_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_action_spec.rb @@ -2,7 +2,7 @@ require "spec_helper" -RSpec.describe SolidusFriendlyPromotions::Action do +RSpec.describe SolidusFriendlyPromotions::PromotionAction do it { is_expected.to belong_to(:promotion) } it { is_expected.to have_one(:calculator) } @@ -21,7 +21,7 @@ let(:variant) { create(:variant) } let(:order) { create(:order) } let(:adjustable) { Spree::LineItem.new(order: order, variant: variant, price: 10)} - let(:promotion) { Spree::Promotion.new(name: "20 Perzent off") } + let(:promotion) { SolidusFriendlyPromotions::Promotion.new(name: "20 Perzent off") } let(:action) { described_class.new(promotion: promotion)} before do allow(action).to receive(:compute_amount).and_return(-1) diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/code/batch_builder_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code/batch_builder_spec.rb similarity index 91% rename from friendly_promotions/spec/models/solidus_friendly_promotions/code/batch_builder_spec.rb rename to friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code/batch_builder_spec.rb index 4d0e3156a71..b124f031cc7 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/code/batch_builder_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code/batch_builder_spec.rb @@ -2,13 +2,13 @@ require "spec_helper" -RSpec.describe SolidusFriendlyPromotions::Code::BatchBuilder do +RSpec.describe SolidusFriendlyPromotions::PromotionCode::BatchBuilder do let(:promotion) { create(:friendly_promotion) } let(:base_code) { "abc" } let(:options) { {} } let(:number_of_codes) { 10 } let(:code_batch) do - SolidusFriendlyPromotions::CodeBatch.create!( + SolidusFriendlyPromotions::PromotionCodeBatch.create!( promotion_id: promotion.id, base_code: base_code, number_of_codes: number_of_codes, @@ -42,7 +42,7 @@ end it "update the promotion codes count for the batch" do - expect(code_batch.codes.count).to eq(10) + expect(code_batch.promotion_codes.count).to eq(10) end it "builds the correct number of codes" do @@ -87,7 +87,7 @@ context "with a custom join separator" do let(:code_batch) do - SolidusFriendlyPromotions::CodeBatch.create!( + SolidusFriendlyPromotions::PromotionCodeBatch.create!( promotion_id: promotion.id, base_code: base_code, number_of_codes: number_of_codes, diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/code_batch_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code_batch_spec.rb similarity index 68% rename from friendly_promotions/spec/models/solidus_friendly_promotions/code_batch_spec.rb rename to friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code_batch_spec.rb index b4b23104249..ed0472183e1 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/code_batch_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code_batch_spec.rb @@ -2,7 +2,7 @@ require "spec_helper" -RSpec.describe SolidusFriendlyPromotions::CodeBatch, type: :model do +RSpec.describe SolidusFriendlyPromotions::PromotionCodeBatch, type: :model do subject do described_class.create!( promotion_id: create(:friendly_promotion).id, @@ -17,7 +17,7 @@ context "with a pending code batch" do it "should call the worker" do expect { subject.process } - .to have_enqueued_job(SolidusFriendlyPromotions::CodeBatchJob) + .to have_enqueued_job(SolidusFriendlyPromotions::PromotionCodeBatchJob) end it "should update the state to processing" do @@ -31,7 +31,7 @@ before { subject.update_attribute(:state, "processing") } it "should raise an error" do - expect{ subject.process }.to raise_error SolidusFriendlyPromotions::CodeBatch::CantProcessStartedBatch + expect{ subject.process }.to raise_error described_class::CantProcessStartedBatch end end @@ -39,7 +39,7 @@ before { subject.update_attribute(:state, "completed") } it "should raise an error" do - expect{ subject.process }.to raise_error SolidusFriendlyPromotions::CodeBatch::CantProcessStartedBatch + expect{ subject.process }.to raise_error described_class::CantProcessStartedBatch end end @@ -47,7 +47,7 @@ before { subject.update_attribute(:state, "failed") } it "should raise an error" do - expect{ subject.process }.to raise_error SolidusFriendlyPromotions::CodeBatch::CantProcessStartedBatch + expect{ subject.process }.to raise_error described_class::CantProcessStartedBatch end end end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/code_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code_spec.rb similarity index 99% rename from friendly_promotions/spec/models/solidus_friendly_promotions/code_spec.rb rename to friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code_spec.rb index 7ed0a8cacd2..39d751d9841 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/code_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe SolidusFriendlyPromotions::Code do +RSpec.describe SolidusFriendlyPromotions::PromotionCode do context 'callbacks' do subject { promotion_code.save } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rule_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rule_spec.rb similarity index 81% rename from friendly_promotions/spec/models/solidus_friendly_promotions/rule_spec.rb rename to friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rule_spec.rb index 57d8bda464b..85e31ad9c27 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rule_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rule_spec.rb @@ -2,10 +2,10 @@ require "spec_helper" -RSpec.describe SolidusFriendlyPromotions::Rule do - class BadTestRule < SolidusFriendlyPromotions::Rule; end +RSpec.describe SolidusFriendlyPromotions::PromotionRule do + class BadTestRule < SolidusFriendlyPromotions::PromotionRule; end - class TestRule < SolidusFriendlyPromotions::Rule + class TestRule < SolidusFriendlyPromotions::PromotionRule def eligible?(_promotable, _options = {}) true end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_store_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_store_spec.rb new file mode 100644 index 00000000000..e212e065331 --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_store_spec.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SolidusFriendlyPromotions::PromotionRulesStore do + it { is_expected.to belong_to(:store) } + it { is_expected.to belong_to(:promotion_rule) } +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_taxon_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_taxon_spec.rb new file mode 100644 index 00000000000..9214b152643 --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_taxon_spec.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SolidusFriendlyPromotions::PromotionRulesTaxon do + it { is_expected.to belong_to(:taxon) } + it { is_expected.to belong_to(:promotion_rule) } +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_user_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_user_spec.rb new file mode 100644 index 00000000000..e7003c49341 --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_user_spec.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SolidusFriendlyPromotions::PromotionRulesUser do + it { is_expected.to belong_to(:user) } + it { is_expected.to belong_to(:promotion_rule) } +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/product_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/product_spec.rb index aa2ab511209..1f5c574e77f 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/product_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/product_spec.rb @@ -108,8 +108,8 @@ let(:rule) do SolidusFriendlyPromotions::Rules::Product.create!( promotion: create(:friendly_promotion), - products_rules: [ - SolidusFriendlyPromotions::ProductsRule.new(product: product) + products_promotion_rules: [ + SolidusFriendlyPromotions::ProductsPromotionRule.new(product: product) ] ).tap do |rule| rule.preferred_match_policy = "invalid" diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules_store_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules_store_spec.rb deleted file mode 100644 index c0b2635a37e..00000000000 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules_store_spec.rb +++ /dev/null @@ -1,8 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe SolidusFriendlyPromotions::RulesStore do - it { is_expected.to belong_to(:store) } - it { is_expected.to belong_to(:rule) } -end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules_taxon_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules_taxon_spec.rb deleted file mode 100644 index cceff860d66..00000000000 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules_taxon_spec.rb +++ /dev/null @@ -1,8 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe SolidusFriendlyPromotions::RulesTaxon do - it { is_expected.to belong_to(:taxon) } - it { is_expected.to belong_to(:rule) } -end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules_user_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules_user_spec.rb deleted file mode 100644 index 0fdb7b479ce..00000000000 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules_user_spec.rb +++ /dev/null @@ -1,8 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe SolidusFriendlyPromotions::RulesUser do - it { is_expected.to belong_to(:user) } - it { is_expected.to belong_to(:rule) } -end diff --git a/friendly_promotions/spec/models/spree/order_spec.rb b/friendly_promotions/spec/models/spree/order_spec.rb new file mode 100644 index 00000000000..51bfa5af2b9 --- /dev/null +++ b/friendly_promotions/spec/models/spree/order_spec.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe Spree::Order do + it { is_expected.to have_many :friendly_promotions } + it { is_expected.to have_many :friendly_order_promotions } +end From 42425d530309f7d19b92f4f4812cb0fb33475a79 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 6 Jul 2023 14:09:38 +0200 Subject: [PATCH 090/524] Fix Promotion Code spec and usage-count based eligibility --- .../adjustment_decorator.rb | 14 +++++ .../order_decorator.rb | 12 +++++ .../line_item_adjuster.rb | 2 +- .../order_promotion_adjuster.rb | 5 +- .../solidus_friendly_promotions/promotion.rb | 2 +- .../promotion_code.rb | 6 +-- .../testing_support/factories.rb | 1 + .../testing_support/friendly_order_factory.rb | 21 ++++++++ .../promotion_code_spec.rb | 51 +++++++++++++------ friendly_promotions/spec/spec_helper.rb | 2 + 10 files changed, 94 insertions(+), 22 deletions(-) create mode 100644 friendly_promotions/app/decorators/models/solidus_friendly_promotions/adjustment_decorator.rb create mode 100644 friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_order_factory.rb diff --git a/friendly_promotions/app/decorators/models/solidus_friendly_promotions/adjustment_decorator.rb b/friendly_promotions/app/decorators/models/solidus_friendly_promotions/adjustment_decorator.rb new file mode 100644 index 00000000000..35567c97c13 --- /dev/null +++ b/friendly_promotions/app/decorators/models/solidus_friendly_promotions/adjustment_decorator.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module AdjustmentDecorator + def self.prepended(base) + base.scope :friendly_promotion, -> { where(source_type: "SolidusFriendlyPromotions::PromotionAction") } + end + + def friendly_promotion? + source_type == "SolidusFriendlyPromotions::PromotionAction" + end + Spree::Adjustment.prepend self + end +end diff --git a/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_decorator.rb b/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_decorator.rb index 0604d5237d7..29d97327d9b 100644 --- a/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_decorator.rb +++ b/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_decorator.rb @@ -5,5 +5,17 @@ def self.prepended(base) base.has_many :friendly_order_promotions, class_name: "SolidusFriendlyPromotions::OrderPromotion", inverse_of: :order base.has_many :friendly_promotions, through: :friendly_order_promotions, source: :promotion end + + def ensure_promotions_eligible + Spree::Config.promotion_adjuster_class.new(self).call + if promo_total_changed? + restart_checkout_flow + recalculate + errors.add(:base, I18n.t('spree.promotion_total_changed_before_complete')) + end + + super + end + Spree::Order.prepend self end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/line_item_adjuster.rb b/friendly_promotions/app/models/solidus_friendly_promotions/line_item_adjuster.rb index ef748830aa2..0b195f421cd 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/line_item_adjuster.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/line_item_adjuster.rb @@ -10,7 +10,7 @@ def initialize(promotions:) def call(line_item) return unless line_item.variant.product.promotionable? - non_promotion_adjustments = line_item.adjustments.reject(&:promotion?) + non_promotion_adjustments = line_item.adjustments.reject(&:friendly_promotion?) eligible_promotions = PromotionEligibility.new(promotable: line_item, possible_promotions: promotions).call diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/order_promotion_adjuster.rb b/friendly_promotions/app/models/solidus_friendly_promotions/order_promotion_adjuster.rb index 8edb21594d5..b7a569696dd 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/order_promotion_adjuster.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/order_promotion_adjuster.rb @@ -44,7 +44,10 @@ def preload(records:, associations:) end def connected_order_promotions - order.friendly_promotions.active.includes(promotion_includes) + eligible_connected_promotion_ids = order.friendly_order_promotions.select do |order_promotion| + order_promotion.promotion_code.nil? || !order_promotion.promotion_code.usage_limit_exceeded?(excluded_orders: [order]) + end.map(&:promotion_id) + order.friendly_promotions.active.where(id: eligible_connected_promotion_ids).includes(promotion_includes) end def sale_promotions diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb index bca33c18ac3..9f7e91283d8 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb @@ -36,7 +36,7 @@ def discounted_orders joins(:all_adjustments). where( spree_adjustments: { - source_type: "SolidusFriendlyPromotions::Action", + source_type: "SolidusFriendlyPromotions::PromotionAction", source_id: actions.map(&:id), eligible: true } diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code.rb index 23a1a546149..6ed524a5b17 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code.rb @@ -4,7 +4,7 @@ module SolidusFriendlyPromotions class PromotionCode < Spree::Base belongs_to :promotion, inverse_of: :codes belongs_to :promotion_code_batch, inverse_of: :promotion_codes, optional: true - has_many :adjustments + has_many :adjustments, class_name: "Spree::Adjustment" before_validation :normalize_code @@ -32,8 +32,8 @@ def usage_count(excluded_orders: []) discounted_orders. complete. where.not(spree_orders: { state: :canceled }). - joins(:order_promotions). - where(spree_orders_promotions: { promotion_code_id: self.id }). + joins(:friendly_order_promotions). + where(friendly_order_promotions: { promotion_code_id: self.id }). where.not(id: excluded_orders.map(&:id)). count end diff --git a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/factories.rb b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/factories.rb index 0d01d1d037a..cf315d7e46d 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/factories.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/factories.rb @@ -9,6 +9,7 @@ def self.table_name_prefix require "solidus_friendly_promotions/testing_support/friendly_promotion_category_factory" require "solidus_friendly_promotions/testing_support/friendly_promotion_factory" require "solidus_friendly_promotions/testing_support/friendly_order_promotion_factory" +require "solidus_friendly_promotions/testing_support/friendly_order_factory" FactoryBot.define do end diff --git a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_order_factory.rb b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_order_factory.rb new file mode 100644 index 00000000000..04819d8b05b --- /dev/null +++ b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_order_factory.rb @@ -0,0 +1,21 @@ + +# frozen_string_literal: true + +FactoryBot.define do + factory :completed_order_with_friendly_promotion, parent: :order_with_line_items do + transient do + completed_at { Time.current } + promotion { nil } + end + + after(:create) do |order, evaluator| + promotion = evaluator.promotion || create(:friendly_promotion, code: "test") + promotion_code = promotion.codes.first || create(:promotion_code, promotion: promotion) + + order.friendly_order_promotions.create!(promotion: promotion, promotion_code: promotion_code) + order.recalculate + order.update_column(:completed_at, evaluator.completed_at) + order.update_column(:state, "complete") + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code_spec.rb index 39d751d9841..81003cf63bf 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code_spec.rb @@ -75,7 +75,7 @@ context "on a different order" do before do FactoryBot.create( - :completed_order_with_promotion, + :completed_order_with_friendly_promotion, promotion: promotion ) code.adjustments.update_all(eligible: true) @@ -106,7 +106,7 @@ end let(:promotable) do FactoryBot.create( - :completed_order_with_promotion, + :completed_order_with_friendly_promotion, promotion: promotion ) end @@ -119,15 +119,11 @@ :friendly_promotion, :with_line_item_adjustment, code: "discount", - per_code_usage_limit: usage_limit + per_code_usage_limit: usage_limit, ) end before do - promotion.actions.first.perform({ - order: order, - promotion: promotion, - promotion_code: code - }) + order.recalculate end context "when there are multiple line items" do let(:order) { FactoryBot.create(:order_with_line_items, line_items_count: 2) } @@ -160,15 +156,25 @@ subject { code.usage_count } + context "when the code is applied to a non-complete order" do let(:order) { FactoryBot.create(:order_with_line_items) } - before { promotion.activate(order: order, promotion_code: code) } + + before do + order.friendly_order_promotions.create( + promotion: promotion, + promotion_code: code + ) + order.recalculate + end + it { is_expected.to eq 0 } end + context "when the code is applied to a complete order" do let!(:order) do FactoryBot.create( - :completed_order_with_promotion, + :completed_order_with_friendly_promotion, promotion: promotion ) end @@ -176,7 +182,7 @@ it { is_expected.to eq 1 } end context "and the promo is ineligible" do - before { order.adjustments.promotion.update_all(eligible: false) } + before { order.all_adjustments.update_all(eligible: false) } it { is_expected.to eq 0 } end context "and the order is canceled" do @@ -198,28 +204,41 @@ ) end let(:code) { promotion.codes.first } + let(:order) do FactoryBot.create(:order_with_line_items, line_items_price: 40, shipment_cost: 0).tap do |order| FactoryBot.create(:payment, amount: 30, order: order) - promotion.activate(order: order, promotion_code: code) end end - let(:promo_adjustment) { order.adjustments.promotion.first } + + let(:promo_adjustment) { order.all_adjustments.friendly_promotion.first } + before do + order.friendly_order_promotions.create!( + order: order, + promotion: promotion, + promotion_code: SolidusFriendlyPromotions::PromotionCode.find_by(value: "discount") + ) + order.recalculate order.next! until order.can_complete? FactoryBot.create(:order_with_line_items, line_items_price: 40, shipment_cost: 0).tap do |order| FactoryBot.create(:payment, amount: 30, order: order) - promotion.activate(order: order, promotion_code: code) + order.friendly_order_promotions.create!( + order: order, + promotion: promotion, + promotion_code: SolidusFriendlyPromotions::PromotionCode.find_by(value: "discount") + ) + order.recalculate order.next! until order.can_complete? order.complete! end end - it "makes the promotion ineligible" do + it "makes the adjustment disappear" do expect{ order.complete - }.to change{ promo_adjustment.reload.eligible }.to(false) + }.to change { order.all_adjustments.friendly_promotion }.to([]) end it "adjusts the promo_total" do diff --git a/friendly_promotions/spec/spec_helper.rb b/friendly_promotions/spec/spec_helper.rb index 3413d8d94b9..7be29035966 100644 --- a/friendly_promotions/spec/spec_helper.rb +++ b/friendly_promotions/spec/spec_helper.rb @@ -37,6 +37,8 @@ config.before do Spree::Config.order_contents_class = "SolidusFriendlyPromotions::SimpleOrderContents" + Spree::Config.promotion_adjuster_class = "SolidusFriendlyPromotions::OrderPromotionAdjuster" + Spree::Config.promotion_chooser_class = "SolidusFriendlyPromotions::PromotionAdjustmentChooser" end end From aa57816f59f514124e1d1bb88589d0525ae871df Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 6 Jul 2023 14:18:14 +0200 Subject: [PATCH 091/524] Fix promotion code job and mailer --- .../promotion_code_batch_job.rb | 16 ++++----- .../promotion_code_batch_mailer.rb | 12 +++---- .../promotion_code_batch_errored.text.erb | 4 +-- .../promotion_code_batch_finished.text.erb | 2 +- friendly_promotions/config/locales/en.yml | 6 ++-- .../configuration.rb | 2 +- ...ec.rb => promotion_code_batch_job_spec.rb} | 34 +++++++++---------- ...rb => promotion_code_batch_mailer_spec.rb} | 12 +++---- 8 files changed, 44 insertions(+), 44 deletions(-) rename friendly_promotions/spec/jobs/solidus_friendly_promotions/{code_batch_job_spec.rb => promotion_code_batch_job_spec.rb} (62%) rename friendly_promotions/spec/mailers/solidus_friendly_promotions/{code_batch_mailer_spec.rb => promotion_code_batch_mailer_spec.rb} (73%) diff --git a/friendly_promotions/app/jobs/solidus_friendly_promotions/promotion_code_batch_job.rb b/friendly_promotions/app/jobs/solidus_friendly_promotions/promotion_code_batch_job.rb index 7d2f8010928..204bca2f01f 100644 --- a/friendly_promotions/app/jobs/solidus_friendly_promotions/promotion_code_batch_job.rb +++ b/friendly_promotions/app/jobs/solidus_friendly_promotions/promotion_code_batch_job.rb @@ -4,20 +4,20 @@ module SolidusFriendlyPromotions class PromotionCodeBatchJob < ActiveJob::Base queue_as :default - def perform(code_batch) + def perform(promotion_code_batch) PromotionCode::BatchBuilder.new( - code_batch + promotion_code_batch ).build_promotion_codes - if code_batch.email? - SolidusFriendlyPromotions.config.code_batch_mailer_class - .code_batch_finished(code_batch) + if promotion_code_batch.email? + SolidusFriendlyPromotions.config.promotion_code_batch_mailer_class + .promotion_code_batch_finished(promotion_code_batch) .deliver_now end rescue StandardError => error - if code_batch.email? - SolidusFriendlyPromotions.config.code_batch_mailer_class - .code_batch_errored(code_batch) + if promotion_code_batch.email? + SolidusFriendlyPromotions.config.promotion_code_batch_mailer_class + .promotion_code_batch_errored(promotion_code_batch) .deliver_now end raise error diff --git a/friendly_promotions/app/mailers/solidus_friendly_promotions/promotion_code_batch_mailer.rb b/friendly_promotions/app/mailers/solidus_friendly_promotions/promotion_code_batch_mailer.rb index 4ad53b93e92..d5c0e79f4c9 100644 --- a/friendly_promotions/app/mailers/solidus_friendly_promotions/promotion_code_batch_mailer.rb +++ b/friendly_promotions/app/mailers/solidus_friendly_promotions/promotion_code_batch_mailer.rb @@ -2,14 +2,14 @@ module SolidusFriendlyPromotions class PromotionCodeBatchMailer < Spree::BaseMailer - def promotion_code_batch_finished(code_batch) - @code_batch = code_batch - mail(to: code_batch.email) + def promotion_code_batch_finished(promotion_code_batch) + @promotion_code_batch = promotion_code_batch + mail(to: promotion_code_batch.email) end - def promotion_code_batch_errored(code_batch) - @code_batch = code_batch - mail(to: code_batch.email) + def promotion_code_batch_errored(promotion_code_batch) + @promotion_code_batch = promotion_code_batch + mail(to: promotion_code_batch.email) end end end diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/promotion_code_batch_mailer/promotion_code_batch_errored.text.erb b/friendly_promotions/app/views/solidus_friendly_promotions/promotion_code_batch_mailer/promotion_code_batch_errored.text.erb index 21aea738112..bb6b68eb69d 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/promotion_code_batch_mailer/promotion_code_batch_errored.text.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/promotion_code_batch_mailer/promotion_code_batch_errored.text.erb @@ -1,2 +1,2 @@ -<%= t(".message", error: @code_batch.error) %> -<%= @code_batch.promotion.name %> +<%= t(".message", error: @promotion_code_batch.error) %> +<%= @promotion_code_batch.promotion.name %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/promotion_code_batch_mailer/promotion_code_batch_finished.text.erb b/friendly_promotions/app/views/solidus_friendly_promotions/promotion_code_batch_mailer/promotion_code_batch_finished.text.erb index 872138b0c1b..9db0326bb62 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/promotion_code_batch_mailer/promotion_code_batch_finished.text.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/promotion_code_batch_mailer/promotion_code_batch_finished.text.erb @@ -1,2 +1,2 @@ <%= t(".message", number_of_codes: 10) %> -<%= @code_batch.promotion.name %> +<%= @promotion_code_batch.promotion.name %> diff --git a/friendly_promotions/config/locales/en.yml b/friendly_promotions/config/locales/en.yml index 81d13a6461e..5d47b489f33 100644 --- a/friendly_promotions/config/locales/en.yml +++ b/friendly_promotions/config/locales/en.yml @@ -69,11 +69,11 @@ en: starts_at_placeholder: Immediately edit: order_rules: Order Rules - code_batch_mailer: - code_batch_errored: + promotion_code_batch_mailer: + promotion_code_batch_errored: message: 'Promotion code batch errored (%{error}) for promotion: ' subject: Promotion code batch errored - code_batch_finished: + promotion_code_batch_finished: message: 'All %{number_of_codes} codes have been created for promotion: ' subject: Promotion code batch finished activerecord: diff --git a/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb b/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb index 32d5dcaac8c..78a16c3c344 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb @@ -16,7 +16,7 @@ class Configuration < Spree::Preferences::Configuration add_class_set :actions class_name_attribute :promotion_chooser_class, default: "SolidusFriendlyPromotions::PromotionAdjustmentChooser" - class_name_attribute :code_batch_mailer_class, default: "SolidusFriendlyPromotions::CodeBatchMailer" + class_name_attribute :promotion_code_batch_mailer_class, default: "SolidusFriendlyPromotions::PromotionCodeBatchMailer" end class << self diff --git a/friendly_promotions/spec/jobs/solidus_friendly_promotions/code_batch_job_spec.rb b/friendly_promotions/spec/jobs/solidus_friendly_promotions/promotion_code_batch_job_spec.rb similarity index 62% rename from friendly_promotions/spec/jobs/solidus_friendly_promotions/code_batch_job_spec.rb rename to friendly_promotions/spec/jobs/solidus_friendly_promotions/promotion_code_batch_job_spec.rb index bad0f6c2744..0ad6ca3b47a 100644 --- a/friendly_promotions/spec/jobs/solidus_friendly_promotions/code_batch_job_spec.rb +++ b/friendly_promotions/spec/jobs/solidus_friendly_promotions/promotion_code_batch_job_spec.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true require 'spec_helper' -RSpec.describe SolidusFriendlyPromotions::CodeBatchJob, type: :job do +RSpec.describe SolidusFriendlyPromotions::PromotionCodeBatchJob, type: :job do let(:email) { "test@email.com" } let(:code_batch) do - SolidusFriendlyPromotions::CodeBatch.create!( + SolidusFriendlyPromotions::PromotionCodeBatch.create!( promotion: create(:friendly_promotion), base_code: "test", number_of_codes: 10, @@ -13,13 +13,13 @@ end context "with a successful build" do before do - allow(SolidusFriendlyPromotions::CodeBatchMailer) - .to receive(:code_batch_finished) + allow(SolidusFriendlyPromotions::PromotionCodeBatchMailer) + .to receive(:promotion_code_batch_finished) .and_call_original end def codes - SolidusFriendlyPromotions::Code.pluck(:value) + SolidusFriendlyPromotions::PromotionCode.pluck(:value) end context 'with the default join character' do @@ -32,7 +32,7 @@ def codes end context 'with a custom join character' do let(:code_batch) do - SolidusFriendlyPromotions::CodeBatch.create!( + SolidusFriendlyPromotions::PromotionCodeBatch.create!( promotion: create(:friendly_promotion), base_code: "test", number_of_codes: 10, @@ -50,28 +50,28 @@ def codes context "with an email address" do it "sends an email" do subject.perform(code_batch) - expect(SolidusFriendlyPromotions::CodeBatchMailer) - .to have_received(:code_batch_finished) + expect(SolidusFriendlyPromotions::PromotionCodeBatchMailer) + .to have_received(:promotion_code_batch_finished) end end context "with no email address" do let(:email) { nil } it "sends an email" do subject.perform(code_batch) - expect(SolidusFriendlyPromotions::CodeBatchMailer) - .to_not have_received(:code_batch_finished) + expect(SolidusFriendlyPromotions::PromotionCodeBatchMailer) + .to_not have_received(:promotion_code_batch_finished) end end end context "with a failed build" do before do - allow_any_instance_of(SolidusFriendlyPromotions::Code::BatchBuilder) + allow_any_instance_of(SolidusFriendlyPromotions::PromotionCode::BatchBuilder) .to receive(:build_promotion_codes) .and_raise("Error") - allow(SolidusFriendlyPromotions::CodeBatchMailer) - .to receive(:code_batch_errored) + allow(SolidusFriendlyPromotions::PromotionCodeBatchMailer) + .to receive(:promotion_code_batch_errored) .and_call_original expect { subject.perform(code_batch) } @@ -80,16 +80,16 @@ def codes context "with an email address" do it "sends an email" do - expect(SolidusFriendlyPromotions::CodeBatchMailer) - .to have_received(:code_batch_errored) + expect(SolidusFriendlyPromotions::PromotionCodeBatchMailer) + .to have_received(:promotion_code_batch_errored) end end context "with no email address" do let(:email) { nil } it "sends an email" do - expect(SolidusFriendlyPromotions::CodeBatchMailer) - .to_not have_received(:code_batch_errored) + expect(SolidusFriendlyPromotions::PromotionCodeBatchMailer) + .to_not have_received(:promotion_code_batch_errored) end end end diff --git a/friendly_promotions/spec/mailers/solidus_friendly_promotions/code_batch_mailer_spec.rb b/friendly_promotions/spec/mailers/solidus_friendly_promotions/promotion_code_batch_mailer_spec.rb similarity index 73% rename from friendly_promotions/spec/mailers/solidus_friendly_promotions/code_batch_mailer_spec.rb rename to friendly_promotions/spec/mailers/solidus_friendly_promotions/promotion_code_batch_mailer_spec.rb index 129cd5a19e7..9180710df43 100644 --- a/friendly_promotions/spec/mailers/solidus_friendly_promotions/code_batch_mailer_spec.rb +++ b/friendly_promotions/spec/mailers/solidus_friendly_promotions/promotion_code_batch_mailer_spec.rb @@ -2,10 +2,10 @@ require 'spec_helper' -RSpec.describe SolidusFriendlyPromotions::CodeBatchMailer, type: :mailer do +RSpec.describe SolidusFriendlyPromotions::PromotionCodeBatchMailer, type: :mailer do let(:promotion) { create(:friendly_promotion, name: "Promotion Test") } let(:code_batch) do - SolidusFriendlyPromotions::CodeBatch.create!( + SolidusFriendlyPromotions::PromotionCodeBatch.create!( promotion_id: promotion.id, base_code: "test", number_of_codes: 10, @@ -13,8 +13,8 @@ ) end - describe "#code_batch_finished" do - subject { described_class.code_batch_finished(code_batch) } + describe "#promotion_code_batch_finished" do + subject { described_class.promotion_code_batch_finished(code_batch) } it "sends the email to the email attached to the promotion code batch " do expect(subject.to).to eq([code_batch.email]) @@ -29,9 +29,9 @@ end end - describe "#code_batch_errored" do + describe "#promotion_code_batch_errored" do before { code_batch.update(error: "Test error") } - subject { described_class.code_batch_errored(code_batch) } + subject { described_class.promotion_code_batch_errored(code_batch) } it "sends the email to the email attached to the promotion code batch " do expect(subject.to).to eq([code_batch.email]) From aefece393aece5e01ab84aa3f888749400b9636d Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 6 Jul 2023 14:44:24 +0200 Subject: [PATCH 092/524] Require table_name_prefix in initializer --- .../config/initializers/solidus_friendly_promotions.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/friendly_promotions/config/initializers/solidus_friendly_promotions.rb b/friendly_promotions/config/initializers/solidus_friendly_promotions.rb index e69de29bb2d..d4678cdaad8 100644 --- a/friendly_promotions/config/initializers/solidus_friendly_promotions.rb +++ b/friendly_promotions/config/initializers/solidus_friendly_promotions.rb @@ -0,0 +1 @@ +require_relative SolidusFriendlyPromotions::Engine.root.join("app/models/solidus_friendly_promotions.rb") From d7710810bb6abaaca5d8dd211ca5c72182200713 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 6 Jul 2023 14:44:59 +0200 Subject: [PATCH 093/524] Adapt PromotionRulesController to work with new models --- .../admin/promotion_rules_controller.rb | 6 +++--- .../admin/promotion_rules/_type_select.html.erb | 2 +- .../admin/promotions/_form.html.erb | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb index 3cfe1f55c63..dcebbebc991 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb @@ -12,7 +12,7 @@ class PromotionRulesController < Spree::Admin::BaseController def new if params.dig(:promotion_rule, :type) validate_promotion_rule_type - @promotion_rule = @promotion.promotion_rules.build(type: @promotion_rule_type) + @promotion_rule = @promotion.rules.build(type: @promotion_rule_type) end render layout: false end @@ -50,11 +50,11 @@ def location_after_save end def load_promotion - @promotion = Spree::Promotion.find(params[:promotion_id]) + @promotion = SolidusFriendlyPromotions::Promotion.find(params[:promotion_id]) end def model_class - Spree::PromotionRule + SolidusFriendlyPromotions::PromotionRule end def validate_promotion_rule_type diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/_type_select.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/_type_select.html.erb index 5dd101e1256..223cf88fc65 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/_type_select.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/_type_select.html.erb @@ -1,5 +1,5 @@ <%= form_with( - model: @promotion_rule || Spree::PromotionRule.new(promotion: @promotion), + model: @promotion_rule || SolidusFriendlyPromotions::PromotionRule.new(promotion: @promotion), scope: :promotion_rule, url: solidus_friendly_promotions.new_admin_promotion_promotion_rule_path(@promotion), method: :get diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_form.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_form.html.erb index 5f89e10c3ca..307210a9ee3 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_form.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_form.html.erb @@ -18,9 +18,9 @@ <% end %> <%= f.field_container :category do %> - <%= f.label :category_id, Spree::PromotionCategory.model_name.human %>
    + <%= f.label :promotion_category_id, SolidusFriendlyPromotions::PromotionCategory.model_name.human %>
    <%= - f.collection_select(:category_id, @promotion_categories, :id, :name, { include_blank: t('spree.match_choices.none') }, + f.collection_select(:promotion_category_id, @promotion_categories, :id, :name, { include_blank: t('spree.match_choices.none') }, { class: 'custom-select fullwidth' }) %> <% end %> From 81d9e162937d975d677ae148a66a251701efafdf Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 6 Jul 2023 19:29:02 +0200 Subject: [PATCH 094/524] Moar Giant Commits --- .../admin/promotion_rules_controller.rb | 4 ++-- .../products_promotion_rule.rb | 2 +- .../solidus_friendly_promotions/promotion.rb | 22 +++++++++++++++++++ .../promotion_rule.rb | 4 ++++ .../rules/line_item_product.rb | 4 ++++ .../rules/line_item_taxon.rb | 4 ++++ .../rules/product.rb | 4 ++++ .../rules/store.rb | 4 ++++ .../solidus_friendly_promotions/rules/user.rb | 4 ++++ .../promotion_rules/_promotion_rule.html.erb | 8 +++++++ .../admin/promotion_rules/new.html.erb | 7 ++++++ .../_first_repeat_purchase_since.html.erb | 6 ----- .../rules/_item_total.html.erb | 6 ----- .../rules/_line_item_option_value.html.erb | 6 ----- .../rules/_line_item_product.html.erb | 6 ----- .../rules/_line_item_taxon.html.erb | 6 ----- .../promotion_rules/rules/_nth_order.html.erb | 6 ----- .../rules/_option_value.html.erb | 6 ----- .../promotion_rules/rules/_product.html.erb | 6 ----- .../promotion_rules/rules/_store.html.erb | 6 ----- .../promotion_rules/rules/_taxon.html.erb | 15 ------------- .../promotion_rules/rules/_user.html.erb | 6 ----- .../promotion_rules/rules/_user_role.html.erb | 6 ----- .../admin/promotions/_new_rule_form.html.erb | 18 --------------- .../migrate/20230704083830_add_rule_tables.rb | 2 +- 25 files changed, 65 insertions(+), 103 deletions(-) delete mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_taxon.html.erb delete mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_new_rule_form.html.erb diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb index dcebbebc991..dc19ee5e595 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb @@ -27,7 +27,7 @@ def create end def update - @promotion_rule = @promotion.promotion_rules.find(params[:id]) + @promotion_rule = @promotion.rules.find(params[:id]) @promotion_rule.assign_attributes(promotion_rule_params) if @promotion_rule.save flash[:success] = t('spree.successfully_updated', resource: t('spree.promotion_rule')) @@ -36,7 +36,7 @@ def update end def destroy - @promotion_rule = @promotion.promotion_rules.find(params[:id]) + @promotion_rule = @promotion.rules.find(params[:id]) if @promotion_rule.destroy flash[:success] = t('spree.successfully_removed', resource: t('spree.promotion_rule')) end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/products_promotion_rule.rb b/friendly_promotions/app/models/solidus_friendly_promotions/products_promotion_rule.rb index b4091a1e3ad..dbe45cfb096 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/products_promotion_rule.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/products_promotion_rule.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions class ProductsPromotionRule < Spree::Base - belongs_to :product, class_name: "Spree::Product" + belongs_to :product, class_name: "Spree::Product", optional: true belongs_to :promotion_rule end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb index 9f7e91283d8..dcd5c112d33 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb @@ -64,5 +64,27 @@ def usage_limit_exceeded?(excluded_orders: []) usage_count(excluded_orders: excluded_orders) >= usage_limit end end + + def not_expired? + !expired? + end + + def not_started? + !started? + end + def started? + starts_at.nil? || starts_at < Time.current + end + def active? + started? && not_expired? && actions.present? + end + + def not_expired? + !expired? + end + + def expired? + expires_at.present? && expires_at < Time.current + end end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rule.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rule.rb index 9e083d1bc07..06181f15f24 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rule.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rule.rb @@ -32,6 +32,10 @@ def to_partial_path "solidus_friendly_promotions/admin/promotion_rules/rules/#{model_name.element}" end + def updateable? + preferences.any? + end + private def unique_per_promotion diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb index 732a008c6aa..c6ee3621a60 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb @@ -36,6 +36,10 @@ def product_ids_string=(product_ids) self.product_ids = product_ids.to_s.split(",").map(&:strip) end + def updateable? + true + end + private def inverse? diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb index 13af307926a..57bffa744a0 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb @@ -41,6 +41,10 @@ def taxon_ids_string=(taxon_ids) self.taxons = Spree::Taxon.find(taxon_ids) end + def updateable? + true + end + private # ids of taxons rules and taxons rules children diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb index c8906689faf..28bcf80296b 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb @@ -62,6 +62,10 @@ def product_ids_string=(product_ids) self.product_ids = product_ids.to_s.split(",").map(&:strip) end + def updateable? + true + end + private def order_products(order) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb index 70da23578a6..5e0077c2eb8 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb @@ -17,6 +17,10 @@ def applicable?(promotable) def eligible?(order, _options = {}) stores.none? || stores.include?(order.store) end + + def updateable? + true + end end end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb index 44dd285880b..96ae2e51299 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb @@ -25,6 +25,10 @@ def user_ids_string def user_ids_string=(user_ids) self.user_ids = user_ids.to_s.split(",").map(&:strip) end + + def updateable? + true + end end end end diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/_promotion_rule.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/_promotion_rule.html.erb index a2564b48829..e1c55fcec50 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/_promotion_rule.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/_promotion_rule.html.erb @@ -12,5 +12,13 @@ <%= hidden_field_tag "#{param_prefix}[id]", promotion_rule.id %> <%= render partial: "spree/shared/error_messages", locals: { target: promotion_rule } %> <%= render promotion_rule, promotion_rule: promotion_rule, param_prefix: "promotion_rule", form: form %> + + <% if promotion_rule.updateable? %> +
    +
    + <%= form.submit(t(:update, scope: [:solidus_friendly_promotions, :crud]), class: "btn btn-secondary float-right") %> +
    +
    + <% end %> <% end %>
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/new.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/new.html.erb index 022c47c3e7a..ad0174a57bd 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/new.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/new.html.erb @@ -14,6 +14,13 @@ <%= hidden_field_tag :level, @level %> <%= hidden_field_tag "promotion_rule[type]", @promotion_rule.class.name %> <%= render @promotion_rule, promotion_rule: @promotion_rule, param_prefix: "promotion_rule", form: form %> + +
    +
    + <%= form.submit(t(:add, scope: [:solidus_friendly_promotions, :crud]), class: "btn btn-secondary float-right") %> +
    +
    + <% end %>
    <% end %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_first_repeat_purchase_since.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_first_repeat_purchase_since.html.erb index a19c540b415..8e2e587aab7 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_first_repeat_purchase_since.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_first_repeat_purchase_since.html.erb @@ -11,9 +11,3 @@
    - -
    -
    - <%= form.submit(t((form.object.persisted? ? :update : :add), scope: [:solidus_friendly_promotions, :crud]), class: "btn btn-secondary float-right") %> -
    -
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_item_total.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_item_total.html.erb index 90c9d17c967..1b0767b2ea9 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_item_total.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_item_total.html.erb @@ -12,9 +12,3 @@
    - -
    -
    - <%= form.submit(t((form.object.persisted? ? :update : :add), scope: [:solidus_friendly_promotions, :crud]), class: "btn btn-secondary float-right") %> -
    -
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_line_item_option_value.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_line_item_option_value.html.erb index bedd2b5cb63..e3de3176772 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_line_item_option_value.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_line_item_option_value.html.erb @@ -19,9 +19,3 @@ - -
    -
    - <%= form.submit(t((form.object.persisted? ? :update : :add), scope: [:solidus_friendly_promotions, :crud]), class: "btn btn-secondary float-right") %> -
    -
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_line_item_product.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_line_item_product.html.erb index e2d7ac86d95..2c6f7dd5288 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_line_item_product.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_line_item_product.html.erb @@ -16,9 +16,3 @@ - -
    -
    - <%= form.submit(t((form.object.persisted? ? :update : :add), scope: [:solidus_friendly_promotions, :crud]), class: "btn btn-secondary float-right") %> -
    -
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_line_item_taxon.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_line_item_taxon.html.erb index 14db1d05fc8..1a2dbcf7e48 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_line_item_taxon.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_line_item_taxon.html.erb @@ -12,9 +12,3 @@ ) %> - -
    -
    - <%= form.submit(t((form.object.persisted? ? :update : :add), scope: [:solidus_friendly_promotions, :crud]), class: "btn btn-secondary float-right") %> -
    -
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_nth_order.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_nth_order.html.erb index 2ccb5f0eebc..0a37ce60cf1 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_nth_order.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_nth_order.html.erb @@ -10,9 +10,3 @@ - -
    -
    - <%= form.submit(t((form.object.persisted? ? :update : :add), scope: [:solidus_friendly_promotions, :crud]), class: "btn btn-secondary float-right") %> -
    -
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_option_value.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_option_value.html.erb index bedd2b5cb63..e3de3176772 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_option_value.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_option_value.html.erb @@ -19,9 +19,3 @@ - -
    -
    - <%= form.submit(t((form.object.persisted? ? :update : :add), scope: [:solidus_friendly_promotions, :crud]), class: "btn btn-secondary float-right") %> -
    -
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_product.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_product.html.erb index c0ec650e450..2d11f098832 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_product.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_product.html.erb @@ -13,9 +13,3 @@ - -
    -
    - <%= form.submit(t((form.object.persisted? ? :update : :add), scope: [:solidus_friendly_promotions, :crud]), class: "btn btn-secondary float-right") %> -
    -
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_store.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_store.html.erb index 1018cccf30d..cce8ac2e37f 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_store.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_store.html.erb @@ -4,9 +4,3 @@ options_from_collection_for_select(Spree::Store.all, :id, :name, promotion_rule.store_ids), multiple: true, class: "select2 fullwidth" %> - -
    -
    - <%= form.submit(t((form.object.persisted? ? :update : :add), scope: [:solidus_friendly_promotions, :crud]), class: "btn btn-secondary float-right") %> -
    -
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_taxon.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_taxon.html.erb deleted file mode 100644 index 3df5bf3bde5..00000000000 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_taxon.html.erb +++ /dev/null @@ -1,15 +0,0 @@ -
    - <%= label_tag "#{param_prefix}_taxon_ids_string", t('spree.taxon_rule.choose_taxons') %> - <%= hidden_field_tag "#{param_prefix}[taxon_ids_string]", promotion_rule.taxon_ids.join(","), class: "taxon_picker fullwidth", id: 'product_taxon_ids' %> -
    -
    - -
    - -
    -
    - <%= form.submit(t((form.object.persisted? ? :update : :add), scope: [:solidus_friendly_promotions, :crud]), class: "btn btn-secondary float-right") %> -
    -
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_user.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_user.html.erb index 1f76adf4a52..91391a18fd0 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_user.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_user.html.erb @@ -2,9 +2,3 @@
    - -
    -
    - <%= form.submit(t((form.object.persisted? ? :update : :add), scope: [:solidus_friendly_promotions, :crud]), class: "btn btn-secondary float-right") %> -
    -
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_user_role.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_user_role.html.erb index cd82a42faa1..e078526d08f 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_user_role.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_user_role.html.erb @@ -10,9 +10,3 @@ <%= t('spree.user_role_rule.label', select: select_tag("#{param_prefix}[preferred_match_policy]", options_for_select(Spree::Promotion::Rules::UserRole::MATCH_POLICIES.map{ |s| [t("spree.user_role_rule.match_#{s}"),s] }, promotion_rule.preferred_match_policy), { class: 'custom-select'})).html_safe %> - -
    -
    - <%= form.submit(t((form.object.persisted? ? :update : :add), scope: [:solidus_friendly_promotions, :crud]), class: "btn btn-secondary float-right") %> -
    -
    diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_new_rule_form.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_new_rule_form.html.erb deleted file mode 100644 index a4ff1196a01..00000000000 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_new_rule_form.html.erb +++ /dev/null @@ -1,18 +0,0 @@ -<%= turbo_frame_tag @promotion, "new_promotion_rule" do %> -
    - <%= form_tag solidus_friendly_promotions.new_admin_promotion_promotion_rule_path(@promotion), method: :get, remote: false do %> - <% if can?(:update, @promotion) %> -
    - <%= label_tag :promotion_rule_type, Spree::PromotionRule.human_attribute_name(:type) %> -
    - <%= select_tag('promotion_rule[type]', options_for_promotion_rule_types(@promotion, level), include_blank: t(:choose_promotion_rule, scope: 'spree'), class: 'custom-select fullwidth', required: true) %> - <%= hidden_field_tag("level", @level) %> -
    - <%= button_tag t('spree.actions.add'), class: 'btn btn-secondary' %> -
    -
    -
    - <% end %> - <% end %> -
    -<% end %> diff --git a/friendly_promotions/db/migrate/20230704083830_add_rule_tables.rb b/friendly_promotions/db/migrate/20230704083830_add_rule_tables.rb index 314b2d8cf4d..bdabd239ccf 100644 --- a/friendly_promotions/db/migrate/20230704083830_add_rule_tables.rb +++ b/friendly_promotions/db/migrate/20230704083830_add_rule_tables.rb @@ -22,7 +22,7 @@ def change end create_table :friendly_promotion_rules_stores do |t| - t.references :store, index: true, null: false, foreign_key: { to_table: :spree_users } + t.references :store, index: true, null: false, foreign_key: { to_table: :spree_stores } t.references :promotion_rule, index: true, null: false t.timestamps From 7a434455f9a2a80525ae63ee2d6fa0e24214cc00 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 6 Jul 2023 20:41:58 +0200 Subject: [PATCH 095/524] Fix Taxon promotion rule --- .../promotion_rules_taxon.rb | 4 ++-- .../solidus_friendly_promotions/rules/taxon.rb | 14 +++++++++----- .../admin/promotion_rules/rules/_taxon.html.erb | 9 +++++++++ .../rules/taxon_spec.rb | 16 ++++++++++++++++ 4 files changed, 36 insertions(+), 7 deletions(-) create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_taxon.html.erb diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rules_taxon.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rules_taxon.rb index d020bdaeb04..976b6644e85 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rules_taxon.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rules_taxon.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions class PromotionRulesTaxon < Spree::Base - belongs_to :promotion_rule - belongs_to :taxon, class_name: "Spree::Taxon" + belongs_to :promotion_rule, class_name: "SolidusFriendlyPromotions::PromotionRule", optional: true + belongs_to :taxon, class_name: "Spree::Taxon", optional: true end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb index a2d4750af0d..38925b555cc 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb @@ -3,8 +3,9 @@ module SolidusFriendlyPromotions module Rules class Taxon < PromotionRule - has_many :promotion_rules_taxons, inverse_of: :promotion_rule, dependent: :destroy - has_many :taxons, through: :promotion_rules_taxons, class_name: "Spree::Taxon" + has_many :promotion_rules_taxons, class_name: 'SolidusFriendlyPromotions::PromotionRulesTaxon', foreign_key: :promotion_rule_id, + dependent: :destroy + has_many :taxons, through: :promotion_rules_taxons, class_name: 'Spree::Taxon' def preload_relations [:taxons] @@ -47,12 +48,15 @@ def eligible?(order, _options = {}) end def taxon_ids_string - taxons.pluck(:id).join(",") + taxon_ids.join(",") end def taxon_ids_string=(taxon_ids) - taxon_ids = taxon_ids.to_s.split(",").map(&:strip) - self.taxons = Spree::Taxon.find(taxon_ids) + self.taxon_ids = taxon_ids.to_s.split(",").map(&:strip) + end + + def updateable? + true end private diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_taxon.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_taxon.html.erb new file mode 100644 index 00000000000..bec9eb7e9c3 --- /dev/null +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_taxon.html.erb @@ -0,0 +1,9 @@ +
    + <%= label_tag "#{param_prefix}_taxon_ids_string", t('spree.taxon_rule.choose_taxons') %> + <%= hidden_field_tag "#{param_prefix}[taxon_ids_string]", promotion_rule.taxon_ids.join(","), class: "taxon_picker fullwidth", id: 'product_taxon_ids' %> +
    +
    + +
    diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/taxon_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/taxon_spec.rb index c3108065d28..4c237479243 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/taxon_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/taxon_spec.rb @@ -14,6 +14,22 @@ SolidusFriendlyPromotions::Rules::Taxon.create!(promotion: create(:friendly_promotion)) end + describe "taxon_ids_string=" do + let!(:promotion) { create(:friendly_promotion) } + let!(:taxon_1) { create(:taxon) } + let!(:taxon_2) { create(:taxon) } + let(:rule) { promotion.rules.build(type: described_class.to_s) } + + subject { rule.assign_attributes("taxon_ids_string" => taxon_2.id.to_s) } + + it "creates a valid rule with a taxon" do + subject + expect(rule).to be_valid + rule.save! + expect(rule.reload.taxons).to include(taxon_2) + end + end + context "#eligible?(order)" do context "with any match policy" do before do From 461afc00d5789219c71798310e93204d83aaf127 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 6 Jul 2023 20:45:31 +0200 Subject: [PATCH 096/524] Fix Promotion Rule User --- .../promotion_rules_user.rb | 4 ++-- .../models/solidus_friendly_promotions/rules/user.rb | 5 ++++- .../solidus_friendly_promotions/rules/user_spec.rb | 12 ++++++++++++ 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rules_user.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rules_user.rb index 91f2f209be8..01661959e33 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rules_user.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rules_user.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions class PromotionRulesUser < Spree::Base - belongs_to :promotion_rule - belongs_to :user, class_name: Spree::UserClassHandle.new + belongs_to :promotion_rule, class_name: "SolidusFriendlyPromotions::PromotionRule", optional: true + belongs_to :user, class_name: Spree::UserClassHandle.new, optional: true end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb index 96ae2e51299..98b2d94c0f0 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb @@ -3,7 +3,10 @@ module SolidusFriendlyPromotions module Rules class User < PromotionRule - has_many :promotion_rules_users, inverse_of: :promotion_rule, dependent: :destroy + has_many :promotion_rules_users, + class_name: 'SolidusFriendlyPromotions::PromotionRulesUser', + foreign_key: :promotion_rule_id, + dependent: :destroy has_many :users, through: :promotion_rules_users, class_name: Spree::UserClassHandle.new def preload_relations diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_spec.rb index 0b78257bb31..7f38bf41da5 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_spec.rb @@ -6,6 +6,18 @@ it { is_expected.to have_many(:users) } let(:rule) { described_class.new } + describe "user_ids=" do + let(:promotion) { create(:friendly_promotion) } + let(:user) { create(:user) } + let(:rule) { promotion.rules.new } + + subject { rule.user_ids = [user.id] } + + it "creates a valid rule with a user" do + expect(rule).to be_valid + end + end + context "#eligible?(order)" do let(:order) { Spree::Order.new } From 77cf7ce510ff889a5c2dd570ad5706d1a91e8f47 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 6 Jul 2023 23:45:46 +0200 Subject: [PATCH 097/524] Tweak PromotionRulesController --- .../admin/promotion_rules_controller.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb index dc19ee5e595..a12f4a1ad56 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb @@ -18,8 +18,9 @@ def new end def create - @promotion_rule = @promotion_rule_type.new(promotion_rule_params) - @promotion_rule.promotion = @promotion + @promotion_rule = @promotion.rules.build( + promotion_rule_params.merge(type: @promotion_rule_type.to_s) + ) if @promotion_rule.save flash[:success] = t('spree.successfully_created', resource: t('spree.promotion_rule')) end @@ -83,7 +84,7 @@ def validate_level end def promotion_rule_params - params[:promotion_rule].permit! + params[:promotion_rule].try(:permit!) || {} end def promotion_rule_types From 2b3524c2e070fc127954daa46d5a3de08f079756 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 6 Jul 2023 23:51:32 +0200 Subject: [PATCH 098/524] Fix Store promotion rule --- .../promotion_rules_store.rb | 4 ++-- .../solidus_friendly_promotions/rules/store.rb | 4 +++- .../rules/store_spec.rb | 15 +++++++++++++++ 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rules_store.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rules_store.rb index 2f6dd6c8a5c..b326a0f23cb 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rules_store.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rules_store.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions class PromotionRulesStore < Spree::Base - belongs_to :promotion_rule - belongs_to :store, class_name: "Spree::Store" + belongs_to :promotion_rule, class_name: "SolidusFriendlyPromotions::PromotionRule", optional: true + belongs_to :store, class_name: "Spree::Store", optional: true end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb index 5e0077c2eb8..3c08784975c 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb @@ -3,7 +3,9 @@ module SolidusFriendlyPromotions module Rules class Store < PromotionRule - has_many :promotion_rules_stores, inverse_of: :promotion_rule, dependent: :destroy + has_many :promotion_rules_stores, class_name: "SolidusFriendlyPromotions::PromotionRulesStore", + foreign_key: :promotion_rule_id, + dependent: :destroy has_many :stores, through: :promotion_rules_stores, class_name: "Spree::Store" def preload_relations diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/store_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/store_spec.rb index 50120abb572..7f4d9d2139b 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/store_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/store_spec.rb @@ -7,6 +7,21 @@ let(:rule) { described_class.new } + describe "store_ids=" do + let!(:promotion) { create(:friendly_promotion) } + let!(:unimportant_store) { create(:store) } + let!(:store) { create(:store) } + let(:rule) { promotion.rules.build(type: described_class.to_s) } + + subject { rule.store_ids = [store.id] } + + it "creates a valid rule with a store" do + subject + expect(rule).to be_valid + expect(rule.stores).to include(store) + end + end + context "#eligible?(order)" do let(:order) { Spree::Order.new } From 13a1ab579e80d80ada4e9d46c2f59aa64cfb5d42 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 6 Jul 2023 23:57:06 +0200 Subject: [PATCH 099/524] Fix promotion rules product --- .../products_promotion_rule.rb | 2 +- .../models/solidus_friendly_promotions/rules/product.rb | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/products_promotion_rule.rb b/friendly_promotions/app/models/solidus_friendly_promotions/products_promotion_rule.rb index dbe45cfb096..2f14e2d648d 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/products_promotion_rule.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/products_promotion_rule.rb @@ -3,6 +3,6 @@ module SolidusFriendlyPromotions class ProductsPromotionRule < Spree::Base belongs_to :product, class_name: "Spree::Product", optional: true - belongs_to :promotion_rule + belongs_to :promotion_rule, class_name: "SolidusFriendlyPromotions::PromotionRule", optional: true end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb index 28bcf80296b..9ae2b78f76d 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb @@ -7,8 +7,12 @@ module Rules # either come from assigned product group or are assingned directly to # the rule. class Product < PromotionRule - has_many :products_promotion_rules, inverse_of: :promotion_rule, dependent: :destroy - has_many :products, class_name: "Spree::Product", through: :products_promotion_rules + has_many :products_promotion_rules, + dependent: :destroy, + foreign_key: :promotion_rule_id, + class_name: 'SolidusFriendlyPromotions::ProductsPromotionRule' + has_many :products, class_name: 'Spree::Product', through: :products_promotion_rules + def preload_relations [:products] From aa0908462d40fc1f63df45f9d7e268505d7bdc97 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 7 Jul 2023 12:10:45 +0200 Subject: [PATCH 100/524] Fix Actions Controller for new actions --- .../admin/promotion_actions_controller.rb | 26 ++++++++--- .../admin/promotion_actions_helper.rb | 2 +- .../admin/promotion_actions/new.html.erb | 2 +- .../admin/promotions/edit.html.erb | 44 ++++++++++--------- 4 files changed, 46 insertions(+), 28 deletions(-) diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb index 0d592ff055d..ccbfbfada7d 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb @@ -3,15 +3,24 @@ module SolidusFriendlyPromotions module Admin class PromotionActionsController < Spree::Admin::BaseController - before_action :load_promotion, only: [:create, :destroy, :new] + before_action :validate_level, only: :new + before_action :load_promotion, only: [:create, :destroy, :new, :update] before_action :validate_promotion_action_type, only: :create def new + if params.dig(:promotion_action, :type) + validate_promotion_action_type + @promotion_action = @promotion.actions.build(type: @promotion_action_type) + + if params.dig(:promotion_action, :calculator_type) + @promotion_action.calculator_type = params[:promotion_action][:calculator_type] + end + end render layout: false end def create - @promotion_action = @promotion_action_type.new(permitted_resource_params) + @promotion_action = @promotion_action_type.new(promotion_action_params) @promotion_action.promotion = @promotion if @promotion_action.save(validate: false) flash[:success] = t('spree.successfully_created', resource: t('spree.promotion_action')) @@ -22,8 +31,8 @@ def create end def update - @promotion_action = @promotion.promotion_actions.find(params[:id]) - @promotion_action.assign_attributes(permitted_resource_params) + @promotion_action = @promotion.actions.find(params[:id]) + @promotion_action.assign_attributes(promotion_action_params) if @promotion_action.save flash[:success] = t('spree.successfully_updated', resource: t('spree.promotion_action')) redirect_to location_after_save, format: :html @@ -33,7 +42,7 @@ def update end def destroy - @promotion_action = @promotion.promotion_actions.find(params[:id]) + @promotion_action = @promotion.actions.find(params[:id]) if @promotion_action.discard flash[:success] = t('spree.successfully_removed', resource: t('spree.promotion_action')) end @@ -47,7 +56,7 @@ def location_after_save end def load_promotion - @promotion = Spree::Promotion.find(params[:promotion_id]) + @promotion = SolidusFriendlyPromotions::Promotion.find(params[:promotion_id]) end def validate_level @@ -60,6 +69,11 @@ def validate_level end end + + def promotion_action_params + params[:promotion_action].try(:permit!) || {} + end + def validate_promotion_action_type requested_type = params[:promotion_action].delete(:type) promotion_action_types = SolidusFriendlyPromotions.config.actions diff --git a/friendly_promotions/app/helpers/solidus_friendly_promotions/admin/promotion_actions_helper.rb b/friendly_promotions/app/helpers/solidus_friendly_promotions/admin/promotion_actions_helper.rb index ba897d8b304..1d97662340d 100644 --- a/friendly_promotions/app/helpers/solidus_friendly_promotions/admin/promotion_actions_helper.rb +++ b/friendly_promotions/app/helpers/solidus_friendly_promotions/admin/promotion_actions_helper.rb @@ -12,7 +12,7 @@ def options_for_promotion_action_calculator_types(promotion_action) def options_for_promotion_action_types(promotion_action) actions = SolidusFriendlyPromotions.config.actions options = actions.map { |action| [action.model_name.human, action.name] } - options_for_select(options, promotion_action.type.to_s) + options_for_select(options, promotion_action&.type&.to_s) end def promotion_actions_by_level(promotion, level) diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/new.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/new.html.erb index b95fe080550..f7ea1bd4cdb 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/new.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/new.html.erb @@ -1,4 +1,4 @@ -<%= turbo_frame_tag @promotion, "new_#{@level}_promotion_action" do %> +<%= turbo_frame_tag @promotion, "new_promotion_action" do %>
    <%= t(:add_action, scope: :solidus_friendly_promotions) %>
    <%= link_to_with_icon 'trash', '', spree.edit_admin_promotion_path(@promotion), class: 'delete' %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/edit.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/edit.html.erb index 97ed6162bf6..34402a4f4cf 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/edit.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/edit.html.erb @@ -40,30 +40,34 @@
    - <% [:line_item, :shipment].each do |level| %> -
    -
    - <%= t("#{level}_actions", scope: :solidus_friendly_promotions) %> - - <%= render partial: 'solidus_friendly_promotions/admin/promotion_actions/promotion_action', collection: promotion_actions_by_level(@promotion, level), locals: {} %> - - <%= turbo_frame_tag @promotion, "new_#{level}_promotion_action" do %> - <%= link_to t(:add_action, scope: :solidus_friendly_promotions), solidus_friendly_promotions.new_admin_promotion_promotion_action_path(@promotion, level: level), class: 'btn btn-secondary' %> - <% end %> -
    +
    + <%= turbo_frame_tag @promotion, "new_promotion_action" do %> + <%= link_to t(:add_action, scope: :solidus_friendly_promotions), solidus_friendly_promotions.new_admin_promotion_promotion_action_path(@promotion), class: 'btn btn-secondary' %> + <% end %>
    -
    +
    + +
    + <% [:line_item, :shipment].each do |level| %> <% if promotion_actions_by_level(@promotion, level).any? %> -
    - <%= t("#{level}_rules", scope: :solidus_friendly_promotions) %> +
    +
    + <%= t("#{level}_actions", scope: :solidus_friendly_promotions) %> - <%= render partial: 'solidus_friendly_promotions/admin/promotion_rules/promotion_rule', collection: promotion_rules_by_level(@promotion, level), locals: { level: level } %> + <%= render partial: 'solidus_friendly_promotions/admin/promotion_actions/promotion_action', collection: promotion_actions_by_level(@promotion, level), locals: {} %> +
    +
    +
    +
    + <%= t("#{level}_rules", scope: :solidus_friendly_promotions) %> - <%= turbo_frame_tag @promotion, "new_#{level}_promotion_rule" do %> - <%= link_to t(:add_rule, scope: :solidus_friendly_promotions), solidus_friendly_promotions.new_admin_promotion_promotion_rule_path(@promotion, level: level), class: 'btn btn-secondary' %> - <% end %> -
    + <%= render partial: 'solidus_friendly_promotions/admin/promotion_rules/promotion_rule', collection: promotion_rules_by_level(@promotion, level), locals: { level: level } %> + + <%= turbo_frame_tag @promotion, "new_#{level}_promotion_rule" do %> + <%= link_to t(:add_rule, scope: :solidus_friendly_promotions), solidus_friendly_promotions.new_admin_promotion_promotion_rule_path(@promotion, level: level), class: 'btn btn-secondary' %> + <% end %> +
    +
    <% end %> -
    <% end %>
    From 257bed8e2e186f8f8e61639620b6de6f0365b90c Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 7 Jul 2023 12:29:29 +0200 Subject: [PATCH 101/524] Fix admin promotions spec --- .../admin/promotions/index.html.erb | 8 ++++---- .../testing_support/friendly_promotion_factory.rb | 10 ---------- .../products_promotion_rule_spec.rb | 4 ++-- .../promotion_rules_store_spec.rb | 4 ++-- .../promotion_rules_taxon_spec.rb | 4 ++-- .../promotion_rules_user_spec.rb | 4 ++-- .../admin/promotions_spec.rb | 14 +++++++------- 7 files changed, 19 insertions(+), 29 deletions(-) diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/index.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/index.html.erb index 4c35e9d766c..8395681c812 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/index.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/index.html.erb @@ -18,28 +18,28 @@
    - <%= label_tag :q_name_cont, Spree::Promotion.human_attribute_name(:name) %> + <%= label_tag :q_name_cont, SolidusFriendlyPromotions::Promotion.human_attribute_name(:name) %> <%= f.text_field :name_cont, tabindex: 1 %>
    - <%= label_tag :q_codes_value_cont, Spree::Promotion.human_attribute_name(:code) %> + <%= label_tag :q_codes_value_cont, SolidusFriendlyPromotions::Promotion.human_attribute_name(:code) %> <%= f.text_field :codes_value_cont, tabindex: 1 %>
    - <%= label_tag :q_path_cont, Spree::Promotion.human_attribute_name(:path) %> + <%= label_tag :q_path_cont, SolidusFriendlyPromotions::Promotion.human_attribute_name(:path) %> <%= f.text_field :path_cont, tabindex: 1 %>
    - <%= label_tag :q_promotion_category_id_eq, Spree::PromotionCategory.model_name.human %>
    + <%= label_tag :q_promotion_category_id_eq, SolidusFriendlyPromotions::PromotionCategory.model_name.human %>
    <%= f.collection_select(:promotion_category_id_eq, @promotion_categories, :id, :name, { include_blank: t('spree.match_choices.all') }, { class: 'custom-select fullwidth' }) %>
    diff --git a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_factory.rb b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_factory.rb index b48f67d86ad..244716876c8 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_factory.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_factory.rb @@ -13,16 +13,6 @@ end end - trait :with_action do - transient do - promotion_action_class { SolidusFriendlyPromotions::Actions::AdjustLineItem } - end - - after(:create) do |promotion, evaluator| - promotion.actions << evaluator.promotion_action_class.new - end - end - trait :with_adjustable_action do transient do preferred_amount { 10 } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/products_promotion_rule_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/products_promotion_rule_spec.rb index fff40dd1c28..c837246e2a6 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/products_promotion_rule_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/products_promotion_rule_spec.rb @@ -3,6 +3,6 @@ require 'spec_helper' RSpec.describe SolidusFriendlyPromotions::ProductsPromotionRule do - it { is_expected.to belong_to(:product) } - it { is_expected.to belong_to(:promotion_rule) } + it { is_expected.to belong_to(:product).optional } + it { is_expected.to belong_to(:promotion_rule).optional } end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_store_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_store_spec.rb index e212e065331..02e47b4cdb5 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_store_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_store_spec.rb @@ -3,6 +3,6 @@ require 'spec_helper' RSpec.describe SolidusFriendlyPromotions::PromotionRulesStore do - it { is_expected.to belong_to(:store) } - it { is_expected.to belong_to(:promotion_rule) } + it { is_expected.to belong_to(:store).optional } + it { is_expected.to belong_to(:promotion_rule).optional } end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_taxon_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_taxon_spec.rb index 9214b152643..b0c06384862 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_taxon_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_taxon_spec.rb @@ -3,6 +3,6 @@ require 'spec_helper' RSpec.describe SolidusFriendlyPromotions::PromotionRulesTaxon do - it { is_expected.to belong_to(:taxon) } - it { is_expected.to belong_to(:promotion_rule) } + it { is_expected.to belong_to(:taxon).optional } + it { is_expected.to belong_to(:promotion_rule).optional } end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_user_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_user_spec.rb index e7003c49341..981cc07d43a 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_user_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_user_spec.rb @@ -3,6 +3,6 @@ require 'spec_helper' RSpec.describe SolidusFriendlyPromotions::PromotionRulesUser do - it { is_expected.to belong_to(:user) } - it { is_expected.to belong_to(:promotion_rule) } + it { is_expected.to belong_to(:user).optional } + it { is_expected.to belong_to(:promotion_rule).optional } end diff --git a/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotions_spec.rb b/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotions_spec.rb index d73fc22bd13..c12e2cc1d63 100644 --- a/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotions_spec.rb +++ b/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotions_spec.rb @@ -6,10 +6,10 @@ stub_authorization! describe "#index" do - let!(:promotion1) { create(:friendly_promotion, :with_action, name: "name1", code: "code1", path: "path1") } - let!(:promotion2) { create(:friendly_promotion, :with_action, name: "name2", code: "code2", path: "path2") } + let!(:promotion1) { create(:friendly_promotion, :with_adjustable_action, name: "name1", code: "code1", path: "path1") } + let!(:promotion2) { create(:friendly_promotion, :with_adjustable_action, name: "name2", code: "code2", path: "path2") } let!(:promotion3) do - create(:friendly_promotion, :with_action, name: "name3", code: "code3", path: "path3", expires_at: Date.yesterday) + create(:friendly_promotion, :with_adjustable_action, name: "name3", code: "code3", path: "path3", expires_at: Date.yesterday) end let!(:category) { create :friendly_promotion_category } @@ -64,12 +64,12 @@ visit solidus_friendly_promotions.admin_promotions_path click_link "New Promotion" expect(page).to have_field("Name") - expect(page).to have_field("Start") - expect(page).to have_field("End") + expect(page).to have_field("Starts at") + expect(page).to have_field("Expires at") expect(page).to have_field("Description") fill_in("Name", with: "20 percent off") - fill_in("Start", with: Time.current) - fill_in("End", with: 1.week.from_now) + fill_in("Starts at", with: Time.current) + fill_in("Expires at", with: 1.week.from_now) choose("Apply to all orders") click_button("Create") expect(page).to have_content("20 percent off") From 031a8a1a4294f8b63ac052092b2b65d5d64a44d7 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 7 Jul 2023 14:11:54 +0200 Subject: [PATCH 102/524] add more promotion interface --- .../solidus_friendly_promotions/promotion.rb | 30 ++ .../promotion_action.rb | 2 +- .../promotion_spec.rb | 458 ++++++++++++++++++ 3 files changed, 489 insertions(+), 1 deletion(-) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb index dcd5c112d33..362ee580158 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb @@ -13,8 +13,11 @@ class Promotion < Spree::Base validates :usage_limit, numericality: { greater_than: 0, allow_nil: true } validates :per_code_usage_limit, numericality: { greater_than_or_equal_to: 0, allow_nil: true } validates :description, length: { maximum: 255 } + validate :apply_automatically_disallowed_with_paths scope :active, -> { has_actions.started_and_unexpired } + scope :advertised, -> { where(advertise: true) } + scope :coupons, -> { joins(:codes).distinct } scope :started_and_unexpired, -> do table = arel_table time = Time.current @@ -55,6 +58,15 @@ def usage_count(excluded_orders: []) count end + def used_by?(user, excluded_orders = []) + discounted_orders. + complete. + where.not(id: excluded_orders.map(&:id)). + where(user: user). + where.not(spree_orders: { state: :canceled }). + exists? + end + # Whether the promotion has exceeded its usage restrictions. # # @param excluded_orders [Array] Orders to exclude from usage limit @@ -72,13 +84,19 @@ def not_expired? def not_started? !started? end + def started? starts_at.nil? || starts_at < Time.current end + def active? started? && not_expired? && actions.present? end + def inactive? + !active? + end + def not_expired? !expired? end @@ -86,5 +104,17 @@ def not_expired? def expired? expires_at.present? && expires_at < Time.current end + + def products + rules.where(type: "SolidusFriendlyPromotions::Rules::Product").flat_map(&:products).uniq + end + + private + + def apply_automatically_disallowed_with_paths + return unless apply_automatically + + errors.add(:apply_automatically, :disallowed_with_path) if path.present? + end end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb index 265a42c4ee0..ae0b075ca5b 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb @@ -13,7 +13,7 @@ class PromotionAction < Spree::Base include Spree::AdjustmentSource belongs_to :promotion, inverse_of: :actions - has_many :adjustments, as: :source + has_many :adjustments, class_name: "Spree::Adjustment", as: :source scope :of_type, ->(type) { where(type: Array.wrap(type).map(&:to_s)) } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb index 7a0e1ce116d..b60bcb9e93a 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb @@ -30,4 +30,462 @@ expect(@valid_promotion).not_to be_valid end end + + describe ".advertised" do + let(:promotion) { create(:friendly_promotion) } + let(:advertised_promotion) { create(:friendly_promotion, advertise: true) } + + it "only shows advertised promotions" do + advertised = described_class.advertised + expect(advertised).to include(advertised_promotion) + expect(advertised).not_to include(promotion) + end + end + + describe ".coupons" do + let(:promotion_code) { create(:friendly_promotion_code) } + let!(:promotion_with_code) { promotion_code.promotion } + let!(:another_promotion_code) { create(:friendly_promotion_code, promotion: promotion_with_code) } + let!(:promotion_without_code) { create(:friendly_promotion) } + + subject { described_class.coupons } + + it "returns only distinct promotions with a code associated" do + expect(subject).to eq [promotion_with_code] + end + end + + describe '.active' do + subject { described_class.active } + + let(:promotion) { create(:friendly_promotion, starts_at: Date.yesterday, name: "name1") } + + before { promotion } + + it "doesn't return promotion without actions" do + expect(subject).to be_empty + end + + context 'when promotion has an action' do + let(:promotion) { create(:friendly_promotion, :with_adjustable_action, starts_at: Date.yesterday, name: "name1") } + + it 'returns promotion with action' do + expect(subject).to match [promotion] + end + end + end + + describe '.has_actions' do + subject { described_class.has_actions } + + let(:promotion) { create(:friendly_promotion, starts_at: Date.yesterday, name: "name1") } + + before { promotion } + + it "doesn't return promotion without actions" do + expect(subject).to be_empty + end + + context 'when promotion has two actions' do + let(:promotion) { create(:friendly_promotion, :with_adjustable_action, starts_at: Date.yesterday, name: "name1") } + + before do + promotion.actions << SolidusFriendlyPromotions::Actions::AdjustShipment.new(calculator: SolidusFriendlyPromotions::Calculators::Percent.new) + end + + it 'returns distinct promotion' do + expect(subject).to match [promotion] + end + end + end + + describe "#apply_automatically" do + subject { build(:friendly_promotion) } + + it "defaults to false" do + expect(subject.apply_automatically).to eq(false) + end + + context "when set to true" do + before { subject.apply_automatically = true } + + it "should remain valid" do + expect(subject).to be_valid + end + + it "invalidates the promotion when it has a path" do + subject.path = "foo" + expect(subject).to_not be_valid + expect(subject.errors).to include(:apply_automatically) + end + end + end + + describe "#usage_limit_exceeded?" do + subject { promotion.usage_limit_exceeded? } + + shared_examples "it should" do + context "when there is a usage limit" do + context "and the limit is not exceeded" do + let(:usage_limit) { 10 } + it { is_expected.to be_falsy } + end + context "and the limit is exceeded" do + let(:usage_limit) { 1 } + context "on a different order" do + before do + FactoryBot.create( + :completed_order_with_friendly_promotion, + promotion: promotion + ) + promotion.actions.first.adjustments.update_all(eligible: true) + end + it { is_expected.to be_truthy } + end + context "on the same order" do + it { is_expected.to be_falsy } + end + end + end + context "when there is no usage limit" do + let(:usage_limit) { nil } + it { is_expected.to be_falsy } + end + end + + context "with an item-level adjustment" do + let(:promotion) do + FactoryBot.create( + :friendly_promotion, + :with_line_item_adjustment, + code: "discount", + usage_limit: usage_limit + ) + end + before do + order.friendly_order_promotions.create( + promotion_code: promotion.codes.first, + promotion: promotion + ) + order.recalculate + end + context "when there are multiple line items" do + let(:order) { FactoryBot.create(:order_with_line_items, line_items_count: 2) } + describe "the first item" do + let(:promotable) { order.line_items.first } + it_behaves_like "it should" + end + describe "the second item" do + let(:promotable) { order.line_items.last } + it_behaves_like "it should" + end + end + context "when there is a single line item" do + let(:order) { FactoryBot.create(:order_with_line_items) } + let(:promotable) { order.line_items.first } + it_behaves_like "it should" + end + end + end + + describe "#usage_count" do + let(:promotion) do + FactoryBot.create( + :friendly_promotion, + :with_line_item_adjustment, + code: "discount" + ) + end + + subject { promotion.usage_count } + + context "when the code is applied to a non-complete order" do + let(:order) { FactoryBot.create(:order_with_line_items) } + before do + order.friendly_order_promotions.create( + promotion_code: promotion.codes.first, + promotion: promotion + ) + order.recalculate + end + it { is_expected.to eq 0 } + end + context "when the code is applied to a complete order" do + let!(:order) do + FactoryBot.create( + :completed_order_with_friendly_promotion, + promotion: promotion + ) + end + context "and the promo is eligible" do + it { is_expected.to eq 1 } + end + context "and the promo is ineligible" do + before { order.all_adjustments.friendly_promotion.update_all(eligible: false) } + it { is_expected.to eq 0 } + end + context "and the order is canceled" do + before { order.cancel! } + it { is_expected.to eq 0 } + it { expect(order.state).to eq 'canceled' } + end + end + end + + + context "#inactive" do + let(:promotion) { create(:friendly_promotion, :with_adjustable_action) } + + it "should not be exipired" do + expect(promotion).not_to be_inactive + end + + it "should be inactive if it hasn't started yet" do + promotion.starts_at = Time.current + 1.day + expect(promotion).to be_inactive + end + + it "should be inactive if it has already ended" do + promotion.expires_at = Time.current - 1.day + expect(promotion).to be_inactive + end + + it "should not be inactive if it has started already" do + promotion.starts_at = Time.current - 1.day + expect(promotion).not_to be_inactive + end + + it "should not be inactive if it has not ended yet" do + promotion.expires_at = Time.current + 1.day + expect(promotion).not_to be_inactive + end + + it "should not be inactive if current time is within starts_at and expires_at range" do + promotion.starts_at = Time.current - 1.day + promotion.expires_at = Time.current + 1.day + expect(promotion).not_to be_inactive + end + end + + describe '#not_started?' do + let(:promotion) { SolidusFriendlyPromotions::Promotion.new(starts_at: starts_at) } + subject { promotion.not_started? } + + context 'no starts_at date' do + let(:starts_at) { nil } + it { is_expected.to be_falsey } + end + + context 'when starts_at date is in the past' do + let(:starts_at) { Time.current - 1.day } + it { is_expected.to be_falsey } + end + + context 'when starts_at date is not already reached' do + let(:starts_at) { Time.current + 1.day } + it { is_expected.to be_truthy } + end + end + + describe '#started?' do + let(:promotion) { SolidusFriendlyPromotions::Promotion.new(starts_at: starts_at) } + subject { promotion.started? } + + context 'when no starts_at date' do + let(:starts_at) { nil } + it { is_expected.to be_truthy } + end + + context 'when starts_at date is in the past' do + let(:starts_at) { Time.current - 1.day } + it { is_expected.to be_truthy } + end + + context 'when starts_at date is not already reached' do + let(:starts_at) { Time.current + 1.day } + it { is_expected.to be_falsey } + end + end + + describe '#expired?' do + let(:promotion) { SolidusFriendlyPromotions::Promotion.new(expires_at: expires_at) } + subject { promotion.expired? } + + context 'when no expires_at date' do + let(:expires_at) { nil } + it { is_expected.to be_falsey } + end + + context 'when expires_at date is not already reached' do + let(:expires_at) { Time.current + 1.day } + it { is_expected.to be_falsey } + end + + context 'when expires_at date is in the past' do + let(:expires_at) { Time.current - 1.day } + it { is_expected.to be_truthy } + end + end + + describe '#not_expired?' do + let(:promotion) { SolidusFriendlyPromotions::Promotion.new(expires_at: expires_at) } + subject { promotion.not_expired? } + + context 'when no expired_at date' do + let(:expires_at) { nil } + it { is_expected.to be_truthy } + end + + context 'when expires_at date is not already reached' do + let(:expires_at) { Time.current + 1.day } + it { is_expected.to be_truthy } + end + + context 'when expires_at date is in the past' do + let(:expires_at) { Time.current - 1.day } + it { is_expected.to be_falsey } + end + end + + context "#active" do + it "shouldn't be active if it has started already" do + promotion.starts_at = Time.current - 1.day + expect(promotion.active?).to eq(false) + end + + it "shouldn't be active if it has not ended yet" do + promotion.expires_at = Time.current + 1.day + expect(promotion.active?).to eq(false) + end + + it "shouldn't be active if current time is within starts_at and expires_at range" do + promotion.starts_at = Time.current - 1.day + promotion.expires_at = Time.current + 1.day + expect(promotion.active?).to eq(false) + end + + it "shouldn't be active if there are no start and end times set" do + promotion.starts_at = nil + promotion.expires_at = nil + expect(promotion.active?).to eq(false) + end + + context 'when promotion has an action' do + let(:promotion) { create(:promotion, :with_action, name: "name1") } + + it "should be active if it has started already" do + promotion.starts_at = Time.current - 1.day + expect(promotion.active?).to eq(true) + end + + it "should be active if it has not ended yet" do + promotion.expires_at = Time.current + 1.day + expect(promotion.active?).to eq(true) + end + + it "should be active if current time is within starts_at and expires_at range" do + promotion.starts_at = Time.current - 1.day + promotion.expires_at = Time.current + 1.day + expect(promotion.active?).to eq(true) + end + + it "should be active if there are no start and end times set" do + promotion.starts_at = nil + promotion.expires_at = nil + expect(promotion.active?).to eq(true) + end + end + end + + context "#products" do + let(:promotion) { create(:friendly_promotion) } + + context "when it has product rules with products associated" do + let(:promotion_rule) { SolidusFriendlyPromotions::Rules::Product.new } + + before do + promotion_rule.promotion = promotion + promotion_rule.products << create(:product) + promotion_rule.save + end + + it "should have products" do + expect(promotion.reload.products.size).to eq(1) + end + end + + context "when there's no product rule associated" do + it "should not have products but still return an empty array" do + expect(promotion.products).to be_blank + end + end + end + + # regression for https://github.com/spree/spree/issues/4059 + # admin form posts the code and path as empty string + describe "normalize blank values for path" do + it "will save blank value as nil value instead" do + promotion = Spree::Promotion.create(name: "A promotion", path: "") + expect(promotion.path).to be_nil + end + end + + describe '#used_by?' do + subject { promotion.used_by? user, [excluded_order] } + + let(:promotion) { create :friendly_promotion, :with_adjustable_action } + let(:user) { create :user } + let(:order) { create :order_with_line_items, user: user } + let(:excluded_order) { create :order_with_line_items, user: user } + + before do + order.user_id = user.id + order.save! + end + + context 'when the user has used this promo' do + before do + order.friendly_order_promotions.create( + promotion: promotion + ) + order.recalculate + order.completed_at = Time.current + order.save! + end + + context 'when the order is complete' do + it { is_expected.to be true } + + context 'when the promotion was not eligible' do + let(:adjustment) { order.all_adjustments.first } + + before do + adjustment.eligible = false + adjustment.save! + end + + it { is_expected.to be false } + end + + context 'when the only matching order is the excluded order' do + let(:excluded_order) { order } + it { is_expected.to be false } + end + end + + context 'when the order is not complete' do + let(:order) { create :order, user: user } + + # The before clause above sets the completed at + # value for this order + before { order.update completed_at: nil } + + it { is_expected.to be false } + end + end + + context 'when the user has not used this promo' do + it { is_expected.to be false } + end + end end From e0f225bffd3a7d2d551e128bf480ce9b5f16e933 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 7 Jul 2023 16:11:25 +0200 Subject: [PATCH 103/524] Several more fixes Remove unused JS and CSS Fix Promotion Codes Controllers --- .../backend/solidus_friendly_promotions.js | 2 -- .../frontend/solidus_friendly_promotions.js | 2 -- .../backend/solidus_friendly_promotions.css | 4 ---- .../frontend/solidus_friendly_promotions.css | 4 ---- .../admin/promotion_actions_controller.rb | 10 +++++----- .../admin/promotion_code_batches_controller.rb | 4 ++-- .../admin/promotion_codes_controller.rb | 18 +++++++++--------- .../admin/promotion_rules_controller.rb | 10 +++++----- .../order_decorator.rb | 2 +- .../promotion_action.rb | 4 ++-- .../_calculator_select.html.erb | 2 +- .../promotion_actions/_type_select.html.erb | 4 ++-- .../admin/promotion_actions/new.html.erb | 2 +- .../promotion_rules/_type_select.html.erb | 2 +- friendly_promotions/config/locales/en.yml | 10 ++++++++++ 15 files changed, 39 insertions(+), 41 deletions(-) delete mode 100644 friendly_promotions/app/assets/javascripts/spree/backend/solidus_friendly_promotions.js delete mode 100644 friendly_promotions/app/assets/javascripts/spree/frontend/solidus_friendly_promotions.js delete mode 100644 friendly_promotions/app/assets/stylesheets/spree/backend/solidus_friendly_promotions.css delete mode 100644 friendly_promotions/app/assets/stylesheets/spree/frontend/solidus_friendly_promotions.css diff --git a/friendly_promotions/app/assets/javascripts/spree/backend/solidus_friendly_promotions.js b/friendly_promotions/app/assets/javascripts/spree/backend/solidus_friendly_promotions.js deleted file mode 100644 index 8aa3b014409..00000000000 --- a/friendly_promotions/app/assets/javascripts/spree/backend/solidus_friendly_promotions.js +++ /dev/null @@ -1,2 +0,0 @@ -// Placeholder manifest file. -// the installer will append this file to the app vendored assets here: vendor/assets/javascripts/spree/backend/all.js' \ No newline at end of file diff --git a/friendly_promotions/app/assets/javascripts/spree/frontend/solidus_friendly_promotions.js b/friendly_promotions/app/assets/javascripts/spree/frontend/solidus_friendly_promotions.js deleted file mode 100644 index a79f2e948dd..00000000000 --- a/friendly_promotions/app/assets/javascripts/spree/frontend/solidus_friendly_promotions.js +++ /dev/null @@ -1,2 +0,0 @@ -// Placeholder manifest file. -// the installer will append this file to the app vendored assets here: vendor/assets/javascripts/spree/frontend/all.js' \ No newline at end of file diff --git a/friendly_promotions/app/assets/stylesheets/spree/backend/solidus_friendly_promotions.css b/friendly_promotions/app/assets/stylesheets/spree/backend/solidus_friendly_promotions.css deleted file mode 100644 index e3c236629e3..00000000000 --- a/friendly_promotions/app/assets/stylesheets/spree/backend/solidus_friendly_promotions.css +++ /dev/null @@ -1,4 +0,0 @@ -/* -Placeholder manifest file. -the installer will append this file to the app vendored assets here: 'vendor/assets/stylesheets/spree/backend/all.css' -*/ diff --git a/friendly_promotions/app/assets/stylesheets/spree/frontend/solidus_friendly_promotions.css b/friendly_promotions/app/assets/stylesheets/spree/frontend/solidus_friendly_promotions.css deleted file mode 100644 index da236237c52..00000000000 --- a/friendly_promotions/app/assets/stylesheets/spree/frontend/solidus_friendly_promotions.css +++ /dev/null @@ -1,4 +0,0 @@ -/* -Placeholder manifest file. -the installer will append this file to the app vendored assets here: 'vendor/assets/stylesheets/spree/frontend/all.css' -*/ diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb index ccbfbfada7d..8581e38a7ba 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb @@ -23,7 +23,7 @@ def create @promotion_action = @promotion_action_type.new(promotion_action_params) @promotion_action.promotion = @promotion if @promotion_action.save(validate: false) - flash[:success] = t('spree.successfully_created', resource: t('spree.promotion_action')) + flash[:success] = t('spree.successfully_created', resource: SolidusFriendlyPromotions::PromotionAction.model_name.human) redirect_to location_after_save, format: :html else render :new, layout: false @@ -34,7 +34,7 @@ def update @promotion_action = @promotion.actions.find(params[:id]) @promotion_action.assign_attributes(promotion_action_params) if @promotion_action.save - flash[:success] = t('spree.successfully_updated', resource: t('spree.promotion_action')) + flash[:success] = t('spree.successfully_updated', resource: SolidusFriendlyPromotions::PromotionAction.model_name.human) redirect_to location_after_save, format: :html else render :edit @@ -44,7 +44,7 @@ def update def destroy @promotion_action = @promotion.actions.find(params[:id]) if @promotion_action.discard - flash[:success] = t('spree.successfully_removed', resource: t('spree.promotion_action')) + flash[:success] = t('spree.successfully_removed', resource: SolidusFriendlyPromotions::PromotionAction.model_name.human) end redirect_to location_after_save, format: :html end @@ -81,9 +81,9 @@ def validate_promotion_action_type klass.name == requested_type end if !@promotion_action_type - flash[:error] = t('spree.invalid_promotion_action') + flash[:error] = t('solidus_friendly_promotions.invalid_promotion_action') respond_to do |format| - format.html { redirect_to spree.edit_admin_promotion_path(@promotion) } + format.html { redirect_to solidus_friendly_promotions.edit_admin_promotion_path(@promotion) } format.js { render layout: false } end end diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_code_batches_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_code_batches_controller.rb index 850c9ce772f..44cea4fde55 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_code_batches_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_code_batches_controller.rb @@ -3,14 +3,14 @@ module SolidusFriendlyPromotions module Admin class PromotionCodeBatchesController < Spree::Admin::ResourceController - belongs_to 'spree/promotion' + belongs_to 'solidus_friendly_promotions/promotion' create.after :build_promotion_code_batch def download require "csv" - @promotion_code_batch = Spree::PromotionCodeBatch.find( + @promotion_code_batch = SolidusFriendlyPromotions::PromotionCodeBatch.find( params[:promotion_code_batch_id] ) diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_codes_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_codes_controller.rb index 5cf5628f98c..42b857d0fbe 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_codes_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_codes_controller.rb @@ -6,8 +6,8 @@ module SolidusFriendlyPromotions module Admin class PromotionCodesController < Spree::Admin::ResourceController def index - @promotion = Spree::Promotion.accessible_by(current_ability, :show).find(params[:promotion_id]) - @promotion_codes = @promotion.promotion_codes.order(:value) + @promotion = SolidusFriendlyPromotions::Promotion.accessible_by(current_ability, :show).find(params[:promotion_id]) + @promotion_codes = @promotion.codes.order(:value) respond_to do |format| format.html do @@ -22,22 +22,22 @@ def index end def new - @promotion = Spree::Promotion.accessible_by(current_ability, :show).find(params[:promotion_id]) + @promotion = SolidusFriendlyPromotions::Promotion.accessible_by(current_ability, :show).find(params[:promotion_id]) if @promotion.apply_automatically - flash[:error] = t('activerecord.errors.models.spree/promotion_code.attributes.base.disallowed_with_apply_automatically') - redirect_to admin_promotion_promotion_codes_url(@promotion) + flash[:error] = t('activerecord.errors.models.solidus_friendly_promotions/promotion_code.attributes.base.disallowed_with_apply_automatically') + redirect_to solidus_friendly_promotions.admin_promotion_promotion_codes_url(@promotion) else - @promotion_code = @promotion.promotion_codes.build + @promotion_code = @promotion.codes.build end end def create - @promotion = Spree::Promotion.accessible_by(current_ability, :show).find(params[:promotion_id]) - @promotion_code = @promotion.promotion_codes.build(value: params[:promotion_code][:value]) + @promotion = SolidusFriendlyPromotions::Promotion.accessible_by(current_ability, :show).find(params[:promotion_id]) + @promotion_code = @promotion.codes.build(value: params[:promotion_code][:value]) if @promotion_code.save flash[:success] = flash_message_for(@promotion_code, :successfully_created) - redirect_to admin_promotion_promotion_codes_url(@promotion) + redirect_to solidus_friendly_promotions.admin_promotion_promotion_codes_url(@promotion) else flash.now[:error] = @promotion_code.errors.full_messages.to_sentence render_after_create_error diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb index a12f4a1ad56..9f0cb2c43d4 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb @@ -22,7 +22,7 @@ def create promotion_rule_params.merge(type: @promotion_rule_type.to_s) ) if @promotion_rule.save - flash[:success] = t('spree.successfully_created', resource: t('spree.promotion_rule')) + flash[:success] = t('spree.successfully_created', resource: SolidusFriendlyPromotions::PromotionRule.model_name.human) end redirect_to location_after_save end @@ -31,7 +31,7 @@ def update @promotion_rule = @promotion.rules.find(params[:id]) @promotion_rule.assign_attributes(promotion_rule_params) if @promotion_rule.save - flash[:success] = t('spree.successfully_updated', resource: t('spree.promotion_rule')) + flash[:success] = t('spree.successfully_updated', resource: SolidusFriendlyPromotions::PromotionRule.model_name.human) end redirect_to location_after_save end @@ -39,7 +39,7 @@ def update def destroy @promotion_rule = @promotion.rules.find(params[:id]) if @promotion_rule.destroy - flash[:success] = t('spree.successfully_removed', resource: t('spree.promotion_rule')) + flash[:success] = t('spree.successfully_removed', resource: SolidusFriendlyPromotions::PromotionRule.model_name.human) end redirect_to location_after_save end @@ -65,9 +65,9 @@ def validate_promotion_rule_type klass.name == requested_type end if !@promotion_rule_type - flash[:error] = t('spree.invalid_promotion_rule') + flash[:error] = t('solidus_friendly_promotions.invalid_promotion_rule') respond_to do |format| - format.html { redirect_to spree.edit_admin_promotion_path(@promotion) } + format.html { redirect_to solidus_friendly_promotions.edit_admin_promotion_path(@promotion) } format.js { render layout: false } end end diff --git a/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_decorator.rb b/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_decorator.rb index 29d97327d9b..e1b4e4d1d8e 100644 --- a/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_decorator.rb +++ b/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_decorator.rb @@ -11,7 +11,7 @@ def ensure_promotions_eligible if promo_total_changed? restart_checkout_flow recalculate - errors.add(:base, I18n.t('spree.promotion_total_changed_before_complete')) + errors.add(:base, I18n.t('solidus_friendly_promotions.promotion_total_changed_before_complete')) end super diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb index ae0b075ca5b..b5676cf3885 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb @@ -42,8 +42,8 @@ def compute_amount(adjustable) def adjustment_label(adjustable) I18n.t( - "spree.adjustment_labels.#{adjustable.class.name.demodulize.underscore}", - promotion: Spree::Promotion.model_name.human, + "solidus_friendly_promotions.adjustment_labels.#{adjustable.class.name.demodulize.underscore}", + promotion: SolidusFriendlyPromotions::Promotion.model_name.human, promotion_name: promotion.name, ) end diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/_calculator_select.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/_calculator_select.html.erb index 90cf01e4eac..17315241c0c 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/_calculator_select.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/_calculator_select.html.erb @@ -6,7 +6,7 @@ form.select :calculator_type, options_for_promotion_action_calculator_types(form.object), { - include_blank: t(:choose_promotion_action_calculator, scope: 'solidus_friendly_shipping') + include_blank: t(:choose_promotion_action_calculator, scope: 'solidus_friendly_promotions') }, class: 'custom-select fullwidth', onchange: 'this.form.requestSubmit()', diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/_type_select.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/_type_select.html.erb index 2823d3ab3b9..137858065cd 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/_type_select.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/_type_select.html.erb @@ -1,11 +1,11 @@ <%= form_with model: @promotion_action, scope: :promotion_action, url: solidus_friendly_promotions.new_admin_promotion_promotion_action_path(@promotion), method: :get do |form| %> <%= form.label :type %> - <%= admin_hint t('spree.adjustment_type'), t(:promotions, scope: [:spree, :hints, "spree/calculator"]) %> + <%= admin_hint t('solidus_friendly_promotions.adjustment_type'), t(:promotions, scope: [:solidus_friendly_promotions, :hints, "solidus_friendly_promotions/calculator"]) %> <%= form.select :type, options_for_promotion_action_types(form.object), { - include_blank: t(:choose_promotion_action, scope: 'solidus_friendly_shipping') + include_blank: t(:choose_promotion_action, scope: 'solidus_friendly_promotions') }, class: 'custom-select fullwidth', onchange: 'this.form.requestSubmit()', diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/new.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/new.html.erb index f7ea1bd4cdb..8a251db50a7 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/new.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/new.html.erb @@ -1,7 +1,7 @@ <%= turbo_frame_tag @promotion, "new_promotion_action" do %>
    <%= t(:add_action, scope: :solidus_friendly_promotions) %>
    - <%= link_to_with_icon 'trash', '', spree.edit_admin_promotion_path(@promotion), class: 'delete' %> + <%= link_to_with_icon 'trash', '', solidus_friendly_promotions.edit_admin_promotion_path(@promotion), class: 'delete' %> <%= render 'type_select' %> <% if @promotion_action.respond_to?(:calculator) %> <%= render 'calculator_select', path: solidus_friendly_promotions.new_admin_promotion_promotion_action_path(@promotion), promotion_action: @promotion_action %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/_type_select.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/_type_select.html.erb index 223cf88fc65..8e049733d25 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/_type_select.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/_type_select.html.erb @@ -11,7 +11,7 @@ form.select :type, options_for_promotion_rule_types(form.object, level), { - include_blank: t(:choose_promotion_rule, scope: 'solidus_friendly_shipping') + include_blank: t(:choose_promotion_rule, scope: 'solidus_friendly_promotions') }, class: 'custom-select fullwidth', onchange: 'this.form.requestSubmit()', diff --git a/friendly_promotions/config/locales/en.yml b/friendly_promotions/config/locales/en.yml index 5d47b489f33..33920c0e76a 100644 --- a/friendly_promotions/config/locales/en.yml +++ b/friendly_promotions/config/locales/en.yml @@ -4,6 +4,10 @@ en: solidus_friendly_promotions: actions: Actions + adjustment_labels: + line_item: "%{promotion} (%{promotion_name})" + shipment: "%{promotion} (%{promotion_name})" + adjustment_type: Adjustment type add_action: New Action add_rule: New Rule order_rules: Order Rules @@ -12,6 +16,8 @@ en: line_item_actions: Line Item Actions shipment_actions: Shipment Actions invalid_promotion_rule_level: Invalid Promotion Rule Level. Must be one of "order", "shipment", or "line_item" + invalid_promotion_action: Invalid promotion action. + invalid_promotion_rule: Invalid promotion rule. create_promotion_code: Create promotion code current_promotion_usage: 'Current Usage: %{count}' discount_rules: Promotion Rules @@ -21,6 +27,7 @@ en: new_promotion_code_batch: New Promotion Code Batch no_rules_addes: No Rules Added promotion_successfully_created: Promotion has been successfully created! + promotion_total_changed_before_complete: One or more of the promotions on your order have become ineligible and were removed. Please check the new order amounts and try again. view_promotion_codes_list: View codes list promotion_rules: line_item_product: @@ -31,6 +38,9 @@ en: match_policies: include: Line item's product has one of the chosen taxons exclude: Line item's product does not have one of the chosen taxons + hints: + solidus_friendly_promotions/calculator: + promotions: This is used to determine the promotional discount to be applied to an order, an item, or shipping charges. product_rule: choose_products: Choose products label: Order must contain %{select} of these products From 7c8e675cc6dc6b9a431f113f3d980bc0b2b4991e Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 14 Jul 2023 11:38:31 +0200 Subject: [PATCH 104/524] Fill out README. --- friendly_promotions/README.md | 63 +++++++++++++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 3 deletions(-) diff --git a/friendly_promotions/README.md b/friendly_promotions/README.md index 950e71818ea..b9dbab8a5b0 100644 --- a/friendly_promotions/README.md +++ b/friendly_promotions/README.md @@ -3,25 +3,82 @@ [![CircleCI](https://circleci.com/gh/solidusio-contrib/solidus_friendly_promotions.svg?style=shield)](https://circleci.com/gh/solidusio-contrib/solidus_friendly_promotions) [![codecov](https://codecov.io/gh/solidusio-contrib/solidus_friendly_promotions/branch/master/graph/badge.svg)](https://codecov.io/gh/solidusio-contrib/solidus_friendly_promotions) - +This extension replaces Solidus core's promotion system. It is intended as both a research project and a working alternative to how promotions work in core. + +The basic architecture is very similar to the one in core Solidus, but with a few decisive tweaks, which I'll explain in the coming sections. + +## Architecture + +This extension centralizes promotion handling in the order updater. A service class, the `SolidusFriendlyShipping::OrderPromotionAdjuster` applies the current promotion configuration to the order, adjusting or removing adjustments as necessary. + +In core, Promotion adjustments get recalculated twice on every change to the cart; once in `Spree::OrderContents#after_add_or_remove` and in `Spree::OrderUpdater#update_promotions`. To make things more complicated, `Spree::OrderContents` leverages the `Spree::PromotionHandler#cart`, while the order updater goes through `Spree::Adjustment#recalculate`. + +The design decision here is to make the code path easier to follow, and consequently to make it more performant ("Make it easy, then make it fast"). + +`SolidusFriendlyShipping::Promotion` objects have rules and actions, just like `Spree::Promotion`. However, both rules and actions work slightly differently. + +### Promotion Rules + +Promotion rules can be applicable to either `Spree::Order`, `Spree::LineItem`, or `Spree::Shipment` objects. If they are applicable, they will be asked for eligibility. Rules applicable to orders are processed first. If a promotion has a rule that makes it inapplicable for an order, line items and shipments will not be adjusted. + +### Promotion Actions + +There are only two actions by default that should cover most use cases: `AdjustLineItem` and `AdjustShipment`. Ther is no action that creates order-level adjustments, as this feature of core Solidus has proven to be very difficult for customer service and finance departments due to the difficulty of accruing order-level adjustments to individual line items when e.g. processing returns. In order to give a fixed discount to all line items in an order, use the `AdjustLineItem` action with the `DistributedAmount` calculator. + +Both actions are calculable. By setting their `calculator` to one of the classes provided, a great range of discount possibilities is maintained. + +### Connecting promotions to orders + +When there is a join record `SolidusFriendlyPromotions::OrderPromotion`, the promotion and the order will be "connected", and the promotion will be applied even if it does not `apply_automatically` (see below). There's a difference to Solidus' system here in that promotions are not automatically connected to orders when they apply. + +One way of connecting orders to promotions is via a promotion code. + +### Promotion categories + +Promotion categories simply allow admins to group promotion actions. They have no further significance with regards to the functionality of the promotion system. This is the same behavior as in core. + ## Installation Add solidus_friendly_promotions to your Gemfile: ```ruby -gem 'solidus_friendly_promotions' +gem 'solidus_friendly_promotions', github: 'friendlycart/solidus_friendly_promotion', branch: 'main' ``` +Once this project is out of the research phase, a proper gem release will follow. + Bundle your dependencies and run the installation generator: ```shell bin/rails generate solidus_friendly_promotions:install ``` +This will create the tables for this extension. It will also replace the promotion administration system under +`/admin/promotions` with a new one that needs `turbo-rails`. + +It will also create an initializer within which Solidus is configured to use this extension's `SimpleOrderContents` and `OrderPromotionAdjuster` classes. Feel free to override with your own implementations! + ## Usage - +Add a promotion using the admin. Add rules and actions, and observe promotions getting applied how you'd expect them to. + +In the admin screen, you can set a number of attributes on your promotion: +- Name: The name of the promotion. The `name` attribute will also be used to generate adjustment labels. In multi-lingual stores, you probably want different promotions per language for this reason. + +- Description: This is purely informative. Some stores use `description` in order display information about this promotion to customers, but there is nothing in core Solidus that does it. + +- Start and End: Outside of the time range between `starts_at` and `expires_at`, the promotion will not be eligible to any order. + +- Usage Limit: `usage_limit` controls to how many orders this promotion can be applied, independent of promotion code or user. This is most commonly used to limit the risk of losing too much revenue with a particular promotion. + +- Path: `path` is a URL path that connects the promotion to the order upon visitation. Not currently implemented in either Solidus core or this extension. + +- Per Code Usage Limit: How often each code can be used. Useful for limiting how many orders can be placed with a single promotion code. + +- Apply Automatically: Whether this promotion should apply automatically. This means that the promotion is checked for eligibility every time the customer's order is recalculated. Promotion Codes and automatic applications are incompatible. + +- Promotion Category: Used to group promotions in the admin view. ## Development From 9c74b0a5ef59995e0998aa5881e42b4fb1ef3376 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 14 Jul 2023 11:41:39 +0200 Subject: [PATCH 105/524] Move Solidus configuration to initializer It's documented like this in the README. --- .../install/templates/initializer.rb | 4 ++++ friendly_promotions/spec/models/promotion/integration_spec.rb | 1 - friendly_promotions/spec/spec_helper.rb | 2 -- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb index 6db51718192..ea1a5762cc4 100644 --- a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb +++ b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb @@ -1,5 +1,9 @@ # frozen_string_literal: true +# Replace solidus core's order contents and promotion adjuster classes with ours. +Spree::Config.order_contents_class = "SolidusFriendlyPromotions::SimpleOrderContents" +Spree::Config.promotion_adjuster_class = "SolidusFriendlyPromotions::OrderPromotionAdjuster" + # Replace the promotions menu from core with ours Spree::Backend::Config.configure do |config| config.menu_items = Spree::Backend::Config.menu_items.map do |item| diff --git a/friendly_promotions/spec/models/promotion/integration_spec.rb b/friendly_promotions/spec/models/promotion/integration_spec.rb index d03c7c8b42e..9fce597fbd7 100644 --- a/friendly_promotions/spec/models/promotion/integration_spec.rb +++ b/friendly_promotions/spec/models/promotion/integration_spec.rb @@ -10,7 +10,6 @@ let(:order) { create(:order) } before do - Spree::Config.promotion_adjuster_class = "SolidusFriendlyPromotions::OrderPromotionAdjuster" Spree::Config.promotion_chooser_class = "SolidusFriendlyPromotions::PromotionAdjustmentChooser" promotion.rules << rule promotion.actions << action diff --git a/friendly_promotions/spec/spec_helper.rb b/friendly_promotions/spec/spec_helper.rb index 7be29035966..94f54247705 100644 --- a/friendly_promotions/spec/spec_helper.rb +++ b/friendly_promotions/spec/spec_helper.rb @@ -36,8 +36,6 @@ config.include SolidusFriendlyPromotions::Engine.routes.url_helpers, type: :request config.before do - Spree::Config.order_contents_class = "SolidusFriendlyPromotions::SimpleOrderContents" - Spree::Config.promotion_adjuster_class = "SolidusFriendlyPromotions::OrderPromotionAdjuster" Spree::Config.promotion_chooser_class = "SolidusFriendlyPromotions::PromotionAdjustmentChooser" end end From a69aa2929efb91d5c7721010a75436120703e963 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 14 Jul 2023 11:45:50 +0200 Subject: [PATCH 106/524] Move spec setup to initializer This is more in-line with the README. --- .../install/templates/initializer.rb | 6 ++++-- .../spec/models/promotion/integration_spec.rb | 1 - .../calculators/distributed_amount_spec.rb | 5 ----- .../simple_order_contents_spec.rb | 5 ----- friendly_promotions/spec/spec_helper.rb | 4 ---- 5 files changed, 4 insertions(+), 17 deletions(-) diff --git a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb index ea1a5762cc4..f0ea79c260f 100644 --- a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb +++ b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb @@ -20,8 +20,10 @@ end SolidusFriendlyPromotions.configure do |config| - # TODO: Remember to change this with the actual preferences you have implemented! - # config.sample_preference = 'sample_value' + # This class chooses which promotion should apply to a line item in case + # that more than one promotion is eligible. + config.promotion_chooser_class = "SolidusFriendlyPromotions::PromotionAdjustmentChooser" + config.shipment_discount_calculators = [ "SolidusFriendlyPromotions::Calculators::FlatRate", "SolidusFriendlyPromotions::Calculators::FlexiRate", diff --git a/friendly_promotions/spec/models/promotion/integration_spec.rb b/friendly_promotions/spec/models/promotion/integration_spec.rb index 9fce597fbd7..f4623fc4878 100644 --- a/friendly_promotions/spec/models/promotion/integration_spec.rb +++ b/friendly_promotions/spec/models/promotion/integration_spec.rb @@ -10,7 +10,6 @@ let(:order) { create(:order) } before do - Spree::Config.promotion_chooser_class = "SolidusFriendlyPromotions::PromotionAdjustmentChooser" promotion.rules << rule promotion.actions << action order.contents.add(shirt.master, 1) diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/distributed_amount_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/distributed_amount_spec.rb index e43050cf6d9..469d3630965 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/distributed_amount_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/distributed_amount_spec.rb @@ -11,11 +11,6 @@ let(:order) { create(:order_with_line_items, line_items_attributes: line_items_attributes) } let(:currency) { "USD" } - before do - Spree::Config.promotion_adjuster_class = "SolidusFriendlyPromotions::OrderPromotionAdjuster" - Spree::Config.promotion_chooser_class = "SolidusFriendlyPromotions::PromotionAdjustmentChooser" - end - context 'applied to an order' do let(:line_items_attributes) { [{ price: 20 }, { price: 30 }, { price: 100 }] } before do diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/simple_order_contents_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/simple_order_contents_spec.rb index c6e68a392bd..90a287f2bf7 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/simple_order_contents_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/simple_order_contents_spec.rb @@ -86,11 +86,6 @@ let(:promotion) { create(:friendly_promotion, apply_automatically: true) } let(:calculator) { SolidusFriendlyPromotions::Calculators::FlatRate.new(preferred_amount: 10) } - before do - Spree::Config.promotion_adjuster_class = "SolidusFriendlyPromotions::OrderPromotionAdjuster" - Spree::Config.promotion_chooser_class = "SolidusFriendlyPromotions::PromotionAdjustmentChooser" - end - context "one active line item promotion" do let!(:action) { SolidusFriendlyPromotions::Actions::AdjustLineItem.create(promotion: promotion, calculator: calculator) } diff --git a/friendly_promotions/spec/spec_helper.rb b/friendly_promotions/spec/spec_helper.rb index 94f54247705..fa15aea38ff 100644 --- a/friendly_promotions/spec/spec_helper.rb +++ b/friendly_promotions/spec/spec_helper.rb @@ -34,10 +34,6 @@ end config.include SolidusFriendlyPromotions::Engine.routes.url_helpers, type: :request - - config.before do - Spree::Config.promotion_chooser_class = "SolidusFriendlyPromotions::PromotionAdjustmentChooser" - end end Shoulda::Matchers.configure do |config| From fea211f122950a919b44ed884b6694f192b2eea2 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 14 Jul 2023 11:54:49 +0200 Subject: [PATCH 107/524] Implement our own `promotions_per_page` preference If we want to replace Solidus' promotion system, we also have to care about the small things --- .../admin/promotions_controller.rb | 2 +- .../install/templates/initializer.rb | 3 +++ .../lib/solidus_friendly_promotions/configuration.rb | 4 ++++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb index b6584c09bcf..48dc47aae63 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb @@ -38,7 +38,7 @@ def collection @collection = @search.result(distinct: true). includes(promotion_includes). page(params[:page]). - per(params[:per_page] || Spree::Config[:promotions_per_page]) + per(params[:per_page] || SolidusFriendlyPromotions.config.promotions_per_page) @collection end diff --git a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb index f0ea79c260f..120c96dc04e 100644 --- a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb +++ b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb @@ -24,6 +24,9 @@ # that more than one promotion is eligible. config.promotion_chooser_class = "SolidusFriendlyPromotions::PromotionAdjustmentChooser" + # How many promotions should be displayed on the index page in the admin. + config.promotions_per_page = 25 + config.shipment_discount_calculators = [ "SolidusFriendlyPromotions::Calculators::FlatRate", "SolidusFriendlyPromotions::Calculators::FlexiRate", diff --git a/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb b/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb index 78a16c3c344..208064e3f95 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb @@ -17,6 +17,10 @@ class Configuration < Spree::Preferences::Configuration class_name_attribute :promotion_chooser_class, default: "SolidusFriendlyPromotions::PromotionAdjustmentChooser" class_name_attribute :promotion_code_batch_mailer_class, default: "SolidusFriendlyPromotions::PromotionCodeBatchMailer" + + # @!attribute [rw] promotions_per_page + # @return [Integer] Promotions to show per-page in the admin (default: +25+) + preference :promotions_per_page, :integer, default: 25 end class << self From 90c3b569984a739ab47c9520da012f24c672ec43 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 14 Jul 2023 11:55:20 +0200 Subject: [PATCH 108/524] Test class under test --- .../solidus_friendly_promotions/calculators/flexi_rate_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/flexi_rate_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/flexi_rate_spec.rb index e1c2056b27d..a3df09a76eb 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/flexi_rate_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/flexi_rate_spec.rb @@ -152,7 +152,7 @@ it "should allow creation of new object with all the attributes" do attributes = { preferred_first_item: 1, preferred_additional_item: 1, preferred_max_items: 1 } - calculator = Spree::Calculator::FlexiRate.new(attributes) + calculator = described_class.new(attributes) expect(calculator).to have_attributes(attributes) end end From 36f1d7423c0f0a0e4460ce0c1ef37106d22d8133 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 14 Jul 2023 12:08:37 +0200 Subject: [PATCH 109/524] Remove CodeCov badge --- friendly_promotions/README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/friendly_promotions/README.md b/friendly_promotions/README.md index b9dbab8a5b0..bdf3fbd8b10 100644 --- a/friendly_promotions/README.md +++ b/friendly_promotions/README.md @@ -1,7 +1,6 @@ # Solidus Friendly Promotions -[![CircleCI](https://circleci.com/gh/solidusio-contrib/solidus_friendly_promotions.svg?style=shield)](https://circleci.com/gh/solidusio-contrib/solidus_friendly_promotions) -[![codecov](https://codecov.io/gh/solidusio-contrib/solidus_friendly_promotions/branch/master/graph/badge.svg)](https://codecov.io/gh/solidusio-contrib/solidus_friendly_promotions) +[![CircleCI](https://circleci.com/gh/friendlycart/solidus_friendly_promotions.svg?style=shield)](https://circleci.com/gh/friendlycart/solidus_friendly_promotions) This extension replaces Solidus core's promotion system. It is intended as both a research project and a working alternative to how promotions work in core. From cc99602fbf1946f0b431571ea335558d6298fe7e Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Mon, 17 Jul 2023 10:47:42 +0200 Subject: [PATCH 110/524] Fix Ruby version in CI --- friendly_promotions/.circleci/config.yml | 55 ++++++++++-------------- 1 file changed, 22 insertions(+), 33 deletions(-) diff --git a/friendly_promotions/.circleci/config.yml b/friendly_promotions/.circleci/config.yml index 28d70ebd500..ec33318050e 100644 --- a/friendly_promotions/.circleci/config.yml +++ b/friendly_promotions/.circleci/config.yml @@ -1,53 +1,42 @@ -version: 2.1 - +version: '2.1' orbs: - # Required for feature specs. browser-tools: circleci/browser-tools@1.1 - - # Always take the latest version of the orb, this allows us to - # run specs against Solidus supported versions only without the need - # to change this configuration every time a Solidus version is released - # or goes EOL. solidusio_extensions: solidusio/extensions@volatile - jobs: - run-specs-with-sqlite: - executor: solidusio_extensions/sqlite + run-specs-with-mysql: + executor: + name: solidusio_extensions/mysql + ruby_version: '3.1' steps: - - browser-tools/install-chrome + - browser-tools/install-browser-tools - solidusio_extensions/run-tests run-specs-with-postgres: - executor: solidusio_extensions/postgres + executor: + name: solidusio_extensions/postgres + ruby_version: '3.2' steps: - - browser-tools/install-chrome + - browser-tools/install-browser-tools - solidusio_extensions/run-tests - run-specs-with-mysql: - executor: solidusio_extensions/mysql + run-specs-with-sqlite: + executor: + name: solidusio_extensions/sqlite + ruby_version: '3.0' steps: - - browser-tools/install-chrome + - browser-tools/install-browser-tools - solidusio_extensions/run-tests - lint-code: - executor: solidusio_extensions/sqlite-memory - steps: - - solidusio_extensions/lint-code - workflows: - "Run specs on supported Solidus versions": + Run specs on supported Solidus versions: jobs: - - run-specs-with-sqlite - run-specs-with-postgres - run-specs-with-mysql - - lint-code - - "Weekly run specs against master": + - run-specs-with-sqlite + Weekly run specs against main: + jobs: + - run-specs-with-sqlite triggers: - schedule: - cron: "0 0 * * 4" # every Thursday + cron: 0 0 * * 4 filters: branches: only: - - master - jobs: - - run-specs-with-sqlite - - run-specs-with-postgres - - run-specs-with-mysql + - main From f3d1aad7dbb96fda377bad9a2754e8360e7fe606 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 14 Jul 2023 12:28:05 +0200 Subject: [PATCH 111/524] Add OrderDiscounts / ItemDiscount class This set of classes is modelled after the OrderTaxation / ItemTax classes in Solidus, and shall facilitate discounts from a variety of sources, such as the promotion system in this gem or a third-party promotion system. --- .../item_discount.rb | 17 +++++++++++++++++ .../order_discounts.rb | 18 ++++++++++++++++++ .../item_discount_spec.rb | 11 +++++++++++ .../order_discounts_spec.rb | 7 +++++++ 4 files changed, 53 insertions(+) create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/item_discount.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/order_discounts.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/item_discount_spec.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/order_discounts_spec.rb diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/item_discount.rb b/friendly_promotions/app/models/solidus_friendly_promotions/item_discount.rb new file mode 100644 index 00000000000..e72f48de719 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/item_discount.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + # Simple object used to hold discount data for an item. + # + # This generic object will hold the amount of discount that should be applied to + # an item. (Either a {Spree::LineItem} or a {Spree::Shipment}.) + # + # @attr_reader [Integer] item_id the {Spree::LineItem} or {Spree::Shipment} ID. + # @attr_reader [String] label information about the discount + # @attr_reader [ApplicationRecord] source will be used as the source for adjustments + # @attr_reader [BigDecimal] amount the amount of discount applied to the item + class ItemDiscount + include ActiveModel::Model + attr_accessor :item_id, :label, :source, :amount + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/order_discounts.rb b/friendly_promotions/app/models/solidus_friendly_promotions/order_discounts.rb new file mode 100644 index 00000000000..83b3174c127 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/order_discounts.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + # Simple object to pass back discount data from a promoter. + # + # Will be used by {SolidusFriendlyPromotions::OrderDiscounter} to create or update promotion + # adjustments on an order. + # + # @attr_reader [Integer] order_id the {Spree::Order} these discounts apply to + # @attr_reader [Array] line_item_discounts an array of + # discount data for order's line items + # @attr_reader [Array] shipment_discounts an array of + # discount data for the order's shipments + class OrderDiscounts + include ActiveModel::Model + attr_accessor :order_id, :line_item_discounts, :shipment_discounts + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/item_discount_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/item_discount_spec.rb new file mode 100644 index 00000000000..c22e1c892c9 --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/item_discount_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require 'spec_helper' + + +RSpec.describe SolidusFriendlyPromotions::ItemDiscount do + it { is_expected.to respond_to(:item_id) } + it { is_expected.to respond_to(:source) } + it { is_expected.to respond_to(:amount) } + it { is_expected.to respond_to(:label) } +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/order_discounts_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/order_discounts_spec.rb new file mode 100644 index 00000000000..2654b2c452d --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/order_discounts_spec.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +RSpec.describe SolidusFriendlyPromotions::OrderDiscounts do + it { is_expected.to respond_to :order_id } + it { is_expected.to respond_to :line_item_discounts } + it { is_expected.to respond_to :shipment_discounts } +end From cd0312103ca3d234d46c0deb2381774826640bd7 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Mon, 17 Jul 2023 10:43:59 +0200 Subject: [PATCH 112/524] Add in-memory discount objects This refactor helps us with accepting discounts from multiple sources, and it adds a layer of indirection before we add adjustments or discounts. --- .../actions/adjust_line_item.rb | 2 +- .../actions/adjust_shipment.rb | 2 +- ...er.rb => friendly_promotion_discounter.rb} | 19 ++--- .../item_discount.rb | 4 + .../line_item_adjuster.rb | 32 -------- .../line_item_discounter.rb | 28 +++++++ .../order_discounter.rb | 77 +++++++++++++++++++ .../promotion_action.rb | 16 ++-- .../promotion_adjustment_chooser.rb | 4 +- ...ent_adjuster.rb => shipment_discounter.rb} | 14 +--- .../install/templates/initializer.rb | 8 +- .../configuration.rb | 3 +- .../configuration_spec.rb | 2 +- .../spec/models/promotion/integration_spec.rb | 4 +- .../friendly_promotion_discounter_spec.rb | 50 ++++++++++++ ...uster_spec.rb => order_discounter_spec.rb} | 2 +- .../promotion_action_spec.rb | 24 +++--- 17 files changed, 211 insertions(+), 80 deletions(-) rename friendly_promotions/app/models/solidus_friendly_promotions/{order_promotion_adjuster.rb => friendly_promotion_discounter.rb} (77%) delete mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/line_item_adjuster.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/line_item_discounter.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb rename friendly_promotions/app/models/solidus_friendly_promotions/{shipment_adjuster.rb => shipment_discounter.rb} (54%) create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/friendly_promotion_discounter_spec.rb rename friendly_promotions/spec/models/solidus_friendly_promotions/{order_promotion_adjuster_spec.rb => order_discounter_spec.rb} (96%) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb index 0870be2522a..394d0b660cd 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb @@ -3,7 +3,7 @@ module SolidusFriendlyPromotions module Actions class AdjustLineItem < PromotionAction - def can_adjust?(object) + def can_discount?(object) object.is_a? Spree::LineItem end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb index e5c8502eca6..0e252f7b05a 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb @@ -3,7 +3,7 @@ module SolidusFriendlyPromotions module Actions class AdjustShipment < PromotionAction - def can_adjust?(object) + def can_discount?(object) object.is_a? Spree::Shipment end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/order_promotion_adjuster.rb b/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb similarity index 77% rename from friendly_promotions/app/models/solidus_friendly_promotions/order_promotion_adjuster.rb rename to friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb index b7a569696dd..9778723d96f 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/order_promotion_adjuster.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module SolidusFriendlyPromotions - class OrderPromotionAdjuster + class FriendlyPromotionDiscounter attr_reader :order, :promotions def initialize(order) @@ -10,22 +10,23 @@ def initialize(order) end def call - adjust_line_items - adjust_shipments - order.promo_total = (order.line_items + order.shipments).sum { |item| item.promo_total } - order + OrderDiscounts.new( + order_id: order.id, + line_item_discounts: adjust_line_items, + shipment_discounts: adjust_shipments + ) end private def adjust_line_items - line_item_adjuster = LineItemAdjuster.new(promotions: promotions) - order.line_items.each { |line_item| line_item_adjuster.call(line_item) } + line_item_adjuster = LineItemDiscounter.new(promotions: promotions) + order.line_items.flat_map { |line_item| line_item_adjuster.call(line_item) } end def adjust_shipments - shipment_adjuster = ShipmentAdjuster.new(promotions: promotions) - order.shipments.each { |shipment| shipment_adjuster.call(shipment) } + shipment_adjuster = ShipmentDiscounter.new(promotions: promotions) + order.shipments.flat_map { |shipment| shipment_adjuster.call(shipment) } end def possible_promotions diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/item_discount.rb b/friendly_promotions/app/models/solidus_friendly_promotions/item_discount.rb index e72f48de719..32f6810cd8d 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/item_discount.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/item_discount.rb @@ -13,5 +13,9 @@ module SolidusFriendlyPromotions class ItemDiscount include ActiveModel::Model attr_accessor :item_id, :label, :source, :amount + + def ==(other) + item_id == other.item_id && label == other.label && source == other.source && amount == other.amount + end end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/line_item_adjuster.rb b/friendly_promotions/app/models/solidus_friendly_promotions/line_item_adjuster.rb deleted file mode 100644 index 0b195f421cd..00000000000 --- a/friendly_promotions/app/models/solidus_friendly_promotions/line_item_adjuster.rb +++ /dev/null @@ -1,32 +0,0 @@ -# frozen_string_literal: true - -module SolidusFriendlyPromotions - class LineItemAdjuster - attr_reader :promotions - - def initialize(promotions:) - @promotions = promotions - end - - def call(line_item) - return unless line_item.variant.product.promotionable? - non_promotion_adjustments = line_item.adjustments.reject(&:friendly_promotion?) - - eligible_promotions = PromotionEligibility.new(promotable: line_item, possible_promotions: promotions).call - - possible_adjustments = eligible_promotions.flat_map do |promotion| - promotion.actions.select do |action| - action.can_adjust?(line_item) - end.map do |action| - action.adjust(line_item) - end - end - - chosen_adjustments = SolidusFriendlyPromotions.config.promotion_chooser_class.new(line_item).call(possible_adjustments) - - line_item.promo_total = chosen_adjustments.sum(&:amount) - line_item.adjustments = non_promotion_adjustments + chosen_adjustments - line_item - end - end -end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/line_item_discounter.rb b/friendly_promotions/app/models/solidus_friendly_promotions/line_item_discounter.rb new file mode 100644 index 00000000000..4edb6a3a0ef --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/line_item_discounter.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + class LineItemDiscounter + attr_reader :promotions + + def initialize(promotions:) + @promotions = promotions + end + + def call(line_item) + return [] unless line_item.variant.product.promotionable? + + eligible_promotions = PromotionEligibility.new( + promotable: line_item, + possible_promotions: promotions + ).call + + possible_adjustments = eligible_promotions.flat_map do |promotion| + promotion.actions.select do |action| + action.can_discount?(line_item) + end.map do |action| + action.discount(line_item) + end + end + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb b/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb new file mode 100644 index 00000000000..1a807d3f000 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + class OrderDiscounter + def initialize(order) + @order = order + end + + def call + all_order_discounts = SolidusFriendlyPromotions.config.discounters.map do |discounter| + discounter.new(order).call + end + + @order.line_items.each do |item| + all_line_item_discounts = all_order_discounts.flat_map(&:line_item_discounts) + item_discounts = all_line_item_discounts.select { |element| element.item_id == item.id } + chosen_item_discounts = SolidusFriendlyPromotions.config.discount_chooser_class.new(item).call(item_discounts) + update_adjustments(item, chosen_item_discounts) + end + + @order.shipments.each do |item| + all_shipment_discounts = all_order_discounts.flat_map(&:shipment_discounts) + item_discounts = all_shipment_discounts.select { |element| element.item_id == item.id } + chosen_item_discounts = SolidusFriendlyPromotions.config.discount_chooser_class.new(item).call(item_discounts) + update_adjustments(item, chosen_item_discounts) + end + + @order.promo_total = (order.line_items + order.shipments).sum { |item| item.promo_total } + @order + end + + + private + attr_reader :order + + # Walk through the discounts for an item and update adjustments for it. Once + # all of the discounts have been added as adjustments, remove any old tax + # adjustments that weren't touched. + # + # @private + # @param [#adjustments] item a {Spree::LineItem} or {Spree::Shipment} + # @param [Array] taxed_items a list of calculated discounts for an item + # @return [void] + def update_adjustments(item, taxed_items) + promotion_adjustments = item.adjustments.select(&:friendly_promotion?) + + active_adjustments = taxed_items.map do |tax_item| + update_adjustment(item, tax_item) + end + item.update(promo_total: active_adjustments.sum(&:amount)) + # Remove any tax adjustments tied to rates which no longer match. + unmatched_adjustments = promotion_adjustments - active_adjustments + item.adjustments.destroy(unmatched_adjustments) + end + + # Update or create a new tax adjustment on an item. + # + # @private + # @param [#adjustments] item a {Spree::LineItem} or {Spree::Shipment} + # @param [SolidusFriendlyPromotions::ItemDiscount] tax_item calculated discounts for an item + # @return [Spree::Adjustment] the created or updated tax adjustment + def update_adjustment(item, discount_item) + adjustment = item.adjustments.detect do |adjustment| + adjustment.source == discount_item.source + end + + adjustment ||= item.adjustments.new( + source: discount_item.source, + order_id: item.is_a?(Spree::Order) ? item.id : item.order_id, + label: discount_item.label, + eligible: true + ) + adjustment.update!(amount: discount_item.amount) + adjustment + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb index b5676cf3885..e5203ccaff9 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb @@ -21,17 +21,17 @@ def preload_relations [:calculator] end - def can_adjust?(object) + def can_discount?(object) raise NotImplementedError end - def adjust(adjustable) - adjustment = adjustable.adjustments.detect do |adjustment| - adjustment.source == self - end || adjustable.adjustments.build(source: self, order: adjustable.order) - adjustment.label = adjustment_label(adjustable) - adjustment.amount = compute_amount(adjustable) - adjustment + def discount(adjustable) + ItemDiscount.new( + item_id: adjustable.id, + label: adjustment_label(adjustable), + amount: compute_amount(adjustable), + source: self + ) end # Ensure a negative amount which does not exceed the object's amount diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_adjustment_chooser.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_adjustment_chooser.rb index 4408a1d5f95..8c0d7a2645a 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_adjustment_chooser.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_adjustment_chooser.rb @@ -10,8 +10,8 @@ def initialize(adjustable) def call(adjustments) Array.wrap( - adjustments.select(&:eligible?).min_by do |adjustment| - [adjustment.amount, -adjustment.id.to_i] + adjustments.min_by do |adjustment| + [adjustment.amount, -adjustment.source&.id.to_i] end ) end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/shipment_adjuster.rb b/friendly_promotions/app/models/solidus_friendly_promotions/shipment_discounter.rb similarity index 54% rename from friendly_promotions/app/models/solidus_friendly_promotions/shipment_adjuster.rb rename to friendly_promotions/app/models/solidus_friendly_promotions/shipment_discounter.rb index 73bab73e396..5d9a59c22a2 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/shipment_adjuster.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/shipment_discounter.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module SolidusFriendlyPromotions - class ShipmentAdjuster + class ShipmentDiscounter attr_reader :promotions def initialize(promotions:) @@ -9,25 +9,17 @@ def initialize(promotions:) end def call(shipment) - non_promotion_adjustments = shipment.adjustments.reject(&:promotion?) - eligible_promotions = promotions.select do |promotion| PromotionEligibility.new(promotable: shipment, possible_promotions: promotions).call end possible_adjustments = eligible_promotions.flat_map do |promotion| promotion.actions.select do |action| - action.can_adjust?(shipment) + action.can_discount?(shipment) end.map do |action| - action.adjust(shipment) + action.discount(shipment) end end - - chosen_adjustments = SolidusFriendlyPromotions.config.promotion_chooser_class.new(shipment).call(possible_adjustments) - - shipment.promo_total = chosen_adjustments.sum(&:amount) - shipment.adjustments = non_promotion_adjustments + chosen_adjustments - shipment end end end diff --git a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb index 120c96dc04e..1b77f2b6ea4 100644 --- a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb +++ b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb @@ -2,7 +2,7 @@ # Replace solidus core's order contents and promotion adjuster classes with ours. Spree::Config.order_contents_class = "SolidusFriendlyPromotions::SimpleOrderContents" -Spree::Config.promotion_adjuster_class = "SolidusFriendlyPromotions::OrderPromotionAdjuster" +Spree::Config.promotion_adjuster_class = "SolidusFriendlyPromotions::OrderDiscounter" # Replace the promotions menu from core with ours Spree::Backend::Config.configure do |config| @@ -22,11 +22,15 @@ SolidusFriendlyPromotions.configure do |config| # This class chooses which promotion should apply to a line item in case # that more than one promotion is eligible. - config.promotion_chooser_class = "SolidusFriendlyPromotions::PromotionAdjustmentChooser" + config.discount_chooser_class = "SolidusFriendlyPromotions::PromotionAdjustmentChooser" # How many promotions should be displayed on the index page in the admin. config.promotions_per_page = 25 + config.discounters = [ + "SolidusFriendlyPromotions::FriendlyPromotionDiscounter" + ] + config.shipment_discount_calculators = [ "SolidusFriendlyPromotions::Calculators::FlatRate", "SolidusFriendlyPromotions::Calculators::FlexiRate", diff --git a/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb b/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb index 208064e3f95..a291e6a3b38 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb @@ -14,8 +14,9 @@ class Configuration < Spree::Preferences::Configuration add_class_set :shipment_rules add_class_set :actions + add_class_set :discounters - class_name_attribute :promotion_chooser_class, default: "SolidusFriendlyPromotions::PromotionAdjustmentChooser" + class_name_attribute :discount_chooser_class, default: "SolidusFriendlyPromotions::PromotionAdjustmentChooser" class_name_attribute :promotion_code_batch_mailer_class, default: "SolidusFriendlyPromotions::PromotionCodeBatchMailer" # @!attribute [rw] promotions_per_page diff --git a/friendly_promotions/spec/lib/solidus_friendly_promotions/configuration_spec.rb b/friendly_promotions/spec/lib/solidus_friendly_promotions/configuration_spec.rb index 3a14b9c824c..f62ec665ba3 100644 --- a/friendly_promotions/spec/lib/solidus_friendly_promotions/configuration_spec.rb +++ b/friendly_promotions/spec/lib/solidus_friendly_promotions/configuration_spec.rb @@ -15,7 +15,7 @@ describe ".promotion_chooser_class" do it "is the promotion chooser" do - expect(subject.promotion_chooser_class).to eq(SolidusFriendlyPromotions::PromotionAdjustmentChooser) + expect(subject.discount_chooser_class).to eq(SolidusFriendlyPromotions::PromotionAdjustmentChooser) end end diff --git a/friendly_promotions/spec/models/promotion/integration_spec.rb b/friendly_promotions/spec/models/promotion/integration_spec.rb index f4623fc4878..37f67bbbe92 100644 --- a/friendly_promotions/spec/models/promotion/integration_spec.rb +++ b/friendly_promotions/spec/models/promotion/integration_spec.rb @@ -20,7 +20,7 @@ let(:rule) { SolidusFriendlyPromotions::Rules::Product.new(products: [shirt]) } context "with an line item level action" do - let(:calculator) { Spree::Calculator::PercentOnLineItem.new(preferred_percent: 20) } + let(:calculator) { SolidusFriendlyPromotions::Calculators::Percent.new(preferred_percent: 20) } let(:action) { SolidusFriendlyPromotions::Actions::AdjustLineItem.new(calculator: calculator) } it "creates one line item level adjustment" do @@ -37,7 +37,7 @@ let(:rule) { SolidusFriendlyPromotions::Rules::LineItemProduct.new(products: [shirt]) } context "with an line item level action" do - let(:calculator) { Spree::Calculator::PercentOnLineItem.new(preferred_percent: 20) } + let(:calculator) { SolidusFriendlyPromotions::Calculators::Percent.new(preferred_percent: 20) } let(:action) { SolidusFriendlyPromotions::Actions::AdjustLineItem.new(calculator: calculator) } it "creates one line item level adjustment" do diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/friendly_promotion_discounter_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/friendly_promotion_discounter_spec.rb new file mode 100644 index 00000000000..e429c5c5e18 --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/friendly_promotion_discounter_spec.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe SolidusFriendlyPromotions::FriendlyPromotionDiscounter do + describe "selecting promotions" do + let(:order) { create(:order) } + subject { described_class.new(order) } + + let!(:active_promotion) { create(:friendly_promotion, :with_adjustable_action, apply_automatically: true) } + let!(:inactive_promotion) { create(:friendly_promotion, :with_adjustable_action, expires_at: 2.days.ago, apply_automatically: true) } + let!(:connectable_promotion) { create(:friendly_promotion, :with_adjustable_action) } + let!(:connectable_inactive_promotion) { create(:friendly_promotion, :with_adjustable_action, expires_at: 2.days.ago) } + + context "no promo is connected to the order" do + it "checks only active promotions" do + expect(SolidusFriendlyPromotions::PromotionEligibility).to receive(:new). + with(promotable: order, possible_promotions: [active_promotion]). + and_call_original + subject + end + end + + context "an active promo is connected to the order" do + before do + order.friendly_promotions << connectable_promotion + end + + it "checks active and connected promotions" do + expect(SolidusFriendlyPromotions::PromotionEligibility).to receive(:new). + with(promotable: order, possible_promotions: array_including(active_promotion, connectable_promotion)). + and_call_original + subject + end + end + + context "an inactive promo is connected to the order" do + before do + order.friendly_promotions << connectable_inactive_promotion + end + + it "does not check connected inactive promotions" do + expect(SolidusFriendlyPromotions::PromotionEligibility).to receive(:new). + with(promotable: order, possible_promotions: array_including(active_promotion)). + and_call_original + subject + end + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/order_promotion_adjuster_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/order_discounter_spec.rb similarity index 96% rename from friendly_promotions/spec/models/solidus_friendly_promotions/order_promotion_adjuster_spec.rb rename to friendly_promotions/spec/models/solidus_friendly_promotions/order_discounter_spec.rb index 3b6b948f4f4..dbd6e4f76ac 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/order_promotion_adjuster_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/order_discounter_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe SolidusFriendlyPromotions::OrderPromotionAdjuster, type: :model do +RSpec.describe SolidusFriendlyPromotions::OrderDiscounter, type: :model do let(:line_item) { create(:line_item) } let(:order) { line_item.order } let(:promotion) { create(:friendly_promotion, apply_automatically: true) } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_action_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_action_spec.rb index 3099a5d20d6..2fed8b63a80 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_action_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_action_spec.rb @@ -6,18 +6,18 @@ it { is_expected.to belong_to(:promotion) } it { is_expected.to have_one(:calculator) } - it { is_expected.to respond_to :adjust } - it { is_expected.to respond_to :can_adjust? } + it { is_expected.to respond_to :discount } + it { is_expected.to respond_to :can_discount? } describe "#can_adjust?" do - subject { described_class.new.can_adjust?(double) } + subject { described_class.new.can_discount?(double) } it "raises a NotImplementedError" do expect { subject }.to raise_exception(NotImplementedError) end end - describe "#adjust" do + describe "#discount" do let(:variant) { create(:variant) } let(:order) { create(:order) } let(:adjustable) { Spree::LineItem.new(order: order, variant: variant, price: 10)} @@ -27,11 +27,17 @@ allow(action).to receive(:compute_amount).and_return(-1) end - subject { action.adjust(adjustable) } - - it "adds an adjustment to the adjustable" do - expect { subject }.to change { adjustable.adjustments.length }.by(1) - expect(adjustable.adjustments.first.label).to eq("Promotion (20 Perzent off)") + subject { action.discount(adjustable) } + + it "returs an discount to the adjustable" do + expect(subject).to eq( + SolidusFriendlyPromotions::ItemDiscount.new( + item_id: adjustable.id, + label: "Promotion (20 Perzent off)", + source: action, + amount: -1 + ) + ) end end end From 7f3b1c4050452a3b9d3aeb90b2d513e908fc50f5 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Mon, 17 Jul 2023 11:09:52 +0200 Subject: [PATCH 113/524] Use items instead of IDs Item IDs don't carry type information, and we will want to discount shipping rates, too, at some point. --- .../models/solidus_friendly_promotions/item_discount.rb | 8 ++++---- .../solidus_friendly_promotions/order_discounter.rb | 4 ++-- .../solidus_friendly_promotions/promotion_action.rb | 2 +- .../solidus_friendly_promotions/item_discount_spec.rb | 2 +- .../solidus_friendly_promotions/promotion_action_spec.rb | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/item_discount.rb b/friendly_promotions/app/models/solidus_friendly_promotions/item_discount.rb index 32f6810cd8d..f476fcde27a 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/item_discount.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/item_discount.rb @@ -4,18 +4,18 @@ module SolidusFriendlyPromotions # Simple object used to hold discount data for an item. # # This generic object will hold the amount of discount that should be applied to - # an item. (Either a {Spree::LineItem} or a {Spree::Shipment}.) + # an item. # - # @attr_reader [Integer] item_id the {Spree::LineItem} or {Spree::Shipment} ID. + # @attr_reader [Spree::LineItem,Spree::Shipment] the item to be discounted. # @attr_reader [String] label information about the discount # @attr_reader [ApplicationRecord] source will be used as the source for adjustments # @attr_reader [BigDecimal] amount the amount of discount applied to the item class ItemDiscount include ActiveModel::Model - attr_accessor :item_id, :label, :source, :amount + attr_accessor :item, :label, :source, :amount def ==(other) - item_id == other.item_id && label == other.label && source == other.source && amount == other.amount + item == other.item && label == other.label && source == other.source && amount == other.amount end end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb b/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb index 1a807d3f000..d08751ebfb8 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb @@ -13,14 +13,14 @@ def call @order.line_items.each do |item| all_line_item_discounts = all_order_discounts.flat_map(&:line_item_discounts) - item_discounts = all_line_item_discounts.select { |element| element.item_id == item.id } + item_discounts = all_line_item_discounts.select { |element| element.item == item } chosen_item_discounts = SolidusFriendlyPromotions.config.discount_chooser_class.new(item).call(item_discounts) update_adjustments(item, chosen_item_discounts) end @order.shipments.each do |item| all_shipment_discounts = all_order_discounts.flat_map(&:shipment_discounts) - item_discounts = all_shipment_discounts.select { |element| element.item_id == item.id } + item_discounts = all_shipment_discounts.select { |element| element.item == item } chosen_item_discounts = SolidusFriendlyPromotions.config.discount_chooser_class.new(item).call(item_discounts) update_adjustments(item, chosen_item_discounts) end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb index e5203ccaff9..17be3ed46fe 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb @@ -27,7 +27,7 @@ def can_discount?(object) def discount(adjustable) ItemDiscount.new( - item_id: adjustable.id, + item: adjustable, label: adjustment_label(adjustable), amount: compute_amount(adjustable), source: self diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/item_discount_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/item_discount_spec.rb index c22e1c892c9..0fc1feacc31 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/item_discount_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/item_discount_spec.rb @@ -4,7 +4,7 @@ RSpec.describe SolidusFriendlyPromotions::ItemDiscount do - it { is_expected.to respond_to(:item_id) } + it { is_expected.to respond_to(:item) } it { is_expected.to respond_to(:source) } it { is_expected.to respond_to(:amount) } it { is_expected.to respond_to(:label) } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_action_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_action_spec.rb index 2fed8b63a80..757145a5050 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_action_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_action_spec.rb @@ -32,7 +32,7 @@ it "returs an discount to the adjustable" do expect(subject).to eq( SolidusFriendlyPromotions::ItemDiscount.new( - item_id: adjustable.id, + item: adjustable, label: "Promotion (20 Perzent off)", source: action, amount: -1 From 1c015a8b2ae0450a9cac7a906402b863fa3a41d1 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Mon, 17 Jul 2023 11:40:41 +0200 Subject: [PATCH 114/524] Fix circleCI --- friendly_promotions/.circleci/config.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/friendly_promotions/.circleci/config.yml b/friendly_promotions/.circleci/config.yml index ec33318050e..11d33880218 100644 --- a/friendly_promotions/.circleci/config.yml +++ b/friendly_promotions/.circleci/config.yml @@ -9,21 +9,24 @@ jobs: ruby_version: '3.1' steps: - browser-tools/install-browser-tools - - solidusio_extensions/run-tests + - solidusio_extensions/run-tests: + working_directory: '~/project/solidus_friendly_promotions' run-specs-with-postgres: executor: name: solidusio_extensions/postgres ruby_version: '3.2' steps: - browser-tools/install-browser-tools - - solidusio_extensions/run-tests + - solidusio_extensions/run-tests: + working_directory: '~/project/solidus_friendly_promotions' run-specs-with-sqlite: executor: name: solidusio_extensions/sqlite ruby_version: '3.0' steps: - browser-tools/install-browser-tools - - solidusio_extensions/run-tests + - solidusio_extensions/run-tests: + working_directory: '~/project/solidus_friendly_promotions' workflows: Run specs on supported Solidus versions: jobs: From b9e1bd5163a94cc0650e7da42d2a1e7a38633f62 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 25 Jul 2023 10:14:52 +0200 Subject: [PATCH 115/524] Add Shipping Rate discount model and relationships This is useful for allowing customers to know what discounts will apply if they choose a particular shipping rate. The model is similar to the `Spree::ShippingRateTax` model that previews taxes on shipping rates. --- .../shipping_rate_decorator.rb | 26 +++++++++++ .../shipping_rate_discount.rb | 11 +++++ ...25074235_create_shipping_rate_discounts.rb | 10 ++++ .../testing_support/factories.rb | 1 + ...friendly_shipping_rate_discount_factory.rb | 10 ++++ .../shipping_rate_discount_spec.rb | 13 ++++++ .../spec/models/spree/shipping_rate_spec.rb | 46 +++++++++++++++++++ 7 files changed, 117 insertions(+) create mode 100644 friendly_promotions/app/decorators/models/solidus_friendly_promotions/shipping_rate_decorator.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/shipping_rate_discount.rb create mode 100644 friendly_promotions/db/migrate/20230725074235_create_shipping_rate_discounts.rb create mode 100644 friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_shipping_rate_discount_factory.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/shipping_rate_discount_spec.rb create mode 100644 friendly_promotions/spec/models/spree/shipping_rate_spec.rb diff --git a/friendly_promotions/app/decorators/models/solidus_friendly_promotions/shipping_rate_decorator.rb b/friendly_promotions/app/decorators/models/solidus_friendly_promotions/shipping_rate_decorator.rb new file mode 100644 index 00000000000..635bbf151e3 --- /dev/null +++ b/friendly_promotions/app/decorators/models/solidus_friendly_promotions/shipping_rate_decorator.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions::ShippingRateDecorator + def self.prepended(base) + base.class_eval do + has_many :discounts, + class_name: "SolidusFriendlyPromotions::ShippingRateDiscount", + foreign_key: :shipping_rate_id, + dependent: :destroy, + inverse_of: :shipping_rate, + autosave: true + + money_methods :total_before_tax, :promo_total + end + end + + def total_before_tax + amount + promo_total + end + + def promo_total + discounts.sum(&:amount) + end +end + +Spree::ShippingRate.prepend(SolidusFriendlyPromotions::ShippingRateDecorator) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/shipping_rate_discount.rb b/friendly_promotions/app/models/solidus_friendly_promotions/shipping_rate_discount.rb new file mode 100644 index 00000000000..2de000f7856 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/shipping_rate_discount.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + class ShippingRateDiscount < Spree::Base + belongs_to :shipping_rate, inverse_of: :discounts, class_name: "Spree::ShippingRate" + belongs_to :promotion_action, -> { with_discarded }, inverse_of: false + + extend Spree::DisplayMoney + money_methods :amount + end +end diff --git a/friendly_promotions/db/migrate/20230725074235_create_shipping_rate_discounts.rb b/friendly_promotions/db/migrate/20230725074235_create_shipping_rate_discounts.rb new file mode 100644 index 00000000000..bcbe7beb59a --- /dev/null +++ b/friendly_promotions/db/migrate/20230725074235_create_shipping_rate_discounts.rb @@ -0,0 +1,10 @@ +class CreateShippingRateDiscounts < ActiveRecord::Migration[7.0] + def change + create_table :friendly_shipping_rate_discounts do |t| + t.references :promotion_action, null: false, foreign_key: { to_table: :friendly_promotion_actions } + t.references :shipping_rate, null: false, foreign_key: { to_table: :spree_shipping_rates} + t.decimal :amount, precision: 10, scale: 2, null: false + t.string :label, null: false + end + end +end diff --git a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/factories.rb b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/factories.rb index cf315d7e46d..ced5cd0d102 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/factories.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/factories.rb @@ -10,6 +10,7 @@ def self.table_name_prefix require "solidus_friendly_promotions/testing_support/friendly_promotion_factory" require "solidus_friendly_promotions/testing_support/friendly_order_promotion_factory" require "solidus_friendly_promotions/testing_support/friendly_order_factory" +require "solidus_friendly_promotions/testing_support/friendly_shipping_rate_discount_factory" FactoryBot.define do end diff --git a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_shipping_rate_discount_factory.rb b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_shipping_rate_discount_factory.rb new file mode 100644 index 00000000000..1fbe1b7e46b --- /dev/null +++ b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_shipping_rate_discount_factory.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :friendly_shipping_rate_discount, class: 'SolidusFriendlyPromotions::ShippingRateDiscount' do + amount { BigDecimal("-4.00") } + shipping_rate + promotion_action { SolidusFriendlyPromotions::Actions::AdjustShipment.new } + label { "10% off" } + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/shipping_rate_discount_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/shipping_rate_discount_spec.rb new file mode 100644 index 00000000000..3cd42af13e5 --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/shipping_rate_discount_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe SolidusFriendlyPromotions::ShippingRateDiscount do + subject(:shipping_rate_discount) { build(:friendly_shipping_rate_discount) } + + it { is_expected.to respond_to(:shipping_rate) } + it { is_expected.to respond_to(:promotion_action) } + it { is_expected.to respond_to(:amount) } + it { is_expected.to respond_to(:display_amount) } + it { is_expected.to respond_to(:label) } +end diff --git a/friendly_promotions/spec/models/spree/shipping_rate_spec.rb b/friendly_promotions/spec/models/spree/shipping_rate_spec.rb new file mode 100644 index 00000000000..be172d70c29 --- /dev/null +++ b/friendly_promotions/spec/models/spree/shipping_rate_spec.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe Spree::ShippingRate do + let(:subject) { build(:shipping_rate) } + + describe '#display_price' do + before(:each) { subject.amount = 5 } + + it 'returns formatted amount' do + expect(subject.display_price).to eq('$5.00') + end + end + + describe "#total_before_tax" do + let(:shipping_rate) { build(:shipping_rate, cost: 4) } + + subject { shipping_rate.total_before_tax } + + it { is_expected.to eq(4) } + + context "with discounts" do + let(:shipping_rate) { build(:shipping_rate, cost: 4, discounts: discounts) } + let(:discounts) { build_list(:friendly_shipping_rate_discount, 2, amount: -1.5, label: "DISCOUNT") } + + it { is_expected.to eq(1) } + end + end + + describe "#display_total_before_tax" do + let(:shipping_rate) { build(:shipping_rate, cost: 10) } + + subject { shipping_rate.display_total_before_tax } + + it { is_expected.to eq(Spree::Money.new("10.00")) } + end + + describe "#display_promo_total" do + let(:shipping_rate) { build(:shipping_rate) } + + subject { shipping_rate.display_promo_total } + + it { is_expected.to eq(Spree::Money.new("0")) } + end +end From a469442fa7e91ddfb080bf501040b3378ba50837 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 25 Jul 2023 10:23:41 +0200 Subject: [PATCH 116/524] Allow Shipment discounter to also discount rates Because we've abstracted away the type of adjustment, the `AdjustShipment` action can now also discount shipping rates. --- .../actions/adjust_shipment.rb | 2 +- .../actions/adjust_shipment_spec.rb | 22 +++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb index 0e252f7b05a..82d73533943 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb @@ -4,7 +4,7 @@ module SolidusFriendlyPromotions module Actions class AdjustShipment < PromotionAction def can_discount?(object) - object.is_a? Spree::Shipment + object.is_a?(Spree::Shipment) || object.is_a?(Spree::ShippingRate) end def available_calculators diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_shipment_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_shipment_spec.rb index 5495a3eb512..fdd07df85e0 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_shipment_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_shipment_spec.rb @@ -10,4 +10,26 @@ it { is_expected.to eq("Discount matching shipments") } end + + describe "#can_discount?" do + subject { action.can_discount?(promotable) } + + context "with a line item" do + let(:promotable) { Spree::LineItem.new } + + it { is_expected.to be false } + end + + context "with a shipment" do + let(:promotable) { Spree::Shipment.new } + + it { is_expected.to be true } + end + + context "with a shipping rate" do + let(:promotable) { Spree::ShippingRate.new } + + it { is_expected.to be true } + end + end end From 78823cd5d92797b9b17de30875b250772eebfc77 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 25 Jul 2023 11:52:01 +0200 Subject: [PATCH 117/524] Add Shipping method rule The first rule that applies to shipments and shipping rates. --- .../rules/shipping_method.rb | 21 ++++++ .../rules/_shipping_method.html.erb | 7 ++ .../install/templates/initializer.rb | 4 +- .../rules/shipping_method_spec.rb | 67 +++++++++++++++++++ 4 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/rules/shipping_method.rb create mode 100644 friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_shipping_method.html.erb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/rules/shipping_method_spec.rb diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/shipping_method.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/shipping_method.rb new file mode 100644 index 00000000000..46b118f9002 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/shipping_method.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module Rules + class ShippingMethod < PromotionRule + preference :shipping_method_ids, type: :array, default: [] + + def applicable?(promotable) + promotable.is_a?(Spree::Shipment) || promotable.is_a?(Spree::ShippingRate) + end + + def eligible?(promotable) + promotable.shipping_method&.id.in?(preferred_shipping_method_ids.map(&:to_i)) + end + + def updateable? + true + end + end + end +end diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_shipping_method.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_shipping_method.html.erb new file mode 100644 index 00000000000..fc5954ff32e --- /dev/null +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_shipping_method.html.erb @@ -0,0 +1,7 @@ +
    + <%= label_tag "#{param_prefix}_preferred_shipping_method_ids", SolidusFriendlyShipping::Rules::ShippingMethod.human_attribute_name(:preferred_shipping_method_ids) %> + <%= select_tag "#{param_prefix}[preferred_shipping_method_ids]", + options_from_collection_for_select( + Spree::ShippingMethod.all, :id, :name, promotion_rule.preferred_shipping_method_ids + ), class: 'select2 fullwidth', multiple: true %> +
    diff --git a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb index 1b77f2b6ea4..0dca999a063 100644 --- a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb +++ b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb @@ -66,7 +66,9 @@ "SolidusFriendlyPromotions::Rules::LineItemProduct", "SolidusFriendlyPromotions::Rules::LineItemTaxon", ] - config.shipment_rules = [] + config.shipment_rules = [ + "SolidusFriendlyPromotions::Rules::ShippingMethod", + ] config.actions = [ "SolidusFriendlyPromotions::Actions::AdjustLineItem", diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/shipping_method_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/shipping_method_spec.rb new file mode 100644 index 00000000000..1ee9f66b5ed --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/shipping_method_spec.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe SolidusFriendlyPromotions::Rules::ShippingMethod, type: :model do + it { is_expected.to respond_to(:preferred_shipping_method_ids) } + + let(:rule) { described_class.new } + + describe "preferred_shipping_methods_ids=" do + let!(:promotion) { create(:friendly_promotion) } + let(:ups_ground) { create(:shipping_method) } + let(:dhl_saver) { create(:shipping_method) } + let(:rule) { promotion.rules.build(type: described_class.to_s) } + + subject { rule.preferred_shipping_method_ids = [ups_ground.id] } + + it "creates a valid rule with a shipping method" do + subject + expect(rule).to be_valid + expect(rule.preferred_shipping_method_ids).to include(ups_ground.id) + end + end + + describe "#eligible?" do + let!(:promotion) { create(:friendly_promotion) } + let(:ups_ground) { create(:shipping_method) } + let(:dhl_saver) { create(:shipping_method) } + let(:rule) { promotion.rules.build(type: described_class.to_s, preferred_shipping_method_ids: [ups_ground.id]) } + + subject { rule.eligible?(promotable) } + + context "with a shipment" do + context "when the shipment has the right shipping method selected" do + let(:promotable) { create(:shipment, shipping_method: ups_ground) } + + it { is_expected.to be true } + end + + context "when the shipment does not have the right shipping method selected" do + let(:promotable) { create(:shipment, shipping_method: dhl_saver) } + + it { is_expected.to be false } + end + + context "when the shipment has no shipping method selected" do + let(:promotable) { create(:shipment, shipping_method: nil) } + + it { is_expected.to be false } + end + end + + context "with a shipping rate" do + context "when the shipping rate has the right shipping method selected" do + let(:promotable) { create(:shipping_rate, shipping_method: ups_ground) } + + it { is_expected.to be true } + end + + context "when the shipping rate does not have the right shipping method selected" do + let(:promotable) { create(:shipping_rate, shipping_method: dhl_saver) } + + it { is_expected.to be false } + end + end + end +end From 87b7e702723a214cc2ce0687a9ec0bbe16ff276c Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 25 Jul 2023 11:52:14 +0200 Subject: [PATCH 118/524] Fix Shipment Discounter Oops, an empty array is truthy. The select already happens in `PromotionEligibility`. --- .../solidus_friendly_promotions/shipment_discounter.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/shipment_discounter.rb b/friendly_promotions/app/models/solidus_friendly_promotions/shipment_discounter.rb index 5d9a59c22a2..01e3fbc7df9 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/shipment_discounter.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/shipment_discounter.rb @@ -9,9 +9,10 @@ def initialize(promotions:) end def call(shipment) - eligible_promotions = promotions.select do |promotion| - PromotionEligibility.new(promotable: shipment, possible_promotions: promotions).call - end + eligible_promotions = PromotionEligibility.new( + promotable: shipment, + possible_promotions: promotions + ).call possible_adjustments = eligible_promotions.flat_map do |promotion| promotion.actions.select do |action| From af7bd26f276bd21b2f8a686c04362c529c67e104 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 25 Jul 2023 11:53:45 +0200 Subject: [PATCH 119/524] Add shipment integration spec --- .../spec/models/promotion/integration_spec.rb | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/friendly_promotions/spec/models/promotion/integration_spec.rb b/friendly_promotions/spec/models/promotion/integration_spec.rb index 37f67bbbe92..58763a17d20 100644 --- a/friendly_promotions/spec/models/promotion/integration_spec.rb +++ b/friendly_promotions/spec/models/promotion/integration_spec.rb @@ -50,4 +50,63 @@ end end end + + context "with a shipment-level rule" do + let!(:ups_ground) { create(:shipping_method) } + let!(:dhl_saver) { create(:shipping_method) } + let(:promotion) { create(:friendly_promotion, name: "20 percent off UPS Ground", apply_automatically: true) } + let(:rule) { SolidusFriendlyPromotions::Rules::ShippingMethod.new(preferred_shipping_method_ids: [ups_ground.id]) } + let(:order) { create(:order_with_line_items, shipping_method: shipping_method) } + + before do + promotion.rules << rule + promotion.actions << action + end + + context "with a line item level action" do + let(:calculator) { SolidusFriendlyPromotions::Calculators::Percent.new(preferred_percent: 20) } + let(:action) { SolidusFriendlyPromotions::Actions::AdjustLineItem.new(calculator: calculator) } + let(:shipping_method) { ups_ground } + + it "creates adjustments" do + expect(order.adjustments).to be_empty + expect(order.total).to eq(139.98) + expect(order.item_total).to eq(49.98) + expect(order.item_total_before_tax).to eq(39.98) + expect(order.promo_total).to eq(-10) + expect(order.line_items.flat_map(&:adjustments).length).to eq(3) + end + end + + context "with a shipment level action" do + let(:calculator) { SolidusFriendlyPromotions::Calculators::Percent.new(preferred_percent: 20) } + let(:action) { SolidusFriendlyPromotions::Actions::AdjustShipment.new(calculator: calculator) } + + context "when the order is eligible" do + let(:shipping_method) { ups_ground } + it "creates adjustments" do + expect(order.adjustments).to be_empty + expect(order.total).to eq(90) + expect(order.item_total).to eq(10) + expect(order.item_total_before_tax).to eq(10) + expect(order.line_items.flat_map(&:adjustments)).to be_empty + expect(order.shipments.flat_map(&:adjustments)).not_to be_empty + end + end + + context "when the order is not eligible" do + let(:shipping_method) { dhl_saver } + + it "creates no adjustments" do + expect(order.adjustments).to be_empty + expect(order.total).to eq(110) + expect(order.item_total).to eq(10) + expect(order.item_total_before_tax).to eq(10) + expect(order.promo_total).to eq(0) + expect(order.line_items.flat_map(&:adjustments)).to be_empty + expect(order.shipments.flat_map(&:adjustments)).to be_empty + end + end + end + end end From 09561dd735ff802943caa978b55d1630949efac7 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 25 Jul 2023 11:58:21 +0200 Subject: [PATCH 120/524] Remove useless variable --- .../models/solidus_friendly_promotions/line_item_discounter.rb | 2 +- .../models/solidus_friendly_promotions/shipment_discounter.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/line_item_discounter.rb b/friendly_promotions/app/models/solidus_friendly_promotions/line_item_discounter.rb index 4edb6a3a0ef..1436104466e 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/line_item_discounter.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/line_item_discounter.rb @@ -16,7 +16,7 @@ def call(line_item) possible_promotions: promotions ).call - possible_adjustments = eligible_promotions.flat_map do |promotion| + eligible_promotions.flat_map do |promotion| promotion.actions.select do |action| action.can_discount?(line_item) end.map do |action| diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/shipment_discounter.rb b/friendly_promotions/app/models/solidus_friendly_promotions/shipment_discounter.rb index 01e3fbc7df9..d5e5fbf29cb 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/shipment_discounter.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/shipment_discounter.rb @@ -14,7 +14,7 @@ def call(shipment) possible_promotions: promotions ).call - possible_adjustments = eligible_promotions.flat_map do |promotion| + eligible_promotions.flat_map do |promotion| promotion.actions.select do |action| action.can_discount?(shipment) end.map do |action| From 6c859b6598c6a7d874ad7e5ad1839a679e91c247 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 25 Jul 2023 12:07:24 +0200 Subject: [PATCH 121/524] Refactor: Only use a single item discounter --- .../friendly_promotion_discounter.rb | 11 ++++---- ..._item_discounter.rb => item_discounter.rb} | 4 +-- .../shipment_discounter.rb | 26 ------------------- 3 files changed, 7 insertions(+), 34 deletions(-) rename friendly_promotions/app/models/solidus_friendly_promotions/{line_item_discounter.rb => item_discounter.rb} (86%) delete mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/shipment_discounter.rb diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb b/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb index 9778723d96f..70ed1c316cd 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb @@ -2,11 +2,12 @@ module SolidusFriendlyPromotions class FriendlyPromotionDiscounter - attr_reader :order, :promotions + attr_reader :order, :promotions, :item_discounter def initialize(order) @order = order @promotions = PromotionEligibility.new(promotable: order, possible_promotions: possible_promotions).call + @item_discounter = ItemDiscounter.new(promotions: promotions) end def call @@ -20,13 +21,13 @@ def call private def adjust_line_items - line_item_adjuster = LineItemDiscounter.new(promotions: promotions) - order.line_items.flat_map { |line_item| line_item_adjuster.call(line_item) } + order.line_items.select do |line_item| + line_item.variant.product.promotionable? + end.flat_map { |line_item| item_discounter.call(line_item) } end def adjust_shipments - shipment_adjuster = ShipmentDiscounter.new(promotions: promotions) - order.shipments.flat_map { |shipment| shipment_adjuster.call(shipment) } + order.shipments.flat_map { |shipment| item_discounter.call(shipment) } end def possible_promotions diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/line_item_discounter.rb b/friendly_promotions/app/models/solidus_friendly_promotions/item_discounter.rb similarity index 86% rename from friendly_promotions/app/models/solidus_friendly_promotions/line_item_discounter.rb rename to friendly_promotions/app/models/solidus_friendly_promotions/item_discounter.rb index 1436104466e..296fcc9eea5 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/line_item_discounter.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/item_discounter.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module SolidusFriendlyPromotions - class LineItemDiscounter + class ItemDiscounter attr_reader :promotions def initialize(promotions:) @@ -9,8 +9,6 @@ def initialize(promotions:) end def call(line_item) - return [] unless line_item.variant.product.promotionable? - eligible_promotions = PromotionEligibility.new( promotable: line_item, possible_promotions: promotions diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/shipment_discounter.rb b/friendly_promotions/app/models/solidus_friendly_promotions/shipment_discounter.rb deleted file mode 100644 index d5e5fbf29cb..00000000000 --- a/friendly_promotions/app/models/solidus_friendly_promotions/shipment_discounter.rb +++ /dev/null @@ -1,26 +0,0 @@ -# frozen_string_literal: true - -module SolidusFriendlyPromotions - class ShipmentDiscounter - attr_reader :promotions - - def initialize(promotions:) - @promotions = promotions - end - - def call(shipment) - eligible_promotions = PromotionEligibility.new( - promotable: shipment, - possible_promotions: promotions - ).call - - eligible_promotions.flat_map do |promotion| - promotion.actions.select do |action| - action.can_discount?(shipment) - end.map do |action| - action.discount(shipment) - end - end - end - end -end From 09b3165058f293c3fb635df4f2d0bdffc428ed55 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 25 Jul 2023 12:33:09 +0200 Subject: [PATCH 122/524] Fix promotion integration spec The system behaves correctly, the specs were off. --- .../spec/models/promotion/integration_spec.rb | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/friendly_promotions/spec/models/promotion/integration_spec.rb b/friendly_promotions/spec/models/promotion/integration_spec.rb index 58763a17d20..6d76ee7f632 100644 --- a/friendly_promotions/spec/models/promotion/integration_spec.rb +++ b/friendly_promotions/spec/models/promotion/integration_spec.rb @@ -70,11 +70,12 @@ it "creates adjustments" do expect(order.adjustments).to be_empty - expect(order.total).to eq(139.98) - expect(order.item_total).to eq(49.98) - expect(order.item_total_before_tax).to eq(39.98) - expect(order.promo_total).to eq(-10) - expect(order.line_items.flat_map(&:adjustments).length).to eq(3) + expect(order.total).to eq(108.00) + expect(order.item_total).to eq(10) + expect(order.item_total_before_tax).to eq(8) + expect(order.promo_total).to eq(-2) + expect(order.line_items.flat_map(&:adjustments).length).to eq(1) + expect(order.shipments.flat_map(&:adjustments)).to be_empty end end From 41971823759619ee4a3cabe0c3336f19be7ab66f Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 25 Jul 2023 12:33:48 +0200 Subject: [PATCH 123/524] Refactor item discounter it's not line items --- .../models/solidus_friendly_promotions/item_discounter.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/item_discounter.rb b/friendly_promotions/app/models/solidus_friendly_promotions/item_discounter.rb index 296fcc9eea5..e1fd61a16ba 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/item_discounter.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/item_discounter.rb @@ -8,17 +8,17 @@ def initialize(promotions:) @promotions = promotions end - def call(line_item) + def call(item) eligible_promotions = PromotionEligibility.new( - promotable: line_item, + promotable: item, possible_promotions: promotions ).call eligible_promotions.flat_map do |promotion| promotion.actions.select do |action| - action.can_discount?(line_item) + action.can_discount?(item) end.map do |action| - action.discount(line_item) + action.discount(item) end end end From bb2ed6ea57118eaa58787640cb58be03afd8a4b0 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 25 Jul 2023 13:56:08 +0200 Subject: [PATCH 124/524] Add support for shipping rate discounts --- .../friendly_promotion_discounter.rb | 7 ++- .../order_discounter.rb | 9 ++++ .../order_discounts.rb | 2 +- .../spec/models/promotion/integration_spec.rb | 44 +++++++++++++------ .../order_discounts_spec.rb | 1 + 5 files changed, 48 insertions(+), 15 deletions(-) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb b/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb index 70ed1c316cd..061a0be395e 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb @@ -14,7 +14,8 @@ def call OrderDiscounts.new( order_id: order.id, line_item_discounts: adjust_line_items, - shipment_discounts: adjust_shipments + shipment_discounts: adjust_shipments, + shipping_rate_discounts: adjust_shipping_rates ) end @@ -30,6 +31,10 @@ def adjust_shipments order.shipments.flat_map { |shipment| item_discounter.call(shipment) } end + def adjust_shipping_rates + order.shipments.flat_map(&:shipping_rates).flat_map { |rate| item_discounter.call(rate) } + end + def possible_promotions promos = connected_order_promotions | sale_promotions promos.flat_map(&:actions).group_by(&:preload_relations).each do |preload_relations, actions| diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb b/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb index d08751ebfb8..8db13a4855d 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb @@ -25,6 +25,15 @@ def call update_adjustments(item, chosen_item_discounts) end + @order.shipments.flat_map(&:shipping_rates).each do |item| + all_item_discounts = all_order_discounts.flat_map(&:shipping_rate_discounts) + item_discounts = all_item_discounts.select { |element| element.item == item } + chosen_item_discounts = SolidusFriendlyPromotions.config.discount_chooser_class.new(item).call(item_discounts) + item.discounts = chosen_item_discounts.map do |discount| + SolidusFriendlyPromotions::ShippingRateDiscount.new(shipping_rate: item, amount: discount.amount, label: discount.label) + end + end + @order.promo_total = (order.line_items + order.shipments).sum { |item| item.promo_total } @order end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/order_discounts.rb b/friendly_promotions/app/models/solidus_friendly_promotions/order_discounts.rb index 83b3174c127..df340659703 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/order_discounts.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/order_discounts.rb @@ -13,6 +13,6 @@ module SolidusFriendlyPromotions # discount data for the order's shipments class OrderDiscounts include ActiveModel::Model - attr_accessor :order_id, :line_item_discounts, :shipment_discounts + attr_accessor :order_id, :line_item_discounts, :shipment_discounts, :shipping_rate_discounts end end diff --git a/friendly_promotions/spec/models/promotion/integration_spec.rb b/friendly_promotions/spec/models/promotion/integration_spec.rb index 6d76ee7f632..b12c83cb3e8 100644 --- a/friendly_promotions/spec/models/promotion/integration_spec.rb +++ b/friendly_promotions/spec/models/promotion/integration_spec.rb @@ -52,15 +52,29 @@ end context "with a shipment-level rule" do - let!(:ups_ground) { create(:shipping_method) } - let!(:dhl_saver) { create(:shipping_method) } + let!(:address) { create(:address) } + let(:shipping_zone) { create(:global_zone) } + let(:store) { create(:store) } + let!(:ups_ground) { create(:shipping_method, zones: [shipping_zone], cost: 23) } + let!(:dhl_saver) { create(:shipping_method, zones: [shipping_zone], cost: 37) } + let(:variant) { create(:variant, price: 13) } let(:promotion) { create(:friendly_promotion, name: "20 percent off UPS Ground", apply_automatically: true) } let(:rule) { SolidusFriendlyPromotions::Rules::ShippingMethod.new(preferred_shipping_method_ids: [ups_ground.id]) } - let(:order) { create(:order_with_line_items, shipping_method: shipping_method) } + let(:order) { Spree::Order.create!(store: store) } before do promotion.rules << rule promotion.actions << action + + order.contents.add(variant, 1) + order.ship_address = address + order.bill_address = address + + order.create_proposed_shipments + + order.shipments.first.selected_shipping_rate_id = order.shipments.first.shipping_rates.detect { |r| r.shipping_method == shipping_method }.id + + order.recalculate end context "with a line item level action" do @@ -70,12 +84,13 @@ it "creates adjustments" do expect(order.adjustments).to be_empty - expect(order.total).to eq(108.00) - expect(order.item_total).to eq(10) - expect(order.item_total_before_tax).to eq(8) - expect(order.promo_total).to eq(-2) + expect(order.total).to eq(33.40) + expect(order.item_total).to eq(13) + expect(order.item_total_before_tax).to eq(10.40) + expect(order.promo_total).to eq(-2.60) expect(order.line_items.flat_map(&:adjustments).length).to eq(1) expect(order.shipments.flat_map(&:adjustments)).to be_empty + expect(order.shipments.flat_map(&:shipping_rates).flat_map(&:discounts)).to be_empty end end @@ -87,11 +102,13 @@ let(:shipping_method) { ups_ground } it "creates adjustments" do expect(order.adjustments).to be_empty - expect(order.total).to eq(90) - expect(order.item_total).to eq(10) - expect(order.item_total_before_tax).to eq(10) + expect(order.total).to eq(31.40) + expect(order.item_total).to eq(13) + expect(order.item_total_before_tax).to eq(13) + expect(order.promo_total).to eq(-4.6) expect(order.line_items.flat_map(&:adjustments)).to be_empty expect(order.shipments.flat_map(&:adjustments)).not_to be_empty + expect(order.shipments.flat_map(&:shipping_rates).flat_map(&:discounts)).not_to be_empty end end @@ -100,12 +117,13 @@ it "creates no adjustments" do expect(order.adjustments).to be_empty - expect(order.total).to eq(110) - expect(order.item_total).to eq(10) - expect(order.item_total_before_tax).to eq(10) + expect(order.total).to eq(50) + expect(order.item_total).to eq(13) + expect(order.item_total_before_tax).to eq(13) expect(order.promo_total).to eq(0) expect(order.line_items.flat_map(&:adjustments)).to be_empty expect(order.shipments.flat_map(&:adjustments)).to be_empty + expect(order.shipments.flat_map(&:shipping_rates).flat_map(&:discounts)).not_to be_empty end end end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/order_discounts_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/order_discounts_spec.rb index 2654b2c452d..9fe907d0f5a 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/order_discounts_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/order_discounts_spec.rb @@ -4,4 +4,5 @@ it { is_expected.to respond_to :order_id } it { is_expected.to respond_to :line_item_discounts } it { is_expected.to respond_to :shipment_discounts } + it { is_expected.to respond_to :shipping_rate_discounts } end From 7a08a966b63596c722a73e366b88cb868a2f20f5 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 25 Jul 2023 14:50:58 +0200 Subject: [PATCH 125/524] Rubocop -A --- .../admin/promotion_actions_controller.rb | 22 ++-- .../admin/promotion_codes_controller.rb | 16 ++- .../admin/promotion_rules_controller.rb | 23 ++-- .../admin/promotions_controller.rb | 9 +- .../order_decorator.rb | 8 +- .../shipping_rate_decorator.rb | 34 +++--- .../promotion_code_batch_job.rb | 14 +-- .../app/models/solidus_friendly_promotions.rb | 2 + .../calculators/distributed_amount.rb | 2 + .../calculators/flexi_rate.rb | 2 +- .../calculators/tiered_flat_rate.rb | 2 +- .../calculators/tiered_percent.rb | 2 +- .../order_discounter.rb | 10 +- .../solidus_friendly_promotions/promotion.rb | 9 +- .../promotion_action.rb | 3 +- .../promotion_code.rb | 8 +- .../promotion_code/batch_builder.rb | 9 +- .../promotion_code_batch.rb | 12 +-- .../promotion_rule.rb | 6 +- .../rules/first_order.rb | 6 +- .../rules/first_repeat_purchase_since.rb | 2 +- .../rules/item_total.rb | 6 +- .../rules/line_item_product.rb | 2 +- .../rules/line_item_taxon.rb | 4 +- .../rules/nth_order.rb | 2 +- .../rules/one_use_per_user.rb | 6 +- .../rules/product.rb | 5 +- .../rules/taxon.rb | 24 +++-- .../rules/user_logged_in.rb | 2 +- .../rules/user_role.rb | 3 +- .../simple_order_contents.rb | 1 - friendly_promotions/config/importmap.rb | 8 +- .../solidus_friendly_promotions.rb | 2 + ...25074235_create_shipping_rate_discounts.rb | 2 +- .../install/install_generator.rb | 2 +- .../install/templates/initializer.rb | 1 + .../configuration.rb | 3 +- .../testing_support/factories.rb | 2 +- .../testing_support/friendly_order_factory.rb | 1 - .../solidus_friendly_promotions.gemspec | 2 +- .../promotion_code_batch_job_spec.rb | 11 +- .../promotion_code_batch_mailer_spec.rb | 3 +- .../spec/models/promotion/integration_spec.rb | 5 +- .../actions/adjust_line_item_spec.rb | 1 + .../calculators/distributed_amount_spec.rb | 10 +- .../calculators/flat_rate_spec.rb | 12 ++- .../calculators/flexi_rate_spec.rb | 75 ++++++++----- .../calculators/tiered_flat_rate_spec.rb | 32 ++++-- .../calculators/tiered_percent_spec.rb | 41 +++++-- .../friendly_promotion_discounter_spec.rb | 11 +- .../item_discount_spec.rb | 1 - .../order_discounter_spec.rb | 20 +++- .../order_promotion_spec.rb | 1 + .../promotion_action_spec.rb | 9 +- .../promotion_category_spec.rb | 4 +- .../promotion_code/batch_builder_spec.rb | 4 +- .../promotion_code_batch_spec.rb | 10 +- .../promotion_code_spec.rb | 33 ++++-- .../promotion_spec.rb | 101 ++++++++++++------ .../rules/first_order_spec.rb | 19 +++- .../rules/first_repeat_purchase_since_spec.rb | 4 +- .../rules/item_total_spec.rb | 11 +- .../rules/line_item_product_spec.rb | 12 ++- .../rules/nth_order_spec.rb | 3 +- .../rules/one_use_per_user_spec.rb | 6 ++ .../rules/option_value_spec.rb | 26 +++-- .../rules/product_spec.rb | 35 +++--- .../rules/shipping_method_spec.rb | 12 +-- .../rules/store_spec.rb | 10 +- .../rules/taxon_spec.rb | 33 +++--- .../rules/user_logged_in_spec.rb | 9 +- .../rules/user_role_spec.rb | 46 ++++---- .../rules/user_spec.rb | 15 +-- .../simple_order_contents_spec.rb | 59 +++++----- .../spec/models/spree/shipping_rate_spec.rb | 14 +-- .../admin/promotion_actions_request_spec.rb | 6 +- .../admin/promotion_rules_request_spec.rb | 4 +- .../calculator_shared_examples.rb | 1 + .../admin/promotion_categories_spec.rb | 16 +-- .../admin/promotions_spec.rb | 22 +++- 80 files changed, 661 insertions(+), 365 deletions(-) diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb index 8581e38a7ba..69b307f8d6e 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb @@ -23,7 +23,8 @@ def create @promotion_action = @promotion_action_type.new(promotion_action_params) @promotion_action.promotion = @promotion if @promotion_action.save(validate: false) - flash[:success] = t('spree.successfully_created', resource: SolidusFriendlyPromotions::PromotionAction.model_name.human) + flash[:success] = + t('spree.successfully_created', resource: SolidusFriendlyPromotions::PromotionAction.model_name.human) redirect_to location_after_save, format: :html else render :new, layout: false @@ -34,7 +35,8 @@ def update @promotion_action = @promotion.actions.find(params[:id]) @promotion_action.assign_attributes(promotion_action_params) if @promotion_action.save - flash[:success] = t('spree.successfully_updated', resource: SolidusFriendlyPromotions::PromotionAction.model_name.human) + flash[:success] = + t('spree.successfully_updated', resource: SolidusFriendlyPromotions::PromotionAction.model_name.human) redirect_to location_after_save, format: :html else render :edit @@ -44,7 +46,8 @@ def update def destroy @promotion_action = @promotion.actions.find(params[:id]) if @promotion_action.discard - flash[:success] = t('spree.successfully_removed', resource: SolidusFriendlyPromotions::PromotionAction.model_name.human) + flash[:success] = + t('spree.successfully_removed', resource: SolidusFriendlyPromotions::PromotionAction.model_name.human) end redirect_to location_after_save, format: :html end @@ -69,7 +72,6 @@ def validate_level end end - def promotion_action_params params[:promotion_action].try(:permit!) || {} end @@ -80,12 +82,12 @@ def validate_promotion_action_type @promotion_action_type = promotion_action_types.detect do |klass| klass.name == requested_type end - if !@promotion_action_type - flash[:error] = t('solidus_friendly_promotions.invalid_promotion_action') - respond_to do |format| - format.html { redirect_to solidus_friendly_promotions.edit_admin_promotion_path(@promotion) } - format.js { render layout: false } - end + return if @promotion_action_type + + flash[:error] = t('solidus_friendly_promotions.invalid_promotion_action') + respond_to do |format| + format.html { redirect_to solidus_friendly_promotions.edit_admin_promotion_path(@promotion) } + format.js { render layout: false } end end end diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_codes_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_codes_controller.rb index 42b857d0fbe..d77de5f734e 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_codes_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_codes_controller.rb @@ -5,8 +5,9 @@ module SolidusFriendlyPromotions module Admin class PromotionCodesController < Spree::Admin::ResourceController + before_action :load_promotion + def index - @promotion = SolidusFriendlyPromotions::Promotion.accessible_by(current_ability, :show).find(params[:promotion_id]) @promotion_codes = @promotion.codes.order(:value) respond_to do |format| @@ -22,9 +23,9 @@ def index end def new - @promotion = SolidusFriendlyPromotions::Promotion.accessible_by(current_ability, :show).find(params[:promotion_id]) if @promotion.apply_automatically - flash[:error] = t('activerecord.errors.models.solidus_friendly_promotions/promotion_code.attributes.base.disallowed_with_apply_automatically') + flash[:error] = + t('activerecord.errors.models.solidus_friendly_promotions/promotion_code.attributes.base.disallowed_with_apply_automatically') redirect_to solidus_friendly_promotions.admin_promotion_promotion_codes_url(@promotion) else @promotion_code = @promotion.codes.build @@ -32,7 +33,6 @@ def new end def create - @promotion = SolidusFriendlyPromotions::Promotion.accessible_by(current_ability, :show).find(params[:promotion_id]) @promotion_code = @promotion.codes.build(value: params[:promotion_code][:value]) if @promotion_code.save @@ -43,6 +43,14 @@ def create render_after_create_error end end + + private + + def load_promotion + @promotion = SolidusFriendlyPromotions::Promotion. + accessible_by(current_ability, :show). + find(params[:promotion_id]) + end end end end diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb index 9f0cb2c43d4..7ecf39e4db0 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb @@ -22,7 +22,8 @@ def create promotion_rule_params.merge(type: @promotion_rule_type.to_s) ) if @promotion_rule.save - flash[:success] = t('spree.successfully_created', resource: SolidusFriendlyPromotions::PromotionRule.model_name.human) + flash[:success] = + t('spree.successfully_created', resource: SolidusFriendlyPromotions::PromotionRule.model_name.human) end redirect_to location_after_save end @@ -31,7 +32,8 @@ def update @promotion_rule = @promotion.rules.find(params[:id]) @promotion_rule.assign_attributes(promotion_rule_params) if @promotion_rule.save - flash[:success] = t('spree.successfully_updated', resource: SolidusFriendlyPromotions::PromotionRule.model_name.human) + flash[:success] = + t('spree.successfully_updated', resource: SolidusFriendlyPromotions::PromotionRule.model_name.human) end redirect_to location_after_save end @@ -39,7 +41,8 @@ def update def destroy @promotion_rule = @promotion.rules.find(params[:id]) if @promotion_rule.destroy - flash[:success] = t('spree.successfully_removed', resource: SolidusFriendlyPromotions::PromotionRule.model_name.human) + flash[:success] = + t('spree.successfully_removed', resource: SolidusFriendlyPromotions::PromotionRule.model_name.human) end redirect_to location_after_save end @@ -64,12 +67,12 @@ def validate_promotion_rule_type @promotion_rule_type = promotion_rule_types.detect do |klass| klass.name == requested_type end - if !@promotion_rule_type - flash[:error] = t('solidus_friendly_promotions.invalid_promotion_rule') - respond_to do |format| - format.html { redirect_to solidus_friendly_promotions.edit_admin_promotion_path(@promotion) } - format.js { render layout: false } - end + return if @promotion_rule_type + + flash[:error] = t('solidus_friendly_promotions.invalid_promotion_rule') + respond_to do |format| + format.html { redirect_to solidus_friendly_promotions.edit_admin_promotion_path(@promotion) } + format.js { render layout: false } end end @@ -88,7 +91,7 @@ def promotion_rule_params end def promotion_rule_types - case params.dig(:level) + case params[:level] when "order" SolidusFriendlyPromotions.config.order_rules when "line_item" diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb index 48dc47aae63..46ecf67558c 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb @@ -17,7 +17,7 @@ def create end if @promotion.save - @code_batch.process if @code_batch + @code_batch&.process flash[:success] = t('solidus_friendly_promotions.promotion_successfully_created') redirect_to location_after_save else @@ -30,15 +30,16 @@ def create def collection return @collection if @collection + params[:q] ||= HashWithIndifferentAccess.new params[:q][:s] ||= 'id desc' @collection = super @search = @collection.ransack(params[:q]) @collection = @search.result(distinct: true). - includes(promotion_includes). - page(params[:page]). - per(params[:per_page] || SolidusFriendlyPromotions.config.promotions_per_page) + includes(promotion_includes). + page(params[:page]). + per(params[:per_page] || SolidusFriendlyPromotions.config.promotions_per_page) @collection end diff --git a/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_decorator.rb b/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_decorator.rb index e1b4e4d1d8e..991f7d7c4ad 100644 --- a/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_decorator.rb +++ b/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_decorator.rb @@ -1,8 +1,11 @@ # frozen_string_literal: true -module SolidusFriendlyPromotions::OrderDecorator +module SolidusFriendlyPromotions + module OrderDecorator def self.prepended(base) - base.has_many :friendly_order_promotions, class_name: "SolidusFriendlyPromotions::OrderPromotion", inverse_of: :order + base.has_many :friendly_order_promotions, + class_name: "SolidusFriendlyPromotions::OrderPromotion", + inverse_of: :order base.has_many :friendly_promotions, through: :friendly_order_promotions, source: :promotion end @@ -19,3 +22,4 @@ def ensure_promotions_eligible Spree::Order.prepend self end +end diff --git a/friendly_promotions/app/decorators/models/solidus_friendly_promotions/shipping_rate_decorator.rb b/friendly_promotions/app/decorators/models/solidus_friendly_promotions/shipping_rate_decorator.rb index 635bbf151e3..66cf3cf5cbf 100644 --- a/friendly_promotions/app/decorators/models/solidus_friendly_promotions/shipping_rate_decorator.rb +++ b/friendly_promotions/app/decorators/models/solidus_friendly_promotions/shipping_rate_decorator.rb @@ -1,25 +1,27 @@ # frozen_string_literal: true -module SolidusFriendlyPromotions::ShippingRateDecorator - def self.prepended(base) - base.class_eval do - has_many :discounts, - class_name: "SolidusFriendlyPromotions::ShippingRateDiscount", - foreign_key: :shipping_rate_id, - dependent: :destroy, - inverse_of: :shipping_rate, - autosave: true +module SolidusFriendlyPromotions + module ShippingRateDecorator + def self.prepended(base) + base.class_eval do + has_many :discounts, + class_name: "SolidusFriendlyPromotions::ShippingRateDiscount", + foreign_key: :shipping_rate_id, + dependent: :destroy, + inverse_of: :shipping_rate, + autosave: true - money_methods :total_before_tax, :promo_total + money_methods :total_before_tax, :promo_total + end end - end - def total_before_tax - amount + promo_total - end + def total_before_tax + amount + promo_total + end - def promo_total - discounts.sum(&:amount) + def promo_total + discounts.sum(&:amount) + end end end diff --git a/friendly_promotions/app/jobs/solidus_friendly_promotions/promotion_code_batch_job.rb b/friendly_promotions/app/jobs/solidus_friendly_promotions/promotion_code_batch_job.rb index 204bca2f01f..e580d7dfead 100644 --- a/friendly_promotions/app/jobs/solidus_friendly_promotions/promotion_code_batch_job.rb +++ b/friendly_promotions/app/jobs/solidus_friendly_promotions/promotion_code_batch_job.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module SolidusFriendlyPromotions - class PromotionCodeBatchJob < ActiveJob::Base + class PromotionCodeBatchJob < ApplicationJob queue_as :default def perform(promotion_code_batch) @@ -11,16 +11,16 @@ def perform(promotion_code_batch) if promotion_code_batch.email? SolidusFriendlyPromotions.config.promotion_code_batch_mailer_class - .promotion_code_batch_finished(promotion_code_batch) - .deliver_now + .promotion_code_batch_finished(promotion_code_batch) + .deliver_now end - rescue StandardError => error + rescue StandardError => e if promotion_code_batch.email? SolidusFriendlyPromotions.config.promotion_code_batch_mailer_class - .promotion_code_batch_errored(promotion_code_batch) - .deliver_now + .promotion_code_batch_errored(promotion_code_batch) + .deliver_now end - raise error + raise e end end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions.rb b/friendly_promotions/app/models/solidus_friendly_promotions.rb index 56b3908fe0c..db8181d1339 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module SolidusFriendlyPromotions def self.table_name_prefix 'friendly_' diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/distributed_amount.rb b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/distributed_amount.rb index 5513190d90e..c2d940b3af2 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/distributed_amount.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/distributed_amount.rb @@ -15,8 +15,10 @@ class DistributedAmount < Spree::Calculator def compute_line_item(line_item) return 0 unless line_item return 0 unless preferred_currency.casecmp(line_item.currency).zero? + distributable_line_items = eligible_line_items(line_item.order) return 0 unless line_item.in?(distributable_line_items) + DistributedAmountsHandler.new( distributable_line_items, preferred_amount diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/flexi_rate.rb b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/flexi_rate.rb index 2ab4ed53063..29efc3e9605 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/flexi_rate.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/flexi_rate.rb @@ -14,7 +14,7 @@ def compute(object) items_count = object.quantity items_count = [items_count, preferred_max_items].min unless preferred_max_items.zero? - return BigDecimal(0) if items_count == 0 + return BigDecimal('0') if items_count == 0 additional_items_count = items_count - 1 preferred_first_item + preferred_additional_item * additional_items_count diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_flat_rate.rb b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_flat_rate.rb index 77e04246fe7..b09845a14f2 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_flat_rate.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_flat_rate.rb @@ -39,7 +39,7 @@ def compute_item(object) def cast_to_d(value) value.to_s.to_d rescue ArgumentError - BigDecimal(0) + BigDecimal('0') end def preferred_tiers_content diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_percent.rb b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_percent.rb index 7e3f75d1005..28b725eee2f 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_percent.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_percent.rb @@ -46,7 +46,7 @@ def compute_item(object) def cast_to_d(value) value.to_s.to_d rescue ArgumentError - BigDecimal(0) + BigDecimal('0') end def preferred_tiers_content diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb b/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb index 8db13a4855d..a9f950a3897 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb @@ -30,16 +30,20 @@ def call item_discounts = all_item_discounts.select { |element| element.item == item } chosen_item_discounts = SolidusFriendlyPromotions.config.discount_chooser_class.new(item).call(item_discounts) item.discounts = chosen_item_discounts.map do |discount| - SolidusFriendlyPromotions::ShippingRateDiscount.new(shipping_rate: item, amount: discount.amount, label: discount.label) + SolidusFriendlyPromotions::ShippingRateDiscount.new( + shipping_rate: item, + amount: discount.amount, + label: discount.label + ) end end - @order.promo_total = (order.line_items + order.shipments).sum { |item| item.promo_total } + @order.promo_total = (order.line_items + order.shipments).sum(&:promo_total) @order end - private + attr_reader :order # Walk through the discounts for an item and update adjustments for it. Once diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb index 362ee580158..50cb94d9fcf 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb @@ -2,7 +2,8 @@ module SolidusFriendlyPromotions class Promotion < Spree::Base - belongs_to :category, class_name: "SolidusFriendlyPromotions::PromotionCategory", foreign_key: :promotion_category_id, optional: true + belongs_to :category, class_name: "SolidusFriendlyPromotions::PromotionCategory", + foreign_key: :promotion_category_id, optional: true has_many :rules, class_name: "SolidusFriendlyPromotions::PromotionRule" has_many :actions, class_name: "SolidusFriendlyPromotions::PromotionAction" has_many :codes, class_name: "SolidusFriendlyPromotions::PromotionCode" @@ -72,9 +73,9 @@ def used_by?(user, excluded_orders = []) # @param excluded_orders [Array] Orders to exclude from usage limit # @return true or false def usage_limit_exceeded?(excluded_orders: []) - if usage_limit - usage_count(excluded_orders: excluded_orders) >= usage_limit - end + return unless usage_limit + + usage_count(excluded_orders: excluded_orders) >= usage_limit end def not_expired? diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb index 17be3ed46fe..2388485cadc 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + require 'spree/preferences/persistable' module SolidusFriendlyPromotions @@ -36,7 +37,7 @@ def discount(adjustable) # Ensure a negative amount which does not exceed the object's amount def compute_amount(adjustable) - promotion_amount = calculator.compute(adjustable) || BigDecimal(0) + promotion_amount = calculator.compute(adjustable) || BigDecimal('0') [adjustable.amount, promotion_amount.abs].min * -1 end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code.rb index 6ed524a5b17..0f2a7c82fbd 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code.rb @@ -18,9 +18,9 @@ class PromotionCode < Spree::Base # @param excluded_orders [Array] Orders to exclude from usage limit # @return true or false def usage_limit_exceeded?(excluded_orders: []) - if usage_limit - usage_count(excluded_orders: excluded_orders) >= usage_limit - end + return unless usage_limit + + usage_count(excluded_orders: excluded_orders) >= usage_limit end # Number of times the code has been used overall @@ -33,7 +33,7 @@ def usage_count(excluded_orders: []) complete. where.not(spree_orders: { state: :canceled }). joins(:friendly_order_promotions). - where(friendly_order_promotions: { promotion_code_id: self.id }). + where(friendly_order_promotions: { promotion_code_id: id }). where.not(id: excluded_orders.map(&:id)). count end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code/batch_builder.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code/batch_builder.rb index 37dc95aead3..58ba96fb692 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code/batch_builder.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code/batch_builder.rb @@ -4,13 +4,14 @@ module SolidusFriendlyPromotions class PromotionCode < Spree::Base class BatchBuilder attr_reader :promotion_code_batch, :options + delegate :promotion, :number_of_codes, :base_code, to: :promotion_code_batch DEFAULT_OPTIONS = { random_code_length: 6, batch_size: 1000, sample_characters: ('a'..'z').to_a + (2..9).to_a.map(&:to_s) - } + }.freeze def initialize(promotion_code_batch, options = {}) @promotion_code_batch = promotion_code_batch @@ -21,12 +22,12 @@ def initialize(promotion_code_batch, options = {}) def build_promotion_codes generate_random_codes promotion_code_batch.update!(state: "completed") - rescue StandardError => error + rescue StandardError => e promotion_code_batch.update!( - error: error.inspect, + error: e.inspect, state: "failed" ) - raise error + raise e end private diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code_batch.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code_batch.rb index 135fbb8998f..cbb33bfb59b 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code_batch.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code_batch.rb @@ -9,19 +9,17 @@ class CantProcessStartedBatch < StandardError has_many :promotion_codes, dependent: :destroy validates :number_of_codes, numericality: { greater_than: 0 } - validates_presence_of :base_code, :number_of_codes + validates :base_code, :number_of_codes, presence: true def finished? state == "completed" end def process - if state == "pending" - update!(state: "processing") - PromotionCodeBatchJob.perform_later(self) - else - raise CantProcessStartedBatch.new("Batch #{id} already started") - end + raise CantProcessStartedBatch, "Batch #{id} already started" unless state == "pending" + + update!(state: "processing") + PromotionCodeBatchJob.perform_later(self) end end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rule.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rule.rb index 06181f15f24..79cf24e4d69 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rule.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rule.rb @@ -39,9 +39,9 @@ def updateable? private def unique_per_promotion - if self.class.exists?(promotion_id: promotion_id, type: self.class.name) - errors[:base] << "Promotion already contains this rule type" - end + return unless self.class.exists?(promotion_id: promotion_id, type: self.class.name) + + errors[:base] << "Promotion already contains this rule type" end def eligibility_error_message(key, options = {}) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_order.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_order.rb index bd4df181558..65ccd9921de 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_order.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_order.rb @@ -13,10 +13,8 @@ def eligible?(order, options = {}) @user = order.try(:user) || options[:user] @email = order.email - if user || email - if !completed_orders.blank? && completed_orders.first != order - eligibility_errors.add(:base, eligibility_error_message(:not_first_order), error_code: :not_first_order) - end + if (user || email) && (completed_orders.present? && completed_orders.first != order) + eligibility_errors.add(:base, eligibility_error_message(:not_first_order), error_code: :not_first_order) end eligibility_errors.empty? diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb index dfca964597d..1e23f2b2a98 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb @@ -4,7 +4,7 @@ module SolidusFriendlyPromotions module Rules class FirstRepeatPurchaseSince < PromotionRule preference :days_ago, :integer, default: 365 - validates :preferred_days_ago, numericality: {only_integer: true, greater_than: 0} + validates :preferred_days_ago, numericality: { only_integer: true, greater_than: 0 } # This promotion is applicable to orders only. def applicable?(promotable) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb index a1573067ea8..12df85239e9 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb @@ -68,7 +68,11 @@ def ineligible_message when "gt" eligibility_error_message(:item_total_less_than_or_equal, amount: formatted_amount) else - eligibility_error_message(:item_total_doesnt_match_with_operator, amount: formatted_amount, operator: preferred_operator) + eligibility_error_message( + :item_total_doesnt_match_with_operator, + amount: formatted_amount, + operator: preferred_operator + ) end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb index c6ee3621a60..b24e8e55a38 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb @@ -4,7 +4,7 @@ module SolidusFriendlyPromotions module Rules # A rule to apply a promotion only to line items with or without a chosen product class LineItemProduct < PromotionRule - MATCH_POLICIES = %w[include exclude] + MATCH_POLICIES = %w[include exclude].freeze has_many :product_promotion_rules, dependent: :destroy, diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb index 57bffa744a0..1308aacde98 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb @@ -7,9 +7,9 @@ class LineItemTaxon < PromotionRule dependent: :destroy has_many :taxons, through: :promotion_rule_taxons, class_name: "Spree::Taxon" - MATCH_POLICIES = %w[include exclude] + MATCH_POLICIES = %w[include exclude].freeze - validates_inclusion_of :preferred_match_policy, in: MATCH_POLICIES + validates :preferred_match_policy, inclusion: { in: MATCH_POLICIES } preference :match_policy, :string, default: MATCH_POLICIES.first def applicable?(promotable) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb index 04cb592bd6c..f542ef13250 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb @@ -6,7 +6,7 @@ class NthOrder < PromotionRule preference :nth_order, :integer, default: 2 # It does not make sense to have this apply to the first order using preferred_nth_order == 1 # Instead we could use the first_order rule - validates :preferred_nth_order, numericality: {only_integer: true, greater_than: 1} + validates :preferred_nth_order, numericality: { only_integer: true, greater_than: 1 } # This promotion is applicable to orders only. def applicable?(promotable) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb index 770d964371f..5c9d13a89ef 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb @@ -10,7 +10,11 @@ def applicable?(promotable) def eligible?(order, _options = {}) if order.user.present? if promotion.used_by?(order.user, [order]) - eligibility_errors.add(:base, eligibility_error_message(:limit_once_per_user), error_code: :limit_once_per_user) + eligibility_errors.add( + :base, + eligibility_error_message(:limit_once_per_user), + error_code: :limit_once_per_user + ) end else eligibility_errors.add(:base, eligibility_error_message(:no_user_specified), error_code: :no_user_specified) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb index 9ae2b78f76d..c5bbd4503be 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb @@ -13,14 +13,13 @@ class Product < PromotionRule class_name: 'SolidusFriendlyPromotions::ProductsPromotionRule' has_many :products, class_name: 'Spree::Product', through: :products_promotion_rules - def preload_relations [:products] end - MATCH_POLICIES = %w[any all none] + MATCH_POLICIES = %w[any all none].freeze - validates_inclusion_of :preferred_match_policy, in: MATCH_POLICIES + validates :preferred_match_policy, inclusion: { in: MATCH_POLICIES } preference :match_policy, :string, default: MATCH_POLICIES.first diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb index 38925b555cc..f972e4c1160 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb @@ -4,16 +4,16 @@ module SolidusFriendlyPromotions module Rules class Taxon < PromotionRule has_many :promotion_rules_taxons, class_name: 'SolidusFriendlyPromotions::PromotionRulesTaxon', foreign_key: :promotion_rule_id, - dependent: :destroy + dependent: :destroy has_many :taxons, through: :promotion_rules_taxons, class_name: 'Spree::Taxon' def preload_relations [:taxons] end - MATCH_POLICIES = %w[any all none] + MATCH_POLICIES = %w[any all none].freeze - validates_inclusion_of :preferred_match_policy, in: MATCH_POLICIES + validates :preferred_match_policy, inclusion: { in: MATCH_POLICIES } preference :match_policy, :string, default: MATCH_POLICIES.first def applicable?(promotable) @@ -34,11 +34,19 @@ def eligible?(order, _options = {}) end when "any" unless order_taxons.where(id: rule_taxon_ids_with_children).exists? - eligibility_errors.add(:base, eligibility_error_message(:no_matching_taxons), error_code: :no_matching_taxons) + eligibility_errors.add( + :base, + eligibility_error_message(:no_matching_taxons), + error_code: :no_matching_taxons + ) end when "none" if order_taxons.where(id: rule_taxon_ids_with_children).exists? - eligibility_errors.add(:base, eligibility_error_message(:has_excluded_taxon), error_code: :has_excluded_taxon) + eligibility_errors.add( + :base, + eligibility_error_message(:has_excluded_taxon), + error_code: :has_excluded_taxon + ) end else raise "unexpected match policy: #{preferred_match_policy.inspect}" @@ -63,8 +71,10 @@ def updateable? # All taxons in an order def taxons_in_order(order) - Spree::Taxon.joins(products: {variants_including_master: :line_items}) - .where(spree_line_items: {order_id: order.id}).distinct + Spree::Taxon. + joins(products: { variants_including_master: :line_items }). + where(spree_line_items: { order_id: order.id }). + distinct end # ids of taxons rules and taxons rules children diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb index f43e85083c9..5be6e2ea802 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb @@ -8,7 +8,7 @@ def applicable?(promotable) end def eligible?(order, _options = {}) - unless order.user.present? + if order.user.blank? eligibility_errors.add(:base, eligibility_error_message(:no_user_specified), error_code: :no_user_specified) end eligibility_errors.empty? diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_role.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_role.rb index 7763145e979..e8df12f71ba 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_role.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_role.rb @@ -5,7 +5,7 @@ module Rules class UserRole < PromotionRule preference :role_ids, :array, default: [] - MATCH_POLICIES = %w[any all] + MATCH_POLICIES = %w[any all].freeze preference :match_policy, default: MATCH_POLICIES.first def applicable?(promotable) @@ -14,6 +14,7 @@ def applicable?(promotable) def eligible?(order, _options = {}) return false unless order.user + if all_match_policy? match_all_roles?(order) else diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/simple_order_contents.rb b/friendly_promotions/app/models/solidus_friendly_promotions/simple_order_contents.rb index c66dfa8c979..015949aad9c 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/simple_order_contents.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/simple_order_contents.rb @@ -2,7 +2,6 @@ module SolidusFriendlyPromotions class SimpleOrderContents < Spree::OrderContents - def update_cart(params) if order.update(params) unless order.completed? diff --git a/friendly_promotions/config/importmap.rb b/friendly_promotions/config/importmap.rb index f0ffda37249..c9b8e9895a9 100644 --- a/friendly_promotions/config/importmap.rb +++ b/friendly_promotions/config/importmap.rb @@ -1,8 +1,12 @@ +# frozen_string_literal: true + pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true -pin_all_from SolidusFriendlyPromotions::Engine.root.join("app/javascript/solidus_friendly_promotions/controllers"), under: "solidus_friendly_promotions/controllers" -pin_all_from SolidusFriendlyPromotions::Engine.root.join("app/javascript/solidus_friendly_promotions/jquery"), under: "solidus_friendly_promotions/jquery" +pin_all_from SolidusFriendlyPromotions::Engine.root.join("app/javascript/solidus_friendly_promotions/controllers"), + under: "solidus_friendly_promotions/controllers" +pin_all_from SolidusFriendlyPromotions::Engine.root.join("app/javascript/solidus_friendly_promotions/jquery"), + under: "solidus_friendly_promotions/jquery" pin "solidus_friendly_promotions", to: "solidus_friendly_promotions.js", preload: true diff --git a/friendly_promotions/config/initializers/solidus_friendly_promotions.rb b/friendly_promotions/config/initializers/solidus_friendly_promotions.rb index d4678cdaad8..68f60b8fdc3 100644 --- a/friendly_promotions/config/initializers/solidus_friendly_promotions.rb +++ b/friendly_promotions/config/initializers/solidus_friendly_promotions.rb @@ -1 +1,3 @@ +# frozen_string_literal: true + require_relative SolidusFriendlyPromotions::Engine.root.join("app/models/solidus_friendly_promotions.rb") diff --git a/friendly_promotions/db/migrate/20230725074235_create_shipping_rate_discounts.rb b/friendly_promotions/db/migrate/20230725074235_create_shipping_rate_discounts.rb index bcbe7beb59a..2bb7c594f3e 100644 --- a/friendly_promotions/db/migrate/20230725074235_create_shipping_rate_discounts.rb +++ b/friendly_promotions/db/migrate/20230725074235_create_shipping_rate_discounts.rb @@ -2,7 +2,7 @@ class CreateShippingRateDiscounts < ActiveRecord::Migration[7.0] def change create_table :friendly_shipping_rate_discounts do |t| t.references :promotion_action, null: false, foreign_key: { to_table: :friendly_promotion_actions } - t.references :shipping_rate, null: false, foreign_key: { to_table: :spree_shipping_rates} + t.references :shipping_rate, null: false, foreign_key: { to_table: :spree_shipping_rates } t.decimal :amount, precision: 10, scale: 2, null: false t.string :label, null: false end diff --git a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/install_generator.rb b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/install_generator.rb index ec1ebee171f..7fb1a28b878 100644 --- a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/install_generator.rb +++ b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/install_generator.rb @@ -25,7 +25,7 @@ def add_migrations def mount_engine inject_into_file "config/routes.rb", " mount SolidusFriendlyPromotions::Engine => '/'\n", - before: %r{ mount Spree::Core::Engine.*}, + before: / mount Spree::Core::Engine.*/, verbose: true end diff --git a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb index 0dca999a063..b235d5ab74b 100644 --- a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb +++ b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb @@ -8,6 +8,7 @@ Spree::Backend::Config.configure do |config| config.menu_items = Spree::Backend::Config.menu_items.map do |item| next item unless item.url == :admin_promotions_path + Spree::BackendConfiguration::MenuItem.new( [:promotions, :promotion_categories], 'bullhorn', diff --git a/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb b/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb index a291e6a3b38..cb996be8f20 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb @@ -17,7 +17,8 @@ class Configuration < Spree::Preferences::Configuration add_class_set :discounters class_name_attribute :discount_chooser_class, default: "SolidusFriendlyPromotions::PromotionAdjustmentChooser" - class_name_attribute :promotion_code_batch_mailer_class, default: "SolidusFriendlyPromotions::PromotionCodeBatchMailer" + class_name_attribute :promotion_code_batch_mailer_class, + default: "SolidusFriendlyPromotions::PromotionCodeBatchMailer" # @!attribute [rw] promotions_per_page # @return [Integer] Promotions to show per-page in the admin (default: +25+) diff --git a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/factories.rb b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/factories.rb index ced5cd0d102..7c8483323fe 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/factories.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/factories.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions def self.table_name_prefix - "friendly_" + "friendly_" end end require "solidus_friendly_promotions/testing_support/friendly_promotion_code_factory" diff --git a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_order_factory.rb b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_order_factory.rb index 04819d8b05b..33388b50487 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_order_factory.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_order_factory.rb @@ -1,4 +1,3 @@ - # frozen_string_literal: true FactoryBot.define do diff --git a/friendly_promotions/solidus_friendly_promotions.gemspec b/friendly_promotions/solidus_friendly_promotions.gemspec index af999ef9f5d..8db44d9ff47 100644 --- a/friendly_promotions/solidus_friendly_promotions.gemspec +++ b/friendly_promotions/solidus_friendly_promotions.gemspec @@ -32,8 +32,8 @@ Gem::Specification.new do |spec| spec.add_dependency "solidus_support", "~> 0.5" spec.add_dependency "turbo-rails", "~> 1.4" - spec.add_development_dependency "solidus_dev_support", "~> 2.6" spec.add_development_dependency "importmap-rails", "~> 1.2" spec.add_development_dependency "rspec-activemodel-mocks", "~> 1.0" spec.add_development_dependency "shoulda-matchers", "~> 5.3" + spec.add_development_dependency "solidus_dev_support", "~> 2.6" end diff --git a/friendly_promotions/spec/jobs/solidus_friendly_promotions/promotion_code_batch_job_spec.rb b/friendly_promotions/spec/jobs/solidus_friendly_promotions/promotion_code_batch_job_spec.rb index 0ad6ca3b47a..46a497b8702 100644 --- a/friendly_promotions/spec/jobs/solidus_friendly_promotions/promotion_code_batch_job_spec.rb +++ b/friendly_promotions/spec/jobs/solidus_friendly_promotions/promotion_code_batch_job_spec.rb @@ -11,6 +11,7 @@ email: email ) end + context "with a successful build" do before do allow(SolidusFriendlyPromotions::PromotionCodeBatchMailer) @@ -30,6 +31,7 @@ def codes end end end + context 'with a custom join character' do let(:code_batch) do SolidusFriendlyPromotions::PromotionCodeBatch.create!( @@ -40,6 +42,7 @@ def codes join_characters: '-' ) end + it 'uses the custom join characters', :aggregate_failures do subject.perform(code_batch) codes.each do |code| @@ -47,6 +50,7 @@ def codes end end end + context "with an email address" do it "sends an email" do subject.perform(code_batch) @@ -54,12 +58,14 @@ def codes .to have_received(:promotion_code_batch_finished) end end + context "with no email address" do let(:email) { nil } + it "sends an email" do subject.perform(code_batch) expect(SolidusFriendlyPromotions::PromotionCodeBatchMailer) - .to_not have_received(:promotion_code_batch_finished) + .not_to have_received(:promotion_code_batch_finished) end end end @@ -87,9 +93,10 @@ def codes context "with no email address" do let(:email) { nil } + it "sends an email" do expect(SolidusFriendlyPromotions::PromotionCodeBatchMailer) - .to_not have_received(:promotion_code_batch_errored) + .not_to have_received(:promotion_code_batch_errored) end end end diff --git a/friendly_promotions/spec/mailers/solidus_friendly_promotions/promotion_code_batch_mailer_spec.rb b/friendly_promotions/spec/mailers/solidus_friendly_promotions/promotion_code_batch_mailer_spec.rb index 9180710df43..9ee8ba28729 100644 --- a/friendly_promotions/spec/mailers/solidus_friendly_promotions/promotion_code_batch_mailer_spec.rb +++ b/friendly_promotions/spec/mailers/solidus_friendly_promotions/promotion_code_batch_mailer_spec.rb @@ -30,9 +30,10 @@ end describe "#promotion_code_batch_errored" do - before { code_batch.update(error: "Test error") } subject { described_class.promotion_code_batch_errored(code_batch) } + before { code_batch.update(error: "Test error") } + it "sends the email to the email attached to the promotion code batch " do expect(subject.to).to eq([code_batch.email]) end diff --git a/friendly_promotions/spec/models/promotion/integration_spec.rb b/friendly_promotions/spec/models/promotion/integration_spec.rb index b12c83cb3e8..8ab354e7607 100644 --- a/friendly_promotions/spec/models/promotion/integration_spec.rb +++ b/friendly_promotions/spec/models/promotion/integration_spec.rb @@ -72,7 +72,9 @@ order.create_proposed_shipments - order.shipments.first.selected_shipping_rate_id = order.shipments.first.shipping_rates.detect { |r| r.shipping_method == shipping_method }.id + order.shipments.first.selected_shipping_rate_id = order.shipments.first.shipping_rates.detect do |r| + r.shipping_method == shipping_method + end.id order.recalculate end @@ -100,6 +102,7 @@ context "when the order is eligible" do let(:shipping_method) { ups_ground } + it "creates adjustments" do expect(order.adjustments).to be_empty expect(order.total).to eq(31.40) diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_line_item_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_line_item_spec.rb index 8d8ab9ae105..863f4be290a 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_line_item_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_line_item_spec.rb @@ -13,6 +13,7 @@ describe ".to_partial_path" do subject { described_class.new.to_partial_path } + it { is_expected.to eq("solidus_friendly_promotions/admin/promotion_actions/actions/adjust_line_item") } end end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/distributed_amount_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/distributed_amount_spec.rb index 469d3630965..9ad4690ce94 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/distributed_amount_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/distributed_amount_spec.rb @@ -5,7 +5,9 @@ RSpec.describe SolidusFriendlyPromotions::Calculators::DistributedAmount, type: :model do let(:calculator) { described_class.new(preferred_amount: 15, preferred_currency: currency) } - let!(:promotion) { create :friendly_promotion, apply_automatically: true, name: '15 spread', actions: [action], rules: rules } + let!(:promotion) do + create :friendly_promotion, apply_automatically: true, name: '15 spread', actions: [action], rules: rules + end let(:rules) { [] } let(:action) { SolidusFriendlyPromotions::Actions::AdjustLineItem.create(calculator: calculator) } let(:order) { create(:order_with_line_items, line_items_attributes: line_items_attributes) } @@ -13,6 +15,7 @@ context 'applied to an order' do let(:line_items_attributes) { [{ price: 20 }, { price: 30 }, { price: 100 }] } + before do order.recalculate end @@ -42,19 +45,20 @@ end describe "#compute_line_item" do - let(:line_items_attributes) { [{ price: 50 }, { price: 50 }, { price: 50 }] } - subject { calculator.compute_line_item(order.line_items.first) } + let(:line_items_attributes) { [{ price: 50 }, { price: 50 }, { price: 50 }] } context "when the order currency matches the store's currency" do let(:currency) { "USD" } + it { is_expected.to eq 5 } it { is_expected.to be_a BigDecimal } end context "when the order currency does not match the store's currency" do let(:currency) { "CAD" } + it { is_expected.to eq 0 } end end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/flat_rate_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/flat_rate_spec.rb index d893989e153..e89918d7865 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/flat_rate_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/flat_rate_spec.rb @@ -4,24 +4,25 @@ require 'shared_examples/calculator_shared_examples' RSpec.describe SolidusFriendlyPromotions::Calculators::FlatRate, type: :model do - it_behaves_like 'a calculator with a description' + subject { calculator.compute(line_item) } + let(:line_item) { mock_model(Spree::LineItem, order: order) } + let(:order) { mock_model(Spree::Order, currency: order_currency) } let(:calculator) do described_class.new( preferred_amount: preferred_amount, preferred_currency: preferred_currency ) end - let(:order) { mock_model(Spree::Order, currency: order_currency) } - let(:line_item) { mock_model(Spree::LineItem, order: order) } - subject { calculator.compute(line_item) } + it_behaves_like 'a calculator with a description' context "compute" do describe "when preferred currency matches order" do let(:preferred_currency) { "GBP" } let(:order_currency) { "GBP" } let(:preferred_amount) { 25 } + it { is_expected.to eq(25.0) } end @@ -29,6 +30,7 @@ let(:preferred_currency) { "GBP" } let(:order_currency) { "USD" } let(:preferred_amount) { 25 } + it { is_expected.to be_zero } end @@ -36,6 +38,7 @@ let(:preferred_currency) { "" } let(:order_currency) { "USD" } let(:preferred_amount) { 25 } + it { is_expected.to be_zero } end @@ -43,6 +46,7 @@ let(:preferred_currency) { "gbP" } let(:order_currency) { "GBP" } let(:preferred_amount) { 25 } + it { is_expected.to eq(25.0) } end end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/flexi_rate_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/flexi_rate_spec.rb index a3df09a76eb..078cecac60d 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/flexi_rate_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/flexi_rate_spec.rb @@ -11,39 +11,49 @@ preferred_max_items: max_items ) end + let(:line_item) do + mock_model( + Spree::LineItem, quantity: quantity + ) + end let(:first_item) { 0 } let(:additional_item) { 0 } let(:max_items) { 0 } - it_behaves_like 'a calculator with a description' - let(:line_item) do mock_model( Spree::LineItem, quantity: quantity ) end + it_behaves_like 'a calculator with a description' + context "compute" do subject { calculator.compute(line_item) } + context "with all amounts 0" do context "with quantity 0" do let(:quantity) { 0 } - it { should eq 0 } + + it { is_expected.to eq 0 } end context "with quantity 1" do let(:quantity) { 1 } - it { should eq 0 } + + it { is_expected.to eq 0 } end context "with quantity 2" do let(:quantity) { 2 } - it { should eq 0 } + + it { is_expected.to eq 0 } end context "with quantity 10" do let(:quantity) { 10 } - it { should eq 0 } + + it { is_expected.to eq 0 } end end @@ -52,22 +62,26 @@ context "with quantity 0" do let(:quantity) { 0 } - it { should eq 0 } + + it { is_expected.to eq 0 } end context "with quantity 1" do let(:quantity) { 1 } - it { should eq 1.23 } + + it { is_expected.to eq 1.23 } end context "with quantity 2" do let(:quantity) { 2 } - it { should eq 1.23 } + + it { is_expected.to eq 1.23 } end context "with quantity 10" do let(:quantity) { 10 } - it { should eq 1.23 } + + it { is_expected.to eq 1.23 } end end @@ -76,22 +90,26 @@ context "with quantity 0" do let(:quantity) { 0 } - it { should eq 0 } + + it { is_expected.to eq 0 } end context "with quantity 1" do let(:quantity) { 1 } - it { should eq 0 } + + it { is_expected.to eq 0 } end context "with quantity 2" do let(:quantity) { 2 } - it { should eq 1.23 } + + it { is_expected.to eq 1.23 } end context "with quantity 10" do let(:quantity) { 10 } - it { should eq 11.07 } + + it { is_expected.to eq 11.07 } end end @@ -101,22 +119,26 @@ context "with quantity 0" do let(:quantity) { 0 } - it { should eq 0 } + + it { is_expected.to eq 0 } end context "with quantity 1" do let(:quantity) { 1 } - it { should eq 1.13 } + + it { is_expected.to eq 1.13 } end context "with quantity 2" do let(:quantity) { 2 } - it { should eq 3.24 } + + it { is_expected.to eq 3.24 } end context "with quantity 10" do let(:quantity) { 10 } - it { should eq 20.12 } + + it { is_expected.to eq 20.12 } end context "with max_items 5" do @@ -124,33 +146,38 @@ context "with quantity 0" do let(:quantity) { 0 } - it { should eq 0 } + + it { is_expected.to eq 0 } end context "with quantity 1" do let(:quantity) { 1 } - it { should eq 1.13 } + + it { is_expected.to eq 1.13 } end context "with quantity 2" do let(:quantity) { 2 } - it { should eq 3.24 } + + it { is_expected.to eq 3.24 } end context "with quantity 5" do let(:quantity) { 5 } - it { should eq 9.57 } + + it { is_expected.to eq 9.57 } end context "with quantity 10" do let(:quantity) { 10 } - it { should eq 9.57 } + + it { is_expected.to eq 9.57 } end end end end - it "should allow creation of new object with all the attributes" do + it "allows creation of new object with all the attributes" do attributes = { preferred_first_item: 1, preferred_additional_item: 1, preferred_max_items: 1 } calculator = described_class.new(attributes) expect(calculator).to have_attributes(attributes) diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_flat_rate_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_flat_rate_spec.rb index c42b5c34cf4..b476776b7e6 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_flat_rate_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_flat_rate_spec.rb @@ -10,48 +10,55 @@ describe "#valid?" do subject { calculator.valid? } + context "when tiers is a hash" do context "and the key is not a positive number" do before { calculator.preferred_tiers = { "nope" => 20 } } + it { is_expected.to be false } end context "and the key is an integer" do before { calculator.preferred_tiers = { 20 => 20 } } + it "converts successfully" do - is_expected.to be true + expect(subject).to be true expect(calculator.preferred_tiers).to eq({ BigDecimal('20') => BigDecimal('20') }) end end context "and the key is a float" do before { calculator.preferred_tiers = { 20.5 => 20.5 } } + it "converts successfully" do - is_expected.to be true + expect(subject).to be true expect(calculator.preferred_tiers).to eq({ BigDecimal('20.5') => BigDecimal('20.5') }) end end context "and the key is a string number" do before { calculator.preferred_tiers = { "20" => 20 } } + it "converts successfully" do - is_expected.to be true + expect(subject).to be true expect(calculator.preferred_tiers).to eq({ BigDecimal('20') => BigDecimal('20') }) end end context "and the key is a numeric string with spaces" do before { calculator.preferred_tiers = { " 20 " => 20 } } + it "converts successfully" do - is_expected.to be true + expect(subject).to be true expect(calculator.preferred_tiers).to eq({ BigDecimal('20') => BigDecimal('20') }) end end context "and the key is a string number with decimals" do before { calculator.preferred_tiers = { "20.5" => "20.5" } } + it "converts successfully" do - is_expected.to be true + expect(subject).to be true expect(calculator.preferred_tiers).to eq({ BigDecimal('20.5') => BigDecimal('20.5') }) end end @@ -59,6 +66,8 @@ end describe "#compute" do + subject { calculator.compute(line_item) } + let(:order) do create( :order_with_line_items, @@ -78,46 +87,49 @@ calculator.preferred_currency = preferred_currency end - subject { calculator.compute(line_item) } - context "when amount falls within the first tier" do let(:amount) { 50 } + it { is_expected.to eq 10 } end context "when amount falls within the second tier" do let(:amount) { 150 } + it { is_expected.to eq 15 } end context "when the order's currency does not match the calculator" do let(:preferred_currency) { "CAD" } let(:amount) { 50 } + it { is_expected.to eq 0 } end context "with a shipment" do - let(:shipment) { Spree::Shipment.new(order: order, amount: shipping_cost) } - let(:amount) { 10 } - subject { calculator.compute(shipment) } + let(:shipment) { Spree::Shipment.new(order: order, amount: shipping_cost) } let(:line_item_count) { 1 } + let(:amount) { 10 } context "for multiple line items" do context "when amount falls within the first tier" do let(:shipping_cost) { 110 } + it { is_expected.to eq 15 } end context "when amount falls within the second tier" do let(:shipping_cost) { 210 } + it { is_expected.to eq 20 } end context "when the order's currency does not match the calculator" do let(:preferred_currency) { "CAD" } let(:shipping_cost) { 110 } + it { is_expected.to eq 0 } end end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_percent_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_percent_spec.rb index 02db8f2bfbe..73cd5fd6458 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_percent_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_percent_spec.rb @@ -13,61 +13,70 @@ context "when base percent is less than zero" do before { calculator.preferred_base_percent = -1 } + it { is_expected.to be false } end context "when base percent is greater than 100" do before { calculator.preferred_base_percent = 110 } + it { is_expected.to be false } end context "when tiers is a hash" do context "and the key is not a positive number" do before { calculator.preferred_tiers = { "nope" => 20 } } + it { is_expected.to be false } end context "and one of the values is not a percent" do before { calculator.preferred_tiers = { 10 => 110 } } + it { is_expected.to be false } end context "and the key is an integer" do before { calculator.preferred_tiers = { 20 => 20 } } + it "converts successfully" do - is_expected.to be true + expect(subject).to be true expect(calculator.preferred_tiers).to eq({ BigDecimal('20') => BigDecimal('20') }) end end context "and the key is a float" do before { calculator.preferred_tiers = { 20.5 => 20.5 } } + it "converts successfully" do - is_expected.to be true + expect(subject).to be true expect(calculator.preferred_tiers).to eq({ BigDecimal('20.5') => BigDecimal('20.5') }) end end context "and the key is a string number" do before { calculator.preferred_tiers = { "20" => 20 } } + it "converts successfully" do - is_expected.to be true + expect(subject).to be true expect(calculator.preferred_tiers).to eq({ BigDecimal('20') => BigDecimal('20') }) end end context "and the key is a numeric string with spaces" do before { calculator.preferred_tiers = { " 20 " => 20 } } + it "converts successfully" do - is_expected.to be true + expect(subject).to be true expect(calculator.preferred_tiers).to eq({ BigDecimal('20') => BigDecimal('20') }) end end context "and the key is a string number with decimals" do before { calculator.preferred_tiers = { "20.5" => "20.5" } } + it "converts successfully" do - is_expected.to be true + expect(subject).to be true expect(calculator.preferred_tiers).to eq({ BigDecimal('20.5') => BigDecimal('20.5') }) end end @@ -95,22 +104,26 @@ end context "with a line item" do - let(:line_item) { order.line_items.first } subject { calculator.compute(line_item) } + let(:line_item) { order.line_items.first } + context "for multiple line items" do context "when amount falls within the first tier" do let(:line_item_count) { 1 } + it { is_expected.to eq 1.0 } end context "when amount falls within the second tier" do let(:line_item_count) { 2 } + it { is_expected.to eq 1.5 } end context "when amount falls within the third tier" do let(:line_item_count) { 3 } + it { is_expected.to eq 2.0 } end end @@ -120,16 +133,19 @@ context "when amount falls within the first tier" do let(:price) { 10 } + it { is_expected.to eq 1.0 } end context "when amount falls within the second tier" do let(:price) { 20 } + it { is_expected.to eq 3.0 } end context "when amount falls within the third tier" do let(:price) { 30 } + it { is_expected.to eq 6.0 } end end @@ -138,6 +154,7 @@ let(:preferred_currency) { "JPY" } let(:line_item_count) { 1 } let(:price) { 15 } + it { is_expected.to eq 0 } it "rounds based on currency" do @@ -148,26 +165,28 @@ end context "with a shipment" do - let(:shipment) { Spree::Shipment.new(order: order, amount: shipping_cost) } - let(:shipping_cost) { 10 } - subject { calculator.compute(shipment) } + let(:shipment) { Spree::Shipment.new(order: order, amount: shipping_cost) } let(:line_item_count) { 1 } + let(:shipping_cost) { 10 } context "for multiple line items" do context "when amount falls within the first tier" do let(:line_item_count) { 1 } + it { is_expected.to eq 1.0 } end context "when amount falls within the second tier" do let(:line_item_count) { 2 } + it { is_expected.to eq 1.5 } end context "when amount falls within the third tier" do let(:line_item_count) { 3 } + it { is_expected.to eq 2.0 } end end @@ -177,24 +196,28 @@ context "when amount falls within the first tier" do let(:price) { 10 } + it { is_expected.to eq 1.0 } end context "when amount falls within the second tier" do let(:price) { 20 } let(:shipping_cost) { 20 } + it { is_expected.to eq 3.0 } end context "when amount falls within the third tier" do let(:price) { 30 } let(:shipping_cost) { 30 } + it { is_expected.to eq 6.0 } end end context "when the order's currency does not match the calculator" do let(:preferred_currency) { "CAD" } + it { is_expected.to eq 0 } end end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/friendly_promotion_discounter_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/friendly_promotion_discounter_spec.rb index e429c5c5e18..9965d27cb45 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/friendly_promotion_discounter_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/friendly_promotion_discounter_spec.rb @@ -4,13 +4,18 @@ RSpec.describe SolidusFriendlyPromotions::FriendlyPromotionDiscounter do describe "selecting promotions" do - let(:order) { create(:order) } subject { described_class.new(order) } + let(:order) { create(:order) } + let!(:active_promotion) { create(:friendly_promotion, :with_adjustable_action, apply_automatically: true) } - let!(:inactive_promotion) { create(:friendly_promotion, :with_adjustable_action, expires_at: 2.days.ago, apply_automatically: true) } + let!(:inactive_promotion) do + create(:friendly_promotion, :with_adjustable_action, expires_at: 2.days.ago, apply_automatically: true) + end let!(:connectable_promotion) { create(:friendly_promotion, :with_adjustable_action) } - let!(:connectable_inactive_promotion) { create(:friendly_promotion, :with_adjustable_action, expires_at: 2.days.ago) } + let!(:connectable_inactive_promotion) do + create(:friendly_promotion, :with_adjustable_action, expires_at: 2.days.ago) + end context "no promo is connected to the order" do it "checks only active promotions" do diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/item_discount_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/item_discount_spec.rb index 0fc1feacc31..1d53505d6e3 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/item_discount_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/item_discount_spec.rb @@ -2,7 +2,6 @@ require 'spec_helper' - RSpec.describe SolidusFriendlyPromotions::ItemDiscount do it { is_expected.to respond_to(:item) } it { is_expected.to respond_to(:source) } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/order_discounter_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/order_discounter_spec.rb index dbd6e4f76ac..43f2262f3f2 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/order_discounter_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/order_discounter_spec.rb @@ -3,15 +3,17 @@ require 'spec_helper' RSpec.describe SolidusFriendlyPromotions::OrderDiscounter, type: :model do + subject { described_class.new(order) } + let(:line_item) { create(:line_item) } let(:order) { line_item.order } let(:promotion) { create(:friendly_promotion, apply_automatically: true) } let(:calculator) { Spree::Calculator::FlatPercentItemTotal.new(preferred_flat_percent: 10) } - subject { described_class.new(order) } - context "adjusting line items" do - let!(:action) { SolidusFriendlyPromotions::Actions::AdjustLineItem.create(promotion: promotion, calculator: calculator) } + let!(:action) do + SolidusFriendlyPromotions::Actions::AdjustLineItem.create(promotion: promotion, calculator: calculator) + end let(:adjustable) { line_item } context "promotion with no rules" do @@ -41,7 +43,9 @@ end context "promotion includes item involved" do - let!(:rule) { SolidusFriendlyPromotions::Rules::Product.create(products: [line_item.product], promotion: promotion) } + let!(:rule) do + SolidusFriendlyPromotions::Rules::Product.create(products: [line_item.product], promotion: promotion) + end context "creates the adjustment" do it "creates the adjustment" do @@ -53,7 +57,13 @@ end context "promotion has item total rule" do - let!(:rule) { SolidusFriendlyPromotions::Rules::ItemTotal.create(preferred_operator: 'gt', preferred_amount: 50, promotion: promotion) } + let!(:rule) do + SolidusFriendlyPromotions::Rules::ItemTotal.create( + preferred_operator: 'gt', + preferred_amount: 50, + promotion: promotion + ) + end before do # Makes the order eligible for this promotion diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/order_promotion_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/order_promotion_spec.rb index b1d79ff4910..2a22422e019 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/order_promotion_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/order_promotion_spec.rb @@ -6,6 +6,7 @@ subject do order_promotion end + let(:promotion) { build(:friendly_promotion) } let(:order_promotion) { build(:friendly_order_promotion, promotion: promotion) } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_action_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_action_spec.rb index 757145a5050..20f580a0331 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_action_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_action_spec.rb @@ -18,17 +18,18 @@ end describe "#discount" do + subject { action.discount(adjustable) } + let(:variant) { create(:variant) } let(:order) { create(:order) } - let(:adjustable) { Spree::LineItem.new(order: order, variant: variant, price: 10)} + let(:adjustable) { Spree::LineItem.new(order: order, variant: variant, price: 10) } let(:promotion) { SolidusFriendlyPromotions::Promotion.new(name: "20 Perzent off") } - let(:action) { described_class.new(promotion: promotion)} + let(:action) { described_class.new(promotion: promotion) } + before do allow(action).to receive(:compute_amount).and_return(-1) end - subject { action.discount(adjustable) } - it "returs an discount to the adjustable" do expect(subject).to eq( SolidusFriendlyPromotions::ItemDiscount.new( diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_category_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_category_spec.rb index 25d2a5bfbb1..50ce646d63d 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_category_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_category_spec.rb @@ -6,15 +6,17 @@ it { is_expected.to have_many :promotions } describe 'validation' do - let(:name) { 'Nom' } subject { described_class.new name: name } + let(:name) { 'Nom' } + context 'when all required attributes are specified' do it { is_expected.to be_valid } end context 'when name is missing' do let(:name) { nil } + it { is_expected.not_to be_valid } end end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code/batch_builder_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code/batch_builder_spec.rb index b124f031cc7..cea4f8a78e5 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code/batch_builder_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code/batch_builder_spec.rb @@ -3,6 +3,8 @@ require "spec_helper" RSpec.describe SolidusFriendlyPromotions::PromotionCode::BatchBuilder do + subject { described_class.new(code_batch, options) } + let(:promotion) { create(:friendly_promotion) } let(:base_code) { "abc" } let(:options) { {} } @@ -16,8 +18,6 @@ ) end - subject { described_class.new(code_batch, options) } - describe "#build_promotion_codes" do context "with a failed build" do before do diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code_batch_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code_batch_spec.rb index ed0472183e1..1051afbe7c1 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code_batch_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code_batch_spec.rb @@ -15,12 +15,12 @@ describe "#process" do context "with a pending code batch" do - it "should call the worker" do + it "calls the worker" do expect { subject.process } .to have_enqueued_job(SolidusFriendlyPromotions::PromotionCodeBatchJob) end - it "should update the state to processing" do + it "updates the state to processing" do subject.process expect(subject.state).to eq("processing") @@ -30,7 +30,7 @@ context "with a processing batch" do before { subject.update_attribute(:state, "processing") } - it "should raise an error" do + it "raises an error" do expect{ subject.process }.to raise_error described_class::CantProcessStartedBatch end end @@ -38,7 +38,7 @@ context "with a completed batch" do before { subject.update_attribute(:state, "completed") } - it "should raise an error" do + it "raises an error" do expect{ subject.process }.to raise_error described_class::CantProcessStartedBatch end end @@ -46,7 +46,7 @@ context "with a failed batch" do before { subject.update_attribute(:state, "failed") } - it "should raise an error" do + it "raises an error" do expect{ subject.process }.to raise_error described_class::CantProcessStartedBatch end end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code_spec.rb index 81003cf63bf..755ecb8ddea 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code_spec.rb @@ -68,10 +68,13 @@ context "when there is a usage limit" do context "and the limit is not exceeded" do let(:usage_limit) { 10 } + it { is_expected.to be_falsy } end + context "and the limit is exceeded" do let(:usage_limit) { 1 } + context "on a different order" do before do FactoryBot.create( @@ -80,15 +83,19 @@ ) code.adjustments.update_all(eligible: true) end + it { is_expected.to be_truthy } end + context "on the same order" do it { is_expected.to be_falsy } end end end + context "when there is no usage limit" do let(:usage_limit) { nil } + it { is_expected.to be_falsy } end end @@ -110,6 +117,7 @@ promotion: promotion ) end + it_behaves_like "it should" end @@ -122,29 +130,39 @@ per_code_usage_limit: usage_limit, ) end + before do order.recalculate end + context "when there are multiple line items" do let(:order) { FactoryBot.create(:order_with_line_items, line_items_count: 2) } + describe "the first item" do let(:promotable) { order.line_items.first } + it_behaves_like "it should" end + describe "the second item" do let(:promotable) { order.line_items.last } + it_behaves_like "it should" end end + context "when there is a single line item" do let(:order) { FactoryBot.create(:order_with_line_items) } let(:promotable) { order.line_items.first } + it_behaves_like "it should" end end end describe "#usage_count" do + subject { code.usage_count } + let(:promotion) do FactoryBot.create( :friendly_promotion, @@ -154,9 +172,6 @@ end let(:code) { promotion.codes.first } - subject { code.usage_count } - - context "when the code is applied to a non-complete order" do let(:order) { FactoryBot.create(:order_with_line_items) } @@ -178,15 +193,20 @@ promotion: promotion ) end + context "and the promo is eligible" do it { is_expected.to eq 1 } end + context "and the promo is ineligible" do before { order.all_adjustments.update_all(eligible: false) } + it { is_expected.to eq 0 } end + context "and the order is canceled" do before { order.cancel! } + it { is_expected.to eq 0 } it { expect(order.state).to eq 'canceled' } end @@ -217,7 +237,7 @@ order.friendly_order_promotions.create!( order: order, promotion: promotion, - promotion_code: SolidusFriendlyPromotions::PromotionCode.find_by(value: "discount") + promotion_code: described_class.find_by(value: "discount") ) order.recalculate order.next! until order.can_complete? @@ -227,7 +247,7 @@ order.friendly_order_promotions.create!( order: order, promotion: promotion, - promotion_code: SolidusFriendlyPromotions::PromotionCode.find_by(value: "discount") + promotion_code: described_class.find_by(value: "discount") ) order.recalculate order.next! until order.can_complete? @@ -264,6 +284,7 @@ promotion = create(:friendly_promotion, apply_automatically: true) expect { create(:friendly_promotion_code, promotion: promotion) - }.to raise_error ActiveRecord::RecordInvalid, "Validation failed: Could not create promotion code on promotion that apply automatically" + }.to raise_error ActiveRecord::RecordInvalid, + "Validation failed: Could not create promotion code on promotion that apply automatically" end end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb index b60bcb9e93a..450ce485c45 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb @@ -9,7 +9,7 @@ it { is_expected.to have_many :rules } describe "validations" do - before :each do + before do @valid_promotion = described_class.new name: "A promotion" end @@ -43,13 +43,13 @@ end describe ".coupons" do + subject { described_class.coupons } + let(:promotion_code) { create(:friendly_promotion_code) } let!(:promotion_with_code) { promotion_code.promotion } let!(:another_promotion_code) { create(:friendly_promotion_code, promotion: promotion_with_code) } let!(:promotion_without_code) { create(:friendly_promotion) } - subject { described_class.coupons } - it "returns only distinct promotions with a code associated" do expect(subject).to eq [promotion_with_code] end @@ -109,13 +109,13 @@ context "when set to true" do before { subject.apply_automatically = true } - it "should remain valid" do + it "remains valid" do expect(subject).to be_valid end it "invalidates the promotion when it has a path" do subject.path = "foo" - expect(subject).to_not be_valid + expect(subject).not_to be_valid expect(subject.errors).to include(:apply_automatically) end end @@ -128,10 +128,13 @@ context "when there is a usage limit" do context "and the limit is not exceeded" do let(:usage_limit) { 10 } + it { is_expected.to be_falsy } end + context "and the limit is exceeded" do let(:usage_limit) { 1 } + context "on a different order" do before do FactoryBot.create( @@ -140,15 +143,19 @@ ) promotion.actions.first.adjustments.update_all(eligible: true) end + it { is_expected.to be_truthy } end + context "on the same order" do it { is_expected.to be_falsy } end end end + context "when there is no usage limit" do let(:usage_limit) { nil } + it { is_expected.to be_falsy } end end @@ -162,6 +169,7 @@ usage_limit: usage_limit ) end + before do order.friendly_order_promotions.create( promotion_code: promotion.codes.first, @@ -169,26 +177,35 @@ ) order.recalculate end + context "when there are multiple line items" do let(:order) { FactoryBot.create(:order_with_line_items, line_items_count: 2) } + describe "the first item" do let(:promotable) { order.line_items.first } + it_behaves_like "it should" end + describe "the second item" do let(:promotable) { order.line_items.last } + it_behaves_like "it should" end end + context "when there is a single line item" do let(:order) { FactoryBot.create(:order_with_line_items) } let(:promotable) { order.line_items.first } + it_behaves_like "it should" end end end describe "#usage_count" do + subject { promotion.usage_count } + let(:promotion) do FactoryBot.create( :friendly_promotion, @@ -197,10 +214,9 @@ ) end - subject { promotion.usage_count } - context "when the code is applied to a non-complete order" do let(:order) { FactoryBot.create(:order_with_line_items) } + before do order.friendly_order_promotions.create( promotion_code: promotion.codes.first, @@ -208,8 +224,10 @@ ) order.recalculate end + it { is_expected.to eq 0 } end + context "when the code is applied to a complete order" do let!(:order) do FactoryBot.create( @@ -217,50 +235,54 @@ promotion: promotion ) end + context "and the promo is eligible" do it { is_expected.to eq 1 } end + context "and the promo is ineligible" do before { order.all_adjustments.friendly_promotion.update_all(eligible: false) } + it { is_expected.to eq 0 } end + context "and the order is canceled" do before { order.cancel! } + it { is_expected.to eq 0 } it { expect(order.state).to eq 'canceled' } end end end - - context "#inactive" do + describe "#inactive" do let(:promotion) { create(:friendly_promotion, :with_adjustable_action) } - it "should not be exipired" do + it "is not exipired" do expect(promotion).not_to be_inactive end - it "should be inactive if it hasn't started yet" do + it "is inactive if it hasn't started yet" do promotion.starts_at = Time.current + 1.day expect(promotion).to be_inactive end - it "should be inactive if it has already ended" do + it "is inactive if it has already ended" do promotion.expires_at = Time.current - 1.day expect(promotion).to be_inactive end - it "should not be inactive if it has started already" do + it "is not inactive if it has started already" do promotion.starts_at = Time.current - 1.day expect(promotion).not_to be_inactive end - it "should not be inactive if it has not ended yet" do + it "is not inactive if it has not ended yet" do promotion.expires_at = Time.current + 1.day expect(promotion).not_to be_inactive end - it "should not be inactive if current time is within starts_at and expires_at range" do + it "is not inactive if current time is within starts_at and expires_at range" do promotion.starts_at = Time.current - 1.day promotion.expires_at = Time.current + 1.day expect(promotion).not_to be_inactive @@ -268,103 +290,119 @@ end describe '#not_started?' do - let(:promotion) { SolidusFriendlyPromotions::Promotion.new(starts_at: starts_at) } subject { promotion.not_started? } + let(:promotion) { described_class.new(starts_at: starts_at) } + context 'no starts_at date' do let(:starts_at) { nil } + it { is_expected.to be_falsey } end context 'when starts_at date is in the past' do let(:starts_at) { Time.current - 1.day } + it { is_expected.to be_falsey } end context 'when starts_at date is not already reached' do let(:starts_at) { Time.current + 1.day } + it { is_expected.to be_truthy } end end describe '#started?' do - let(:promotion) { SolidusFriendlyPromotions::Promotion.new(starts_at: starts_at) } subject { promotion.started? } + let(:promotion) { described_class.new(starts_at: starts_at) } + context 'when no starts_at date' do let(:starts_at) { nil } + it { is_expected.to be_truthy } end context 'when starts_at date is in the past' do let(:starts_at) { Time.current - 1.day } + it { is_expected.to be_truthy } end context 'when starts_at date is not already reached' do let(:starts_at) { Time.current + 1.day } + it { is_expected.to be_falsey } end end describe '#expired?' do - let(:promotion) { SolidusFriendlyPromotions::Promotion.new(expires_at: expires_at) } subject { promotion.expired? } + let(:promotion) { described_class.new(expires_at: expires_at) } + context 'when no expires_at date' do let(:expires_at) { nil } + it { is_expected.to be_falsey } end context 'when expires_at date is not already reached' do let(:expires_at) { Time.current + 1.day } + it { is_expected.to be_falsey } end context 'when expires_at date is in the past' do let(:expires_at) { Time.current - 1.day } + it { is_expected.to be_truthy } end end describe '#not_expired?' do - let(:promotion) { SolidusFriendlyPromotions::Promotion.new(expires_at: expires_at) } subject { promotion.not_expired? } + let(:promotion) { described_class.new(expires_at: expires_at) } + context 'when no expired_at date' do let(:expires_at) { nil } + it { is_expected.to be_truthy } end context 'when expires_at date is not already reached' do let(:expires_at) { Time.current + 1.day } + it { is_expected.to be_truthy } end context 'when expires_at date is in the past' do let(:expires_at) { Time.current - 1.day } + it { is_expected.to be_falsey } end end - context "#active" do - it "shouldn't be active if it has started already" do + describe "#active" do + it "is not active if it has started already" do promotion.starts_at = Time.current - 1.day expect(promotion.active?).to eq(false) end - it "shouldn't be active if it has not ended yet" do + it "is not active if it has not ended yet" do promotion.expires_at = Time.current + 1.day expect(promotion.active?).to eq(false) end - it "shouldn't be active if current time is within starts_at and expires_at range" do + it "is not active if current time is within starts_at and expires_at range" do promotion.starts_at = Time.current - 1.day promotion.expires_at = Time.current + 1.day expect(promotion.active?).to eq(false) end - it "shouldn't be active if there are no start and end times set" do + it "is not active if there are no start and end times set" do promotion.starts_at = nil promotion.expires_at = nil expect(promotion.active?).to eq(false) @@ -373,23 +411,23 @@ context 'when promotion has an action' do let(:promotion) { create(:promotion, :with_action, name: "name1") } - it "should be active if it has started already" do + it "is active if it has started already" do promotion.starts_at = Time.current - 1.day expect(promotion.active?).to eq(true) end - it "should be active if it has not ended yet" do + it "is active if it has not ended yet" do promotion.expires_at = Time.current + 1.day expect(promotion.active?).to eq(true) end - it "should be active if current time is within starts_at and expires_at range" do + it "is active if current time is within starts_at and expires_at range" do promotion.starts_at = Time.current - 1.day promotion.expires_at = Time.current + 1.day expect(promotion.active?).to eq(true) end - it "should be active if there are no start and end times set" do + it "is active if there are no start and end times set" do promotion.starts_at = nil promotion.expires_at = nil expect(promotion.active?).to eq(true) @@ -397,7 +435,7 @@ end end - context "#products" do + describe "#products" do let(:promotion) { create(:friendly_promotion) } context "when it has product rules with products associated" do @@ -409,13 +447,13 @@ promotion_rule.save end - it "should have products" do + it "has products" do expect(promotion.reload.products.size).to eq(1) end end context "when there's no product rule associated" do - it "should not have products but still return an empty array" do + it "does not have products but still return an empty array" do expect(promotion.products).to be_blank end end @@ -469,6 +507,7 @@ context 'when the only matching order is the excluded order' do let(:excluded_order) { order } + it { is_expected.to be false } end end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_order_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_order_spec.rb index bc6c2e82ba8..b9997ae30e9 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_order_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_order_spec.rb @@ -3,17 +3,19 @@ require "spec_helper" RSpec.describe SolidusFriendlyPromotions::Rules::FirstOrder, type: :model do - let(:rule) { SolidusFriendlyPromotions::Rules::FirstOrder.new } + let(:rule) { described_class.new } let(:order) { mock_model(Spree::Order, user: nil, email: nil) } let(:user) { mock_model(Spree::LegacyUser) } describe ".to_partial_path" do subject { rule.to_partial_path } + it { is_expected.to eq("solidus_friendly_promotions/admin/promotion_rules/rules/first_order") } end context "without a user or email" do it { expect(rule).to be_eligible(order) } + it "does not set an error message" do rule.eligible?(order) expect(rule.eligibility_errors.full_messages.first) @@ -24,7 +26,7 @@ context "first order" do context "for a signed user" do context "with no completed orders" do - before(:each) do + before do allow(user).to receive_message_chain(:orders, complete: []) end @@ -33,29 +35,32 @@ expect(rule).to be_eligible(order) end - it "should be eligible when user passed in payload data" do + it "is eligible when user passed in payload data" do expect(rule).to be_eligible(order, user: user) end end context "with completed orders" do - before(:each) do + before do allow(order).to receive_messages(user: user) end - it "should be eligible when checked against first completed order" do + it "is eligible when checked against first completed order" do allow(user).to receive_message_chain(:orders, complete: [order]) expect(rule).to be_eligible(order) end context "with another order" do before { allow(user).to receive_message_chain(:orders, complete: [mock_model(Spree::Order)]) } + it { expect(rule).not_to be_eligible(order) } + it "sets an error message" do rule.eligible?(order) expect(rule.eligibility_errors.full_messages.first) .to eq "This coupon code can only be applied to your first order." end + it "sets an error code" do rule.eligible?(order) expect(rule.eligibility_errors.details[:base].first[:error_code]) @@ -67,6 +72,7 @@ context "for a guest user" do let(:email) { "user@solidus.io" } + before { allow(order).to receive_messages email: "user@solidus.io" } context "with no other orders" do @@ -75,12 +81,15 @@ context "with another order" do before { allow(rule).to receive_messages(orders_by_email: [mock_model(Spree::Order)]) } + it { expect(rule).not_to be_eligible(order) } + it "sets an error message" do rule.eligible?(order) expect(rule.eligibility_errors.full_messages.first) .to eq "This coupon code can only be applied to your first order." end + it "sets an error code" do rule.eligible?(order) expect(rule.eligibility_errors.details[:base].first[:error_code]) diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_repeat_purchase_since_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_repeat_purchase_since_spec.rb index 33dd9487c7f..e23fd17e3a3 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_repeat_purchase_since_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_repeat_purchase_since_spec.rb @@ -20,9 +20,10 @@ end describe "eligible?" do - let(:instance) { described_class.new } subject { instance.eligible?(order) } + let(:instance) { described_class.new } + before do instance.preferred_days_ago = 365 end @@ -40,6 +41,7 @@ context "when the user has completed orders" do let(:order_completion_date_1) { 1.day.ago } let(:order_completion_date_2) { 1.day.ago } + before do old_order_1 = create :completed_order_with_totals, user: user old_order_1.update(completed_at: order_completion_date_1) diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/item_total_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/item_total_spec.rb index 4c396723103..73da9a6bab5 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/item_total_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/item_total_spec.rb @@ -4,7 +4,7 @@ RSpec.describe SolidusFriendlyPromotions::Rules::ItemTotal, type: :model do let(:rule) do - SolidusFriendlyPromotions::Rules::ItemTotal.new( + described_class.new( preferred_amount: preferred_amount, preferred_operator: preferred_operator ) @@ -19,7 +19,7 @@ context "item total is greater than preferred amount" do let(:item_total) { 51 } - it "should be eligible when item total is greater than preferred amount" do + it "is eligible when item total is greater than preferred amount" do expect(rule).to be_eligible(order) end @@ -44,6 +44,7 @@ expect(rule.eligibility_errors.full_messages.first) .to eq "This coupon code can't be applied to orders less than or equal to $50.00." end + it "sets an error code" do rule.eligible?(order) expect(rule.eligibility_errors.details[:base].first[:error_code]) @@ -63,6 +64,7 @@ expect(rule.eligibility_errors.full_messages.first) .to eq "This coupon code can't be applied to orders less than or equal to $50.00." end + it "sets an error code" do rule.eligible?(order) expect(rule.eligibility_errors.details[:base].first[:error_code]) @@ -77,7 +79,7 @@ context "total is greater than preferred amount" do let(:item_total) { 51 } - it "should be eligible when item total is greater than preferred amount" do + it "is eligible when item total is greater than preferred amount" do expect(rule).to be_eligible(order) end @@ -93,7 +95,7 @@ context "item total is equal to preferred amount" do let(:item_total) { 50 } - it "should be eligible" do + it "is eligible" do expect(rule).to be_eligible(order) end @@ -118,6 +120,7 @@ expect(rule.eligibility_errors.full_messages.first) .to eq "This coupon code can't be applied to orders less than $50.00." end + it "sets an error code" do rule.eligible?(order) expect(rule.eligibility_errors.details[:base].first[:error_code]) diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/line_item_product_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/line_item_product_spec.rb index ee5d634c574..53afccbb610 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/line_item_product_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/line_item_product_spec.rb @@ -6,7 +6,9 @@ let(:rule) { described_class.new(rule_options) } let(:rule_options) { {} } - context "#eligible?(line_item)" do + describe "#eligible?(line_item)" do + subject { rule.eligible?(line_item, {}) } + let(:rule_line_item) { Spree::LineItem.new(product: rule_product) } let(:other_line_item) { Spree::LineItem.new(product: other_product) } @@ -14,19 +16,19 @@ let(:rule_product) { mock_model(Spree::Product) } let(:other_product) { mock_model(Spree::Product) } - it "should be eligible if there are no products" do + it "is eligible if there are no products" do expect(rule).to be_eligible(rule_line_item) end - subject { rule.eligible?(line_item, {}) } - context "for product in rule" do let(:line_item) { rule_line_item } + it { is_expected.to be_truthy } end context "for product not in rule" do let(:line_item) { other_line_item } + it { is_expected.to be_falsey } end @@ -35,11 +37,13 @@ context "for product in rule" do let(:line_item) { rule_line_item } + it { is_expected.to be_falsey } end context "for product not in rule" do let(:line_item) { other_line_item } + it { is_expected.to be_truthy } end end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/nth_order_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/nth_order_spec.rb index e478a3630cc..d2464d35286 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/nth_order_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/nth_order_spec.rb @@ -20,9 +20,10 @@ end describe "eligible?" do - let(:instance) { described_class.new } subject { instance.eligible?(order) } + let(:instance) { described_class.new } + before do instance.preferred_nth_order = 2 end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/one_use_per_user_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/one_use_per_user_spec.rb index d04bf2c16d5..b04f8717849 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/one_use_per_user_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/one_use_per_user_spec.rb @@ -7,6 +7,7 @@ describe "#eligible?(order)" do subject { rule.eligible?(order) } + let(:order) { double Spree::Order, user: user } let(:user) { double Spree::LegacyUser } let(:promotion) { stub_model SolidusFriendlyPromotions::Promotion, used_by?: used_by } @@ -19,11 +20,13 @@ let(:used_by) { true } it { is_expected.to be false } + it "sets an error message" do subject expect(rule.eligibility_errors.full_messages.first) .to eq "This coupon code can only be used once per user." end + it "sets an error code" do rule.eligible?(order) expect(rule.eligibility_errors.details[:base].first[:error_code]) @@ -38,12 +41,15 @@ context "when the order is not assigned to a user" do let(:user) { nil } + it { is_expected.to be false } + it "sets an error message" do subject expect(rule.eligibility_errors.full_messages.first) .to eq "You need to login before applying this coupon code." end + it "sets an error code" do rule.eligible?(order) expect(rule.eligibility_errors.details[:base].first[:error_code]) diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/option_value_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/option_value_spec.rb index 913944f2bee..c8078873ac8 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/option_value_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/option_value_spec.rb @@ -3,45 +3,55 @@ require "spec_helper" RSpec.describe SolidusFriendlyPromotions::Rules::OptionValue do - let(:rule) { SolidusFriendlyPromotions::Rules::OptionValue.new } + let(:rule) { described_class.new } describe "#preferred_eligible_values" do subject { rule.preferred_eligible_values } + it "assigns a nicely formatted hash" do - rule.preferred_eligible_values= {"5" => "1,2", "6" => "1"} - expect(subject).to eq({5 => [1, 2], 6 => [1]}) + rule.preferred_eligible_values = { "5" => "1,2", "6" => "1" } + expect(subject).to eq({ 5 => [1, 2], 6 => [1] }) end end describe "#applicable?" do subject { rule.applicable?(promotable) } + context "when promotable is an order" do let(:promotable) { Spree::Order.new } + it { is_expected.to be true } end + context "when promotable is not an order" do let(:promotable) { Spree::LineItem.new } + it { is_expected.to be false } end end describe "#eligible?" do + subject { rule.eligible?(promotable) } + let(:variant) { create :variant } let(:line_item) { create :line_item, variant: variant } let(:promotable) { line_item.order } - subject { rule.eligible?(promotable) } + context "when there are any applicable line items" do before do - rule.preferred_eligible_values= {line_item.product.id => [ - line_item.variant.option_values.pluck(:id).first - ]} + rule.preferred_eligible_values = { line_item.product.id => [ + line_item.variant.option_values.pick(:id) + ] } end + it { is_expected.to be true } end + context "when there are no applicable line items" do before do - rule.preferred_eligible_values= {99 => [99]} + rule.preferred_eligible_values = { 99 => [99] } end + it { is_expected.to be false } end end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/product_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/product_spec.rb index 1f5c574e77f..b8fffa7429c 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/product_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/product_spec.rb @@ -3,27 +3,27 @@ require "spec_helper" RSpec.describe SolidusFriendlyPromotions::Rules::Product, type: :model do - it { is_expected.to have_many(:products) } - - let(:rule) { described_class.new(rule_options) } let(:rule_options) { {} } + let(:rule) { described_class.new(rule_options) } - context "#eligible?(order)" do - let(:order) { Spree::Order.new } + it { is_expected.to have_many(:products) } - it "should be eligible if there are no products" do - allow(rule).to receive_messages(eligible_products: []) - expect(rule).to be_eligible(order) - end + describe "#eligible?(order)" do + let(:order) { Spree::Order.new } before do 3.times { |i| instance_variable_set("@product#{i}", mock_model(Spree::Product)) } end + it "is eligible if there are no products" do + allow(rule).to receive_messages(eligible_products: []) + expect(rule).to be_eligible(order) + end + context "with 'any' match policy" do let(:rule_options) { super().merge(preferred_match_policy: "any") } - it "should be eligible if any of the products is in eligible products" do + it "is eligible if any of the products is in eligible products" do allow(rule).to receive_messages(order_products: [@product1, @product2]) allow(rule).to receive_messages(eligible_products: [@product2, @product3]) expect(rule).to be_eligible(order) @@ -34,12 +34,15 @@ allow(rule).to receive_messages(order_products: [@product1]) allow(rule).to receive_messages(eligible_products: [@product2, @product3]) end + it { expect(rule).not_to be_eligible(order) } + it "sets an error message" do rule.eligible?(order) expect(rule.eligibility_errors.full_messages.first) .to eq "You need to add an applicable product before applying this coupon code." end + it "sets an error code" do rule.eligible?(order) expect(rule.eligibility_errors.details[:base].first[:error_code]) @@ -51,7 +54,7 @@ context "with 'all' match policy" do let(:rule_options) { super().merge(preferred_match_policy: "all") } - it "should be eligible if all of the eligible products are ordered" do + it "is eligible if all of the eligible products are ordered" do allow(rule).to receive_messages(order_products: [@product3, @product2, @product1]) allow(rule).to receive_messages(eligible_products: [@product2, @product3]) expect(rule).to be_eligible(order) @@ -62,12 +65,15 @@ allow(rule).to receive_messages(order_products: [@product1, @product2]) allow(rule).to receive_messages(eligible_products: [@product1, @product2, @product3]) end + it { expect(rule).not_to be_eligible(order) } + it "sets an error message" do rule.eligible?(order) expect(rule.eligibility_errors.full_messages.first) .to eq "This coupon code can't be applied because you don't have all of the necessary products in your cart." end + it "sets an error code" do rule.eligible?(order) expect(rule.eligibility_errors.details[:base].first[:error_code]) @@ -79,7 +85,7 @@ context "with 'none' match policy" do let(:rule_options) { super().merge(preferred_match_policy: "none") } - it "should be eligible if none of the order's products are in eligible products" do + it "is eligible if none of the order's products are in eligible products" do allow(rule).to receive_messages(order_products: [@product1]) allow(rule).to receive_messages(eligible_products: [@product2, @product3]) expect(rule).to be_eligible(order) @@ -90,12 +96,15 @@ allow(rule).to receive_messages(order_products: [@product1, @product2]) allow(rule).to receive_messages(eligible_products: [@product2, @product3]) end + it { expect(rule).not_to be_eligible(order) } + it "sets an error message" do rule.eligible?(order) expect(rule.eligibility_errors.full_messages.first) .to eq "Your cart contains a product that prevents this coupon code from being applied." end + it "sets an error code" do rule.eligible?(order) expect(rule.eligibility_errors.details[:base].first[:error_code]) @@ -106,7 +115,7 @@ context "with an invalid match policy" do let(:rule) do - SolidusFriendlyPromotions::Rules::Product.create!( + described_class.create!( promotion: create(:friendly_promotion), products_promotion_rules: [ SolidusFriendlyPromotions::ProductsPromotionRule.new(product: product) diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/shipping_method_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/shipping_method_spec.rb index 1ee9f66b5ed..52d0d782776 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/shipping_method_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/shipping_method_spec.rb @@ -3,18 +3,18 @@ require "spec_helper" RSpec.describe SolidusFriendlyPromotions::Rules::ShippingMethod, type: :model do - it { is_expected.to respond_to(:preferred_shipping_method_ids) } - let(:rule) { described_class.new } + it { is_expected.to respond_to(:preferred_shipping_method_ids) } + describe "preferred_shipping_methods_ids=" do + subject { rule.preferred_shipping_method_ids = [ups_ground.id] } + let!(:promotion) { create(:friendly_promotion) } let(:ups_ground) { create(:shipping_method) } let(:dhl_saver) { create(:shipping_method) } let(:rule) { promotion.rules.build(type: described_class.to_s) } - subject { rule.preferred_shipping_method_ids = [ups_ground.id] } - it "creates a valid rule with a shipping method" do subject expect(rule).to be_valid @@ -23,13 +23,13 @@ end describe "#eligible?" do + subject { rule.eligible?(promotable) } + let!(:promotion) { create(:friendly_promotion) } let(:ups_ground) { create(:shipping_method) } let(:dhl_saver) { create(:shipping_method) } let(:rule) { promotion.rules.build(type: described_class.to_s, preferred_shipping_method_ids: [ups_ground.id]) } - subject { rule.eligible?(promotable) } - context "with a shipment" do context "when the shipment has the right shipping method selected" do let(:promotable) { create(:shipment, shipping_method: ups_ground) } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/store_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/store_spec.rb index 7f4d9d2139b..4a2eb9fd13f 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/store_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/store_spec.rb @@ -3,18 +3,18 @@ require "spec_helper" RSpec.describe SolidusFriendlyPromotions::Rules::Store, type: :model do - it { is_expected.to have_many(:stores) } - let(:rule) { described_class.new } + it { is_expected.to have_many(:stores) } + describe "store_ids=" do + subject { rule.store_ids = [store.id] } + let!(:promotion) { create(:friendly_promotion) } let!(:unimportant_store) { create(:store) } let!(:store) { create(:store) } let(:rule) { promotion.rules.build(type: described_class.to_s) } - subject { rule.store_ids = [store.id] } - it "creates a valid rule with a store" do subject expect(rule).to be_valid @@ -22,7 +22,7 @@ end end - context "#eligible?(order)" do + describe "#eligible?(order)" do let(:order) { Spree::Order.new } it "is eligible if no stores are provided" do diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/taxon_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/taxon_spec.rb index 4c237479243..7b2f635cbd2 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/taxon_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/taxon_spec.rb @@ -3,25 +3,24 @@ require "spec_helper" RSpec.describe SolidusFriendlyPromotions::Rules::Taxon, type: :model do - it { is_expected.to have_many(:taxons) } - - let(:taxon) { create :taxon, name: "first" } - let(:taxon2) { create :taxon, name: "second" } - let(:order) { create :order_with_line_items } - let(:product) { order.products.first } - let(:rule) do - SolidusFriendlyPromotions::Rules::Taxon.create!(promotion: create(:friendly_promotion)) + described_class.create!(promotion: create(:friendly_promotion)) end + let(:product) { order.products.first } + let(:order) { create :order_with_line_items } + let(:taxon2) { create :taxon, name: "second" } + let(:taxon) { create :taxon, name: "first" } + + it { is_expected.to have_many(:taxons) } describe "taxon_ids_string=" do + subject { rule.assign_attributes("taxon_ids_string" => taxon_2.id.to_s) } + let!(:promotion) { create(:friendly_promotion) } let!(:taxon_1) { create(:taxon) } let!(:taxon_2) { create(:taxon) } let(:rule) { promotion.rules.build(type: described_class.to_s) } - subject { rule.assign_attributes("taxon_ids_string" => taxon_2.id.to_s) } - it "creates a valid rule with a taxon" do subject expect(rule).to be_valid @@ -30,7 +29,7 @@ end end - context "#eligible?(order)" do + describe "#eligible?(order)" do context "with any match policy" do before do rule.update!(preferred_match_policy: "any") @@ -44,12 +43,15 @@ context "when order does not have any prefered taxon" do before { rule.taxons << taxon2 } + it { expect(rule).not_to be_eligible(order) } + it "sets an error message" do rule.eligible?(order) expect(rule.eligibility_errors.full_messages.first) .to eq "You need to add a product from an applicable category before applying this coupon code." end + it "sets an error code" do rule.eligible?(order) expect(rule.eligibility_errors.details[:base].first[:error_code]) @@ -84,12 +86,15 @@ context "when order does not have all prefered taxons" do before { rule.taxons << taxon } + it { expect(rule).not_to be_eligible(order) } + it "sets an error message" do rule.eligible?(order) expect(rule.eligibility_errors.full_messages.first) .to eq "You need to add a product from all applicable categories before applying this coupon code." end + it "sets an error code" do rule.eligible?(order) expect(rule.eligibility_errors.details[:base].first[:error_code]) @@ -120,6 +125,7 @@ context "none of the order's products are in listed taxon" do before { rule.taxons << taxon2 } + it { expect(rule).to be_eligible(order) } end @@ -128,14 +134,17 @@ order.products.first.taxons << taxon rule.taxons << taxon end - it "should not be eligible" do + + it "is not eligible" do expect(rule).not_to be_eligible(order) end + it "sets an error message" do rule.eligible?(order) expect(rule.eligibility_errors.full_messages.first) .to eq "Your cart contains a product from an excluded category that prevents this coupon code from being applied." end + it "sets an error code" do rule.eligible?(order) expect(rule.eligibility_errors.details[:base].first[:error_code]) diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_logged_in_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_logged_in_spec.rb index 4147c5d4bd0..4901483a5bb 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_logged_in_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_logged_in_spec.rb @@ -3,12 +3,12 @@ require "spec_helper" RSpec.describe SolidusFriendlyPromotions::Rules::UserLoggedIn, type: :model do - let(:rule) { SolidusFriendlyPromotions::Rules::UserLoggedIn.new } + let(:rule) { described_class.new } - context "#eligible?(order)" do + describe "#eligible?(order)" do let(:order) { Spree::Order.new } - it "should be eligible if order has an associated user" do + it "is eligible if order has an associated user" do user = double("User") allow(order).to receive_messages(user: user) @@ -17,12 +17,15 @@ context "when user is not logged in" do before { allow(order).to receive_messages(user: nil) } # better to be explicit here + it { expect(rule).not_to be_eligible(order) } + it "sets an error message" do rule.eligible?(order) expect(rule.eligibility_errors.full_messages.first) .to eq "You need to login before applying this coupon code." end + it "sets an error code" do rule.eligible?(order) expect(rule.eligibility_errors.details[:base].first[:error_code]) diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_role_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_role_spec.rb index 49c6f0060bc..36cabae0e42 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_role_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_role_spec.rb @@ -3,51 +3,55 @@ require "spec_helper" RSpec.describe SolidusFriendlyPromotions::Rules::UserRole, type: :model do + subject { rule } + let(:rule) { described_class.new(preferred_role_ids: roles_for_rule.map(&:id)) } let(:user) { create(:user, spree_roles: roles_for_user) } let(:roles_for_rule) { [] } let(:roles_for_user) { [] } - subject { rule } - shared_examples "eligibility" do context "no roles on rule" do - let(:roles_for_user) { [create(:role)] } - it "should not be eligible" do - expect(rule).to_not be_eligible(order) + let(:roles_for_user) { create_list(:role, 1) } + + it "is not eligible" do + expect(rule).not_to be_eligible(order) end end context "no roles on user" do - let(:roles_for_rule) { [create(:role)] } - it "should not be eligible" do - expect(rule).to_not be_eligible(order) + let(:roles_for_rule) { create_list(:role, 1) } + + it "is not eligible" do + expect(rule).not_to be_eligible(order) end end context "mismatched roles" do - let(:roles_for_user) { [create(:role)] } - let(:roles_for_rule) { [create(:role)] } - it "should not be eligible" do - expect(rule).to_not be_eligible(order) + let(:roles_for_user) { create_list(:role, 1) } + let(:roles_for_rule) { create_list(:role, 1) } + + it "is not eligible" do + expect(rule).not_to be_eligible(order) end end context "matching all roles" do - let(:roles_for_user) { [create(:role), create(:role)] } + let(:roles_for_user) { create_list(:role, 2) } let(:roles_for_rule) { roles_for_user } - it "should be eligible" do + + it "is eligible" do expect(rule).to be_eligible(order) end end end - context "#eligible?(order)" do + describe "#eligible?(order)" do context "order with no user" do let(:order) { Spree::Order.new } - it "should not be eligible" do - expect(rule).to_not be_eligible(order) + it "is not eligible" do + expect(rule).not_to be_eligible(order) end end @@ -63,7 +67,8 @@ let(:shared_role) { create(:role) } let(:roles_for_user) { [create(:role), shared_role] } let(:roles_for_rule) { [create(:role), shared_role] } - it "should be eligible" do + + it "is eligible" do expect(rule).to be_eligible(order) end end @@ -78,8 +83,9 @@ let(:shared_role) { create(:role) } let(:roles_for_user) { [create(:role), shared_role] } let(:roles_for_rule) { [create(:role), shared_role] } - it "should not be eligible" do - expect(rule).to_not be_eligible(order) + + it "is not eligible" do + expect(rule).not_to be_eligible(order) end end end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_spec.rb index 7f38bf41da5..087009f1150 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_spec.rb @@ -3,29 +3,30 @@ require "spec_helper" RSpec.describe SolidusFriendlyPromotions::Rules::User, type: :model do - it { is_expected.to have_many(:users) } let(:rule) { described_class.new } + it { is_expected.to have_many(:users) } + describe "user_ids=" do + subject { rule.user_ids = [user.id] } + let(:promotion) { create(:friendly_promotion) } let(:user) { create(:user) } let(:rule) { promotion.rules.new } - subject { rule.user_ids = [user.id] } - it "creates a valid rule with a user" do expect(rule).to be_valid end end - context "#eligible?(order)" do + describe "#eligible?(order)" do let(:order) { Spree::Order.new } - it "should not be eligible if users are not provided" do + it "is not eligible if users are not provided" do expect(rule).not_to be_eligible(order) end - it "should be eligible if users include user placing the order" do + it "is eligible if users include user placing the order" do user = mock_model(Spree::LegacyUser) users = [user, mock_model(Spree::LegacyUser)] allow(rule).to receive_messages(users: users) @@ -34,7 +35,7 @@ expect(rule).to be_eligible(order) end - it "should not be eligible if user placing the order is not listed" do + it "is not eligible if user placing the order is not listed" do allow(order).to receive_messages(user: mock_model(Spree::LegacyUser)) users = [mock_model(Spree::LegacyUser), mock_model(Spree::LegacyUser)] allow(rule).to receive_messages(users: users) diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/simple_order_contents_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/simple_order_contents_spec.rb index 90a287f2bf7..bd3c248d4eb 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/simple_order_contents_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/simple_order_contents_spec.rb @@ -3,17 +3,17 @@ require 'spec_helper' RSpec.describe SolidusFriendlyPromotions::SimpleOrderContents, type: :model do + subject(:order_contents) { described_class.new(order) } + let!(:store) { create :store } let(:order) { create(:order) } let(:variant) { create(:variant) } let!(:stock_location) { variant.stock_locations.first } let(:stock_location_2) { create(:stock_location) } - subject(:order_contents) { described_class.new(order) } - - context "#add" do + describe "#add" do context 'given quantity is not explicitly provided' do - it 'should add one line item' do + it 'adds one line item' do line_item = subject.add(variant) expect(line_item.quantity).to eq(1) expect(order.line_items.size).to eq(1) @@ -24,7 +24,7 @@ let!(:shipment) { create(:shipment, order: order) } it "ensure shipment calls update_amounts instead of order calling check_shipments_and_restart_checkout" do - expect(subject.order).to_not receive(:check_shipments_and_restart_checkout) + expect(subject.order).not_to receive(:check_shipments_and_restart_checkout) expect(shipment).to receive(:update_amounts).at_least(:once) subject.add(variant, 1, shipment: shipment) end @@ -59,20 +59,20 @@ end end - it 'should add line item if one does not exist' do + it 'adds line item if one does not exist' do line_item = subject.add(variant, 1) expect(line_item.quantity).to eq(1) expect(order.line_items.size).to eq(1) end - it 'should update line item if one exists' do + it 'updates line item if one exists' do subject.add(variant, 1) line_item = subject.add(variant, 1) expect(line_item.quantity).to eq(2) expect(order.line_items.size).to eq(1) end - it "should update order totals" do + it "updates order totals" do expect(order.item_total.to_f).to eq(0.00) expect(order.total.to_f).to eq(0.00) @@ -87,7 +87,9 @@ let(:calculator) { SolidusFriendlyPromotions::Calculators::FlatRate.new(preferred_amount: 10) } context "one active line item promotion" do - let!(:action) { SolidusFriendlyPromotions::Actions::AdjustLineItem.create(promotion: promotion, calculator: calculator) } + let!(:action) do + SolidusFriendlyPromotions::Actions::AdjustLineItem.create(promotion: promotion, calculator: calculator) + end it "creates valid discount on order" do subject.add(variant, 1) @@ -97,6 +99,7 @@ context "discount changes order total" do before { subject.add(variant, 1) } + it { expect(subject.order.total).not_to eq variant.price } end end @@ -135,7 +138,7 @@ end end - context "#remove" do + describe "#remove" do context "given an invalid variant" do it "raises an exception" do expect { @@ -145,7 +148,7 @@ end context 'given quantity is not explicitly provided' do - it 'should remove one line item' do + it 'removes one line item' do line_item = subject.add(variant, 3) subject.remove(variant) @@ -157,7 +160,7 @@ it "ensure shipment calls update_amounts instead of order calling check_shipments_and_restart_checkout" do subject.add(variant, 1) shipment = create(:shipment) - expect(subject.order).to_not receive(:check_shipments_and_restart_checkout) + expect(subject.order).not_to receive(:check_shipments_and_restart_checkout) expect(shipment).to receive(:update_amounts) subject.remove(variant, 1, shipment: shipment) end @@ -171,28 +174,28 @@ end end - it 'should reduce line_item quantity if quantity is less the line_item quantity' do + it 'reduces line_item quantity if quantity is less the line_item quantity' do line_item = subject.add(variant, 3) subject.remove(variant, 1) expect(line_item.reload.quantity).to eq(2) end - it 'should remove line_item if quantity matches line_item quantity' do + it 'removes line_item if quantity matches line_item quantity' do subject.add(variant, 1) subject.remove(variant, 1) expect(order.reload.find_line_item_by_variant(variant)).to be_nil end - it 'should remove line_item if quantity is greater than line_item quantity' do + it 'removes line_item if quantity is greater than line_item quantity' do subject.add(variant, 1) subject.remove(variant, 2) expect(order.reload.find_line_item_by_variant(variant)).to be_nil end - it "should update order totals" do + it "updates order totals" do expect(order.item_total.to_f).to eq(0.00) expect(order.total.to_f).to eq(0.00) @@ -207,12 +210,12 @@ end end - context "#remove_line_item" do + describe "#remove_line_item" do context 'given a shipment' do it "ensure shipment calls update_amounts instead of order calling check_shipments_and_restart_checkout" do line_item = subject.add(variant, 1) shipment = create(:shipment) - expect(subject.order).to_not receive(:check_shipments_and_restart_checkout) + expect(subject.order).not_to receive(:check_shipments_and_restart_checkout) expect(shipment).to receive(:update_amounts) subject.remove_line_item(line_item, shipment: shipment) end @@ -226,14 +229,14 @@ end end - it 'should remove line_item' do + it 'removes line_item' do line_item = subject.add(variant, 1) subject.remove_line_item(line_item) - expect(order.reload.line_items).to_not include(line_item) + expect(order.reload.line_items).not_to include(line_item) end - it "should update order totals" do + it "updates order totals" do expect(order.item_total.to_f).to eq(0.00) expect(order.total.to_f).to eq(0.00) @@ -265,7 +268,7 @@ it "updates order totals" do expect { subject.update_cart params - }.to change { subject.order.total } + }.to(change { subject.order.total }) end context "submits item quantity 0" do @@ -278,7 +281,7 @@ it "removes item from order" do expect { subject.update_cart params - }.to change { subject.order.line_items.count } + }.to(change { subject.order.line_items.count }) end end @@ -302,11 +305,11 @@ it "updates order payment state" do expect { subject.add variant - }.to change { order.payment_state } + }.to(change(order, :payment_state)) expect { subject.remove variant - }.to change { order.payment_state } + }.to(change(order, :payment_state)) end end @@ -317,7 +320,7 @@ expect(order.approver).to be_nil expect(order.approver_name).to eq('Jordan') expect(order.approved_at).to be_present - expect(order.approved?).to be_truthy + expect(order).to be_approved end end @@ -329,7 +332,7 @@ expect(order.approver).to eq(user) expect(order.approver_name).to be_nil expect(order.approved_at).to be_present - expect(order.approved?).to be_truthy + expect(order).to be_approved end end @@ -341,7 +344,7 @@ expect(order.approver).to eq(user) expect(order.approver_name).to eq('Jordan') expect(order.approved_at).to be_present - expect(order.approved?).to be_truthy + expect(order).to be_approved end end diff --git a/friendly_promotions/spec/models/spree/shipping_rate_spec.rb b/friendly_promotions/spec/models/spree/shipping_rate_spec.rb index be172d70c29..ccdddf887d8 100644 --- a/friendly_promotions/spec/models/spree/shipping_rate_spec.rb +++ b/friendly_promotions/spec/models/spree/shipping_rate_spec.rb @@ -6,7 +6,7 @@ let(:subject) { build(:shipping_rate) } describe '#display_price' do - before(:each) { subject.amount = 5 } + before { subject.amount = 5 } it 'returns formatted amount' do expect(subject.display_price).to eq('$5.00') @@ -14,10 +14,10 @@ end describe "#total_before_tax" do - let(:shipping_rate) { build(:shipping_rate, cost: 4) } - subject { shipping_rate.total_before_tax } + let(:shipping_rate) { build(:shipping_rate, cost: 4) } + it { is_expected.to eq(4) } context "with discounts" do @@ -29,18 +29,18 @@ end describe "#display_total_before_tax" do - let(:shipping_rate) { build(:shipping_rate, cost: 10) } - subject { shipping_rate.display_total_before_tax } + let(:shipping_rate) { build(:shipping_rate, cost: 10) } + it { is_expected.to eq(Spree::Money.new("10.00")) } end describe "#display_promo_total" do - let(:shipping_rate) { build(:shipping_rate) } - subject { shipping_rate.display_promo_total } + let(:shipping_rate) { build(:shipping_rate) } + it { is_expected.to eq(Spree::Money.new("0")) } end end diff --git a/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_actions_request_spec.rb b/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_actions_request_spec.rb index bec5489a4e9..7aef8b450b5 100644 --- a/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_actions_request_spec.rb +++ b/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_actions_request_spec.rb @@ -20,9 +20,11 @@ end it "can not create a promotion action of an invalid type" do - post solidus_friendly_promotions.admin_promotion_promotion_actions_path(promotion_id: promotion.id, promotion_action: { type: "Spree::InvalidType" }) + post solidus_friendly_promotions.admin_promotion_promotion_actions_path(promotion_id: promotion.id), params: { + promotion_action: { type: "Spree::InvalidType" } + } expect(response).to be_redirect expect(response).to redirect_to spree.edit_admin_promotion_path(promotion) - expect(promotion.rules.count).to eq(0) + expect(promotion.actions.count).to eq(0) end end diff --git a/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_rules_request_spec.rb b/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_rules_request_spec.rb index d4067f55ace..6c2469d9f09 100644 --- a/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_rules_request_spec.rb +++ b/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_rules_request_spec.rb @@ -20,7 +20,9 @@ end it "can not create a promotion rule of an invalid type" do - post solidus_friendly_promotions.admin_promotion_promotion_rules_path(promotion_id: promotion.id, promotion_rule: { type: "Spree::InvalidType" }) + post solidus_friendly_promotions.admin_promotion_promotion_rules_path(promotion_id: promotion.id), params: { + promotion_rule: { type: "Spree::InvalidType" } + } expect(response).to be_redirect expect(response).to redirect_to solidus_friendly_promotions.edit_admin_promotion_path(promotion) expect(promotion.rules.count).to eq(0) diff --git a/friendly_promotions/spec/shared_examples/calculator_shared_examples.rb b/friendly_promotions/spec/shared_examples/calculator_shared_examples.rb index c41b3c7e4e6..b4318576f91 100644 --- a/friendly_promotions/spec/shared_examples/calculator_shared_examples.rb +++ b/friendly_promotions/spec/shared_examples/calculator_shared_examples.rb @@ -3,6 +3,7 @@ RSpec.shared_examples_for 'a calculator with a description' do describe ".description" do subject { described_class.description } + it "has a description" do expect(subject.size).to be > 0 end diff --git a/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotion_categories_spec.rb b/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotion_categories_spec.rb index 34c534ae262..206b5bb2752 100644 --- a/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotion_categories_spec.rb +++ b/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotion_categories_spec.rb @@ -13,7 +13,7 @@ end context "listing promotion categories" do - it "should list the existing promotion categories" do + it "lists the existing promotion categories" do within_row(1) do expect(column_text(1)).to eq("name1") expect(column_text(2)).to eq("code1") @@ -33,14 +33,14 @@ click_on "New Promotion Category" end - it "should allow an admin to create a new promotion category" do + it "allows an admin to create a new promotion category" do fill_in "promotion_category_name", with: "promotion test" fill_in "promotion_category_code", with: "prtest" click_button "Create" expect(page).to have_content("successfully created!") end - it "should not allow admin to create promotion category when invalid data" do + it "does not allow admin to create promotion category when invalid data" do fill_in "promotion_category_name", with: "" fill_in "promotion_category_code", with: "prtest" click_button "Create" @@ -49,20 +49,20 @@ end context "edit" do - before(:each) do + before do create(:friendly_promotion_category, name: 'name1') visit solidus_friendly_promotions.admin_promotion_categories_path within_row(1) { click_icon :edit } end - it "should allow an admin to edit an existing promotion category" do + it "allows an admin to edit an existing promotion category" do fill_in "promotion_category_name", with: "name 99" click_button "Update" expect(page).to have_content("successfully updated!") expect(page).to have_content("name 99") end - it "should show validation errors" do + it "shows validation errors" do fill_in "promotion_category_name", with: "" click_button "Update" expect(page).to have_content("Name can't be blank") @@ -70,12 +70,12 @@ end context "delete" do - before(:each) do + before do create(:friendly_promotion_category, name: 'name1') visit solidus_friendly_promotions.admin_promotion_categories_path end - it "should allow an admin to delete an existing promotion category", js: true do + it "allows an admin to delete an existing promotion category", js: true do accept_alert do click_icon :trash end diff --git a/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotions_spec.rb b/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotions_spec.rb index c12e2cc1d63..3d6da8a3fbd 100644 --- a/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotions_spec.rb +++ b/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotions_spec.rb @@ -6,10 +6,21 @@ stub_authorization! describe "#index" do - let!(:promotion1) { create(:friendly_promotion, :with_adjustable_action, name: "name1", code: "code1", path: "path1") } - let!(:promotion2) { create(:friendly_promotion, :with_adjustable_action, name: "name2", code: "code2", path: "path2") } + let!(:promotion1) do + create(:friendly_promotion, :with_adjustable_action, name: "name1", code: "code1", path: "path1") + end + let!(:promotion2) do + create(:friendly_promotion, :with_adjustable_action, name: "name2", code: "code2", path: "path2") + end let!(:promotion3) do - create(:friendly_promotion, :with_adjustable_action, name: "name3", code: "code3", path: "path3", expires_at: Date.yesterday) + create( + :friendly_promotion, + :with_adjustable_action, + name: "name3", + code: "code3", + path: "path3", + expires_at: Date.yesterday + ) end let!(:category) { create :friendly_promotion_category } @@ -22,7 +33,10 @@ it "shows promotion categories" do visit solidus_friendly_promotions.admin_promotions_path - expect(page).to have_select(SolidusFriendlyPromotions::PromotionCategory.model_name.human, options: ["All", category.name]) + expect(page).to have_select( + SolidusFriendlyPromotions::PromotionCategory.model_name.human, + options: ["All", category.name] + ) end context "search" do From c81f3ef0db20d26a912a70ca4267076297232d8d Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 25 Jul 2023 15:07:27 +0200 Subject: [PATCH 126/524] Rubocop controller fixes --- .../admin/promotion_codes_controller.rb | 6 ++++-- .../admin/promotion_rules_controller.rb | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_codes_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_codes_controller.rb index d77de5f734e..38ee1184eeb 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_codes_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_codes_controller.rb @@ -24,8 +24,10 @@ def index def new if @promotion.apply_automatically - flash[:error] = - t('activerecord.errors.models.solidus_friendly_promotions/promotion_code.attributes.base.disallowed_with_apply_automatically') + flash[:error] = t( + :disallowed_with_apply_automatically, + scope: 'activerecord.errors.models.solidus_friendly_promotions/promotion_code.attributes.base' + ) redirect_to solidus_friendly_promotions.admin_promotion_promotion_codes_url(@promotion) else @promotion_code = @promotion.codes.build diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb index 7ecf39e4db0..fb4e960e38a 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb @@ -5,7 +5,7 @@ module Admin class PromotionRulesController < Spree::Admin::BaseController helper 'spree/promotion_rules' - before_action :validate_level, only: [:new, :edit, :create] + before_action :validate_level, only: [:new, :create] before_action :load_promotion, only: [:create, :destroy, :update, :new] before_action :validate_promotion_rule_type, only: [:create] From de8c45af8935c903cd3aaf2f4e5ba62db793c7ef Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 25 Jul 2023 15:15:37 +0200 Subject: [PATCH 127/524] Skip some cops in this project Solidus is complex. I'm upping the number of allowed expectations per spec, and of allowed memoized helpers. Spec setup in this system is hard enough as it is. --- friendly_promotions/.rubocop.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/friendly_promotions/.rubocop.yml b/friendly_promotions/.rubocop.yml index b075a8f6826..1ff53b95e64 100644 --- a/friendly_promotions/.rubocop.yml +++ b/friendly_promotions/.rubocop.yml @@ -3,3 +3,25 @@ require: AllCops: NewCops: disable + + +RSpec/MultipleExpectations: + Max: 10 + +RSpec/MultipleMemoizedHelpers: + Max: 10 + +RSpec/NamedSubject: + Enabled: false + +RSpec/MessageSpies: + Enabled: false + +RSpec/ContextWording: + Enabled: false + +RSpec/NestedGroups: + Max: 5 + +RSpec/LetSetup: + Enabled: false From da5e6e0f8fb23ea19098cfc3590a09ba43ea078a Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 25 Jul 2023 15:18:12 +0200 Subject: [PATCH 128/524] More rubocop fixes --- .../admin/promotion_actions_request_spec.rb | 2 +- .../admin/promotion_rules_request_spec.rb | 2 +- .../admin/{promotions_spec.rb => promotions_request_spec.rb} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename friendly_promotions/spec/requests/solidus_friendly_promotions/admin/{promotions_spec.rb => promotions_request_spec.rb} (100%) diff --git a/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_actions_request_spec.rb b/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_actions_request_spec.rb index 7aef8b450b5..ec0ecd06aa5 100644 --- a/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_actions_request_spec.rb +++ b/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_actions_request_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -describe SolidusFriendlyPromotions::Admin::PromotionActionsController, type: :request do +describe "Admin::PromotionActions", type: :request do stub_authorization! let!(:promotion) { create(:friendly_promotion) } diff --git a/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_rules_request_spec.rb b/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_rules_request_spec.rb index 6c2469d9f09..75e7ae83c79 100644 --- a/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_rules_request_spec.rb +++ b/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_rules_request_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -describe SolidusFriendlyPromotions::Admin::PromotionRulesController, type: :request do +describe "Admin::PromotionRules", type: :request do let!(:promotion) { create(:friendly_promotion) } context "when the user is authorized" do diff --git a/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotions_spec.rb b/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotions_request_spec.rb similarity index 100% rename from friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotions_spec.rb rename to friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotions_request_spec.rb From 37eb18415cfb52b8b8c591dd34b7b5f12fc7f563 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 25 Jul 2023 15:19:13 +0200 Subject: [PATCH 129/524] Fix RC offense --- .../models/solidus_friendly_promotions/order_discounter.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb b/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb index a9f950a3897..8333194982c 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb @@ -73,8 +73,8 @@ def update_adjustments(item, taxed_items) # @param [SolidusFriendlyPromotions::ItemDiscount] tax_item calculated discounts for an item # @return [Spree::Adjustment] the created or updated tax adjustment def update_adjustment(item, discount_item) - adjustment = item.adjustments.detect do |adjustment| - adjustment.source == discount_item.source + adjustment = item.adjustments.detect do |item_adjustment| + item_adjustment.source == discount_item.source end adjustment ||= item.adjustments.new( From 86a126efe6ea2b25caf013f84f01042d8021f6eb Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 25 Jul 2023 15:27:51 +0200 Subject: [PATCH 130/524] Fix RC offenses in simple order contents spec --- .../simple_order_contents_spec.rb | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/simple_order_contents_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/simple_order_contents_spec.rb index bd3c248d4eb..fefa74d8db7 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/simple_order_contents_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/simple_order_contents_spec.rb @@ -9,7 +9,6 @@ let(:order) { create(:order) } let(:variant) { create(:variant) } let!(:stock_location) { variant.stock_locations.first } - let(:stock_location_2) { create(:stock_location) } describe "#add" do context 'given quantity is not explicitly provided' do @@ -112,11 +111,8 @@ end context 'when the order has a taxable address' do - before do - expect(order.tax_address.country_id).to be_present - end - it 'creates a tax adjustment' do + expect(order.tax_address.country_id).to be_present order_contents.add(variant) line_item = order.find_line_item_by_variant(variant) expect(line_item.adjustments.tax.count).to eq(1) @@ -126,10 +122,10 @@ context 'when the order does not have a taxable address' do before do order.update!(ship_address: nil, bill_address: nil) - expect(order.tax_address.country_id).to be_nil end it 'creates a tax adjustment' do + expect(order.tax_address.country_id).to be_nil order_contents.add(variant) line_item = order.find_line_item_by_variant(variant) expect(line_item.adjustments.tax.count).to eq(0) From 235faa0f222e4b6ce797cc5e1739cbf3263a9e8f Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 25 Jul 2023 15:29:07 +0200 Subject: [PATCH 131/524] Fix user logged in spec --- .../rules/product_spec.rb | 36 +++++++++---------- .../rules/user_logged_in_spec.rb | 2 +- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/product_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/product_spec.rb index b8fffa7429c..3c0b03ab616 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/product_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/product_spec.rb @@ -10,10 +10,9 @@ describe "#eligible?(order)" do let(:order) { Spree::Order.new } - - before do - 3.times { |i| instance_variable_set("@product#{i}", mock_model(Spree::Product)) } - end + let(:product_one) { build(:product) } + let(:product_two) { build(:product) } + let(:product_three) { build(:product) } it "is eligible if there are no products" do allow(rule).to receive_messages(eligible_products: []) @@ -24,15 +23,15 @@ let(:rule_options) { super().merge(preferred_match_policy: "any") } it "is eligible if any of the products is in eligible products" do - allow(rule).to receive_messages(order_products: [@product1, @product2]) - allow(rule).to receive_messages(eligible_products: [@product2, @product3]) + allow(rule).to receive_messages(order_products: [product_one, product_two]) + allow(rule).to receive_messages(eligible_products: [product_two, product_three]) expect(rule).to be_eligible(order) end context "when none of the products are eligible products" do before do - allow(rule).to receive_messages(order_products: [@product1]) - allow(rule).to receive_messages(eligible_products: [@product2, @product3]) + allow(rule).to receive_messages(order_products: [product_one]) + allow(rule).to receive_messages(eligible_products: [product_two, product_three]) end it { expect(rule).not_to be_eligible(order) } @@ -55,23 +54,24 @@ let(:rule_options) { super().merge(preferred_match_policy: "all") } it "is eligible if all of the eligible products are ordered" do - allow(rule).to receive_messages(order_products: [@product3, @product2, @product1]) - allow(rule).to receive_messages(eligible_products: [@product2, @product3]) + allow(rule).to receive_messages(order_products: [product_three, product_two, product_one]) + allow(rule).to receive_messages(eligible_products: [product_two, product_three]) expect(rule).to be_eligible(order) end context "when any of the eligible products is not ordered" do before do - allow(rule).to receive_messages(order_products: [@product1, @product2]) - allow(rule).to receive_messages(eligible_products: [@product1, @product2, @product3]) + allow(rule).to receive_messages(order_products: [product_one, product_two]) + allow(rule).to receive_messages(eligible_products: [product_one, product_two, product_three]) end it { expect(rule).not_to be_eligible(order) } it "sets an error message" do rule.eligible?(order) - expect(rule.eligibility_errors.full_messages.first) - .to eq "This coupon code can't be applied because you don't have all of the necessary products in your cart." + expect(rule.eligibility_errors.full_messages.first).to eq( + "This coupon code can't be applied because you don't have all of the necessary products in your cart." + ) end it "sets an error code" do @@ -86,15 +86,15 @@ let(:rule_options) { super().merge(preferred_match_policy: "none") } it "is eligible if none of the order's products are in eligible products" do - allow(rule).to receive_messages(order_products: [@product1]) - allow(rule).to receive_messages(eligible_products: [@product2, @product3]) + allow(rule).to receive_messages(order_products: [product_one]) + allow(rule).to receive_messages(eligible_products: [product_two, product_three]) expect(rule).to be_eligible(order) end context "when any of the order's products are in eligible products" do before do - allow(rule).to receive_messages(order_products: [@product1, @product2]) - allow(rule).to receive_messages(eligible_products: [@product2, @product3]) + allow(rule).to receive_messages(order_products: [product_one, product_two]) + allow(rule).to receive_messages(eligible_products: [product_two, product_three]) end it { expect(rule).not_to be_eligible(order) } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_logged_in_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_logged_in_spec.rb index 4901483a5bb..1d60a17f1c1 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_logged_in_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/user_logged_in_spec.rb @@ -9,7 +9,7 @@ let(:order) { Spree::Order.new } it "is eligible if order has an associated user" do - user = double("User") + user = Spree::LegacyUser.new allow(order).to receive_messages(user: user) expect(rule).to be_eligible(order) From 1dc703b2915309b8fb09c647570d4236a437e480 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 25 Jul 2023 16:15:38 +0200 Subject: [PATCH 132/524] Fix taxon spec --- .../rules/taxon_spec.rb | 68 ++++++++++--------- 1 file changed, 35 insertions(+), 33 deletions(-) diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/taxon_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/taxon_spec.rb index 7b2f635cbd2..ff879ca3de5 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/taxon_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/taxon_spec.rb @@ -8,24 +8,23 @@ end let(:product) { order.products.first } let(:order) { create :order_with_line_items } - let(:taxon2) { create :taxon, name: "second" } - let(:taxon) { create :taxon, name: "first" } + let(:taxon_one) { create :taxon, name: "first" } + let(:taxon_two) { create :taxon, name: "second" } it { is_expected.to have_many(:taxons) } describe "taxon_ids_string=" do - subject { rule.assign_attributes("taxon_ids_string" => taxon_2.id.to_s) } + subject { rule.assign_attributes("taxon_ids_string" => taxon_two.id.to_s) } let!(:promotion) { create(:friendly_promotion) } - let!(:taxon_1) { create(:taxon) } - let!(:taxon_2) { create(:taxon) } + let(:rule) { promotion.rules.build(type: described_class.to_s) } it "creates a valid rule with a taxon" do subject expect(rule).to be_valid rule.save! - expect(rule.reload.taxons).to include(taxon_2) + expect(rule.reload.taxons).to include(taxon_two) end end @@ -36,20 +35,21 @@ end it "is eligible if order does have any prefered taxon" do - product.taxons << taxon - rule.taxons << taxon + product.taxons << taxon_one + rule.taxons << taxon_one expect(rule).to be_eligible(order) end context "when order does not have any prefered taxon" do - before { rule.taxons << taxon2 } + before { rule.taxons << taxon_two } it { expect(rule).not_to be_eligible(order) } it "sets an error message" do rule.eligible?(order) - expect(rule.eligibility_errors.full_messages.first) - .to eq "You need to add a product from an applicable category before applying this coupon code." + expect(rule.eligibility_errors.full_messages.first).to eq( + "You need to add a product from an applicable category before applying this coupon code." + ) end it "sets an error code" do @@ -61,9 +61,9 @@ context "when a product has a taxon child of a taxon rule" do before do - taxon.children << taxon2 - product.taxons << taxon2 - rule.taxons << taxon + taxon_one.children << taxon_two + product.taxons << taxon_two + rule.taxons << taxon_one end it { expect(rule).to be_eligible(order) } @@ -76,23 +76,24 @@ end it "is eligible order has all prefered taxons" do - product.taxons << taxon2 - order.products.last.taxons << taxon + product.taxons << taxon_two + order.products.last.taxons << taxon_one - rule.taxons = [taxon, taxon2] + rule.taxons = [taxon_one, taxon_two] expect(rule).to be_eligible(order) end context "when order does not have all prefered taxons" do - before { rule.taxons << taxon } + before { rule.taxons << taxon_one } it { expect(rule).not_to be_eligible(order) } it "sets an error message" do rule.eligible?(order) - expect(rule.eligibility_errors.full_messages.first) - .to eq "You need to add a product from all applicable categories before applying this coupon code." + expect(rule.eligibility_errors.full_messages.first).to eq( + "You need to add a product from all applicable categories before applying this coupon code." + ) end it "sets an error code" do @@ -103,15 +104,15 @@ end context "when a product has a taxon child of a taxon rule" do - let(:taxon3) { create :taxon } + let(:taxon_three) { create :taxon } before do - taxon.children << taxon2 - taxon.save! - taxon.reload + taxon_one.children << taxon_two + taxon_one.save! + taxon_one.reload - product.taxons = [taxon2, taxon3] - rule.taxons = [taxon, taxon3] + product.taxons = [taxon_two, taxon_three] + rule.taxons = [taxon_one, taxon_three] end it { expect(rule).to be_eligible(order) } @@ -124,15 +125,15 @@ end context "none of the order's products are in listed taxon" do - before { rule.taxons << taxon2 } + before { rule.taxons << taxon_two } it { expect(rule).to be_eligible(order) } end context "one of the order's products is in a listed taxon" do before do - order.products.first.taxons << taxon - rule.taxons << taxon + order.products.first.taxons << taxon_one + rule.taxons << taxon_one end it "is not eligible" do @@ -141,8 +142,9 @@ it "sets an error message" do rule.eligible?(order) - expect(rule.eligibility_errors.full_messages.first) - .to eq "Your cart contains a product from an excluded category that prevents this coupon code from being applied." + expect(rule.eligibility_errors.full_messages.first).to eq( + "Your cart contains a product from an excluded category that prevents this coupon code from being applied." + ) end it "sets an error code" do @@ -155,8 +157,8 @@ context "with an invalid match policy" do before do - order.products.first.taxons << taxon - rule.taxons << taxon + order.products.first.taxons << taxon_one + rule.taxons << taxon_one rule.preferred_match_policy = "invalid" rule.save!(validate: false) end From 4357bd8a927d80737f385e3ee22ab5b99e961879 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 25 Jul 2023 16:18:13 +0200 Subject: [PATCH 133/524] Fix one use per user spec --- .../rules/one_use_per_user_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/one_use_per_user_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/one_use_per_user_spec.rb index b04f8717849..d7a863ce539 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/one_use_per_user_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/one_use_per_user_spec.rb @@ -8,8 +8,8 @@ describe "#eligible?(order)" do subject { rule.eligible?(order) } - let(:order) { double Spree::Order, user: user } - let(:user) { double Spree::LegacyUser } + let(:order) { instance_double("Spree::Order", user: user) } + let(:user) { instance_double("Spree::LegacyUser") } let(:promotion) { stub_model SolidusFriendlyPromotions::Promotion, used_by?: used_by } let(:used_by) { false } From 1336288abb115e1c227d7036a1bf53869ce1e566 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 25 Jul 2023 16:21:40 +0200 Subject: [PATCH 134/524] RC Fix: Item total rule --- .../models/solidus_friendly_promotions/rules/item_total_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/item_total_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/item_total_spec.rb index 73da9a6bab5..38fc3a2f307 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/item_total_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/item_total_spec.rb @@ -9,7 +9,7 @@ preferred_operator: preferred_operator ) end - let(:order) { double(:order, item_total: item_total, currency: order_currency) } + let(:order) { instance_double('Spree::Order', item_total: item_total, currency: order_currency) } let(:preferred_amount) { 50 } let(:order_currency) { "USD" } From 61b19ad0a61897785e0c94794b01a18bff4b8beb Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 25 Jul 2023 16:54:56 +0200 Subject: [PATCH 135/524] Run on Chrome only --- friendly_promotions/.circleci/config.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/friendly_promotions/.circleci/config.yml b/friendly_promotions/.circleci/config.yml index 11d33880218..c153c92e2e5 100644 --- a/friendly_promotions/.circleci/config.yml +++ b/friendly_promotions/.circleci/config.yml @@ -8,7 +8,7 @@ jobs: name: solidusio_extensions/mysql ruby_version: '3.1' steps: - - browser-tools/install-browser-tools + - browser-tools/install-chrome - solidusio_extensions/run-tests: working_directory: '~/project/solidus_friendly_promotions' run-specs-with-postgres: @@ -16,7 +16,7 @@ jobs: name: solidusio_extensions/postgres ruby_version: '3.2' steps: - - browser-tools/install-browser-tools + - browser-tools/install-chrome - solidusio_extensions/run-tests: working_directory: '~/project/solidus_friendly_promotions' run-specs-with-sqlite: @@ -24,7 +24,7 @@ jobs: name: solidusio_extensions/sqlite ruby_version: '3.0' steps: - - browser-tools/install-browser-tools + - browser-tools/install-chrome - solidusio_extensions/run-tests: working_directory: '~/project/solidus_friendly_promotions' workflows: From 5a00b1f611f30596823f1fb660dc641404525694 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 25 Jul 2023 17:32:40 +0200 Subject: [PATCH 136/524] Fix Rubocop for good --- friendly_promotions/.rubocop.yml | 4 +- friendly_promotions/.rubocop_todo.yml | 157 ++++++++++++++++++++++++++ 2 files changed, 160 insertions(+), 1 deletion(-) create mode 100644 friendly_promotions/.rubocop_todo.yml diff --git a/friendly_promotions/.rubocop.yml b/friendly_promotions/.rubocop.yml index 1ff53b95e64..efca29831b3 100644 --- a/friendly_promotions/.rubocop.yml +++ b/friendly_promotions/.rubocop.yml @@ -1,3 +1,5 @@ +inherit_from: .rubocop_todo.yml + require: - solidus_dev_support/rubocop @@ -9,7 +11,7 @@ RSpec/MultipleExpectations: Max: 10 RSpec/MultipleMemoizedHelpers: - Max: 10 + Max: 12 RSpec/NamedSubject: Enabled: false diff --git a/friendly_promotions/.rubocop_todo.yml b/friendly_promotions/.rubocop_todo.yml new file mode 100644 index 00000000000..02169fb61eb --- /dev/null +++ b/friendly_promotions/.rubocop_todo.yml @@ -0,0 +1,157 @@ +# This configuration was generated by +# `rubocop --auto-gen-config --exclude-limit 10000` +# on 2023-07-25 15:26:43 UTC using RuboCop version 1.54.1. +# The point is for the user to remove these configuration records +# one by one as the offenses are removed from the code base. +# Note that changes in the inspected code, or installation of new +# versions of RuboCop, may require this file to be generated again. + +# Offense count: 2 +# Configuration parameters: AllowedMethods. +# AllowedMethods: enums +Lint/ConstantDefinitionInBlock: + Exclude: + - 'spec/models/solidus_friendly_promotions/promotion_rule_spec.rb' + +# Offense count: 1 +Lint/DuplicateMethods: + Exclude: + - 'app/models/solidus_friendly_promotions/promotion.rb' + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +Lint/RedundantCopDisableDirective: + Exclude: + - 'lib/generators/solidus_friendly_promotions/install/install_generator.rb' + +# Offense count: 8 +# Configuration parameters: EnforcedStyle, CheckMethodNames, CheckSymbols, AllowedIdentifiers, AllowedPatterns. +# SupportedStyles: snake_case, normalcase, non_integer +# AllowedIdentifiers: capture3, iso8601, rfc1123_date, rfc822, rfc2822, rfc3339, x86_64 +Naming/VariableNumber: + Exclude: + - 'spec/models/solidus_friendly_promotions/rules/first_repeat_purchase_since_spec.rb' + +# Offense count: 1 +RSpec/AnyInstance: + Exclude: + - 'spec/jobs/solidus_friendly_promotions/promotion_code_batch_job_spec.rb' + +# Offense count: 2 +# Configuration parameters: IgnoredMetadata. +RSpec/DescribeClass: + Exclude: + - 'spec/lib/solidus_friendly_promotions/testing_support/factories_spec.rb' + - 'spec/models/promotion/integration_spec.rb' + +# Offense count: 2 +RSpec/ExpectInHook: + Exclude: + - 'spec/jobs/solidus_friendly_promotions/promotion_code_batch_job_spec.rb' + - 'spec/models/solidus_friendly_promotions/promotion_code/batch_builder_spec.rb' + +# Offense count: 7 +# Configuration parameters: AssignmentOnly. +RSpec/InstanceVariable: + Exclude: + - 'spec/models/solidus_friendly_promotions/promotion_spec.rb' + +# Offense count: 2 +RSpec/IteratedExpectation: + Exclude: + - 'spec/jobs/solidus_friendly_promotions/promotion_code_batch_job_spec.rb' + +# Offense count: 2 +RSpec/LeakyConstantDeclaration: + Exclude: + - 'spec/models/solidus_friendly_promotions/promotion_rule_spec.rb' + +# Offense count: 3 +RSpec/MessageChain: + Exclude: + - 'spec/models/solidus_friendly_promotions/rules/first_order_spec.rb' + +# Offense count: 4 +# Configuration parameters: AllowSubject. +RSpec/MultipleMemoizedHelpers: + Max: 12 + +# Offense count: 1 +RSpec/OverwritingSetup: + Exclude: + - 'spec/models/solidus_friendly_promotions/calculators/flexi_rate_spec.rb' + +# Offense count: 2 +RSpec/RepeatedExampleGroupBody: + Exclude: + - 'spec/lib/solidus_friendly_promotions/configuration_spec.rb' + +# Offense count: 2 +RSpec/RepeatedExampleGroupDescription: + Exclude: + - 'spec/models/solidus_friendly_promotions/calculators/flat_rate_spec.rb' + +# Offense count: 1 +RSpec/SubjectStub: + Exclude: + - 'spec/models/solidus_friendly_promotions/promotion_code/batch_builder_spec.rb' + +# Offense count: 2 +# Configuration parameters: IgnoreNameless, IgnoreSymbolicNames. +RSpec/VerifiedDoubles: + Exclude: + - 'spec/models/solidus_friendly_promotions/calculators/percent_spec.rb' + +# Offense count: 1 +# Configuration parameters: Include. +# Include: db/**/*.rb +Rails/CreateTableWithTimestamps: + Exclude: + - 'db/migrate/20230725074235_create_shipping_rate_discounts.rb' + +# Offense count: 7 +# Configuration parameters: Include. +# Include: app/models/**/*.rb +Rails/HasManyOrHasOneDependent: + Exclude: + - 'app/models/solidus_friendly_promotions/promotion.rb' + - 'app/models/solidus_friendly_promotions/promotion_action.rb' + - 'app/models/solidus_friendly_promotions/promotion_category.rb' + - 'app/models/solidus_friendly_promotions/promotion_code.rb' + +# Offense count: 7 +# Configuration parameters: IgnoreScopes, Include. +# Include: app/models/**/*.rb +Rails/InverseOf: + Exclude: + - 'app/models/solidus_friendly_promotions/promotion.rb' + - 'app/models/solidus_friendly_promotions/rules/line_item_product.rb' + - 'app/models/solidus_friendly_promotions/rules/line_item_taxon.rb' + - 'app/models/solidus_friendly_promotions/rules/product.rb' + - 'app/models/solidus_friendly_promotions/rules/store.rb' + - 'app/models/solidus_friendly_promotions/rules/taxon.rb' + - 'app/models/solidus_friendly_promotions/rules/user.rb' + +# Offense count: 2 +# This cop supports unsafe autocorrection (--autocorrect-all). +Rails/ReflectionClassName: + Exclude: + - 'app/models/solidus_friendly_promotions/promotion_rules_user.rb' + - 'app/models/solidus_friendly_promotions/rules/user.rb' + +# Offense count: 9 +# Configuration parameters: ForbiddenMethods, AllowedMethods. +# ForbiddenMethods: decrement!, decrement_counter, increment!, increment_counter, insert, insert!, insert_all, insert_all!, toggle!, touch, touch_all, update_all, update_attribute, update_column, update_columns, update_counters, upsert, upsert_all +Rails/SkipsModelValidations: + Exclude: + - 'lib/solidus_friendly_promotions/testing_support/friendly_order_factory.rb' + - 'spec/models/solidus_friendly_promotions/promotion_code_batch_spec.rb' + - 'spec/models/solidus_friendly_promotions/promotion_code_spec.rb' + - 'spec/models/solidus_friendly_promotions/promotion_spec.rb' + +# Offense count: 5 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns. +# URISchemes: http, https +Layout/LineLength: + Max: 148 From cf62a428b2bec0f98ed596c29230e6396a38d644 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 25 Jul 2023 17:36:19 +0200 Subject: [PATCH 137/524] Only run tests on main --- friendly_promotions/.circleci/config.yml | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/friendly_promotions/.circleci/config.yml b/friendly_promotions/.circleci/config.yml index c153c92e2e5..3c38e460b05 100644 --- a/friendly_promotions/.circleci/config.yml +++ b/friendly_promotions/.circleci/config.yml @@ -9,24 +9,30 @@ jobs: ruby_version: '3.1' steps: - browser-tools/install-chrome - - solidusio_extensions/run-tests: - working_directory: '~/project/solidus_friendly_promotions' + - checkout + - solidusio_extensions/dependencies + - solidusio_extensions/run-tests-solidus-current + - solidusio_extensions/run-tests-solidus-main run-specs-with-postgres: executor: name: solidusio_extensions/postgres ruby_version: '3.2' steps: - browser-tools/install-chrome - - solidusio_extensions/run-tests: - working_directory: '~/project/solidus_friendly_promotions' + - checkout + - solidusio_extensions/dependencies + - solidusio_extensions/run-tests-solidus-current + - solidusio_extensions/run-tests-solidus-main run-specs-with-sqlite: executor: name: solidusio_extensions/sqlite ruby_version: '3.0' steps: - browser-tools/install-chrome - - solidusio_extensions/run-tests: - working_directory: '~/project/solidus_friendly_promotions' + - checkout + - solidusio_extensions/dependencies + - solidusio_extensions/run-tests-solidus-current + - solidusio_extensions/run-tests-solidus-main workflows: Run specs on supported Solidus versions: jobs: From 6677c0356451b4bba121cd3912de3d78f4c72bcf Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 26 Jul 2023 10:55:46 +0200 Subject: [PATCH 138/524] Remove duplicate index --- .../migrate/20230704102656_create_promotion_code_batches.rb | 6 ------ 1 file changed, 6 deletions(-) diff --git a/friendly_promotions/db/migrate/20230704102656_create_promotion_code_batches.rb b/friendly_promotions/db/migrate/20230704102656_create_promotion_code_batches.rb index 0a1cd5aba53..7eee38d0181 100644 --- a/friendly_promotions/db/migrate/20230704102656_create_promotion_code_batches.rb +++ b/friendly_promotions/db/migrate/20230704102656_create_promotion_code_batches.rb @@ -13,12 +13,6 @@ def change t.timestamps precision: 6 end - add_foreign_key( - :friendly_promotion_code_batches, - :friendly_promotions, - column: :promotion_id - ) - add_column( :friendly_promotion_codes, :promotion_code_batch_id, From ac9d516f2004bf775bc102613005a0cac005fd99 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 26 Jul 2023 12:15:27 +0200 Subject: [PATCH 139/524] Fix MySQL build --- .../db/migrate/20230704083830_add_rule_tables.rb | 8 ++++---- .../20230704102656_create_promotion_code_batches.rb | 2 +- .../20230705171556_create_friendly_order_promotions.rb | 2 +- .../20230725074235_create_shipping_rate_discounts.rb | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/friendly_promotions/db/migrate/20230704083830_add_rule_tables.rb b/friendly_promotions/db/migrate/20230704083830_add_rule_tables.rb index bdabd239ccf..e412ea601c0 100644 --- a/friendly_promotions/db/migrate/20230704083830_add_rule_tables.rb +++ b/friendly_promotions/db/migrate/20230704083830_add_rule_tables.rb @@ -1,28 +1,28 @@ class AddRuleTables < ActiveRecord::Migration[7.0] def change create_table :friendly_products_promotion_rules, force: :cascade do |t| - t.references :product, index: true, null: false, foreign_key: { to_table: :spree_products } + t.references :product, type: :integer, index: true, null: false, foreign_key: { to_table: :spree_products } t.references :promotion_rule, index: true, null: false, foreign_key: { to_table: :friendly_promotion_rules } t.timestamps end create_table :friendly_promotion_rules_taxons, force: :cascade do |t| - t.references :taxon, index: true, null: false, foreign_key: { to_table: :spree_taxons } + t.references :taxon, type: :integer, index: true, null: false, foreign_key: { to_table: :spree_taxons } t.references :promotion_rule, index: true, null: false, foreign_key: { to_table: :friendly_promotion_rules } t.timestamps end create_table :friendly_promotion_rules_users, force: :cascade do |t| - t.references :user, index: true, null: false, foreign_key: { to_table: Spree.user_class.table_name } + t.references :user, type: :integer, index: true, null: false, foreign_key: { to_table: Spree.user_class.table_name } t.references :promotion_rule, index: true, null: false, foreign_key: { to_table: :friendly_promotion_rules } t.timestamps end create_table :friendly_promotion_rules_stores do |t| - t.references :store, index: true, null: false, foreign_key: { to_table: :spree_stores } + t.references :store, type: :integer, index: true, null: false, foreign_key: { to_table: :spree_stores } t.references :promotion_rule, index: true, null: false t.timestamps diff --git a/friendly_promotions/db/migrate/20230704102656_create_promotion_code_batches.rb b/friendly_promotions/db/migrate/20230704102656_create_promotion_code_batches.rb index 7eee38d0181..0e7c2a9940d 100644 --- a/friendly_promotions/db/migrate/20230704102656_create_promotion_code_batches.rb +++ b/friendly_promotions/db/migrate/20230704102656_create_promotion_code_batches.rb @@ -16,7 +16,7 @@ def change add_column( :friendly_promotion_codes, :promotion_code_batch_id, - :integer + :bigint ) add_foreign_key( diff --git a/friendly_promotions/db/migrate/20230705171556_create_friendly_order_promotions.rb b/friendly_promotions/db/migrate/20230705171556_create_friendly_order_promotions.rb index a04e320aa0b..e1cdee49d82 100644 --- a/friendly_promotions/db/migrate/20230705171556_create_friendly_order_promotions.rb +++ b/friendly_promotions/db/migrate/20230705171556_create_friendly_order_promotions.rb @@ -1,7 +1,7 @@ class CreateFriendlyOrderPromotions < ActiveRecord::Migration[7.0] def change create_table :friendly_order_promotions do |t| - t.references :order, index: true, null: false, foreign_key: { to_table: :spree_orders } + t.references :order, type: :integer, index: true, null: false, foreign_key: { to_table: :spree_orders } t.references :promotion, index: true, null: false, foreign_key: { to_table: :friendly_promotions } t.references :promotion_code, index: true, null: true, foreign_key: { to_table: :friendly_promotion_codes } diff --git a/friendly_promotions/db/migrate/20230725074235_create_shipping_rate_discounts.rb b/friendly_promotions/db/migrate/20230725074235_create_shipping_rate_discounts.rb index 2bb7c594f3e..711375f001a 100644 --- a/friendly_promotions/db/migrate/20230725074235_create_shipping_rate_discounts.rb +++ b/friendly_promotions/db/migrate/20230725074235_create_shipping_rate_discounts.rb @@ -1,8 +1,8 @@ class CreateShippingRateDiscounts < ActiveRecord::Migration[7.0] def change create_table :friendly_shipping_rate_discounts do |t| - t.references :promotion_action, null: false, foreign_key: { to_table: :friendly_promotion_actions } - t.references :shipping_rate, null: false, foreign_key: { to_table: :spree_shipping_rates } + t.references :promotion_action, type: :bigint, null: false, foreign_key: { to_table: :friendly_promotion_actions } + t.references :shipping_rate, type: :integer, null: false, foreign_key: { to_table: :spree_shipping_rates } t.decimal :amount, precision: 10, scale: 2, null: false t.string :label, null: false end From a6bbd04ff50b6934b877d9e9c90c189164397f23 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 26 Jul 2023 14:06:21 +0200 Subject: [PATCH 140/524] Add timestamps to shipping rate discounts --- .../db/migrate/20230725074235_create_shipping_rate_discounts.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/friendly_promotions/db/migrate/20230725074235_create_shipping_rate_discounts.rb b/friendly_promotions/db/migrate/20230725074235_create_shipping_rate_discounts.rb index 711375f001a..a2072b1839f 100644 --- a/friendly_promotions/db/migrate/20230725074235_create_shipping_rate_discounts.rb +++ b/friendly_promotions/db/migrate/20230725074235_create_shipping_rate_discounts.rb @@ -5,6 +5,8 @@ def change t.references :shipping_rate, type: :integer, null: false, foreign_key: { to_table: :spree_shipping_rates } t.decimal :amount, precision: 10, scale: 2, null: false t.string :label, null: false + + t.timestamps end end end From 62861aeae3c297601ff5758abd6fa287005d1e37 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 26 Jul 2023 14:09:34 +0200 Subject: [PATCH 141/524] Rename PromotionAdjustmentChooser to DiscountChooser This reflects more accurately what objects this class operates on. --- .../discount_chooser.rb | 19 +++++++++++++++++++ .../promotion_adjustment_chooser.rb | 19 ------------------- .../install/templates/initializer.rb | 2 +- .../configuration.rb | 2 +- .../configuration_spec.rb | 2 +- 5 files changed, 22 insertions(+), 22 deletions(-) create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/discount_chooser.rb delete mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/promotion_adjustment_chooser.rb diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/discount_chooser.rb b/friendly_promotions/app/models/solidus_friendly_promotions/discount_chooser.rb new file mode 100644 index 00000000000..5e478e84df3 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/discount_chooser.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + class DiscountChooser + attr_reader :item + + def initialize(item) + @item = item + end + + def call(discounts) + Array.wrap( + discounts.min_by do |discount| + [discount.amount, -discount.source&.id.to_i] + end + ) + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_adjustment_chooser.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_adjustment_chooser.rb deleted file mode 100644 index 8c0d7a2645a..00000000000 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_adjustment_chooser.rb +++ /dev/null @@ -1,19 +0,0 @@ -# frozen_string_literal: true - -module SolidusFriendlyPromotions - class PromotionAdjustmentChooser - attr_reader :adjustable - - def initialize(adjustable) - @adjustable = adjustable - end - - def call(adjustments) - Array.wrap( - adjustments.min_by do |adjustment| - [adjustment.amount, -adjustment.source&.id.to_i] - end - ) - end - end -end diff --git a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb index b235d5ab74b..ce286030f46 100644 --- a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb +++ b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb @@ -23,7 +23,7 @@ SolidusFriendlyPromotions.configure do |config| # This class chooses which promotion should apply to a line item in case # that more than one promotion is eligible. - config.discount_chooser_class = "SolidusFriendlyPromotions::PromotionAdjustmentChooser" + config.discount_chooser_class = "SolidusFriendlyPromotions::DiscountChooser" # How many promotions should be displayed on the index page in the admin. config.promotions_per_page = 25 diff --git a/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb b/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb index cb996be8f20..e433417ce48 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb @@ -16,7 +16,7 @@ class Configuration < Spree::Preferences::Configuration add_class_set :actions add_class_set :discounters - class_name_attribute :discount_chooser_class, default: "SolidusFriendlyPromotions::PromotionAdjustmentChooser" + class_name_attribute :discount_chooser_class, default: "SolidusFriendlyPromotions::DiscountChooser" class_name_attribute :promotion_code_batch_mailer_class, default: "SolidusFriendlyPromotions::PromotionCodeBatchMailer" diff --git a/friendly_promotions/spec/lib/solidus_friendly_promotions/configuration_spec.rb b/friendly_promotions/spec/lib/solidus_friendly_promotions/configuration_spec.rb index f62ec665ba3..b726905e73b 100644 --- a/friendly_promotions/spec/lib/solidus_friendly_promotions/configuration_spec.rb +++ b/friendly_promotions/spec/lib/solidus_friendly_promotions/configuration_spec.rb @@ -15,7 +15,7 @@ describe ".promotion_chooser_class" do it "is the promotion chooser" do - expect(subject.discount_chooser_class).to eq(SolidusFriendlyPromotions::PromotionAdjustmentChooser) + expect(subject.discount_chooser_class).to eq(SolidusFriendlyPromotions::DiscountChooser) end end From e64f7d00f1360a1494882f0b66a8d12db85e01f8 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 26 Jul 2023 14:12:48 +0200 Subject: [PATCH 142/524] Update README --- friendly_promotions/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/friendly_promotions/README.md b/friendly_promotions/README.md index bdf3fbd8b10..8c1bc391297 100644 --- a/friendly_promotions/README.md +++ b/friendly_promotions/README.md @@ -8,9 +8,9 @@ The basic architecture is very similar to the one in core Solidus, but with a fe ## Architecture -This extension centralizes promotion handling in the order updater. A service class, the `SolidusFriendlyShipping::OrderPromotionAdjuster` applies the current promotion configuration to the order, adjusting or removing adjustments as necessary. +This extension centralizes promotion handling in the order updater. A service class, the `SolidusFriendlyPromotions::OrderDiscounter` applies the current promotion configuration to the order, adjusting or removing adjustments as necessary. -In core, Promotion adjustments get recalculated twice on every change to the cart; once in `Spree::OrderContents#after_add_or_remove` and in `Spree::OrderUpdater#update_promotions`. To make things more complicated, `Spree::OrderContents` leverages the `Spree::PromotionHandler#cart`, while the order updater goes through `Spree::Adjustment#recalculate`. +In Solidus Core, Promotion adjustments get recalculated twice on every change to the cart; once in `Spree::OrderContents#after_add_or_remove` and in `Spree::OrderUpdater#update_promotions`. To make things more complicated, `Spree::OrderContents` leverages the `Spree::PromotionHandler#cart`, while the order updater goes through `Spree::Adjustment#recalculate`. The design decision here is to make the code path easier to follow, and consequently to make it more performant ("Make it easy, then make it fast"). @@ -18,7 +18,7 @@ The design decision here is to make the code path easier to follow, and conseque ### Promotion Rules -Promotion rules can be applicable to either `Spree::Order`, `Spree::LineItem`, or `Spree::Shipment` objects. If they are applicable, they will be asked for eligibility. Rules applicable to orders are processed first. If a promotion has a rule that makes it inapplicable for an order, line items and shipments will not be adjusted. +Promotion rules can be applicable to either `Spree::Order`, `Spree::LineItem`, or `Spree::Shipment` objects. If they are applicable, they will be asked for eligibility. Rules applicable to orders are processed first. If a promotion has a rule that makes it ineligible for an order, line items and shipments will not be adjusted. If there are no rules that are applicable, the promotion will be considered eligible. ### Promotion Actions From 41b344f914c6f9ece889cd63e35caaa50d6bbe7e Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 1 Aug 2023 09:58:40 +0200 Subject: [PATCH 143/524] Update Readme --- friendly_promotions/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/friendly_promotions/README.md b/friendly_promotions/README.md index 8c1bc391297..5cbe02d1ee2 100644 --- a/friendly_promotions/README.md +++ b/friendly_promotions/README.md @@ -56,7 +56,7 @@ bin/rails generate solidus_friendly_promotions:install This will create the tables for this extension. It will also replace the promotion administration system under `/admin/promotions` with a new one that needs `turbo-rails`. -It will also create an initializer within which Solidus is configured to use this extension's `SimpleOrderContents` and `OrderPromotionAdjuster` classes. Feel free to override with your own implementations! +It will also create an initializer within which Solidus is configured to use this extension's `SimpleOrderContents` and `OrderDiscounter` classes. Feel free to override with your own implementations! ## Usage From 361f2551fee1dbcedf1a45214e54cc36405ec411 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 3 Aug 2023 16:09:07 +0200 Subject: [PATCH 144/524] Parameterize active? and friends These methods determine whether a promotion is currently active, but they can just as well be used to determine whether a promotion was active at a given time. --- .../solidus_friendly_promotions/promotion.rb | 28 +++++++++---------- .../promotion_spec.rb | 24 +++++++++++++++- 2 files changed, 37 insertions(+), 15 deletions(-) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb index 50cb94d9fcf..e0995df1c29 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb @@ -78,32 +78,32 @@ def usage_limit_exceeded?(excluded_orders: []) usage_count(excluded_orders: excluded_orders) >= usage_limit end - def not_expired? - !expired? + def not_expired?(time = Time.current) + !expired?(time) end - def not_started? - !started? + def not_started?(time = Time.current) + !started?(time) end - def started? - starts_at.nil? || starts_at < Time.current + def started?(time = Time.current) + starts_at.nil? || starts_at < time end - def active? - started? && not_expired? && actions.present? + def active?(time = Time.current) + started?(time) && not_expired?(time) && actions.present? end - def inactive? - !active? + def inactive?(time = Time.current) + !active?(time) end - def not_expired? - !expired? + def not_expired?(time = Time.current) + !expired?(time) end - def expired? - expires_at.present? && expires_at < Time.current + def expired?(time = Time.current) + expires_at.present? && expires_at < time end def products diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb index 450ce485c45..3376d1dab5a 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb @@ -409,7 +409,7 @@ end context 'when promotion has an action' do - let(:promotion) { create(:promotion, :with_action, name: "name1") } + let(:promotion) { create(:friendly_promotion, :with_adjustable_action, name: "name1") } it "is active if it has started already" do promotion.starts_at = Time.current - 1.day @@ -432,6 +432,28 @@ promotion.expires_at = nil expect(promotion.active?).to eq(true) end + + context "when called with a time" do + subject { promotion.active?(1.day.ago) } + + context "if promo was active a day ago" do + before do + promotion.starts_at = 2.days.ago + promotion.expires_at = 1.hour.ago + end + + it { is_expected.to be true } + end + + context "if promo was not active a day ago" do + before do + promotion.starts_at = 1.hour.ago + promotion.expires_at = 1.day.from_now + end + + it { is_expected.to be false } + end + end end end From 4f7d48a54012a0831b0dc1ce0db670d18b954253 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 3 Aug 2023 16:51:08 +0200 Subject: [PATCH 145/524] Allow `.active` scope to travel in time We can find out what was active in the past! --- .../solidus_friendly_promotions/promotion.rb | 5 ++--- .../promotion_spec.rb | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb index e0995df1c29..83c8656216e 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb @@ -16,12 +16,11 @@ class Promotion < Spree::Base validates :description, length: { maximum: 255 } validate :apply_automatically_disallowed_with_paths - scope :active, -> { has_actions.started_and_unexpired } + scope :active, ->(time = Time.current) { has_actions.started_and_unexpired(time) } scope :advertised, -> { where(advertise: true) } scope :coupons, -> { joins(:codes).distinct } - scope :started_and_unexpired, -> do + scope :started_and_unexpired, ->(time = Time.current) do table = arel_table - time = Time.current where(table[:starts_at].eq(nil).or(table[:starts_at].lt(time))). where(table[:expires_at].eq(nil).or(table[:expires_at].gt(time))) diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb index 3376d1dab5a..fb8ba937086 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb @@ -73,6 +73,24 @@ expect(subject).to match [promotion] end end + + context 'when called with a time that is not current' do + subject { described_class.active(4.days.ago) } + + let(:promotion) do + create( + :friendly_promotion, + :with_adjustable_action, + starts_at: 5.days.ago, + expires_at: 3.days.ago, + name: "name1" + ) + end + + it 'returns promotion that was active then' do + expect(subject).to match [promotion] + end + end end describe '.has_actions' do From ccae95c19b63afbcd93bc75d5e12f0d5b7c46499 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 3 Aug 2023 17:04:14 +0200 Subject: [PATCH 146/524] Deal with completed orders For orders that are complete but not shipped yet, use the promotions active at the time of order completion and recalculate the order. Shipped orders shouldn't really be changed; we return early in this case. --- .../friendly_promotion_discounter.rb | 10 +++++-- .../order_discounter.rb | 2 +- .../friendly_promotion_discounter_spec.rb | 29 +++++++++++++++++++ 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb b/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb index 061a0be395e..6fedc522ab7 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb @@ -11,6 +11,8 @@ def initialize(order) end def call + return nil if order.shipped? + OrderDiscounts.new( order_id: order.id, line_item_discounts: adjust_line_items, @@ -54,11 +56,15 @@ def connected_order_promotions eligible_connected_promotion_ids = order.friendly_order_promotions.select do |order_promotion| order_promotion.promotion_code.nil? || !order_promotion.promotion_code.usage_limit_exceeded?(excluded_orders: [order]) end.map(&:promotion_id) - order.friendly_promotions.active.where(id: eligible_connected_promotion_ids).includes(promotion_includes) + order.friendly_promotions.active(reference_time).where(id: eligible_connected_promotion_ids).includes(promotion_includes) end def sale_promotions - SolidusFriendlyPromotions::Promotion.where(apply_automatically: true).active.includes(promotion_includes) + SolidusFriendlyPromotions::Promotion.where(apply_automatically: true).active(reference_time).includes(promotion_includes) + end + + def reference_time + order.completed_at || Time.current end def promotion_includes diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb b/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb index 8333194982c..cc693364741 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb @@ -7,7 +7,7 @@ def initialize(order) end def call - all_order_discounts = SolidusFriendlyPromotions.config.discounters.map do |discounter| + all_order_discounts = SolidusFriendlyPromotions.config.discounters.filter_map do |discounter| discounter.new(order).call end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/friendly_promotion_discounter_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/friendly_promotion_discounter_spec.rb index 9965d27cb45..c4bcda58226 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/friendly_promotion_discounter_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/friendly_promotion_discounter_spec.rb @@ -52,4 +52,33 @@ end end end + + context "promotions in the past" do + let(:order) { create(:order, completed_at: 7.days.ago) } + let(:currently_active_promotion) { create(:friendly_promotion, :with_adjustable_action, starts_at: 1.hour.ago) } + let(:past_promotion) { create(:friendly_promotion, :with_adjustable_action, starts_at: 1.year.ago, expires_at: 11.months.ago) } + let(:order_promotion) { create(:friendly_promotion, :with_adjustable_action, starts_at: 8.days.ago, expires_at: 6.days.ago) } + + before do + order.friendly_promotions << past_promotion + order.friendly_promotions << order_promotion + order.friendly_promotions << currently_active_promotion + end + + subject { described_class.new(order) } + + it "only evaluates the past promotion that was active when the order was completed" do + expect(subject.promotions).to eq([order_promotion]) + end + end + + context "shipped orders" do + let(:order) { create(:order, shipment_state: "shipped") } + + subject { described_class.new(order).call } + + it "returns false" do + expect(subject).to be false + end + end end From f253c407bd25f26d54f7937121fceb779d7e9487 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Mon, 11 Sep 2023 09:49:37 +0200 Subject: [PATCH 147/524] Fix red test suite --- .../friendly_promotion_discounter_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/friendly_promotion_discounter_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/friendly_promotion_discounter_spec.rb index c4bcda58226..3574adadf0e 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/friendly_promotion_discounter_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/friendly_promotion_discounter_spec.rb @@ -77,8 +77,8 @@ subject { described_class.new(order).call } - it "returns false" do - expect(subject).to be false + it "returns nil" do + expect(subject).to be nil end end end From 6d67420c0527d2f97262c703a370721ce3d5e221 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 26 Sep 2023 18:24:12 +0200 Subject: [PATCH 148/524] Initializer: Use new MenuItem API from Solidus 4.2 onwards The API for the Menu Item has recently changed in Solidus' `main` branch. This commit makes the initializer work for both APIs. --- .../install/templates/initializer.rb | 43 ++++++++++++++----- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb index ce286030f46..67f7f40de0d 100644 --- a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb +++ b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb @@ -6,17 +6,40 @@ # Replace the promotions menu from core with ours Spree::Backend::Config.configure do |config| - config.menu_items = Spree::Backend::Config.menu_items.map do |item| - next item unless item.url == :admin_promotions_path + config.menu_items = config.menu_items.map do |item| + next item unless item.label.to_sym == :promotions - Spree::BackendConfiguration::MenuItem.new( - [:promotions, :promotion_categories], - 'bullhorn', - partial: 'spree/admin/shared/promotion_sub_menu', - condition: -> { can?(:admin, Spree::Promotion) }, - url: -> { SolidusFriendlyPromotions::Engine.routes.url_helpers.admin_promotions_path }, - position: 2 - ) + # The API of the MenuItem class changes in Solidus 4.2.0 + if item.respond_to?(:children) + Spree::BackendConfiguration::MenuItem.new( + label: :promotions, + icon: 'bullhorn', + condition: -> { can?(:admin, SolidusFriendlyPromotions::Promotion) }, + url: -> { SolidusFriendlyPromotions::Engine.routes.url_helpers.admin_promotions_path }, + data_hook: :admin_promotion_sub_tabs, + children: [ + Spree::BackendConfiguration::MenuItem.new( + label: :promotions, + url: -> { SolidusFriendlyPromotions::Engine.routes.url_helpers.admin_promotions_path }, + condition: -> { can?(:admin, SolidusFriendlyPromotions::Promotion) }, + ), + Spree::BackendConfiguration::MenuItem.new( + label: :promotion_categories, + url: -> { SolidusFriendlyPromotions::Engine.routes.url_helpers.admin_promotion_categories_path }, + condition: -> { can?(:admin, SolidusFriendlyPromotions::PromotionCategory) }, + ), + ], + ) + else + Spree::BackendConfiguration::MenuItem.new( + [:promotions, :promotion_categories], + 'bullhorn', + partial: 'spree/admin/shared/promotion_sub_menu', + condition: -> { can?(:admin, Spree::Promotion) }, + url: -> { SolidusFriendlyPromotions::Engine.routes.url_helpers.admin_promotions_path }, + position: 2 + ) + end end end From 5c66b8c1cc08ebd88fcd9b0ddb18dab394a12626 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 26 Sep 2023 23:07:17 +0200 Subject: [PATCH 149/524] Promotion Actions: Implement "edit" action Previously, when changing the promotion calculator type for an action, we would end up with an unrescued 500 error. This implements editing an action. --- .../admin/promotion_actions_controller.rb | 12 +++++-- .../admin/promotion_actions/edit.html.erb | 36 ++++++++----------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb index 69b307f8d6e..19dcabdd18b 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb @@ -4,8 +4,8 @@ module SolidusFriendlyPromotions module Admin class PromotionActionsController < Spree::Admin::BaseController before_action :validate_level, only: :new - before_action :load_promotion, only: [:create, :destroy, :new, :update] - before_action :validate_promotion_action_type, only: :create + before_action :load_promotion, only: [:create, :destroy, :new, :update, :edit] + before_action :validate_promotion_action_type, only: [:create, :edit] def new if params.dig(:promotion_action, :type) @@ -31,6 +31,14 @@ def create end end + def edit + @promotion_action = @promotion.actions.find(params[:id]) + if params.dig(:promotion_action, :calculator_type) + @promotion_action.calculator_type = params[:promotion_action][:calculator_type] + end + render layout: false + end + def update @promotion_action = @promotion.actions.find(params[:id]) @promotion_action.assign_attributes(promotion_action_params) diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/edit.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/edit.html.erb index c2f4cdef315..8103697d430 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/edit.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_actions/edit.html.erb @@ -2,30 +2,22 @@
    <%= @promotion_action.class.human_attribute_name(:description) %>
    <%= link_to_with_icon 'trash', '', solidus_friendly_promotions.admin_promotion_promotion_action_path(@promotion, @promotion_action), method: :delete, class: 'delete' %> -
    -
    - <%= render 'calculator_select', path: solidus_friendly_promotions.edit_admin_promotion_promotion_action_path(@promotion, @promotion_action), promotion_action: @promotion_action %> + <%= render 'calculator_select', path: solidus_friendly_promotions.edit_admin_promotion_promotion_action_path(@promotion, @promotion_action), promotion_action: @promotion_action %> - <%= - form_with( - model: @promotion_action, - scope: :promotion_action, - url: solidus_friendly_promotions.admin_promotion_promotion_action_path(@promotion, @promotion_action), - data: { turbo: false } - ) do |form| %> - <%= render "form", form: form %> + <%= + form_with( + model: @promotion_action, + scope: :promotion_action, + url: solidus_friendly_promotions.admin_promotion_promotion_action_path(@promotion, @promotion_action), + ) do |form| %> + <%= render "form", form: form %> -
    -
    - <%= button_tag "Update", class: "btn btn-secondary float-right" %> -
    -
    - - <% end %> -
    -
    - BUTTON +
    +
    + <%= button_tag "Update", class: "btn btn-secondary float-right" %> +
    -
    + + <% end %>
    <% end %> From cf32993bdf5356849c8bd4dc9154c9070469879e Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 27 Sep 2023 17:28:12 +0200 Subject: [PATCH 150/524] Ignore sandbox and dummy app These apps are not our code, so we don't lint them. --- friendly_promotions/.standard.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 friendly_promotions/.standard.yml diff --git a/friendly_promotions/.standard.yml b/friendly_promotions/.standard.yml new file mode 100644 index 00000000000..8a60962ff40 --- /dev/null +++ b/friendly_promotions/.standard.yml @@ -0,0 +1,4 @@ +ruby_version: 3.0 +ignore: + - 'sandbox/**/*' + - 'spec/dummy/**/*' From 6a1673a4a759a97613558d625a64f247d6e0f6bd Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 27 Sep 2023 17:10:24 +0200 Subject: [PATCH 151/524] Apply standardrb --fix --- .../admin/promotion_actions_controller.rb | 10 +-- .../promotion_code_batches_controller.rb | 2 +- .../admin/promotion_codes_controller.rb | 10 +-- .../admin/promotion_rules_controller.rb | 12 +-- .../admin/promotions_controller.rb | 18 ++-- .../order_decorator.rb | 2 +- .../promotion_code_batch_job.rb | 10 +-- .../app/models/solidus_friendly_promotions.rb | 2 +- .../calculators/distributed_amount.rb | 2 +- .../calculators/flat_rate.rb | 4 +- .../calculators/flexi_rate.rb | 10 +-- .../calculators/percent.rb | 2 +- .../calculators/tiered_flat_rate.rb | 6 +- .../calculators/tiered_percent.rb | 8 +- .../friendly_promotion_discounter.rb | 2 +- .../order_promotion.rb | 6 +- .../solidus_friendly_promotions/promotion.rb | 42 +++++----- .../promotion_action.rb | 6 +- .../promotion_code.rb | 20 ++--- .../promotion_code/batch_builder.rb | 4 +- .../promotion_code_batch.rb | 2 +- .../promotion_rule.rb | 4 +- .../rules/first_repeat_purchase_since.rb | 2 +- .../rules/line_item_taxon.rb | 2 +- .../rules/nth_order.rb | 2 +- .../rules/product.rb | 6 +- .../rules/shipping_method.rb | 2 +- .../rules/taxon.rb | 14 ++-- .../solidus_friendly_promotions/rules/user.rb | 2 +- .../admin/promotion_codes/index.csv.ruby | 2 +- friendly_promotions/config/routes.rb | 2 +- ...30703141116_create_promotion_categories.rb | 2 +- .../20230703143943_create_promotion_rules.rb | 2 +- .../migrate/20230704083830_add_rule_tables.rb | 14 ++-- ...20230704093625_create_promotion_actions.rb | 2 +- .../20230704102444_create_promotion_codes.rb | 2 +- ...704102656_create_promotion_code_batches.rb | 2 +- ...171556_create_friendly_order_promotions.rb | 6 +- ...25074235_create_shipping_rate_discounts.rb | 4 +- .../install/templates/initializer.rb | 26 +++--- .../configuration.rb | 2 +- .../friendly_order_promotion_factory.rb | 2 +- .../friendly_promotion_category_factory.rb | 4 +- .../friendly_promotion_code_factory.rb | 2 +- .../friendly_promotion_factory.rb | 8 +- ...friendly_shipping_rate_discount_factory.rb | 2 +- .../promotion_code_batch_job_spec.rb | 12 +-- .../testing_support/factories_spec.rb | 2 +- .../promotion_code_batch_mailer_spec.rb | 2 +- .../actions/adjust_line_item_spec.rb | 2 +- .../actions/adjust_shipment_spec.rb | 2 +- .../calculators/distributed_amount_spec.rb | 18 ++-- .../calculators/flat_rate_spec.rb | 6 +- .../calculators/flexi_rate_spec.rb | 8 +- .../calculators/percent_spec.rb | 6 +- .../calculators/tiered_flat_rate_spec.rb | 28 +++---- .../calculators/tiered_percent_spec.rb | 30 +++---- .../distributed_amounts_handler_spec.rb | 6 +- .../friendly_promotion_discounter_spec.rb | 22 ++--- .../item_discount_spec.rb | 2 +- .../order_discounter_spec.rb | 4 +- .../order_promotion_spec.rb | 2 +- .../products_promotion_rule_spec.rb | 2 +- .../promotion_category_spec.rb | 10 +-- .../promotion_code_batch_spec.rb | 6 +- .../promotion_code_spec.rb | 56 ++++++------- .../promotion_rule_spec.rb | 2 +- .../promotion_rules_store_spec.rb | 2 +- .../promotion_rules_taxon_spec.rb | 2 +- .../promotion_rules_user_spec.rb | 2 +- .../promotion_spec.rb | 68 +++++++-------- .../rules/item_total_spec.rb | 2 +- .../rules/option_value_spec.rb | 10 +-- .../simple_order_contents_spec.rb | 84 +++++++++---------- .../spec/models/spree/shipping_rate_spec.rb | 6 +- .../admin/promotion_actions_request_spec.rb | 6 +- .../admin/promotion_rules_request_spec.rb | 10 +-- .../admin/promotions_request_spec.rb | 2 +- .../calculator_shared_examples.rb | 2 +- .../admin/promotion_categories_spec.rb | 12 +-- .../admin/promotions_spec.rb | 12 +-- 81 files changed, 373 insertions(+), 373 deletions(-) diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb index 19dcabdd18b..6d379ee3eba 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb @@ -24,7 +24,7 @@ def create @promotion_action.promotion = @promotion if @promotion_action.save(validate: false) flash[:success] = - t('spree.successfully_created', resource: SolidusFriendlyPromotions::PromotionAction.model_name.human) + t("spree.successfully_created", resource: SolidusFriendlyPromotions::PromotionAction.model_name.human) redirect_to location_after_save, format: :html else render :new, layout: false @@ -44,7 +44,7 @@ def update @promotion_action.assign_attributes(promotion_action_params) if @promotion_action.save flash[:success] = - t('spree.successfully_updated', resource: SolidusFriendlyPromotions::PromotionAction.model_name.human) + t("spree.successfully_updated", resource: SolidusFriendlyPromotions::PromotionAction.model_name.human) redirect_to location_after_save, format: :html else render :edit @@ -55,7 +55,7 @@ def destroy @promotion_action = @promotion.actions.find(params[:id]) if @promotion_action.discard flash[:success] = - t('spree.successfully_removed', resource: SolidusFriendlyPromotions::PromotionAction.model_name.human) + t("spree.successfully_removed", resource: SolidusFriendlyPromotions::PromotionAction.model_name.human) end redirect_to location_after_save, format: :html end @@ -92,10 +92,10 @@ def validate_promotion_action_type end return if @promotion_action_type - flash[:error] = t('solidus_friendly_promotions.invalid_promotion_action') + flash[:error] = t("solidus_friendly_promotions.invalid_promotion_action") respond_to do |format| format.html { redirect_to solidus_friendly_promotions.edit_admin_promotion_path(@promotion) } - format.js { render layout: false } + format.js { render layout: false } end end end diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_code_batches_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_code_batches_controller.rb index 44cea4fde55..24d5be4ddc8 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_code_batches_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_code_batches_controller.rb @@ -3,7 +3,7 @@ module SolidusFriendlyPromotions module Admin class PromotionCodeBatchesController < Spree::Admin::ResourceController - belongs_to 'solidus_friendly_promotions/promotion' + belongs_to "solidus_friendly_promotions/promotion" create.after :build_promotion_code_batch diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_codes_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_codes_controller.rb index 38ee1184eeb..291e3d3b315 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_codes_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_codes_controller.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'csv' +require "csv" module SolidusFriendlyPromotions module Admin @@ -26,7 +26,7 @@ def new if @promotion.apply_automatically flash[:error] = t( :disallowed_with_apply_automatically, - scope: 'activerecord.errors.models.solidus_friendly_promotions/promotion_code.attributes.base' + scope: "activerecord.errors.models.solidus_friendly_promotions/promotion_code.attributes.base" ) redirect_to solidus_friendly_promotions.admin_promotion_promotion_codes_url(@promotion) else @@ -49,9 +49,9 @@ def create private def load_promotion - @promotion = SolidusFriendlyPromotions::Promotion. - accessible_by(current_ability, :show). - find(params[:promotion_id]) + @promotion = SolidusFriendlyPromotions::Promotion + .accessible_by(current_ability, :show) + .find(params[:promotion_id]) end end end diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb index fb4e960e38a..41d234a1d44 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb @@ -3,7 +3,7 @@ module SolidusFriendlyPromotions module Admin class PromotionRulesController < Spree::Admin::BaseController - helper 'spree/promotion_rules' + helper "spree/promotion_rules" before_action :validate_level, only: [:new, :create] before_action :load_promotion, only: [:create, :destroy, :update, :new] @@ -23,7 +23,7 @@ def create ) if @promotion_rule.save flash[:success] = - t('spree.successfully_created', resource: SolidusFriendlyPromotions::PromotionRule.model_name.human) + t("spree.successfully_created", resource: SolidusFriendlyPromotions::PromotionRule.model_name.human) end redirect_to location_after_save end @@ -33,7 +33,7 @@ def update @promotion_rule.assign_attributes(promotion_rule_params) if @promotion_rule.save flash[:success] = - t('spree.successfully_updated', resource: SolidusFriendlyPromotions::PromotionRule.model_name.human) + t("spree.successfully_updated", resource: SolidusFriendlyPromotions::PromotionRule.model_name.human) end redirect_to location_after_save end @@ -42,7 +42,7 @@ def destroy @promotion_rule = @promotion.rules.find(params[:id]) if @promotion_rule.destroy flash[:success] = - t('spree.successfully_removed', resource: SolidusFriendlyPromotions::PromotionRule.model_name.human) + t("spree.successfully_removed", resource: SolidusFriendlyPromotions::PromotionRule.model_name.human) end redirect_to location_after_save end @@ -69,10 +69,10 @@ def validate_promotion_rule_type end return if @promotion_rule_type - flash[:error] = t('solidus_friendly_promotions.invalid_promotion_rule') + flash[:error] = t("solidus_friendly_promotions.invalid_promotion_rule") respond_to do |format| format.html { redirect_to solidus_friendly_promotions.edit_admin_promotion_path(@promotion) } - format.js { render layout: false } + format.js { render layout: false } end end diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb index 46ecf67558c..ef5bb8bbdbf 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb @@ -5,8 +5,8 @@ module Admin class PromotionsController < ::Spree::Admin::ResourceController before_action :load_data - helper 'solidus_friendly_promotions/admin/promotion_rules' - helper 'solidus_friendly_promotions/admin/promotion_actions' + helper "solidus_friendly_promotions/admin/promotion_rules" + helper "solidus_friendly_promotions/admin/promotion_actions" def create @promotion = model_class.new(permitted_resource_params) @@ -18,11 +18,11 @@ def create if @promotion.save @code_batch&.process - flash[:success] = t('solidus_friendly_promotions.promotion_successfully_created') + flash[:success] = t("solidus_friendly_promotions.promotion_successfully_created") redirect_to location_after_save else flash[:error] = @promotion.errors.full_messages.to_sentence - render action: 'new' + render action: "new" end end @@ -32,14 +32,14 @@ def collection return @collection if @collection params[:q] ||= HashWithIndifferentAccess.new - params[:q][:s] ||= 'id desc' + params[:q][:s] ||= "id desc" @collection = super @search = @collection.ransack(params[:q]) - @collection = @search.result(distinct: true). - includes(promotion_includes). - page(params[:page]). - per(params[:per_page] || SolidusFriendlyPromotions.config.promotions_per_page) + @collection = @search.result(distinct: true) + .includes(promotion_includes) + .page(params[:page]) + .per(params[:per_page] || SolidusFriendlyPromotions.config.promotions_per_page) @collection end diff --git a/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_decorator.rb b/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_decorator.rb index 991f7d7c4ad..23e13c39abc 100644 --- a/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_decorator.rb +++ b/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_decorator.rb @@ -14,7 +14,7 @@ def ensure_promotions_eligible if promo_total_changed? restart_checkout_flow recalculate - errors.add(:base, I18n.t('solidus_friendly_promotions.promotion_total_changed_before_complete')) + errors.add(:base, I18n.t("solidus_friendly_promotions.promotion_total_changed_before_complete")) end super diff --git a/friendly_promotions/app/jobs/solidus_friendly_promotions/promotion_code_batch_job.rb b/friendly_promotions/app/jobs/solidus_friendly_promotions/promotion_code_batch_job.rb index e580d7dfead..9de31e63471 100644 --- a/friendly_promotions/app/jobs/solidus_friendly_promotions/promotion_code_batch_job.rb +++ b/friendly_promotions/app/jobs/solidus_friendly_promotions/promotion_code_batch_job.rb @@ -11,14 +11,14 @@ def perform(promotion_code_batch) if promotion_code_batch.email? SolidusFriendlyPromotions.config.promotion_code_batch_mailer_class - .promotion_code_batch_finished(promotion_code_batch) - .deliver_now + .promotion_code_batch_finished(promotion_code_batch) + .deliver_now end - rescue StandardError => e + rescue => e if promotion_code_batch.email? SolidusFriendlyPromotions.config.promotion_code_batch_mailer_class - .promotion_code_batch_errored(promotion_code_batch) - .deliver_now + .promotion_code_batch_errored(promotion_code_batch) + .deliver_now end raise e end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions.rb b/friendly_promotions/app/models/solidus_friendly_promotions.rb index db8181d1339..62b0bdab0d4 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions.rb @@ -2,6 +2,6 @@ module SolidusFriendlyPromotions def self.table_name_prefix - 'friendly_' + "friendly_" end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/distributed_amount.rb b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/distributed_amount.rb index c2d940b3af2..05778fac1b9 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/distributed_amount.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/distributed_amount.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_dependency 'spree/calculator' +require_dependency "spree/calculator" # This is a calculator for line item adjustment actions. It accepts a line item # and calculates its weighted adjustment amount based on the value of the diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/flat_rate.rb b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/flat_rate.rb index 0b6bab74482..9b318340860 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/flat_rate.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/flat_rate.rb @@ -1,12 +1,12 @@ # frozen_string_literal: true -require_dependency 'spree/calculator' +require_dependency "spree/calculator" module SolidusFriendlyPromotions module Calculators class FlatRate < Spree::Calculator preference :amount, :decimal, default: 0 - preference :currency, :string, default: ->{ Spree::Config[:currency] } + preference :currency, :string, default: -> { Spree::Config[:currency] } def compute(object = nil) currency = object.order.currency diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/flexi_rate.rb b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/flexi_rate.rb index 29efc3e9605..af3f1e212cb 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/flexi_rate.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/flexi_rate.rb @@ -1,20 +1,20 @@ # frozen_string_literal: true -require_dependency 'spree/calculator' +require_dependency "spree/calculator" module SolidusFriendlyPromotions module Calculators class FlexiRate < Spree::Calculator - preference :first_item, :decimal, default: 0 + preference :first_item, :decimal, default: 0 preference :additional_item, :decimal, default: 0 - preference :max_items, :integer, default: 0 - preference :currency, :string, default: ->{ Spree::Config[:currency] } + preference :max_items, :integer, default: 0 + preference :currency, :string, default: -> { Spree::Config[:currency] } def compute(object) items_count = object.quantity items_count = [items_count, preferred_max_items].min unless preferred_max_items.zero? - return BigDecimal('0') if items_count == 0 + return BigDecimal("0") if items_count == 0 additional_items_count = items_count - 1 preferred_first_item + preferred_additional_item * additional_items_count diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/percent.rb b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/percent.rb index 1ab796788e9..8864904f254 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/percent.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/percent.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_dependency 'spree/calculator' +require_dependency "spree/calculator" module SolidusFriendlyPromotions module Calculators diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_flat_rate.rb b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_flat_rate.rb index b09845a14f2..1fda3e91a05 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_flat_rate.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_flat_rate.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_dependency 'spree/calculator' +require_dependency "spree/calculator" module SolidusFriendlyPromotions module Calculators @@ -39,12 +39,12 @@ def compute_item(object) def cast_to_d(value) value.to_s.to_d rescue ArgumentError - BigDecimal('0') + BigDecimal("0") end def preferred_tiers_content if preferred_tiers.is_a? Hash - unless preferred_tiers.keys.all?{ |key| key.is_a?(Numeric) && key > 0 } + unless preferred_tiers.keys.all? { |key| key.is_a?(Numeric) && key > 0 } errors.add(:base, :keys_should_be_positive_number) end else diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_percent.rb b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_percent.rb index 28b725eee2f..01819ca3919 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_percent.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_percent.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_dependency 'spree/calculator' +require_dependency "spree/calculator" module SolidusFriendlyPromotions module Calculators @@ -46,15 +46,15 @@ def compute_item(object) def cast_to_d(value) value.to_s.to_d rescue ArgumentError - BigDecimal('0') + BigDecimal("0") end def preferred_tiers_content if preferred_tiers.is_a? Hash - unless preferred_tiers.keys.all?{ |key| key.is_a?(Numeric) && key > 0 } + unless preferred_tiers.keys.all? { |key| key.is_a?(Numeric) && key > 0 } errors.add(:base, :keys_should_be_positive_number) end - unless preferred_tiers.values.all?{ |key| key.is_a?(Numeric) && key >= 0 && key <= 100 } + unless preferred_tiers.values.all? { |key| key.is_a?(Numeric) && key >= 0 && key <= 100 } errors.add(:base, :values_should_be_percent) end else diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb b/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb index 6fedc522ab7..1401bd811e2 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb @@ -70,7 +70,7 @@ def reference_time def promotion_includes [ :rules, - :actions, + :actions ] end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/order_promotion.rb b/friendly_promotions/app/models/solidus_friendly_promotions/order_promotion.rb index 623cf9154e2..46533d6d98c 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/order_promotion.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/order_promotion.rb @@ -6,9 +6,9 @@ module SolidusFriendlyPromotions # 1. A promotion that a user attempted to apply to their order # 2. The specific code that they used class OrderPromotion < Spree::Base - belongs_to :order, class_name: 'Spree::Order' - belongs_to :promotion, class_name: 'SolidusFriendlyPromotions::Promotion' - belongs_to :promotion_code, class_name: 'SolidusFriendlyPromotions::PromotionCode', optional: true + belongs_to :order, class_name: "Spree::Order" + belongs_to :promotion, class_name: "SolidusFriendlyPromotions::Promotion" + belongs_to :promotion_code, class_name: "SolidusFriendlyPromotions::PromotionCode", optional: true validates :promotion_code, presence: true, if: :require_promotion_code? diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb index 83c8656216e..02189238141 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb @@ -10,10 +10,10 @@ class Promotion < Spree::Base has_many :code_batches, class_name: "SolidusFriendlyPromotions::PromotionCodeBatch" validates :name, presence: true - validates :path, uniqueness: { allow_blank: true, case_sensitive: true } - validates :usage_limit, numericality: { greater_than: 0, allow_nil: true } - validates :per_code_usage_limit, numericality: { greater_than_or_equal_to: 0, allow_nil: true } - validates :description, length: { maximum: 255 } + validates :path, uniqueness: {allow_blank: true, case_sensitive: true} + validates :usage_limit, numericality: {greater_than: 0, allow_nil: true} + validates :per_code_usage_limit, numericality: {greater_than_or_equal_to: 0, allow_nil: true} + validates :description, length: {maximum: 255} validate :apply_automatically_disallowed_with_paths scope :active, ->(time = Time.current) { has_actions.started_and_unexpired(time) } @@ -22,22 +22,22 @@ class Promotion < Spree::Base scope :started_and_unexpired, ->(time = Time.current) do table = arel_table - where(table[:starts_at].eq(nil).or(table[:starts_at].lt(time))). - where(table[:expires_at].eq(nil).or(table[:expires_at].gt(time))) + where(table[:starts_at].eq(nil).or(table[:starts_at].lt(time))) + .where(table[:expires_at].eq(nil).or(table[:expires_at].gt(time))) end scope :has_actions, -> do joins(:actions).distinct end - self.allowed_ransackable_associations = ['codes'] + self.allowed_ransackable_associations = ["codes"] self.allowed_ransackable_attributes = %w[name path promotion_category_id] self.allowed_ransackable_scopes = %i[active] # All orders that have been discounted using this promotion def discounted_orders - Spree::Order. - joins(:all_adjustments). - where( + Spree::Order + .joins(:all_adjustments) + .where( spree_adjustments: { source_type: "SolidusFriendlyPromotions::PromotionAction", source_id: actions.map(&:id), @@ -51,20 +51,20 @@ def discounted_orders # @param excluded_orders [Array] Orders to exclude from usage count # @return [Integer] usage count def usage_count(excluded_orders: []) - discounted_orders. - complete. - where.not(id: [excluded_orders.map(&:id)]). - where.not(spree_orders: { state: :canceled }). - count + discounted_orders + .complete + .where.not(id: [excluded_orders.map(&:id)]) + .where.not(spree_orders: {state: :canceled}) + .count end def used_by?(user, excluded_orders = []) - discounted_orders. - complete. - where.not(id: excluded_orders.map(&:id)). - where(user: user). - where.not(spree_orders: { state: :canceled }). - exists? + discounted_orders + .complete + .where.not(id: excluded_orders.map(&:id)) + .where(user: user) + .where.not(spree_orders: {state: :canceled}) + .exists? end # Whether the promotion has exceeded its usage restrictions. diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb index 2388485cadc..27eb152b3b2 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spree/preferences/persistable' +require "spree/preferences/persistable" module SolidusFriendlyPromotions # Base class for all types of promotion action. @@ -37,7 +37,7 @@ def discount(adjustable) # Ensure a negative amount which does not exceed the object's amount def compute_amount(adjustable) - promotion_amount = calculator.compute(adjustable) || BigDecimal('0') + promotion_amount = calculator.compute(adjustable) || BigDecimal("0") [adjustable.amount, promotion_amount.abs].min * -1 end @@ -45,7 +45,7 @@ def adjustment_label(adjustable) I18n.t( "solidus_friendly_promotions.adjustment_labels.#{adjustable.class.name.demodulize.underscore}", promotion: SolidusFriendlyPromotions::Promotion.model_name.human, - promotion_name: promotion.name, + promotion_name: promotion.name ) end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code.rb index 0f2a7c82fbd..943bcc478c9 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code.rb @@ -8,10 +8,10 @@ class PromotionCode < Spree::Base before_validation :normalize_code - validates :value, presence: true, uniqueness: { allow_blank: true, case_sensitive: true } + validates :value, presence: true, uniqueness: {allow_blank: true, case_sensitive: true} validate :promotion_not_apply_automatically, on: :create - self.allowed_ransackable_attributes = ['value'] + self.allowed_ransackable_attributes = ["value"] # Whether the promotion code has exceeded its usage restrictions # @@ -28,14 +28,14 @@ def usage_limit_exceeded?(excluded_orders: []) # @param excluded_orders [Array] Orders to exclude from usage count # @return [Integer] usage count def usage_count(excluded_orders: []) - promotion. - discounted_orders. - complete. - where.not(spree_orders: { state: :canceled }). - joins(:friendly_order_promotions). - where(friendly_order_promotions: { promotion_code_id: id }). - where.not(id: excluded_orders.map(&:id)). - count + promotion + .discounted_orders + .complete + .where.not(spree_orders: {state: :canceled}) + .joins(:friendly_order_promotions) + .where(friendly_order_promotions: {promotion_code_id: id}) + .where.not(id: excluded_orders.map(&:id)) + .count end def usage_limit diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code/batch_builder.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code/batch_builder.rb index 58ba96fb692..f40dcd43f44 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code/batch_builder.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code/batch_builder.rb @@ -10,7 +10,7 @@ class BatchBuilder DEFAULT_OPTIONS = { random_code_length: 6, batch_size: 1000, - sample_characters: ('a'..'z').to_a + (2..9).to_a.map(&:to_s) + sample_characters: ("a".."z").to_a + (2..9).to_a.map(&:to_s) }.freeze def initialize(promotion_code_batch, options = {}) @@ -22,7 +22,7 @@ def initialize(promotion_code_batch, options = {}) def build_promotion_codes generate_random_codes promotion_code_batch.update!(state: "completed") - rescue StandardError => e + rescue => e promotion_code_batch.update!( error: e.inspect, state: "failed" diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code_batch.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code_batch.rb index cbb33bfb59b..85d076d1c1b 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code_batch.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_code_batch.rb @@ -8,7 +8,7 @@ class CantProcessStartedBatch < StandardError belongs_to :promotion has_many :promotion_codes, dependent: :destroy - validates :number_of_codes, numericality: { greater_than: 0 } + validates :number_of_codes, numericality: {greater_than: 0} validates :base_code, :number_of_codes, presence: true def finished? diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rule.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rule.rb index 79cf24e4d69..9d4e1f74d98 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rule.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_rule.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spree/preferences/persistable' +require "spree/preferences/persistable" module SolidusFriendlyPromotions class PromotionRule < Spree::Base @@ -45,7 +45,7 @@ def unique_per_promotion end def eligibility_error_message(key, options = {}) - I18n.t(key, **{ scope: [:spree, :eligibility_errors, :messages] }.merge(options)) + I18n.t(key, **{scope: [:spree, :eligibility_errors, :messages]}.merge(options)) end end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb index 1e23f2b2a98..dfca964597d 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb @@ -4,7 +4,7 @@ module SolidusFriendlyPromotions module Rules class FirstRepeatPurchaseSince < PromotionRule preference :days_ago, :integer, default: 365 - validates :preferred_days_ago, numericality: { only_integer: true, greater_than: 0 } + validates :preferred_days_ago, numericality: {only_integer: true, greater_than: 0} # This promotion is applicable to orders only. def applicable?(promotable) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb index 1308aacde98..e0fd27b693f 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb @@ -9,7 +9,7 @@ class LineItemTaxon < PromotionRule MATCH_POLICIES = %w[include exclude].freeze - validates :preferred_match_policy, inclusion: { in: MATCH_POLICIES } + validates :preferred_match_policy, inclusion: {in: MATCH_POLICIES} preference :match_policy, :string, default: MATCH_POLICIES.first def applicable?(promotable) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb index f542ef13250..04cb592bd6c 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb @@ -6,7 +6,7 @@ class NthOrder < PromotionRule preference :nth_order, :integer, default: 2 # It does not make sense to have this apply to the first order using preferred_nth_order == 1 # Instead we could use the first_order rule - validates :preferred_nth_order, numericality: { only_integer: true, greater_than: 1 } + validates :preferred_nth_order, numericality: {only_integer: true, greater_than: 1} # This promotion is applicable to orders only. def applicable?(promotable) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb index c5bbd4503be..c7a5d83732e 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb @@ -10,8 +10,8 @@ class Product < PromotionRule has_many :products_promotion_rules, dependent: :destroy, foreign_key: :promotion_rule_id, - class_name: 'SolidusFriendlyPromotions::ProductsPromotionRule' - has_many :products, class_name: 'Spree::Product', through: :products_promotion_rules + class_name: "SolidusFriendlyPromotions::ProductsPromotionRule" + has_many :products, class_name: "Spree::Product", through: :products_promotion_rules def preload_relations [:products] @@ -19,7 +19,7 @@ def preload_relations MATCH_POLICIES = %w[any all none].freeze - validates :preferred_match_policy, inclusion: { in: MATCH_POLICIES } + validates :preferred_match_policy, inclusion: {in: MATCH_POLICIES} preference :match_policy, :string, default: MATCH_POLICIES.first diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/shipping_method.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/shipping_method.rb index 46b118f9002..eb745f7605e 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/shipping_method.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/shipping_method.rb @@ -10,7 +10,7 @@ def applicable?(promotable) end def eligible?(promotable) - promotable.shipping_method&.id.in?(preferred_shipping_method_ids.map(&:to_i)) + promotable.shipping_method&.id&.in?(preferred_shipping_method_ids.map(&:to_i)) end def updateable? diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb index f972e4c1160..9130cadcfe2 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb @@ -3,9 +3,9 @@ module SolidusFriendlyPromotions module Rules class Taxon < PromotionRule - has_many :promotion_rules_taxons, class_name: 'SolidusFriendlyPromotions::PromotionRulesTaxon', foreign_key: :promotion_rule_id, + has_many :promotion_rules_taxons, class_name: "SolidusFriendlyPromotions::PromotionRulesTaxon", foreign_key: :promotion_rule_id, dependent: :destroy - has_many :taxons, through: :promotion_rules_taxons, class_name: 'Spree::Taxon' + has_many :taxons, through: :promotion_rules_taxons, class_name: "Spree::Taxon" def preload_relations [:taxons] @@ -13,7 +13,7 @@ def preload_relations MATCH_POLICIES = %w[any all none].freeze - validates :preferred_match_policy, inclusion: { in: MATCH_POLICIES } + validates :preferred_match_policy, inclusion: {in: MATCH_POLICIES} preference :match_policy, :string, default: MATCH_POLICIES.first def applicable?(promotable) @@ -71,10 +71,10 @@ def updateable? # All taxons in an order def taxons_in_order(order) - Spree::Taxon. - joins(products: { variants_including_master: :line_items }). - where(spree_line_items: { order_id: order.id }). - distinct + Spree::Taxon + .joins(products: {variants_including_master: :line_items}) + .where(spree_line_items: {order_id: order.id}) + .distinct end # ids of taxons rules and taxons rules children diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb index 98b2d94c0f0..1cb7e9fd9da 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb @@ -4,7 +4,7 @@ module SolidusFriendlyPromotions module Rules class User < PromotionRule has_many :promotion_rules_users, - class_name: 'SolidusFriendlyPromotions::PromotionRulesUser', + class_name: "SolidusFriendlyPromotions::PromotionRulesUser", foreign_key: :promotion_rule_id, dependent: :destroy has_many :users, through: :promotion_rules_users, class_name: Spree::UserClassHandle.new diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_codes/index.csv.ruby b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_codes/index.csv.ruby index 9299279b0e0..288b953b196 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_codes/index.csv.ruby +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_codes/index.csv.ruby @@ -1,7 +1,7 @@ # frozen_string_literal: true CSV.generate do |csv| - csv << ['Code'] + csv << ["Code"] @promotion_codes.order(:id).pluck(:value).each do |value| csv << [value] end diff --git a/friendly_promotions/config/routes.rb b/friendly_promotions/config/routes.rb index 1dd211b9c62..911a8da73f4 100644 --- a/friendly_promotions/config/routes.rb +++ b/friendly_promotions/config/routes.rb @@ -9,7 +9,7 @@ resources :promotion_actions resources :promotion_codes, only: [:index, :new, :create] resources :promotion_code_batches, only: [:index, :new, :create] do - get '/download', to: "promotion_code_batches#download", defaults: { format: "csv" } + get "/download", to: "promotion_code_batches#download", defaults: {format: "csv"} end end end diff --git a/friendly_promotions/db/migrate/20230703141116_create_promotion_categories.rb b/friendly_promotions/db/migrate/20230703141116_create_promotion_categories.rb index 97b1499d7ab..f8e2518db84 100644 --- a/friendly_promotions/db/migrate/20230703141116_create_promotion_categories.rb +++ b/friendly_promotions/db/migrate/20230703141116_create_promotion_categories.rb @@ -9,6 +9,6 @@ def change add_reference :friendly_promotions, :promotion_category, - foreign_key: { to_table: :friendly_promotion_categories } + foreign_key: {to_table: :friendly_promotion_categories} end end diff --git a/friendly_promotions/db/migrate/20230703143943_create_promotion_rules.rb b/friendly_promotions/db/migrate/20230703143943_create_promotion_rules.rb index 980ac111d56..3a382c42e13 100644 --- a/friendly_promotions/db/migrate/20230703143943_create_promotion_rules.rb +++ b/friendly_promotions/db/migrate/20230703143943_create_promotion_rules.rb @@ -2,7 +2,7 @@ class CreatePromotionRules < ActiveRecord::Migration[7.0] def change create_table :friendly_promotion_rules do |t| t.references :promotion, - foreign_key: { to_table: :friendly_promotions } + foreign_key: {to_table: :friendly_promotions} t.string :type t.text :preferences diff --git a/friendly_promotions/db/migrate/20230704083830_add_rule_tables.rb b/friendly_promotions/db/migrate/20230704083830_add_rule_tables.rb index e412ea601c0..0c95fba165a 100644 --- a/friendly_promotions/db/migrate/20230704083830_add_rule_tables.rb +++ b/friendly_promotions/db/migrate/20230704083830_add_rule_tables.rb @@ -1,28 +1,28 @@ class AddRuleTables < ActiveRecord::Migration[7.0] def change create_table :friendly_products_promotion_rules, force: :cascade do |t| - t.references :product, type: :integer, index: true, null: false, foreign_key: { to_table: :spree_products } - t.references :promotion_rule, index: true, null: false, foreign_key: { to_table: :friendly_promotion_rules } + t.references :product, type: :integer, index: true, null: false, foreign_key: {to_table: :spree_products} + t.references :promotion_rule, index: true, null: false, foreign_key: {to_table: :friendly_promotion_rules} t.timestamps end create_table :friendly_promotion_rules_taxons, force: :cascade do |t| - t.references :taxon, type: :integer, index: true, null: false, foreign_key: { to_table: :spree_taxons } - t.references :promotion_rule, index: true, null: false, foreign_key: { to_table: :friendly_promotion_rules } + t.references :taxon, type: :integer, index: true, null: false, foreign_key: {to_table: :spree_taxons} + t.references :promotion_rule, index: true, null: false, foreign_key: {to_table: :friendly_promotion_rules} t.timestamps end create_table :friendly_promotion_rules_users, force: :cascade do |t| - t.references :user, type: :integer, index: true, null: false, foreign_key: { to_table: Spree.user_class.table_name } - t.references :promotion_rule, index: true, null: false, foreign_key: { to_table: :friendly_promotion_rules } + t.references :user, type: :integer, index: true, null: false, foreign_key: {to_table: Spree.user_class.table_name} + t.references :promotion_rule, index: true, null: false, foreign_key: {to_table: :friendly_promotion_rules} t.timestamps end create_table :friendly_promotion_rules_stores do |t| - t.references :store, type: :integer, index: true, null: false, foreign_key: { to_table: :spree_stores } + t.references :store, type: :integer, index: true, null: false, foreign_key: {to_table: :spree_stores} t.references :promotion_rule, index: true, null: false t.timestamps diff --git a/friendly_promotions/db/migrate/20230704093625_create_promotion_actions.rb b/friendly_promotions/db/migrate/20230704093625_create_promotion_actions.rb index 731ee6abd44..5009c266dc0 100644 --- a/friendly_promotions/db/migrate/20230704093625_create_promotion_actions.rb +++ b/friendly_promotions/db/migrate/20230704093625_create_promotion_actions.rb @@ -1,7 +1,7 @@ class CreatePromotionActions < ActiveRecord::Migration[7.0] def change create_table :friendly_promotion_actions do |t| - t.references :promotion, index: true, null: false, foreign_key: { to_table: :friendly_promotions } + t.references :promotion, index: true, null: false, foreign_key: {to_table: :friendly_promotions} t.string :type t.datetime :deleted_at, precision: nil t.text :preferences diff --git a/friendly_promotions/db/migrate/20230704102444_create_promotion_codes.rb b/friendly_promotions/db/migrate/20230704102444_create_promotion_codes.rb index 9168098cb96..5154c629456 100644 --- a/friendly_promotions/db/migrate/20230704102444_create_promotion_codes.rb +++ b/friendly_promotions/db/migrate/20230704102444_create_promotion_codes.rb @@ -1,7 +1,7 @@ class CreatePromotionCodes < ActiveRecord::Migration[7.0] def change create_table :friendly_promotion_codes, force: :cascade do |t| - t.references :promotion, null: false, index: true, foreign_key: { to_table: :friendly_promotions } + t.references :promotion, null: false, index: true, foreign_key: {to_table: :friendly_promotions} t.string :value, null: false t.timestamps diff --git a/friendly_promotions/db/migrate/20230704102656_create_promotion_code_batches.rb b/friendly_promotions/db/migrate/20230704102656_create_promotion_code_batches.rb index 0e7c2a9940d..4dbcde3e298 100644 --- a/friendly_promotions/db/migrate/20230704102656_create_promotion_code_batches.rb +++ b/friendly_promotions/db/migrate/20230704102656_create_promotion_code_batches.rb @@ -3,7 +3,7 @@ class CreatePromotionCodeBatches < ActiveRecord::Migration[7.0] def change create_table :friendly_promotion_code_batches do |t| - t.references :promotion, null: false, index: true, foreign_key: { to_table: :friendly_promotions } + t.references :promotion, null: false, index: true, foreign_key: {to_table: :friendly_promotions} t.string :base_code, null: false t.integer :number_of_codes, null: false t.string :join_characters, null: false, default: "_" diff --git a/friendly_promotions/db/migrate/20230705171556_create_friendly_order_promotions.rb b/friendly_promotions/db/migrate/20230705171556_create_friendly_order_promotions.rb index e1cdee49d82..3b29f44bbcd 100644 --- a/friendly_promotions/db/migrate/20230705171556_create_friendly_order_promotions.rb +++ b/friendly_promotions/db/migrate/20230705171556_create_friendly_order_promotions.rb @@ -1,9 +1,9 @@ class CreateFriendlyOrderPromotions < ActiveRecord::Migration[7.0] def change create_table :friendly_order_promotions do |t| - t.references :order, type: :integer, index: true, null: false, foreign_key: { to_table: :spree_orders } - t.references :promotion, index: true, null: false, foreign_key: { to_table: :friendly_promotions } - t.references :promotion_code, index: true, null: true, foreign_key: { to_table: :friendly_promotion_codes } + t.references :order, type: :integer, index: true, null: false, foreign_key: {to_table: :spree_orders} + t.references :promotion, index: true, null: false, foreign_key: {to_table: :friendly_promotions} + t.references :promotion_code, index: true, null: true, foreign_key: {to_table: :friendly_promotion_codes} t.timestamps end diff --git a/friendly_promotions/db/migrate/20230725074235_create_shipping_rate_discounts.rb b/friendly_promotions/db/migrate/20230725074235_create_shipping_rate_discounts.rb index a2072b1839f..0abf8d24937 100644 --- a/friendly_promotions/db/migrate/20230725074235_create_shipping_rate_discounts.rb +++ b/friendly_promotions/db/migrate/20230725074235_create_shipping_rate_discounts.rb @@ -1,8 +1,8 @@ class CreateShippingRateDiscounts < ActiveRecord::Migration[7.0] def change create_table :friendly_shipping_rate_discounts do |t| - t.references :promotion_action, type: :bigint, null: false, foreign_key: { to_table: :friendly_promotion_actions } - t.references :shipping_rate, type: :integer, null: false, foreign_key: { to_table: :spree_shipping_rates } + t.references :promotion_action, type: :bigint, null: false, foreign_key: {to_table: :friendly_promotion_actions} + t.references :shipping_rate, type: :integer, null: false, foreign_key: {to_table: :spree_shipping_rates} t.decimal :amount, precision: 10, scale: 2, null: false t.string :label, null: false diff --git a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb index 67f7f40de0d..75343fc0892 100644 --- a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb +++ b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb @@ -13,7 +13,7 @@ if item.respond_to?(:children) Spree::BackendConfiguration::MenuItem.new( label: :promotions, - icon: 'bullhorn', + icon: "bullhorn", condition: -> { can?(:admin, SolidusFriendlyPromotions::Promotion) }, url: -> { SolidusFriendlyPromotions::Engine.routes.url_helpers.admin_promotions_path }, data_hook: :admin_promotion_sub_tabs, @@ -21,20 +21,20 @@ Spree::BackendConfiguration::MenuItem.new( label: :promotions, url: -> { SolidusFriendlyPromotions::Engine.routes.url_helpers.admin_promotions_path }, - condition: -> { can?(:admin, SolidusFriendlyPromotions::Promotion) }, + condition: -> { can?(:admin, SolidusFriendlyPromotions::Promotion) } ), Spree::BackendConfiguration::MenuItem.new( label: :promotion_categories, url: -> { SolidusFriendlyPromotions::Engine.routes.url_helpers.admin_promotion_categories_path }, - condition: -> { can?(:admin, SolidusFriendlyPromotions::PromotionCategory) }, - ), - ], + condition: -> { can?(:admin, SolidusFriendlyPromotions::PromotionCategory) } + ) + ] ) else Spree::BackendConfiguration::MenuItem.new( [:promotions, :promotion_categories], - 'bullhorn', - partial: 'spree/admin/shared/promotion_sub_menu', + "bullhorn", + partial: "spree/admin/shared/promotion_sub_menu", condition: -> { can?(:admin, Spree::Promotion) }, url: -> { SolidusFriendlyPromotions::Engine.routes.url_helpers.admin_promotions_path }, position: 2 @@ -60,7 +60,7 @@ "SolidusFriendlyPromotions::Calculators::FlexiRate", "SolidusFriendlyPromotions::Calculators::Percent", "SolidusFriendlyPromotions::Calculators::TieredFlatRate", - "SolidusFriendlyPromotions::Calculators::TieredPercent", + "SolidusFriendlyPromotions::Calculators::TieredPercent" ] config.line_item_discount_calculators = [ "SolidusFriendlyPromotions::Calculators::DistributedAmount", @@ -68,7 +68,7 @@ "SolidusFriendlyPromotions::Calculators::FlexiRate", "SolidusFriendlyPromotions::Calculators::Percent", "SolidusFriendlyPromotions::Calculators::TieredFlatRate", - "SolidusFriendlyPromotions::Calculators::TieredPercent", + "SolidusFriendlyPromotions::Calculators::TieredPercent" ] config.order_rules = [ @@ -83,19 +83,19 @@ "SolidusFriendlyPromotions::Rules::Taxon", "SolidusFriendlyPromotions::Rules::UserLoggedIn", "SolidusFriendlyPromotions::Rules::UserRole", - "SolidusFriendlyPromotions::Rules::User", + "SolidusFriendlyPromotions::Rules::User" ] config.line_item_rules = [ "SolidusFriendlyPromotions::Rules::LineItemOptionValue", "SolidusFriendlyPromotions::Rules::LineItemProduct", - "SolidusFriendlyPromotions::Rules::LineItemTaxon", + "SolidusFriendlyPromotions::Rules::LineItemTaxon" ] config.shipment_rules = [ - "SolidusFriendlyPromotions::Rules::ShippingMethod", + "SolidusFriendlyPromotions::Rules::ShippingMethod" ] config.actions = [ "SolidusFriendlyPromotions::Actions::AdjustLineItem", - "SolidusFriendlyPromotions::Actions::AdjustShipment", + "SolidusFriendlyPromotions::Actions::AdjustShipment" ] end diff --git a/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb b/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb index e433417ce48..26922dd1b64 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spree/core/environment_extension' +require "spree/core/environment_extension" module SolidusFriendlyPromotions class Configuration < Spree::Preferences::Configuration diff --git a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_order_promotion_factory.rb b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_order_promotion_factory.rb index 2361cbedae5..aefa80384f0 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_order_promotion_factory.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_order_promotion_factory.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true FactoryBot.define do - factory :friendly_order_promotion, class: 'SolidusFriendlyPromotions::OrderPromotion' do + factory :friendly_order_promotion, class: "SolidusFriendlyPromotions::OrderPromotion" do association :order, factory: :order association :promotion, factory: :friendly_promotion end diff --git a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_category_factory.rb b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_category_factory.rb index 07155b7b7fc..ea720a21492 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_category_factory.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_category_factory.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true FactoryBot.define do - factory :friendly_promotion_category, class: 'SolidusFriendlyPromotions::PromotionCategory' do - name { 'Promotion Category' } + factory :friendly_promotion_category, class: "SolidusFriendlyPromotions::PromotionCategory" do + name { "Promotion Category" } end end diff --git a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_code_factory.rb b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_code_factory.rb index dbf38774456..3de00d1eaf5 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_code_factory.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_code_factory.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true FactoryBot.define do - factory :friendly_promotion_code, class: 'SolidusFriendlyPromotions::PromotionCode' do + factory :friendly_promotion_code, class: "SolidusFriendlyPromotions::PromotionCode" do association :promotion, factory: :friendly_promotion sequence(:value) { |i| "code#{i}" } end diff --git a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_factory.rb b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_factory.rb index 244716876c8..f23dedbb355 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_factory.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_promotion_factory.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true FactoryBot.define do - factory :friendly_promotion, class: 'SolidusFriendlyPromotions::Promotion' do - name { 'Promo' } + factory :friendly_promotion, class: "SolidusFriendlyPromotions::Promotion" do + name { "Promo" } transient do code { nil } @@ -68,7 +68,7 @@ after(:create) do |promotion, evaluator| rule = SolidusFriendlyPromotions::Rules::ItemTotal.create!( promotion: promotion, - preferred_operator: 'gte', + preferred_operator: "gte", preferred_amount: evaluator.item_total_threshold_amount ) promotion.rules << rule @@ -79,7 +79,7 @@ trait :with_first_order_rule do after(:create) do |promotion, _evaluator| rule = SolidusFriendlyPromotions::Rules::FirstOrder.create!( - promotion: promotion, + promotion: promotion ) promotion.rules << rule promotion.save! diff --git a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_shipping_rate_discount_factory.rb b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_shipping_rate_discount_factory.rb index 1fbe1b7e46b..27594efc6b2 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_shipping_rate_discount_factory.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions/testing_support/friendly_shipping_rate_discount_factory.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true FactoryBot.define do - factory :friendly_shipping_rate_discount, class: 'SolidusFriendlyPromotions::ShippingRateDiscount' do + factory :friendly_shipping_rate_discount, class: "SolidusFriendlyPromotions::ShippingRateDiscount" do amount { BigDecimal("-4.00") } shipping_rate promotion_action { SolidusFriendlyPromotions::Actions::AdjustShipment.new } diff --git a/friendly_promotions/spec/jobs/solidus_friendly_promotions/promotion_code_batch_job_spec.rb b/friendly_promotions/spec/jobs/solidus_friendly_promotions/promotion_code_batch_job_spec.rb index 46a497b8702..148073427be 100644 --- a/friendly_promotions/spec/jobs/solidus_friendly_promotions/promotion_code_batch_job_spec.rb +++ b/friendly_promotions/spec/jobs/solidus_friendly_promotions/promotion_code_batch_job_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" RSpec.describe SolidusFriendlyPromotions::PromotionCodeBatchJob, type: :job do let(:email) { "test@email.com" } let(:code_batch) do @@ -23,8 +23,8 @@ def codes SolidusFriendlyPromotions::PromotionCode.pluck(:value) end - context 'with the default join character' do - it 'uses the default join characters', :aggregate_failures do + context "with the default join character" do + it "uses the default join characters", :aggregate_failures do subject.perform(code_batch) codes.each do |code| expect(code).to match(/^test_/) @@ -32,18 +32,18 @@ def codes end end - context 'with a custom join character' do + context "with a custom join character" do let(:code_batch) do SolidusFriendlyPromotions::PromotionCodeBatch.create!( promotion: create(:friendly_promotion), base_code: "test", number_of_codes: 10, email: email, - join_characters: '-' + join_characters: "-" ) end - it 'uses the custom join characters', :aggregate_failures do + it "uses the custom join characters", :aggregate_failures do subject.perform(code_batch) codes.each do |code| expect(code).to match(/^test-/) diff --git a/friendly_promotions/spec/lib/solidus_friendly_promotions/testing_support/factories_spec.rb b/friendly_promotions/spec/lib/solidus_friendly_promotions/testing_support/factories_spec.rb index 23ee0b5c8c8..1e46e37fe51 100644 --- a/friendly_promotions/spec/lib/solidus_friendly_promotions/testing_support/factories_spec.rb +++ b/friendly_promotions/spec/lib/solidus_friendly_promotions/testing_support/factories_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" RSpec.describe "Friendly Factories" do it "has a bunch of working factories" do diff --git a/friendly_promotions/spec/mailers/solidus_friendly_promotions/promotion_code_batch_mailer_spec.rb b/friendly_promotions/spec/mailers/solidus_friendly_promotions/promotion_code_batch_mailer_spec.rb index 9ee8ba28729..272afc5103e 100644 --- a/friendly_promotions/spec/mailers/solidus_friendly_promotions/promotion_code_batch_mailer_spec.rb +++ b/friendly_promotions/spec/mailers/solidus_friendly_promotions/promotion_code_batch_mailer_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" RSpec.describe SolidusFriendlyPromotions::PromotionCodeBatchMailer, type: :mailer do let(:promotion) { create(:friendly_promotion, name: "Promotion Test") } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_line_item_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_line_item_spec.rb index 863f4be290a..b3d8e04432d 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_line_item_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_line_item_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" RSpec.describe SolidusFriendlyPromotions::Actions::AdjustLineItem do subject(:action) { described_class.new } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_shipment_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_shipment_spec.rb index fdd07df85e0..7fc0206ea49 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_shipment_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_shipment_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" RSpec.describe SolidusFriendlyPromotions::Actions::AdjustShipment do subject(:action) { described_class.new } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/distributed_amount_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/distributed_amount_spec.rb index 9ad4690ce94..65aca311d33 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/distributed_amount_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/distributed_amount_spec.rb @@ -1,31 +1,31 @@ # frozen_string_literal: true -require 'spec_helper' -require 'shared_examples/calculator_shared_examples' +require "spec_helper" +require "shared_examples/calculator_shared_examples" RSpec.describe SolidusFriendlyPromotions::Calculators::DistributedAmount, type: :model do let(:calculator) { described_class.new(preferred_amount: 15, preferred_currency: currency) } let!(:promotion) do - create :friendly_promotion, apply_automatically: true, name: '15 spread', actions: [action], rules: rules + create :friendly_promotion, apply_automatically: true, name: "15 spread", actions: [action], rules: rules end let(:rules) { [] } let(:action) { SolidusFriendlyPromotions::Actions::AdjustLineItem.create(calculator: calculator) } let(:order) { create(:order_with_line_items, line_items_attributes: line_items_attributes) } let(:currency) { "USD" } - context 'applied to an order' do - let(:line_items_attributes) { [{ price: 20 }, { price: 30 }, { price: 100 }] } + context "applied to an order" do + let(:line_items_attributes) { [{price: 20}, {price: 30}, {price: 100}] } before do order.recalculate end - it 'correctly distributes the entire discount' do + it "correctly distributes the entire discount" do expect(order.promo_total).to eq(-15) expect(order.line_items.map(&:adjustment_total)).to eq([-2, -3, -10]) end - context 'with product promotion rule' do + context "with product promotion rule" do let(:first_product) { order.line_items.first.product } let(:rules) do [ @@ -37,7 +37,7 @@ order.recalculate end - it 'still distributes the entire discount' do + it "still distributes the entire discount" do expect(order.promo_total).to eq(-15) expect(order.line_items.map(&:adjustment_total)).to eq([-15, 0, 0]) end @@ -47,7 +47,7 @@ describe "#compute_line_item" do subject { calculator.compute_line_item(order.line_items.first) } - let(:line_items_attributes) { [{ price: 50 }, { price: 50 }, { price: 50 }] } + let(:line_items_attributes) { [{price: 50}, {price: 50}, {price: 50}] } context "when the order currency matches the store's currency" do let(:currency) { "USD" } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/flat_rate_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/flat_rate_spec.rb index e89918d7865..da44375c416 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/flat_rate_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/flat_rate_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require 'spec_helper' -require 'shared_examples/calculator_shared_examples' +require "spec_helper" +require "shared_examples/calculator_shared_examples" RSpec.describe SolidusFriendlyPromotions::Calculators::FlatRate, type: :model do subject { calculator.compute(line_item) } @@ -15,7 +15,7 @@ ) end - it_behaves_like 'a calculator with a description' + it_behaves_like "a calculator with a description" context "compute" do describe "when preferred currency matches order" do diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/flexi_rate_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/flexi_rate_spec.rb index 078cecac60d..3acd59c13d6 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/flexi_rate_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/flexi_rate_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require 'spec_helper' -require 'shared_examples/calculator_shared_examples' +require "spec_helper" +require "shared_examples/calculator_shared_examples" RSpec.describe SolidusFriendlyPromotions::Calculators::FlexiRate, type: :model do let(:calculator) do @@ -26,7 +26,7 @@ ) end - it_behaves_like 'a calculator with a description' + it_behaves_like "a calculator with a description" context "compute" do subject { calculator.compute(line_item) } @@ -178,7 +178,7 @@ end it "allows creation of new object with all the attributes" do - attributes = { preferred_first_item: 1, preferred_additional_item: 1, preferred_max_items: 1 } + attributes = {preferred_first_item: 1, preferred_additional_item: 1, preferred_max_items: 1} calculator = described_class.new(attributes) expect(calculator).to have_attributes(attributes) end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/percent_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/percent_spec.rb index 78ce5c5e59a..f71734952e3 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/percent_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/percent_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require 'spec_helper' -require 'shared_examples/calculator_shared_examples' +require "spec_helper" +require "shared_examples/calculator_shared_examples" RSpec.describe SolidusFriendlyPromotions::Calculators::Percent, type: :model do context "compute" do @@ -16,5 +16,5 @@ end end - it_behaves_like 'a calculator with a description' + it_behaves_like "a calculator with a description" end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_flat_rate_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_flat_rate_spec.rb index b476776b7e6..ee438e6c34c 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_flat_rate_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_flat_rate_spec.rb @@ -1,65 +1,65 @@ # frozen_string_literal: true -require 'spec_helper' -require 'shared_examples/calculator_shared_examples' +require "spec_helper" +require "shared_examples/calculator_shared_examples" RSpec.describe SolidusFriendlyPromotions::Calculators::TieredFlatRate, type: :model do let(:calculator) { described_class.new } - it_behaves_like 'a calculator with a description' + it_behaves_like "a calculator with a description" describe "#valid?" do subject { calculator.valid? } context "when tiers is a hash" do context "and the key is not a positive number" do - before { calculator.preferred_tiers = { "nope" => 20 } } + before { calculator.preferred_tiers = {"nope" => 20} } it { is_expected.to be false } end context "and the key is an integer" do - before { calculator.preferred_tiers = { 20 => 20 } } + before { calculator.preferred_tiers = {20 => 20} } it "converts successfully" do expect(subject).to be true - expect(calculator.preferred_tiers).to eq({ BigDecimal('20') => BigDecimal('20') }) + expect(calculator.preferred_tiers).to eq({BigDecimal("20") => BigDecimal("20")}) end end context "and the key is a float" do - before { calculator.preferred_tiers = { 20.5 => 20.5 } } + before { calculator.preferred_tiers = {20.5 => 20.5} } it "converts successfully" do expect(subject).to be true - expect(calculator.preferred_tiers).to eq({ BigDecimal('20.5') => BigDecimal('20.5') }) + expect(calculator.preferred_tiers).to eq({BigDecimal("20.5") => BigDecimal("20.5")}) end end context "and the key is a string number" do - before { calculator.preferred_tiers = { "20" => 20 } } + before { calculator.preferred_tiers = {"20" => 20} } it "converts successfully" do expect(subject).to be true - expect(calculator.preferred_tiers).to eq({ BigDecimal('20') => BigDecimal('20') }) + expect(calculator.preferred_tiers).to eq({BigDecimal("20") => BigDecimal("20")}) end end context "and the key is a numeric string with spaces" do - before { calculator.preferred_tiers = { " 20 " => 20 } } + before { calculator.preferred_tiers = {" 20 " => 20} } it "converts successfully" do expect(subject).to be true - expect(calculator.preferred_tiers).to eq({ BigDecimal('20') => BigDecimal('20') }) + expect(calculator.preferred_tiers).to eq({BigDecimal("20") => BigDecimal("20")}) end end context "and the key is a string number with decimals" do - before { calculator.preferred_tiers = { "20.5" => "20.5" } } + before { calculator.preferred_tiers = {"20.5" => "20.5"} } it "converts successfully" do expect(subject).to be true - expect(calculator.preferred_tiers).to eq({ BigDecimal('20.5') => BigDecimal('20.5') }) + expect(calculator.preferred_tiers).to eq({BigDecimal("20.5") => BigDecimal("20.5")}) end end end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_percent_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_percent_spec.rb index 73cd5fd6458..6f9bb1518b1 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_percent_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_percent_spec.rb @@ -1,12 +1,12 @@ # frozen_string_literal: true -require 'spec_helper' -require 'shared_examples/calculator_shared_examples' +require "spec_helper" +require "shared_examples/calculator_shared_examples" RSpec.describe SolidusFriendlyPromotions::Calculators::TieredPercent, type: :model do let(:calculator) { described_class.new } - it_behaves_like 'a calculator with a description' + it_behaves_like "a calculator with a description" describe "#valid?" do subject { calculator.valid? } @@ -25,59 +25,59 @@ context "when tiers is a hash" do context "and the key is not a positive number" do - before { calculator.preferred_tiers = { "nope" => 20 } } + before { calculator.preferred_tiers = {"nope" => 20} } it { is_expected.to be false } end context "and one of the values is not a percent" do - before { calculator.preferred_tiers = { 10 => 110 } } + before { calculator.preferred_tiers = {10 => 110} } it { is_expected.to be false } end context "and the key is an integer" do - before { calculator.preferred_tiers = { 20 => 20 } } + before { calculator.preferred_tiers = {20 => 20} } it "converts successfully" do expect(subject).to be true - expect(calculator.preferred_tiers).to eq({ BigDecimal('20') => BigDecimal('20') }) + expect(calculator.preferred_tiers).to eq({BigDecimal("20") => BigDecimal("20")}) end end context "and the key is a float" do - before { calculator.preferred_tiers = { 20.5 => 20.5 } } + before { calculator.preferred_tiers = {20.5 => 20.5} } it "converts successfully" do expect(subject).to be true - expect(calculator.preferred_tiers).to eq({ BigDecimal('20.5') => BigDecimal('20.5') }) + expect(calculator.preferred_tiers).to eq({BigDecimal("20.5") => BigDecimal("20.5")}) end end context "and the key is a string number" do - before { calculator.preferred_tiers = { "20" => 20 } } + before { calculator.preferred_tiers = {"20" => 20} } it "converts successfully" do expect(subject).to be true - expect(calculator.preferred_tiers).to eq({ BigDecimal('20') => BigDecimal('20') }) + expect(calculator.preferred_tiers).to eq({BigDecimal("20") => BigDecimal("20")}) end end context "and the key is a numeric string with spaces" do - before { calculator.preferred_tiers = { " 20 " => 20 } } + before { calculator.preferred_tiers = {" 20 " => 20} } it "converts successfully" do expect(subject).to be true - expect(calculator.preferred_tiers).to eq({ BigDecimal('20') => BigDecimal('20') }) + expect(calculator.preferred_tiers).to eq({BigDecimal("20") => BigDecimal("20")}) end end context "and the key is a string number with decimals" do - before { calculator.preferred_tiers = { "20.5" => "20.5" } } + before { calculator.preferred_tiers = {"20.5" => "20.5"} } it "converts successfully" do expect(subject).to be true - expect(calculator.preferred_tiers).to eq({ BigDecimal('20.5') => BigDecimal('20.5') }) + expect(calculator.preferred_tiers).to eq({BigDecimal("20.5") => BigDecimal("20.5")}) end end end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/distributed_amounts_handler_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/distributed_amounts_handler_spec.rb index 4780f71f144..f3d244902c0 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/distributed_amounts_handler_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/distributed_amounts_handler_spec.rb @@ -18,7 +18,7 @@ let(:total_amount) { 15 } context "when there is only one line item" do - let(:line_items_attributes) { [{ price: 100 }] } + let(:line_items_attributes) { [{price: 100}] } let(:line_item) { order.line_items.first } it "applies the entire amount to the line item" do @@ -28,7 +28,7 @@ context "when there are multiple line items" do let(:line_items_attributes) do - [{ price: 50 }, { price: 50 }, { price: 50 }] + [{price: 50}, {price: 50}, {price: 50}] end context "and the line items are equally priced" do @@ -63,7 +63,7 @@ context "and the line items do not have equal subtotal amounts" do let(:line_items_attributes) do - [{ price: 50, quantity: 3 }, { price: 50, quantity: 1 }, { price: 50, quantity: 2 }] + [{price: 50, quantity: 3}, {price: 50, quantity: 1}, {price: 50, quantity: 2}] end it "distributes the total amount relative to the item's price" do diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/friendly_promotion_discounter_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/friendly_promotion_discounter_spec.rb index 3574adadf0e..fb97da02a6a 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/friendly_promotion_discounter_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/friendly_promotion_discounter_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" RSpec.describe SolidusFriendlyPromotions::FriendlyPromotionDiscounter do describe "selecting promotions" do @@ -19,9 +19,9 @@ context "no promo is connected to the order" do it "checks only active promotions" do - expect(SolidusFriendlyPromotions::PromotionEligibility).to receive(:new). - with(promotable: order, possible_promotions: [active_promotion]). - and_call_original + expect(SolidusFriendlyPromotions::PromotionEligibility).to receive(:new) + .with(promotable: order, possible_promotions: [active_promotion]) + .and_call_original subject end end @@ -32,9 +32,9 @@ end it "checks active and connected promotions" do - expect(SolidusFriendlyPromotions::PromotionEligibility).to receive(:new). - with(promotable: order, possible_promotions: array_including(active_promotion, connectable_promotion)). - and_call_original + expect(SolidusFriendlyPromotions::PromotionEligibility).to receive(:new) + .with(promotable: order, possible_promotions: array_including(active_promotion, connectable_promotion)) + .and_call_original subject end end @@ -45,16 +45,16 @@ end it "does not check connected inactive promotions" do - expect(SolidusFriendlyPromotions::PromotionEligibility).to receive(:new). - with(promotable: order, possible_promotions: array_including(active_promotion)). - and_call_original + expect(SolidusFriendlyPromotions::PromotionEligibility).to receive(:new) + .with(promotable: order, possible_promotions: array_including(active_promotion)) + .and_call_original subject end end end context "promotions in the past" do - let(:order) { create(:order, completed_at: 7.days.ago) } + let(:order) { create(:order, completed_at: 7.days.ago) } let(:currently_active_promotion) { create(:friendly_promotion, :with_adjustable_action, starts_at: 1.hour.ago) } let(:past_promotion) { create(:friendly_promotion, :with_adjustable_action, starts_at: 1.year.ago, expires_at: 11.months.ago) } let(:order_promotion) { create(:friendly_promotion, :with_adjustable_action, starts_at: 8.days.ago, expires_at: 6.days.ago) } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/item_discount_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/item_discount_spec.rb index 1d53505d6e3..77f1e6824c1 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/item_discount_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/item_discount_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" RSpec.describe SolidusFriendlyPromotions::ItemDiscount do it { is_expected.to respond_to(:item) } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/order_discounter_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/order_discounter_spec.rb index 43f2262f3f2..ac9a300bc7a 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/order_discounter_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/order_discounter_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" RSpec.describe SolidusFriendlyPromotions::OrderDiscounter, type: :model do subject { described_class.new(order) } @@ -59,7 +59,7 @@ context "promotion has item total rule" do let!(:rule) do SolidusFriendlyPromotions::Rules::ItemTotal.create( - preferred_operator: 'gt', + preferred_operator: "gt", preferred_amount: 50, promotion: promotion ) diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/order_promotion_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/order_promotion_spec.rb index 2a22422e019..2843422a32c 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/order_promotion_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/order_promotion_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" RSpec.describe SolidusFriendlyPromotions::OrderPromotion do subject do diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/products_promotion_rule_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/products_promotion_rule_spec.rb index c837246e2a6..9e55f0821ef 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/products_promotion_rule_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/products_promotion_rule_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" RSpec.describe SolidusFriendlyPromotions::ProductsPromotionRule do it { is_expected.to belong_to(:product).optional } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_category_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_category_spec.rb index 50ce646d63d..c19a36457d9 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_category_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_category_spec.rb @@ -1,20 +1,20 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" RSpec.describe SolidusFriendlyPromotions::PromotionCategory, type: :model do it { is_expected.to have_many :promotions } - describe 'validation' do + describe "validation" do subject { described_class.new name: name } - let(:name) { 'Nom' } + let(:name) { "Nom" } - context 'when all required attributes are specified' do + context "when all required attributes are specified" do it { is_expected.to be_valid } end - context 'when name is missing' do + context "when name is missing" do let(:name) { nil } it { is_expected.not_to be_valid } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code_batch_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code_batch_spec.rb index 1051afbe7c1..be244f8d9a9 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code_batch_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code_batch_spec.rb @@ -31,7 +31,7 @@ before { subject.update_attribute(:state, "processing") } it "raises an error" do - expect{ subject.process }.to raise_error described_class::CantProcessStartedBatch + expect { subject.process }.to raise_error described_class::CantProcessStartedBatch end end @@ -39,7 +39,7 @@ before { subject.update_attribute(:state, "completed") } it "raises an error" do - expect{ subject.process }.to raise_error described_class::CantProcessStartedBatch + expect { subject.process }.to raise_error described_class::CantProcessStartedBatch end end @@ -47,7 +47,7 @@ before { subject.update_attribute(:state, "failed") } it "raises an error" do - expect{ subject.process }.to raise_error described_class::CantProcessStartedBatch + expect { subject.process }.to raise_error described_class::CantProcessStartedBatch end end end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code_spec.rb index 755ecb8ddea..285a4ce1235 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_code_spec.rb @@ -1,59 +1,59 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" RSpec.describe SolidusFriendlyPromotions::PromotionCode do - context 'callbacks' do + context "callbacks" do subject { promotion_code.save } - describe '#normalize_code' do + describe "#normalize_code" do let(:promotion) { create(:friendly_promotion, code: code) } before { subject } - context 'when no other code with the same value exists' do + context "when no other code with the same value exists" do let(:promotion_code) { promotion.codes.first } - context 'with mixed case' do - let(:code) { 'NewCoDe' } + context "with mixed case" do + let(:code) { "NewCoDe" } - it 'downcases the value before saving' do - expect(promotion_code.value).to eq('newcode') + it "downcases the value before saving" do + expect(promotion_code.value).to eq("newcode") end end - context 'with extra spacing' do - let(:code) { ' new code ' } + context "with extra spacing" do + let(:code) { " new code " } - it 'removes surrounding whitespace' do - expect(promotion_code.value).to eq 'new code' + it "removes surrounding whitespace" do + expect(promotion_code.value).to eq "new code" end end end - context 'when another code with the same value exists' do + context "when another code with the same value exists" do let(:promotion_code) { promotion.codes.build(value: code) } - context 'with mixed case' do - let(:code) { 'NewCoDe' } + context "with mixed case" do + let(:code) { "NewCoDe" } - it 'does not save the record and marks it as invalid' do + it "does not save the record and marks it as invalid" do expect(promotion_code.valid?).to eq false expect(promotion_code.errors.messages[:value]).to contain_exactly( - 'has already been taken' + "has already been taken" ) end end - context 'with extra spacing' do - let(:code) { ' new code ' } + context "with extra spacing" do + let(:code) { " new code " } - it 'does not save the record and marks it as invalid' do + it "does not save the record and marks it as invalid" do expect(promotion_code.valid?).to eq false expect(promotion_code.errors.messages[:value]).to contain_exactly( - 'has already been taken' + "has already been taken" ) end end @@ -127,7 +127,7 @@ :friendly_promotion, :with_line_item_adjustment, code: "discount", - per_code_usage_limit: usage_limit, + per_code_usage_limit: usage_limit ) end @@ -208,7 +208,7 @@ before { order.cancel! } it { is_expected.to eq 0 } - it { expect(order.state).to eq 'canceled' } + it { expect(order.state).to eq "canceled" } end end end @@ -256,27 +256,27 @@ end it "makes the adjustment disappear" do - expect{ + expect { order.complete }.to change { order.all_adjustments.friendly_promotion }.to([]) end it "adjusts the promo_total" do - expect{ + expect { order.complete }.to change(order, :promo_total).by(10) end it "increases the total to remove the promo" do - expect{ + expect { order.complete }.to change(order, :total).from(30).to(40) end it "resets the state of the order" do - expect{ + expect { order.complete - }.to change{ order.reload.state }.from("confirm").to("address") + }.to change { order.reload.state }.from("confirm").to("address") end end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rule_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rule_spec.rb index 85e31ad9c27..90ff42a9059 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rule_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rule_spec.rb @@ -33,6 +33,6 @@ def eligible?(_promotable, _options = {}) it "generates its own partial path" do rule = TestRule.new - expect(rule.to_partial_path).to eq 'solidus_friendly_promotions/admin/promotion_rules/rules/test_rule' + expect(rule.to_partial_path).to eq "solidus_friendly_promotions/admin/promotion_rules/rules/test_rule" end end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_store_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_store_spec.rb index 02e47b4cdb5..d1507f1f0ac 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_store_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_store_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" RSpec.describe SolidusFriendlyPromotions::PromotionRulesStore do it { is_expected.to belong_to(:store).optional } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_taxon_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_taxon_spec.rb index b0c06384862..a5c767fb1af 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_taxon_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_taxon_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" RSpec.describe SolidusFriendlyPromotions::PromotionRulesTaxon do it { is_expected.to belong_to(:taxon).optional } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_user_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_user_spec.rb index 981cc07d43a..ebcd062ec1a 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_user_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rules_user_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" RSpec.describe SolidusFriendlyPromotions::PromotionRulesUser do it { is_expected.to belong_to(:user).optional } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb index fb8ba937086..afefc52803a 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" RSpec.describe SolidusFriendlyPromotions::Promotion, type: :model do let(:promotion) { described_class.new } @@ -55,7 +55,7 @@ end end - describe '.active' do + describe ".active" do subject { described_class.active } let(:promotion) { create(:friendly_promotion, starts_at: Date.yesterday, name: "name1") } @@ -66,15 +66,15 @@ expect(subject).to be_empty end - context 'when promotion has an action' do + context "when promotion has an action" do let(:promotion) { create(:friendly_promotion, :with_adjustable_action, starts_at: Date.yesterday, name: "name1") } - it 'returns promotion with action' do + it "returns promotion with action" do expect(subject).to match [promotion] end end - context 'when called with a time that is not current' do + context "when called with a time that is not current" do subject { described_class.active(4.days.ago) } let(:promotion) do @@ -87,13 +87,13 @@ ) end - it 'returns promotion that was active then' do + it "returns promotion that was active then" do expect(subject).to match [promotion] end end end - describe '.has_actions' do + describe ".has_actions" do subject { described_class.has_actions } let(:promotion) { create(:friendly_promotion, starts_at: Date.yesterday, name: "name1") } @@ -104,14 +104,14 @@ expect(subject).to be_empty end - context 'when promotion has two actions' do + context "when promotion has two actions" do let(:promotion) { create(:friendly_promotion, :with_adjustable_action, starts_at: Date.yesterday, name: "name1") } before do promotion.actions << SolidusFriendlyPromotions::Actions::AdjustShipment.new(calculator: SolidusFriendlyPromotions::Calculators::Percent.new) end - it 'returns distinct promotion' do + it "returns distinct promotion" do expect(subject).to match [promotion] end end @@ -268,7 +268,7 @@ before { order.cancel! } it { is_expected.to eq 0 } - it { expect(order.state).to eq 'canceled' } + it { expect(order.state).to eq "canceled" } end end end @@ -307,96 +307,96 @@ end end - describe '#not_started?' do + describe "#not_started?" do subject { promotion.not_started? } let(:promotion) { described_class.new(starts_at: starts_at) } - context 'no starts_at date' do + context "no starts_at date" do let(:starts_at) { nil } it { is_expected.to be_falsey } end - context 'when starts_at date is in the past' do + context "when starts_at date is in the past" do let(:starts_at) { Time.current - 1.day } it { is_expected.to be_falsey } end - context 'when starts_at date is not already reached' do + context "when starts_at date is not already reached" do let(:starts_at) { Time.current + 1.day } it { is_expected.to be_truthy } end end - describe '#started?' do + describe "#started?" do subject { promotion.started? } let(:promotion) { described_class.new(starts_at: starts_at) } - context 'when no starts_at date' do + context "when no starts_at date" do let(:starts_at) { nil } it { is_expected.to be_truthy } end - context 'when starts_at date is in the past' do + context "when starts_at date is in the past" do let(:starts_at) { Time.current - 1.day } it { is_expected.to be_truthy } end - context 'when starts_at date is not already reached' do + context "when starts_at date is not already reached" do let(:starts_at) { Time.current + 1.day } it { is_expected.to be_falsey } end end - describe '#expired?' do + describe "#expired?" do subject { promotion.expired? } let(:promotion) { described_class.new(expires_at: expires_at) } - context 'when no expires_at date' do + context "when no expires_at date" do let(:expires_at) { nil } it { is_expected.to be_falsey } end - context 'when expires_at date is not already reached' do + context "when expires_at date is not already reached" do let(:expires_at) { Time.current + 1.day } it { is_expected.to be_falsey } end - context 'when expires_at date is in the past' do + context "when expires_at date is in the past" do let(:expires_at) { Time.current - 1.day } it { is_expected.to be_truthy } end end - describe '#not_expired?' do + describe "#not_expired?" do subject { promotion.not_expired? } let(:promotion) { described_class.new(expires_at: expires_at) } - context 'when no expired_at date' do + context "when no expired_at date" do let(:expires_at) { nil } it { is_expected.to be_truthy } end - context 'when expires_at date is not already reached' do + context "when expires_at date is not already reached" do let(:expires_at) { Time.current + 1.day } it { is_expected.to be_truthy } end - context 'when expires_at date is in the past' do + context "when expires_at date is in the past" do let(:expires_at) { Time.current - 1.day } it { is_expected.to be_falsey } @@ -426,7 +426,7 @@ expect(promotion.active?).to eq(false) end - context 'when promotion has an action' do + context "when promotion has an action" do let(:promotion) { create(:friendly_promotion, :with_adjustable_action, name: "name1") } it "is active if it has started already" do @@ -508,7 +508,7 @@ end end - describe '#used_by?' do + describe "#used_by?" do subject { promotion.used_by? user, [excluded_order] } let(:promotion) { create :friendly_promotion, :with_adjustable_action } @@ -521,7 +521,7 @@ order.save! end - context 'when the user has used this promo' do + context "when the user has used this promo" do before do order.friendly_order_promotions.create( promotion: promotion @@ -531,10 +531,10 @@ order.save! end - context 'when the order is complete' do + context "when the order is complete" do it { is_expected.to be true } - context 'when the promotion was not eligible' do + context "when the promotion was not eligible" do let(:adjustment) { order.all_adjustments.first } before do @@ -545,14 +545,14 @@ it { is_expected.to be false } end - context 'when the only matching order is the excluded order' do + context "when the only matching order is the excluded order" do let(:excluded_order) { order } it { is_expected.to be false } end end - context 'when the order is not complete' do + context "when the order is not complete" do let(:order) { create :order, user: user } # The before clause above sets the completed at @@ -563,7 +563,7 @@ end end - context 'when the user has not used this promo' do + context "when the user has not used this promo" do it { is_expected.to be false } end end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/item_total_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/item_total_spec.rb index 38fc3a2f307..72f7dc61110 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/item_total_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/item_total_spec.rb @@ -9,7 +9,7 @@ preferred_operator: preferred_operator ) end - let(:order) { instance_double('Spree::Order', item_total: item_total, currency: order_currency) } + let(:order) { instance_double("Spree::Order", item_total: item_total, currency: order_currency) } let(:preferred_amount) { 50 } let(:order_currency) { "USD" } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/option_value_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/option_value_spec.rb index c8078873ac8..519309378d5 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/option_value_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/option_value_spec.rb @@ -9,8 +9,8 @@ subject { rule.preferred_eligible_values } it "assigns a nicely formatted hash" do - rule.preferred_eligible_values = { "5" => "1,2", "6" => "1" } - expect(subject).to eq({ 5 => [1, 2], 6 => [1] }) + rule.preferred_eligible_values = {"5" => "1,2", "6" => "1"} + expect(subject).to eq({5 => [1, 2], 6 => [1]}) end end @@ -39,9 +39,9 @@ context "when there are any applicable line items" do before do - rule.preferred_eligible_values = { line_item.product.id => [ + rule.preferred_eligible_values = {line_item.product.id => [ line_item.variant.option_values.pick(:id) - ] } + ]} end it { is_expected.to be true } @@ -49,7 +49,7 @@ context "when there are no applicable line items" do before do - rule.preferred_eligible_values = { 99 => [99] } + rule.preferred_eligible_values = {99 => [99]} end it { is_expected.to be false } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/simple_order_contents_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/simple_order_contents_spec.rb index fefa74d8db7..11c44a17c3b 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/simple_order_contents_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/simple_order_contents_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" RSpec.describe SolidusFriendlyPromotions::SimpleOrderContents, type: :model do subject(:order_contents) { described_class.new(order) } @@ -11,15 +11,15 @@ let!(:stock_location) { variant.stock_locations.first } describe "#add" do - context 'given quantity is not explicitly provided' do - it 'adds one line item' do + context "given quantity is not explicitly provided" do + it "adds one line item" do line_item = subject.add(variant) expect(line_item.quantity).to eq(1) expect(order.line_items.size).to eq(1) end end - context 'given a shipment' do + context "given a shipment" do let!(:shipment) { create(:shipment, order: order) } it "ensure shipment calls update_amounts instead of order calling check_shipments_and_restart_checkout" do @@ -51,20 +51,20 @@ end end - context 'not given a shipment' do + context "not given a shipment" do it "ensures updated shipments" do expect(subject.order).to receive(:check_shipments_and_restart_checkout) subject.add(variant) end end - it 'adds line item if one does not exist' do + it "adds line item if one does not exist" do line_item = subject.add(variant, 1) expect(line_item.quantity).to eq(1) expect(order.line_items.size).to eq(1) end - it 'updates line item if one exists' do + it "updates line item if one exists" do subject.add(variant, 1) line_item = subject.add(variant, 1) expect(line_item.quantity).to eq(2) @@ -104,14 +104,14 @@ end end - describe 'tax calculations' do + describe "tax calculations" do let!(:zone) { create(:global_zone) } let!(:tax_rate) do create(:tax_rate, zone: zone, tax_categories: [variant.tax_category]) end - context 'when the order has a taxable address' do - it 'creates a tax adjustment' do + context "when the order has a taxable address" do + it "creates a tax adjustment" do expect(order.tax_address.country_id).to be_present order_contents.add(variant) line_item = order.find_line_item_by_variant(variant) @@ -119,12 +119,12 @@ end end - context 'when the order does not have a taxable address' do + context "when the order does not have a taxable address" do before do order.update!(ship_address: nil, bill_address: nil) end - it 'creates a tax adjustment' do + it "creates a tax adjustment" do expect(order.tax_address.country_id).to be_nil order_contents.add(variant) line_item = order.find_line_item_by_variant(variant) @@ -143,8 +143,8 @@ end end - context 'given quantity is not explicitly provided' do - it 'removes one line item' do + context "given quantity is not explicitly provided" do + it "removes one line item" do line_item = subject.add(variant, 3) subject.remove(variant) @@ -152,7 +152,7 @@ end end - context 'given a shipment' do + context "given a shipment" do it "ensure shipment calls update_amounts instead of order calling check_shipments_and_restart_checkout" do subject.add(variant, 1) shipment = create(:shipment) @@ -162,7 +162,7 @@ end end - context 'not given a shipment' do + context "not given a shipment" do it "ensures updated shipments" do subject.add(variant, 1) expect(subject.order).to receive(:check_shipments_and_restart_checkout) @@ -170,21 +170,21 @@ end end - it 'reduces line_item quantity if quantity is less the line_item quantity' do + it "reduces line_item quantity if quantity is less the line_item quantity" do line_item = subject.add(variant, 3) subject.remove(variant, 1) expect(line_item.reload.quantity).to eq(2) end - it 'removes line_item if quantity matches line_item quantity' do + it "removes line_item if quantity matches line_item quantity" do subject.add(variant, 1) subject.remove(variant, 1) expect(order.reload.find_line_item_by_variant(variant)).to be_nil end - it 'removes line_item if quantity is greater than line_item quantity' do + it "removes line_item if quantity is greater than line_item quantity" do subject.add(variant, 1) subject.remove(variant, 2) @@ -207,7 +207,7 @@ end describe "#remove_line_item" do - context 'given a shipment' do + context "given a shipment" do it "ensure shipment calls update_amounts instead of order calling check_shipments_and_restart_checkout" do line_item = subject.add(variant, 1) shipment = create(:shipment) @@ -217,7 +217,7 @@ end end - context 'not given a shipment' do + context "not given a shipment" do it "ensures updated shipments" do line_item = subject.add(variant, 1) expect(subject.order).to receive(:check_shipments_and_restart_checkout) @@ -225,7 +225,7 @@ end end - it 'removes line_item' do + it "removes line_item" do line_item = subject.add(variant, 1) subject.remove_line_item(line_item) @@ -251,9 +251,9 @@ let!(:shirt) { subject.add variant, 1 } let(:params) do - { line_items_attributes: { - "0" => { id: shirt.id, quantity: 3 } - } } + {line_items_attributes: { + "0" => {id: shirt.id, quantity: 3} + }} end it "changes item quantity" do @@ -269,9 +269,9 @@ context "submits item quantity 0" do let(:params) do - { line_items_attributes: { - "0" => { id: shirt.id, quantity: 0 } - } } + {line_items_attributes: { + "0" => {id: shirt.id, quantity: 0} + }} end it "removes item from order" do @@ -290,7 +290,7 @@ context "completed order" do let(:order) do Spree::Order.create!( - state: 'complete', + state: "complete", completed_at: Time.current, email: "test@example.com" ) @@ -310,20 +310,20 @@ end describe "#approve" do - context 'when a name is supplied' do - it 'approves the order' do - order.contents.approve(name: 'Jordan') + context "when a name is supplied" do + it "approves the order" do + order.contents.approve(name: "Jordan") expect(order.approver).to be_nil - expect(order.approver_name).to eq('Jordan') + expect(order.approver_name).to eq("Jordan") expect(order.approved_at).to be_present expect(order).to be_approved end end - context 'when a user is supplied' do + context "when a user is supplied" do let(:user) { create(:user) } - it 'approves the order' do + it "approves the order" do order.contents.approve(user: user) expect(order.approver).to eq(user) expect(order.approver_name).to be_nil @@ -332,23 +332,23 @@ end end - context 'when a user and a name are supplied' do + context "when a user and a name are supplied" do let(:user) { create(:user) } - it 'approves the order' do - order.contents.approve(user: user, name: 'Jordan') + it "approves the order" do + order.contents.approve(user: user, name: "Jordan") expect(order.approver).to eq(user) - expect(order.approver_name).to eq('Jordan') + expect(order.approver_name).to eq("Jordan") expect(order.approved_at).to be_present expect(order).to be_approved end end - context 'when neither a user nor a name are supplied' do - it 'raises' do + context "when neither a user nor a name are supplied" do + it "raises" do expect { order.contents.approve - }.to raise_error(ArgumentError, 'user or name must be specified') + }.to raise_error(ArgumentError, "user or name must be specified") end end end diff --git a/friendly_promotions/spec/models/spree/shipping_rate_spec.rb b/friendly_promotions/spec/models/spree/shipping_rate_spec.rb index ccdddf887d8..05a770710eb 100644 --- a/friendly_promotions/spec/models/spree/shipping_rate_spec.rb +++ b/friendly_promotions/spec/models/spree/shipping_rate_spec.rb @@ -5,11 +5,11 @@ RSpec.describe Spree::ShippingRate do let(:subject) { build(:shipping_rate) } - describe '#display_price' do + describe "#display_price" do before { subject.amount = 5 } - it 'returns formatted amount' do - expect(subject.display_price).to eq('$5.00') + it "returns formatted amount" do + expect(subject.display_price).to eq("$5.00") end end diff --git a/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_actions_request_spec.rb b/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_actions_request_spec.rb index ec0ecd06aa5..09a979c8f0f 100644 --- a/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_actions_request_spec.rb +++ b/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_actions_request_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" describe "Admin::PromotionActions", type: :request do stub_authorization! @@ -11,7 +11,7 @@ post solidus_friendly_promotions.admin_promotion_promotion_actions_path(promotion_id: promotion.id), params: { promotion_action: { type: "SolidusFriendlyPromotions::Actions::AdjustLineItem", - calculator_attributes: { type: "SolidusFriendlyPromotions::Calculators::FlatRate" } + calculator_attributes: {type: "SolidusFriendlyPromotions::Calculators::FlatRate"} } } expect(response).to be_redirect @@ -21,7 +21,7 @@ it "can not create a promotion action of an invalid type" do post solidus_friendly_promotions.admin_promotion_promotion_actions_path(promotion_id: promotion.id), params: { - promotion_action: { type: "Spree::InvalidType" } + promotion_action: {type: "Spree::InvalidType"} } expect(response).to be_redirect expect(response).to redirect_to spree.edit_admin_promotion_path(promotion) diff --git a/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_rules_request_spec.rb b/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_rules_request_spec.rb index 75e7ae83c79..998d578c130 100644 --- a/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_rules_request_spec.rb +++ b/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_rules_request_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" describe "Admin::PromotionRules", type: :request do let!(:promotion) { create(:friendly_promotion) } @@ -12,7 +12,7 @@ it "can create a promotion rule of a valid type" do post solidus_friendly_promotions.admin_promotion_promotion_rules_path(promotion_id: promotion.id), params: { - promotion_rule: { type: "SolidusFriendlyPromotions::Rules::Product" } + promotion_rule: {type: "SolidusFriendlyPromotions::Rules::Product"} } expect(response).to be_redirect expect(response).to redirect_to solidus_friendly_promotions.edit_admin_promotion_path(promotion) @@ -21,7 +21,7 @@ it "can not create a promotion rule of an invalid type" do post solidus_friendly_promotions.admin_promotion_promotion_rules_path(promotion_id: promotion.id), params: { - promotion_rule: { type: "Spree::InvalidType" } + promotion_rule: {type: "Spree::InvalidType"} } expect(response).to be_redirect expect(response).to redirect_to solidus_friendly_promotions.edit_admin_promotion_path(promotion) @@ -32,9 +32,9 @@ context "when the user is not authorized" do it "redirects the user to login" do post solidus_friendly_promotions.admin_promotion_promotion_rules_path(promotion_id: promotion.id), params: { - promotion_rule: { type: "SolidusFriendlyPromotions::Rules::Product" } + promotion_rule: {type: "SolidusFriendlyPromotions::Rules::Product"} } - expect(response).to redirect_to('/admin/login') + expect(response).to redirect_to("/admin/login") end end end diff --git a/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotions_request_spec.rb b/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotions_request_spec.rb index 9c98a0df6e4..a90662c13cf 100644 --- a/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotions_request_spec.rb +++ b/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotions_request_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" RSpec.describe "Admin::Promotions", type: :request do describe "GET /index" do diff --git a/friendly_promotions/spec/shared_examples/calculator_shared_examples.rb b/friendly_promotions/spec/shared_examples/calculator_shared_examples.rb index b4318576f91..0eb256ce669 100644 --- a/friendly_promotions/spec/shared_examples/calculator_shared_examples.rb +++ b/friendly_promotions/spec/shared_examples/calculator_shared_examples.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.shared_examples_for 'a calculator with a description' do +RSpec.shared_examples_for "a calculator with a description" do describe ".description" do subject { described_class.description } diff --git a/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotion_categories_spec.rb b/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotion_categories_spec.rb index 206b5bb2752..a93ec955173 100644 --- a/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotion_categories_spec.rb +++ b/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotion_categories_spec.rb @@ -1,14 +1,14 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" -describe 'Promotion Categories', type: :system do +describe "Promotion Categories", type: :system do stub_authorization! context "index" do before do - create(:friendly_promotion_category, name: 'name1', code: 'code1') - create(:friendly_promotion_category, name: 'name2', code: 'code2') + create(:friendly_promotion_category, name: "name1", code: "code1") + create(:friendly_promotion_category, name: "name2", code: "code2") visit solidus_friendly_promotions.admin_promotion_categories_path end @@ -50,7 +50,7 @@ context "edit" do before do - create(:friendly_promotion_category, name: 'name1') + create(:friendly_promotion_category, name: "name1") visit solidus_friendly_promotions.admin_promotion_categories_path within_row(1) { click_icon :edit } end @@ -71,7 +71,7 @@ context "delete" do before do - create(:friendly_promotion_category, name: 'name1') + create(:friendly_promotion_category, name: "name1") visit solidus_friendly_promotions.admin_promotion_categories_path end diff --git a/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotions_spec.rb b/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotions_spec.rb index 3d6da8a3fbd..705e18847e2 100644 --- a/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotions_spec.rb +++ b/friendly_promotions/spec/system/solidus_friendly_promotions/admin/promotions_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'spec_helper' +require "spec_helper" RSpec.describe "Promotions admin", type: :system do stub_authorization! @@ -41,31 +41,31 @@ context "search" do it "pages results" do - visit solidus_friendly_promotions.admin_promotions_path(per_page: '1') + visit solidus_friendly_promotions.admin_promotions_path(per_page: "1") expect(page).to have_content(promotion3.name) expect(page).not_to have_content(promotion1.name) end it "filters by name" do - visit solidus_friendly_promotions.admin_promotions_path(q: { name_cont: promotion1.name }) + visit solidus_friendly_promotions.admin_promotions_path(q: {name_cont: promotion1.name}) expect(page).to have_content(promotion1.name) expect(page).not_to have_content(promotion2.name) end it "filters by code" do - visit solidus_friendly_promotions.admin_promotions_path(q: { codes_value_cont: promotion1.codes.first.value }) + visit solidus_friendly_promotions.admin_promotions_path(q: {codes_value_cont: promotion1.codes.first.value}) expect(page).to have_content(promotion1.name) expect(page).not_to have_content(promotion2.name) end it "filters by path" do - visit solidus_friendly_promotions.admin_promotions_path(q: { path_cont: promotion1.path }) + visit solidus_friendly_promotions.admin_promotions_path(q: {path_cont: promotion1.path}) expect(page).to have_content(promotion1.name) expect(page).not_to have_content(promotion2.name) end it "filters by active" do - visit solidus_friendly_promotions.admin_promotions_path(q: { active: true }) + visit solidus_friendly_promotions.admin_promotions_path(q: {active: true}) expect(page).to have_content(promotion1.name) expect(page).to have_content(promotion2.name) expect(page).not_to have_content(promotion3.name) From 9638e8ebdf6a60ba00fcd385a81bbc309c5e510a Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 27 Sep 2023 17:27:20 +0200 Subject: [PATCH 152/524] Remove duplicate method --- .../app/models/solidus_friendly_promotions/promotion.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb index 02189238141..208f0d572d7 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb @@ -97,10 +97,6 @@ def inactive?(time = Time.current) !active?(time) end - def not_expired?(time = Time.current) - !expired?(time) - end - def expired?(time = Time.current) expires_at.present? && expires_at < time end From b0546fa516b35d4fd15b10be4036046eb03bcb4f Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 27 Sep 2023 17:27:32 +0200 Subject: [PATCH 153/524] Simplify hash generation syntax in distributed amounts handler --- .../solidus_friendly_promotions/distributed_amounts_handler.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/distributed_amounts_handler.rb b/friendly_promotions/app/models/solidus_friendly_promotions/distributed_amounts_handler.rb index 1129e91da7c..6e0c0630c0d 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/distributed_amounts_handler.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/distributed_amounts_handler.rb @@ -21,7 +21,7 @@ def amount(line_item) # @return [Hash] a hash of line item IDs and their # corresponding weighted adjustments def distributed_amounts - Hash[line_item_ids.zip(allocated_amounts)] + line_item_ids.zip(allocated_amounts).to_h end def line_item_ids From 93cdb3bd81ab6ceae0f79321f725315960074e46 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 28 Sep 2023 11:46:13 +0200 Subject: [PATCH 154/524] Add "lane" enum to promotions In order to implement stacked promotions, we need some way for the user to specify the order in which promotions run. It's really hard to imagine a UI where all promotions are sorted, so we're opting here for a coarse ordering via promotion "lanes". We imagine three lanes, "pre", "post" and "default", ordered via their enum integer. The system should first evaluate "pre" promotions, then it should evaluate "default" promotions, and then it should evaluate "post" promotions. --- .../app/models/solidus_friendly_promotions/promotion.rb | 6 ++++++ ..._add_lane_to_solidus_friendly_promotions_promotions.rb | 5 +++++ .../models/solidus_friendly_promotions/promotion_spec.rb | 8 ++++++++ 3 files changed, 19 insertions(+) create mode 100644 friendly_promotions/db/migrate/20230928093138_add_lane_to_solidus_friendly_promotions_promotions.rb diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb index 208f0d572d7..df4c7d36de8 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb @@ -29,6 +29,12 @@ class Promotion < Spree::Base joins(:actions).distinct end + enum lane: { + pre: 0, + default: 1, + post: 2 + } + self.allowed_ransackable_associations = ["codes"] self.allowed_ransackable_attributes = %w[name path promotion_category_id] self.allowed_ransackable_scopes = %i[active] diff --git a/friendly_promotions/db/migrate/20230928093138_add_lane_to_solidus_friendly_promotions_promotions.rb b/friendly_promotions/db/migrate/20230928093138_add_lane_to_solidus_friendly_promotions_promotions.rb new file mode 100644 index 00000000000..7961d469bd2 --- /dev/null +++ b/friendly_promotions/db/migrate/20230928093138_add_lane_to_solidus_friendly_promotions_promotions.rb @@ -0,0 +1,5 @@ +class AddLaneToSolidusFriendlyPromotionsPromotions < ActiveRecord::Migration[7.0] + def change + add_column :friendly_promotions, :lane, :integer, null: false, default: 1 + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb index afefc52803a..e486cd6279b 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb @@ -8,6 +8,14 @@ it { is_expected.to belong_to(:category).optional } it { is_expected.to have_many :rules } + describe "lane" do + it { is_expected.to respond_to(:lane) } + + it "is default be default" do + expect(subject.lane).to eq("default") + end + end + describe "validations" do before do @valid_promotion = described_class.new name: "A promotion" From 78d34f3dbcf5bb15181f22771338d9661333b27e Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 28 Sep 2023 12:04:04 +0200 Subject: [PATCH 155/524] Add a lane select to promotions form Every promotion now belongs to a lane, and users can configure that lane on the promotions form. --- .../models/solidus_friendly_promotions/promotion.rb | 10 ++++++++++ .../admin/promotions/_form.html.erb | 8 ++++++++ friendly_promotions/config/locales/en.yml | 5 +++++ 3 files changed, 23 insertions(+) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb index df4c7d36de8..322f0cc14cc 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb @@ -35,6 +35,16 @@ class Promotion < Spree::Base post: 2 } + def self.human_enum_name(enum_name, enum_value) + I18n.t("activerecord.attributes.#{model_name.i18n_key}.#{enum_name.to_s.pluralize}.#{enum_value}") + end + + def self.lane_options + lanes.map do |lane_name, _index| + [human_enum_name(:lane, lane_name), lane_name] + end + end + self.allowed_ransackable_associations = ["codes"] self.allowed_ransackable_attributes = %w[name path promotion_category_id] self.allowed_ransackable_scopes = %i[active] diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_form.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_form.html.erb index 307210a9ee3..e73c7a5f91b 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_form.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/_form.html.erb @@ -68,6 +68,14 @@ data: { :'enable-time' => true, :'default-hour' => 0 } %>
    +
    + <%= f.field_container :lane do %> + <%= f.label :lane %>
    + <%= + f.select(:lane, SolidusFriendlyPromotions::Promotion.lane_options, {}, { class: 'custom-select fullwidth' }) + %> + <% end %> +
    diff --git a/friendly_promotions/config/locales/en.yml b/friendly_promotions/config/locales/en.yml index 33920c0e76a..87fef33be7f 100644 --- a/friendly_promotions/config/locales/en.yml +++ b/friendly_promotions/config/locales/en.yml @@ -112,6 +112,11 @@ en: solidus_friendly_promotions/rules/user_logged_in: User Logged In solidus_friendly_promotions/rules/user_role: User Role(s) attributes: + solidus_friendly_promotions/promotion: + lanes: + pre: Pre + default: Default + post: Post solidus_friendly_promotions/actions/adjust_line_item: description: Creates a promotion credit on matching line items solidus_friendly_promotions/actions/adjust_shipment: From 23a690dcedf8f7ea1c298e2830a094cec0ac4c4a Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 28 Sep 2023 12:17:50 +0200 Subject: [PATCH 156/524] Add "ordered_lanes" method We need the lanes to be sorted by enum integer value, and we don't want that to rely on the enum declaration. --- .../app/models/solidus_friendly_promotions/promotion.rb | 6 +++++- .../models/solidus_friendly_promotions/promotion_spec.rb | 6 ++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb index 322f0cc14cc..793f66c9c08 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion.rb @@ -40,11 +40,15 @@ def self.human_enum_name(enum_name, enum_value) end def self.lane_options - lanes.map do |lane_name, _index| + ordered_lanes.map do |lane_name, _index| [human_enum_name(:lane, lane_name), lane_name] end end + def self.ordered_lanes + lanes.sort_by(&:last).to_h + end + self.allowed_ransackable_associations = ["codes"] self.allowed_ransackable_attributes = %w[name path promotion_category_id] self.allowed_ransackable_scopes = %i[active] diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb index e486cd6279b..ba7437bd244 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_spec.rb @@ -16,6 +16,12 @@ end end + describe ".ordered_lanes" do + subject { described_class.ordered_lanes } + + it { is_expected.to eq({"pre" => 0, "default" => 1, "post" => 2}) } + end + describe "validations" do before do @valid_promotion = described_class.new name: "A promotion" From 7e428f709f3f124107b9fcfeb44223366aa7dcc0 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 29 Sep 2023 12:44:30 +0200 Subject: [PATCH 157/524] Add Facade objects for orders For calculating and retrieving intermediate amounts on line items, orders, shipments, and shipping rates, we need some kind of value object that holds a reference to the order and behaves more or less like the real things. I've considered using Ruby Refinements instead of Delegators for this, but they are clunky in that they need to be referenced in every file using them. --- .../discountable/line_item.rb | 23 ++++++++++ .../discountable/order.rb | 19 ++++++++ .../discountable/shipment.rb | 24 ++++++++++ .../discountable/shipping_rate.rb | 23 ++++++++++ .../discountable/line_item_spec.rb | 34 ++++++++++++++ .../discountable/order_spec.rb | 39 ++++++++++++++++ .../discountable/shipment_spec.rb | 45 +++++++++++++++++++ .../discountable/shipping_rate_spec.rb | 35 +++++++++++++++ 8 files changed, 242 insertions(+) create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/discountable/line_item.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/discountable/order.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/discountable/shipment.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/discountable/shipping_rate.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/discountable/line_item_spec.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/discountable/order_spec.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/discountable/shipment_spec.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/discountable/shipping_rate_spec.rb diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/discountable/line_item.rb b/friendly_promotions/app/models/solidus_friendly_promotions/discountable/line_item.rb new file mode 100644 index 00000000000..d196c55386a --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/discountable/line_item.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module Discountable + class LineItem < SimpleDelegator + attr_reader :discounts, :order + + def initialize(line_item, order:) + super(line_item) + @order = order + @discounts = [] + end + + def line_item + __getobj__ + end + + def discounted_amount + amount + discounts.sum(&:amount) + end + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/discountable/order.rb b/friendly_promotions/app/models/solidus_friendly_promotions/discountable/order.rb new file mode 100644 index 00000000000..ef2bd8c110a --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/discountable/order.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module Discountable + class Order < SimpleDelegator + attr_reader :line_items, :shipments + + def initialize(order) + super + @line_items = order.line_items.map { |line_item| LineItem.new(line_item, order: self) } + @shipments = order.shipments.map { |shipment| Shipment.new(shipment, order: self) } + end + + def order + __getobj__ + end + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/discountable/shipment.rb b/friendly_promotions/app/models/solidus_friendly_promotions/discountable/shipment.rb new file mode 100644 index 00000000000..e84a22a787b --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/discountable/shipment.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module Discountable + class Shipment < SimpleDelegator + attr_reader :discounts, :shipping_rates, :order + + def initialize(shipment, order:) + super(shipment) + @order = order + @discounts = [] + @shipping_rates = shipment.shipping_rates.map { |shipping_rate| ShippingRate.new(shipping_rate, shipment: self) } + end + + def shipment + __getobj__ + end + + def discounted_amount + amount + discounts.sum(&:amount) + end + end + end +end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/discountable/shipping_rate.rb b/friendly_promotions/app/models/solidus_friendly_promotions/discountable/shipping_rate.rb new file mode 100644 index 00000000000..bc938e696e3 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/discountable/shipping_rate.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module Discountable + class ShippingRate < SimpleDelegator + attr_reader :discounts, :shipment + + def initialize(shipping_rate, shipment:) + super(shipping_rate) + @shipment = shipment + @discounts = [] + end + + def shipping_rate + __getobj__ + end + + def discounted_amount + amount + discounts.sum(&:amount) + end + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/line_item_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/line_item_spec.rb new file mode 100644 index 00000000000..2778f52243d --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/line_item_spec.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe SolidusFriendlyPromotions::Discountable::LineItem do + let(:discountable_order) { double(SolidusFriendlyPromotions::Discountable::Order) } + let(:spree_line_item) { build(:line_item, price: 10, quantity: 2) } + + subject(:discountable_line_item) { described_class.new(spree_line_item, order: discountable_order) } + + describe "#order" do + subject { discountable_line_item.order } + + it { is_expected.to eq(discountable_order) } + end + + describe "#discounted_amount" do + subject(:discounted_amount) { discountable_line_item.discounted_amount } + + context "with no discounts" do + it { is_expected.to eq(20) } + end + + context "with discounts" do + let(:discount) { SolidusFriendlyPromotions::ItemDiscount.new(amount: -4) } + + before do + discountable_line_item.discounts << discount + end + + it { is_expected.to eq(16) } + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/order_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/order_spec.rb new file mode 100644 index 00000000000..637b8a050cd --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/order_spec.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe SolidusFriendlyPromotions::Discountable::Order do + subject(:discountable_order) { described_class.new(spree_order) } + + let(:spree_order) { Spree::Order.new } + + describe "#line_items" do + let(:spree_order) { create(:order_with_line_items) } + subject(:line_items) { discountable_order.line_items } + + specify "are converted into Discountable Line Items" do + line_items.each do |line_item| + expect(line_item).to be_a(SolidusFriendlyPromotions::Discountable::LineItem) + end + end + end + + describe "#shipments" do + let(:spree_order) { create(:order_ready_to_ship) } + subject(:shipments) { discountable_order.shipments } + + specify "are converted into Discountable Shipments" do + shipments.each do |shipment| + expect(shipment).to be_a(SolidusFriendlyPromotions::Discountable::Shipment) + end + end + end + + describe "delegation" do + let(:spree_order) { Spree::Order.new(email: "yoda@example.com") } + + it "forwards order attributes" do + expect(subject.email).to eq("yoda@example.com") + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/shipment_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/shipment_spec.rb new file mode 100644 index 00000000000..3c963f7dbab --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/shipment_spec.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe SolidusFriendlyPromotions::Discountable::Shipment do + let(:discountable_order) { double(SolidusFriendlyPromotions::Discountable::Order) } + + let(:spree_shipment) { build(:shipment, amount: 20) } + + subject(:discountable_shipment) { described_class.new(spree_shipment, order: discountable_order) } + + describe "#order" do + subject { discountable_shipment.order } + + it { is_expected.to eq(discountable_order) } + end + + describe "#discounted_amount" do + subject(:discounted_amount) { discountable_shipment.discounted_amount } + + context "with no discounts" do + it { is_expected.to eq(20) } + end + + context "with discounts" do + let(:discount) { SolidusFriendlyPromotions::ItemDiscount.new(amount: -4) } + + before do + discountable_shipment.discounts << discount + end + + it { is_expected.to eq(16) } + end + end + + describe "#shipping_rates" do + subject(:shipping_rates) { discountable_shipment.shipping_rates } + + specify "are converted into Discountable Shipments" do + shipping_rates.each do |shipping_rate| + expect(shipping_rate).to be_a(SolidusFriendlyPromotions::Discountable::ShippingRate) + end + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/shipping_rate_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/shipping_rate_spec.rb new file mode 100644 index 00000000000..25cbaf6a747 --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/shipping_rate_spec.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe SolidusFriendlyPromotions::Discountable::ShippingRate do + let(:discountable_shipment) { double(SolidusFriendlyPromotions::Discountable::Shipment) } + + let(:spree_shipping_rate) { build(:shipping_rate, amount: 20) } + + subject(:discountable_shipping_rate) { described_class.new(spree_shipping_rate, shipment: discountable_shipment) } + + describe "#shipment" do + subject { discountable_shipping_rate.shipment } + + it { is_expected.to eq(discountable_shipment) } + end + + describe "#discounted_amount" do + subject(:discounted_amount) { discountable_shipping_rate.discounted_amount } + + context "with no discounts" do + it { is_expected.to eq(20) } + end + + context "with discounts" do + let(:discount) { SolidusFriendlyPromotions::ItemDiscount.new(amount: -4) } + + before do + discountable_shipping_rate.discounts << discount + end + + it { is_expected.to eq(16) } + end + end +end From 326a0287d38727164c48eefbbce58ec3d57e6cc8 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 29 Sep 2023 12:53:57 +0200 Subject: [PATCH 158/524] Add integration spec for stacking promotions --- .../spec/models/promotion/integration_spec.rb | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/friendly_promotions/spec/models/promotion/integration_spec.rb b/friendly_promotions/spec/models/promotion/integration_spec.rb index 8ab354e7607..5d9d1ab1115 100644 --- a/friendly_promotions/spec/models/promotion/integration_spec.rb +++ b/friendly_promotions/spec/models/promotion/integration_spec.rb @@ -51,6 +51,49 @@ end end + context "with two promotions that should stack", :pending do + let(:shirt) { create(:product, name: "Shirt", price: 30) } + let(:pants) { create(:product, name: "Pants", price: 40) } + + let!(:distributed_amount_promo) do + create(:friendly_promotion, + :with_adjustable_action, + preferred_amount: 10.0, + apply_automatically: true, + lane: :post, + calculator_class: SolidusFriendlyPromotions::Calculators::DistributedAmount) + end + let(:shirts_rule) { SolidusFriendlyPromotions::Rules::LineItemProduct.new(products: [shirt]) } + let(:shirts_calculator) { SolidusFriendlyPromotions::Calculators::Percent.new(preferred_percent: 20) } + let(:shirts_action) { SolidusFriendlyPromotions::Actions::AdjustLineItem.new(calculator: shirts_calculator) } + let!(:shirts_promotion) do + create( + :friendly_promotion, + rules: [shirts_rule], + actions: [shirts_action], + name: "20% off shirts", + apply_automatically: true + ) + end + let(:order) { create(:order) } + + before do + order.contents.add(shirt.master, 1) + order.contents.add(pants.master, 1) + end + + it "does all the right things" do + expect(order.adjustments).to be_empty + # shirt: 30 USD - 20% = 24 USD + # Remaining total: 64 USD + # 10 USD distributed off: 54 USD + expect(order.total).to eq(54.00) + expect(order.item_total).to eq(70.00) + expect(order.item_total_before_tax).to eq(54) + expect(order.line_items.flat_map(&:adjustments).length).to eq(3) + end + end + context "with a shipment-level rule" do let!(:address) { create(:address) } let(:shipping_zone) { create(:global_zone) } From 2090add315fe10b4a5a4c2bbe7ff4876f2f2f4a3 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 29 Sep 2023 13:25:05 +0200 Subject: [PATCH 159/524] Refactor system to use Discountable/{Order, Shipment, Lineitem} We need a way to store intermediate amounts on the order we adjust. This accomplishes this in part by storing the discounts on custom delegators to the Solidus objects. --- .../actions/adjust_line_item.rb | 2 +- .../actions/adjust_shipment.rb | 2 +- .../friendly_promotion_discounter.rb | 12 +++++------- .../order_discounter.rb | 18 +++++------------- .../promotion_action.rb | 2 +- .../rules/first_order.rb | 2 +- .../rules/first_repeat_purchase_since.rb | 2 +- .../rules/item_total.rb | 2 +- .../rules/line_item_option_value.rb | 2 +- .../rules/line_item_product.rb | 2 +- .../rules/line_item_taxon.rb | 2 +- .../rules/nth_order.rb | 2 +- .../rules/one_use_per_user.rb | 2 +- .../rules/option_value.rb | 2 +- .../rules/product.rb | 2 +- .../rules/shipping_method.rb | 2 +- .../solidus_friendly_promotions/rules/store.rb | 2 +- .../solidus_friendly_promotions/rules/taxon.rb | 2 +- .../solidus_friendly_promotions/rules/user.rb | 2 +- .../rules/user_logged_in.rb | 2 +- .../rules/user_role.rb | 2 +- .../actions/adjust_shipment_spec.rb | 6 +++--- .../promotion_action_spec.rb | 13 +++++++------ .../rules/first_repeat_purchase_since_spec.rb | 4 ++-- .../rules/nth_order_spec.rb | 2 +- .../rules/option_value_spec.rb | 4 ++-- 26 files changed, 44 insertions(+), 53 deletions(-) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb index 394d0b660cd..0e2bc9ce9ba 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb @@ -4,7 +4,7 @@ module SolidusFriendlyPromotions module Actions class AdjustLineItem < PromotionAction def can_discount?(object) - object.is_a? Spree::LineItem + object.is_a? Discountable::LineItem end def available_calculators diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb index 82d73533943..d52354ab0d2 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb @@ -4,7 +4,7 @@ module SolidusFriendlyPromotions module Actions class AdjustShipment < PromotionAction def can_discount?(object) - object.is_a?(Spree::Shipment) || object.is_a?(Spree::ShippingRate) + object.is_a?(Discountable::Shipment) || object.is_a?(Discountable::ShippingRate) end def available_calculators diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb b/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb index 1401bd811e2..db0310b3717 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb @@ -5,7 +5,7 @@ class FriendlyPromotionDiscounter attr_reader :order, :promotions, :item_discounter def initialize(order) - @order = order + @order = Discountable::Order.new(order) @promotions = PromotionEligibility.new(promotable: order, possible_promotions: possible_promotions).call @item_discounter = ItemDiscounter.new(promotions: promotions) end @@ -13,12 +13,10 @@ def initialize(order) def call return nil if order.shipped? - OrderDiscounts.new( - order_id: order.id, - line_item_discounts: adjust_line_items, - shipment_discounts: adjust_shipments, - shipping_rate_discounts: adjust_shipping_rates - ) + adjust_line_items + adjust_shipments + adjust_shipping_rates + order end private diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb b/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb index cc693364741..7e60bf031e2 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb @@ -7,31 +7,23 @@ def initialize(order) end def call - all_order_discounts = SolidusFriendlyPromotions.config.discounters.filter_map do |discounter| - discounter.new(order).call - end + @order = FriendlyPromotionDiscounter.new(order).call @order.line_items.each do |item| - all_line_item_discounts = all_order_discounts.flat_map(&:line_item_discounts) - item_discounts = all_line_item_discounts.select { |element| element.item == item } - chosen_item_discounts = SolidusFriendlyPromotions.config.discount_chooser_class.new(item).call(item_discounts) + chosen_item_discounts = SolidusFriendlyPromotions.config.discount_chooser_class.new(item).call(item.discounts) update_adjustments(item, chosen_item_discounts) end @order.shipments.each do |item| - all_shipment_discounts = all_order_discounts.flat_map(&:shipment_discounts) - item_discounts = all_shipment_discounts.select { |element| element.item == item } - chosen_item_discounts = SolidusFriendlyPromotions.config.discount_chooser_class.new(item).call(item_discounts) + chosen_item_discounts = SolidusFriendlyPromotions.config.discount_chooser_class.new(item).call(item.discounts) update_adjustments(item, chosen_item_discounts) end @order.shipments.flat_map(&:shipping_rates).each do |item| - all_item_discounts = all_order_discounts.flat_map(&:shipping_rate_discounts) - item_discounts = all_item_discounts.select { |element| element.item == item } - chosen_item_discounts = SolidusFriendlyPromotions.config.discount_chooser_class.new(item).call(item_discounts) + chosen_item_discounts = SolidusFriendlyPromotions.config.discount_chooser_class.new(item).call(item.discounts) item.discounts = chosen_item_discounts.map do |discount| SolidusFriendlyPromotions::ShippingRateDiscount.new( - shipping_rate: item, + shipping_rate: item.shipping_rate, amount: discount.amount, label: discount.label ) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb index 27eb152b3b2..6f4998ceba7 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb @@ -27,7 +27,7 @@ def can_discount?(object) end def discount(adjustable) - ItemDiscount.new( + adjustable.discounts << ItemDiscount.new( item: adjustable, label: adjustment_label(adjustable), amount: compute_amount(adjustable), diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_order.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_order.rb index 65ccd9921de..9d6f81f9e99 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_order.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_order.rb @@ -6,7 +6,7 @@ class FirstOrder < PromotionRule attr_reader :user, :email def applicable?(promotable) - promotable.is_a?(Spree::Order) + promotable.is_a?(Discountable::Order) end def eligible?(order, options = {}) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb index dfca964597d..2f64e45448a 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb @@ -8,7 +8,7 @@ class FirstRepeatPurchaseSince < PromotionRule # This promotion is applicable to orders only. def applicable?(promotable) - promotable.is_a?(Spree::Order) + promotable.is_a?(Discountable::Order) end # This is never eligible if the order does not have a user, and that user does not have any previous completed orders. diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb index 12df85239e9..55fc42ec782 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb @@ -27,7 +27,7 @@ def self.operator_options end def applicable?(promotable) - promotable.is_a?(Spree::Order) + promotable.is_a?(Discountable::Order) end def eligible?(order, _options = {}) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_option_value.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_option_value.rb index b8252820c6e..a914702a4d9 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_option_value.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_option_value.rb @@ -6,7 +6,7 @@ class LineItemOptionValue < PromotionRule preference :eligible_values, :hash def applicable?(promotable) - promotable.is_a?(Spree::LineItem) + promotable.is_a?(Discountable::LineItem) end def eligible?(line_item, _options = {}) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb index b24e8e55a38..fb16c2fd645 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb @@ -17,7 +17,7 @@ class LineItemProduct < PromotionRule preference :match_policy, :string, default: MATCH_POLICIES.first def applicable?(promotable) - promotable.is_a?(Spree::LineItem) + promotable.is_a?(Discountable::LineItem) end def eligible?(line_item, _options = {}) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb index e0fd27b693f..204a12e220c 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb @@ -13,7 +13,7 @@ class LineItemTaxon < PromotionRule preference :match_policy, :string, default: MATCH_POLICIES.first def applicable?(promotable) - promotable.is_a?(Spree::LineItem) + promotable.is_a?(Discountable::LineItem) end def eligible?(line_item, _options = {}) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb index 04cb592bd6c..041cbc538ba 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb @@ -10,7 +10,7 @@ class NthOrder < PromotionRule # This promotion is applicable to orders only. def applicable?(promotable) - promotable.is_a?(Spree::Order) + promotable.is_a?(Discountable::Order) end # This is never eligible if the order does not have a user, and that user does not have any previous completed orders. diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb index 5c9d13a89ef..8393c1ffddf 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb @@ -4,7 +4,7 @@ module SolidusFriendlyPromotions module Rules class OneUsePerUser < PromotionRule def applicable?(promotable) - promotable.is_a?(Spree::Order) + promotable.is_a?(Discountable::Order) end def eligible?(order, _options = {}) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/option_value.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/option_value.rb index f8b25aa040e..9cd76948052 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/option_value.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/option_value.rb @@ -6,7 +6,7 @@ class OptionValue < PromotionRule preference :eligible_values, :hash def applicable?(promotable) - promotable.is_a?(Spree::Order) + promotable.is_a?(Discountable::Order) end def eligible?(order, _options = {}) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb index c7a5d83732e..02fadb6945d 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb @@ -29,7 +29,7 @@ def eligible_products end def applicable?(promotable) - promotable.is_a?(Spree::Order) + promotable.is_a?(Discountable::Order) end def eligible?(order, _options = {}) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/shipping_method.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/shipping_method.rb index eb745f7605e..dce6d5afad8 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/shipping_method.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/shipping_method.rb @@ -6,7 +6,7 @@ class ShippingMethod < PromotionRule preference :shipping_method_ids, type: :array, default: [] def applicable?(promotable) - promotable.is_a?(Spree::Shipment) || promotable.is_a?(Spree::ShippingRate) + promotable.is_a?(Discountable::Shipment) || promotable.is_a?(Discountable::ShippingRate) end def eligible?(promotable) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb index 3c08784975c..182e9f4928c 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb @@ -13,7 +13,7 @@ def preload_relations end def applicable?(promotable) - promotable.is_a?(Spree::Order) + promotable.is_a?(Discountable::Order) end def eligible?(order, _options = {}) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb index 9130cadcfe2..9f20c285791 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb @@ -17,7 +17,7 @@ def preload_relations preference :match_policy, :string, default: MATCH_POLICIES.first def applicable?(promotable) - promotable.is_a?(Spree::Order) + promotable.is_a?(Discountable::Order) end def eligible?(order, _options = {}) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb index 1cb7e9fd9da..7c5a170f88c 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb @@ -14,7 +14,7 @@ def preload_relations end def applicable?(promotable) - promotable.is_a?(Spree::Order) + promotable.is_a?(Discountable::Order) end def eligible?(order, _options = {}) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb index 5be6e2ea802..a866884b841 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb @@ -4,7 +4,7 @@ module SolidusFriendlyPromotions module Rules class UserLoggedIn < PromotionRule def applicable?(promotable) - promotable.is_a?(Spree::Order) + promotable.is_a?(Discountable::Order) end def eligible?(order, _options = {}) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_role.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_role.rb index e8df12f71ba..08354c58daf 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_role.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_role.rb @@ -9,7 +9,7 @@ class UserRole < PromotionRule preference :match_policy, default: MATCH_POLICIES.first def applicable?(promotable) - promotable.is_a?(Spree::Order) + promotable.is_a?(Discountable::Order) end def eligible?(order, _options = {}) diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_shipment_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_shipment_spec.rb index 7fc0206ea49..887349b32f1 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_shipment_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_shipment_spec.rb @@ -15,19 +15,19 @@ subject { action.can_discount?(promotable) } context "with a line item" do - let(:promotable) { Spree::LineItem.new } + let(:promotable) { SolidusFriendlyPromotions::Discountable::LineItem.new(Spree::Order.new, order: double) } it { is_expected.to be false } end context "with a shipment" do - let(:promotable) { Spree::Shipment.new } + let(:promotable) { SolidusFriendlyPromotions::Discountable::Shipment.new(Spree::Shipment.new, order: double) } it { is_expected.to be true } end context "with a shipping rate" do - let(:promotable) { Spree::ShippingRate.new } + let(:promotable) { SolidusFriendlyPromotions::Discountable::ShippingRate.new(Spree::ShippingRate.new, shipment: double) } it { is_expected.to be true } end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_action_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_action_spec.rb index 20f580a0331..93b1cf90cbb 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_action_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_action_spec.rb @@ -18,11 +18,12 @@ end describe "#discount" do - subject { action.discount(adjustable) } + subject { action.discount(discountable) } let(:variant) { create(:variant) } let(:order) { create(:order) } - let(:adjustable) { Spree::LineItem.new(order: order, variant: variant, price: 10) } + let(:discountable) { SolidusFriendlyPromotions::Discountable::LineItem.new(line_item, order: SolidusFriendlyPromotions::Discountable::Order.new(order)) } + let(:line_item) { Spree::LineItem.new(order: order, variant: variant, price: 10) } let(:promotion) { SolidusFriendlyPromotions::Promotion.new(name: "20 Perzent off") } let(:action) { described_class.new(promotion: promotion) } @@ -30,15 +31,15 @@ allow(action).to receive(:compute_amount).and_return(-1) end - it "returs an discount to the adjustable" do - expect(subject).to eq( + it "adds a discount to the discountable" do + expect { subject }.to change { discountable.discounts }.from([]).to([ SolidusFriendlyPromotions::ItemDiscount.new( - item: adjustable, + item: discountable, label: "Promotion (20 Perzent off)", source: action, amount: -1 ) - ) + ]) end end end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_repeat_purchase_since_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_repeat_purchase_since_spec.rb index e23fd17e3a3..8edd838e559 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_repeat_purchase_since_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_repeat_purchase_since_spec.rb @@ -7,13 +7,13 @@ subject { described_class.new.applicable?(promotable) } context "when the promotable is an order" do - let(:promotable) { Spree::Order.new } + let(:promotable) { SolidusFriendlyPromotions::Discountable::Order.new(Spree::Order.new) } it { is_expected.to be true } end context "when the promotable is not a order" do - let(:promotable) { Spree::LineItem.new } + let(:promotable) { SolidusFriendlyPromotions::Discountable::LineItem.new(Spree::LineItem.new, order: double) } it { is_expected.to be false } end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/nth_order_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/nth_order_spec.rb index d2464d35286..a85d4feccc1 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/nth_order_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/nth_order_spec.rb @@ -7,7 +7,7 @@ subject { described_class.new.applicable?(promotable) } context "when the promotable is an order" do - let(:promotable) { Spree::Order.new } + let(:promotable) { SolidusFriendlyPromotions::Discountable::Order.new(Spree::Order.new) } it { is_expected.to be true } end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/option_value_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/option_value_spec.rb index 519309378d5..4e29b4c16ce 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/option_value_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/option_value_spec.rb @@ -18,13 +18,13 @@ subject { rule.applicable?(promotable) } context "when promotable is an order" do - let(:promotable) { Spree::Order.new } + let(:promotable) { SolidusFriendlyPromotions::Discountable::Order.new(Spree::Order.new) } it { is_expected.to be true } end context "when promotable is not an order" do - let(:promotable) { Spree::LineItem.new } + let(:promotable) { SolidusFriendlyPromotions::Discountable::LineItem.new(Spree::LineItem.new, order: double) } it { is_expected.to be false } end From 889fd135abcec5fa8ae245ede81a31280ce6ca31 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 29 Sep 2023 13:45:18 +0200 Subject: [PATCH 160/524] Remove unused OrderDiscounts class The responsibilities for this class are now in the Discountable::* classes. --- .../order_discounts.rb | 18 ------------------ .../order_discounts_spec.rb | 8 -------- 2 files changed, 26 deletions(-) delete mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/order_discounts.rb delete mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/order_discounts_spec.rb diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/order_discounts.rb b/friendly_promotions/app/models/solidus_friendly_promotions/order_discounts.rb deleted file mode 100644 index df340659703..00000000000 --- a/friendly_promotions/app/models/solidus_friendly_promotions/order_discounts.rb +++ /dev/null @@ -1,18 +0,0 @@ -# frozen_string_literal: true - -module SolidusFriendlyPromotions - # Simple object to pass back discount data from a promoter. - # - # Will be used by {SolidusFriendlyPromotions::OrderDiscounter} to create or update promotion - # adjustments on an order. - # - # @attr_reader [Integer] order_id the {Spree::Order} these discounts apply to - # @attr_reader [Array] line_item_discounts an array of - # discount data for order's line items - # @attr_reader [Array] shipment_discounts an array of - # discount data for the order's shipments - class OrderDiscounts - include ActiveModel::Model - attr_accessor :order_id, :line_item_discounts, :shipment_discounts, :shipping_rate_discounts - end -end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/order_discounts_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/order_discounts_spec.rb deleted file mode 100644 index 9fe907d0f5a..00000000000 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/order_discounts_spec.rb +++ /dev/null @@ -1,8 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe SolidusFriendlyPromotions::OrderDiscounts do - it { is_expected.to respond_to :order_id } - it { is_expected.to respond_to :line_item_discounts } - it { is_expected.to respond_to :shipment_discounts } - it { is_expected.to respond_to :shipping_rate_discounts } -end From e8f83194c44c11aa4284032b0a8db6b33c20f6d2 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 29 Sep 2023 13:56:18 +0200 Subject: [PATCH 161/524] Apply promotions by lane --- .../friendly_promotion_discounter.rb | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb b/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb index db0310b3717..ac743555828 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb @@ -2,36 +2,40 @@ module SolidusFriendlyPromotions class FriendlyPromotionDiscounter - attr_reader :order, :promotions, :item_discounter + attr_reader :order, :promotions def initialize(order) @order = Discountable::Order.new(order) @promotions = PromotionEligibility.new(promotable: order, possible_promotions: possible_promotions).call - @item_discounter = ItemDiscounter.new(promotions: promotions) end def call return nil if order.shipped? - adjust_line_items - adjust_shipments - adjust_shipping_rates + SolidusFriendlyPromotions::Promotion.ordered_lanes.each do |lane, _index| + lane_promotions = promotions.select { |promotion| promotion.lane == lane } + item_discounter = ItemDiscounter.new(promotions: lane_promotions) + adjust_line_items(item_discounter) + adjust_shipments(item_discounter) + adjust_shipping_rates(item_discounter) + end + order end private - def adjust_line_items + def adjust_line_items(item_discounter) order.line_items.select do |line_item| line_item.variant.product.promotionable? end.flat_map { |line_item| item_discounter.call(line_item) } end - def adjust_shipments + def adjust_shipments(item_discounter) order.shipments.flat_map { |shipment| item_discounter.call(shipment) } end - def adjust_shipping_rates + def adjust_shipping_rates(item_discounter) order.shipments.flat_map(&:shipping_rates).flat_map { |rate| item_discounter.call(rate) } end From 2e3458054c9449d4b257ced8b2279ed4b8f8e603 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 29 Sep 2023 14:00:58 +0200 Subject: [PATCH 162/524] Add item discounts in Promotion Discounter We want to run the promotion chooser on all discounts eligible for an item by lane. For that we need a collection of discounts for that item on that lane. This separates creating the discount and adding it, giving us space to do the selection. --- .../friendly_promotion_discounter.rb | 15 ++++++++++++--- .../promotion_action.rb | 2 +- .../promotion_action_spec.rb | 6 +++--- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb b/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb index ac743555828..98a1676d639 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb @@ -28,15 +28,24 @@ def call def adjust_line_items(item_discounter) order.line_items.select do |line_item| line_item.variant.product.promotionable? - end.flat_map { |line_item| item_discounter.call(line_item) } + end.flat_map do |line_item| + discounts = item_discounter.call(line_item) + line_item.discounts.concat(discounts) + end end def adjust_shipments(item_discounter) - order.shipments.flat_map { |shipment| item_discounter.call(shipment) } + order.shipments.flat_map do |shipment| + discounts = item_discounter.call(shipment) + shipment.discounts.concat(discounts) + end end def adjust_shipping_rates(item_discounter) - order.shipments.flat_map(&:shipping_rates).flat_map { |rate| item_discounter.call(rate) } + order.shipments.flat_map(&:shipping_rates).flat_map do |rate| + discounts = item_discounter.call(rate) + rate.discounts.concat(discounts) + end end def possible_promotions diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb index 6f4998ceba7..27eb152b3b2 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb @@ -27,7 +27,7 @@ def can_discount?(object) end def discount(adjustable) - adjustable.discounts << ItemDiscount.new( + ItemDiscount.new( item: adjustable, label: adjustment_label(adjustable), amount: compute_amount(adjustable), diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_action_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_action_spec.rb index 93b1cf90cbb..940f806027a 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_action_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_action_spec.rb @@ -31,15 +31,15 @@ allow(action).to receive(:compute_amount).and_return(-1) end - it "adds a discount to the discountable" do - expect { subject }.to change { discountable.discounts }.from([]).to([ + it "returns an discount to the discountable" do + expect(subject).to eq( SolidusFriendlyPromotions::ItemDiscount.new( item: discountable, label: "Promotion (20 Perzent off)", source: action, amount: -1 ) - ]) + ) end end end From b7659b7e844c7c3a4f26e8ac58a58309076f2dfb Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 29 Sep 2023 14:09:21 +0200 Subject: [PATCH 163/524] Move discount choosing to FriendlyPromotionDiscounter We need the discount choosing to happen per lane, and that can't happen in the OrderDiscounter. --- .../friendly_promotion_discounter.rb | 9 ++++++--- .../solidus_friendly_promotions/order_discounter.rb | 13 +++++-------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb b/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb index 98a1676d639..07a062969bd 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb @@ -30,21 +30,24 @@ def adjust_line_items(item_discounter) line_item.variant.product.promotionable? end.flat_map do |line_item| discounts = item_discounter.call(line_item) - line_item.discounts.concat(discounts) + chosen_item_discounts = SolidusFriendlyPromotions.config.discount_chooser_class.new(line_item).call(discounts) + line_item.discounts.concat(chosen_item_discounts) end end def adjust_shipments(item_discounter) order.shipments.flat_map do |shipment| discounts = item_discounter.call(shipment) - shipment.discounts.concat(discounts) + chosen_item_discounts = SolidusFriendlyPromotions.config.discount_chooser_class.new(shipment).call(discounts) + shipment.discounts.concat(chosen_item_discounts) end end def adjust_shipping_rates(item_discounter) order.shipments.flat_map(&:shipping_rates).flat_map do |rate| discounts = item_discounter.call(rate) - rate.discounts.concat(discounts) + chosen_item_discounts = SolidusFriendlyPromotions.config.discount_chooser_class.new(rate).call(discounts) + rate.discounts.concat(chosen_item_discounts) end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb b/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb index 7e60bf031e2..cdb8734227a 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb @@ -10,20 +10,17 @@ def call @order = FriendlyPromotionDiscounter.new(order).call @order.line_items.each do |item| - chosen_item_discounts = SolidusFriendlyPromotions.config.discount_chooser_class.new(item).call(item.discounts) - update_adjustments(item, chosen_item_discounts) + update_adjustments(item.line_item, item.discounts) end @order.shipments.each do |item| - chosen_item_discounts = SolidusFriendlyPromotions.config.discount_chooser_class.new(item).call(item.discounts) - update_adjustments(item, chosen_item_discounts) + update_adjustments(item.shipment, item.discounts) end - @order.shipments.flat_map(&:shipping_rates).each do |item| - chosen_item_discounts = SolidusFriendlyPromotions.config.discount_chooser_class.new(item).call(item.discounts) - item.discounts = chosen_item_discounts.map do |discount| + @order.shipments.flat_map(&:shipping_rates).each do |shipping_rate| + shipping_rate.shipping_rate.discounts = shipping_rate.discounts.map do |discount| SolidusFriendlyPromotions::ShippingRateDiscount.new( - shipping_rate: item.shipping_rate, + shipping_rate: shipping_rate.shipping_rate, amount: discount.amount, label: discount.label ) From a5e7f81cb34d07de6008883ebce14a9b99f5287d Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 29 Sep 2023 14:14:51 +0200 Subject: [PATCH 164/524] Refactor order discounter This has no functional changes, but it renames some variables in this class so things don't seem as confusing. --- .../order_discounter.rb | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb b/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb index cdb8734227a..d0cfb2046cc 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb @@ -7,28 +7,29 @@ def initialize(order) end def call - @order = FriendlyPromotionDiscounter.new(order).call + discountable_order = FriendlyPromotionDiscounter.new(order).call - @order.line_items.each do |item| - update_adjustments(item.line_item, item.discounts) + discountable_order.line_items.each do |discountable_line_item| + update_adjustments(discountable_line_item.line_item, discountable_line_item.discounts) end - @order.shipments.each do |item| - update_adjustments(item.shipment, item.discounts) + discountable_order.shipments.each do |discountable_shipment| + update_adjustments(discountable_shipment.shipment, discountable_shipment.discounts) end - @order.shipments.flat_map(&:shipping_rates).each do |shipping_rate| - shipping_rate.shipping_rate.discounts = shipping_rate.discounts.map do |discount| + discountable_order.shipments.flat_map(&:shipping_rates).each do |discountable_shipping_rate| + spree_shipping_rate = discountable_shipping_rate.shipping_rate + spree_shipping_rate.discounts = discountable_shipping_rate.discounts.map do |discount| SolidusFriendlyPromotions::ShippingRateDiscount.new( - shipping_rate: shipping_rate.shipping_rate, + shipping_rate: spree_shipping_rate, amount: discount.amount, label: discount.label ) end end - @order.promo_total = (order.line_items + order.shipments).sum(&:promo_total) - @order + order.promo_total = (order.line_items + order.shipments).sum(&:promo_total) + order end private From 6f81c89560c6b89f670772b1cba72a29e9069e0a Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 29 Sep 2023 14:26:17 +0200 Subject: [PATCH 165/524] Remove unused "discounters" configuration option This was used in the order discounter anticipating features we don't have. --- .../install/templates/initializer.rb | 4 ---- .../lib/solidus_friendly_promotions/configuration.rb | 1 - 2 files changed, 5 deletions(-) diff --git a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb index 75343fc0892..95a31d53772 100644 --- a/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb +++ b/friendly_promotions/lib/generators/solidus_friendly_promotions/install/templates/initializer.rb @@ -51,10 +51,6 @@ # How many promotions should be displayed on the index page in the admin. config.promotions_per_page = 25 - config.discounters = [ - "SolidusFriendlyPromotions::FriendlyPromotionDiscounter" - ] - config.shipment_discount_calculators = [ "SolidusFriendlyPromotions::Calculators::FlatRate", "SolidusFriendlyPromotions::Calculators::FlexiRate", diff --git a/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb b/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb index 26922dd1b64..7ec7bd16c92 100644 --- a/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb +++ b/friendly_promotions/lib/solidus_friendly_promotions/configuration.rb @@ -14,7 +14,6 @@ class Configuration < Spree::Preferences::Configuration add_class_set :shipment_rules add_class_set :actions - add_class_set :discounters class_name_attribute :discount_chooser_class, default: "SolidusFriendlyPromotions::DiscountChooser" class_name_attribute :promotion_code_batch_mailer_class, From 69509fdd944baeef0ee3799b400e20bf4ccdedf6 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 29 Sep 2023 15:05:04 +0200 Subject: [PATCH 166/524] Add discounts to items after calculation Otherwise we get discounted amounts from discounts from the current lane (which we don't want). --- .../friendly_promotion_discounter.rb | 21 +++++++++++-------- .../spec/models/promotion/integration_spec.rb | 2 +- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb b/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb index 07a062969bd..e3945e1eefc 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb @@ -15,9 +15,12 @@ def call SolidusFriendlyPromotions::Promotion.ordered_lanes.each do |lane, _index| lane_promotions = promotions.select { |promotion| promotion.lane == lane } item_discounter = ItemDiscounter.new(promotions: lane_promotions) - adjust_line_items(item_discounter) - adjust_shipments(item_discounter) - adjust_shipping_rates(item_discounter) + line_item_discounts = adjust_line_items(item_discounter) + shipment_discounts = adjust_shipments(item_discounter) + shipping_rate_discounts = adjust_shipping_rates(item_discounter) + (line_item_discounts + shipment_discounts + shipping_rate_discounts).each do |item, chosen_discounts| + item.discounts.concat(chosen_discounts) + end end order @@ -28,26 +31,26 @@ def call def adjust_line_items(item_discounter) order.line_items.select do |line_item| line_item.variant.product.promotionable? - end.flat_map do |line_item| + end.map do |line_item| discounts = item_discounter.call(line_item) chosen_item_discounts = SolidusFriendlyPromotions.config.discount_chooser_class.new(line_item).call(discounts) - line_item.discounts.concat(chosen_item_discounts) + [line_item, chosen_item_discounts] end end def adjust_shipments(item_discounter) - order.shipments.flat_map do |shipment| + order.shipments.map do |shipment| discounts = item_discounter.call(shipment) chosen_item_discounts = SolidusFriendlyPromotions.config.discount_chooser_class.new(shipment).call(discounts) - shipment.discounts.concat(chosen_item_discounts) + [shipment, chosen_item_discounts] end end def adjust_shipping_rates(item_discounter) - order.shipments.flat_map(&:shipping_rates).flat_map do |rate| + order.shipments.flat_map(&:shipping_rates).map do |rate| discounts = item_discounter.call(rate) chosen_item_discounts = SolidusFriendlyPromotions.config.discount_chooser_class.new(rate).call(discounts) - rate.discounts.concat(chosen_item_discounts) + [rate, chosen_item_discounts] end end diff --git a/friendly_promotions/spec/models/promotion/integration_spec.rb b/friendly_promotions/spec/models/promotion/integration_spec.rb index 5d9d1ab1115..b51dc38d034 100644 --- a/friendly_promotions/spec/models/promotion/integration_spec.rb +++ b/friendly_promotions/spec/models/promotion/integration_spec.rb @@ -51,7 +51,7 @@ end end - context "with two promotions that should stack", :pending do + context "with two promotions that should stack" do let(:shirt) { create(:product, name: "Shirt", price: 30) } let(:pants) { create(:product, name: "Pants", price: 40) } From ebcebcc75662c3553a2fe3de4e7b905367c938ed Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 29 Sep 2023 14:48:47 +0200 Subject: [PATCH 167/524] Change calculators to use #discountable_amount This change allows use to actually use the "lanes" feature in our calculators. --- .../solidus_friendly_promotions/calculators/percent.rb | 2 +- .../calculators/tiered_flat_rate.rb | 2 +- .../solidus_friendly_promotions/discountable/line_item.rb | 2 +- .../solidus_friendly_promotions/discountable/shipment.rb | 2 +- .../discountable/shipping_rate.rb | 2 +- .../distributed_amounts_handler.rb | 2 +- .../models/solidus_friendly_promotions/promotion_action.rb | 2 +- .../calculators/distributed_amount_spec.rb | 3 ++- .../solidus_friendly_promotions/calculators/percent_spec.rb | 2 +- .../calculators/tiered_flat_rate_spec.rb | 5 +++-- .../discountable/line_item_spec.rb | 4 ++-- .../discountable/shipment_spec.rb | 4 ++-- .../discountable/shipping_rate_spec.rb | 4 ++-- .../distributed_amounts_handler_spec.rb | 6 +++++- 14 files changed, 24 insertions(+), 18 deletions(-) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/percent.rb b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/percent.rb index 8864904f254..50d51225450 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/percent.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/percent.rb @@ -10,7 +10,7 @@ class Percent < Spree::Calculator def compute(object) preferred_currency = object.order.currency currency_exponent = ::Money::Currency.find(preferred_currency).exponent - (object.amount * preferred_percent / 100).round(currency_exponent) + (object.discountable_amount * preferred_percent / 100).round(currency_exponent) end end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_flat_rate.rb b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_flat_rate.rb index 1fda3e91a05..f6cda6cc226 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_flat_rate.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/calculators/tiered_flat_rate.rb @@ -22,7 +22,7 @@ class TieredFlatRate < Spree::Calculator def compute_item(object) _base, amount = preferred_tiers.sort.reverse.detect do |value, _| - object.amount >= value + object.discountable_amount >= value end if preferred_currency.casecmp(object.currency).zero? diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/discountable/line_item.rb b/friendly_promotions/app/models/solidus_friendly_promotions/discountable/line_item.rb index d196c55386a..f732710d534 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/discountable/line_item.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/discountable/line_item.rb @@ -15,7 +15,7 @@ def line_item __getobj__ end - def discounted_amount + def discountable_amount amount + discounts.sum(&:amount) end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/discountable/shipment.rb b/friendly_promotions/app/models/solidus_friendly_promotions/discountable/shipment.rb index e84a22a787b..e16c5a96dc7 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/discountable/shipment.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/discountable/shipment.rb @@ -16,7 +16,7 @@ def shipment __getobj__ end - def discounted_amount + def discountable_amount amount + discounts.sum(&:amount) end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/discountable/shipping_rate.rb b/friendly_promotions/app/models/solidus_friendly_promotions/discountable/shipping_rate.rb index bc938e696e3..77a6ca46ea6 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/discountable/shipping_rate.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/discountable/shipping_rate.rb @@ -15,7 +15,7 @@ def shipping_rate __getobj__ end - def discounted_amount + def discountable_amount amount + discounts.sum(&:amount) end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/distributed_amounts_handler.rb b/friendly_promotions/app/models/solidus_friendly_promotions/distributed_amounts_handler.rb index 6e0c0630c0d..485b8b22574 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/distributed_amounts_handler.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/distributed_amounts_handler.rb @@ -29,7 +29,7 @@ def line_item_ids end def elligible_amounts - line_items.map(&:amount) + line_items.map(&:discountable_amount) end def subtotal diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb index 27eb152b3b2..9ce0c9df2d9 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_action.rb @@ -38,7 +38,7 @@ def discount(adjustable) # Ensure a negative amount which does not exceed the object's amount def compute_amount(adjustable) promotion_amount = calculator.compute(adjustable) || BigDecimal("0") - [adjustable.amount, promotion_amount.abs].min * -1 + [adjustable.discountable_amount, promotion_amount.abs].min * -1 end def adjustment_label(adjustable) diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/distributed_amount_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/distributed_amount_spec.rb index 65aca311d33..1f4cc9200b0 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/distributed_amount_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/distributed_amount_spec.rb @@ -10,7 +10,8 @@ end let(:rules) { [] } let(:action) { SolidusFriendlyPromotions::Actions::AdjustLineItem.create(calculator: calculator) } - let(:order) { create(:order_with_line_items, line_items_attributes: line_items_attributes) } + let(:spree_order) { create(:order_with_line_items, line_items_attributes: line_items_attributes) } + let(:order) { SolidusFriendlyPromotions::Discountable::Order.new(spree_order) } let(:currency) { "USD" } context "applied to an order" do diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/percent_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/percent_spec.rb index f71734952e3..fe98125d7dc 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/percent_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/percent_spec.rb @@ -7,7 +7,7 @@ context "compute" do let(:currency) { "USD" } let(:order) { double(currency: currency) } - let(:line_item) { double("LineItem", amount: 100, order: order) } + let(:line_item) { double("SolidusFriendlyPromotions::Discountable::LineItem", discountable_amount: 100, order: order) } before { subject.preferred_percent = 15 } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_flat_rate_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_flat_rate_spec.rb index ee438e6c34c..490dac04247 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_flat_rate_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_flat_rate_spec.rb @@ -75,7 +75,7 @@ line_items_price: amount ) end - let(:line_item) { order.line_items.first } + let(:line_item) { SolidusFriendlyPromotions::Discountable::LineItem.new(order.line_items.first, order: order) } let(:preferred_currency) { "USD" } before do @@ -109,7 +109,8 @@ context "with a shipment" do subject { calculator.compute(shipment) } - let(:shipment) { Spree::Shipment.new(order: order, amount: shipping_cost) } + let(:spree_shipment) { Spree::Shipment.new(order: order, amount: shipping_cost) } + let(:shipment) { SolidusFriendlyPromotions::Discountable::Shipment.new(spree_shipment, order: order) } let(:line_item_count) { 1 } let(:amount) { 10 } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/line_item_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/line_item_spec.rb index 2778f52243d..c107b8211fa 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/line_item_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/line_item_spec.rb @@ -14,8 +14,8 @@ it { is_expected.to eq(discountable_order) } end - describe "#discounted_amount" do - subject(:discounted_amount) { discountable_line_item.discounted_amount } + describe "#discountable_amount" do + subject(:discountable_amount) { discountable_line_item.discountable_amount } context "with no discounts" do it { is_expected.to eq(20) } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/shipment_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/shipment_spec.rb index 3c963f7dbab..96e58f1fe1a 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/shipment_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/shipment_spec.rb @@ -15,8 +15,8 @@ it { is_expected.to eq(discountable_order) } end - describe "#discounted_amount" do - subject(:discounted_amount) { discountable_shipment.discounted_amount } + describe "#discountable_amount" do + subject(:discountable_amount) { discountable_shipment.discountable_amount } context "with no discounts" do it { is_expected.to eq(20) } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/shipping_rate_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/shipping_rate_spec.rb index 25cbaf6a747..f6c6d7781c3 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/shipping_rate_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/shipping_rate_spec.rb @@ -15,8 +15,8 @@ it { is_expected.to eq(discountable_shipment) } end - describe "#discounted_amount" do - subject(:discounted_amount) { discountable_shipping_rate.discounted_amount } + describe "#discountable_amount" do + subject(:discountable_amount) { discountable_shipping_rate.discountable_amount } context "with no discounts" do it { is_expected.to eq(20) } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/distributed_amounts_handler_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/distributed_amounts_handler_spec.rb index f3d244902c0..ee267f901c6 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/distributed_amounts_handler_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/distributed_amounts_handler_spec.rb @@ -3,13 +3,17 @@ require "spec_helper" RSpec.describe SolidusFriendlyPromotions::DistributedAmountsHandler, type: :model do - let(:order) do + let(:spree_order) do FactoryBot.create( :order_with_line_items, line_items_attributes: line_items_attributes ) end + let(:order) do + SolidusFriendlyPromotions::Discountable::Order.new(spree_order) + end + let(:handler) { described_class.new(order.line_items, total_amount) } From 7499b173ea6c46c55c5641b283e8cc8c808ab787 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Mon, 2 Oct 2023 15:10:00 +0200 Subject: [PATCH 168/524] Add Spree::{LineItem,Shipment,ShippingRate}#discountable_amount This little module allows us to remove the delegators in a future commit. They are quite clunky to work with. --- .../line_item_decorator.rb | 7 ++++ .../shipment_decorator.rb | 7 ++++ .../shipping_rate_decorator.rb | 5 ++- .../discountable_amount.rb | 21 +++++++++++ .../spec/models/spree/line_item_spec.rb | 37 +++++++++++++++++++ .../spec/models/spree/shipment_spec.rb | 24 ++++++++++++ .../spec/models/spree/shipping_rate_spec.rb | 32 ++++++++++++++++ 7 files changed, 131 insertions(+), 2 deletions(-) create mode 100644 friendly_promotions/app/decorators/models/solidus_friendly_promotions/line_item_decorator.rb create mode 100644 friendly_promotions/app/decorators/models/solidus_friendly_promotions/shipment_decorator.rb create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/discountable_amount.rb create mode 100644 friendly_promotions/spec/models/spree/line_item_spec.rb create mode 100644 friendly_promotions/spec/models/spree/shipment_spec.rb diff --git a/friendly_promotions/app/decorators/models/solidus_friendly_promotions/line_item_decorator.rb b/friendly_promotions/app/decorators/models/solidus_friendly_promotions/line_item_decorator.rb new file mode 100644 index 00000000000..686d0686f48 --- /dev/null +++ b/friendly_promotions/app/decorators/models/solidus_friendly_promotions/line_item_decorator.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module LineItemDecorator + Spree::LineItem.prepend SolidusFriendlyPromotions::DiscountableAmount + end +end diff --git a/friendly_promotions/app/decorators/models/solidus_friendly_promotions/shipment_decorator.rb b/friendly_promotions/app/decorators/models/solidus_friendly_promotions/shipment_decorator.rb new file mode 100644 index 00000000000..8772f6d53ae --- /dev/null +++ b/friendly_promotions/app/decorators/models/solidus_friendly_promotions/shipment_decorator.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module ShipmentDecorator + Spree::Shipment.prepend SolidusFriendlyPromotions::DiscountableAmount + end +end diff --git a/friendly_promotions/app/decorators/models/solidus_friendly_promotions/shipping_rate_decorator.rb b/friendly_promotions/app/decorators/models/solidus_friendly_promotions/shipping_rate_decorator.rb index 66cf3cf5cbf..b39a4c40a64 100644 --- a/friendly_promotions/app/decorators/models/solidus_friendly_promotions/shipping_rate_decorator.rb +++ b/friendly_promotions/app/decorators/models/solidus_friendly_promotions/shipping_rate_decorator.rb @@ -22,7 +22,8 @@ def total_before_tax def promo_total discounts.sum(&:amount) end + + Spree::ShippingRate.prepend SolidusFriendlyPromotions::DiscountableAmount + Spree::ShippingRate.prepend self end end - -Spree::ShippingRate.prepend(SolidusFriendlyPromotions::ShippingRateDecorator) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/discountable_amount.rb b/friendly_promotions/app/models/solidus_friendly_promotions/discountable_amount.rb new file mode 100644 index 00000000000..e67e60d6ee2 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/discountable_amount.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module DiscountableAmount + def discountable_amount + amount + current_discounts.sum(&:amount) + end + + def current_discounts + @current_discounts ||= [] + end + + def current_discounts=(args) + @current_discounts = args + end + + def reset_current_discounts + @current_discounts = [] + end + end +end diff --git a/friendly_promotions/spec/models/spree/line_item_spec.rb b/friendly_promotions/spec/models/spree/line_item_spec.rb new file mode 100644 index 00000000000..44fc87336c9 --- /dev/null +++ b/friendly_promotions/spec/models/spree/line_item_spec.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe Spree::LineItem do + describe "#discountable_amount" do + let(:discounts) { [] } + let(:line_item) { Spree::LineItem.new(price: 10, quantity: 2, current_discounts: discounts) } + + subject(:discountable_amount) { line_item.discountable_amount } + + it { is_expected.to eq(20) } + + context "with a proposed discount" do + let(:discounts) do + [ + SolidusFriendlyPromotions::ItemDiscount.new(item: double, amount: -2, label: "Foo", source: double) + ] + end + + it { is_expected.to eq(18) } + end + end + + describe "#reset_current_discounts" do + let(:line_item) { Spree::LineItem.new } + + subject { line_item.reset_current_discounts } + before do + line_item.current_discounts << SolidusFriendlyPromotions::ItemDiscount.new(item: double, amount: -2, label: "Foo", source: double) + end + + it "resets the current discounts to an empty array" do + expect { subject }.to change { line_item.current_discounts.length }.from(1).to(0) + end + end +end diff --git a/friendly_promotions/spec/models/spree/shipment_spec.rb b/friendly_promotions/spec/models/spree/shipment_spec.rb new file mode 100644 index 00000000000..65133bd1c70 --- /dev/null +++ b/friendly_promotions/spec/models/spree/shipment_spec.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe Spree::Shipment do + describe "#discountable_amount" do + let(:discounts) { [] } + let(:shipment) { Spree::Shipment.new(amount: 20, current_discounts: discounts) } + + subject(:discountable_amount) { shipment.discountable_amount } + + it { is_expected.to eq(20) } + + context "with a proposed discount" do + let(:discounts) do + [ + SolidusFriendlyPromotions::ItemDiscount.new(item: double, amount: -2, label: "Foo", source: double) + ] + end + + it { is_expected.to eq(18) } + end + end +end diff --git a/friendly_promotions/spec/models/spree/shipping_rate_spec.rb b/friendly_promotions/spec/models/spree/shipping_rate_spec.rb index 05a770710eb..63a12db3d92 100644 --- a/friendly_promotions/spec/models/spree/shipping_rate_spec.rb +++ b/friendly_promotions/spec/models/spree/shipping_rate_spec.rb @@ -43,4 +43,36 @@ it { is_expected.to eq(Spree::Money.new("0")) } end + + describe "#discountable_amount" do + let(:discounts) { [] } + let(:shipping_rate) { Spree::ShippingRate.new(amount: 20, current_discounts: discounts) } + + subject(:discountable_amount) { shipping_rate.discountable_amount } + + it { is_expected.to eq(20) } + + context "with a proposed discount" do + let(:discounts) do + [ + SolidusFriendlyPromotions::ItemDiscount.new(item: double, amount: -2, label: "Foo", source: double) + ] + end + + it { is_expected.to eq(18) } + end + end + + describe "#reset_current_discounts" do + let(:shipping_rate) { Spree::ShippingRate.new } + + subject { shipping_rate.reset_current_discounts } + before do + shipping_rate.current_discounts << SolidusFriendlyPromotions::ItemDiscount.new(item: double, amount: -2, label: "Foo", source: double) + end + + it "resets the current discounts to an empty array" do + expect { subject }.to change { shipping_rate.current_discounts.length }.from(1).to(0) + end + end end From 06573c4b452af1e18f80818ed34ae19cd0a8eedb Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Mon, 2 Oct 2023 20:04:47 +0200 Subject: [PATCH 169/524] Provide API for resetting order discounts on order and shipment The order can be called to reset discounts on all leaf items. This is what does it. --- .../order_decorator.rb | 5 +++++ .../shipment_decorator.rb | 7 +++++++ .../spec/models/spree/order_spec.rb | 14 ++++++++++++++ .../spec/models/spree/shipment_spec.rb | 15 +++++++++++++++ 4 files changed, 41 insertions(+) diff --git a/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_decorator.rb b/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_decorator.rb index 23e13c39abc..a70817c8a4e 100644 --- a/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_decorator.rb +++ b/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_decorator.rb @@ -20,6 +20,11 @@ def ensure_promotions_eligible super end + def reset_current_discounts + line_items.each(&:reset_current_discounts) + shipments.each(&:reset_current_discounts) + end + Spree::Order.prepend self end end diff --git a/friendly_promotions/app/decorators/models/solidus_friendly_promotions/shipment_decorator.rb b/friendly_promotions/app/decorators/models/solidus_friendly_promotions/shipment_decorator.rb index 8772f6d53ae..cca74442c8b 100644 --- a/friendly_promotions/app/decorators/models/solidus_friendly_promotions/shipment_decorator.rb +++ b/friendly_promotions/app/decorators/models/solidus_friendly_promotions/shipment_decorator.rb @@ -3,5 +3,12 @@ module SolidusFriendlyPromotions module ShipmentDecorator Spree::Shipment.prepend SolidusFriendlyPromotions::DiscountableAmount + + def reset_current_discounts + super + shipping_rates.each(&:reset_current_discounts) + end + + Spree::Shipment.prepend self end end diff --git a/friendly_promotions/spec/models/spree/order_spec.rb b/friendly_promotions/spec/models/spree/order_spec.rb index 51bfa5af2b9..15ee8d76024 100644 --- a/friendly_promotions/spec/models/spree/order_spec.rb +++ b/friendly_promotions/spec/models/spree/order_spec.rb @@ -5,4 +5,18 @@ RSpec.describe Spree::Order do it { is_expected.to have_many :friendly_promotions } it { is_expected.to have_many :friendly_order_promotions } + + describe "#reset_current_discounts" do + let(:line_item) { Spree::LineItem.new } + let(:shipment) { Spree::Shipment.new } + let(:order) { Spree::Order.new(shipments: [shipment], line_items: [line_item]) } + + subject { order.reset_current_discounts } + + it "resets the current discounts on all line items and shipments" do + expect(line_item).to receive(:reset_current_discounts) + expect(shipment).to receive(:reset_current_discounts) + subject + end + end end diff --git a/friendly_promotions/spec/models/spree/shipment_spec.rb b/friendly_promotions/spec/models/spree/shipment_spec.rb index 65133bd1c70..06a2f3df8f8 100644 --- a/friendly_promotions/spec/models/spree/shipment_spec.rb +++ b/friendly_promotions/spec/models/spree/shipment_spec.rb @@ -20,5 +20,20 @@ it { is_expected.to eq(18) } end + + describe "#reset_current_discounts" do + let(:shipping_rate) { Spree::ShippingRate.new } + let(:shipment) { Spree::Shipment.new(shipping_rates: [shipping_rate]) } + + subject { shipment.reset_current_discounts } + before do + shipment.current_discounts << SolidusFriendlyPromotions::ItemDiscount.new(item: double, amount: -2, label: "Foo", source: double) + end + + it "resets the current discounts to an empty array and resets current discounts on all shipping rates" do + expect(shipping_rate).to receive(:reset_current_discounts) + expect { subject }.to change { shipment.current_discounts.length }.from(1).to(0) + end + end end end From 038f918d27ef17e300ec7e051370dcd3404c7e9d Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Mon, 2 Oct 2023 20:05:31 +0200 Subject: [PATCH 170/524] Use Spree::{Order;LineItem;ShippingRate} in all models It's less change to all rules and actions if we just use the core Solidus models to store discounts. --- .../actions/adjust_line_item.rb | 2 +- .../actions/adjust_shipment.rb | 2 +- .../friendly_promotion_discounter.rb | 6 +++-- .../order_discounter.rb | 24 +++++++++---------- .../rules/first_order.rb | 2 +- .../rules/first_repeat_purchase_since.rb | 2 +- .../rules/item_total.rb | 2 +- .../rules/line_item_option_value.rb | 2 +- .../rules/line_item_product.rb | 2 +- .../rules/line_item_taxon.rb | 2 +- .../rules/nth_order.rb | 2 +- .../rules/one_use_per_user.rb | 2 +- .../rules/option_value.rb | 2 +- .../rules/product.rb | 2 +- .../rules/shipping_method.rb | 2 +- .../rules/store.rb | 2 +- .../rules/taxon.rb | 2 +- .../solidus_friendly_promotions/rules/user.rb | 2 +- .../rules/user_logged_in.rb | 2 +- .../rules/user_role.rb | 2 +- .../actions/adjust_shipment_spec.rb | 6 ++--- .../calculators/distributed_amount_spec.rb | 3 +-- .../calculators/percent_spec.rb | 2 +- .../calculators/tiered_flat_rate_spec.rb | 5 ++-- .../distributed_amounts_handler_spec.rb | 6 +---- .../promotion_action_spec.rb | 3 +-- .../rules/first_repeat_purchase_since_spec.rb | 4 ++-- .../rules/nth_order_spec.rb | 2 +- .../rules/option_value_spec.rb | 4 ++-- 29 files changed, 48 insertions(+), 53 deletions(-) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb index 0e2bc9ce9ba..394d0b660cd 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_line_item.rb @@ -4,7 +4,7 @@ module SolidusFriendlyPromotions module Actions class AdjustLineItem < PromotionAction def can_discount?(object) - object.is_a? Discountable::LineItem + object.is_a? Spree::LineItem end def available_calculators diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb index d52354ab0d2..82d73533943 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/actions/adjust_shipment.rb @@ -4,7 +4,7 @@ module SolidusFriendlyPromotions module Actions class AdjustShipment < PromotionAction def can_discount?(object) - object.is_a?(Discountable::Shipment) || object.is_a?(Discountable::ShippingRate) + object.is_a?(Spree::Shipment) || object.is_a?(Spree::ShippingRate) end def available_calculators diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb b/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb index e3945e1eefc..9c1d7b4e978 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb @@ -5,13 +5,15 @@ class FriendlyPromotionDiscounter attr_reader :order, :promotions def initialize(order) - @order = Discountable::Order.new(order) + @order = order @promotions = PromotionEligibility.new(promotable: order, possible_promotions: possible_promotions).call end def call return nil if order.shipped? + order.reset_current_discounts + SolidusFriendlyPromotions::Promotion.ordered_lanes.each do |lane, _index| lane_promotions = promotions.select { |promotion| promotion.lane == lane } item_discounter = ItemDiscounter.new(promotions: lane_promotions) @@ -19,7 +21,7 @@ def call shipment_discounts = adjust_shipments(item_discounter) shipping_rate_discounts = adjust_shipping_rates(item_discounter) (line_item_discounts + shipment_discounts + shipping_rate_discounts).each do |item, chosen_discounts| - item.discounts.concat(chosen_discounts) + item.current_discounts.concat(chosen_discounts) end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb b/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb index d0cfb2046cc..7792c65afed 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/order_discounter.rb @@ -9,19 +9,18 @@ def initialize(order) def call discountable_order = FriendlyPromotionDiscounter.new(order).call - discountable_order.line_items.each do |discountable_line_item| - update_adjustments(discountable_line_item.line_item, discountable_line_item.discounts) + discountable_order.line_items.each do |line_item| + update_adjustments(line_item, line_item.current_discounts) end - discountable_order.shipments.each do |discountable_shipment| - update_adjustments(discountable_shipment.shipment, discountable_shipment.discounts) + discountable_order.shipments.each do |shipment| + update_adjustments(shipment, shipment.current_discounts) end - discountable_order.shipments.flat_map(&:shipping_rates).each do |discountable_shipping_rate| - spree_shipping_rate = discountable_shipping_rate.shipping_rate - spree_shipping_rate.discounts = discountable_shipping_rate.discounts.map do |discount| + discountable_order.shipments.flat_map(&:shipping_rates).each do |shipping_rate| + shipping_rate.discounts = shipping_rate.current_discounts.map do |discount| SolidusFriendlyPromotions::ShippingRateDiscount.new( - shipping_rate: spree_shipping_rate, + shipping_rate: shipping_rate, amount: discount.amount, label: discount.label ) @@ -42,17 +41,18 @@ def call # # @private # @param [#adjustments] item a {Spree::LineItem} or {Spree::Shipment} - # @param [Array] taxed_items a list of calculated discounts for an item + # @param [Array] item_discounts a list of calculated discounts for an item # @return [void] - def update_adjustments(item, taxed_items) + def update_adjustments(item, item_discounts) promotion_adjustments = item.adjustments.select(&:friendly_promotion?) - active_adjustments = taxed_items.map do |tax_item| - update_adjustment(item, tax_item) + active_adjustments = item_discounts.map do |item_discount| + update_adjustment(item, item_discount) end item.update(promo_total: active_adjustments.sum(&:amount)) # Remove any tax adjustments tied to rates which no longer match. unmatched_adjustments = promotion_adjustments - active_adjustments + item.adjustments.destroy(unmatched_adjustments) end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_order.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_order.rb index 9d6f81f9e99..65ccd9921de 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_order.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_order.rb @@ -6,7 +6,7 @@ class FirstOrder < PromotionRule attr_reader :user, :email def applicable?(promotable) - promotable.is_a?(Discountable::Order) + promotable.is_a?(Spree::Order) end def eligible?(order, options = {}) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb index 2f64e45448a..dfca964597d 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/first_repeat_purchase_since.rb @@ -8,7 +8,7 @@ class FirstRepeatPurchaseSince < PromotionRule # This promotion is applicable to orders only. def applicable?(promotable) - promotable.is_a?(Discountable::Order) + promotable.is_a?(Spree::Order) end # This is never eligible if the order does not have a user, and that user does not have any previous completed orders. diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb index 55fc42ec782..12df85239e9 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/item_total.rb @@ -27,7 +27,7 @@ def self.operator_options end def applicable?(promotable) - promotable.is_a?(Discountable::Order) + promotable.is_a?(Spree::Order) end def eligible?(order, _options = {}) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_option_value.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_option_value.rb index a914702a4d9..b8252820c6e 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_option_value.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_option_value.rb @@ -6,7 +6,7 @@ class LineItemOptionValue < PromotionRule preference :eligible_values, :hash def applicable?(promotable) - promotable.is_a?(Discountable::LineItem) + promotable.is_a?(Spree::LineItem) end def eligible?(line_item, _options = {}) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb index fb16c2fd645..b24e8e55a38 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_product.rb @@ -17,7 +17,7 @@ class LineItemProduct < PromotionRule preference :match_policy, :string, default: MATCH_POLICIES.first def applicable?(promotable) - promotable.is_a?(Discountable::LineItem) + promotable.is_a?(Spree::LineItem) end def eligible?(line_item, _options = {}) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb index 204a12e220c..e0fd27b693f 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/line_item_taxon.rb @@ -13,7 +13,7 @@ class LineItemTaxon < PromotionRule preference :match_policy, :string, default: MATCH_POLICIES.first def applicable?(promotable) - promotable.is_a?(Discountable::LineItem) + promotable.is_a?(Spree::LineItem) end def eligible?(line_item, _options = {}) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb index 041cbc538ba..04cb592bd6c 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/nth_order.rb @@ -10,7 +10,7 @@ class NthOrder < PromotionRule # This promotion is applicable to orders only. def applicable?(promotable) - promotable.is_a?(Discountable::Order) + promotable.is_a?(Spree::Order) end # This is never eligible if the order does not have a user, and that user does not have any previous completed orders. diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb index 8393c1ffddf..5c9d13a89ef 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/one_use_per_user.rb @@ -4,7 +4,7 @@ module SolidusFriendlyPromotions module Rules class OneUsePerUser < PromotionRule def applicable?(promotable) - promotable.is_a?(Discountable::Order) + promotable.is_a?(Spree::Order) end def eligible?(order, _options = {}) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/option_value.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/option_value.rb index 9cd76948052..f8b25aa040e 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/option_value.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/option_value.rb @@ -6,7 +6,7 @@ class OptionValue < PromotionRule preference :eligible_values, :hash def applicable?(promotable) - promotable.is_a?(Discountable::Order) + promotable.is_a?(Spree::Order) end def eligible?(order, _options = {}) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb index 02fadb6945d..c7a5d83732e 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/product.rb @@ -29,7 +29,7 @@ def eligible_products end def applicable?(promotable) - promotable.is_a?(Discountable::Order) + promotable.is_a?(Spree::Order) end def eligible?(order, _options = {}) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/shipping_method.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/shipping_method.rb index dce6d5afad8..eb745f7605e 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/shipping_method.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/shipping_method.rb @@ -6,7 +6,7 @@ class ShippingMethod < PromotionRule preference :shipping_method_ids, type: :array, default: [] def applicable?(promotable) - promotable.is_a?(Discountable::Shipment) || promotable.is_a?(Discountable::ShippingRate) + promotable.is_a?(Spree::Shipment) || promotable.is_a?(Spree::ShippingRate) end def eligible?(promotable) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb index 182e9f4928c..3c08784975c 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/store.rb @@ -13,7 +13,7 @@ def preload_relations end def applicable?(promotable) - promotable.is_a?(Discountable::Order) + promotable.is_a?(Spree::Order) end def eligible?(order, _options = {}) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb index 9f20c285791..9130cadcfe2 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/taxon.rb @@ -17,7 +17,7 @@ def preload_relations preference :match_policy, :string, default: MATCH_POLICIES.first def applicable?(promotable) - promotable.is_a?(Discountable::Order) + promotable.is_a?(Spree::Order) end def eligible?(order, _options = {}) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb index 7c5a170f88c..1cb7e9fd9da 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user.rb @@ -14,7 +14,7 @@ def preload_relations end def applicable?(promotable) - promotable.is_a?(Discountable::Order) + promotable.is_a?(Spree::Order) end def eligible?(order, _options = {}) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb index a866884b841..5be6e2ea802 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_logged_in.rb @@ -4,7 +4,7 @@ module SolidusFriendlyPromotions module Rules class UserLoggedIn < PromotionRule def applicable?(promotable) - promotable.is_a?(Discountable::Order) + promotable.is_a?(Spree::Order) end def eligible?(order, _options = {}) diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_role.rb b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_role.rb index 08354c58daf..e8df12f71ba 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_role.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/rules/user_role.rb @@ -9,7 +9,7 @@ class UserRole < PromotionRule preference :match_policy, default: MATCH_POLICIES.first def applicable?(promotable) - promotable.is_a?(Discountable::Order) + promotable.is_a?(Spree::Order) end def eligible?(order, _options = {}) diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_shipment_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_shipment_spec.rb index 887349b32f1..eb1709dea0b 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_shipment_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/actions/adjust_shipment_spec.rb @@ -15,19 +15,19 @@ subject { action.can_discount?(promotable) } context "with a line item" do - let(:promotable) { SolidusFriendlyPromotions::Discountable::LineItem.new(Spree::Order.new, order: double) } + let(:promotable) { Spree::Order.new } it { is_expected.to be false } end context "with a shipment" do - let(:promotable) { SolidusFriendlyPromotions::Discountable::Shipment.new(Spree::Shipment.new, order: double) } + let(:promotable) { Spree::Shipment.new } it { is_expected.to be true } end context "with a shipping rate" do - let(:promotable) { SolidusFriendlyPromotions::Discountable::ShippingRate.new(Spree::ShippingRate.new, shipment: double) } + let(:promotable) { Spree::ShippingRate.new } it { is_expected.to be true } end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/distributed_amount_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/distributed_amount_spec.rb index 1f4cc9200b0..65aca311d33 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/distributed_amount_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/distributed_amount_spec.rb @@ -10,8 +10,7 @@ end let(:rules) { [] } let(:action) { SolidusFriendlyPromotions::Actions::AdjustLineItem.create(calculator: calculator) } - let(:spree_order) { create(:order_with_line_items, line_items_attributes: line_items_attributes) } - let(:order) { SolidusFriendlyPromotions::Discountable::Order.new(spree_order) } + let(:order) { create(:order_with_line_items, line_items_attributes: line_items_attributes) } let(:currency) { "USD" } context "applied to an order" do diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/percent_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/percent_spec.rb index fe98125d7dc..025c3578a65 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/percent_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/percent_spec.rb @@ -7,7 +7,7 @@ context "compute" do let(:currency) { "USD" } let(:order) { double(currency: currency) } - let(:line_item) { double("SolidusFriendlyPromotions::Discountable::LineItem", discountable_amount: 100, order: order) } + let(:line_item) { double("Spree::LineItem", discountable_amount: 100, order: order) } before { subject.preferred_percent = 15 } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_flat_rate_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_flat_rate_spec.rb index 490dac04247..ee438e6c34c 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_flat_rate_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/calculators/tiered_flat_rate_spec.rb @@ -75,7 +75,7 @@ line_items_price: amount ) end - let(:line_item) { SolidusFriendlyPromotions::Discountable::LineItem.new(order.line_items.first, order: order) } + let(:line_item) { order.line_items.first } let(:preferred_currency) { "USD" } before do @@ -109,8 +109,7 @@ context "with a shipment" do subject { calculator.compute(shipment) } - let(:spree_shipment) { Spree::Shipment.new(order: order, amount: shipping_cost) } - let(:shipment) { SolidusFriendlyPromotions::Discountable::Shipment.new(spree_shipment, order: order) } + let(:shipment) { Spree::Shipment.new(order: order, amount: shipping_cost) } let(:line_item_count) { 1 } let(:amount) { 10 } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/distributed_amounts_handler_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/distributed_amounts_handler_spec.rb index ee267f901c6..f3d244902c0 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/distributed_amounts_handler_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/distributed_amounts_handler_spec.rb @@ -3,17 +3,13 @@ require "spec_helper" RSpec.describe SolidusFriendlyPromotions::DistributedAmountsHandler, type: :model do - let(:spree_order) do + let(:order) do FactoryBot.create( :order_with_line_items, line_items_attributes: line_items_attributes ) end - let(:order) do - SolidusFriendlyPromotions::Discountable::Order.new(spree_order) - end - let(:handler) { described_class.new(order.line_items, total_amount) } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_action_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_action_spec.rb index 940f806027a..64f039db186 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_action_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_action_spec.rb @@ -22,8 +22,7 @@ let(:variant) { create(:variant) } let(:order) { create(:order) } - let(:discountable) { SolidusFriendlyPromotions::Discountable::LineItem.new(line_item, order: SolidusFriendlyPromotions::Discountable::Order.new(order)) } - let(:line_item) { Spree::LineItem.new(order: order, variant: variant, price: 10) } + let(:discountable) { Spree::LineItem.new(order: order, variant: variant, price: 10) } let(:promotion) { SolidusFriendlyPromotions::Promotion.new(name: "20 Perzent off") } let(:action) { described_class.new(promotion: promotion) } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_repeat_purchase_since_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_repeat_purchase_since_spec.rb index 8edd838e559..e23fd17e3a3 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_repeat_purchase_since_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/first_repeat_purchase_since_spec.rb @@ -7,13 +7,13 @@ subject { described_class.new.applicable?(promotable) } context "when the promotable is an order" do - let(:promotable) { SolidusFriendlyPromotions::Discountable::Order.new(Spree::Order.new) } + let(:promotable) { Spree::Order.new } it { is_expected.to be true } end context "when the promotable is not a order" do - let(:promotable) { SolidusFriendlyPromotions::Discountable::LineItem.new(Spree::LineItem.new, order: double) } + let(:promotable) { Spree::LineItem.new } it { is_expected.to be false } end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/nth_order_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/nth_order_spec.rb index a85d4feccc1..d2464d35286 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/nth_order_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/nth_order_spec.rb @@ -7,7 +7,7 @@ subject { described_class.new.applicable?(promotable) } context "when the promotable is an order" do - let(:promotable) { SolidusFriendlyPromotions::Discountable::Order.new(Spree::Order.new) } + let(:promotable) { Spree::Order.new } it { is_expected.to be true } end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/option_value_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/option_value_spec.rb index 4e29b4c16ce..519309378d5 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/rules/option_value_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/rules/option_value_spec.rb @@ -18,13 +18,13 @@ subject { rule.applicable?(promotable) } context "when promotable is an order" do - let(:promotable) { SolidusFriendlyPromotions::Discountable::Order.new(Spree::Order.new) } + let(:promotable) { Spree::Order.new } it { is_expected.to be true } end context "when promotable is not an order" do - let(:promotable) { SolidusFriendlyPromotions::Discountable::LineItem.new(Spree::LineItem.new, order: double) } + let(:promotable) { Spree::LineItem.new } it { is_expected.to be false } end From 320d8d74cdcb6dae76414b668d9824c0f32bfd9c Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Mon, 2 Oct 2023 20:06:56 +0200 Subject: [PATCH 171/524] Remove Delegators We don't need these delegators, as they are somewhat unwieldy. --- .../discountable/line_item.rb | 23 ---------- .../discountable/order.rb | 19 -------- .../discountable/shipment.rb | 24 ---------- .../discountable/shipping_rate.rb | 23 ---------- .../discountable/line_item_spec.rb | 34 -------------- .../discountable/order_spec.rb | 39 ---------------- .../discountable/shipment_spec.rb | 45 ------------------- .../discountable/shipping_rate_spec.rb | 35 --------------- 8 files changed, 242 deletions(-) delete mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/discountable/line_item.rb delete mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/discountable/order.rb delete mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/discountable/shipment.rb delete mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/discountable/shipping_rate.rb delete mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/discountable/line_item_spec.rb delete mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/discountable/order_spec.rb delete mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/discountable/shipment_spec.rb delete mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/discountable/shipping_rate_spec.rb diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/discountable/line_item.rb b/friendly_promotions/app/models/solidus_friendly_promotions/discountable/line_item.rb deleted file mode 100644 index f732710d534..00000000000 --- a/friendly_promotions/app/models/solidus_friendly_promotions/discountable/line_item.rb +++ /dev/null @@ -1,23 +0,0 @@ -# frozen_string_literal: true - -module SolidusFriendlyPromotions - module Discountable - class LineItem < SimpleDelegator - attr_reader :discounts, :order - - def initialize(line_item, order:) - super(line_item) - @order = order - @discounts = [] - end - - def line_item - __getobj__ - end - - def discountable_amount - amount + discounts.sum(&:amount) - end - end - end -end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/discountable/order.rb b/friendly_promotions/app/models/solidus_friendly_promotions/discountable/order.rb deleted file mode 100644 index ef2bd8c110a..00000000000 --- a/friendly_promotions/app/models/solidus_friendly_promotions/discountable/order.rb +++ /dev/null @@ -1,19 +0,0 @@ -# frozen_string_literal: true - -module SolidusFriendlyPromotions - module Discountable - class Order < SimpleDelegator - attr_reader :line_items, :shipments - - def initialize(order) - super - @line_items = order.line_items.map { |line_item| LineItem.new(line_item, order: self) } - @shipments = order.shipments.map { |shipment| Shipment.new(shipment, order: self) } - end - - def order - __getobj__ - end - end - end -end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/discountable/shipment.rb b/friendly_promotions/app/models/solidus_friendly_promotions/discountable/shipment.rb deleted file mode 100644 index e16c5a96dc7..00000000000 --- a/friendly_promotions/app/models/solidus_friendly_promotions/discountable/shipment.rb +++ /dev/null @@ -1,24 +0,0 @@ -# frozen_string_literal: true - -module SolidusFriendlyPromotions - module Discountable - class Shipment < SimpleDelegator - attr_reader :discounts, :shipping_rates, :order - - def initialize(shipment, order:) - super(shipment) - @order = order - @discounts = [] - @shipping_rates = shipment.shipping_rates.map { |shipping_rate| ShippingRate.new(shipping_rate, shipment: self) } - end - - def shipment - __getobj__ - end - - def discountable_amount - amount + discounts.sum(&:amount) - end - end - end -end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/discountable/shipping_rate.rb b/friendly_promotions/app/models/solidus_friendly_promotions/discountable/shipping_rate.rb deleted file mode 100644 index 77a6ca46ea6..00000000000 --- a/friendly_promotions/app/models/solidus_friendly_promotions/discountable/shipping_rate.rb +++ /dev/null @@ -1,23 +0,0 @@ -# frozen_string_literal: true - -module SolidusFriendlyPromotions - module Discountable - class ShippingRate < SimpleDelegator - attr_reader :discounts, :shipment - - def initialize(shipping_rate, shipment:) - super(shipping_rate) - @shipment = shipment - @discounts = [] - end - - def shipping_rate - __getobj__ - end - - def discountable_amount - amount + discounts.sum(&:amount) - end - end - end -end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/line_item_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/line_item_spec.rb deleted file mode 100644 index c107b8211fa..00000000000 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/line_item_spec.rb +++ /dev/null @@ -1,34 +0,0 @@ -# frozen_string_literal: true - -require "spec_helper" - -RSpec.describe SolidusFriendlyPromotions::Discountable::LineItem do - let(:discountable_order) { double(SolidusFriendlyPromotions::Discountable::Order) } - let(:spree_line_item) { build(:line_item, price: 10, quantity: 2) } - - subject(:discountable_line_item) { described_class.new(spree_line_item, order: discountable_order) } - - describe "#order" do - subject { discountable_line_item.order } - - it { is_expected.to eq(discountable_order) } - end - - describe "#discountable_amount" do - subject(:discountable_amount) { discountable_line_item.discountable_amount } - - context "with no discounts" do - it { is_expected.to eq(20) } - end - - context "with discounts" do - let(:discount) { SolidusFriendlyPromotions::ItemDiscount.new(amount: -4) } - - before do - discountable_line_item.discounts << discount - end - - it { is_expected.to eq(16) } - end - end -end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/order_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/order_spec.rb deleted file mode 100644 index 637b8a050cd..00000000000 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/order_spec.rb +++ /dev/null @@ -1,39 +0,0 @@ -# frozen_string_literal: true - -require "spec_helper" - -RSpec.describe SolidusFriendlyPromotions::Discountable::Order do - subject(:discountable_order) { described_class.new(spree_order) } - - let(:spree_order) { Spree::Order.new } - - describe "#line_items" do - let(:spree_order) { create(:order_with_line_items) } - subject(:line_items) { discountable_order.line_items } - - specify "are converted into Discountable Line Items" do - line_items.each do |line_item| - expect(line_item).to be_a(SolidusFriendlyPromotions::Discountable::LineItem) - end - end - end - - describe "#shipments" do - let(:spree_order) { create(:order_ready_to_ship) } - subject(:shipments) { discountable_order.shipments } - - specify "are converted into Discountable Shipments" do - shipments.each do |shipment| - expect(shipment).to be_a(SolidusFriendlyPromotions::Discountable::Shipment) - end - end - end - - describe "delegation" do - let(:spree_order) { Spree::Order.new(email: "yoda@example.com") } - - it "forwards order attributes" do - expect(subject.email).to eq("yoda@example.com") - end - end -end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/shipment_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/shipment_spec.rb deleted file mode 100644 index 96e58f1fe1a..00000000000 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/shipment_spec.rb +++ /dev/null @@ -1,45 +0,0 @@ -# frozen_string_literal: true - -require "spec_helper" - -RSpec.describe SolidusFriendlyPromotions::Discountable::Shipment do - let(:discountable_order) { double(SolidusFriendlyPromotions::Discountable::Order) } - - let(:spree_shipment) { build(:shipment, amount: 20) } - - subject(:discountable_shipment) { described_class.new(spree_shipment, order: discountable_order) } - - describe "#order" do - subject { discountable_shipment.order } - - it { is_expected.to eq(discountable_order) } - end - - describe "#discountable_amount" do - subject(:discountable_amount) { discountable_shipment.discountable_amount } - - context "with no discounts" do - it { is_expected.to eq(20) } - end - - context "with discounts" do - let(:discount) { SolidusFriendlyPromotions::ItemDiscount.new(amount: -4) } - - before do - discountable_shipment.discounts << discount - end - - it { is_expected.to eq(16) } - end - end - - describe "#shipping_rates" do - subject(:shipping_rates) { discountable_shipment.shipping_rates } - - specify "are converted into Discountable Shipments" do - shipping_rates.each do |shipping_rate| - expect(shipping_rate).to be_a(SolidusFriendlyPromotions::Discountable::ShippingRate) - end - end - end -end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/shipping_rate_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/shipping_rate_spec.rb deleted file mode 100644 index f6c6d7781c3..00000000000 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/discountable/shipping_rate_spec.rb +++ /dev/null @@ -1,35 +0,0 @@ -# frozen_string_literal: true - -require "spec_helper" - -RSpec.describe SolidusFriendlyPromotions::Discountable::ShippingRate do - let(:discountable_shipment) { double(SolidusFriendlyPromotions::Discountable::Shipment) } - - let(:spree_shipping_rate) { build(:shipping_rate, amount: 20) } - - subject(:discountable_shipping_rate) { described_class.new(spree_shipping_rate, shipment: discountable_shipment) } - - describe "#shipment" do - subject { discountable_shipping_rate.shipment } - - it { is_expected.to eq(discountable_shipment) } - end - - describe "#discountable_amount" do - subject(:discountable_amount) { discountable_shipping_rate.discountable_amount } - - context "with no discounts" do - it { is_expected.to eq(20) } - end - - context "with discounts" do - let(:discount) { SolidusFriendlyPromotions::ItemDiscount.new(amount: -4) } - - before do - discountable_shipping_rate.discounts << discount - end - - it { is_expected.to eq(16) } - end - end -end From 4ba8c8d7baa4639fc3e2c3d3852396b73f21dd6f Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 4 Oct 2023 13:48:07 +0200 Subject: [PATCH 172/524] Add dependent: :destroy to Spree::Order#friendly_promotions This is the same change as in https://github.com/solidusio/solidus/pull/5411 --- .../solidus_friendly_promotions/order_decorator.rb | 1 + .../spec/models/spree/order_spec.rb | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_decorator.rb b/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_decorator.rb index a70817c8a4e..f6130deff41 100644 --- a/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_decorator.rb +++ b/friendly_promotions/app/decorators/models/solidus_friendly_promotions/order_decorator.rb @@ -5,6 +5,7 @@ module OrderDecorator def self.prepended(base) base.has_many :friendly_order_promotions, class_name: "SolidusFriendlyPromotions::OrderPromotion", + dependent: :destroy, inverse_of: :order base.has_many :friendly_promotions, through: :friendly_order_promotions, source: :promotion end diff --git a/friendly_promotions/spec/models/spree/order_spec.rb b/friendly_promotions/spec/models/spree/order_spec.rb index 15ee8d76024..6be2cbff349 100644 --- a/friendly_promotions/spec/models/spree/order_spec.rb +++ b/friendly_promotions/spec/models/spree/order_spec.rb @@ -19,4 +19,18 @@ subject end end + + describe "order deletion" do + let(:order) { create(:order) } + let(:promotion) { create(:friendly_promotion) } + + subject { order.destroy } + before do + order.friendly_promotions << promotion + end + + it "deletes join table entries when deleting an order" do + expect { subject }.to change { SolidusFriendlyPromotions::OrderPromotion.count }.from(1).to(0) + end + end end From 3a7955c6e6e804b9cb5d514a84597c6ea7f33f4a Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Tue, 3 Oct 2023 11:39:11 +0200 Subject: [PATCH 173/524] Extract promotion loading from promo discounter --- .../friendly_promotion_discounter.rb | 38 +--------- .../promotion_loader.rb | 50 ++++++++++++++ .../friendly_promotion_discounter_spec.rb | 69 ------------------- .../promotion_loader_spec.rb | 66 ++++++++++++++++++ 4 files changed, 117 insertions(+), 106 deletions(-) create mode 100644 friendly_promotions/app/models/solidus_friendly_promotions/promotion_loader.rb create mode 100644 friendly_promotions/spec/models/solidus_friendly_promotions/promotion_loader_spec.rb diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb b/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb index 9c1d7b4e978..05e4d39776f 100644 --- a/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb +++ b/friendly_promotions/app/models/solidus_friendly_promotions/friendly_promotion_discounter.rb @@ -6,6 +6,7 @@ class FriendlyPromotionDiscounter def initialize(order) @order = order + possible_promotions = PromotionLoader.new(order: order).call @promotions = PromotionEligibility.new(promotable: order, possible_promotions: possible_promotions).call end @@ -55,42 +56,5 @@ def adjust_shipping_rates(item_discounter) [rate, chosen_item_discounts] end end - - def possible_promotions - promos = connected_order_promotions | sale_promotions - promos.flat_map(&:actions).group_by(&:preload_relations).each do |preload_relations, actions| - preload(records: actions, associations: preload_relations) - end - promos.flat_map(&:rules).group_by(&:preload_relations).each do |preload_relations, rules| - preload(records: rules, associations: preload_relations) - end - promos.reject { |promotion| promotion.usage_limit_exceeded?(excluded_orders: [order]) } - end - - def preload(records:, associations:) - ActiveRecord::Associations::Preloader.new(records: records, associations: associations).call - end - - def connected_order_promotions - eligible_connected_promotion_ids = order.friendly_order_promotions.select do |order_promotion| - order_promotion.promotion_code.nil? || !order_promotion.promotion_code.usage_limit_exceeded?(excluded_orders: [order]) - end.map(&:promotion_id) - order.friendly_promotions.active(reference_time).where(id: eligible_connected_promotion_ids).includes(promotion_includes) - end - - def sale_promotions - SolidusFriendlyPromotions::Promotion.where(apply_automatically: true).active(reference_time).includes(promotion_includes) - end - - def reference_time - order.completed_at || Time.current - end - - def promotion_includes - [ - :rules, - :actions - ] - end end end diff --git a/friendly_promotions/app/models/solidus_friendly_promotions/promotion_loader.rb b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_loader.rb new file mode 100644 index 00000000000..6e767f041b8 --- /dev/null +++ b/friendly_promotions/app/models/solidus_friendly_promotions/promotion_loader.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + class PromotionLoader + def initialize(order:) + @order = order + end + + def call + promos = connected_order_promotions | sale_promotions + promos.flat_map(&:actions).group_by(&:preload_relations).each do |preload_relations, actions| + preload(records: actions, associations: preload_relations) + end + promos.flat_map(&:rules).group_by(&:preload_relations).each do |preload_relations, rules| + preload(records: rules, associations: preload_relations) + end + promos.reject { |promotion| promotion.usage_limit_exceeded?(excluded_orders: [order]) } + end + + private + + attr_reader :order + + def preload(records:, associations:) + ActiveRecord::Associations::Preloader.new(records: records, associations: associations).call + end + + def connected_order_promotions + eligible_connected_promotion_ids = order.friendly_order_promotions.select do |order_promotion| + order_promotion.promotion_code.nil? || !order_promotion.promotion_code.usage_limit_exceeded?(excluded_orders: [order]) + end.map(&:promotion_id) + order.friendly_promotions.active(reference_time).where(id: eligible_connected_promotion_ids).includes(promotion_includes) + end + + def sale_promotions + SolidusFriendlyPromotions::Promotion.where(apply_automatically: true).active(reference_time).includes(promotion_includes) + end + + def reference_time + order.completed_at || Time.current + end + + def promotion_includes + [ + :rules, + :actions + ] + end + end +end diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/friendly_promotion_discounter_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/friendly_promotion_discounter_spec.rb index fb97da02a6a..f766f83140b 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/friendly_promotion_discounter_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/friendly_promotion_discounter_spec.rb @@ -3,75 +3,6 @@ require "spec_helper" RSpec.describe SolidusFriendlyPromotions::FriendlyPromotionDiscounter do - describe "selecting promotions" do - subject { described_class.new(order) } - - let(:order) { create(:order) } - - let!(:active_promotion) { create(:friendly_promotion, :with_adjustable_action, apply_automatically: true) } - let!(:inactive_promotion) do - create(:friendly_promotion, :with_adjustable_action, expires_at: 2.days.ago, apply_automatically: true) - end - let!(:connectable_promotion) { create(:friendly_promotion, :with_adjustable_action) } - let!(:connectable_inactive_promotion) do - create(:friendly_promotion, :with_adjustable_action, expires_at: 2.days.ago) - end - - context "no promo is connected to the order" do - it "checks only active promotions" do - expect(SolidusFriendlyPromotions::PromotionEligibility).to receive(:new) - .with(promotable: order, possible_promotions: [active_promotion]) - .and_call_original - subject - end - end - - context "an active promo is connected to the order" do - before do - order.friendly_promotions << connectable_promotion - end - - it "checks active and connected promotions" do - expect(SolidusFriendlyPromotions::PromotionEligibility).to receive(:new) - .with(promotable: order, possible_promotions: array_including(active_promotion, connectable_promotion)) - .and_call_original - subject - end - end - - context "an inactive promo is connected to the order" do - before do - order.friendly_promotions << connectable_inactive_promotion - end - - it "does not check connected inactive promotions" do - expect(SolidusFriendlyPromotions::PromotionEligibility).to receive(:new) - .with(promotable: order, possible_promotions: array_including(active_promotion)) - .and_call_original - subject - end - end - end - - context "promotions in the past" do - let(:order) { create(:order, completed_at: 7.days.ago) } - let(:currently_active_promotion) { create(:friendly_promotion, :with_adjustable_action, starts_at: 1.hour.ago) } - let(:past_promotion) { create(:friendly_promotion, :with_adjustable_action, starts_at: 1.year.ago, expires_at: 11.months.ago) } - let(:order_promotion) { create(:friendly_promotion, :with_adjustable_action, starts_at: 8.days.ago, expires_at: 6.days.ago) } - - before do - order.friendly_promotions << past_promotion - order.friendly_promotions << order_promotion - order.friendly_promotions << currently_active_promotion - end - - subject { described_class.new(order) } - - it "only evaluates the past promotion that was active when the order was completed" do - expect(subject.promotions).to eq([order_promotion]) - end - end - context "shipped orders" do let(:order) { create(:order, shipment_state: "shipped") } diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_loader_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_loader_spec.rb new file mode 100644 index 00000000000..0b0ca6ebee9 --- /dev/null +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_loader_spec.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +require "spec_helper" + +RSpec.describe SolidusFriendlyPromotions::PromotionLoader do + describe "selecting promotions" do + subject { described_class.new(order: order).call } + + let(:order) { create(:order) } + + let!(:active_promotion) { create(:friendly_promotion, :with_adjustable_action, apply_automatically: true) } + let!(:inactive_promotion) do + create(:friendly_promotion, :with_adjustable_action, expires_at: 2.days.ago, apply_automatically: true) + end + let!(:connectable_promotion) { create(:friendly_promotion, :with_adjustable_action) } + let!(:connectable_inactive_promotion) do + create(:friendly_promotion, :with_adjustable_action, expires_at: 2.days.ago) + end + + context "no promo is connected to the order" do + it "returns only active promotions" do + expect(subject).to eq([active_promotion]) + end + end + + context "an active promo is connected to the order" do + before do + order.friendly_promotions << connectable_promotion + end + + it "checks active and connected promotions" do + expect(subject).to include(active_promotion, connectable_promotion) + end + end + + context "an inactive promo is connected to the order" do + before do + order.friendly_promotions << connectable_inactive_promotion + end + + it "does not check connected inactive promotions" do + expect(subject).not_to include(connectable_inactive_promotion) + expect(subject).to eq([active_promotion]) + end + end + end + + context "promotions in the past" do + let(:order) { create(:order, completed_at: 7.days.ago) } + let(:currently_active_promotion) { create(:friendly_promotion, :with_adjustable_action, starts_at: 1.hour.ago) } + let(:past_promotion) { create(:friendly_promotion, :with_adjustable_action, starts_at: 1.year.ago, expires_at: 11.months.ago) } + let(:order_promotion) { create(:friendly_promotion, :with_adjustable_action, starts_at: 8.days.ago, expires_at: 6.days.ago) } + + before do + order.friendly_promotions << past_promotion + order.friendly_promotions << order_promotion + order.friendly_promotions << currently_active_promotion + end + + subject { described_class.new(order: order).call } + + it "only evaluates the past promotion that was active when the order was completed" do + expect(subject).to eq([order_promotion]) + end + end +end From 6b75dcf4e2e41539c42721e9e3156c4053262f86 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 29 Sep 2023 17:53:40 +0200 Subject: [PATCH 174/524] Fix standardrb error in promotion rule spec --- .../promotion_rule_spec.rb | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rule_spec.rb b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rule_spec.rb index 90ff42a9059..e2959e5d2a4 100644 --- a/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rule_spec.rb +++ b/friendly_promotions/spec/models/solidus_friendly_promotions/promotion_rule_spec.rb @@ -3,11 +3,16 @@ require "spec_helper" RSpec.describe SolidusFriendlyPromotions::PromotionRule do - class BadTestRule < SolidusFriendlyPromotions::PromotionRule; end - - class TestRule < SolidusFriendlyPromotions::PromotionRule - def eligible?(_promotable, _options = {}) - true + let(:bad_test_rule_class) { Class.new(SolidusFriendlyPromotions::PromotionRule) } + let(:test_rule_class) do + Class.new(SolidusFriendlyPromotions::PromotionRule) do + def self.model_name + ActiveModel::Name.new(self, nil, "test_rule") + end + + def eligible?(_promotable, _options = {}) + true + end end end @@ -18,21 +23,22 @@ def eligible?(_promotable, _options = {}) end it "forces developer to implement eligible? method" do - expect { BadTestRule.new.eligible?("promotable") }.to raise_error NotImplementedError + expect { bad_test_rule_class.new.eligible?("promotable") }.to raise_error NotImplementedError + expect { test_rule_class.new.eligible?("promotable") }.not_to raise_error NotImplementedError end it "validates unique rules for a promotion" do - promotion_one = TestRule.new + promotion_one = test_rule_class.new promotion_one.promotion_id = 1 promotion_one.save - promotion_two = TestRule.new + promotion_two = test_rule_class.new promotion_two.promotion_id = 1 expect(promotion_two).not_to be_valid end it "generates its own partial path" do - rule = TestRule.new + rule = test_rule_class.new expect(rule.to_partial_path).to eq "solidus_friendly_promotions/admin/promotion_rules/rules/test_rule" end end From 277a552fcbac0a94d02948d4696f888ff756d46a Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 4 Oct 2023 15:41:58 +0200 Subject: [PATCH 175/524] Namespace routes to our controllers For the transition process between the two promotion systems, we want the admins to still have access to the legacy promotions. Prior to this commit, this access was thwarted as we used the exact same paths as core Solidus. This adds an extra `friendly/` to the path, so that the paths can be distinguished. This in turn revealed we have to use the right routing proxy for the promotions controller, otherwise we'd be sent to actions in the Solidus world. --- .../admin/promotions_controller.rb | 4 ++++ friendly_promotions/config/routes.rb | 16 +++++++++------- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb index ef5bb8bbdbf..65957c9cb62 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb @@ -59,6 +59,10 @@ def load_data def location_after_save solidus_friendly_promotions.edit_admin_promotion_url(@promotion) end + + def routes_proxy + solidus_friendly_promotions + end end end end diff --git a/friendly_promotions/config/routes.rb b/friendly_promotions/config/routes.rb index 911a8da73f4..74792e801b2 100644 --- a/friendly_promotions/config/routes.rb +++ b/friendly_promotions/config/routes.rb @@ -2,14 +2,16 @@ SolidusFriendlyPromotions::Engine.routes.draw do namespace :admin do - resources :promotion_categories, except: [:show] + scope :friendly do + resources :promotion_categories, except: [:show] - resources :promotions do - resources :promotion_rules - resources :promotion_actions - resources :promotion_codes, only: [:index, :new, :create] - resources :promotion_code_batches, only: [:index, :new, :create] do - get "/download", to: "promotion_code_batches#download", defaults: {format: "csv"} + resources :promotions do + resources :promotion_rules + resources :promotion_actions + resources :promotion_codes, only: [:index, :new, :create] + resources :promotion_code_batches, only: [:index, :new, :create] do + get "/download", to: "promotion_code_batches#download", defaults: {format: "csv"} + end end end end From 5e61d4653fe5f78d76b77c9a5356ea79f1bef358 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Wed, 4 Oct 2023 15:46:16 +0200 Subject: [PATCH 176/524] Use our own objects and translations, add link to legacy promotions This commit isn't super clear cut but really small: It adds a link to the top of the promotions index to the legacy Solidus promotions overview, and it also switches out some of the uses of the legacy system for our system. --- .../admin/promotions/index.html.erb | 11 +++++++---- friendly_promotions/config/locales/en.yml | 1 + .../admin/promotion_actions_request_spec.rb | 4 ++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/index.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/index.html.erb index 8395681c812..0910ed70b23 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/index.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotions/index.html.erb @@ -1,9 +1,12 @@ -<% admin_breadcrumb(plural_resource_name(Spree::Promotion)) %> +<% admin_breadcrumb(plural_resource_name(SolidusFriendlyPromotions::Promotion)) %> <% content_for :page_actions do %> - <% if can? :create, Spree::Promotion %> + <% if can? :create, SolidusFriendlyPromotions::Promotion %>
  • - <%= link_to t('spree.new_promotion'), spree.new_admin_promotion_path, class: 'btn btn-primary' %> + <%= link_to t('solidus_friendly_promotions.new_promotion'), solidus_friendly_promotions.new_admin_promotion_path, class: 'btn btn-primary' %> +
  • +
  • + <%= link_to t('solidus_friendly_promotions.legacy_promotions'), spree.admin_promotions_path, class: 'btn btn-primary' %>
  • <% end %> <% end %> @@ -118,7 +121,7 @@ <% else %>
    <%= render 'spree/admin/shared/no_objects_found', - resource: Spree::Promotion, + resource: SolidusFriendlyPromotions::Promotion, new_resource_url: new_object_url %>
    <% end %> diff --git a/friendly_promotions/config/locales/en.yml b/friendly_promotions/config/locales/en.yml index 87fef33be7f..8d9070d2f41 100644 --- a/friendly_promotions/config/locales/en.yml +++ b/friendly_promotions/config/locales/en.yml @@ -25,6 +25,7 @@ en: new_promotion: New Promotion new_promotion_category: New Promotion Category new_promotion_code_batch: New Promotion Code Batch + legacy_promotions: Legacy Promotions no_rules_addes: No Rules Added promotion_successfully_created: Promotion has been successfully created! promotion_total_changed_before_complete: One or more of the promotions on your order have become ineligible and were removed. Please check the new order amounts and try again. diff --git a/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_actions_request_spec.rb b/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_actions_request_spec.rb index 09a979c8f0f..1067952c44e 100644 --- a/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_actions_request_spec.rb +++ b/friendly_promotions/spec/requests/solidus_friendly_promotions/admin/promotion_actions_request_spec.rb @@ -15,7 +15,7 @@ } } expect(response).to be_redirect - expect(response).to redirect_to spree.edit_admin_promotion_path(promotion) + expect(response).to redirect_to solidus_friendly_promotions.edit_admin_promotion_path(promotion) expect(promotion.actions.count).to eq(1) end @@ -24,7 +24,7 @@ promotion_action: {type: "Spree::InvalidType"} } expect(response).to be_redirect - expect(response).to redirect_to spree.edit_admin_promotion_path(promotion) + expect(response).to redirect_to solidus_friendly_promotions.edit_admin_promotion_path(promotion) expect(promotion.actions.count).to eq(0) end end From 34ba08c07c32f431caeeb2853080510f24a7cc2a Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 5 Oct 2023 13:20:19 +0200 Subject: [PATCH 177/524] Add custom base controller The controller code we were using relied on a patch to Solidus[1] that will only be released with Solidus 4.2. We want to be compatible with Solidus 4 though, so we're incorporating that patch into our own BaseController and use that instead of Solidus' ResourceController. [1] https://github.com/solidusio/solidus/pull/5219 --- .../admin/base_controller.rb | 54 +++++++++++++++++++ .../admin/promotion_categories_controller.rb | 6 +-- .../promotion_code_batches_controller.rb | 2 +- .../admin/promotion_codes_controller.rb | 2 +- .../admin/promotions_controller.rb | 6 +-- 5 files changed, 58 insertions(+), 12 deletions(-) create mode 100644 friendly_promotions/app/controllers/solidus_friendly_promotions/admin/base_controller.rb diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/base_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/base_controller.rb new file mode 100644 index 00000000000..d87386bb3e6 --- /dev/null +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/base_controller.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +module SolidusFriendlyPromotions + module Admin + class BaseController < Spree::Admin::ResourceController + def parent + @parent ||= self.class.parent_data[:model_class] + .includes(self.class.parent_data[:includes]) + .find_by!(self.class.parent_data[:find_by] => params["#{parent_model_name}_id"]) + instance_variable_set("@#{parent_model_name}", @parent) + rescue ActiveRecord::RecordNotFound => e + resource_not_found(flash_class: e.model.constantize, redirect_url: routes_proxy.polymorphic_url([:admin, parent_model_name.pluralize.to_sym])) + end + + def new_object_url(options = {}) + if parent? + routes_proxy.new_polymorphic_url([:admin, parent, model_class], options) + else + routes_proxy.new_polymorphic_url([:admin, model_class], options) + end + end + + def edit_object_url(object, options = {}) + if parent? + routes_proxy.polymorphic_url([:edit, :admin, parent, object], options) + else + routes_proxy.polymorphic_url([:edit, :admin, object], options) + end + end + + def object_url(object = nil, options = {}) + target = object || @object + + if parent? + routes_proxy.polymorphic_url([:admin, parent, target], options) + else + routes_proxy.polymorphic_url([:admin, target], options) + end + end + + def collection_url(options = {}) + if parent? + routes_proxy.polymorphic_url([:admin, parent, model_class], options) + else + routes_proxy.polymorphic_url([:admin, model_class], options) + end + end + + def routes_proxy + solidus_friendly_promotions + end + end + end +end diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_categories_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_categories_controller.rb index a9861ca3693..53a1e1f35ef 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_categories_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_categories_controller.rb @@ -2,16 +2,12 @@ module SolidusFriendlyPromotions module Admin - class PromotionCategoriesController < Spree::Admin::ResourceController + class PromotionCategoriesController < BaseController private def model_class SolidusFriendlyPromotions::PromotionCategory end - - def routes_proxy - solidus_friendly_promotions - end end end end diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_code_batches_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_code_batches_controller.rb index 24d5be4ddc8..7361d860cca 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_code_batches_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_code_batches_controller.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Admin - class PromotionCodeBatchesController < Spree::Admin::ResourceController + class PromotionCodeBatchesController < BaseController belongs_to "solidus_friendly_promotions/promotion" create.after :build_promotion_code_batch diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_codes_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_codes_controller.rb index 291e3d3b315..d95bc9baeec 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_codes_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotion_codes_controller.rb @@ -4,7 +4,7 @@ module SolidusFriendlyPromotions module Admin - class PromotionCodesController < Spree::Admin::ResourceController + class PromotionCodesController < BaseController before_action :load_promotion def index diff --git a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb index 65957c9cb62..bd838a23821 100644 --- a/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb +++ b/friendly_promotions/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb @@ -2,7 +2,7 @@ module SolidusFriendlyPromotions module Admin - class PromotionsController < ::Spree::Admin::ResourceController + class PromotionsController < BaseController before_action :load_data helper "solidus_friendly_promotions/admin/promotion_rules" @@ -59,10 +59,6 @@ def load_data def location_after_save solidus_friendly_promotions.edit_admin_promotion_url(@promotion) end - - def routes_proxy - solidus_friendly_promotions - end end end end From 6d7e864216eae4f5e2fbb562ce5388a83738c129 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Fri, 6 Oct 2023 11:43:24 +0200 Subject: [PATCH 178/524] Change references to legacy promotion system in views There were many references to the promotion system in solidus core in our views folder still. This commit changes them. --- .../admin/promotion_categories/edit.html.erb | 4 ++-- .../admin/promotion_categories/new.html.erb | 4 ++-- .../admin/promotion_codes/index.html.erb | 14 +++++++------- .../admin/promotion_codes/new.html.erb | 4 ++-- .../admin/promotion_rules/_promotion_rule.html.erb | 2 +- .../admin/promotion_rules/new.html.erb | 2 +- .../rules/_first_repeat_purchase_since.html.erb | 2 +- .../promotion_rules/rules/_nth_order.html.erb | 2 +- .../admin/promotion_rules/rules/_product.html.erb | 2 +- .../admin/promotion_rules/rules/_taxon.html.erb | 2 +- .../promotion_rules/rules/_user_role.html.erb | 2 +- .../admin/promotions/_activations_new.html.erb | 2 +- .../admin/promotions/edit.html.erb | 8 ++++---- .../admin/promotions/index.html.erb | 14 +++++++------- .../admin/promotions/new.html.erb | 2 +- 15 files changed, 33 insertions(+), 33 deletions(-) diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_categories/edit.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_categories/edit.html.erb index f6b3136f964..e5112ac90b2 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_categories/edit.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_categories/edit.html.erb @@ -1,5 +1,5 @@ -<% admin_breadcrumb(link_to plural_resource_name(Spree::Promotion), solidus_friendly_promotions.admin_promotions_path) %> -<% admin_breadcrumb(link_to plural_resource_name(Spree::PromotionCategory), solidus_friendly_promotions.admin_promotion_categories_path) %> +<% admin_breadcrumb(link_to plural_resource_name(SolidusFriendlyPromotions::Promotion), solidus_friendly_promotions.admin_promotions_path) %> +<% admin_breadcrumb(link_to plural_resource_name(SolidusFriendlyPromotions::PromotionCategory), solidus_friendly_promotions.admin_promotion_categories_path) %> <% admin_breadcrumb(@promotion_category.name) %> <%= form_for @promotion_category, url: object_url, method: :put do |f| %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_categories/new.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_categories/new.html.erb index 7b557fc61c2..f0bdf88de64 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_categories/new.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_categories/new.html.erb @@ -1,5 +1,5 @@ -<% admin_breadcrumb(link_to plural_resource_name(Spree::Promotion), solidus_friendly_promotions.admin_promotions_path) %> -<% admin_breadcrumb(link_to plural_resource_name(Spree::PromotionCategory), solidus_friendly_promotions.admin_promotion_categories_path) %> +<% admin_breadcrumb(link_to plural_resource_name(SolidusFriendlyPromotions::Promotion), solidus_friendly_promotions.admin_promotions_path) %> +<% admin_breadcrumb(link_to plural_resource_name(SolidusFriendlyPromotions::PromotionCategory), solidus_friendly_promotions.admin_promotion_categories_path) %> <% admin_breadcrumb(t('spree.new_promotion_category')) %> <%= form_for :promotion_category, url: collection_url do |f| %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_codes/index.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_codes/index.html.erb index 75e00969544..300ef013e2d 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_codes/index.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_codes/index.html.erb @@ -1,14 +1,14 @@ -<% admin_breadcrumb link_to plural_resource_name(Spree::Promotion), spree.admin_promotions_path %> -<% admin_breadcrumb link_to(@promotion.name, spree.edit_admin_promotion_path(@promotion)) %> -<% admin_breadcrumb plural_resource_name(Spree::PromotionCode) %> +<% admin_breadcrumb link_to plural_resource_name(SolidusFriendlyPromotions::Promotion), solidus_friendly_promotions.admin_promotions_path %> +<% admin_breadcrumb link_to(@promotion.name, solidus_friendly_promotions.edit_admin_promotion_path(@promotion)) %> +<% admin_breadcrumb plural_resource_name(SolidusFriendlyPromotions::PromotionCode) %> <% content_for :page_actions do %>
  • - <% if can?(:create, Spree::PromotionCode) && !@promotion.apply_automatically? %> - <%= link_to t('spree.create_promotion_code'), new_admin_promotion_promotion_code_path(promotion_id: @promotion.id), class: 'btn btn-primary' %> + <% if can?(:create, SolidusFriendlyPromotions::PromotionCode) && !@promotion.apply_automatically? %> + <%= link_to t('spree.create_promotion_code'), solidus_friendly_promotions.new_admin_promotion_promotion_code_path(promotion_id: @promotion.id), class: 'btn btn-primary' %> <% end %> - <%= link_to t('spree.download_promotion_codes_list'), admin_promotion_promotion_codes_path(promotion_id: @promotion.id, format: :csv), class: 'btn btn-primary' %> + <%= link_to t('spree.download_promotion_codes_list'), solidus_friendly_promotions.admin_promotion_promotion_codes_path(promotion_id: @promotion.id, format: :csv), class: 'btn btn-primary' %>
  • <% end %> @@ -18,7 +18,7 @@ - + <% @promotion_codes.each do |promotion_code| %> diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_codes/new.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_codes/new.html.erb index 894df09e29e..5b82b40d333 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_codes/new.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_codes/new.html.erb @@ -1,6 +1,6 @@ -<% admin_breadcrumb link_to plural_resource_name(Spree::Promotion), solidus_friendly_promotions.admin_promotions_path %> +<% admin_breadcrumb link_to plural_resource_name(SolidusFriendlyPromotions::Promotion), solidus_friendly_promotions.admin_promotions_path %> <% admin_breadcrumb link_to(@promotion.name, solidus_friendly_promotions.edit_admin_promotion_path(@promotion)) %> -<% admin_breadcrumb plural_resource_name(Spree::PromotionCode) %> +<% admin_breadcrumb plural_resource_name(SolidusFriendlyPromotions::PromotionCode) %> <% content_for :page_actions do %>
  • diff --git a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/_promotion_rule.html.erb b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/_promotion_rule.html.erb index e1c55fcec50..493d6ddd582 100644 --- a/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/_promotion_rule.html.erb +++ b/friendly_promotions/app/views/solidus_friendly_promotions/admin/promotion_rules/_promotion_rule.html.erb @@ -3,7 +3,7 @@ <%= form_with model: promotion_rule, scope: :promotion_rule, url: solidus_friendly_promotions.admin_promotion_promotion_rule_path(@promotion, promotion_rule), method: :patch do |form| %>
    <%= promotion_rule.class.model_name.human %>
    <% if can?(:destroy, promotion_rule) %> - <%= link_to_with_icon 'trash', '', spree.admin_promotion_promotion_rule_path(@promotion, promotion_rule), method: :delete, class: 'delete' %> + <%= link_to_with_icon 'trash', '', solidus_friendly_promotions.admin_promotion_promotion_rule_path(@promotion, promotion_rule), method: :delete, class: 'delete' %> <% end %>

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

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