solidus_promotions 4.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (234) hide show
  1. checksums.yaml +7 -0
  2. data/.eslintrc.json +10 -0
  3. data/.github/stale.yml +1 -0
  4. data/MIGRATING.md +184 -0
  5. data/README.md +122 -0
  6. data/Rakefile +20 -0
  7. data/app/assets/config/solidus_promotions/manifest.js +13 -0
  8. data/app/decorators/models/solidus_promotions/adjustment_decorator.rb +11 -0
  9. data/app/decorators/models/solidus_promotions/line_item_decorator.rb +27 -0
  10. data/app/decorators/models/solidus_promotions/order_decorator.rb +42 -0
  11. data/app/decorators/models/solidus_promotions/order_recalculator_decorator.rb +15 -0
  12. data/app/decorators/models/solidus_promotions/shipment_decorator.rb +14 -0
  13. data/app/decorators/models/solidus_promotions/shipping_rate_decorator.rb +29 -0
  14. data/app/helpers/solidus_promotions/admin/benefits_helper.rb +19 -0
  15. data/app/helpers/solidus_promotions/admin/conditions_helper.rb +14 -0
  16. data/app/helpers/solidus_promotions/admin/promotions_helper.rb +15 -0
  17. data/app/javascript/backend/solidus_promotions/controllers/application.js +9 -0
  18. data/app/javascript/backend/solidus_promotions/controllers/calculator_tiers_controller.js +37 -0
  19. data/app/javascript/backend/solidus_promotions/controllers/flash_controller.js +10 -0
  20. data/app/javascript/backend/solidus_promotions/controllers/index.js +8 -0
  21. data/app/javascript/backend/solidus_promotions/controllers/product_option_values_controller.js +62 -0
  22. data/app/javascript/backend/solidus_promotions/web_components/number_with_currency.js +35 -0
  23. data/app/javascript/backend/solidus_promotions/web_components/option_value_picker.js +52 -0
  24. data/app/javascript/backend/solidus_promotions/web_components/product_picker.js +7 -0
  25. data/app/javascript/backend/solidus_promotions/web_components/select_two.js +11 -0
  26. data/app/javascript/backend/solidus_promotions/web_components/taxon_picker.js +7 -0
  27. data/app/javascript/backend/solidus_promotions/web_components/user_picker.js +7 -0
  28. data/app/javascript/backend/solidus_promotions/web_components/variant_picker.js +7 -0
  29. data/app/javascript/backend/solidus_promotions.js +11 -0
  30. data/app/jobs/solidus_promotions/promotion_code_batch_job.rb +26 -0
  31. data/app/mailers/solidus_promotions/promotion_code_batch_mailer.rb +15 -0
  32. data/app/models/concerns/solidus_promotions/benefits/line_item_benefit.rb +15 -0
  33. data/app/models/concerns/solidus_promotions/benefits/order_benefit.rb +15 -0
  34. data/app/models/concerns/solidus_promotions/benefits/shipment_benefit.rb +15 -0
  35. data/app/models/concerns/solidus_promotions/calculators/promotion_calculator.rb +11 -0
  36. data/app/models/concerns/solidus_promotions/conditions/line_item_applicable_order_level_condition.rb +23 -0
  37. data/app/models/concerns/solidus_promotions/conditions/line_item_level_condition.rb +15 -0
  38. data/app/models/concerns/solidus_promotions/conditions/order_level_condition.rb +15 -0
  39. data/app/models/concerns/solidus_promotions/conditions/shipment_level_condition.rb +15 -0
  40. data/app/models/concerns/solidus_promotions/discountable_amount.rb +21 -0
  41. data/app/models/solidus_promotions/benefit.rb +123 -0
  42. data/app/models/solidus_promotions/benefits/adjust_line_item.rb +13 -0
  43. data/app/models/solidus_promotions/benefits/adjust_line_item_quantity_groups.rb +107 -0
  44. data/app/models/solidus_promotions/benefits/adjust_shipment.rb +13 -0
  45. data/app/models/solidus_promotions/benefits/create_discounted_item.rb +49 -0
  46. data/app/models/solidus_promotions/calculators/distributed_amount.rb +31 -0
  47. data/app/models/solidus_promotions/calculators/flat_rate.rb +23 -0
  48. data/app/models/solidus_promotions/calculators/flexi_rate.rb +26 -0
  49. data/app/models/solidus_promotions/calculators/percent.rb +19 -0
  50. data/app/models/solidus_promotions/calculators/tiered_flat_rate.rb +58 -0
  51. data/app/models/solidus_promotions/calculators/tiered_percent.rb +68 -0
  52. data/app/models/solidus_promotions/calculators/tiered_percent_on_eligible_item_quantity.rb +40 -0
  53. data/app/models/solidus_promotions/condition.rb +61 -0
  54. data/app/models/solidus_promotions/condition_product.rb +8 -0
  55. data/app/models/solidus_promotions/condition_store.rb +8 -0
  56. data/app/models/solidus_promotions/condition_taxon.rb +8 -0
  57. data/app/models/solidus_promotions/condition_user.rb +8 -0
  58. data/app/models/solidus_promotions/conditions/discounted_item_total.rb +22 -0
  59. data/app/models/solidus_promotions/conditions/first_order.rb +31 -0
  60. data/app/models/solidus_promotions/conditions/first_repeat_purchase_since.rb +31 -0
  61. data/app/models/solidus_promotions/conditions/item_total.rb +75 -0
  62. data/app/models/solidus_promotions/conditions/line_item_option_value.rb +37 -0
  63. data/app/models/solidus_promotions/conditions/line_item_product.rb +56 -0
  64. data/app/models/solidus_promotions/conditions/line_item_taxon.rb +59 -0
  65. data/app/models/solidus_promotions/conditions/minimum_quantity.rb +42 -0
  66. data/app/models/solidus_promotions/conditions/nth_order.rb +40 -0
  67. data/app/models/solidus_promotions/conditions/one_use_per_user.rb +25 -0
  68. data/app/models/solidus_promotions/conditions/option_value.rb +28 -0
  69. data/app/models/solidus_promotions/conditions/product.rb +85 -0
  70. data/app/models/solidus_promotions/conditions/shipping_method.rb +19 -0
  71. data/app/models/solidus_promotions/conditions/store.rb +26 -0
  72. data/app/models/solidus_promotions/conditions/taxon.rb +98 -0
  73. data/app/models/solidus_promotions/conditions/user.rb +35 -0
  74. data/app/models/solidus_promotions/conditions/user_logged_in.rb +16 -0
  75. data/app/models/solidus_promotions/conditions/user_role.rb +42 -0
  76. data/app/models/solidus_promotions/distributed_amounts_handler.rb +39 -0
  77. data/app/models/solidus_promotions/eligibility_result.rb +5 -0
  78. data/app/models/solidus_promotions/eligibility_results.rb +47 -0
  79. data/app/models/solidus_promotions/item_discount.rb +21 -0
  80. data/app/models/solidus_promotions/migration_support/order_promotion_syncer.rb +54 -0
  81. data/app/models/solidus_promotions/order_adjuster/choose_discounts.rb +21 -0
  82. data/app/models/solidus_promotions/order_adjuster/discount_order.rb +93 -0
  83. data/app/models/solidus_promotions/order_adjuster/load_promotions.rb +53 -0
  84. data/app/models/solidus_promotions/order_adjuster/persist_discounted_order.rb +79 -0
  85. data/app/models/solidus_promotions/order_adjuster.rb +33 -0
  86. data/app/models/solidus_promotions/order_promotion.rb +23 -0
  87. data/app/models/solidus_promotions/permission_sets/solidus_promotion_management.rb +15 -0
  88. data/app/models/solidus_promotions/promotion.rb +163 -0
  89. data/app/models/solidus_promotions/promotion_advertiser.rb +10 -0
  90. data/app/models/solidus_promotions/promotion_category.rb +9 -0
  91. data/app/models/solidus_promotions/promotion_code/batch_builder.rb +73 -0
  92. data/app/models/solidus_promotions/promotion_code.rb +56 -0
  93. data/app/models/solidus_promotions/promotion_code_batch.rb +25 -0
  94. data/app/models/solidus_promotions/promotion_finder.rb +10 -0
  95. data/app/models/solidus_promotions/promotion_handler/coupon.rb +113 -0
  96. data/app/models/solidus_promotions/promotion_handler/page.rb +30 -0
  97. data/app/models/solidus_promotions/shipping_rate_discount.rb +11 -0
  98. data/app/subscribers/solidus_promotions/order_promotion_subscriber.rb +20 -0
  99. data/bin/rails +13 -0
  100. data/config/importmap.rb +14 -0
  101. data/config/locales/en.yml +376 -0
  102. data/config/locales/promotion_categories.en.yml +6 -0
  103. data/config/locales/promotions.en.yml +6 -0
  104. data/config/routes.rb +38 -0
  105. data/db/migrate/20230703101637_create_promotions.rb +25 -0
  106. data/db/migrate/20230703113625_create_promotion_benefits.rb +15 -0
  107. data/db/migrate/20230703141116_create_promotion_categories.rb +14 -0
  108. data/db/migrate/20230703143943_create_promotion_conditions.rb +12 -0
  109. data/db/migrate/20230704083830_add_condition_join_tables.rb +31 -0
  110. data/db/migrate/20230704102444_create_promotion_codes.rb +11 -0
  111. data/db/migrate/20230704102656_create_promotion_code_batches.rb +34 -0
  112. data/db/migrate/20230705171556_create_order_promotions.rb +11 -0
  113. data/db/migrate/20230725074235_create_shipping_rate_discounts.rb +12 -0
  114. data/db/migrate/20231011100059_add_db_comments_to_order_promotions.rb +61 -0
  115. data/db/migrate/20231011120928_add_db_comments_to_condition_taxons.rb +54 -0
  116. data/db/migrate/20231011131324_add_db_comments_to_conditions.rb +60 -0
  117. data/db/migrate/20231011142040_add_db_comments_to_condition_users.rb +53 -0
  118. data/db/migrate/20231011155822_add_db_comments_to_promotions.rb +123 -0
  119. data/db/migrate/20231011163030_add_db_comments_to_promotion_codes.rb +60 -0
  120. data/db/migrate/20231011173312_add_db_comments_to_promotion_code_batches.rb +91 -0
  121. data/db/migrate/20231011184205_add_db_comments_to_condition_stores.rb +53 -0
  122. data/db/migrate/20231011190222_add_db_comments_to_benefits.rb +61 -0
  123. data/db/migrate/20231012020928_add_db_comments_to_condition_products.rb +52 -0
  124. data/db/migrate/20231012120928_add_db_comments_to_promotion_categories.rb +52 -0
  125. data/db/migrate/20231104135812_add_managed_by_order_benefit_to_line_items.rb +5 -0
  126. data/lib/components/admin/solidus_admin/orders/show/adjustments/index/source/solidus_promotions_benefit/component.rb +17 -0
  127. data/lib/components/admin/solidus_promotions/orders/index/component.rb +15 -0
  128. data/lib/components/admin/solidus_promotions/orders/index/component.yml +3 -0
  129. data/lib/components/admin/solidus_promotions/promotion_categories/index/component.rb +60 -0
  130. data/lib/components/admin/solidus_promotions/promotions/index/component.rb +108 -0
  131. data/lib/components/admin/solidus_promotions/promotions/index/component.yml +10 -0
  132. data/lib/controllers/admin/solidus_promotions/promotion_categories_controller.rb +29 -0
  133. data/lib/controllers/admin/solidus_promotions/promotions_controller.rb +46 -0
  134. data/lib/controllers/backend/solidus_promotions/admin/base_controller.rb +15 -0
  135. data/lib/controllers/backend/solidus_promotions/admin/benefits_controller.rb +85 -0
  136. data/lib/controllers/backend/solidus_promotions/admin/conditions_controller.rb +69 -0
  137. data/lib/controllers/backend/solidus_promotions/admin/promotion_categories_controller.rb +13 -0
  138. data/lib/controllers/backend/solidus_promotions/admin/promotion_code_batches_controller.rb +42 -0
  139. data/lib/controllers/backend/solidus_promotions/admin/promotion_codes_controller.rb +58 -0
  140. data/lib/controllers/backend/solidus_promotions/admin/promotions_controller.rb +65 -0
  141. data/lib/generators/solidus_promotions/install/install_generator.rb +56 -0
  142. data/lib/generators/solidus_promotions/install/templates/initializer.rb +6 -0
  143. data/lib/solidus_promotions/configuration.rb +125 -0
  144. data/lib/solidus_promotions/engine.rb +115 -0
  145. data/lib/solidus_promotions/migrate_adjustments.rb +62 -0
  146. data/lib/solidus_promotions/migrate_order_promotions.rb +73 -0
  147. data/lib/solidus_promotions/promotion_map.rb +126 -0
  148. data/lib/solidus_promotions/promotion_migrator.rb +103 -0
  149. data/lib/solidus_promotions/testing_support/factories/completed_order_with_solidus_promotion_factory.rb +20 -0
  150. data/lib/solidus_promotions/testing_support/factories/solidus_order_promotion_factory.rb +8 -0
  151. data/lib/solidus_promotions/testing_support/factories/solidus_promotion_category_factory.rb +7 -0
  152. data/lib/solidus_promotions/testing_support/factories/solidus_promotion_code_factory.rb +8 -0
  153. data/lib/solidus_promotions/testing_support/factories/solidus_promotion_factory.rb +65 -0
  154. data/lib/solidus_promotions/testing_support/factories/solidus_shipping_rate_discount_factory.rb +14 -0
  155. data/lib/solidus_promotions/testing_support/factory_bot.rb +30 -0
  156. data/lib/solidus_promotions.rb +37 -0
  157. data/lib/tasks/solidus_promotions/migrate_adjustments.rake +17 -0
  158. data/lib/tasks/solidus_promotions/migrate_existing_promotions.rake +12 -0
  159. data/lib/tasks/solidus_promotions/migrate_order_promotions.rake +17 -0
  160. data/lib/views/backend/solidus_promotions/admin/benefit_fields/_adjust_line_item.html.erb +6 -0
  161. data/lib/views/backend/solidus_promotions/admin/benefit_fields/_adjust_line_item_quantity_groups.html.erb +13 -0
  162. data/lib/views/backend/solidus_promotions/admin/benefit_fields/_adjust_shipment.html.erb +6 -0
  163. data/lib/views/backend/solidus_promotions/admin/benefit_fields/_calculator_fields.erb +11 -0
  164. data/lib/views/backend/solidus_promotions/admin/benefit_fields/_create_discounted_item.html.erb +21 -0
  165. data/lib/views/backend/solidus_promotions/admin/benefits/_benefit.html.erb +50 -0
  166. data/lib/views/backend/solidus_promotions/admin/benefits/_calculator_select.html.erb +16 -0
  167. data/lib/views/backend/solidus_promotions/admin/benefits/_form.html.erb +3 -0
  168. data/lib/views/backend/solidus_promotions/admin/benefits/_new_benefit.html.erb +33 -0
  169. data/lib/views/backend/solidus_promotions/admin/benefits/_type_select.html.erb +16 -0
  170. data/lib/views/backend/solidus_promotions/admin/benefits/edit.html.erb +19 -0
  171. data/lib/views/backend/solidus_promotions/admin/benefits/new.html.erb +1 -0
  172. data/lib/views/backend/solidus_promotions/admin/calculator_fields/_default_fields.html.erb +6 -0
  173. data/lib/views/backend/solidus_promotions/admin/calculator_fields/distributed_amount/_fields.html.erb +18 -0
  174. data/lib/views/backend/solidus_promotions/admin/calculator_fields/flat_rate/_fields.html.erb +6 -0
  175. data/lib/views/backend/solidus_promotions/admin/calculator_fields/flexi_rate/_fields.html.erb +24 -0
  176. data/lib/views/backend/solidus_promotions/admin/calculator_fields/percent/_fields.html.erb +6 -0
  177. data/lib/views/backend/solidus_promotions/admin/calculator_fields/tiered_flat_rate/_fields.html.erb +32 -0
  178. data/lib/views/backend/solidus_promotions/admin/calculator_fields/tiered_flat_rate/_tier_fields.html.erb +31 -0
  179. data/lib/views/backend/solidus_promotions/admin/calculator_fields/tiered_percent/_fields.html.erb +47 -0
  180. data/lib/views/backend/solidus_promotions/admin/calculator_fields/tiered_percent/_tier_fields.html.erb +33 -0
  181. data/lib/views/backend/solidus_promotions/admin/calculator_fields/tiered_percent_on_eligible_item_quantity/_fields.html.erb +36 -0
  182. data/lib/views/backend/solidus_promotions/admin/calculator_fields/tiered_percent_on_eligible_item_quantity/_tier_fields.html.erb +35 -0
  183. data/lib/views/backend/solidus_promotions/admin/condition_fields/_first_order.html.erb +3 -0
  184. data/lib/views/backend/solidus_promotions/admin/condition_fields/_first_repeat_purchase_since.html.erb +7 -0
  185. data/lib/views/backend/solidus_promotions/admin/condition_fields/_item_total.html.erb +15 -0
  186. data/lib/views/backend/solidus_promotions/admin/condition_fields/_line_item_option_value.html.erb +25 -0
  187. data/lib/views/backend/solidus_promotions/admin/condition_fields/_line_item_product.html.erb +21 -0
  188. data/lib/views/backend/solidus_promotions/admin/condition_fields/_line_item_taxon.html.erb +17 -0
  189. data/lib/views/backend/solidus_promotions/admin/condition_fields/_minimum_quantity.html.erb +5 -0
  190. data/lib/views/backend/solidus_promotions/admin/condition_fields/_nth_order.html.erb +15 -0
  191. data/lib/views/backend/solidus_promotions/admin/condition_fields/_one_use_per_user.html.erb +3 -0
  192. data/lib/views/backend/solidus_promotions/admin/condition_fields/_option_value.html.erb +63 -0
  193. data/lib/views/backend/solidus_promotions/admin/condition_fields/_product.html.erb +27 -0
  194. data/lib/views/backend/solidus_promotions/admin/condition_fields/_shipping_method.html.erb +10 -0
  195. data/lib/views/backend/solidus_promotions/admin/condition_fields/_store.html.erb +9 -0
  196. data/lib/views/backend/solidus_promotions/admin/condition_fields/_taxon.html.erb +27 -0
  197. data/lib/views/backend/solidus_promotions/admin/condition_fields/_user.html.erb +7 -0
  198. data/lib/views/backend/solidus_promotions/admin/condition_fields/_user_logged_in.html.erb +3 -0
  199. data/lib/views/backend/solidus_promotions/admin/condition_fields/_user_role.html.erb +15 -0
  200. data/lib/views/backend/solidus_promotions/admin/condition_fields/line_item_option_value/_option_value_fields.html.erb +21 -0
  201. data/lib/views/backend/solidus_promotions/admin/conditions/_condition.html.erb +26 -0
  202. data/lib/views/backend/solidus_promotions/admin/conditions/_new_condition.html.erb +26 -0
  203. data/lib/views/backend/solidus_promotions/admin/conditions/_type_select.html.erb +20 -0
  204. data/lib/views/backend/solidus_promotions/admin/conditions/new.html.erb +1 -0
  205. data/lib/views/backend/solidus_promotions/admin/promotion_categories/_form.html.erb +14 -0
  206. data/lib/views/backend/solidus_promotions/admin/promotion_categories/edit.html.erb +10 -0
  207. data/lib/views/backend/solidus_promotions/admin/promotion_categories/index.html.erb +47 -0
  208. data/lib/views/backend/solidus_promotions/admin/promotion_categories/new.html.erb +10 -0
  209. data/lib/views/backend/solidus_promotions/admin/promotion_code_batches/_form_fields.html.erb +22 -0
  210. data/lib/views/backend/solidus_promotions/admin/promotion_code_batches/download.csv.ruby +8 -0
  211. data/lib/views/backend/solidus_promotions/admin/promotion_code_batches/index.html.erb +65 -0
  212. data/lib/views/backend/solidus_promotions/admin/promotion_code_batches/new.html.erb +8 -0
  213. data/lib/views/backend/solidus_promotions/admin/promotion_codes/index.csv.ruby +8 -0
  214. data/lib/views/backend/solidus_promotions/admin/promotion_codes/index.html.erb +32 -0
  215. data/lib/views/backend/solidus_promotions/admin/promotion_codes/new.html.erb +31 -0
  216. data/lib/views/backend/solidus_promotions/admin/promotions/_form.html.erb +124 -0
  217. data/lib/views/backend/solidus_promotions/admin/promotions/_table.html.erb +57 -0
  218. data/lib/views/backend/solidus_promotions/admin/promotions/_table_filter.html.erb +78 -0
  219. data/lib/views/backend/solidus_promotions/admin/promotions/edit.html.erb +41 -0
  220. data/lib/views/backend/solidus_promotions/admin/promotions/index.html.erb +23 -0
  221. data/lib/views/backend/solidus_promotions/admin/promotions/new.html.erb +9 -0
  222. data/lib/views/backend/solidus_promotions/admin/shared/_number_with_currency.html.erb +20 -0
  223. data/lib/views/backend/solidus_promotions/admin/shared/_promotion_sub_menu.html.erb +14 -0
  224. data/lib/views/backend/solidus_promotions/admin/shared/preference_fields/_boolean.html.erb +14 -0
  225. data/lib/views/backend/solidus_promotions/admin/shared/preference_fields/_decimal.html.erb +12 -0
  226. data/lib/views/backend/solidus_promotions/admin/shared/preference_fields/_encrypted_string.html.erb +12 -0
  227. data/lib/views/backend/solidus_promotions/admin/shared/preference_fields/_integer.html.erb +12 -0
  228. data/lib/views/backend/solidus_promotions/admin/shared/preference_fields/_password.html.erb +12 -0
  229. data/lib/views/backend/solidus_promotions/admin/shared/preference_fields/_string.html.erb +12 -0
  230. data/lib/views/backend/solidus_promotions/admin/shared/preference_fields/_text.html.erb +12 -0
  231. data/lib/views/backend/solidus_promotions/promotion_code_batch_mailer/promotion_code_batch_errored.text.erb +2 -0
  232. data/lib/views/backend/solidus_promotions/promotion_code_batch_mailer/promotion_code_batch_finished.text.erb +2 -0
  233. data/solidus_promotions.gemspec +33 -0
  234. metadata +366 -0
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusPromotions
4
+ module Admin
5
+ class PromotionCategoriesController < BaseController
6
+ private
7
+
8
+ def model_class
9
+ SolidusPromotions::PromotionCategory
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusPromotions
4
+ module Admin
5
+ class PromotionCodeBatchesController < BaseController
6
+ belongs_to "solidus_promotions/promotion"
7
+
8
+ create.after :build_promotion_code_batch
9
+
10
+ def download
11
+ require "csv"
12
+
13
+ @promotion_code_batch = SolidusPromotions::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
+
29
+ def model_class
30
+ SolidusPromotions::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
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "csv"
4
+
5
+ module SolidusPromotions
6
+ module Admin
7
+ class PromotionCodesController < BaseController
8
+ before_action :load_promotion
9
+
10
+ def index
11
+ @promotion_codes = @promotion.codes.order(:value)
12
+
13
+ respond_to do |format|
14
+ format.html do
15
+ @promotion_codes = @promotion_codes.page(params[:page]).per(50)
16
+ end
17
+ format.csv do
18
+ filename = "promotion-code-list-#{@promotion.id}.csv"
19
+ headers["Content-Type"] = "text/csv"
20
+ headers["Content-disposition"] = "attachment; filename=\"#{filename}\""
21
+ end
22
+ end
23
+ end
24
+
25
+ def new
26
+ if @promotion.apply_automatically
27
+ flash[:error] = t(
28
+ :disallowed_with_apply_automatically,
29
+ scope: "activerecord.errors.models.solidus_promotions/promotion_code.attributes.base"
30
+ )
31
+ redirect_to solidus_promotions.admin_promotion_promotion_codes_url(@promotion)
32
+ else
33
+ @promotion_code = @promotion.codes.build
34
+ end
35
+ end
36
+
37
+ def create
38
+ @promotion_code = @promotion.codes.build(value: params[:promotion_code][:value])
39
+
40
+ if @promotion_code.save
41
+ flash[:success] = flash_message_for(@promotion_code, :successfully_created)
42
+ redirect_to solidus_promotions.admin_promotion_promotion_codes_url(@promotion)
43
+ else
44
+ flash.now[:error] = @promotion_code.errors.full_messages.to_sentence
45
+ render_after_create_error
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ def load_promotion
52
+ @promotion = SolidusPromotions::Promotion
53
+ .accessible_by(current_ability, :show)
54
+ .find(params[:promotion_id])
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusPromotions
4
+ module Admin
5
+ class PromotionsController < BaseController
6
+ before_action :load_data
7
+
8
+ helper "solidus_promotions/admin/conditions"
9
+ helper "solidus_promotions/admin/benefits"
10
+ helper "solidus_promotions/admin/promotions"
11
+
12
+ def create
13
+ @promotion = model_class.new(permitted_resource_params)
14
+ @promotion.codes.new(value: params[:single_code]) if params[:single_code].present?
15
+
16
+ if params[:code_batch]
17
+ @code_batch = @promotion.code_batches.new(code_batch_params)
18
+ end
19
+
20
+ if @promotion.save
21
+ @code_batch&.process
22
+ flash[:success] = t("solidus_promotions.promotion_successfully_created")
23
+ redirect_to location_after_save
24
+ else
25
+ flash[:error] = @promotion.errors.full_messages.to_sentence
26
+ render action: "new"
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def collection
33
+ return @collection if @collection
34
+
35
+ params[:q] ||= HashWithIndifferentAccess.new
36
+ params[:q][:s] ||= "updated_at desc"
37
+
38
+ @collection = super
39
+ @search = @collection.ransack(params[:q])
40
+ @collection = @search.result(distinct: true)
41
+ .includes(promotion_includes)
42
+ .page(params[:page])
43
+ .per(params[:per_page] || SolidusPromotions.config.promotions_per_page)
44
+
45
+ @collection
46
+ end
47
+
48
+ def promotion_includes
49
+ [:benefits]
50
+ end
51
+
52
+ def model_class
53
+ SolidusPromotions::Promotion
54
+ end
55
+
56
+ def load_data
57
+ @promotion_categories = PromotionCategory.order(:name)
58
+ end
59
+
60
+ def location_after_save
61
+ solidus_promotions.edit_admin_promotion_url(@promotion)
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusPromotions
4
+ module Generators
5
+ class InstallGenerator < Rails::Generators::Base
6
+ class_option :auto_run_migrations, type: :boolean, default: false
7
+ source_root File.expand_path("templates", __dir__)
8
+
9
+ def self.exit_on_failure?
10
+ true
11
+ end
12
+
13
+ def copy_initializer
14
+ template "initializer.rb", "config/initializers/solidus_promotions.rb"
15
+ end
16
+
17
+ def add_migrations
18
+ run "bin/rails railties:install:migrations FROM=solidus_promotions"
19
+ end
20
+
21
+ def modify_spree_initializer
22
+ spree_rb_path = "config/initializers/spree.rb"
23
+ new_content = <<-RUBY.gsub(/^ {8}/, "")
24
+ # Make sure we use Spree::SimpleOrderContents
25
+ # Spree::Config.order_contents_class = "Spree::SimpleOrderContents"
26
+ # Set the promotion configuration to ours
27
+ # Spree::Config.promotions = SolidusPromotions.configuration
28
+ RUBY
29
+ insert_into_file spree_rb_path, new_content, after: "Spree.config do |config|\n"
30
+ end
31
+
32
+ def mount_engine
33
+ inject_into_file "config/routes.rb",
34
+ " mount SolidusPromotions::Engine => '/'\n",
35
+ before: / mount Spree::Core::Engine.*/,
36
+ verbose: true
37
+ end
38
+
39
+ def run_migrations
40
+ run_migrations = options[:auto_run_migrations] || ["", "y", "Y"].include?(ask("Would you like to run the migrations now? [Y/n]"))
41
+ if run_migrations
42
+ run "bin/rails db:migrate"
43
+ else
44
+ puts "Skipping bin/rails db:migrate, don't forget to run it!"
45
+ end
46
+ end
47
+
48
+ def explain_promotion_config
49
+ say "SolidusPromotions is now installed. You can configure it by editing the initializer at `config/initializers/solidus_promotions.rb`."
50
+ say "By default, it is not activated. In order to activate it, you need to set `Spree::Config.promotions` to `SolidusPromotions.configuration`" \
51
+ "in your `config/initializers/spree.rb` file."
52
+ say "If you have been running the legacy promotion system, we recommend converting your existing promotions using the `solidus_promotions:migrate_existing_promotions` rake task."
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ # SolidusPromotions.configure do |config|
4
+ # # Add your own configuration here. See https://github.com/solidusio/solidus/blob/main/promotions/lib/solidus_promotions/configuration.rb
5
+ # # to learn more about the available options.
6
+ # end
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spree/core/environment_extension"
4
+
5
+ module SolidusPromotions
6
+ class Configuration < Spree::Preferences::Configuration
7
+ include Spree::Core::EnvironmentExtension
8
+
9
+ class_name_attribute :order_adjuster_class, default: "SolidusPromotions::OrderAdjuster"
10
+
11
+ class_name_attribute :coupon_code_handler_class, default: "SolidusPromotions::PromotionHandler::Coupon"
12
+
13
+ class_name_attribute :promotion_finder_class, default: "SolidusPromotions::PromotionFinder"
14
+
15
+ # Allows providing a different promotion advertiser.
16
+ # @!attribute [rw] advertiser_class
17
+ # @see Spree::NullPromotionAdvertiser
18
+ # @return [Class] an object that conforms to the API of
19
+ # the standard promotion advertiser class
20
+ # Spree::NullPromotionAdvertiser.
21
+ class_name_attribute :advertiser_class, default: "SolidusPromotions::PromotionAdvertiser"
22
+
23
+ # In case solidus_legacy_promotions is loaded, we need to define this.
24
+ class_name_attribute :shipping_promotion_handler_class, default: "Spree::NullPromotionHandler"
25
+
26
+ add_class_set :order_conditions, default: [
27
+ "SolidusPromotions::Conditions::FirstOrder",
28
+ "SolidusPromotions::Conditions::FirstRepeatPurchaseSince",
29
+ "SolidusPromotions::Conditions::ItemTotal",
30
+ "SolidusPromotions::Conditions::DiscountedItemTotal",
31
+ "SolidusPromotions::Conditions::MinimumQuantity",
32
+ "SolidusPromotions::Conditions::NthOrder",
33
+ "SolidusPromotions::Conditions::OneUsePerUser",
34
+ "SolidusPromotions::Conditions::OptionValue",
35
+ "SolidusPromotions::Conditions::Product",
36
+ "SolidusPromotions::Conditions::Store",
37
+ "SolidusPromotions::Conditions::Taxon",
38
+ "SolidusPromotions::Conditions::UserLoggedIn",
39
+ "SolidusPromotions::Conditions::UserRole",
40
+ "SolidusPromotions::Conditions::User"
41
+ ]
42
+
43
+ add_class_set :line_item_conditions, default: [
44
+ "SolidusPromotions::Conditions::LineItemOptionValue",
45
+ "SolidusPromotions::Conditions::LineItemProduct",
46
+ "SolidusPromotions::Conditions::LineItemTaxon"
47
+ ]
48
+ add_class_set :shipment_conditions, default: [
49
+ "SolidusPromotions::Conditions::ShippingMethod"
50
+ ]
51
+
52
+ add_class_set :benefits, default: [
53
+ "SolidusPromotions::Benefits::AdjustLineItem",
54
+ "SolidusPromotions::Benefits::AdjustLineItemQuantityGroups",
55
+ "SolidusPromotions::Benefits::AdjustShipment",
56
+ "SolidusPromotions::Benefits::CreateDiscountedItem"
57
+ ]
58
+
59
+ add_nested_class_set :promotion_calculators, default: {
60
+ "SolidusPromotions::Benefits::AdjustShipment" => [
61
+ "SolidusPromotions::Calculators::FlatRate",
62
+ "SolidusPromotions::Calculators::FlexiRate",
63
+ "SolidusPromotions::Calculators::Percent",
64
+ "SolidusPromotions::Calculators::TieredFlatRate",
65
+ "SolidusPromotions::Calculators::TieredPercent",
66
+ "SolidusPromotions::Calculators::TieredPercentOnEligibleItemQuantity"
67
+ ],
68
+ "SolidusPromotions::Benefits::AdjustLineItem" => [
69
+ "SolidusPromotions::Calculators::DistributedAmount",
70
+ "SolidusPromotions::Calculators::FlatRate",
71
+ "SolidusPromotions::Calculators::FlexiRate",
72
+ "SolidusPromotions::Calculators::Percent",
73
+ "SolidusPromotions::Calculators::TieredFlatRate",
74
+ "SolidusPromotions::Calculators::TieredPercent",
75
+ "SolidusPromotions::Calculators::TieredPercentOnEligibleItemQuantity"
76
+ ],
77
+ "SolidusPromotions::Benefits::AdjustLineItemQuantityGroups" => [
78
+ "SolidusPromotions::Calculators::FlatRate",
79
+ "SolidusPromotions::Calculators::Percent",
80
+ "SolidusPromotions::Calculators::TieredPercentOnEligibleItemQuantity"
81
+ ],
82
+ "SolidusPromotions::Benefits::CreateDiscountedItem" => [
83
+ "SolidusPromotions::Calculators::FlatRate",
84
+ "SolidusPromotions::Calculators::Percent",
85
+ "SolidusPromotions::Calculators::TieredPercentOnEligibleItemQuantity"
86
+ ]
87
+ }
88
+
89
+ class_name_attribute :discount_chooser_class, default: "SolidusPromotions::OrderAdjuster::ChooseDiscounts"
90
+ class_name_attribute :promotion_code_batch_mailer_class,
91
+ default: "SolidusPromotions::PromotionCodeBatchMailer"
92
+
93
+ # @!attribute [rw] promotions_per_page
94
+ # @return [Integer] Promotions to show per-page in the admin (default: +25+)
95
+ preference :promotions_per_page, :integer, default: 25
96
+
97
+ preference :lanes, :hash, default: {
98
+ pre: 0,
99
+ default: 1,
100
+ post: 2
101
+ }
102
+
103
+ preference :recalculate_complete_orders, :boolean, default: true
104
+
105
+ preference :sync_order_promotions, :boolean, default: false
106
+
107
+ preference :use_new_admin, :boolean, default: false
108
+
109
+ def use_new_admin?
110
+ SolidusSupport.admin_available? && preferred_use_new_admin
111
+ end
112
+ end
113
+
114
+ class << self
115
+ def configuration
116
+ @configuration ||= Configuration.new
117
+ end
118
+
119
+ alias_method :config, :configuration
120
+
121
+ def configure
122
+ yield configuration
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "solidus_core"
4
+ require "solidus_support"
5
+
6
+ module SolidusPromotions
7
+ class Engine < Rails::Engine
8
+ include SolidusSupport::EngineExtensions
9
+
10
+ isolate_namespace ::SolidusPromotions
11
+
12
+ engine_name "solidus_promotions"
13
+
14
+ # use rspec for tests
15
+ config.generators do |g|
16
+ g.test_framework :rspec
17
+ end
18
+
19
+ initializer "solidus_promotions.assets" do |app|
20
+ if SolidusSupport.backend_available?
21
+ app.config.assets.precompile << "solidus_promotions/manifest.js"
22
+ end
23
+ end
24
+
25
+ initializer "solidus_promotions.importmap" do |app|
26
+ if SolidusSupport.backend_available?
27
+ SolidusPromotions.importmap.draw(Engine.root.join("config", "importmap.rb"))
28
+
29
+ package_path = Engine.root.join("app/javascript")
30
+ app.config.assets.paths << package_path
31
+
32
+ if app.config.importmap.sweep_cache
33
+ SolidusPromotions.importmap.cache_sweeper(watches: package_path)
34
+ ActiveSupport.on_load(:action_controller_base) do
35
+ before_action { SolidusPromotions.importmap.cache_sweeper.execute_if_updated }
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ initializer "solidus_promotions.spree_config", after: "spree.load_config_initializers" do
42
+ Spree::Config.adjustment_promotion_source_types << "SolidusPromotions::Benefit"
43
+
44
+ Rails.application.config.to_prepare do
45
+ Spree::Order.line_item_comparison_hooks << :free_from_order_benefit?
46
+ end
47
+ end
48
+
49
+ initializer "solidus_promotions.core.pub_sub", after: "spree.core.pub_sub" do |app|
50
+ app.reloader.to_prepare do
51
+ SolidusPromotions::OrderPromotionSubscriber.new.subscribe_to(Spree::Bus)
52
+ end
53
+ end
54
+
55
+ initializer "solidus_promotions.add_admin_order_index_component", after: "solidus_legacy_promotions.add_admin_order_index_component" do
56
+ if SolidusSupport.admin_available?
57
+ SolidusAdmin::Config.components["orders/index"] = "SolidusPromotions::Orders::Index::Component"
58
+ SolidusAdmin::Config.components["promotions/index"] = "SolidusPromotions::Promotions::Index::Component"
59
+ SolidusAdmin::Config.components["promotion_categories/index"] = "SolidusPromotions::PromotionCategories::Index::Component"
60
+ end
61
+ end
62
+
63
+ initializer "solidus_promotions.add_backend_menus", after: "spree.backend.environment" do
64
+ if SolidusSupport.backend_available?
65
+ # Replace the promotions menu from core with ours
66
+ Spree::Backend::Config.configure do |config|
67
+ config.menu_items = config.menu_items.flat_map do |item|
68
+ next item unless item.label.to_sym == :promotions
69
+
70
+ [
71
+ Spree::BackendConfiguration::MenuItem.new(
72
+ label: :promotions,
73
+ icon: config.admin_updated_navbar ? "ri-megaphone-line" : "bullhorn",
74
+ condition: -> { can?(:admin, SolidusPromotions::Promotion) },
75
+ url: -> { SolidusPromotions::Engine.routes.url_helpers.admin_promotions_path },
76
+ data_hook: :admin_solidus_promotion_sub_tabs,
77
+ children: [
78
+ Spree::BackendConfiguration::MenuItem.new(
79
+ label: :promotions,
80
+ url: -> { SolidusPromotions::Engine.routes.url_helpers.admin_promotions_path },
81
+ condition: -> { can?(:admin, SolidusPromotions::Promotion) }
82
+ ),
83
+ Spree::BackendConfiguration::MenuItem.new(
84
+ label: :promotion_categories,
85
+ url: -> { SolidusPromotions::Engine.routes.url_helpers.admin_promotion_categories_path },
86
+ condition: -> { can?(:admin, SolidusPromotions::PromotionCategory) }
87
+ )
88
+ ]
89
+ ),
90
+ Spree::BackendConfiguration::MenuItem.new(
91
+ label: :legacy_promotions,
92
+ icon: config.admin_updated_navbar ? "ri-megaphone-line" : "bullhorn",
93
+ condition: -> { can?(:admin, SolidusPromotions::Promotion) },
94
+ url: -> { Spree::Core::Engine.routes.url_helpers.admin_promotions_path },
95
+ data_hook: :admin_promotion_sub_tabs,
96
+ children: [
97
+ Spree::BackendConfiguration::MenuItem.new(
98
+ label: :legacy_promotions,
99
+ condition: -> { can?(:admin, Spree::Promotion && Spree::Promotion.any?) },
100
+ url: -> { Spree::Core::Engine.routes.url_helpers.admin_promotions_path },
101
+ ),
102
+ Spree::BackendConfiguration::MenuItem.new(
103
+ label: :legacy_promotion_categories,
104
+ condition: -> { can?(:admin, Spree::PromotionCategory && Spree::Promotion.any?) },
105
+ url: -> { Spree::Core::Engine.routes.url_helpers.admin_promotion_categories_path },
106
+ )
107
+ ]
108
+ )
109
+ ]
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusPromotions
4
+ class MigrateAdjustments
5
+ class << self
6
+ def up
7
+ sql = if ActiveRecord::Base.connection_db_config.adapter == "mysql2"
8
+ <<~SQL
9
+ UPDATE spree_adjustments
10
+ INNER JOIN spree_promotion_actions ON spree_adjustments.source_id = spree_promotion_actions.id and spree_adjustments.source_type = 'Spree::PromotionAction'
11
+ INNER JOIN solidus_promotions_benefits ON solidus_promotions_benefits.original_promotion_action_id = spree_promotion_actions.id
12
+ SET source_id = solidus_promotions_benefits.id,
13
+ source_type = 'SolidusPromotions::Benefit'
14
+ SQL
15
+ else
16
+ <<~SQL
17
+ UPDATE spree_adjustments
18
+ SET source_id = solidus_promotions_benefits.id,
19
+ source_type = 'SolidusPromotions::Benefit'
20
+ FROM spree_promotion_actions
21
+ INNER JOIN solidus_promotions_benefits ON solidus_promotions_benefits.original_promotion_action_id = spree_promotion_actions.id
22
+ WHERE spree_adjustments.source_id = spree_promotion_actions.id and spree_adjustments.source_type = 'Spree::PromotionAction'
23
+ SQL
24
+ end
25
+
26
+ execute(sql)
27
+ end
28
+
29
+ def down
30
+ sql = if ActiveRecord::Base.connection_db_config.adapter == "mysql2"
31
+ <<~SQL
32
+ UPDATE spree_adjustments
33
+ INNER JOIN solidus_promotions_benefits
34
+ INNER JOIN spree_promotion_actions ON spree_adjustments.source_id = solidus_promotions_benefits.id and spree_adjustments.source_type = 'SolidusPromotions::Benefit'
35
+ SET source_id = spree_promotion_actions.id,
36
+ source_type = 'Spree::PromotionAction'
37
+ WHERE solidus_promotions_benefits.original_promotion_action_id = spree_promotion_actions.id
38
+ SQL
39
+ else
40
+ <<~SQL
41
+ UPDATE spree_adjustments
42
+ SET source_id = spree_promotion_actions.id,
43
+ source_type = 'Spree::PromotionAction'
44
+ FROM spree_promotion_actions
45
+ INNER JOIN solidus_promotions_benefits ON solidus_promotions_benefits.original_promotion_action_id = spree_promotion_actions.id
46
+ WHERE spree_adjustments.source_id = solidus_promotions_benefits.id and spree_adjustments.source_type = 'SolidusPromotions::Benefit'
47
+ SQL
48
+ end
49
+
50
+ execute(sql)
51
+ end
52
+
53
+ private
54
+
55
+ def execute(sql)
56
+ Spree::Adjustment.transaction do
57
+ ActiveRecord::Base.connection.execute(sql)
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusPromotions
4
+ class MigrateOrderPromotions
5
+ class << self
6
+ def up
7
+ sql = <<~SQL
8
+ INSERT INTO solidus_promotions_order_promotions (
9
+ order_id,
10
+ promotion_id,
11
+ promotion_code_id,
12
+ created_at,
13
+ updated_at
14
+ )
15
+ SELECT
16
+ spree_orders_promotions.order_id AS order_id,
17
+ solidus_promotions_promotions.id AS promotion_id,
18
+ solidus_promotions_promotion_codes.id AS promotion_code_id,
19
+ spree_orders_promotions.created_at,
20
+ spree_orders_promotions.updated_at
21
+ FROM spree_orders_promotions
22
+ INNER JOIN spree_promotions ON spree_orders_promotions.promotion_id = spree_promotions.id
23
+ INNER JOIN solidus_promotions_promotions ON spree_promotions.id = solidus_promotions_promotions.original_promotion_id
24
+ LEFT OUTER JOIN spree_promotion_codes ON spree_orders_promotions.promotion_code_id = spree_promotion_codes.id
25
+ LEFT OUTER JOIN solidus_promotions_promotion_codes ON spree_promotion_codes.value = solidus_promotions_promotion_codes.value
26
+ WHERE NOT EXISTS (
27
+ SELECT NULL
28
+ FROM solidus_promotions_order_promotions
29
+ WHERE solidus_promotions_order_promotions.order_id = order_id
30
+ AND (solidus_promotions_order_promotions.promotion_code_id = promotion_code_id OR promotion_code_id IS NULL)
31
+ AND solidus_promotions_order_promotions.promotion_id = promotion_id
32
+ );
33
+ SQL
34
+ ActiveRecord::Base.connection.execute(sql)
35
+
36
+ Spree::OrderPromotion.delete_all
37
+ end
38
+
39
+ def down
40
+ sql = <<~SQL
41
+ INSERT INTO spree_orders_promotions (
42
+ order_id,
43
+ promotion_id,
44
+ promotion_code_id,
45
+ created_at,
46
+ updated_at
47
+ )
48
+ SELECT
49
+ solidus_promotions_order_promotions.order_id AS order_id,
50
+ spree_promotions.id AS promotion_id,
51
+ spree_promotion_codes.id AS promotion_code_id,
52
+ solidus_promotions_order_promotions.created_at,
53
+ solidus_promotions_order_promotions.updated_at
54
+ FROM solidus_promotions_order_promotions
55
+ INNER JOIN solidus_promotions_promotions ON solidus_promotions_order_promotions.promotion_id = solidus_promotions_promotions.id
56
+ INNER JOIN spree_promotions ON spree_promotions.id = solidus_promotions_promotions.original_promotion_id
57
+ LEFT OUTER JOIN solidus_promotions_promotion_codes ON solidus_promotions_order_promotions.promotion_code_id = solidus_promotions_promotion_codes.id
58
+ LEFT OUTER JOIN spree_promotion_codes ON spree_promotion_codes.value = solidus_promotions_promotion_codes.value
59
+ WHERE NOT EXISTS (
60
+ SELECT NULL
61
+ FROM spree_orders_promotions
62
+ WHERE spree_orders_promotions.order_id = order_id
63
+ AND (spree_orders_promotions.promotion_code_id = promotion_code_id OR promotion_code_id IS NULL)
64
+ AND spree_orders_promotions.promotion_id = promotion_id
65
+ );
66
+ SQL
67
+ ActiveRecord::Base.connection.execute(sql)
68
+
69
+ SolidusPromotions::OrderPromotion.delete_all
70
+ end
71
+ end
72
+ end
73
+ end