solidus_friendly_promotions 1.0.0.rc.3 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +7 -7
  3. data/CHANGELOG.md +66 -8
  4. data/Gemfile +1 -6
  5. data/app/controllers/solidus_friendly_promotions/admin/base_controller.rb +4 -0
  6. data/app/controllers/solidus_friendly_promotions/admin/promotion_actions_controller.rb +1 -1
  7. data/app/controllers/solidus_friendly_promotions/admin/promotion_code_batches_controller.rb +12 -0
  8. data/app/controllers/solidus_friendly_promotions/admin/promotion_rules_controller.rb +1 -1
  9. data/app/controllers/solidus_friendly_promotions/admin/promotions_controller.rb +1 -0
  10. data/app/helpers/solidus_friendly_promotions/admin/promotions_helper.rb +15 -0
  11. data/app/models/solidus_friendly_promotions/promotion.rb +17 -2
  12. data/app/models/solidus_friendly_promotions/promotion_action.rb +12 -1
  13. data/app/models/solidus_friendly_promotions/promotion_code.rb +2 -0
  14. data/app/models/solidus_friendly_promotions/promotion_rule.rb +1 -1
  15. data/app/models/solidus_friendly_promotions/shipping_rate_discount.rb +1 -1
  16. data/app/views/solidus_friendly_promotions/admin/promotion_code_batches/_form_fields.html.erb +22 -0
  17. data/app/views/solidus_friendly_promotions/admin/promotion_code_batches/download.csv.ruby +8 -0
  18. data/app/views/solidus_friendly_promotions/admin/promotion_code_batches/index.html.erb +65 -0
  19. data/app/views/solidus_friendly_promotions/admin/promotion_code_batches/new.html.erb +8 -0
  20. data/app/views/solidus_friendly_promotions/admin/promotions/_form.html.erb +33 -5
  21. data/app/views/solidus_friendly_promotions/admin/promotions/_table.html.erb +3 -3
  22. data/app/views/solidus_friendly_promotions/admin/promotions/_table_filter.html.erb +9 -0
  23. data/bin/sandbox +1 -0
  24. data/config/locales/en.yml +27 -3
  25. data/db/migrate/20240124104855_add_deleted_at_to_promotions.rb +6 -0
  26. data/db/migrate/20240125102050_drop_deleted_at_from_promotion_actions.rb +5 -0
  27. data/lib/solidus_friendly_promotions/version.rb +1 -1
  28. data/lib/solidus_friendly_promotions.rb +9 -4
  29. data/solidus_friendly_promotions.gemspec +2 -2
  30. metadata +13 -10
  31. data/app/models/solidus_friendly_promotions.rb +0 -7
  32. data/app/views/solidus_friendly_promotions/admin/promotions/_activations_edit.html.erb +0 -22
  33. data/app/views/solidus_friendly_promotions/admin/promotions/_activations_new.html.erb +0 -43
  34. data/config/initializers/solidus_friendly_promotions.rb +0 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bb47848b0ee568a1116d827ac87f0071afd5790eff84d501860c0ed68eb37ac1
4
- data.tar.gz: fd5c9d4ac486641ab72f87129cfbf7a7a44ea5b3c1a18970c479794f410dcd9f
3
+ metadata.gz: a6735f8983491ab3b703bc8c095f8331efb28e58b9a59fde2e74a338dc60c03f
4
+ data.tar.gz: 4f4ecb6904c2aba33880b6601bd3a1efb3d72c283f6c1a6aab3167bc2739767d
5
5
  SHA512:
6
- metadata.gz: 71269bc48e02192efa3d41c83fe461bed7b3b0c8b1d123cfa555671ee5baea159f7e49521d203124ec1c23e2bedecc641e2a6b44ff5b18ad013438c2255fee50
7
- data.tar.gz: 91b12062bff9c7324c9efdce8398ce6eac5ffd50b353848b1249c9a4146411b137c8354fee594861d03f490db9e4a05d0556f5bade2160596d04c56a7e3c452b
6
+ metadata.gz: b6a4ae6ae4f22709c40865d60e125f07ac55bd58cf944415f41c6431d8260da20d16f4b33b78d2a0219366c58d13441d5701bc935d09760751a413fceed487e2
7
+ data.tar.gz: 640c4da14f4f8d70fb7f8d9fce147f0344a4812384f44a5da379e61c55a5cafacb0b8ff161f752930523c0ad198dadfd1fd047c577976f1818e28938b5631277
data/.circleci/config.yml CHANGED
@@ -6,7 +6,7 @@ jobs:
6
6
  lint:
7
7
  executor:
8
8
  name: solidusio_extensions/sqlite-memory
9
- ruby_version: '3.0'
9
+ ruby_version: '3.1'
10
10
  steps:
11
11
  - checkout
12
12
  - solidusio_extensions/test-branch:
@@ -15,33 +15,33 @@ jobs:
15
15
  run-specs-with-mysql:
16
16
  executor:
17
17
  name: solidusio_extensions/mysql
18
- ruby_version: '3.1'
18
+ ruby_version: '3.2'
19
19
  steps:
20
20
  - browser-tools/install-chrome
21
21
  - checkout
22
22
  - solidusio_extensions/dependencies
23
23
  - solidusio_extensions/run-tests-solidus-current
24
- # - solidusio_extensions/run-tests-solidus-main
24
+ - solidusio_extensions/run-tests-solidus-main
25
25
  run-specs-with-postgres:
26
26
  executor:
27
27
  name: solidusio_extensions/postgres
28
- ruby_version: '3.2'
28
+ ruby_version: '3.3'
29
29
  steps:
30
30
  - browser-tools/install-chrome
31
31
  - checkout
32
32
  - solidusio_extensions/dependencies
33
33
  - solidusio_extensions/run-tests-solidus-current
34
- # - solidusio_extensions/run-tests-solidus-main
34
+ - solidusio_extensions/run-tests-solidus-main
35
35
  run-specs-with-sqlite:
36
36
  executor:
37
37
  name: solidusio_extensions/sqlite
38
- ruby_version: '3.0'
38
+ ruby_version: '3.1'
39
39
  steps:
40
40
  - browser-tools/install-chrome
