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,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Spree::Admin::PromotionActionsController < Spree::Admin::BaseController
4
+ before_action :load_promotion, only: [:create, :destroy]
5
+ before_action :validate_promotion_action_type, only: :create
6
+
7
+ def create
8
+ @promotion_action = @promotion_action_type.new(params[:promotion_action])
9
+ @calculators = @promotion_action.available_calculators
10
+ @promotion_action.promotion = @promotion
11
+ if @promotion_action.save
12
+ flash[:success] = t('spree.successfully_created', resource: t('spree.promotion_action'))
13
+ end
14
+ respond_to do |format|
15
+ format.html { redirect_to spree.edit_admin_promotion_path(@promotion) }
16
+ format.js { render layout: false }
17
+ end
18
+ end
19
+
20
+ def destroy
21
+ @promotion_action = @promotion.promotion_actions.find(params[:id])
22
+ if @promotion_action.discard
23
+ flash[:success] = t('spree.successfully_removed', resource: t('spree.promotion_action'))
24
+ end
25
+ respond_to do |format|
26
+ format.html { redirect_to spree.edit_admin_promotion_path(@promotion) }
27
+ format.js { render layout: false }
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def load_promotion
34
+ @promotion = Spree::Promotion.find(params[:promotion_id])
35
+ end
36
+
37
+ def validate_promotion_action_type
38
+ requested_type = params[:action_type]
39
+ promotion_action_types = Spree::Config.promotions.actions
40
+ @promotion_action_type = promotion_action_types.detect do |klass|
41
+ klass.name == requested_type
42
+ end
43
+ if !@promotion_action_type
44
+ flash[:error] = t('spree.invalid_promotion_action')
45
+ respond_to do |format|
46
+ format.html { redirect_to spree.edit_admin_promotion_path(@promotion) }
47
+ format.js { render layout: false }
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spree
4
+ module Admin
5
+ class PromotionCategoriesController < ResourceController
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spree
4
+ module Admin
5
+ class PromotionCodeBatchesController < ResourceController
6
+ belongs_to 'spree/promotion'
7
+
8
+ create.after :build_promotion_code_batch
9
+
10
+ def download
11
+ require "csv"
12
+
13
+ @promotion_code_batch = Spree::PromotionCodeBatch.find(
14
+ params[:promotion_code_batch_id]
15
+ )
16
+
17
+ send_data(
18
+ render_to_string,
19
+ filename: "promotion-code-batch-list-#{@promotion_code_batch.id}.csv"
20
+ )
21
+ end
22
+
23
+ private
24
+
25
+ def build_promotion_code_batch
26
+ @promotion_code_batch.process
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'csv'
4
+
5
+ module Spree
6
+ module Admin
7
+ class PromotionCodesController < Spree::Admin::ResourceController
8
+ def index
9
+ @promotion = Spree::Promotion.accessible_by(current_ability, :show).find(params[:promotion_id])
10
+ @promotion_codes = @promotion.promotion_codes.order(:value)
11
+
12
+ respond_to do |format|
13
+ format.html do
14
+ @promotion_codes = @promotion_codes.page(params[:page]).per(50)
15
+ end
16
+ format.csv do
17
+ filename = "promotion-code-list-#{@promotion.id}.csv"
18
+ headers["Content-Type"] = "text/csv"
19
+ headers["Content-disposition"] = "attachment; filename=\"#{filename}\""
20
+ end
21
+ end
22
+ end
23
+
24
+ def new
25
+ @promotion = Spree::Promotion.accessible_by(current_ability, :show).find(params[:promotion_id])
26
+ if @promotion.apply_automatically
27
+ flash[:error] = t('activerecord.errors.models.spree/promotion_code.attributes.base.disallowed_with_apply_automatically')
28
+ redirect_to admin_promotion_promotion_codes_url(@promotion)
29
+ else
30
+ @promotion_code = @promotion.promotion_codes.build
31
+ end
32
+ end
33
+
34
+ def create
35
+ @promotion = Spree::Promotion.accessible_by(current_ability, :show).find(params[:promotion_id])
36
+ @promotion_code = @promotion.promotion_codes.build(value: params[:promotion_code][:value])
37
+
38
+ if @promotion_code.save
39
+ flash[:success] = flash_message_for(@promotion_code, :successfully_created)
40
+ redirect_to admin_promotion_promotion_codes_url(@promotion)
41
+ else
42
+ flash.now[:error] = @promotion_code.errors.full_messages.to_sentence
43
+ render_after_create_error
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Spree::Admin::PromotionRulesController < Spree::Admin::BaseController
4
+ helper 'spree/promotion_rules'
5
+
6
+ before_action :load_promotion, only: [:create, :destroy]
7
+ before_action :validate_promotion_rule_type, only: :create
8
+
9
+ def create
10
+ @promotion_rule = @promotion_rule_type.new(promotion_rule_params)
11
+ @promotion_rule.promotion = @promotion
12
+ if @promotion_rule.save
13
+ flash[:success] = t('spree.successfully_created', resource: t('spree.promotion_rule'))
14
+ end
15
+ respond_to do |format|
16
+ format.html { redirect_to spree.edit_admin_promotion_path(@promotion) }
17
+ format.js { render layout: false }
18
+ end
19
+ end
20
+
21
+ def destroy
22
+ @promotion_rule = @promotion.promotion_rules.find(params[:id])
23
+ if @promotion_rule.destroy
24
+ flash[:success] = t('spree.successfully_removed', resource: t('spree.promotion_rule'))
25
+ end
26
+ respond_to do |format|
27
+ format.html { redirect_to spree.edit_admin_promotion_path(@promotion) }
28
+ format.js { render layout: false }
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def load_promotion
35
+ @promotion = Spree::Promotion.find(params[:promotion_id])
36
+ end
37
+
38
+ def model_class
39
+ Spree::PromotionRule
40
+ end
41
+
42
+ def validate_promotion_rule_type
43
+ requested_type = params[:promotion_rule].delete(:type)
44
+ promotion_rule_types = Spree::Config.promotions.rules
45
+ @promotion_rule_type = promotion_rule_types.detect do |klass|
46
+ klass.name == requested_type
47
+ end
48
+ if !@promotion_rule_type
49
+ flash[:error] = t('spree.invalid_promotion_rule')
50
+ respond_to do |format|
51
+ format.html { redirect_to spree.edit_admin_promotion_path(@promotion) }
52
+ format.js { render layout: false }
53
+ end
54
+ end
55
+ end
56
+
57
+ def promotion_rule_params
58
+ params[:promotion_rule].permit!
59
+ end
60
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spree
4
+ module Admin
5
+ class PromotionsController < ResourceController
6
+ before_action :load_data
7
+
8
+ helper 'spree/promotion_rules'
9
+
10
+ def show
11
+ redirect_to action: :edit
12
+ end
13
+
14
+ def create
15
+ @promotion = Spree::Promotion.new(permitted_resource_params)
16
+ @promotion.codes.new(value: params[:single_code]) if params[:single_code].present?
17
+
18
+ if params[:promotion_code_batch]
19
+ @promotion_code_batch = @promotion.promotion_code_batches.new(promotion_code_batch_params)
20
+ end
21
+
22
+ if @promotion.save
23
+ @promotion_code_batch.process if @promotion_code_batch
24
+ flash[:success] = t('spree.promotion_successfully_created')
25
+ redirect_to location_after_save
26
+ else
27
+ flash[:error] = @promotion.errors.full_messages.to_sentence
28
+ render action: 'new'
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def location_after_save
35
+ spree.edit_admin_promotion_url(@promotion)
36
+ end
37
+
38
+ def load_data
39
+ @promotion_categories = Spree::PromotionCategory.order(:name)
40
+ end
41
+
42
+ def collection
43
+ return @collection if @collection
44
+ params[:q] ||= HashWithIndifferentAccess.new
45
+ params[:q][:s] ||= 'id desc'
46
+
47
+ @collection = super
48
+ @search = @collection.ransack(params[:q])
49
+ @collection = @search.result(distinct: true).
50
+ includes(promotion_includes).
51
+ page(params[:page]).
52
+ per(params[:per_page] || Spree::Config.promotions.promotions_per_page)
53
+
54
+ @collection
55
+ end
56
+
57
+ def promotion_code_batch_params
58
+ params.require(:promotion_code_batch).permit!
59
+ end
60
+
61
+ def promotion_includes
62
+ [:promotion_actions]
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusLegacyPromotions
4
+ class Configuration < Spree::Preferences::Configuration
5
+ include Spree::Core::EnvironmentExtension
6
+
7
+ # @!attribute [rw] promotions_per_page
8
+ # @return [Integer] Promotions to show per-page in the admin (default: +15+)
9
+ preference :promotions_per_page, :integer, default: 15
10
+
11
+ # @!attribute [rw] promotion_attributes
12
+ # @return [Array<Symbol>] Attributes to be returned by the API for a promotion
13
+ preference :promotion_api_attributes, :array, default: [
14
+ :id,
15
+ :name,
16
+ :description,
17
+ :expires_at,
18
+ :starts_at,
19
+ :type,
20
+ :usage_limit,
21
+ :advertise,
22
+ :path
23
+ ]
24
+
25
+ # promotion_chooser_class allows extensions to provide their own PromotionChooser
26
+ class_name_attribute :promotion_chooser_class, default: 'Spree::PromotionChooser'
27
+
28
+ # order_adjuster_class allows extensions to provide their own Order Adjuster
29
+ class_name_attribute :order_adjuster_class, default: 'Spree::Promotion::OrderAdjustmentsRecalculator'
30
+
31
+ # promotion_finder_class allows extensions to provide their own Promotion Finder
32
+ class_name_attribute :promotion_finder_class, default: 'Spree::PromotionFinder'
33
+
34
+ # Allows providing a different shipping promotion handler.
35
+ # @!attribute [rw] shipping_promotion_handler_class
36
+ # @see Spree::PromotionHandler::Shipping
37
+ # @return [Class] an object that conforms to the API of
38
+ # the standard shipping promotion handler class
39
+ # Spree::PromotionHandler::Coupon.
40
+ class_name_attribute :shipping_promotion_handler_class, default: 'Spree::PromotionHandler::Shipping'
41
+
42
+ # Allows providing your own Mailer for promotion code batch mailer.
43
+ #
44
+ # @!attribute [rw] promotion_code_batch_mailer_class
45
+ # @return [ActionMailer::Base] an object that responds to "promotion_code_batch_finished",
46
+ # and "promotion_code_batch_errored"
47
+ # (e.g. an ActionMailer with a "promotion_code_batch_finished" method) with the same
48
+ # signature as Spree::PromotionCodeBatchMailer.promotion_code_batch_finished.
49
+ class_name_attribute :promotion_code_batch_mailer_class, default: 'Spree::PromotionCodeBatchMailer'
50
+
51
+ # Allows providing a different coupon code handler.
52
+ # @!attribute [rw] coupon_code_handler_class
53
+ # @see Spree::PromotionHandler::Coupon
54
+ # @return [Class] an object that conforms to the API of
55
+ # the standard coupon code handler class
56
+ # Spree::PromotionHandler::Coupon.
57
+ class_name_attribute :coupon_code_handler_class, default: 'Spree::PromotionHandler::Coupon'
58
+
59
+ # Allows providing a different promotion advertiser.
60
+ # @!attribute [rw] advertiser_class
61
+ # @see Spree::PromotionAdvertiser
62
+ # @return [Class] an object that conforms to the API of
63
+ # the standard promotion advertiser class
64
+ # Spree::PromotionAdvertiser.
65
+ class_name_attribute :advertiser_class, default: 'Spree::PromotionAdvertiser'
66
+
67
+ add_class_set :rules, default: %w[
68
+ Spree::Promotion::Rules::ItemTotal
69
+ Spree::Promotion::Rules::Product
70
+ Spree::Promotion::Rules::User
71
+ Spree::Promotion::Rules::FirstOrder
72
+ Spree::Promotion::Rules::UserLoggedIn
73
+ Spree::Promotion::Rules::OneUsePerUser
74
+ Spree::Promotion::Rules::Taxon
75
+ Spree::Promotion::Rules::MinimumQuantity
76
+ Spree::Promotion::Rules::NthOrder
77
+ Spree::Promotion::Rules::OptionValue
78
+ Spree::Promotion::Rules::FirstRepeatPurchaseSince
79
+ Spree::Promotion::Rules::UserRole
80
+ Spree::Promotion::Rules::Store
81
+ ]
82
+
83
+ add_class_set :actions, default: %w[
84
+ Spree::Promotion::Actions::CreateAdjustment
85
+ Spree::Promotion::Actions::CreateItemAdjustments
86
+ Spree::Promotion::Actions::CreateQuantityAdjustments
87
+ Spree::Promotion::Actions::FreeShipping
88
+ ]
89
+
90
+ add_class_set :shipping_actions, default: %w[
91
+ Spree::Promotion::Actions::FreeShipping
92
+ ]
93
+
94
+ add_nested_class_set :calculators, default: {
95
+ "Spree::Promotion::Actions::CreateAdjustment" => %w[
96
+ Spree::Calculator::FlatPercentItemTotal
97
+ Spree::Calculator::FlatRate
98
+ Spree::Calculator::FlexiRate
99
+ Spree::Calculator::TieredPercent
100
+ Spree::Calculator::TieredFlatRate
101
+ ],
102
+ "Spree::Promotion::Actions::CreateItemAdjustments" => %w[
103
+ Spree::Calculator::DistributedAmount
104
+ Spree::Calculator::FlatRate
105
+ Spree::Calculator::FlexiRate
106
+ Spree::Calculator::PercentOnLineItem
107
+ Spree::Calculator::TieredPercent
108
+ ],
109
+ "Spree::Promotion::Actions::CreateQuantityAdjustments" => %w[
110
+ Spree::Calculator::PercentOnLineItem
111
+ Spree::Calculator::FlatRate
112
+ ]
113
+ }
114
+ end
115
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'solidus_legacy_promotions'
4
+
5
+ module SolidusLegacyPromotions
6
+ class Engine < ::Rails::Engine
7
+ include SolidusSupport::EngineExtensions
8
+
9
+ initializer "solidus_legacy_promotions.add_backend_menu_item" do
10
+ if SolidusSupport.backend_available?
11
+ promotions_menu_item = Spree::BackendConfiguration::MenuItem.new(
12
+ label: :promotions,
13
+ icon: Spree::Backend::Config.admin_updated_navbar ? "ri-megaphone-line" : "bullhorn",
14
+ partial: "spree/admin/shared/promotion_sub_menu",
15
+ condition: -> { can?(:admin, Spree::Promotion) },
16
+ url: :admin_promotions_path,
17
+ data_hook: :admin_promotion_sub_tabs,
18
+ children: [
19
+ Spree::BackendConfiguration::MenuItem.new(
20
+ label: :promotions,
21
+ condition: -> { can?(:admin, Spree::Promotion) }
22
+ ),
23
+ Spree::BackendConfiguration::MenuItem.new(
24
+ label: :promotion_categories,
25
+ condition: -> { can?(:admin, Spree::PromotionCategory) }
26
+ )
27
+ ]
28
+ )
29
+ product_menu_item_index = Spree::Backend::Config.menu_items.find_index { |item| item.label == :products }
30
+ Spree::Backend::Config.menu_items.insert(product_menu_item_index + 1, promotions_menu_item)
31
+ end
32
+ end
33
+
34
+ initializer "solidus_legacy_promotions.add_admin_order_index_component" do
35
+ if SolidusSupport.admin_available?
36
+ SolidusAdmin::Config.components["orders/index"] = "SolidusLegacyPromotions::Orders::Index::Component"
37
+ end
38
+ end
39
+
40
+ initializer "solidus_legacy_promotions.add_solidus_admin_menu_items" do
41
+ if SolidusSupport.admin_available?
42
+ SolidusAdmin::Config.configure do |config|
43
+ config.menu_items << {
44
+ key: "promotions",
45
+ route: -> { spree.admin_promotions_path },
46
+ icon: "megaphone-line",
47
+ position: 30
48
+ }
49
+ end
50
+ end
51
+ end
52
+
53
+ initializer "solidus_legacy_promotions.add_order_search_field" do
54
+ if SolidusSupport.backend_available?
55
+ email_field_index = Spree::Backend::Config.search_fields["spree/admin/orders"].find_index do |field|
56
+ field.dig(:locals, :ransack) == :email_start
57
+ end
58
+ Spree::Backend::Config.search_fields["spree/admin/orders"].insert(email_field_index + 1, {
59
+ partial: "spree/admin/shared/search_fields/text_field",
60
+ locals: {
61
+ ransack: :order_promotions_promotion_code_value_start,
62
+ label: -> { I18n.t(:promotion, scope: :spree) }
63
+ }
64
+ })
65
+ end
66
+ end
67
+
68
+ initializer 'solidus_legacy_promotions.core.pub_sub', after: 'spree.core.pub_sub' do |app|
69
+ app.reloader.to_prepare do
70
+ Spree::OrderPromotionSubscriber.new.subscribe_to(Spree::Bus)
71
+ end
72
+ end
73
+
74
+ initializer "solidus_legacy_promotions.assets" do |app|
75
+ app.config.assets.precompile << "solidus_legacy_promotions/manifest.js"
76
+ end
77
+
78
+ initializer "solidus_legacy_promotions.add_factories_to_core" do
79
+ if Rails.env.test?
80
+ require "spree/testing_support/factory_bot"
81
+ require "solidus_legacy_promotions/testing_support/factory_bot"
82
+ Spree::TestingSupport::FactoryBot.definition_file_paths.concat(SolidusLegacyPromotions::TestingSupport::FactoryBot.definition_file_paths)
83
+ end
84
+ rescue LoadError
85
+ # FactoryBot is not available, we don't need factories
86
+ end
87
+
88
+ initializer "solidus_legacy_promotions", after: "spree.load_config_initializers" do
89
+ Spree::Config.order_contents_class = "Spree::OrderContents"
90
+ Spree::Config.promotions = SolidusLegacyPromotions::Configuration.new
91
+ Spree::Config.adjustment_promotion_source_types << "Spree::PromotionAction"
92
+
93
+ Spree::Api::Config.adjustment_attributes << :promotion_code_id
94
+ Spree::Api::Config.adjustment_attributes << :eligible
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spree/core'
4
+
5
+ module SolidusLegacyPromotions
6
+ module Migrations
7
+ module PromotionWithCodeHandlers
8
+ class PromotionCode < ::Spree::Base
9
+ self.table_name = "spree_promotion_codes"
10
+ end
11
+
12
+ class Base
13
+ attr_reader :migration_context, :promotions
14
+
15
+ def initialize(migration_context, promotions)
16
+ @migration_context = migration_context
17
+ @promotions = promotions
18
+ end
19
+ end
20
+
21
+ class RaiseException < Base
22
+ def call
23
+ # Please note that this will block the current migration and rollback all
24
+ # the previous ones run with the same "rails db:migrate" command.
25
+ #
26
+ raise StandardError, "You are trying to drop 'code' column from "\
27
+ "spree_promotions table but you have at least one record with that "\
28
+ "column filled. Please take care of that or you could lose data. See:" \
29
+ "\n" \
30
+ "https://github.com/solidusio/solidus/pull/3028"\
31
+ "\n"
32
+ end
33
+ end
34
+
35
+ class MoveToSpreePromotionCode < Base
36
+ def call
37
+ # This is another possible approach, it will convert Spree::Promotion#code
38
+ # to a Spree::PromotionCode before removing the `code` field.
39
+ #
40
+ # NOTE: promotion codes will be downcased and stripped
41
+ promotions.find_each do |promotion|
42
+ normalized_code = promotion.code.downcase.strip
43
+
44
+ PromotionCode.find_or_create_by!(
45
+ value: normalized_code,
46
+ promotion_id: promotion.id
47
+ ) do
48
+ migration_context.say "Creating Spree::PromotionCode with value "\
49
+ "'#{normalized_code}' for Spree::Promotion with id '#{promotion.id}'"
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ class DoNothing < Base
56
+ def call
57
+ # This approach will delete all codes without taking any action. At
58
+ # least we could print a message to track what we are deleting.
59
+ #
60
+ promotions.find_each do |promotion|
61
+ migration_context.say "Code '#{promotion.code}' is going to be removed "\
62
+ "from Spree::Promotion with id '#{promotion.id}'"
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ FactoryBot.define do
4
+ factory :percent_on_item_calculator, class: 'Spree::Calculator::PercentOnLineItem' do
5
+ preferred_percent { 10 }
6
+ end
7
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ FactoryBot.define do
4
+ factory :completed_order_with_promotion, parent: :order_with_line_items do
5
+ transient do
6
+ completed_at { Time.current }
7
+ promotion { nil }
8
+ end
9
+
10
+ after(:create) do |order, evaluator|
11
+ promotion = evaluator.promotion || create(:promotion, code: "test")
12
+ promotion_code = promotion.codes.first || create(:promotion_code, promotion:)
13
+
14
+ promotion.activate(order:, promotion_code:)
15
+ order.order_promotions.create!(promotion:, promotion_code:)
16
+
17
+ # Complete the order after the promotion has been activated
18
+ order.update_column(:completed_at, evaluator.completed_at)
19
+ order.update_column(:state, "complete")
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ FactoryBot.define do
4
+ factory :order_promotion, class: 'Spree::OrderPromotion' do
5
+ association :order
6
+ association :promotion
7
+ end
8
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ FactoryBot.define do
4
+ factory :promotion_category, class: 'Spree::PromotionCategory' do
5
+ name { 'Promotion Category' }
6
+ end
7
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ FactoryBot.define do
4
+ factory :promotion_code, class: 'Spree::PromotionCode' do
5
+ promotion
6
+ sequence(:value) { |i| "code#{i}" }
7
+ end
8
+ end