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,79 @@
1
+ Spree.Views.Promotions.OptionValuesRuleRow = Backbone.View.extend({
2
+ optionValueTemplate: HandlebarsTemplates['promotions/rules/option_values'],
3
+
4
+ events: {
5
+ 'click .js-remove-promo-rule-option-value': 'onRemove',
6
+ 'change .js-promo-rule-option-value-product-select': 'onSelectProduct'
7
+ },
8
+
9
+ initialize: function(options) {
10
+ this.productId = options.productId;
11
+ this.values = options.values;
12
+ this.paramPrefix = options.paramPrefix;
13
+ },
14
+
15
+ render: function() {
16
+ this.$el.html(
17
+ this.optionValueTemplate({
18
+ productSelect: { value: this.productId },
19
+ optionValuesSelect: { value: this.values },
20
+ paramPrefix: this.paramPrefix
21
+ })
22
+ );
23
+
24
+ this.$('.js-promo-rule-option-value-product-select').productAutocomplete({multiple: false});
25
+
26
+ var productSelect = this.$el.find(".js-promo-rule-option-value-product-select")
27
+
28
+ this.$('.js-promo-rule-option-value-option-values-select').optionValueAutocomplete({ productSelect })
29
+
30
+ if(this.productId == null) {
31
+ this.$('.js-promo-rule-option-value-option-values-select').prop('disabled', true);
32
+ }
33
+ },
34
+
35
+ onSelectProduct: function(e) {
36
+ this.productId = this.$('input.js-promo-rule-option-value-product-select').val();
37
+ this.render();
38
+ },
39
+
40
+ onRemove: function(e) {
41
+ this.remove();
42
+ },
43
+ });
44
+
45
+ Spree.Views.Promotions.OptionValuesRule = Backbone.View.extend({
46
+ initialize: function() {
47
+ _.bindAll(this, 'addOptionValue')
48
+ this.$optionValues = this.$('.js-promo-rule-option-values');
49
+ this.paramPrefix = this.$('.param-prefix').data('param-prefix');
50
+
51
+ var originalOptionValues = this.$optionValues.data('original-option-values')
52
+ if ($.isEmptyObject(originalOptionValues)) {
53
+ this.addOptionValue(null, null);
54
+ } else {
55
+ $.each(originalOptionValues, this.addOptionValue);
56
+ }
57
+ },
58
+
59
+ events: {
60
+ 'click .js-add-promo-rule-option-value': 'onAdd'
61
+ },
62
+
63
+ addOptionValue: function(product, values) {
64
+ var row = new Spree.Views.Promotions.OptionValuesRuleRow({
65
+ productId: product,
66
+ values: values,
67
+ paramPrefix: this.paramPrefix
68
+ });
69
+
70
+ this.$optionValues.append(row.el);
71
+
72
+ row.render();
73
+ },
74
+
75
+ onAdd: function(e) {
76
+ e.preventDefault();
77
+ this.addOptionValue(null, null);
78
+ },
79
+ });
@@ -0,0 +1,3 @@
1
+ //= require spree/backend/namespaces.js
2
+
3
+ window.Spree.Views.Promotions = {}
@@ -0,0 +1,138 @@
1
+ #general_fields, #expiry_fields {
2
+ border-bottom: none;
3
+ margin-bottom: 0;
4
+ padding-bottom: 0;
5
+ }
6
+
7
+ #rules_container, #actions_container {
8
+ position: relative;
9
+
10
+ fieldset {
11
+ position: static !important;
12
+ }
13
+ }
14
+
15
+ .promotion-block {
16
+ padding: 0 1.25rem 0.5rem;
17
+ background-color: lighten($color-border, 5);
18
+ border: 1px solid $color-border;
19
+ border-radius: 3px;
20
+ margin-bottom: 15px;
21
+ position: relative;
22
+
23
+ .row {
24
+ margin-bottom: 0;
25
+ }
26
+
27
+ // Fix the padding added by the .fa icon rule
28
+ .fa:before {
29
+ padding-right: 0;
30
+ }
31
+
32
+ .promo-rule-option-value {
33
+ position: relative;
34
+ padding-bottom: 10px;
35
+
36
+ .remove {
37
+ cursor: pointer;
38
+ position: absolute;
39
+ right: 0;
40
+ top: 10px;
41
+
42
+ &:hover {
43
+ color: $color-red;
44
+ }
45
+ }
46
+ }
47
+
48
+ .tier {
49
+ position: relative;
50
+ padding-bottom: 10px;
51
+
52
+ > .row {
53
+ margin-left: 10px;
54
+ }
55
+
56
+ .remove {
57
+ position: absolute;
58
+ cursor: pointer;
59
+ left: 0;
60
+ top: 10px;
61
+
62
+ &:hover {
63
+ color: $color-red;
64
+ }
65
+ }
66
+ }
67
+
68
+ .right-align {
69
+ text-align: right;
70
+ }
71
+
72
+ .calculator-fields {
73
+ padding-bottom: 10px;
74
+ }
75
+
76
+ .field {
77
+ &.alpha {
78
+ margin-left: -1px;
79
+ padding-left: 11px;
80
+ }
81
+
82
+ &.omega {
83
+ margin-right: -1px;
84
+ padding-right: 11px;
85
+ }
86
+ }
87
+
88
+ &.action-remove {
89
+ background-color: lighten($color-red, 50);
90
+ border-color: lighten($color-red, 45);
91
+ text-decoration: line-through;
92
+
93
+ .promotion-title, input[type="text"], input[type="number"],
94
+ .token-input-list {
95
+ border-color: lighten($color-red, 45);
96
+ }
97
+ }
98
+
99
+ .add-line-item {
100
+ #add_quantity {
101
+ width: 50px;
102
+ margin-top: 15px;
103
+ }
104
+ button.add {
105
+ min-width: 80px;
106
+ margin-top: 30px;
107
+
108
+ &:before {
109
+ padding-right: 5px;
110
+ }
111
+ }
112
+ }
113
+
114
+ a.delete {
115
+ position: absolute;
116
+ top: 1.25rem;
117
+ right: 1.25rem;
118
+ cursor: pointer;
119
+
120
+ &:hover {
121
+ color: theme-color("danger");
122
+ }
123
+ }
124
+ }
125
+
126
+ .promotion-title {
127
+ text-align: left;
128
+ font-size: 1rem;
129
+ border-bottom: 1px solid $color-border;
130
+ padding: 0.75rem 0;
131
+ margin-bottom: 0.75rem;
132
+
133
+ &.no-text {
134
+ border: none;
135
+ padding-bottom: 0;
136
+ margin-bottom: 0.25rem;
137
+ }
138
+ }
@@ -0,0 +1,3 @@
1
+ @import "spree/backend/themes/classic";
2
+
3
+ @import "solidus_legacy_promotions/promotions/edit";
@@ -0,0 +1,3 @@
1
+ .adjustment-ineligible {
2
+ text-decoration: line-through;
3
+ }
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusLegacyPromotions
4
+ module SolidusAdminAdjustmentsControllerDecorator
5
+ private
6
+
7
+ def load_adjustments
8
+ @adjustments = @order
9
+ .all_adjustments
10
+ .eligible
11
+ .order("adjustable_type ASC, created_at ASC")
12
+ .ransack(params[:q])
13
+ .result
14
+ end
15
+
16
+ if Object.const_defined?("SolidusAdmin::AdjustmentsController")
17
+ SolidusAdmin::AdjustmentsController.prepend self
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_dependency "spree/core/state_machines/order"
4
+
5
+ module SolidusLegacyPromotions
6
+ module SpreeOrderStateMachineDecorator
7
+ def define_state_machine!
8
+ super
9
+ state_machine do
10
+ if states[:delivery]
11
+ before_transition from: :delivery, do: :apply_shipping_promotions
12
+ end
13
+ end
14
+ end
15
+
16
+ Spree::Core::StateMachines::Order::ClassMethods.prepend self
17
+ end
18
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusLegacyPromotions
4
+ module SpreeAdjustmentDecorator
5
+ def self.prepended(base)
6
+ base.belongs_to :promotion_code, class_name: 'Spree::PromotionCode', optional: true
7
+ base.validates :promotion_code, presence: true, if: :require_promotion_code?
8
+
9
+ base.scope :eligible, -> { where(eligible: true) }
10
+ end
11
+
12
+ # Recalculate and persist the amount from this adjustment's source based on
13
+ # the adjustable ({Order}, {Shipment}, or {LineItem})
14
+ #
15
+ # If the adjustment has no source (such as when created manually from the
16
+ # admin) or is closed, this is a noop.
17
+ #
18
+ # @return [BigDecimal] New amount of this adjustment
19
+ def recalculate
20
+ if finalized? && !tax?
21
+ return amount
22
+ end
23
+
24
+ # If the adjustment has no source, do not attempt to re-calculate the
25
+ # amount.
26
+ # Some scenarios where this happens:
27
+ # - Adjustments that are manually created via the admin backend
28
+ # - PromotionAction adjustments where the PromotionAction was deleted
29
+ # after the order was completed.
30
+ if source.present?
31
+ self.amount = source.compute_amount(adjustable)
32
+
33
+ if promotion?
34
+ self.eligible = calculate_eligibility
35
+ end
36
+
37
+ # Persist only if changed
38
+ # This is only not a save! to avoid the extra queries to load the order
39
+ # (for validations) and to touch the adjustment.
40
+ update_columns(eligible:, amount:, updated_at: Time.current) if changed?
41
+ end
42
+ amount
43
+ end
44
+ deprecate :recalculate, deprecator: Spree.deprecator
45
+
46
+ # Calculates based on attached promotion (if this is a promotion
47
+ # adjustment) whether this promotion is still eligible.
48
+ # @api private
49
+ # @return [true,false] Whether this adjustment is eligible
50
+ def calculate_eligibility
51
+ if !finalized? && source && promotion?
52
+ source.promotion.eligible?(adjustable, promotion_code:)
53
+ else
54
+ eligible?
55
+ end
56
+ end
57
+ deprecate :calculate_eligibility, deprecator: ::Spree.deprecator
58
+
59
+ def eligible
60
+ self[:eligible]
61
+ end
62
+ alias_method :eligible?, :eligible
63
+
64
+ private
65
+
66
+ def legacy_promotion?
67
+ source_type == "Spree::PromotionAction"
68
+ end
69
+
70
+ def require_promotion_code?
71
+ legacy_promotion? && !source.promotion.apply_automatically && source.promotion.codes.any?
72
+ end
73
+
74
+ Spree::Adjustment.prepend self
75
+ end
76
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusLegacyPromotions
4
+ module SpreeCalculatorReturnsDefaultRefundAmountDecorator
5
+ private
6
+
7
+ def weighted_order_adjustment_amount(inventory_unit)
8
+ inventory_unit.order.adjustments.eligible.non_tax.sum(:amount) * percentage_of_order_total(inventory_unit)
9
+ end
10
+
11
+ Spree::Calculator::Returns::DefaultRefundAmount.prepend self
12
+ end
13
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusLegacyPromotions
4
+ module SpreeLineItemDecorator
5
+ def total_before_tax
6
+ amount + adjustments.select { |value| !value.tax? && value.eligible? }.sum(&:amount)
7
+ end
8
+
9
+ Spree::LineItem.prepend self
10
+ end
11
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusLegacyPromotions
4
+ module SpreeOrderDecorator
5
+ module ClassMethods
6
+ def allowed_ransackable_associations
7
+ super + ["promotions", "order_promotions"]
8
+ end
9
+ end
10
+
11
+ def self.prepended(base)
12
+ base.has_many :order_promotions, class_name: 'Spree::OrderPromotion', dependent: :destroy
13
+ base.has_many :promotions, through: :order_promotions
14
+ end
15
+
16
+ def apply_shipping_promotions(_event = nil)
17
+ Spree::Config.promotions.shipping_promotion_handler_class.new(self).activate
18
+ recalculate
19
+ end
20
+
21
+ def shipping_discount
22
+ shipment_adjustments.credit.eligible.sum(:amount) * - 1
23
+ end
24
+
25
+ Spree::Order.prepend(self)
26
+ Spree::Order.singleton_class.prepend self::ClassMethods
27
+ end
28
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusLegacyPromotions
4
+ module SpreeOrderUpdaterDecorator
5
+ def update_adjustment_total
6
+ recalculate_adjustments
7
+
8
+ all_items = line_items + shipments
9
+ order_tax_adjustments = adjustments.select(&:eligible?).select(&:tax?)
10
+
11
+ order.adjustment_total = all_items.sum(&:adjustment_total) + adjustments.select(&:eligible?).sum(&:amount)
12
+ order.included_tax_total = all_items.sum(&:included_tax_total) + order_tax_adjustments.select(&:included?).sum(&:amount)
13
+ order.additional_tax_total = all_items.sum(&:additional_tax_total) + order_tax_adjustments.reject(&:included?).sum(&:amount)
14
+
15
+ update_order_total
16
+ end
17
+
18
+ def update_item_totals
19
+ [*line_items, *shipments].each do |item|
20
+ # The cancellation_total isn't persisted anywhere but is included in
21
+ # the adjustment_total
22
+ item.adjustment_total = item.adjustments.
23
+ select(&:eligible?).
24
+ reject(&:included?).
25
+ sum(&:amount)
26
+
27
+ if item.changed?
28
+ item.update_columns(
29
+ promo_total: item.promo_total,
30
+ included_tax_total: item.included_tax_total,
31
+ additional_tax_total: item.additional_tax_total,
32
+ adjustment_total: item.adjustment_total,
33
+ updated_at: Time.current,
34
+ )
35
+ end
36
+ end
37
+ end
38
+ Spree::OrderUpdater.prepend self
39
+ end
40
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusLegacyPromotions
4
+ module SpreeProductDecorator
5
+ def self.prepended(base)
6
+ base.has_many :product_promotion_rules, dependent: :destroy
7
+ base.has_many :promotion_rules, through: :product_promotion_rules
8
+
9
+ base.after_discard do
10
+ self.product_promotion_rules = []
11
+ end
12
+ end
13
+
14
+ ::Spree::Product.prepend self
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusLegacyPromotions
4
+ module SpreePromotionCodeBatchDecorator
5
+ def process
6
+ if state == "pending"
7
+ update!(state: "processing")
8
+ Spree::PromotionCodeBatchJob.perform_later(self)
9
+ else
10
+ raise Spree::PromotionCodeBatch::CantProcessStartedBatch.new("Batch #{id} already started")
11
+ end
12
+ end
13
+
14
+ Spree::PromotionCodeBatch.prepend(self)
15
+ end
16
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusLegacyPromotions
4
+ module SpreeShipmentDecorator
5
+ # @return [BigDecimal] the amount of this item, taking into consideration
6
+ # all non-tax eligible adjustments.
7
+ def total_before_tax
8
+ amount + adjustments.select { |adjustment| !adjustment.tax? && adjustment.eligible? }.sum(&:amount)
9
+ end
10
+
11
+ Spree::Shipment.prepend self
12
+ end
13
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spree
4
+ module Admin
5
+ module PromotionsHelper
6
+ def admin_promotion_status(promotion)
7
+ return :active if promotion.active?
8
+ return :not_started if promotion.not_started?
9
+ return :expired if promotion.expired?
10
+
11
+ :inactive
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spree
4
+ module PromotionRulesHelper
5
+ def options_for_promotion_rule_types(promotion)
6
+ existing = promotion.rules.map { |rule| rule.class.name }
7
+ rules = Spree::Config.promotions.rules.reject { |rule| existing.include? rule.name }
8
+ options = rules.map { |rule| [rule.model_name.human, rule.name] }
9
+ options_for_select(options)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spree
4
+ class PromotionCodeBatchJob < ActiveJob::Base
5
+ queue_as :default
6
+
7
+ def perform(promotion_code_batch)
8
+ PromotionCode::BatchBuilder.new(
9
+ promotion_code_batch
10
+ ).build_promotion_codes
11
+
12
+ if promotion_code_batch.email?
13
+ Spree::Config.promotions.promotion_code_batch_mailer_class
14
+ .promotion_code_batch_finished(promotion_code_batch)
15
+ .deliver_now
16
+ end
17
+ rescue StandardError => error
18
+ if promotion_code_batch.email?
19
+ Spree::Config.promotions.promotion_code_batch_mailer_class
20
+ .promotion_code_batch_errored(promotion_code_batch)
21
+ .deliver_now
22
+ end
23
+ raise error
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spree
4
+ class PromotionCodeBatchMailer < Spree::BaseMailer
5
+ def promotion_code_batch_finished(promotion_code_batch)
6
+ @promotion_code_batch = promotion_code_batch
7
+ mail(to: promotion_code_batch.email)
8
+ end
9
+
10
+ def promotion_code_batch_errored(promotion_code_batch)
11
+ @promotion_code_batch = promotion_code_batch
12
+ mail(to: promotion_code_batch.email)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_dependency 'spree/calculator'
4
+
5
+ # This is a calculator for line item adjustment actions. It accepts a line item
6
+ # and calculates its weighted adjustment amount based on the value of the
7
+ # preferred amount and the price of the other line items. More expensive line
8
+ # items will receive a greater share of the preferred amount.
9
+
10
+ module Spree
11
+ class Calculator::DistributedAmount < Calculator
12
+ preference :amount, :decimal, default: 0
13
+ preference :currency, :string, default: -> { Spree::Config[:currency] }
14
+
15
+ def compute_line_item(line_item)
16
+ return 0 unless line_item
17
+ return 0 unless preferred_currency.casecmp(line_item.currency).zero?
18
+ return 0 unless calculable.promotion.line_item_actionable?(line_item.order, line_item)
19
+ Spree::DistributedAmountsHandler.new(
20
+ actionable_line_items(line_item.order),
21
+ preferred_amount
22
+ ).amount(line_item)
23
+ end
24
+
25
+ private
26
+
27
+ def actionable_line_items(order)
28
+ order.line_items.select do |line_item|
29
+ calculable.promotion.line_item_actionable?(order, line_item)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_dependency 'spree/calculator'
4
+
5
+ module Spree
6
+ class Calculator::FlatPercentItemTotal < Calculator
7
+ preference :flat_percent, :decimal, default: 0
8
+
9
+ def compute(object)
10
+ order = object.is_a?(Order) ? object : object.order
11
+ preferred_currency = order.currency
12
+ currency_exponent = ::Money::Currency.find(preferred_currency).exponent
13
+ computed_amount = (object.amount * preferred_flat_percent / 100).round(currency_exponent)
14
+
15
+ # We don't want to cause the promotion adjustments to push the order into a negative total.
16
+ if computed_amount > object.amount
17
+ object.amount
18
+ else
19
+ computed_amount
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_dependency 'spree/calculator'
4
+
5
+ module Spree
6
+ class Calculator::FlexiRate < Calculator
7
+ preference :first_item, :decimal, default: 0
8
+ preference :additional_item, :decimal, default: 0
9
+ preference :max_items, :integer, default: 0
10
+ preference :currency, :string, default: ->{ Spree::Config[:currency] }
11
+
12
+ def compute(object)
13
+ items_count = object.quantity
14
+ items_count = [items_count, preferred_max_items].min unless preferred_max_items.zero?
15
+
16
+ return BigDecimal(0) if items_count == 0
17
+
18
+ additional_items_count = items_count - 1
19
+ preferred_first_item + preferred_additional_item * additional_items_count
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_dependency 'spree/calculator'
4
+
5
+ module Spree
6
+ class Calculator::PercentOnLineItem < Calculator
7
+ preference :percent, :decimal, default: 0
8
+
9
+ def compute(object)
10
+ (object.amount * preferred_percent) / 100
11
+ end
12
+ end
13
+ end