solidus_legacy_promotions 4.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +345 -0
- data/Rakefile +43 -0
- data/app/assets/config/solidus_legacy_promotions/manifest.js +2 -0
- data/app/assets/javascripts/spree/backend/edit_promotion.js +7 -0
- data/app/assets/javascripts/spree/backend/promotions/activation.js +26 -0
- data/app/assets/javascripts/spree/backend/promotions.js +35 -0
- data/app/assets/javascripts/spree/backend/templates/promotions/calculators/fields/tiered_flat_rate.hbs +23 -0
- data/app/assets/javascripts/spree/backend/templates/promotions/calculators/fields/tiered_percent.hbs +23 -0
- data/app/assets/javascripts/spree/backend/templates/promotions/rules/option_values.hbs +12 -0
- data/app/assets/javascripts/spree/backend/views/calculators/tiered.js +54 -0
- data/app/assets/javascripts/spree/backend/views/order/details_adjustments.js +43 -0
- data/app/assets/javascripts/spree/backend/views/promotions/option_values_rule.js +79 -0
- data/app/assets/javascripts/spree/backend/views/promotions.js +3 -0
- data/app/assets/stylesheets/solidus_legacy_promotions/promotions/_edit.scss +138 -0
- data/app/assets/stylesheets/solidus_legacy_promotions/promotions.scss +3 -0
- data/app/assets/stylesheets/spree/backend/sections/_adjustments.scss +3 -0
- data/app/decorators/solidus_legacy_promotions/controllers/solidus_admin/solidus_admin_adjustments_controller_decorator.rb +20 -0
- data/app/decorators/solidus_legacy_promotions/lib/spree_order_state_machine_decorator.rb +18 -0
- data/app/decorators/solidus_legacy_promotions/models/spree_adjustment_decorator.rb +76 -0
- data/app/decorators/solidus_legacy_promotions/models/spree_calculator_returns_default_refund_amount_decorator.rb +13 -0
- data/app/decorators/solidus_legacy_promotions/models/spree_line_item_decorator.rb +11 -0
- data/app/decorators/solidus_legacy_promotions/models/spree_order_decorator.rb +28 -0
- data/app/decorators/solidus_legacy_promotions/models/spree_order_updater_decorator.rb +40 -0
- data/app/decorators/solidus_legacy_promotions/models/spree_product_decorator.rb +16 -0
- data/app/decorators/solidus_legacy_promotions/models/spree_promotion_code_batch_decorator.rb +16 -0
- data/app/decorators/solidus_legacy_promotions/models/spree_shipment_decorator.rb +13 -0
- data/app/helpers/spree/admin/promotions_helper.rb +15 -0
- data/app/helpers/spree/promotion_rules_helper.rb +12 -0
- data/app/jobs/spree/promotion_code_batch_job.rb +26 -0
- data/app/mailers/spree/promotion_code_batch_mailer.rb +15 -0
- data/app/models/spree/calculator/distributed_amount.rb +33 -0
- data/app/models/spree/calculator/flat_percent_item_total.rb +23 -0
- data/app/models/spree/calculator/flexi_rate.rb +22 -0
- data/app/models/spree/calculator/percent_on_line_item.rb +13 -0
- data/app/models/spree/calculator/tiered_flat_rate.rb +52 -0
- data/app/models/spree/calculator/tiered_percent.rb +62 -0
- data/app/models/spree/order_contents.rb +36 -0
- data/app/models/spree/order_promotion.rb +27 -0
- data/app/models/spree/permission_sets/promotion_display.rb +25 -0
- data/app/models/spree/permission_sets/promotion_management.rb +25 -0
- data/app/models/spree/promotion/actions/create_adjustment.rb +81 -0
- data/app/models/spree/promotion/actions/create_item_adjustments.rb +98 -0
- data/app/models/spree/promotion/actions/create_quantity_adjustments.rb +139 -0
- data/app/models/spree/promotion/actions/free_shipping.rb +59 -0
- data/app/models/spree/promotion/order_adjustments_recalculator.rb +92 -0
- data/app/models/spree/promotion/rules/first_order.rb +36 -0
- data/app/models/spree/promotion/rules/first_repeat_purchase_since.rb +36 -0
- data/app/models/spree/promotion/rules/item_total.rb +86 -0
- data/app/models/spree/promotion/rules/minimum_quantity.rb +59 -0
- data/app/models/spree/promotion/rules/nth_order.rb +45 -0
- data/app/models/spree/promotion/rules/one_use_per_user.rb +25 -0
- data/app/models/spree/promotion/rules/option_value.rb +50 -0
- data/app/models/spree/promotion/rules/product.rb +86 -0
- data/app/models/spree/promotion/rules/store.rb +26 -0
- data/app/models/spree/promotion/rules/taxon.rb +91 -0
- data/app/models/spree/promotion/rules/user.rb +34 -0
- data/app/models/spree/promotion/rules/user_logged_in.rb +20 -0
- data/app/models/spree/promotion/rules/user_role.rb +45 -0
- data/app/models/spree/promotion.rb +271 -0
- data/app/models/spree/promotion_action.rb +51 -0
- data/app/models/spree/promotion_advertiser.rb +10 -0
- data/app/models/spree/promotion_category.rb +8 -0
- data/app/models/spree/promotion_chooser.rb +34 -0
- data/app/models/spree/promotion_code/batch_builder.rb +68 -0
- data/app/models/spree/promotion_code.rb +54 -0
- data/app/models/spree/promotion_code_batch.rb +18 -0
- data/app/models/spree/promotion_finder.rb +9 -0
- data/app/models/spree/promotion_handler/cart.rb +75 -0
- data/app/models/spree/promotion_handler/coupon.rb +125 -0
- data/app/models/spree/promotion_handler/page.rb +26 -0
- data/app/models/spree/promotion_handler/shipping.rb +61 -0
- data/app/models/spree/promotion_rule.rb +55 -0
- data/app/models/spree/promotion_rule_store.rb +10 -0
- data/app/models/spree/promotion_rule_taxon.rb +8 -0
- data/app/models/spree/promotion_rule_user.rb +10 -0
- data/app/subscribers/spree/order_promotion_subscriber.rb +20 -0
- data/app/views/spree/order_mailer/cancel_email.html.erb +45 -0
- data/app/views/spree/order_mailer/cancel_email.text.erb +16 -0
- data/app/views/spree/order_mailer/confirm_email.html.erb +84 -0
- data/app/views/spree/order_mailer/confirm_email.text.erb +38 -0
- data/app/views/spree/promotion_code_batch_mailer/promotion_code_batch_errored.text.erb +2 -0
- data/app/views/spree/promotion_code_batch_mailer/promotion_code_batch_finished.text.erb +2 -0
- data/bin/rails +13 -0
- data/config/locales/en.yml +101 -0
- data/config/locales/promotion_categories.en.yml +6 -0
- data/config/locales/promotions.en.yml +6 -0
- data/config/routes.rb +28 -0
- data/db/migrate/20160101010001_solidus_one_four_promotions.rb +126 -0
- data/db/migrate/20161017102621_create_spree_promotion_code_batch.rb +48 -0
- data/db/migrate/20180202190713_create_promotion_rule_stores.rb +14 -0
- data/db/migrate/20180328172631_add_join_characters_to_promotion_code_batch.rb +15 -0
- data/db/migrate/20190106184413_remove_code_from_spree_promotions.rb +46 -0
- data/db/migrate/20220317165036_set_promotions_with_any_policy_to_all_if_possible.rb +20 -0
- data/db/migrate/20230322085416_remove_match_policy_from_spree_promotion.rb +7 -0
- data/db/migrate/20230325132905_remove_unused_columns_from_promotion_rules.rb +10 -0
- data/db/migrate/20231027084517_add_order_promotions_foreign_key.rb +14 -0
- data/db/migrate/20240621100123_add_promotion_code_id_to_spree_adjustments.rb +10 -0
- data/db/migrate/20240622113334_move_adjustment_eligible_to_legacy_promotions.rb +11 -0
- data/lib/components/admin/solidus_admin/orders/show/adjustments/index/source/spree_promotion_action/component.rb +17 -0
- data/lib/components/admin/solidus_admin/promotion_categories/index/component.rb +56 -0
- data/lib/components/admin/solidus_admin/promotions/index/component.rb +104 -0
- data/lib/components/admin/solidus_admin/promotions/index/component.yml +10 -0
- data/lib/components/admin/solidus_legacy_promotions/orders/index/component.rb +15 -0
- data/lib/components/admin/solidus_legacy_promotions/orders/index/component.yml +20 -0
- data/lib/controllers/admin/solidus_admin/promotion_categories_controller.rb +29 -0
- data/lib/controllers/admin/solidus_admin/promotions_controller.rb +46 -0
- data/lib/controllers/backend/spree/admin/promotion_actions_controller.rb +51 -0
- data/lib/controllers/backend/spree/admin/promotion_categories_controller.rb +8 -0
- data/lib/controllers/backend/spree/admin/promotion_code_batches_controller.rb +30 -0
- data/lib/controllers/backend/spree/admin/promotion_codes_controller.rb +48 -0
- data/lib/controllers/backend/spree/admin/promotion_rules_controller.rb +60 -0
- data/lib/controllers/backend/spree/admin/promotions_controller.rb +66 -0
- data/lib/solidus_legacy_promotions/configuration.rb +115 -0
- data/lib/solidus_legacy_promotions/engine.rb +97 -0
- data/lib/solidus_legacy_promotions/migrations/promotions_with_code_handlers.rb +68 -0
- data/lib/solidus_legacy_promotions/testing_support/factories/calculator_factory.rb +7 -0
- data/lib/solidus_legacy_promotions/testing_support/factories/order_factory.rb +22 -0
- data/lib/solidus_legacy_promotions/testing_support/factories/order_promotion_factory.rb +8 -0
- data/lib/solidus_legacy_promotions/testing_support/factories/promotion_category_factory.rb +7 -0
- data/lib/solidus_legacy_promotions/testing_support/factories/promotion_code_factory.rb +8 -0
- data/lib/solidus_legacy_promotions/testing_support/factories/promotion_factory.rb +98 -0
- data/lib/solidus_legacy_promotions/testing_support/factory_bot.rb +28 -0
- data/lib/solidus_legacy_promotions.rb +28 -0
- data/lib/tasks/solidus_legacy_promotions/delete_ineligible_adjustments.rake +8 -0
- data/lib/views/backend/spree/admin/adjustments/_adjustment.html.erb +24 -0
- data/lib/views/backend/spree/admin/orders/_adjustments.html.erb +19 -0
- data/lib/views/backend/spree/admin/orders/_order_details.html.erb +32 -0
- data/lib/views/backend/spree/admin/orders/confirm.html.erb +59 -0
- data/lib/views/backend/spree/admin/promotion_actions/create.js.erb +10 -0
- data/lib/views/backend/spree/admin/promotion_actions/destroy.js.erb +1 -0
- data/lib/views/backend/spree/admin/promotion_categories/_form.html.erb +14 -0
- data/lib/views/backend/spree/admin/promotion_categories/edit.html.erb +10 -0
- data/lib/views/backend/spree/admin/promotion_categories/index.html.erb +47 -0
- data/lib/views/backend/spree/admin/promotion_categories/new.html.erb +10 -0
- data/lib/views/backend/spree/admin/promotion_code_batches/_form_fields.html.erb +22 -0
- data/lib/views/backend/spree/admin/promotion_code_batches/download.csv.ruby +8 -0
- data/lib/views/backend/spree/admin/promotion_code_batches/index.html.erb +65 -0
- data/lib/views/backend/spree/admin/promotion_code_batches/new.html.erb +8 -0
- data/lib/views/backend/spree/admin/promotion_codes/index.csv.ruby +8 -0
- data/lib/views/backend/spree/admin/promotion_codes/index.html.erb +32 -0
- data/lib/views/backend/spree/admin/promotion_codes/new.html.erb +31 -0
- data/lib/views/backend/spree/admin/promotion_rules/create.js.erb +8 -0
- data/lib/views/backend/spree/admin/promotion_rules/destroy.js.erb +3 -0
- data/lib/views/backend/spree/admin/promotions/_actions.html.erb +37 -0
- data/lib/views/backend/spree/admin/promotions/_activations_edit.html.erb +22 -0
- data/lib/views/backend/spree/admin/promotions/_activations_new.html.erb +43 -0
- data/lib/views/backend/spree/admin/promotions/_form.html.erb +67 -0
- data/lib/views/backend/spree/admin/promotions/_promotion_action.html.erb +13 -0
- data/lib/views/backend/spree/admin/promotions/_promotion_rule.html.erb +12 -0
- data/lib/views/backend/spree/admin/promotions/_rules.html.erb +42 -0
- data/lib/views/backend/spree/admin/promotions/actions/_create_adjustment.html.erb +2 -0
- data/lib/views/backend/spree/admin/promotions/actions/_create_item_adjustments.html.erb +6 -0
- data/lib/views/backend/spree/admin/promotions/actions/_create_quantity_adjustments.html.erb +10 -0
- data/lib/views/backend/spree/admin/promotions/actions/_free_shipping.html.erb +0 -0
- data/lib/views/backend/spree/admin/promotions/actions/_promotion_calculators_with_custom_fields.html.erb +29 -0
- data/lib/views/backend/spree/admin/promotions/calculators/_default_fields.html.erb +6 -0
- data/lib/views/backend/spree/admin/promotions/calculators/distributed_amount/_fields.html.erb +56 -0
- data/lib/views/backend/spree/admin/promotions/calculators/flat_rate/_fields.html.erb +6 -0
- data/lib/views/backend/spree/admin/promotions/calculators/tiered_flat_rate/_fields.html.erb +30 -0
- data/lib/views/backend/spree/admin/promotions/calculators/tiered_percent/_fields.html.erb +30 -0
- data/lib/views/backend/spree/admin/promotions/edit.html.erb +40 -0
- data/lib/views/backend/spree/admin/promotions/index.html.erb +124 -0
- data/lib/views/backend/spree/admin/promotions/new.html.erb +14 -0
- data/lib/views/backend/spree/admin/promotions/rules/_first_order.html.erb +0 -0
- data/lib/views/backend/spree/admin/promotions/rules/_first_repeat_purchase_since.html.erb +13 -0
- data/lib/views/backend/spree/admin/promotions/rules/_item_total.html.erb +14 -0
- data/lib/views/backend/spree/admin/promotions/rules/_minimum_quantity.html.erb +5 -0
- data/lib/views/backend/spree/admin/promotions/rules/_nth_order.html.erb +12 -0
- data/lib/views/backend/spree/admin/promotions/rules/_one_use_per_user.html.erb +0 -0
- data/lib/views/backend/spree/admin/promotions/rules/_option_value.html.erb +13 -0
- data/lib/views/backend/spree/admin/promotions/rules/_product.html.erb +15 -0
- data/lib/views/backend/spree/admin/promotions/rules/_store.html.erb +6 -0
- data/lib/views/backend/spree/admin/promotions/rules/_taxon.html.erb +9 -0
- data/lib/views/backend/spree/admin/promotions/rules/_user.html.erb +4 -0
- data/lib/views/backend/spree/admin/promotions/rules/_user_logged_in.html.erb +0 -0
- data/lib/views/backend/spree/admin/promotions/rules/_user_role.html.erb +12 -0
- data/solidus_legacy_promotions.gemspec +29 -0
- 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,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,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
|