solidus_legacy_promotions 4.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (179) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +345 -0
  3. data/Rakefile +43 -0
  4. data/app/assets/config/solidus_legacy_promotions/manifest.js +2 -0
  5. data/app/assets/javascripts/spree/backend/edit_promotion.js +7 -0
  6. data/app/assets/javascripts/spree/backend/promotions/activation.js +26 -0
  7. data/app/assets/javascripts/spree/backend/promotions.js +35 -0
  8. data/app/assets/javascripts/spree/backend/templates/promotions/calculators/fields/tiered_flat_rate.hbs +23 -0
  9. data/app/assets/javascripts/spree/backend/templates/promotions/calculators/fields/tiered_percent.hbs +23 -0
  10. data/app/assets/javascripts/spree/backend/templates/promotions/rules/option_values.hbs +12 -0
  11. data/app/assets/javascripts/spree/backend/views/calculators/tiered.js +54 -0
  12. data/app/assets/javascripts/spree/backend/views/order/details_adjustments.js +43 -0
  13. data/app/assets/javascripts/spree/backend/views/promotions/option_values_rule.js +79 -0
  14. data/app/assets/javascripts/spree/backend/views/promotions.js +3 -0
  15. data/app/assets/stylesheets/solidus_legacy_promotions/promotions/_edit.scss +138 -0
  16. data/app/assets/stylesheets/solidus_legacy_promotions/promotions.scss +3 -0
  17. data/app/assets/stylesheets/spree/backend/sections/_adjustments.scss +3 -0
  18. data/app/decorators/solidus_legacy_promotions/controllers/solidus_admin/solidus_admin_adjustments_controller_decorator.rb +20 -0
  19. data/app/decorators/solidus_legacy_promotions/lib/spree_order_state_machine_decorator.rb +18 -0
  20. data/app/decorators/solidus_legacy_promotions/models/spree_adjustment_decorator.rb +76 -0
  21. data/app/decorators/solidus_legacy_promotions/models/spree_calculator_returns_default_refund_amount_decorator.rb +13 -0
  22. data/app/decorators/solidus_legacy_promotions/models/spree_line_item_decorator.rb +11 -0
  23. data/app/decorators/solidus_legacy_promotions/models/spree_order_decorator.rb +28 -0
  24. data/app/decorators/solidus_legacy_promotions/models/spree_order_updater_decorator.rb +40 -0
  25. data/app/decorators/solidus_legacy_promotions/models/spree_product_decorator.rb +16 -0
  26. data/app/decorators/solidus_legacy_promotions/models/spree_promotion_code_batch_decorator.rb +16 -0
  27. data/app/decorators/solidus_legacy_promotions/models/spree_shipment_decorator.rb +13 -0
  28. data/app/helpers/spree/admin/promotions_helper.rb +15 -0
  29. data/app/helpers/spree/promotion_rules_helper.rb +12 -0
  30. data/app/jobs/spree/promotion_code_batch_job.rb +26 -0
  31. data/app/mailers/spree/promotion_code_batch_mailer.rb +15 -0
  32. data/app/models/spree/calculator/distributed_amount.rb +33 -0
  33. data/app/models/spree/calculator/flat_percent_item_total.rb +23 -0
  34. data/app/models/spree/calculator/flexi_rate.rb +22 -0
  35. data/app/models/spree/calculator/percent_on_line_item.rb +13 -0
  36. data/app/models/spree/calculator/tiered_flat_rate.rb +52 -0
  37. data/app/models/spree/calculator/tiered_percent.rb +62 -0
  38. data/app/models/spree/order_contents.rb +36 -0
  39. data/app/models/spree/order_promotion.rb +27 -0
  40. data/app/models/spree/permission_sets/promotion_display.rb +25 -0
  41. data/app/models/spree/permission_sets/promotion_management.rb +25 -0
  42. data/app/models/spree/promotion/actions/create_adjustment.rb +81 -0
  43. data/app/models/spree/promotion/actions/create_item_adjustments.rb +98 -0
  44. data/app/models/spree/promotion/actions/create_quantity_adjustments.rb +139 -0
  45. data/app/models/spree/promotion/actions/free_shipping.rb +59 -0
  46. data/app/models/spree/promotion/order_adjustments_recalculator.rb +92 -0
  47. data/app/models/spree/promotion/rules/first_order.rb +36 -0
  48. data/app/models/spree/promotion/rules/first_repeat_purchase_since.rb +36 -0
  49. data/app/models/spree/promotion/rules/item_total.rb +86 -0
  50. data/app/models/spree/promotion/rules/minimum_quantity.rb +59 -0
  51. data/app/models/spree/promotion/rules/nth_order.rb +45 -0
  52. data/app/models/spree/promotion/rules/one_use_per_user.rb +25 -0
  53. data/app/models/spree/promotion/rules/option_value.rb +50 -0
  54. data/app/models/spree/promotion/rules/product.rb +86 -0
  55. data/app/models/spree/promotion/rules/store.rb +26 -0
  56. data/app/models/spree/promotion/rules/taxon.rb +91 -0
  57. data/app/models/spree/promotion/rules/user.rb +34 -0
  58. data/app/models/spree/promotion/rules/user_logged_in.rb +20 -0
  59. data/app/models/spree/promotion/rules/user_role.rb +45 -0
  60. data/app/models/spree/promotion.rb +271 -0
  61. data/app/models/spree/promotion_action.rb +51 -0
  62. data/app/models/spree/promotion_advertiser.rb +10 -0
  63. data/app/models/spree/promotion_category.rb +8 -0
  64. data/app/models/spree/promotion_chooser.rb +34 -0
  65. data/app/models/spree/promotion_code/batch_builder.rb +68 -0
  66. data/app/models/spree/promotion_code.rb +54 -0
  67. data/app/models/spree/promotion_code_batch.rb +18 -0
  68. data/app/models/spree/promotion_finder.rb +9 -0
  69. data/app/models/spree/promotion_handler/cart.rb +75 -0
  70. data/app/models/spree/promotion_handler/coupon.rb +125 -0
  71. data/app/models/spree/promotion_handler/page.rb +26 -0
  72. data/app/models/spree/promotion_handler/shipping.rb +61 -0
  73. data/app/models/spree/promotion_rule.rb +55 -0
  74. data/app/models/spree/promotion_rule_store.rb +10 -0
  75. data/app/models/spree/promotion_rule_taxon.rb +8 -0
  76. data/app/models/spree/promotion_rule_user.rb +10 -0
  77. data/app/subscribers/spree/order_promotion_subscriber.rb +20 -0
  78. data/app/views/spree/order_mailer/cancel_email.html.erb +45 -0
  79. data/app/views/spree/order_mailer/cancel_email.text.erb +16 -0
  80. data/app/views/spree/order_mailer/confirm_email.html.erb +84 -0
  81. data/app/views/spree/order_mailer/confirm_email.text.erb +38 -0
  82. data/app/views/spree/promotion_code_batch_mailer/promotion_code_batch_errored.text.erb +2 -0
  83. data/app/views/spree/promotion_code_batch_mailer/promotion_code_batch_finished.text.erb +2 -0
  84. data/bin/rails +13 -0
  85. data/config/locales/en.yml +101 -0
  86. data/config/locales/promotion_categories.en.yml +6 -0
  87. data/config/locales/promotions.en.yml +6 -0
  88. data/config/routes.rb +28 -0
  89. data/db/migrate/20160101010001_solidus_one_four_promotions.rb +126 -0
  90. data/db/migrate/20161017102621_create_spree_promotion_code_batch.rb +48 -0
  91. data/db/migrate/20180202190713_create_promotion_rule_stores.rb +14 -0
  92. data/db/migrate/20180328172631_add_join_characters_to_promotion_code_batch.rb +15 -0
  93. data/db/migrate/20190106184413_remove_code_from_spree_promotions.rb +46 -0
  94. data/db/migrate/20220317165036_set_promotions_with_any_policy_to_all_if_possible.rb +20 -0
  95. data/db/migrate/20230322085416_remove_match_policy_from_spree_promotion.rb +7 -0
  96. data/db/migrate/20230325132905_remove_unused_columns_from_promotion_rules.rb +10 -0
  97. data/db/migrate/20231027084517_add_order_promotions_foreign_key.rb +14 -0
  98. data/db/migrate/20240621100123_add_promotion_code_id_to_spree_adjustments.rb +10 -0
  99. data/db/migrate/20240622113334_move_adjustment_eligible_to_legacy_promotions.rb +11 -0
  100. data/lib/components/admin/solidus_admin/orders/show/adjustments/index/source/spree_promotion_action/component.rb +17 -0
  101. data/lib/components/admin/solidus_admin/promotion_categories/index/component.rb +56 -0
  102. data/lib/components/admin/solidus_admin/promotions/index/component.rb +104 -0
  103. data/lib/components/admin/solidus_admin/promotions/index/component.yml +10 -0
  104. data/lib/components/admin/solidus_legacy_promotions/orders/index/component.rb +15 -0
  105. data/lib/components/admin/solidus_legacy_promotions/orders/index/component.yml +20 -0
  106. data/lib/controllers/admin/solidus_admin/promotion_categories_controller.rb +29 -0
  107. data/lib/controllers/admin/solidus_admin/promotions_controller.rb +46 -0
  108. data/lib/controllers/backend/spree/admin/promotion_actions_controller.rb +51 -0
  109. data/lib/controllers/backend/spree/admin/promotion_categories_controller.rb +8 -0
  110. data/lib/controllers/backend/spree/admin/promotion_code_batches_controller.rb +30 -0
  111. data/lib/controllers/backend/spree/admin/promotion_codes_controller.rb +48 -0
  112. data/lib/controllers/backend/spree/admin/promotion_rules_controller.rb +60 -0
  113. data/lib/controllers/backend/spree/admin/promotions_controller.rb +66 -0
  114. data/lib/solidus_legacy_promotions/configuration.rb +115 -0
  115. data/lib/solidus_legacy_promotions/engine.rb +97 -0
  116. data/lib/solidus_legacy_promotions/migrations/promotions_with_code_handlers.rb +68 -0
  117. data/lib/solidus_legacy_promotions/testing_support/factories/calculator_factory.rb +7 -0
  118. data/lib/solidus_legacy_promotions/testing_support/factories/order_factory.rb +22 -0
  119. data/lib/solidus_legacy_promotions/testing_support/factories/order_promotion_factory.rb +8 -0
  120. data/lib/solidus_legacy_promotions/testing_support/factories/promotion_category_factory.rb +7 -0
  121. data/lib/solidus_legacy_promotions/testing_support/factories/promotion_code_factory.rb +8 -0
  122. data/lib/solidus_legacy_promotions/testing_support/factories/promotion_factory.rb +98 -0
  123. data/lib/solidus_legacy_promotions/testing_support/factory_bot.rb +28 -0
  124. data/lib/solidus_legacy_promotions.rb +28 -0
  125. data/lib/tasks/solidus_legacy_promotions/delete_ineligible_adjustments.rake +8 -0
  126. data/lib/views/backend/spree/admin/adjustments/_adjustment.html.erb +24 -0
  127. data/lib/views/backend/spree/admin/orders/_adjustments.html.erb +19 -0
  128. data/lib/views/backend/spree/admin/orders/_order_details.html.erb +32 -0
  129. data/lib/views/backend/spree/admin/orders/confirm.html.erb +59 -0
  130. data/lib/views/backend/spree/admin/promotion_actions/create.js.erb +10 -0
  131. data/lib/views/backend/spree/admin/promotion_actions/destroy.js.erb +1 -0
  132. data/lib/views/backend/spree/admin/promotion_categories/_form.html.erb +14 -0
  133. data/lib/views/backend/spree/admin/promotion_categories/edit.html.erb +10 -0
  134. data/lib/views/backend/spree/admin/promotion_categories/index.html.erb +47 -0
  135. data/lib/views/backend/spree/admin/promotion_categories/new.html.erb +10 -0
  136. data/lib/views/backend/spree/admin/promotion_code_batches/_form_fields.html.erb +22 -0
  137. data/lib/views/backend/spree/admin/promotion_code_batches/download.csv.ruby +8 -0
  138. data/lib/views/backend/spree/admin/promotion_code_batches/index.html.erb +65 -0
  139. data/lib/views/backend/spree/admin/promotion_code_batches/new.html.erb +8 -0
  140. data/lib/views/backend/spree/admin/promotion_codes/index.csv.ruby +8 -0
  141. data/lib/views/backend/spree/admin/promotion_codes/index.html.erb +32 -0
  142. data/lib/views/backend/spree/admin/promotion_codes/new.html.erb +31 -0
  143. data/lib/views/backend/spree/admin/promotion_rules/create.js.erb +8 -0
  144. data/lib/views/backend/spree/admin/promotion_rules/destroy.js.erb +3 -0
  145. data/lib/views/backend/spree/admin/promotions/_actions.html.erb +37 -0
  146. data/lib/views/backend/spree/admin/promotions/_activations_edit.html.erb +22 -0
  147. data/lib/views/backend/spree/admin/promotions/_activations_new.html.erb +43 -0
  148. data/lib/views/backend/spree/admin/promotions/_form.html.erb +67 -0
  149. data/lib/views/backend/spree/admin/promotions/_promotion_action.html.erb +13 -0
  150. data/lib/views/backend/spree/admin/promotions/_promotion_rule.html.erb +12 -0
  151. data/lib/views/backend/spree/admin/promotions/_rules.html.erb +42 -0
  152. data/lib/views/backend/spree/admin/promotions/actions/_create_adjustment.html.erb +2 -0
  153. data/lib/views/backend/spree/admin/promotions/actions/_create_item_adjustments.html.erb +6 -0
  154. data/lib/views/backend/spree/admin/promotions/actions/_create_quantity_adjustments.html.erb +10 -0
  155. data/lib/views/backend/spree/admin/promotions/actions/_free_shipping.html.erb +0 -0
  156. data/lib/views/backend/spree/admin/promotions/actions/_promotion_calculators_with_custom_fields.html.erb +29 -0
  157. data/lib/views/backend/spree/admin/promotions/calculators/_default_fields.html.erb +6 -0
  158. data/lib/views/backend/spree/admin/promotions/calculators/distributed_amount/_fields.html.erb +56 -0
  159. data/lib/views/backend/spree/admin/promotions/calculators/flat_rate/_fields.html.erb +6 -0
  160. data/lib/views/backend/spree/admin/promotions/calculators/tiered_flat_rate/_fields.html.erb +30 -0
  161. data/lib/views/backend/spree/admin/promotions/calculators/tiered_percent/_fields.html.erb +30 -0
  162. data/lib/views/backend/spree/admin/promotions/edit.html.erb +40 -0
  163. data/lib/views/backend/spree/admin/promotions/index.html.erb +124 -0
  164. data/lib/views/backend/spree/admin/promotions/new.html.erb +14 -0
  165. data/lib/views/backend/spree/admin/promotions/rules/_first_order.html.erb +0 -0
  166. data/lib/views/backend/spree/admin/promotions/rules/_first_repeat_purchase_since.html.erb +13 -0
  167. data/lib/views/backend/spree/admin/promotions/rules/_item_total.html.erb +14 -0
  168. data/lib/views/backend/spree/admin/promotions/rules/_minimum_quantity.html.erb +5 -0
  169. data/lib/views/backend/spree/admin/promotions/rules/_nth_order.html.erb +12 -0
  170. data/lib/views/backend/spree/admin/promotions/rules/_one_use_per_user.html.erb +0 -0
  171. data/lib/views/backend/spree/admin/promotions/rules/_option_value.html.erb +13 -0
  172. data/lib/views/backend/spree/admin/promotions/rules/_product.html.erb +15 -0
  173. data/lib/views/backend/spree/admin/promotions/rules/_store.html.erb +6 -0
  174. data/lib/views/backend/spree/admin/promotions/rules/_taxon.html.erb +9 -0
  175. data/lib/views/backend/spree/admin/promotions/rules/_user.html.erb +4 -0
  176. data/lib/views/backend/spree/admin/promotions/rules/_user_logged_in.html.erb +0 -0
  177. data/lib/views/backend/spree/admin/promotions/rules/_user_role.html.erb +12 -0
  178. data/solidus_legacy_promotions.gemspec +29 -0
  179. metadata +262 -0
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spree
4
+ module PromotionHandler
5
+ class Coupon
6
+ attr_reader :order, :coupon_code
7
+ attr_accessor :error, :success, :status_code
8
+
9
+ def initialize(order)
10
+ @order = order
11
+ @coupon_code = order.coupon_code && order.coupon_code.downcase
12
+ end
13
+
14
+ def can_apply?
15
+ Spree::Promotion.order_activatable?(order)
16
+ end
17
+
18
+ def apply
19
+ if coupon_code.present?
20
+ if promotion.present? && promotion.active? && promotion.actions.exists?
21
+ handle_present_promotion(promotion)
22
+ elsif promotion_code&.promotion&.expired?
23
+ set_error_code :coupon_code_expired
24
+ else
25
+ set_error_code :coupon_code_not_found
26
+ end
27
+ end
28
+
29
+ self
30
+ end
31
+
32
+ def remove
33
+ if promotion.blank?
34
+ set_error_code :coupon_code_not_found
35
+ elsif !promotion_exists_on_order?(order, promotion)
36
+ set_error_code :coupon_code_not_present
37
+ else
38
+ promotion.remove_from(order)
39
+ order.recalculate
40
+ set_success_code :coupon_code_removed
41
+ end
42
+
43
+ self
44
+ end
45
+
46
+ def set_success_code(status_code)
47
+ @status_code = status_code
48
+ @success = I18n.t(status_code, scope: 'spree')
49
+ end
50
+
51
+ def set_error_code(status_code, options = {})
52
+ @status_code = status_code
53
+ @error = options[:error] || I18n.t(status_code, scope: 'spree')
54
+ end
55
+
56
+ def promotion
57
+ @promotion ||= if promotion_code && promotion_code.promotion.active?
58
+ promotion_code.promotion
59
+ end
60
+ end
61
+
62
+ def successful?
63
+ success.present? && error.blank?
64
+ end
65
+
66
+ private
67
+
68
+ def promotion_code
69
+ @promotion_code ||= Spree::PromotionCode.where(value: coupon_code).first
70
+ end
71
+
72
+ def handle_present_promotion(promotion)
73
+ return promotion_usage_limit_exceeded if promotion.usage_limit_exceeded? || promotion_code.usage_limit_exceeded?
74
+ return promotion_applied if promotion_exists_on_order?(order, promotion)
75
+
76
+ unless promotion.eligible?(order, promotion_code:)
77
+ set_promotion_eligibility_error_code(promotion)
78
+ return error || ineligible_for_this_order
79
+ end
80
+
81
+ # If any of the actions for the promotion return `true`,
82
+ # then result here will also be `true`.
83
+ result = promotion.activate(order:, promotion_code:)
84
+ if result
85
+ order.recalculate
86
+ set_success_code :coupon_code_applied
87
+ else
88
+ set_error_code :coupon_code_unknown_error
89
+ end
90
+ end
91
+
92
+ def set_promotion_eligibility_error_code(promotion)
93
+ return unless eligibility_error_code_present?(promotion)
94
+
95
+ eligibility_error = promotion.eligibility_errors.details[:base].first
96
+
97
+ @status_code = eligibility_error[:error_code]
98
+ @error = eligibility_error[:error]
99
+ end
100
+
101
+ def promotion_usage_limit_exceeded
102
+ set_error_code :coupon_code_max_usage
103
+ end
104
+
105
+ def ineligible_for_this_order
106
+ set_error_code :coupon_code_not_eligible
107
+ end
108
+
109
+ def promotion_applied
110
+ set_error_code :coupon_code_already_applied
111
+ end
112
+
113
+ def promotion_exists_on_order?(order, promotion)
114
+ order.promotions.include? promotion
115
+ end
116
+
117
+ def eligibility_error_code_present?(promotion)
118
+ promotion.eligibility_errors.present? &&
119
+ promotion.eligibility_errors.details.present? &&
120
+ promotion.eligibility_errors.details.key?(:base) &&
121
+ promotion.eligibility_errors.details[:base].first[:error_code].present?
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spree
4
+ module PromotionHandler
5
+ class Page
6
+ attr_reader :order, :path
7
+
8
+ def initialize(order, path)
9
+ @order = order
10
+ @path = path.delete_prefix('/')
11
+ end
12
+
13
+ def activate
14
+ if promotion && promotion.eligible?(order)
15
+ promotion.activate(order:)
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def promotion
22
+ @promotion ||= Spree::Promotion.active.find_by(path:)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spree
4
+ module PromotionHandler
5
+ # Used for activating promotions with shipping rules
6
+ class Shipping
7
+ attr_reader :order
8
+ attr_accessor :error, :success
9
+
10
+ def initialize(order)
11
+ @order = order
12
+ end
13
+
14
+ def activate
15
+ connected_promotions.each do |order_promotion|
16
+ if order_promotion.promotion.eligible?(order)
17
+ order_promotion.promotion.activate(
18
+ order:,
19
+ promotion_code: order_promotion.promotion_code,
20
+ )
21
+ end
22
+ end
23
+
24
+ not_connected_automatic_promotions.each do |promotion|
25
+ if promotion.eligible?(order)
26
+ promotion.activate(order:)
27
+ end
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def not_connected_automatic_promotions
34
+ automatic_promotions - connected_promotions.map(&:promotion)
35
+ end
36
+
37
+ def automatic_promotions
38
+ @automatic_promotions ||= active_shipping_promotions.
39
+ where(apply_automatically: true).
40
+ to_a.
41
+ uniq
42
+ end
43
+
44
+ def connected_promotions
45
+ @connected_promotions ||= order.order_promotions.
46
+ joins(:promotion).
47
+ includes(:promotion).
48
+ merge(active_shipping_promotions).
49
+ to_a.
50
+ uniq
51
+ end
52
+
53
+ def active_shipping_promotions
54
+ Spree::Promotion.all.
55
+ active.
56
+ joins(:promotion_actions).
57
+ merge(Spree::PromotionAction.shipping)
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spree/preferences/persistable'
4
+
5
+ module Spree
6
+ # Base class for all promotion rules
7
+ class PromotionRule < Spree::Base
8
+ include Spree::Preferences::Persistable
9
+
10
+ belongs_to :promotion, class_name: 'Spree::Promotion', inverse_of: :promotion_rules, optional: true
11
+
12
+ scope :of_type, ->(type) { where(type:) }
13
+
14
+ validates :promotion, presence: true
15
+ validate :unique_per_promotion, on: :create
16
+
17
+ def preload_relations
18
+ []
19
+ end
20
+
21
+ def applicable?(_promotable)
22
+ raise NotImplementedError, "applicable? should be implemented in a sub-class of Spree::PromotionRule"
23
+ end
24
+
25
+ def eligible?(_promotable, _options = {})
26
+ raise NotImplementedError, "eligible? should be implemented in a sub-class of Spree::PromotionRule"
27
+ end
28
+
29
+ # This states if a promotion can be applied to the specified line item
30
+ # It is true by default, but can be overridden by promotion rules to provide conditions
31
+ def actionable?(_line_item)
32
+ true
33
+ end
34
+
35
+ def eligibility_errors
36
+ @eligibility_errors ||= ActiveModel::Errors.new(self)
37
+ end
38
+
39
+ def to_partial_path
40
+ "spree/admin/promotions/rules/#{model_name.element}"
41
+ end
42
+
43
+ private
44
+
45
+ def unique_per_promotion
46
+ if Spree::PromotionRule.exists?(promotion_id:, type: self.class.name)
47
+ errors[:base] << "Promotion already contains this rule type"
48
+ end
49
+ end
50
+
51
+ def eligibility_error_message(key, options = {})
52
+ I18n.t(key, **{ scope: [:spree, :eligibility_errors, :messages] }.merge(options))
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spree
4
+ class PromotionRuleStore < Spree::Base
5
+ self.table_name = "spree_promotion_rules_stores"
6
+
7
+ belongs_to :promotion_rule, class_name: "Spree::PromotionRule", optional: true
8
+ belongs_to :store, class_name: "Spree::Store", optional: true
9
+ end
10
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spree
4
+ class PromotionRuleTaxon < Spree::Base
5
+ belongs_to :promotion_rule, optional: true
6
+ belongs_to :taxon, optional: true
7
+ end
8
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spree
4
+ class PromotionRuleUser < Spree::Base
5
+ self.table_name = 'spree_promotion_rules_users'
6
+
7
+ belongs_to :promotion_rule, class_name: 'Spree::PromotionRule', optional: true
8
+ belongs_to :user, class_name: Spree::UserClassHandle.new, optional: true
9
+ end
10
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spree
4
+ # Clears promotions from an emptied order
5
+ class OrderPromotionSubscriber
6
+ include Omnes::Subscriber
7
+
8
+ handle :order_emptied,
9
+ with: :clear_order_promotions,
10
+ id: :spree_order_promotion_clear_order_promotions
11
+
12
+ # Clears all promotions from the order
13
+ #
14
+ # @param event [Omnes::UnstructuredEvent]
15
+ def clear_order_promotions(event)
16
+ order = event[:order]
17
+ order.order_promotions.destroy_all
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,45 @@
1
+ <table>
2
+ <tr>
3
+ <td>
4
+ <p class="lede">
5
+ <%= t('.dear_customer') %>
6
+ </p>
7
+ <p>
8
+ <%= t('.instructions') %>
9
+ </p>
10
+ <p>
11
+ <%= t('.order_summary_canceled') %>
12
+ </p>
13
+ <table>
14
+ <% @order.line_items.each do |item| %>
15
+ <tr>
16
+ <td><%= item.variant.sku %></td>
17
+ <td>
18
+ <%= item.variant.product.name %>
19
+ <%= item.variant.options_text -%>
20
+ </td>
21
+ <td>(<%=item.quantity%>) @ <%= item.single_money %> = <%= item.display_amount %></td>
22
+ </tr>
23
+ <% end %>
24
+ <tr>
25
+ <td></td>
26
+ <td><%= t('.subtotal') %></td>
27
+ <td><%= @order.display_item_total %></td>
28
+ </tr>
29
+ <% @order.adjustments.eligible.each do |adjustment| %>
30
+ <tr>
31
+ <td></td>
32
+ <td><%= sanitize(adjustment.label) %></td>
33
+ <td><%= adjustment.display_amount %></td>
34
+ </tr>
35
+ <% end %>
36
+ <tr>
37
+ <td></td>
38
+ <td><%= t('.total') %></td>
39
+ <td><%= @order.display_total %></td>
40
+ </tr>
41
+ </table>
42
+ </td>
43
+ <td class="expander"></td>
44
+ </tr>
45
+ </table>
@@ -0,0 +1,16 @@
1
+ <%= t('.dear_customer') %>
2
+
3
+ <%= t('.instructions') %>
4
+
5
+ ============================================================
6
+ <%= t('.order_summary_canceled') %>
7
+ ============================================================
8
+ <% @order.line_items.each do |item| %>
9
+ <%= item.variant.sku %> <%= raw(item.variant.product.name) %> <%= raw(item.variant.options_text) -%> (<%=item.quantity%>) @ <%= item.single_money %> = <%= item.display_amount %>
10
+ <% end %>
11
+ ============================================================
12
+ <%= t('.subtotal') %> <%= @order.display_item_total %>
13
+ <% @order.adjustments.eligible.each do |adjustment| %>
14
+ <%= raw(adjustment.label) %> <%= adjustment.display_amount %>
15
+ <% end %>
16
+ <%= t('.total') %> <%= @order.display_total %>
@@ -0,0 +1,84 @@
1
+ <table>
2
+ <tr>
3
+ <td>
4
+ <p class="lede">
5
+ <%= t('.dear_customer') %>
6
+ </p>
7
+ <p>
8
+ <%= t('.instructions') %>
9
+ </p>
10
+ <p>
11
+ <%= t('.order_summary') %>
12
+ </p>
13
+ <table>
14
+ <% @order.line_items.each do |item| %>
15
+ <tr>
16
+ <td><%= item.variant.sku %></td>
17
+ <td>
18
+ <%= item.variant.product.name %>
19
+ <%= item.variant.options_text -%>
20
+ </td>
21
+ <td>(<%=item.quantity%>) @ <%= item.single_money %> = <%= item.display_amount %></td>
22
+ </tr>
23
+ <% end %>
24
+ <tr>
25
+ <td></td>
26
+ <td>
27
+ <%= t('.subtotal') %>
28
+ </td>
29
+ <td>
30
+ <%= @order.display_item_total %>
31
+ </td>
32
+ </tr>
33
+ <% if @order.line_item_adjustments.exists? %>
34
+ <% if @order.all_adjustments.promotion.eligible.exists? %>
35
+ <% @order.all_adjustments.promotion.eligible.group_by(&:label).each do |label, adjustments| %>
36
+ <tr>
37
+ <td></td>
38
+ <td><%= t('spree.promotion') %> <%= label %>:</td>
39
+ <td><%= Spree::Money.new(adjustments.sum(&:amount), currency: @order.currency) %></td>
40
+ </tr>
41
+ <% end %>
42
+ <% end %>
43
+ <% end %>
44
+ <% @order.shipments.group_by { |s| s.selected_shipping_rate.try(:name) }.each do |name, shipments| %>
45
+ <tr>
46
+ <td></td>
47
+ <td><%= t('spree.shipping') %> <%= name %>:</td>
48
+ <td><%= Spree::Money.new(shipments.sum(&:total_before_tax), currency: @order.currency) %></td>
49
+ </tr>
50
+ <% end %>
51
+ <% if @order.all_adjustments.eligible.tax.exists? %>
52
+ <% @order.all_adjustments.eligible.tax.group_by(&:label).each do |label, adjustments| %>
53
+ <tr>
54
+ <td></td>
55
+ <td><%= t('spree.tax') %> <%= label %>:</td>
56
+ <td><%= Spree::Money.new(adjustments.sum(&:amount), currency: @order.currency) %></td>
57
+ </tr>
58
+ <% end %>
59
+ <% end %>
60
+ <% @order.adjustments.eligible.each do |adjustment| %>
61
+ <% next if (adjustment.source_type == 'Spree::TaxRate') and (adjustment.amount == 0) %>
62
+ <tr>
63
+ <td></td>
64
+ <td><%= adjustment.label %>:</td>
65
+ <td><%= adjustment.display_amount %></td>
66
+ </tr>
67
+ <% end %>
68
+ <tr>
69
+ <td></td>
70
+ <td>
71
+ <%= t('.total') %>
72
+ </td>
73
+ <td>
74
+ <%= @order.display_total %>
75
+ </td>
76
+ </tr>
77
+ </table>
78
+ <p>
79
+ <%= t('.thanks') %>
80
+ </p>
81
+ </td>
82
+ <td class="expander"></td>
83
+ </tr>
84
+ </table>
@@ -0,0 +1,38 @@
1
+ <%= t('.dear_customer') %>
2
+
3
+ <%= t('.instructions') %>
4
+
5
+ ============================================================
6
+ <%= t('.order_summary') %>
7
+ ============================================================
8
+ <% @order.line_items.each do |item| %>
9
+ <%= item.variant.sku %> <%= raw(item.variant.product.name) %> <%= raw(item.variant.options_text) -%> (<%=item.quantity%>) @ <%= item.single_money %> = <%= item.display_amount %>
10
+ <% end %>
11
+ ============================================================
12
+ <%= t('.subtotal') %> <%= @order.display_item_total %>
13
+ <% if @order.line_item_adjustments.exists? %>
14
+ <% if @order.all_adjustments.promotion.eligible.exists? %>
15
+ <% @order.all_adjustments.promotion.eligible.group_by(&:label).each do |label, adjustments| %>
16
+ <%= t('spree.promotion') %>: <%= label %> <%= Spree::Money.new(adjustments.sum(&:amount), currency: @order.currency) %>
17
+ <% end %>
18
+ <% end %>
19
+ <% end %>
20
+
21
+ <% @order.shipments.group_by { |s| s.selected_shipping_rate.try(:name) }.each do |name, shipments| %>
22
+ <%= t('spree.shipping') %>: <%= name %> <%= Spree::Money.new(shipments.sum(&:total_before_tax), currency: @order.currency) %>
23
+ <% end %>
24
+
25
+ <% if @order.all_adjustments.eligible.tax.exists? %>
26
+ <% @order.all_adjustments.eligible.tax.group_by(&:label).each do |label, adjustments| %>
27
+ <%= t('spree.tax') %>: <%= label %> <%= Spree::Money.new(adjustments.sum(&:amount), currency: @order.currency) %>
28
+ <% end %>
29
+ <% end %>
30
+
31
+ <% @order.adjustments.eligible.each do |adjustment| %>
32
+ <% next if (adjustment.source_type == 'Spree::TaxRate') and (adjustment.amount == 0) %>
33
+ <%= adjustment.label %> <%= adjustment.display_amount %>
34
+ <% end %>
35
+ ============================================================
36
+ <%= t('.total') %> <%= @order.display_total %>
37
+
38
+ <%= t('.thanks') %>
@@ -0,0 +1,2 @@
1
+ <%= t(".message", error: @promotion_code_batch.error) %>
2
+ <%= @promotion_code_batch.promotion.name %>
@@ -0,0 +1,2 @@
1
+ <%= t(".message", number_of_codes: 10) %>
2
+ <%= @promotion_code_batch.promotion.name %>
data/bin/rails ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+ # This command will automatically be run when you run "rails" with Rails gems
3
+ # installed from the root of your application.
4
+
5
+ ENGINE_ROOT = File.expand_path('..', __dir__)
6
+ ENGINE_PATH = File.expand_path('../lib/solidus_legacy_promotions/engine', __dir__)
7
+
8
+ # Set up gems listed in the Gemfile.
9
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __dir__)
10
+ require "bundler/setup" if File.exist?(ENV["BUNDLE_GEMFILE"])
11
+
12
+ require "rails/all"
13
+ require "rails/engine/commands"
@@ -0,0 +1,101 @@
1
+ en:
2
+ spree:
3
+ admin:
4
+ promotion_status:
5
+ active: Active
6
+ expired: Expired
7
+ inactive: Inactive
8
+ not_started: Not started
9
+ promotions:
10
+ actions:
11
+ calculator_label: Calculated by
12
+ activations_edit:
13
+ auto: All orders will attempt to use this promotion
14
+ multiple_codes_html: This promotion uses %{count} promotion codes
15
+ single_code_html: 'This promotion uses the promotion code: <code>%{code}</code>'
16
+ activations_new:
17
+ auto: Apply to all orders
18
+ multiple_codes: Multiple promotion codes
19
+ single_code: Single promotion code
20
+ form:
21
+ activation: Activation
22
+ expires_at_placeholder: Never
23
+ general: General
24
+ starts_at_placeholder: Immediately
25
+ tab:
26
+ promotion_categories: Promotion Categories
27
+ promotions: Promotions
28
+ back_to_promotion_categories_list: Back To Promotions Categories List
29
+ back_to_promotions_list: Back To Promotions List
30
+ base_amount: Base Amount
31
+ choose_promotion_action: Choose Action
32
+ choose_promotion_rule: Choose Rule
33
+ create_promotion_code: Create promotion code
34
+ current_promotion_usage: 'Current Usage: %{count}'
35
+ discount_rules: Discount Rules
36
+ download_promotion_codes_list: Download codes list
37
+ editing_promotion: Editing Promotion
38
+ editing_promotion_category: Editing Promotion Category
39
+ group_size: Group size
40
+ hints:
41
+ spree/calculator:
42
+ promotions: This is used to determine the promotional discount to be applied to an order, an item, or shipping charges.
43
+ spree/promotion:
44
+ expires_at: This determines when the promotion expires. <br> If no value is specified, the promotion will never expire.
45
+ 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.
46
+ starts_at: This determines when the promotion can be applied to orders. <br> If no value is specified, the promotion will be immediately available.
47
+ invalid_promotion_action: Invalid promotion action.
48
+ invalid_promotion_rule: Invalid promotion rule.
49
+ manage_promotion_categories: Manage Promotion Categories
50
+ match_choices:
51
+ all: All
52
+ none: None
53
+ new_promotion: New Promotion
54
+ new_promotion_category: New Promotion Category
55
+ new_promotion_code_batch: New Promotion Code Batch
56
+ no_actions_added: No actions added
57
+ no_rules_added: No rules added
58
+ no_promotions_found: No promotions found
59
+ product_rule:
60
+ choose_products: Choose products
61
+ label: Order must contain %{select} of these products
62
+ match_all: all
63
+ match_any: at least one
64
+ match_none: none
65
+ product_source:
66
+ group: From product group
67
+ manual: Manually choose
68
+ promotion: Promotion
69
+ promotion_action: Promotion Action
70
+ promotion_actions: Actions
71
+ promotion_form:
72
+ match_policies:
73
+ all: Match all of these rules
74
+ any: Match any of these rules
75
+ promotion_rule: Promotion Rule
76
+ promotion_successfully_created: Promotion has been successfully created!
77
+ promotion_uses: Promotion uses
78
+ promotionable: Promotable
79
+ promotions: Promotions
80
+ rules: Rules
81
+ store_rule:
82
+ choose_stores: Choose Stores
83
+ taxon_rule:
84
+ choose_taxons: Choose taxons
85
+ label: Order must contain %{select} of these taxons
86
+ match_all: all
87
+ match_any: at least one
88
+ match_none: none
89
+ tiers: Tiers
90
+ promotion_code_batches:
91
+ errored: 'Errored: %{error}'
92
+ finished: All %{number_of_codes} codes have been created.
93
+ processing: 'Processing: %{number_of_codes_processed} / %{number_of_codes}'
94
+ user_role_rule:
95
+ choose_roles: Choose Roles
96
+ label: User must contain %{select} of these roles
97
+ match_all: all
98
+ match_any: at least one
99
+ user_rule:
100
+ choose_users: Choose Users
101
+ view_promotion_codes_list: View codes list
@@ -0,0 +1,6 @@
1
+ en:
2
+ solidus_admin:
3
+ promotion_categories:
4
+ title: "Promotion Categories"
5
+ destroy:
6
+ success: "Promotion Categories were successfully removed."
@@ -0,0 +1,6 @@
1
+ en:
2
+ solidus_admin:
3
+ promotions:
4
+ title: "Promotions"
5
+ destroy:
6
+ success: "Promotions were successfully removed."
data/config/routes.rb ADDED
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ if SolidusSupport.backend_available?
4
+ Spree::Core::Engine.routes.draw do
5
+ namespace :admin do
6
+ resources :promotions do
7
+ resources :promotion_rules
8
+ resources :promotion_actions
9
+ resources :promotion_codes, only: [:index, :new, :create]
10
+ resources :promotion_code_batches, only: [:index, :new, :create] do
11
+ get '/download', to: "promotion_code_batches#download", defaults: { format: "csv" }
12
+ end
13
+ end
14
+
15
+ resources :promotion_categories, except: [:show]
16
+ end
17
+ end
18
+ end
19
+
20
+ if SolidusSupport.admin_available?
21
+ SolidusAdmin::Engine.routes.draw do
22
+ require "solidus_admin/admin_resources"
23
+ extend SolidusAdmin::AdminResources
24
+
25
+ admin_resources :promotions, only: [:index, :destroy]
26
+ admin_resources :promotion_categories, only: [:index, :destroy]
27
+ end
28
+ end