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,125 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Spree
|
4
|
+
module PromotionHandler
|
5
|
+
class Coupon
|
6
|
+
attr_reader :order, :coupon_code
|
7
|
+
attr_accessor :error, :success, :status_code
|
8
|
+
|
9
|
+
def initialize(order)
|
10
|
+
@order = order
|
11
|
+
@coupon_code = order.coupon_code && order.coupon_code.downcase
|
12
|
+
end
|
13
|
+
|
14
|
+
def can_apply?
|
15
|
+
Spree::Promotion.order_activatable?(order)
|
16
|
+
end
|
17
|
+
|
18
|
+
def apply
|
19
|
+
if coupon_code.present?
|
20
|
+
if promotion.present? && promotion.active? && promotion.actions.exists?
|
21
|
+
handle_present_promotion(promotion)
|
22
|
+
elsif promotion_code&.promotion&.expired?
|
23
|
+
set_error_code :coupon_code_expired
|
24
|
+
else
|
25
|
+
set_error_code :coupon_code_not_found
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
def remove
|
33
|
+
if promotion.blank?
|
34
|
+
set_error_code :coupon_code_not_found
|
35
|
+
elsif !promotion_exists_on_order?(order, promotion)
|
36
|
+
set_error_code :coupon_code_not_present
|
37
|
+
else
|
38
|
+
promotion.remove_from(order)
|
39
|
+
order.recalculate
|
40
|
+
set_success_code :coupon_code_removed
|
41
|
+
end
|
42
|
+
|
43
|
+
self
|
44
|
+
end
|
45
|
+
|
46
|
+
def set_success_code(status_code)
|
47
|
+
@status_code = status_code
|
48
|
+
@success = I18n.t(status_code, scope: 'spree')
|
49
|
+
end
|
50
|
+
|
51
|
+
def set_error_code(status_code, options = {})
|
52
|
+
@status_code = status_code
|
53
|
+
@error = options[:error] || I18n.t(status_code, scope: 'spree')
|
54
|
+
end
|
55
|
+
|
56
|
+
def promotion
|
57
|
+
@promotion ||= if promotion_code && promotion_code.promotion.active?
|
58
|
+
promotion_code.promotion
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def successful?
|
63
|
+
success.present? && error.blank?
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def promotion_code
|
69
|
+
@promotion_code ||= Spree::PromotionCode.where(value: coupon_code).first
|
70
|
+
end
|
71
|
+
|
72
|
+
def handle_present_promotion(promotion)
|
73
|
+
return promotion_usage_limit_exceeded if promotion.usage_limit_exceeded? || promotion_code.usage_limit_exceeded?
|
74
|
+
return promotion_applied if promotion_exists_on_order?(order, promotion)
|
75
|
+
|
76
|
+
unless promotion.eligible?(order, promotion_code:)
|
77
|
+
set_promotion_eligibility_error_code(promotion)
|
78
|
+
return error || ineligible_for_this_order
|
79
|
+
end
|
80
|
+
|
81
|
+
# If any of the actions for the promotion return `true`,
|
82
|
+
# then result here will also be `true`.
|
83
|
+
result = promotion.activate(order:, promotion_code:)
|
84
|
+
if result
|
85
|
+
order.recalculate
|
86
|
+
set_success_code :coupon_code_applied
|
87
|
+
else
|
88
|
+
set_error_code :coupon_code_unknown_error
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def set_promotion_eligibility_error_code(promotion)
|
93
|
+
return unless eligibility_error_code_present?(promotion)
|
94
|
+
|
95
|
+
eligibility_error = promotion.eligibility_errors.details[:base].first
|
96
|
+
|
97
|
+
@status_code = eligibility_error[:error_code]
|
98
|
+
@error = eligibility_error[:error]
|
99
|
+
end
|
100
|
+
|
101
|
+
def promotion_usage_limit_exceeded
|
102
|
+
set_error_code :coupon_code_max_usage
|
103
|
+
end
|
104
|
+
|
105
|
+
def ineligible_for_this_order
|
106
|
+
set_error_code :coupon_code_not_eligible
|
107
|
+
end
|
108
|
+
|
109
|
+
def promotion_applied
|
110
|
+
set_error_code :coupon_code_already_applied
|
111
|
+
end
|
112
|
+
|
113
|
+
def promotion_exists_on_order?(order, promotion)
|
114
|
+
order.promotions.include? promotion
|
115
|
+
end
|
116
|
+
|
117
|
+
def eligibility_error_code_present?(promotion)
|
118
|
+
promotion.eligibility_errors.present? &&
|
119
|
+
promotion.eligibility_errors.details.present? &&
|
120
|
+
promotion.eligibility_errors.details.key?(:base) &&
|
121
|
+
promotion.eligibility_errors.details[:base].first[:error_code].present?
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Spree
|
4
|
+
module PromotionHandler
|
5
|
+
class Page
|
6
|
+
attr_reader :order, :path
|
7
|
+
|
8
|
+
def initialize(order, path)
|
9
|
+
@order = order
|
10
|
+
@path = path.delete_prefix('/')
|
11
|
+
end
|
12
|
+
|
13
|
+
def activate
|
14
|
+
if promotion && promotion.eligible?(order)
|
15
|
+
promotion.activate(order:)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def promotion
|
22
|
+
@promotion ||= Spree::Promotion.active.find_by(path:)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Spree
|
4
|
+
module PromotionHandler
|
5
|
+
# Used for activating promotions with shipping rules
|
6
|
+
class Shipping
|
7
|
+
attr_reader :order
|
8
|
+
attr_accessor :error, :success
|
9
|
+
|
10
|
+
def initialize(order)
|
11
|
+
@order = order
|
12
|
+
end
|
13
|
+
|
14
|
+
def activate
|
15
|
+
connected_promotions.each do |order_promotion|
|
16
|
+
if order_promotion.promotion.eligible?(order)
|
17
|
+
order_promotion.promotion.activate(
|
18
|
+
order:,
|
19
|
+
promotion_code: order_promotion.promotion_code,
|
20
|
+
)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
not_connected_automatic_promotions.each do |promotion|
|
25
|
+
if promotion.eligible?(order)
|
26
|
+
promotion.activate(order:)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def not_connected_automatic_promotions
|
34
|
+
automatic_promotions - connected_promotions.map(&:promotion)
|
35
|
+
end
|
36
|
+
|
37
|
+
def automatic_promotions
|
38
|
+
@automatic_promotions ||= active_shipping_promotions.
|
39
|
+
where(apply_automatically: true).
|
40
|
+
to_a.
|
41
|
+
uniq
|
42
|
+
end
|
43
|
+
|
44
|
+
def connected_promotions
|
45
|
+
@connected_promotions ||= order.order_promotions.
|
46
|
+
joins(:promotion).
|
47
|
+
includes(:promotion).
|
48
|
+
merge(active_shipping_promotions).
|
49
|
+
to_a.
|
50
|
+
uniq
|
51
|
+
end
|
52
|
+
|
53
|
+
def active_shipping_promotions
|
54
|
+
Spree::Promotion.all.
|
55
|
+
active.
|
56
|
+
joins(:promotion_actions).
|
57
|
+
merge(Spree::PromotionAction.shipping)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spree/preferences/persistable'
|
4
|
+
|
5
|
+
module Spree
|
6
|
+
# Base class for all promotion rules
|
7
|
+
class PromotionRule < Spree::Base
|
8
|
+
include Spree::Preferences::Persistable
|
9
|
+
|
10
|
+
belongs_to :promotion, class_name: 'Spree::Promotion', inverse_of: :promotion_rules, optional: true
|
11
|
+
|
12
|
+
scope :of_type, ->(type) { where(type:) }
|
13
|
+
|
14
|
+
validates :promotion, presence: true
|
15
|
+
validate :unique_per_promotion, on: :create
|
16
|
+
|
17
|
+
def preload_relations
|
18
|
+
[]
|
19
|
+
end
|
20
|
+
|
21
|
+
def applicable?(_promotable)
|
22
|
+
raise NotImplementedError, "applicable? should be implemented in a sub-class of Spree::PromotionRule"
|
23
|
+
end
|
24
|
+
|
25
|
+
def eligible?(_promotable, _options = {})
|
26
|
+
raise NotImplementedError, "eligible? should be implemented in a sub-class of Spree::PromotionRule"
|
27
|
+
end
|
28
|
+
|
29
|
+
# This states if a promotion can be applied to the specified line item
|
30
|
+
# It is true by default, but can be overridden by promotion rules to provide conditions
|
31
|
+
def actionable?(_line_item)
|
32
|
+
true
|
33
|
+
end
|
34
|
+
|
35
|
+
def eligibility_errors
|
36
|
+
@eligibility_errors ||= ActiveModel::Errors.new(self)
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_partial_path
|
40
|
+
"spree/admin/promotions/rules/#{model_name.element}"
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def unique_per_promotion
|
46
|
+
if Spree::PromotionRule.exists?(promotion_id:, type: self.class.name)
|
47
|
+
errors[:base] << "Promotion already contains this rule type"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def eligibility_error_message(key, options = {})
|
52
|
+
I18n.t(key, **{ scope: [:spree, :eligibility_errors, :messages] }.merge(options))
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Spree
|
4
|
+
class PromotionRuleStore < Spree::Base
|
5
|
+
self.table_name = "spree_promotion_rules_stores"
|
6
|
+
|
7
|
+
belongs_to :promotion_rule, class_name: "Spree::PromotionRule", optional: true
|
8
|
+
belongs_to :store, class_name: "Spree::Store", optional: true
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Spree
|
4
|
+
class PromotionRuleUser < Spree::Base
|
5
|
+
self.table_name = 'spree_promotion_rules_users'
|
6
|
+
|
7
|
+
belongs_to :promotion_rule, class_name: 'Spree::PromotionRule', optional: true
|
8
|
+
belongs_to :user, class_name: Spree::UserClassHandle.new, optional: true
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Spree
|
4
|
+
# Clears promotions from an emptied order
|
5
|
+
class OrderPromotionSubscriber
|
6
|
+
include Omnes::Subscriber
|
7
|
+
|
8
|
+
handle :order_emptied,
|
9
|
+
with: :clear_order_promotions,
|
10
|
+
id: :spree_order_promotion_clear_order_promotions
|
11
|
+
|
12
|
+
# Clears all promotions from the order
|
13
|
+
#
|
14
|
+
# @param event [Omnes::UnstructuredEvent]
|
15
|
+
def clear_order_promotions(event)
|
16
|
+
order = event[:order]
|
17
|
+
order.order_promotions.destroy_all
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
<table>
|
2
|
+
<tr>
|
3
|
+
<td>
|
4
|
+
<p class="lede">
|
5
|
+
<%= t('.dear_customer') %>
|
6
|
+
</p>
|
7
|
+
<p>
|
8
|
+
<%= t('.instructions') %>
|
9
|
+
</p>
|
10
|
+
<p>
|
11
|
+
<%= t('.order_summary_canceled') %>
|
12
|
+
</p>
|
13
|
+
<table>
|
14
|
+
<% @order.line_items.each do |item| %>
|
15
|
+
<tr>
|
16
|
+
<td><%= item.variant.sku %></td>
|
17
|
+
<td>
|
18
|
+
<%= item.variant.product.name %>
|
19
|
+
<%= item.variant.options_text -%>
|
20
|
+
</td>
|
21
|
+
<td>(<%=item.quantity%>) @ <%= item.single_money %> = <%= item.display_amount %></td>
|
22
|
+
</tr>
|
23
|
+
<% end %>
|
24
|
+
<tr>
|
25
|
+
<td></td>
|
26
|
+
<td><%= t('.subtotal') %></td>
|
27
|
+
<td><%= @order.display_item_total %></td>
|
28
|
+
</tr>
|
29
|
+
<% @order.adjustments.eligible.each do |adjustment| %>
|
30
|
+
<tr>
|
31
|
+
<td></td>
|
32
|
+
<td><%= sanitize(adjustment.label) %></td>
|
33
|
+
<td><%= adjustment.display_amount %></td>
|
34
|
+
</tr>
|
35
|
+
<% end %>
|
36
|
+
<tr>
|
37
|
+
<td></td>
|
38
|
+
<td><%= t('.total') %></td>
|
39
|
+
<td><%= @order.display_total %></td>
|
40
|
+
</tr>
|
41
|
+
</table>
|
42
|
+
</td>
|
43
|
+
<td class="expander"></td>
|
44
|
+
</tr>
|
45
|
+
</table>
|
@@ -0,0 +1,16 @@
|
|
1
|
+
<%= t('.dear_customer') %>
|
2
|
+
|
3
|
+
<%= t('.instructions') %>
|
4
|
+
|
5
|
+
============================================================
|
6
|
+
<%= t('.order_summary_canceled') %>
|
7
|
+
============================================================
|
8
|
+
<% @order.line_items.each do |item| %>
|
9
|
+
<%= item.variant.sku %> <%= raw(item.variant.product.name) %> <%= raw(item.variant.options_text) -%> (<%=item.quantity%>) @ <%= item.single_money %> = <%= item.display_amount %>
|
10
|
+
<% end %>
|
11
|
+
============================================================
|
12
|
+
<%= t('.subtotal') %> <%= @order.display_item_total %>
|
13
|
+
<% @order.adjustments.eligible.each do |adjustment| %>
|
14
|
+
<%= raw(adjustment.label) %> <%= adjustment.display_amount %>
|
15
|
+
<% end %>
|
16
|
+
<%= t('.total') %> <%= @order.display_total %>
|
@@ -0,0 +1,84 @@
|
|
1
|
+
<table>
|
2
|
+
<tr>
|
3
|
+
<td>
|
4
|
+
<p class="lede">
|
5
|
+
<%= t('.dear_customer') %>
|
6
|
+
</p>
|
7
|
+
<p>
|
8
|
+
<%= t('.instructions') %>
|
9
|
+
</p>
|
10
|
+
<p>
|
11
|
+
<%= t('.order_summary') %>
|
12
|
+
</p>
|
13
|
+
<table>
|
14
|
+
<% @order.line_items.each do |item| %>
|
15
|
+
<tr>
|
16
|
+
<td><%= item.variant.sku %></td>
|
17
|
+
<td>
|
18
|
+
<%= item.variant.product.name %>
|
19
|
+
<%= item.variant.options_text -%>
|
20
|
+
</td>
|
21
|
+
<td>(<%=item.quantity%>) @ <%= item.single_money %> = <%= item.display_amount %></td>
|
22
|
+
</tr>
|
23
|
+
<% end %>
|
24
|
+
<tr>
|
25
|
+
<td></td>
|
26
|
+
<td>
|
27
|
+
<%= t('.subtotal') %>
|
28
|
+
</td>
|
29
|
+
<td>
|
30
|
+
<%= @order.display_item_total %>
|
31
|
+
</td>
|
32
|
+
</tr>
|
33
|
+
<% if @order.line_item_adjustments.exists? %>
|
34
|
+
<% if @order.all_adjustments.promotion.eligible.exists? %>
|
35
|
+
<% @order.all_adjustments.promotion.eligible.group_by(&:label).each do |label, adjustments| %>
|
36
|
+
<tr>
|
37
|
+
<td></td>
|
38
|
+
<td><%= t('spree.promotion') %> <%= label %>:</td>
|
39
|
+
<td><%= Spree::Money.new(adjustments.sum(&:amount), currency: @order.currency) %></td>
|
40
|
+
</tr>
|
41
|
+
<% end %>
|
42
|
+
<% end %>
|
43
|
+
<% end %>
|
44
|
+
<% @order.shipments.group_by { |s| s.selected_shipping_rate.try(:name) }.each do |name, shipments| %>
|
45
|
+
<tr>
|
46
|
+
<td></td>
|
47
|
+
<td><%= t('spree.shipping') %> <%= name %>:</td>
|
48
|
+
<td><%= Spree::Money.new(shipments.sum(&:total_before_tax), currency: @order.currency) %></td>
|
49
|
+
</tr>
|
50
|
+
<% end %>
|
51
|
+
<% if @order.all_adjustments.eligible.tax.exists? %>
|
52
|
+
<% @order.all_adjustments.eligible.tax.group_by(&:label).each do |label, adjustments| %>
|
53
|
+
<tr>
|
54
|
+
<td></td>
|
55
|
+
<td><%= t('spree.tax') %> <%= label %>:</td>
|
56
|
+
<td><%= Spree::Money.new(adjustments.sum(&:amount), currency: @order.currency) %></td>
|
57
|
+
</tr>
|
58
|
+
<% end %>
|
59
|
+
<% end %>
|
60
|
+
<% @order.adjustments.eligible.each do |adjustment| %>
|
61
|
+
<% next if (adjustment.source_type == 'Spree::TaxRate') and (adjustment.amount == 0) %>
|
62
|
+
<tr>
|
63
|
+
<td></td>
|
64
|
+
<td><%= adjustment.label %>:</td>
|
65
|
+
<td><%= adjustment.display_amount %></td>
|
66
|
+
</tr>
|
67
|
+
<% end %>
|
68
|
+
<tr>
|
69
|
+
<td></td>
|
70
|
+
<td>
|
71
|
+
<%= t('.total') %>
|
72
|
+
</td>
|
73
|
+
<td>
|
74
|
+
<%= @order.display_total %>
|
75
|
+
</td>
|
76
|
+
</tr>
|
77
|
+
</table>
|
78
|
+
<p>
|
79
|
+
<%= t('.thanks') %>
|
80
|
+
</p>
|
81
|
+
</td>
|
82
|
+
<td class="expander"></td>
|
83
|
+
</tr>
|
84
|
+
</table>
|
@@ -0,0 +1,38 @@
|
|
1
|
+
<%= t('.dear_customer') %>
|
2
|
+
|
3
|
+
<%= t('.instructions') %>
|
4
|
+
|
5
|
+
============================================================
|
6
|
+
<%= t('.order_summary') %>
|
7
|
+
============================================================
|
8
|
+
<% @order.line_items.each do |item| %>
|
9
|
+
<%= item.variant.sku %> <%= raw(item.variant.product.name) %> <%= raw(item.variant.options_text) -%> (<%=item.quantity%>) @ <%= item.single_money %> = <%= item.display_amount %>
|
10
|
+
<% end %>
|
11
|
+
============================================================
|
12
|
+
<%= t('.subtotal') %> <%= @order.display_item_total %>
|
13
|
+
<% if @order.line_item_adjustments.exists? %>
|
14
|
+
<% if @order.all_adjustments.promotion.eligible.exists? %>
|
15
|
+
<% @order.all_adjustments.promotion.eligible.group_by(&:label).each do |label, adjustments| %>
|
16
|
+
<%= t('spree.promotion') %>: <%= label %> <%= Spree::Money.new(adjustments.sum(&:amount), currency: @order.currency) %>
|
17
|
+
<% end %>
|
18
|
+
<% end %>
|
19
|
+
<% end %>
|
20
|
+
|
21
|
+
<% @order.shipments.group_by { |s| s.selected_shipping_rate.try(:name) }.each do |name, shipments| %>
|
22
|
+
<%= t('spree.shipping') %>: <%= name %> <%= Spree::Money.new(shipments.sum(&:total_before_tax), currency: @order.currency) %>
|
23
|
+
<% end %>
|
24
|
+
|
25
|
+
<% if @order.all_adjustments.eligible.tax.exists? %>
|
26
|
+
<% @order.all_adjustments.eligible.tax.group_by(&:label).each do |label, adjustments| %>
|
27
|
+
<%= t('spree.tax') %>: <%= label %> <%= Spree::Money.new(adjustments.sum(&:amount), currency: @order.currency) %>
|
28
|
+
<% end %>
|
29
|
+
<% end %>
|
30
|
+
|
31
|
+
<% @order.adjustments.eligible.each do |adjustment| %>
|
32
|
+
<% next if (adjustment.source_type == 'Spree::TaxRate') and (adjustment.amount == 0) %>
|
33
|
+
<%= adjustment.label %> <%= adjustment.display_amount %>
|
34
|
+
<% end %>
|
35
|
+
============================================================
|
36
|
+
<%= t('.total') %> <%= @order.display_total %>
|
37
|
+
|
38
|
+
<%= t('.thanks') %>
|
data/bin/rails
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# This command will automatically be run when you run "rails" with Rails gems
|
3
|
+
# installed from the root of your application.
|
4
|
+
|
5
|
+
ENGINE_ROOT = File.expand_path('..', __dir__)
|
6
|
+
ENGINE_PATH = File.expand_path('../lib/solidus_legacy_promotions/engine', __dir__)
|
7
|
+
|
8
|
+
# Set up gems listed in the Gemfile.
|
9
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __dir__)
|
10
|
+
require "bundler/setup" if File.exist?(ENV["BUNDLE_GEMFILE"])
|
11
|
+
|
12
|
+
require "rails/all"
|
13
|
+
require "rails/engine/commands"
|
@@ -0,0 +1,101 @@
|
|
1
|
+
en:
|
2
|
+
spree:
|
3
|
+
admin:
|
4
|
+
promotion_status:
|
5
|
+
active: Active
|
6
|
+
expired: Expired
|
7
|
+
inactive: Inactive
|
8
|
+
not_started: Not started
|
9
|
+
promotions:
|
10
|
+
actions:
|
11
|
+
calculator_label: Calculated by
|
12
|
+
activations_edit:
|
13
|
+
auto: All orders will attempt to use this promotion
|
14
|
+
multiple_codes_html: This promotion uses %{count} promotion codes
|
15
|
+
single_code_html: 'This promotion uses the promotion code: <code>%{code}</code>'
|
16
|
+
activations_new:
|
17
|
+
auto: Apply to all orders
|
18
|
+
multiple_codes: Multiple promotion codes
|
19
|
+
single_code: Single promotion code
|
20
|
+
form:
|
21
|
+
activation: Activation
|
22
|
+
expires_at_placeholder: Never
|
23
|
+
general: General
|
24
|
+
starts_at_placeholder: Immediately
|
25
|
+
tab:
|
26
|
+
promotion_categories: Promotion Categories
|
27
|
+
promotions: Promotions
|
28
|
+
back_to_promotion_categories_list: Back To Promotions Categories List
|
29
|
+
back_to_promotions_list: Back To Promotions List
|
30
|
+
base_amount: Base Amount
|
31
|
+
choose_promotion_action: Choose Action
|
32
|
+
choose_promotion_rule: Choose Rule
|
33
|
+
create_promotion_code: Create promotion code
|
34
|
+
current_promotion_usage: 'Current Usage: %{count}'
|
35
|
+
discount_rules: Discount Rules
|
36
|
+
download_promotion_codes_list: Download codes list
|
37
|
+
editing_promotion: Editing Promotion
|
38
|
+
editing_promotion_category: Editing Promotion Category
|
39
|
+
group_size: Group size
|
40
|
+
hints:
|
41
|
+
spree/calculator:
|
42
|
+
promotions: This is used to determine the promotional discount to be applied to an order, an item, or shipping charges.
|
43
|
+
spree/promotion:
|
44
|
+
expires_at: This determines when the promotion expires. <br> If no value is specified, the promotion will never expire.
|
45
|
+
promo_code_will_be_disabled: Selecting this option, promo codes will be disabled for this promotion because all its rules / actions will be applied automatically to all orders.
|
46
|
+
starts_at: This determines when the promotion can be applied to orders. <br> If no value is specified, the promotion will be immediately available.
|
47
|
+
invalid_promotion_action: Invalid promotion action.
|
48
|
+
invalid_promotion_rule: Invalid promotion rule.
|
49
|
+
manage_promotion_categories: Manage Promotion Categories
|
50
|
+
match_choices:
|
51
|
+
all: All
|
52
|
+
none: None
|
53
|
+
new_promotion: New Promotion
|
54
|
+
new_promotion_category: New Promotion Category
|
55
|
+
new_promotion_code_batch: New Promotion Code Batch
|
56
|
+
no_actions_added: No actions added
|
57
|
+
no_rules_added: No rules added
|
58
|
+
no_promotions_found: No promotions found
|
59
|
+
product_rule:
|
60
|
+
choose_products: Choose products
|
61
|
+
label: Order must contain %{select} of these products
|
62
|
+
match_all: all
|
63
|
+
match_any: at least one
|
64
|
+
match_none: none
|
65
|
+
product_source:
|
66
|
+
group: From product group
|
67
|
+
manual: Manually choose
|
68
|
+
promotion: Promotion
|
69
|
+
promotion_action: Promotion Action
|
70
|
+
promotion_actions: Actions
|
71
|
+
promotion_form:
|
72
|
+
match_policies:
|
73
|
+
all: Match all of these rules
|
74
|
+
any: Match any of these rules
|
75
|
+
promotion_rule: Promotion Rule
|
76
|
+
promotion_successfully_created: Promotion has been successfully created!
|
77
|
+
promotion_uses: Promotion uses
|
78
|
+
promotionable: Promotable
|
79
|
+
promotions: Promotions
|
80
|
+
rules: Rules
|
81
|
+
store_rule:
|
82
|
+
choose_stores: Choose Stores
|
83
|
+
taxon_rule:
|
84
|
+
choose_taxons: Choose taxons
|
85
|
+
label: Order must contain %{select} of these taxons
|
86
|
+
match_all: all
|
87
|
+
match_any: at least one
|
88
|
+
match_none: none
|
89
|
+
tiers: Tiers
|
90
|
+
promotion_code_batches:
|
91
|
+
errored: 'Errored: %{error}'
|
92
|
+
finished: All %{number_of_codes} codes have been created.
|
93
|
+
processing: 'Processing: %{number_of_codes_processed} / %{number_of_codes}'
|
94
|
+
user_role_rule:
|
95
|
+
choose_roles: Choose Roles
|
96
|
+
label: User must contain %{select} of these roles
|
97
|
+
match_all: all
|
98
|
+
match_any: at least one
|
99
|
+
user_rule:
|
100
|
+
choose_users: Choose Users
|
101
|
+
view_promotion_codes_list: View codes list
|
data/config/routes.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
if SolidusSupport.backend_available?
|
4
|
+
Spree::Core::Engine.routes.draw do
|
5
|
+
namespace :admin do
|
6
|
+
resources :promotions do
|
7
|
+
resources :promotion_rules
|
8
|
+
resources :promotion_actions
|
9
|
+
resources :promotion_codes, only: [:index, :new, :create]
|
10
|
+
resources :promotion_code_batches, only: [:index, :new, :create] do
|
11
|
+
get '/download', to: "promotion_code_batches#download", defaults: { format: "csv" }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
resources :promotion_categories, except: [:show]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
if SolidusSupport.admin_available?
|
21
|
+
SolidusAdmin::Engine.routes.draw do
|
22
|
+
require "solidus_admin/admin_resources"
|
23
|
+
extend SolidusAdmin::AdminResources
|
24
|
+
|
25
|
+
admin_resources :promotions, only: [:index, :destroy]
|
26
|
+
admin_resources :promotion_categories, only: [:index, :destroy]
|
27
|
+
end
|
28
|
+
end
|