41
41
  - checkout
42
42
  - solidusio_extensions/dependencies
43
43
  - solidusio_extensions/run-tests-solidus-current
44
- # - solidusio_extensions/run-tests-solidus-main
44
+ - solidusio_extensions/run-tests-solidus-main
45
45
  workflows:
46
46
  Run specs on supported Solidus versions:
47
47
  jobs:
data/CHANGELOG.md CHANGED
@@ -1,8 +1,49 @@
1
1
  # Changelog
2
2
 
3
- ## [Unreleased](https://github.com/FriendlyCart/solidus_friendly_promotions/tree/HEAD)
3
+ ## [v1.0.1](https://github.com/friendlycart/solidus_friendly_promotions/tree/v1.0.1) (2024-05-07)
4
4
 
5
- ## [v1.0.0.rc.2](https://github.com/FriendlyCart/solidus_friendly_promotions/tree/v1.0.0.rc.3) (2024-01-16)
5
+ [Full Changelog](https://github.com/friendlycart/solidus_friendly_promotions/compare/v1.0.0...v1.0.1)
6
+
7
+ **Closed issues:**
8
+
9
+ - Ransack scope missing [\#105](https://github.com/friendlycart/solidus_friendly_promotions/issues/105)
10
+
11
+ **Merged pull requests:**
12
+
13
+ - Fix selenium path [\#108](https://github.com/friendlycart/solidus_friendly_promotions/pull/108) ([mamhoff](https://github.com/mamhoff))
14
+ - Restrict SQLite to "~\> 1.3" [\#107](https://github.com/friendlycart/solidus_friendly_promotions/pull/107) ([mamhoff](https://github.com/mamhoff))
15
+ - Fix ShippingRateDiscount\#promotion\_action [\#106](https://github.com/friendlycart/solidus_friendly_promotions/pull/106) ([mamhoff](https://github.com/mamhoff))
16
+ - Fix specs [\#104](https://github.com/friendlycart/solidus_friendly_promotions/pull/104) ([mamhoff](https://github.com/mamhoff))
17
+ - Fix Promotion Rule Unique per promotion validation [\#103](https://github.com/friendlycart/solidus_friendly_promotions/pull/103) ([mamhoff](https://github.com/mamhoff))
18
+
19
+ ## [v1.0.0](https://github.com/friendlycart/solidus_friendly_promotions/tree/v1.0.0) (2024-01-25)
20
+
21
+ [Full Changelog](https://github.com/friendlycart/solidus_friendly_promotions/compare/v1.0.0.rc.3...v1.0.0)
22
+
23
+ **Implemented enhancements:**
24
+
25
+ - Implement better soft-delete [\#30](https://github.com/friendlycart/solidus_friendly_promotions/issues/30)
26
+
27
+ **Closed issues:**
28
+
29
+ - Cannot delete promotion with promotion code [\#94](https://github.com/friendlycart/solidus_friendly_promotions/issues/94)
30
+ - Missing action for button: Promotion code batch [\#93](https://github.com/friendlycart/solidus_friendly_promotions/issues/93)
31
+
32
+ **Merged pull requests:**
33
+
34
+ - Improve soft-deletion faculty [\#102](https://github.com/friendlycart/solidus_friendly_promotions/pull/102) ([mamhoff](https://github.com/mamhoff))
35
+ - Add missing association between promo codes and order promotions [\#101](https://github.com/friendlycart/solidus_friendly_promotions/pull/101) ([mamhoff](https://github.com/mamhoff))
36
+ - Revert "Temporarily disable running specs for Solidus main" [\#100](https://github.com/friendlycart/solidus_friendly_promotions/pull/100) ([mamhoff](https://github.com/mamhoff))
37
+ - Add admin UI for promotion code batches [\#99](https://github.com/friendlycart/solidus_friendly_promotions/pull/99) ([mamhoff](https://github.com/mamhoff))
38
+ - Define SolidusFriendlyPromotions.table\_name\_prefix early [\#98](https://github.com/friendlycart/solidus_friendly_promotions/pull/98) ([mamhoff](https://github.com/mamhoff))
39
+
40
+ ## [v1.0.0.rc.3](https://github.com/friendlycart/solidus_friendly_promotions/tree/v1.0.0.rc.3) (2024-01-16)
41
+
42
+ [Full Changelog](https://github.com/friendlycart/solidus_friendly_promotions/compare/v1.0.0.rc.2...v1.0.0.rc.3)
43
+
44
+ **Closed issues:**
45
+
46
+ - Provide Integration Instructions for Starter Frontend [\#90](https://github.com/friendlycart/solidus_friendly_promotions/issues/90)
6
47
 
7
48
  **Merged pull requests:**
8
49
 
@@ -13,9 +54,9 @@
13
54
  - Fix typo [\#89](https://github.com/friendlycart/solidus_friendly_promotions/pull/89) ([jarednorman](https://github.com/jarednorman))
14
55
  - Pass untranslated strings through i18n [\#88](https://github.com/friendlycart/solidus_friendly_promotions/pull/88) ([mamhoff](https://github.com/mamhoff))
15
56
 
16
- ## [v1.0.0.rc.2](https://github.com/FriendlyCart/solidus_friendly_promotions/tree/v1.0.0.rc.2) (2023-11-11)
57
+ ## [v1.0.0.rc.2](https://github.com/friendlycart/solidus_friendly_promotions/tree/v1.0.0.rc.2) (2023-11-11)
17
58
 
18
- [Full Changelog](https://github.com/FriendlyCart/solidus_friendly_promotions/compare/v1.0.0.rc.1...v1.0.0.rc.2)
59
+ [Full Changelog](https://github.com/friendlycart/solidus_friendly_promotions/compare/v1.0.0.rc.1...v1.0.0.rc.2)
19
60
 
20
61
  **Merged pull requests:**
21
62
 
@@ -28,18 +69,35 @@
28
69
  - Promotions index improvements [\#79](https://github.com/friendlycart/solidus_friendly_promotions/pull/79) ([mamhoff](https://github.com/mamhoff))
29
70
  - Lint: Fix standardrb error [\#78](https://github.com/friendlycart/solidus_friendly_promotions/pull/78) ([mamhoff](https://github.com/mamhoff))
30
71
 
31
- ## [v1.0.0.rc.1](https://github.com/FriendlyCart/solidus_friendly_promotions/tree/v1.0.0.rc.1) (2023-11-07)
72
+ ## [v1.0.0.rc.1](https://github.com/friendlycart/solidus_friendly_promotions/tree/v1.0.0.rc.1) (2023-11-07)
73
+
74
+ [Full Changelog](https://github.com/friendlycart/solidus_friendly_promotions/compare/v1.0.0.pre...v1.0.0.rc.1)
75
+
76
+ **Implemented enhancements:**
32
77
 
33
- [Full Changelog](https://github.com/FriendlyCart/solidus_friendly_promotions/compare/v1.0.0.pre...v1.0.0.rc.1)
78
+ - Implement Free items [\#31](https://github.com/friendlycart/solidus_friendly_promotions/issues/31)
34
79
 
35
80
  **Merged pull requests:**
36
81
 
37
82
  - Goodies! [\#75](https://github.com/friendlycart/solidus_friendly_promotions/pull/75) ([mamhoff](https://github.com/mamhoff))
38
83
  - Add a null promotion handler [\#71](https://github.com/friendlycart/solidus_friendly_promotions/pull/71) ([mamhoff](https://github.com/mamhoff))
39
84
 
40
- ## [v1.0.0.pre](https://github.com/FriendlyCart/solidus_friendly_promotions/tree/v1.0.0.pre) (2023-11-07)
85
+ ## [v1.0.0.pre](https://github.com/friendlycart/solidus_friendly_promotions/tree/v1.0.0.pre) (2023-11-07)
86
+
87
+ [Full Changelog](https://github.com/friendlycart/solidus_friendly_promotions/compare/e14802957fdb55d7f4e2730341e4cbb118ebf993...v1.0.0.pre)
88
+
89
+ **Implemented enhancements:**
90
+
91
+ - Migrator: Copy promotion codes [\#34](https://github.com/friendlycart/solidus_friendly_promotions/issues/34)
92
+ - Add original\_promotion\_{action\_}id on promotions and promotion actions [\#33](https://github.com/friendlycart/solidus_friendly_promotions/issues/33)
93
+
94
+ **Fixed bugs:**
95
+
96
+ - Migrator: Add support for promotion categories [\#37](https://github.com/friendlycart/solidus_friendly_promotions/issues/37)
97
+
98
+ **Closed issues:**
41
99
 
42
- [Full Changelog](https://github.com/FriendlyCart/solidus_friendly_promotions/compare/e14802957fdb55d7f4e2730341e4cbb118ebf993...v1.0.0.pre)
100
+ - Document DB tables [\#35](https://github.com/friendlycart/solidus_friendly_promotions/issues/35)
43
101
 
44
102
  **Merged pull requests:**
45
103
 
data/Gemfile CHANGED
@@ -24,14 +24,9 @@ when "mysql"
24
24
  when "postgresql"
25
25
  gem "pg"
26
26
  else
27
- gem "sqlite3"
27
+ gem "sqlite3", "~> 1.3"
28
28
  end
29
29
 
30
- # While we still support Ruby < 3 we need to workaround a limitation in
31
- # the 'async' gem that relies on the latest ruby, since RubyGems doesn't
32
- # resolve gems based on the required ruby version.
33
- gem "async", "< 3" if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("3")
34
-
35
30
  gemspec
36
31
 
37
32
  # Use a local Gemfile to include development dependencies that might not be
@@ -49,6 +49,10 @@ module SolidusFriendlyPromotions
49
49
  def routes_proxy
50
50
  solidus_friendly_promotions
51
51
  end
52
+
53
+ def parent_model_name
54
+ self.class.parent_data[:model_name].gsub("solidus_friendly_promotions/", "")
55
+ end
52
56
  end
53
57
  end
54
58
  end
@@ -53,7 +53,7 @@ module SolidusFriendlyPromotions
53
53
 
54
54
  def destroy
55
55
  @promotion_action = @promotion.actions.find(params[:id])
56
- if @promotion_action.discard
56
+ if @promotion_action.destroy
57
57
  flash[:success] =
58
58
  t("spree.successfully_removed", resource: SolidusFriendlyPromotions::PromotionAction.model_name.human)
59
59
  end
@@ -25,6 +25,18 @@ module SolidusFriendlyPromotions
25
25
  def build_promotion_code_batch
26
26
  @promotion_code_batch.process
27
27
  end
28
+
29
+ def model_class
30
+ SolidusFriendlyPromotions::PromotionCodeBatch
31
+ end
32
+
33
+ def collection
34
+ parent.code_batches
35
+ end
36
+
37
+ def build_resource
38
+ parent.code_batches.build
39
+ end
28
40
  end
29
41
  end
30
42
  end
@@ -3,7 +3,7 @@
3
3
  module SolidusFriendlyPromotions
4
4
  module Admin
5
5
  class PromotionRulesController < Spree::Admin::BaseController
6
- helper "spree/promotion_rules"
6
+ helper "solidus_friendly_promotions/admin/promotion_rules"
7
7
 
8
8
  before_action :validate_level, only: [:new, :create]
9
9
  before_action :load_promotion, only: [:create, :destroy, :update, :new]
@@ -7,6 +7,7 @@ module SolidusFriendlyPromotions
7
7
 
8
8
  helper "solidus_friendly_promotions/admin/promotion_rules"
9
9
  helper "solidus_friendly_promotions/admin/promotion_actions"
10
+ helper "solidus_friendly_promotions/admin/promotions"
10
11
 
11
12
  def create
12
13
  @promotion = model_class.new(permitted_resource_params)
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusFriendlyPromotions
4
+ module Admin
5
+ module PromotionsHelper
6
+ def admin_promotion_status(promotion)
7
+ return :active if promotion.active?
8
+ return :not_started if promotion.not_started?
9
+ return :expired if promotion.expired?
10
+
11
+ :inactive
12
+ end
13
+ end
14
+ end
15
+ end
@@ -2,11 +2,13 @@
2
2
 
3
3
  module SolidusFriendlyPromotions
4
4
  class Promotion < Spree::Base
5
+ include Spree::SoftDeletable
6
+
5
7
  belongs_to :category, class_name: "SolidusFriendlyPromotions::PromotionCategory",
6
8
  foreign_key: :promotion_category_id, optional: true
7
9
  belongs_to :original_promotion, class_name: "Spree::Promotion", optional: true
8
10
  has_many :rules, class_name: "SolidusFriendlyPromotions::PromotionRule", dependent: :destroy
9
- has_many :actions, class_name: "SolidusFriendlyPromotions::PromotionAction", dependent: :nullify
11
+ has_many :actions, class_name: "SolidusFriendlyPromotions::PromotionAction", dependent: :destroy
10
12
  has_many :codes, class_name: "SolidusFriendlyPromotions::PromotionCode", dependent: :destroy
11
13
  has_many :code_batches, class_name: "SolidusFriendlyPromotions::PromotionCodeBatch", dependent: :destroy
12
14
  has_many :order_promotions, class_name: "SolidusFriendlyPromotions::OrderPromotion", dependent: :destroy
@@ -17,6 +19,9 @@ module SolidusFriendlyPromotions
17
19
  validates :per_code_usage_limit, numericality: {greater_than_or_equal_to: 0, allow_nil: true}
18
20
  validates :description, length: {maximum: 255}
19
21
  validate :apply_automatically_disallowed_with_paths
22
+ validate :apply_automatically_disallowed_with_promotion_codes
23
+
24
+ before_save :normalize_blank_values
20
25
 
21
26
  scope :active, ->(time = Time.current) { has_actions.started_and_unexpired(time) }
22
27
  scope :advertised, -> { where(advertise: true) }
@@ -49,7 +54,7 @@ module SolidusFriendlyPromotions
49
54
 
50
55
  self.allowed_ransackable_associations = ["codes"]
51
56
  self.allowed_ransackable_attributes = %w[name customer_label path promotion_category_id lane updated_at]
52
- self.allowed_ransackable_scopes = %i[active]
57
+ self.allowed_ransackable_scopes = %i[active with_discarded]
53
58
 
54
59
  # All orders that have been discounted using this promotion
55
60
  def discounted_orders
@@ -163,10 +168,20 @@ module SolidusFriendlyPromotions
163
168
 
164
169
  private
165
170
 
171
+ def normalize_blank_values
172
+ self[:path] = nil if self[:path].blank?
173
+ end
174
+
166
175
  def apply_automatically_disallowed_with_paths
167
176
  return unless apply_automatically
168
177
 
169
178
  errors.add(:apply_automatically, :disallowed_with_path) if path.present?
170
179
  end
180
+
181
+ def apply_automatically_disallowed_with_promotion_codes
182
+ return unless apply_automatically
183
+
184
+ errors.add(:apply_automatically, :disallowed_with_promotion_codes) if codes.present?
185
+ end
171
186
  end
172
187
  end
@@ -9,13 +9,15 @@ module SolidusFriendlyPromotions
9
9
  # by an event and determined to be eligible.
10
10
  class PromotionAction < Spree::Base
11
11
  include Spree::Preferences::Persistable
12
- include Spree::SoftDeletable
13
12
  include Spree::CalculatedAdjustments
14
13
  include Spree::AdjustmentSource
14
+ before_destroy :remove_adjustments_from_incomplete_orders
15
+ before_destroy :raise_for_adjustments_for_completed_orders
15
16
 
16
17
  belongs_to :promotion, inverse_of: :actions
17
18
  belongs_to :original_promotion_action, class_name: "Spree::PromotionAction", optional: true
18
19
  has_many :adjustments, class_name: "Spree::Adjustment", as: :source
20
+ has_many :shipping_rate_discounts, class_name: "SolidusFriendlyPromotions::ShippingRateDiscount", inverse_of: :promotion_action
19
21
 
20
22
  scope :of_type, ->(type) { where(type: Array.wrap(type).map(&:to_s)) }
21
23
 
@@ -69,5 +71,14 @@ module SolidusFriendlyPromotions
69
71
  def available_calculators
70
72
  SolidusFriendlyPromotions.config.promotion_calculators[self.class] || (raise NotImplementedError)
71
73
  end
74
+
75
+ private
76
+
77
+ def raise_for_adjustments_for_completed_orders
78
+ if adjustments.joins(:order).merge(Spree::Order.complete).any?
79
+ errors.add(:base, :cannot_destroy_if_order_completed)
80
+ throw(:abort)
81
+ end
82
+ end
72
83
  end
73
84
  end
@@ -4,6 +4,8 @@ module SolidusFriendlyPromotions
4
4
  class PromotionCode < Spree::Base
5
5
  belongs_to :promotion, inverse_of: :codes
6
6
  belongs_to :promotion_code_batch, inverse_of: :promotion_codes, optional: true
7
+
8
+ has_many :order_promotions, class_name: "SolidusFriendlyPromotions::OrderPromotion", dependent: :destroy
7
9
  has_many :adjustments, class_name: "Spree::Adjustment"
8
10
 
9
11
  before_validation :normalize_code
@@ -45,7 +45,7 @@ module SolidusFriendlyPromotions
45
45
  def unique_per_promotion
46
46
  return unless self.class.exists?(promotion_id: promotion_id, type: self.class.name)
47
47
 
48
- errors[:base] << "Promotion already contains this rule type"
48
+ errors.add(:promotion, :already_contains_rule_type)
49
49
  end
50
50
 
51
51
  def eligibility_error_message(key, options = {})
@@ -3,7 +3,7 @@
3
3
  module SolidusFriendlyPromotions
4
4
  class ShippingRateDiscount < Spree::Base
5
5
  belongs_to :shipping_rate, inverse_of: :discounts, class_name: "Spree::ShippingRate"
6
- belongs_to :promotion_action, -> { with_discarded }, inverse_of: false
6
+ belongs_to :promotion_action, inverse_of: :shipping_rate_discounts
7
7
 
8
8
  extend Spree::DisplayMoney
9
9
  money_methods :amount
@@ -0,0 +1,22 @@
1
+ <div class="field">
2
+ <%= batch.label :base_code, class: "required" %>
3
+ <%= batch.text_field :base_code, class: "fullwidth", required: true %>
4
+ </div>
5
+ <div class="field">
6
+ <%= batch.label :number_of_codes, class: "required" %>
7
+ <%= batch.number_field :number_of_codes, class: "fullwidth", min: 1, required: true %>
8
+ </div>
9
+ <div class="field">
10
+ <%= batch.label :join_characters %>
11
+ <%= batch.text_field :join_characters, class: "fullwidth" %>
12
+ </div>
13
+ <% unless promotion_id %>
14
+ <div class="field">
15
+ <%= f.label :per_code_usage_limit %>
16
+ <%= f.text_field :per_code_usage_limit, class: "fullwidth" %>
17
+ </div>
18
+ <% end %>
19
+ <div class="field">
20
+ <%= batch.label :email %>
21
+ <%= batch.text_field :email, class: "fullwidth" %>
22
+ </div>
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ CSV.generate do |csv|
4
+ csv << ["Code"]
5
+ @promotion_code_batch.promotion_codes.order(:id).pluck(:value).each do |value|
6
+ csv << [value]
7
+ end
8
+ end
@@ -0,0 +1,65 @@
1
+ <% admin_breadcrumb(link_to plural_resource_name(SolidusFriendlyPromotions::Promotion), solidus_friendly_promotions.admin_promotions_path) %>
2
+ <% admin_breadcrumb(link_to @promotion.name, solidus_friendly_promotions.edit_admin_promotion_path(@promotion.id)) %>
3
+ <% admin_breadcrumb(plural_resource_name(SolidusFriendlyPromotions::PromotionCodeBatch)) %>
4
+
5
+ <% content_for :page_actions do %>
6
+ <li>
7
+ <% if can?(:create, SolidusFriendlyPromotions::PromotionCodeBatch) %>
8
+ <%= link_to t('solidus_friendly_promotions.new_promotion_code_batch'), new_object_url, class: 'btn btn-primary' %>
9
+ <% end %>
10
+ </li>
11
+ <% end %>
12
+
13
+ <% if @promotion_code_batches.any? %>
14
+ <table>
15
+ <thead>
16
+ <tr>
17
+ <th><%= SolidusFriendlyPromotions::PromotionCodeBatch.human_attribute_name(:base_code) %></th>
18
+ <th><%= SolidusFriendlyPromotions::PromotionCodeBatch.human_attribute_name(:total_codes) %></th>
19
+ <th><%= SolidusFriendlyPromotions::PromotionCodeBatch.human_attribute_name(:status) %></th>
20
+ <th><%= SolidusFriendlyPromotions::PromotionCodeBatch.human_attribute_name(:email) %></th>
21
+ </tr>
22
+ </thead>
23
+ <tbody>
24
+ <% @promotion_code_batches.each do |promotion_code_batch| %>
25
+ <tr>
26
+ <td><%= promotion_code_batch.base_code %></td>
27
+ <td><%= promotion_code_batch.number_of_codes %></td>
28
+ <td>
29
+ <% if promotion_code_batch.error.present? %>
30
+ <%= t(
31
+ "solidus_friendly_promotions.promotion_code_batches.errored",
32
+ error: promotion_code_batch.error
33
+ ) %>
34
+ <% elsif promotion_code_batch.finished? %>
35
+ <%= t(
36
+ "solidus_friendly_promotions.promotion_code_batches.finished",
37
+ number_of_codes: promotion_code_batch.number_of_codes
38
+ ) %>
39
+ <%= link_to(
40
+ t('solidus_friendly_promotions.download_promotion_codes_list'),
41
+ admin_promotion_promotion_code_batch_download_path(
42
+ promotion_code_batch_id: promotion_code_batch.id,
43
+ format: :csv
44
+ )
45
+ ) %>
46
+ <% else %>
47
+ <%= t(
48
+ "solidus_friendly_promotions.promotion_code_batches.processing",
49
+ number_of_codes: promotion_code_batch.number_of_codes,
50
+ number_of_codes_processed: promotion_code_batch.promotion_codes.count
51
+ ) %>
52
+ <% end %>
53
+ </td>
54
+ <td><%= promotion_code_batch.email %></td>
55
+ </tr>
56
+ <% end %>
57
+ </tbody>
58
+ </table>
59
+ <% else %>
60
+ <div class="no-objects-found">
61
+ <%= render 'spree/admin/shared/no_objects_found',
62
+ resource: SolidusFriendlyPromotions::PromotionCodeBatch,
63
+ new_resource_url: new_object_url %>
64
+ </div>
65
+ <% end %>
@@ -0,0 +1,8 @@
1
+ <% admin_breadcrumb(link_to plural_resource_name(SolidusFriendlyPromotions::Promotion), solidus_friendly_promotions.admin_promotions_path) %>
2
+ <% admin_breadcrumb(link_to @promotion.name, solidus_friendly_promotions.admin_promotion_path(@promotion.id)) %>
3
+ <% admin_breadcrumb(plural_resource_name(SolidusFriendlyPromotions::PromotionCodeBatch)) %>
4
+ <%= form_for :promotion_code_batch, url: collection_url do |batch| %>
5
+ <%= batch.hidden_field :promotion_id, value: params[:promotion_id] %>
6
+ <%= render partial: 'form_fields', locals: {batch: batch, promotion_id: params[:promotion_id]} %>
7
+ <%= batch.submit t('spree.actions.create'), class: 'btn btn-primary' %>
8
+ <% end %>
@@ -85,12 +85,40 @@
85
85
  </div>
86
86
  </fieldset>
87
87
 
88
- <fieldset class="form-group no-border-bottom">
88
+ <fieldset class="form-group row no-border-bottom">
89
89
  <legend><%= t '.activation' %></legend>
90
90
 
91
- <% if @promotion.new_record? %>
92
- <%= render 'spree/admin/promotions/activations_new', f: f %>
93
- <% else %>
94
- <%= render 'spree/admin/promotions/activations_edit', f: f %>
91
+ <div class="col-4">
92
+ <%= f.field_container :apply_automatically do %>
93
+ <%= f.label :apply_automatically do %>
94
+ <%= f.check_box :apply_automatically, disabled: f.object.codes.any? || f.object.path.present? %>
95
+ <%= SolidusFriendlyPromotions::Promotion.human_attribute_name(:apply_automatically) %>
96
+ <%= f.field_hint :promo_code_will_be_disabled %>
97
+ <% end %>
98
+ <% end %>
99
+ </div>
100
+
101
+ <% if f.object.new_record? || f.object.present? %>
102
+ <div class="col-4">
103
+ <%= f.field_container :path do %>
104
+ <%= f.label :path %>
105
+ <%= f.text_field :path, class: "fullwidth", disabled: f.object.apply_automatically || f.object.codes.present? %>
106
+ <% end %>
107
+ </div>
95
108
  <% end %>
109
+
110
+ <div class="col-4">
111
+ <% if f.object.new_record? %>
112
+ <div id="promotion_single_code" class="field">
113
+ <%= label_tag :single_code, SolidusFriendlyPromotions::PromotionCode.model_name.human %>
114
+ <%= text_field_tag :single_code, @promotion.codes.first.try!(:value), class: "fullwidth", disabled: f.object.apply_automatically || f.object.path.present? %>
115
+ </div>
116
+ <% else %>
117
+ <div class="codes-present">
118
+ <p>
119
+ <%= t('.codes_present') %>
120
+ </p>
121
+ </div>
122
+ <% end %>
123
+ </div>
96
124
  </fieldset>
@@ -14,7 +14,7 @@
14
14
  </thead>
15
15
  <tbody>
16
16
  <% promotions.each do |promotion| %>
17
- <tr id="<%= spree_dom_id promotion %>">
17
+ <tr class="<%= 'deleted' if promotion.discarded? %>" id="<%= spree_dom_id promotion %>">
18
18
  <td><%= promotion.name %></td>
19
19
  <td>
20
20
  <%= (promotion.codes.size == 1) ? promotion.codes.pluck(:value).first : t('solidus_friendly_promotions.number_of_codes', count: promotion.codes.size) %>
@@ -43,10 +43,10 @@
43
43
  <%= l(promotion.updated_at, format: :short) %>
44
44
  </td>
45
45
  <td class="actions">
46
- <% if can?(:edit, promotion) %>
46
+ <% if can?(:edit, promotion) && !promotion.discarded? %>
47
47
  <%= link_to_edit promotion, no_text: true %>
48
48
  <% end %>
49
- <% if can?(:destroy, promotion) %>
49
+ <% if can?(:destroy, promotion) && !promotion.discarded? %>
50
50
  <%= link_to_delete promotion, no_text: true %>
51
51
  <% end %>
52
52
  </td>
@@ -43,6 +43,15 @@
43
43
  </div>
44
44
  </div>
45
45
 
46
+ <div class="col-2">
47
+ <div class="field checkbox">
48
+ <label>
49
+ <%= f.check_box :with_discarded, { checked: params[:q][:with_discarded] == 'true', class: 'js-with-discarded-input' }, 'true', 'false' %>
50
+ <%= t('spree.show_deleted') %>
51
+ </label>
52
+ </div>
53
+ </div>
54
+
46
55
  <div class="col-2">
47
56
  <div class="field">
48
57
  <%= label_tag :q_promotion_category_id_eq, SolidusFriendlyPromotions::PromotionCategory.model_name.human %><br>
data/bin/sandbox CHANGED
@@ -65,6 +65,7 @@ unbundled bundle exec rake db:drop db:create
65
65
 
66
66
  unbundled bin/rails generate solidus:install \
67
67
  --auto-accept \
68
+ --admin_preview=false \
68
69
  $@
69
70
 
70
71
  unbundled bundle exec rails generate solidus:auth:install --auto-run-migrations
@@ -9,6 +9,13 @@ en:
9
9
  promotion_categories: Promotion Categories
10
10
  legacy_promotions: Legacy Promotions
11
11
  legacy_promotion_categories: Legacy Promotion Categories
12
+ hints:
13
+ solidus_friendly_promotions/calculator:
14
+ promotions: This is used to determine the promotional discount to be applied to an order, an item, or shipping charges.
15
+ solidus_friendly_promotions/promotion:
16
+ expires_at: This determines when the promotion expires. <br> If no value is specified, the promotion will never expire.
17
+ promo_code_will_be_disabled: Selecting this option, promo codes will be disabled for this promotion because all its rules / actions will be applied automatically to all orders.
18
+ starts_at: This determines when the promotion can be applied to orders. <br> If no value is specified, the promotion will be immediately available.
12
19
  solidus_friendly_promotions:
13
20
  actions: Actions
14
21
  adjustment_labels:
@@ -39,6 +46,9 @@ en:
39
46
  no_rules_added: No Rules Added
40
47
  promotion_successfully_created: Promotion has been successfully created!
41
48
  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.
49
+ promotion_code_batches:
50
+ finished: Finished
51
+ errored: Errored
42
52
  view_promotion_codes_list: View codes list
43
53
  promotion_rules:
44
54
  line_item_product:
@@ -49,9 +59,6 @@ en:
49
59
  match_policies:
50
60
  include: Line item's product has one of the chosen taxons
51
61
  exclude: Line item's product does not have one of the chosen taxons
52
- hints:
53
- solidus_friendly_promotions/calculator:
54
- promotions: This is used to determine the promotional discount to be applied to an order, an item, or shipping charges.
55
62
  coupon_code: Coupon code
56
63
  eligibility_results:
57
64
  coupon_code_applied: The coupon code was successfully applied to your order.
@@ -144,6 +151,7 @@ en:
144
151
  expires_at_placeholder: Never
145
152
  general: General
146
153
  starts_at_placeholder: Immediately
154
+ codes_present: This promotion has promotion codes defined. You cannot select the apply automatically option.
147
155
  edit:
148
156
  order_rules: Order Rules
149
157
  calculator:
@@ -190,6 +198,9 @@ en:
190
198
  solidus_friendly_promotions/rules/user: User
191
199
  solidus_friendly_promotions/rules/user_logged_in: User Logged In
192
200
  solidus_friendly_promotions/rules/user_role: User Role(s)
201
+ solidus_friendly_promotions/promotion_code_batch:
202
+ one: Code batch
203
+ other: Code batches
193
204
  attributes:
194
205
  solidus_friendly_promotions/promotion:
195
206
  active: Active
@@ -308,6 +319,14 @@ en:
308
319
 
309
320
  errors:
310
321
  models:
322
+ solidus_friendly_promotions/promotion_action:
323
+ attributes:
324
+ base:
325
+ cannot_destroy_if_order_completed: Action has been applied to complete orders. It cannot be destroyed.
326
+ solidus_friendly_promotions/promotion_rule:
327
+ attributes:
328
+ promotion:
329
+ already_contains_rule_type: already contains this rule type
311
330
  solidus_friendly_promotions/promotion_code:
312
331
  attributes:
313
332
  base:
@@ -316,3 +335,8 @@ en:
316
335
  attributes:
317
336
  quantity:
318
337
  cannot_be_changed_for_automated_items: cannot be changed on a line item managed by a promotion action
338
+ solidus_friendly_promotions/promotion:
339
+ attributes:
340
+ apply_automatically:
341
+ disallowed_with_path: cannot be set to true when path is present
342
+ disallowed_with_promotion_codes: cannot be set to true when promotion code is present
@@ -0,0 +1,6 @@
1
+ class AddDeletedAtToPromotions < ActiveRecord::Migration[7.0]
2
+ def change
3
+ add_column :friendly_promotions, :deleted_at, :datetime
4
+ add_index :friendly_promotions, :deleted_at
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ class DropDeletedAtFromPromotionActions < ActiveRecord::Migration[7.0]
2
+ def change
3
+ remove_column :friendly_promotion_actions, :deleted_at, :datetime, null: true
4
+ end
5
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SolidusFriendlyPromotions
4
- VERSION = "1.0.0.rc.3"
4
+ VERSION = "1.0.1"
5
5
  end
@@ -7,13 +7,18 @@ require "turbo-rails"
7
7
  require "importmap-rails"
8
8
  require "stimulus-rails"
9
9
  require "ransack-enum"
10
- require "solidus_friendly_promotions/nested_class_set"
11
- require "solidus_friendly_promotions/configuration"
12
- require "solidus_friendly_promotions/version"
13
- require "solidus_friendly_promotions/engine"
14
10
 
15
11
  module SolidusFriendlyPromotions
12
+ def self.table_name_prefix
13
+ "friendly_"
14
+ end
15
+
16
16
  # JS Importmap instance
17
17
  singleton_class.attr_accessor :importmap
18
18
  self.importmap = Importmap::Map.new
19
19
  end
20
+
21
+ require "solidus_friendly_promotions/nested_class_set"
22
+ require "solidus_friendly_promotions/configuration"
23
+ require "solidus_friendly_promotions/version"
24
+ require "solidus_friendly_promotions/engine"
@@ -14,8 +14,8 @@ Gem::Specification.new do |spec|
14
14
  spec.license = "BSD-3-Clause"
15
15
 
16
16
  spec.metadata["homepage_uri"] = spec.homepage
17
- spec.metadata["source_code_uri"] = "https://github.com/solidusio-contrib/solidus_friendly_promotions"
18
- spec.metadata["changelog_uri"] = "https://github.com/solidusio-contrib/solidus_friendly_promotions/blob/master/CHANGELOG.md"
17
+ spec.metadata["source_code_uri"] = "https://github.com/friendlycart/solidus_friendly_promotions"
18
+ spec.metadata["changelog_uri"] = "https://github.com/friendlycart/solidus_friendly_promotions/blob/master/CHANGELOG.md"
19
19
 
20
20
  spec.required_ruby_version = Gem::Requirement.new(">= 2.5", "< 4")
21
21
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solidus_friendly_promotions
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.rc.3
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Martin Meyerhoff
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-01-16 00:00:00.000000000 Z
11
+ date: 2024-05-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: solidus_core
@@ -193,6 +193,7 @@ files:
193
193
  - app/decorators/models/solidus_friendly_promotions/shipping_rate_decorator.rb
194
194
  - app/helpers/solidus_friendly_promotions/admin/promotion_actions_helper.rb
195
195
  - app/helpers/solidus_friendly_promotions/admin/promotion_rules_helper.rb
196
+ - app/helpers/solidus_friendly_promotions/admin/promotions_helper.rb
196
197
  - app/javascript/solidus_friendly_promotions.js
197
198
  - app/javascript/solidus_friendly_promotions/controllers/application.js
198
199
  - app/javascript/solidus_friendly_promotions/controllers/calculator_tiers_controller.js
@@ -209,7 +210,6 @@ files:
209
210
  - app/models/concerns/solidus_friendly_promotions/rules/line_item_level_rule.rb
210
211
  - app/models/concerns/solidus_friendly_promotions/rules/order_level_rule.rb
211
212
  - app/models/concerns/solidus_friendly_promotions/rules/shipment_level_rule.rb
212
- - app/models/solidus_friendly_promotions.rb
213
213
  - app/models/solidus_friendly_promotions/actions/adjust_line_item.rb
214
214
  - app/models/solidus_friendly_promotions/actions/adjust_line_item_quantity_groups.rb
215
215
  - app/models/solidus_friendly_promotions/actions/adjust_shipment.rb
@@ -291,6 +291,10 @@ files:
291
291
  - app/views/solidus_friendly_promotions/admin/promotion_categories/edit.html.erb
292
292
  - app/views/solidus_friendly_promotions/admin/promotion_categories/index.html.erb
293
293
  - app/views/solidus_friendly_promotions/admin/promotion_categories/new.html.erb
294
+ - app/views/solidus_friendly_promotions/admin/promotion_code_batches/_form_fields.html.erb
295
+ - app/views/solidus_friendly_promotions/admin/promotion_code_batches/download.csv.ruby
296
+ - app/views/solidus_friendly_promotions/admin/promotion_code_batches/index.html.erb
297
+ - app/views/solidus_friendly_promotions/admin/promotion_code_batches/new.html.erb
294
298
  - app/views/solidus_friendly_promotions/admin/promotion_codes/index.csv.ruby
295
299
  - app/views/solidus_friendly_promotions/admin/promotion_codes/index.html.erb
296
300
  - app/views/solidus_friendly_promotions/admin/promotion_codes/new.html.erb
@@ -315,8 +319,6 @@ files:
315
319
  - app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_user_logged_in.html.erb
316
320
  - app/views/solidus_friendly_promotions/admin/promotion_rules/rules/_user_role.html.erb
317
321
  - app/views/solidus_friendly_promotions/admin/promotion_rules/rules/line_item_option_value/_option_value_fields.html.erb
318
- - app/views/solidus_friendly_promotions/admin/promotions/_activations_edit.html.erb
319
- - app/views/solidus_friendly_promotions/admin/promotions/_activations_new.html.erb
320
322
  - app/views/solidus_friendly_promotions/admin/promotions/_form.html.erb
321
323
  - app/views/solidus_friendly_promotions/admin/promotions/_table.html.erb
322
324
  - app/views/solidus_friendly_promotions/admin/promotions/_table_filter.html.erb
@@ -335,7 +337,6 @@ files:
335
337
  - bin/sandbox
336
338
  - bin/setup
337
339
  - config/importmap.rb
338
- - config/initializers/solidus_friendly_promotions.rb
339
340
  - config/locales/en.yml
340
341
  - config/routes.rb
341
342
  - db/migrate/20230703101637_create_promotions.rb
@@ -363,6 +364,8 @@ files:
363
364
  - db/migrate/20231012120928_add_db_comments_to_friendly_promotion_categories.rb
364
365
  - db/migrate/20231013181921_add_original_promotion_ids.rb
365
366
  - db/migrate/20231104135812_add_managed_by_order_action_to_line_items.rb
367
+ - db/migrate/20240124104855_add_deleted_at_to_promotions.rb
368
+ - db/migrate/20240125102050_drop_deleted_at_from_promotion_actions.rb
366
369
  - lib/generators/solidus_friendly_promotions/install/install_generator.rb
367
370
  - lib/generators/solidus_friendly_promotions/install/templates/initializer.rb
368
371
  - lib/solidus_friendly_promotions.rb
@@ -390,8 +393,8 @@ licenses:
390
393
  - BSD-3-Clause
391
394
  metadata:
392
395
  homepage_uri: https://github.com/solidusio-contrib/solidus_friendly_promotions#readme
393
- source_code_uri: https://github.com/solidusio-contrib/solidus_friendly_promotions
394
- changelog_uri: https://github.com/solidusio-contrib/solidus_friendly_promotions/blob/master/CHANGELOG.md
396
+ source_code_uri: https://github.com/friendlycart/solidus_friendly_promotions
397
+ changelog_uri: https://github.com/friendlycart/solidus_friendly_promotions/blob/master/CHANGELOG.md
395
398
  post_install_message:
396
399
  rdoc_options: []
397
400
  require_paths:
@@ -406,9 +409,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
406
409
  version: '4'
407
410
  required_rubygems_version: !ruby/object:Gem::Requirement
408
411
  requirements:
409
- - - ">"
412
+ - - ">="
410
413
  - !ruby/object:Gem::Version
411
- version: 1.3.1
414
+ version: '0'
412
415
  requirements: []
413
416
  rubygems_version: 3.4.20
414
417
  signing_key:
@@ -1,7 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SolidusFriendlyPromotions
4
- def self.table_name_prefix
5
- "friendly_"
6
- end
7
- end
@@ -1,22 +0,0 @@
1
- <% if @promotion.apply_automatically? %>
2
- <p>
3
- <%= t('.auto') %>
4
- </p>
5
- <% end %>
6
-
7
- <% if @promotion.codes.count == 1 %>
8
- <p>
9
- <%= t('.single_code_html', code: @promotion.codes.first.value) %>
10
- </p>
11
- <% elsif @promotion.codes.count > 1 %>
12
- <p>
13
- <%= t('.multiple_codes_html', count: @promotion.codes.count) %>
14
- </p>
15
- <% end %>
16
-
17
- <% if @promotion.path %>
18
- <div class="field">
19
- <%= f.label :path %>
20
- <%= f.text_field :path, class: "fullwidth" %>
21
- </div>
22
- <% end %>
@@ -1,43 +0,0 @@
1
- <% activation_type = params[:activation_type] || 'single_code' %>
2
- <div class="row" id="js_promotion_activation">
3
- <div class="col-3">
4
- <div class="form-check">
5
- <label class="form-check-label">
6
- <%= radio_button_tag('activation_type', 'auto', activation_type == 'auto') %>
7
- <%= t('.auto') %> <%= f.field_hint :promo_code_will_be_disabled %>
8
- </label>
9
- </div>
10
- <div class="form-check">
11
- <label class="form-check-label">
12
- <%= radio_button_tag('activation_type', 'single_code', activation_type == 'single_code') %>
13
- <%= t('.single_code') %>
14
- </label>
15
- </div>
16
- <div class="form-check">
17
- <label class="form-check-label">
18
- <%= radio_button_tag('activation_type', 'multiple_codes', activation_type == 'multiple_codes') %>
19
- <%= t('.multiple_codes') %>
20
- </label>
21
- </div>
22
- </div>
23
- <div class="col-9">
24
- <input name="promotion[apply_automatically]" type="hidden" value="0">
25
- <div data-activation-type="auto">
26
- <input name="promotion[apply_automatically]" type="hidden" value="1">
27
- </div>
28
-
29
- <div data-activation-type="single_code">
30
- <div class="field">
31
- <%= label_tag :single_code, SolidusFriendlyPromotions::PromotionCode.model_name.human, class: "required" %>
32
- <%= text_field_tag :single_code, @promotion.codes.first.try!(:value), class: "fullwidth", required: true %>
33
- </div>
34
- </div>
35
-
36
- <div data-activation-type="multiple_codes">
37
- <%= fields_for :promotion_code_batch, @promotion_code_batch do |batch| %>
38
- <%= render partial: 'spree/admin/promotion_code_batches/form_fields', locals: {f: f, batch: batch, promotion_id: params[:promotion_id]} %>
39
- <% end %>
40
- </div>
41
-
42
- </div>
43
- </div>
@@ -1,3 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative SolidusFriendlyPromotions::Engine.root.join("app/models/solidus_friendly_promotions.rb")