solidus_core 2.11.10 → 3.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Rakefile +6 -2
- data/app/assets/javascripts/spree.js.erb +0 -51
- data/app/controllers/spree/base_controller.rb +1 -1
- data/app/helpers/spree/base_helper.rb +1 -1
- data/app/helpers/spree/products_helper.rb +2 -2
- data/app/helpers/spree/store_helper.rb +0 -11
- data/app/mailers/spree/carton_mailer.rb +1 -5
- data/app/models/concerns/spree/active_storage_adapter/attachment.rb +30 -11
- data/app/models/concerns/spree/active_storage_adapter.rb +1 -1
- data/app/models/concerns/spree/adjustment_source.rb +0 -15
- data/app/models/concerns/spree/calculated_adjustments.rb +0 -18
- data/app/models/concerns/spree/default_price.rb +39 -10
- data/app/models/concerns/spree/ransackable_attributes.rb +24 -4
- data/app/models/concerns/spree/soft_deletable.rb +2 -4
- data/app/models/concerns/spree/user_address_book.rb +10 -37
- data/app/models/concerns/spree/user_methods.rb +38 -13
- data/app/models/spree/ability.rb +0 -37
- data/app/models/spree/address/name.rb +2 -20
- data/app/models/spree/address.rb +8 -186
- data/app/models/spree/adjustment.rb +7 -33
- data/app/models/spree/base.rb +0 -53
- data/app/models/spree/calculator/flat_fee.rb +21 -0
- data/app/models/spree/calculator/flexi_rate.rb +0 -5
- data/app/models/spree/calculator.rb +0 -7
- data/app/models/spree/carton.rb +1 -1
- data/app/models/spree/country.rb +2 -7
- data/app/models/spree/credit_card.rb +1 -28
- data/app/models/spree/customer_return.rb +5 -7
- data/app/models/spree/image/active_storage_attachment.rb +2 -7
- data/app/models/spree/image/paperclip_attachment.rb +2 -2
- data/app/models/spree/image.rb +0 -7
- data/app/models/spree/inventory_unit.rb +0 -21
- data/app/models/spree/line_item.rb +6 -49
- data/app/models/spree/log_entry.rb +74 -1
- data/app/models/spree/option_type.rb +1 -1
- data/app/models/spree/option_value.rb +10 -1
- data/app/models/spree/order/number_generator.rb +7 -1
- data/app/models/spree/order.rb +82 -170
- data/app/models/spree/order_cancellations.rb +4 -24
- data/app/models/spree/order_contents.rb +2 -1
- data/app/models/spree/order_inventory.rb +1 -1
- data/app/models/spree/order_merger.rb +2 -2
- data/app/models/spree/order_promotion.rb +1 -1
- data/app/models/spree/order_shipping.rb +6 -9
- data/app/models/spree/order_taxation.rb +6 -4
- data/app/models/spree/order_updater.rb +17 -16
- data/app/models/spree/payment/cancellation.rb +1 -1
- data/app/models/spree/payment/processing.rb +58 -55
- data/app/models/spree/payment.rb +0 -3
- data/app/models/spree/payment_create.rb +1 -13
- data/app/models/spree/payment_method/bogus_credit_card.rb +6 -7
- data/app/models/spree/payment_method/credit_card.rb +1 -3
- data/app/models/spree/payment_method/simple_bogus_credit_card.rb +8 -0
- data/app/models/spree/payment_method.rb +26 -110
- data/app/models/spree/price.rb +3 -3
- data/app/models/spree/product/scopes.rb +24 -33
- data/app/models/spree/product.rb +15 -42
- data/app/models/spree/product_property.rb +1 -1
- data/app/models/spree/promotion/actions/create_adjustment.rb +4 -3
- data/app/models/spree/promotion/actions/create_item_adjustments.rb +5 -9
- data/app/models/spree/promotion/actions/create_quantity_adjustments.rb +0 -3
- data/app/models/spree/promotion/actions/free_shipping.rb +1 -0
- data/app/models/spree/promotion/order_adjustments_recalculator.rb +92 -0
- data/app/models/spree/promotion/rules/item_total.rb +50 -6
- data/app/models/spree/promotion/rules/product.rb +20 -8
- data/app/models/spree/promotion/rules/store.rb +4 -0
- data/app/models/spree/promotion/rules/taxon.rb +6 -15
- data/app/models/spree/promotion/rules/user.rb +4 -0
- data/app/models/spree/promotion.rb +39 -32
- data/app/models/spree/promotion_action.rb +6 -9
- data/app/models/spree/promotion_code/batch_builder.rb +0 -14
- data/app/models/spree/promotion_code.rb +11 -7
- data/app/models/spree/promotion_handler/cart.rb +26 -6
- data/app/models/spree/promotion_rule.rb +5 -0
- data/app/models/spree/property.rb +1 -1
- data/app/models/spree/refund.rb +8 -52
- data/app/models/spree/reimbursement.rb +5 -43
- data/app/models/spree/reimbursement_performer.rb +2 -8
- data/app/models/spree/reimbursement_type/credit.rb +1 -4
- data/app/models/spree/reimbursement_type/reimbursement_helpers.rb +1 -2
- data/app/models/spree/reimbursement_type/store_credit.rb +1 -4
- data/app/models/spree/return_authorization.rb +2 -5
- data/app/models/spree/return_item.rb +4 -24
- data/app/models/spree/shipment.rb +3 -56
- data/app/models/spree/shipping_method.rb +0 -25
- data/app/models/spree/shipping_rate.rb +0 -2
- data/app/models/spree/shipping_rate_tax.rb +1 -1
- data/app/models/spree/state.rb +1 -5
- data/app/models/spree/stock/allocator/on_hand_first.rb +2 -2
- data/app/models/spree/stock/availability.rb +11 -3
- data/app/models/spree/stock/quantifier.rb +12 -8
- data/app/models/spree/stock/simple_coordinator.rb +8 -26
- data/app/models/spree/stock/splitter/base.rb +2 -7
- data/app/models/spree/stock_item.rb +2 -8
- data/app/models/spree/stock_location.rb +2 -2
- data/app/models/spree/stock_movement.rb +2 -2
- data/app/models/spree/store.rb +0 -12
- data/app/models/spree/store_credit.rb +14 -1
- data/app/models/spree/store_credit_category.rb +0 -32
- data/app/models/spree/store_credit_prioritizer.rb +17 -0
- data/app/models/spree/tax/item_tax.rb +3 -2
- data/app/models/spree/tax/order_tax.rb +3 -1
- data/app/models/spree/tax/tax_helpers.rb +2 -2
- data/app/models/spree/tax/tax_location.rb +4 -7
- data/app/models/spree/tax_calculator/default.rb +31 -0
- data/app/models/spree/tax_calculator/shipping_rate.rb +2 -13
- data/app/models/spree/tax_rate.rb +9 -27
- data/app/models/spree/taxon/active_storage_attachment.rb +2 -7
- data/app/models/spree/taxon/paperclip_attachment.rb +3 -8
- data/app/models/spree/taxon.rb +1 -12
- data/app/models/spree/taxonomy.rb +1 -1
- data/app/models/spree/user_address.rb +0 -5
- data/app/models/spree/user_last_url_storer/rules/authentication_rule.rb +1 -1
- data/app/models/spree/variant/price_selector.rb +34 -4
- data/app/models/spree/variant.rb +52 -66
- data/app/models/spree/zone.rb +1 -1
- data/app/subscribers/spree/mailer_subscriber.rb +4 -0
- data/app/subscribers/spree/order_mailer_subscriber.rb +35 -0
- data/config/i18n-tasks.yml +134 -0
- data/config/locales/en.yml +406 -263
- data/db/migrate/20180416083007_add_apply_to_all_to_variant_property_rule.rb +1 -1
- data/db/migrate/20201127212108_add_type_before_removal_to_spree_payment_methods.rb +7 -0
- data/db/migrate/20210312061050_change_column_null_on_prices.rb +7 -0
- data/db/migrate/20220317165036_set_promotions_with_any_policy_to_all_if_possible.rb +20 -0
- data/db/migrate/20220805202442_add_level_to_spree_tax_rates.rb +5 -0
- data/db/migrate/20221123152807_add_shipping_category_to_spree_variants.rb +5 -0
- data/db/seeds.rb +4 -1
- data/lib/generators/solidus/install/app_templates/authentication/custom.rb +21 -0
- data/lib/generators/solidus/install/app_templates/authentication/devise.rb +16 -0
- data/lib/generators/solidus/install/app_templates/authentication/existing.rb +10 -0
- data/lib/generators/solidus/install/app_templates/authentication/none.rb +1 -0
- data/lib/generators/solidus/install/app_templates/frontend/break_down_solidus_gem.rb +54 -0
- data/lib/generators/solidus/install/app_templates/frontend/classic.rb +16 -0
- data/lib/generators/solidus/install/app_templates/frontend/none.rb +2 -0
- data/lib/generators/solidus/install/app_templates/frontend/starter.rb +3 -0
- data/lib/generators/solidus/install/app_templates/payment_method/bolt.rb +13 -0
- data/lib/generators/solidus/install/app_templates/payment_method/none.rb +1 -0
- data/lib/generators/solidus/install/app_templates/payment_method/paypal.rb +10 -0
- data/lib/generators/solidus/install/install_generator.rb +247 -149
- data/lib/generators/solidus/install/templates/config/initializers/spree.rb.tt +15 -60
- data/lib/generators/solidus/install/templates/vendor/assets/javascripts/spree/backend/all.js +2 -2
- data/lib/generators/solidus/update/templates/config/initializers/new_solidus_defaults.rb.tt +30 -0
- data/lib/generators/solidus/update/update_generator.rb +112 -0
- data/lib/generators/spree/custom_user/custom_user_generator.rb +6 -4
- data/lib/generators/spree/custom_user/templates/authentication_helpers.rb.tt +2 -6
- data/lib/generators/spree/custom_user/templates/migration.rb.tt +7 -3
- data/lib/generators/spree/dummy/dummy_generator.rb +12 -9
- data/lib/generators/spree/dummy/templates/rails/application.rb.tt +0 -1
- data/lib/generators/spree/dummy/templates/rails/database.yml +81 -39
- data/lib/generators/spree/dummy/templates/rails/storage.yml +3 -0
- data/lib/generators/spree/dummy/templates/rails/test.rb +2 -0
- data/lib/spree/app_configuration.rb +134 -64
- data/lib/spree/bus.rb +20 -0
- data/lib/spree/core/class_constantizer.rb +2 -0
- data/lib/spree/core/controller_helpers/auth.rb +10 -15
- data/lib/spree/core/controller_helpers/current_host.rb +5 -3
- data/lib/spree/core/controller_helpers/order.rb +12 -32
- data/lib/spree/core/controller_helpers/payment_parameters.rb +0 -54
- data/lib/spree/core/controller_helpers/pricing.rb +0 -8
- data/lib/spree/core/controller_helpers/search.rb +1 -1
- data/lib/spree/core/controller_helpers/strong_parameters.rb +0 -4
- data/lib/spree/core/engine.rb +54 -50
- data/lib/spree/core/environment_extension.rb +0 -9
- data/lib/spree/core/product_filters.rb +1 -41
- data/lib/spree/core/role_configuration.rb +0 -14
- data/lib/spree/core/search/base.rb +18 -35
- data/lib/spree/core/state_machines/order.rb +2 -2
- data/lib/spree/core/state_machines.rb +2 -11
- data/lib/spree/core/stock_configuration.rb +18 -0
- data/lib/spree/core/validators/email.rb +5 -3
- data/lib/spree/core/version.rb +5 -1
- data/lib/spree/core/versioned_value.rb +75 -0
- data/lib/spree/core.rb +40 -11
- data/lib/spree/deprecation.rb +1 -1
- data/lib/spree/event/configuration.rb +0 -5
- data/lib/spree/event/subscriber.rb +0 -18
- data/lib/spree/event/subscriber_registry.rb +7 -7
- data/lib/spree/event.rb +1 -32
- data/lib/spree/i18n.rb +0 -22
- data/lib/spree/migrations.rb +13 -11
- data/lib/spree/money.rb +3 -18
- data/lib/spree/permission_sets/default_customer.rb +8 -1
- data/lib/spree/permitted_attributes.rb +17 -59
- data/lib/spree/preferences/configuration.rb +84 -0
- data/lib/spree/preferences/preferable.rb +13 -0
- data/lib/spree/preferences/preferable_class_methods.rb +37 -4
- data/lib/spree/preferences/preference_differentiator.rb +29 -0
- data/lib/spree/preferences/static_model_preferences.rb +25 -10
- data/lib/spree/rails_compatibility.rb +106 -0
- data/lib/spree/testing_support/blacklist_urls.rb +1 -1
- data/lib/spree/testing_support/bus_helpers.rb +101 -0
- data/lib/spree/testing_support/capybara_ext.rb +0 -30
- data/lib/spree/testing_support/common_rake.rb +71 -23
- data/lib/spree/testing_support/controller_requests.rb +0 -82
- data/lib/spree/testing_support/dummy_app/assets/javascripts/spree/backend/all.js +1 -1
- data/lib/spree/testing_support/dummy_app/assets/javascripts/spree/frontend/all.js +1 -1
- data/lib/spree/testing_support/dummy_app/database.yml +42 -22
- data/lib/spree/testing_support/dummy_app/migrations.rb +0 -3
- data/lib/spree/testing_support/dummy_app.rb +47 -34
- data/lib/spree/testing_support/factories/address_factory.rb +9 -6
- data/lib/spree/testing_support/factories/calculator_factory.rb +3 -0
- data/lib/spree/testing_support/factories/country_factory.rb +1 -2
- data/lib/spree/testing_support/factories/inventory_unit_factory.rb +1 -1
- data/lib/spree/testing_support/factories/order_factory.rb +8 -5
- data/lib/spree/testing_support/factories/product_factory.rb +4 -1
- data/lib/spree/testing_support/factories/promotion_factory.rb +28 -14
- data/lib/spree/testing_support/factories/refund_factory.rb +0 -1
- data/lib/spree/testing_support/factories/state_factory.rb +8 -2
- data/lib/spree/testing_support/factories/store_credit_factory.rb +4 -4
- data/lib/spree/testing_support/factories/user_factory.rb +6 -0
- data/lib/spree/testing_support/factory_bot.rb +2 -2
- data/lib/spree/testing_support/order_walkthrough.rb +6 -8
- data/lib/spree/testing_support/preferences.rb +0 -25
- data/lib/spree/testing_support/silence_deprecations.rb +9 -0
- data/lib/tasks/colorado_delivery_fee.rake +28 -0
- data/lib/tasks/payment_method.rake +29 -0
- data/lib/tasks/solidus/check_orders_with_invalid_email.rake +18 -0
- data/lib/tasks/solidus/delete_prices_with_nil_amount.rake +8 -0
- data/lib/tasks/solidus/split_promotions_with_any_match_policy.rake +33 -0
- data/solidus_core.gemspec +14 -7
- metadata +127 -78
- data/app/mailers/spree/test_mailer.rb +0 -13
- data/app/models/concerns/spree/user_payment_source.rb +0 -26
- data/app/models/spree/calculator/free_shipping.rb +0 -22
- data/app/models/spree/calculator/percent_per_item.rb +0 -51
- data/app/models/spree/calculator/price_sack.rb +0 -28
- data/app/models/spree/gateway/bogus.rb +0 -13
- data/app/models/spree/gateway/bogus_simple.rb +0 -13
- data/app/models/spree/gateway.rb +0 -14
- data/app/models/spree/order/checkout.rb +0 -244
- data/app/models/spree/order_capturing.rb +0 -50
- data/app/models/spree/promotion_handler/free_shipping.rb +0 -9
- data/app/models/spree/tax/shipping_rate_taxer.rb +0 -24
- data/lib/generators/solidus/install/templates/vendor/assets/javascripts/spree/frontend/all.js +0 -10
- data/lib/generators/solidus/install/templates/vendor/assets/stylesheets/spree/frontend/all.css +0 -9
- data/lib/generators/spree/install/install_generator.rb +0 -15
- data/lib/solidus/migrations/rename_gateways.rb +0 -41
- data/lib/spree/core/current_store.rb +0 -24
- data/lib/spree/paranoia_deprecations.rb +0 -41
- data/lib/spree/promo/environment.rb +0 -12
- data/lib/spree/testing_support/bar_ability.rb +0 -19
- data/lib/tasks/core.rake +0 -104
- data/lib/tasks/email.rake +0 -12
- data/lib/tasks/migrations/copy_order_bill_address_to_credit_card.rake +0 -119
- data/lib/tasks/migrations/migrate_address_names.rake +0 -158
- data/lib/tasks/migrations/migrate_default_billing_addresses_to_address_book.rake +0 -26
- data/lib/tasks/migrations/migrate_shipping_rate_taxes.rake +0 -22
- data/lib/tasks/migrations/migrate_user_addresses.rake +0 -34
- data/lib/tasks/migrations/rename_gateways.rake +0 -23
- data/lib/tasks/order_capturing.rake +0 -27
- data/lib/tasks/upgrade.rake +0 -13
data/app/models/spree/product.rb
CHANGED
@@ -5,10 +5,6 @@ module Spree
|
|
5
5
|
# variations, called variants. Product properties include description,
|
6
6
|
# permalink, availability, shipping category, etc. that do not change by
|
7
7
|
# variant.
|
8
|
-
#
|
9
|
-
# @note this model uses {https://github.com/radar/paranoia paranoia}.
|
10
|
-
# +#destroy+ will only soft-destroy records and the default scope hides
|
11
|
-
# soft-destroyed records using +WHERE deleted_at IS NULL+.
|
12
8
|
class Product < Spree::Base
|
13
9
|
extend FriendlyId
|
14
10
|
friendly_id :slug_candidates, use: :history
|
@@ -65,6 +61,17 @@ module Spree
|
|
65
61
|
has_many :line_items, through: :variants_including_master
|
66
62
|
has_many :orders, through: :line_items
|
67
63
|
|
64
|
+
scope :sort_by_master_default_price_amount_asc, -> {
|
65
|
+
with_default_price.order('spree_prices.amount ASC')
|
66
|
+
}
|
67
|
+
scope :sort_by_master_default_price_amount_desc, -> {
|
68
|
+
with_default_price.order('spree_prices.amount DESC')
|
69
|
+
}
|
70
|
+
scope :with_default_price, -> {
|
71
|
+
left_joins(master: :prices)
|
72
|
+
.where(master: { spree_prices: Spree::Config.default_pricing_options.desired_attributes })
|
73
|
+
}
|
74
|
+
|
68
75
|
def find_or_build_master
|
69
76
|
master || build_master
|
70
77
|
end
|
@@ -89,7 +96,7 @@ module Spree
|
|
89
96
|
:has_default_price?,
|
90
97
|
:images,
|
91
98
|
:price_for,
|
92
|
-
:
|
99
|
+
:price_for_options,
|
93
100
|
:rebuild_vat_prices=,
|
94
101
|
to: :find_or_build_master
|
95
102
|
|
@@ -124,11 +131,11 @@ module Spree
|
|
124
131
|
|
125
132
|
alias :options :product_option_types
|
126
133
|
|
127
|
-
self.
|
128
|
-
self.
|
134
|
+
self.allowed_ransackable_associations = %w[stores variants_including_master master variants]
|
135
|
+
self.allowed_ransackable_attributes = %w[name slug]
|
129
136
|
|
130
137
|
def self.ransackable_scopes(_auth_object = nil)
|
131
|
-
%i(with_discarded with_variant_sku_cont)
|
138
|
+
%i(available with_discarded with_variant_sku_cont with_all_variant_sku_cont with_kept_variant_sku_cont)
|
132
139
|
end
|
133
140
|
|
134
141
|
# @return [Boolean] true if there are any variants
|
@@ -186,19 +193,6 @@ module Spree
|
|
186
193
|
!!discontinue_on&.past?
|
187
194
|
end
|
188
195
|
|
189
|
-
# Groups variants by the specified option type.
|
190
|
-
#
|
191
|
-
# @deprecated This method is not called in the Solidus codebase
|
192
|
-
# @param opt_type [String] the name of the option type to group by
|
193
|
-
# @param pricing_options [Spree::Config.pricing_options_class] the pricing options to search
|
194
|
-
# for, default: the default pricing options
|
195
|
-
# @return [Hash] option_type as keys, array of variants as values.
|
196
|
-
def categorise_variants_from_option(opt_type, pricing_options = Spree::Config.default_pricing_options)
|
197
|
-
return {} unless option_types.include?(opt_type)
|
198
|
-
variants.with_prices(pricing_options).group_by { |variant| variant.option_values.detect { |option| option.option_type == opt_type } }
|
199
|
-
end
|
200
|
-
deprecate :categorise_variants_from_option, deprecator: Spree::Deprecation
|
201
|
-
|
202
196
|
# Poor man's full text search.
|
203
197
|
#
|
204
198
|
# Filters products to those which have any of the strings in +values+ in
|
@@ -214,17 +208,6 @@ module Spree
|
|
214
208
|
where conditions.inject(:or)
|
215
209
|
end
|
216
210
|
|
217
|
-
# @param current_currency [String] currency to filter variants by; defaults to Spree's default
|
218
|
-
# @deprecated This method can only handle prices for currencies
|
219
|
-
# @return [Array<Spree::Variant>] all variants with at least one option value
|
220
|
-
def variants_and_option_values(current_currency = nil)
|
221
|
-
variants.includes(:option_values).active(current_currency).select do |variant|
|
222
|
-
variant.option_values.any?
|
223
|
-
end
|
224
|
-
end
|
225
|
-
deprecate variants_and_option_values: :variants_and_option_values_for,
|
226
|
-
deprecator: Spree::Deprecation
|
227
|
-
|
228
211
|
# @param pricing_options [Spree::Variant::PricingOptions] the pricing options to search
|
229
212
|
# for, default: the default pricing options
|
230
213
|
# @return [Array<Spree::Variant>] all variants with at least one option value
|
@@ -296,16 +279,6 @@ module Spree
|
|
296
279
|
end
|
297
280
|
end
|
298
281
|
|
299
|
-
# Image that can be used for the product.
|
300
|
-
#
|
301
|
-
# Will first search for images on the product, then those belonging to the
|
302
|
-
# variants. If all else fails, will return a new image object.
|
303
|
-
# @return [Spree::Image] the image to display
|
304
|
-
def display_image
|
305
|
-
Spree::Deprecation.warn('Spree::Product#display_image is DEPRECATED. Choose an image from Spree::Product#gallery instead.')
|
306
|
-
images.first || variant_images.first || Spree::Image.new
|
307
|
-
end
|
308
|
-
|
309
282
|
# Finds the variant property rule that matches the provided option value ids.
|
310
283
|
#
|
311
284
|
# @param option_value_ids [Array<Integer>] list of option value ids
|
@@ -9,6 +9,6 @@ module Spree
|
|
9
9
|
belongs_to :product, touch: true, class_name: 'Spree::Product', inverse_of: :product_properties, optional: true
|
10
10
|
belongs_to :property, class_name: 'Spree::Property', inverse_of: :product_properties, optional: true
|
11
11
|
|
12
|
-
self.
|
12
|
+
self.allowed_ransackable_attributes = ['value']
|
13
13
|
end
|
14
14
|
end
|
@@ -15,6 +15,10 @@ module Spree
|
|
15
15
|
before_destroy :remove_adjustments_from_incomplete_orders
|
16
16
|
before_discard :remove_adjustments_from_incomplete_orders
|
17
17
|
|
18
|
+
def preload_relations
|
19
|
+
[:calculator]
|
20
|
+
end
|
21
|
+
|
18
22
|
# Creates the adjustment related to a promotion for the order passed
|
19
23
|
# through options hash
|
20
24
|
#
|
@@ -39,9 +43,6 @@ module Spree
|
|
39
43
|
# item_total and ship_total
|
40
44
|
def compute_amount(calculable)
|
41
45
|
amount = calculator.compute(calculable)
|
42
|
-
if !amount.is_a?(BigDecimal)
|
43
|
-
Spree::Deprecation.warn "#{calculator.class.name}#compute returned #{amount.inspect}, it should return a BigDecimal"
|
44
|
-
end
|
45
46
|
amount ||= BigDecimal(0)
|
46
47
|
amount = amount.abs
|
47
48
|
[(calculable.item_total + calculable.ship_total), amount].min * -1
|
@@ -15,6 +15,10 @@ module Spree
|
|
15
15
|
before_destroy :remove_adjustments_from_incomplete_orders
|
16
16
|
before_discard :remove_adjustments_from_incomplete_orders
|
17
17
|
|
18
|
+
def preload_relations
|
19
|
+
[:calculator]
|
20
|
+
end
|
21
|
+
|
18
22
|
def perform(payload = {})
|
19
23
|
order = payload[:order]
|
20
24
|
promotion = payload[:promotion]
|
@@ -33,9 +37,6 @@ module Spree
|
|
33
37
|
order = adjustable.is_a?(Order) ? adjustable : adjustable.order
|
34
38
|
return 0 unless promotion.line_item_actionable?(order, adjustable)
|
35
39
|
promotion_amount = calculator.compute(adjustable)
|
36
|
-
if !promotion_amount.is_a?(BigDecimal)
|
37
|
-
Spree::Deprecation.warn "#{calculator.class.name}#compute returned #{promotion_amount.inspect}, it should return a BigDecimal"
|
38
|
-
end
|
39
40
|
promotion_amount ||= BigDecimal(0)
|
40
41
|
promotion_amount = promotion_amount.abs
|
41
42
|
[adjustable.amount, promotion_amount].min * -1
|
@@ -86,13 +87,8 @@ module Spree
|
|
86
87
|
end
|
87
88
|
|
88
89
|
def line_items_to_adjust(promotion, order)
|
89
|
-
excluded_ids = adjustments.
|
90
|
-
where(adjustable_id: order.line_items.pluck(:id), adjustable_type: 'Spree::LineItem').
|
91
|
-
pluck(:adjustable_id).
|
92
|
-
to_set
|
93
|
-
|
94
90
|
order.line_items.select do |line_item|
|
95
|
-
|
91
|
+
line_item.adjustments.none? { |adjustment| adjustment.source == self } &&
|
96
92
|
promotion.line_item_actionable?(order, line_item)
|
97
93
|
end
|
98
94
|
end
|
@@ -57,9 +57,6 @@ module Spree
|
|
57
57
|
#
|
58
58
|
def compute_amount(line_item)
|
59
59
|
adjustment_amount = calculator.compute(PartialLineItem.new(line_item))
|
60
|
-
if !adjustment_amount.is_a?(BigDecimal)
|
61
|
-
Spree::Deprecation.warn "#{calculator.class.name}#compute returned #{adjustment_amount.inspect}, it should return a BigDecimal"
|
62
|
-
end
|
63
60
|
adjustment_amount ||= BigDecimal(0)
|
64
61
|
adjustment_amount = adjustment_amount.abs
|
65
62
|
|
@@ -7,6 +7,7 @@ module Spree
|
|
7
7
|
def perform(payload = {})
|
8
8
|
order = payload[:order]
|
9
9
|
promotion_code = payload[:promotion_code]
|
10
|
+
return false unless promotion.eligible? order
|
10
11
|
|
11
12
|
created_adjustments = order.shipments.map do |shipment|
|
12
13
|
next if promotion_credit_exists?(shipment)
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Spree
|
4
|
+
class Promotion < Spree::Base
|
5
|
+
|
6
|
+
# This class encapsulates all the things the promotion system does to
|
7
|
+
# an order. It is called from the `Spree::OrderUpdater` before taxes are
|
8
|
+
# calculated, such that taxes always respect promotions.
|
9
|
+
|
10
|
+
# This class iterates over all existing promotion adjustments and recalculates
|
11
|
+
# their amount and eligibility using their adjustment source.
|
12
|
+
class OrderAdjustmentsRecalculator
|
13
|
+
def initialize(order)
|
14
|
+
@order = order
|
15
|
+
end
|
16
|
+
|
17
|
+
def call
|
18
|
+
all_items = line_items + shipments
|
19
|
+
all_items.each do |item|
|
20
|
+
promotion_adjustments = item.adjustments.select(&:promotion?)
|
21
|
+
|
22
|
+
promotion_adjustments.each { |adjustment| recalculate(adjustment) }
|
23
|
+
Spree::Config.promotion_chooser_class.new(promotion_adjustments).update
|
24
|
+
|
25
|
+
item.promo_total = promotion_adjustments.select(&:eligible?).sum(&:amount)
|
26
|
+
end
|
27
|
+
# Update and select the best promotion adjustment for the order.
|
28
|
+
# We don't update the order.promo_total yet. Order totals are updated later
|
29
|
+
# in #update_adjustment_total since they include the totals from the order's
|
30
|
+
# line items and/or shipments.
|
31
|
+
order_promotion_adjustments = order.adjustments.select(&:promotion?)
|
32
|
+
order_promotion_adjustments.each { |adjustment| recalculate(adjustment) }
|
33
|
+
Spree::Config.promotion_chooser_class.new(order_promotion_adjustments).update
|
34
|
+
|
35
|
+
order.promo_total = all_items.sum(&:promo_total) +
|
36
|
+
order_promotion_adjustments.
|
37
|
+
select(&:eligible?).
|
38
|
+
select(&:promotion?).
|
39
|
+
sum(&:amount)
|
40
|
+
order
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
attr_reader :order
|
46
|
+
delegate :line_items, :shipments, to: :order
|
47
|
+
|
48
|
+
# Recalculate and persist the amount from this adjustment's source based on
|
49
|
+
# the adjustable ({Order}, {Shipment}, or {LineItem})
|
50
|
+
#
|
51
|
+
# If the adjustment has no source (such as when created manually from the
|
52
|
+
# admin) or is closed, this is a noop.
|
53
|
+
#
|
54
|
+
# @return [BigDecimal] New amount of this adjustment
|
55
|
+
def recalculate(adjustment)
|
56
|
+
if adjustment.finalized?
|
57
|
+
return adjustment.amount
|
58
|
+
end
|
59
|
+
|
60
|
+
# If the adjustment has no source, do not attempt to re-calculate the
|
61
|
+
# amount.
|
62
|
+
# Some scenarios where this happens:
|
63
|
+
# - Adjustments that are manually created via the admin backend
|
64
|
+
# - PromotionAction adjustments where the PromotionAction was deleted
|
65
|
+
# after the order was completed.
|
66
|
+
if adjustment.source.present?
|
67
|
+
adjustment.amount = adjustment.source.compute_amount(adjustment.adjustable)
|
68
|
+
|
69
|
+
adjustment.eligible = calculate_eligibility(adjustment)
|
70
|
+
|
71
|
+
# Persist only if changed
|
72
|
+
# This is only not a save! to avoid the extra queries to load the order
|
73
|
+
# (for validations) and to touch the adjustment.
|
74
|
+
adjustment.update_columns(eligible: adjustment.eligible, amount: adjustment.amount, updated_at: Time.current) if adjustment.changed?
|
75
|
+
end
|
76
|
+
adjustment.amount
|
77
|
+
end
|
78
|
+
|
79
|
+
# Calculates based on attached promotion (if this is a promotion
|
80
|
+
# adjustment) whether this promotion is still eligible.
|
81
|
+
# @api private
|
82
|
+
# @return [true,false] Whether this adjustment is eligible
|
83
|
+
def calculate_eligibility(adjustment)
|
84
|
+
if !adjustment.finalized? && adjustment.source
|
85
|
+
adjustment.source.promotion.eligible?(adjustment.adjustable, promotion_code: adjustment.promotion_code)
|
86
|
+
else
|
87
|
+
adjustment.eligible?
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -5,12 +5,38 @@ module Spree
|
|
5
5
|
module Rules
|
6
6
|
# A rule to apply to an order greater than (or greater than or equal to)
|
7
7
|
# a specific amount
|
8
|
+
#
|
9
|
+
# To add extra operators please override `self.operators_map` or any other helper method.
|
10
|
+
# To customize the error message you can also override `ineligible_message`.
|
8
11
|
class ItemTotal < PromotionRule
|
12
|
+
include ActiveSupport::Deprecation::DeprecatedConstantAccessor
|
13
|
+
|
9
14
|
preference :amount, :decimal, default: 100.00
|
10
15
|
preference :currency, :string, default: ->{ Spree::Config[:currency] }
|
11
|
-
preference :operator, :string, default: '
|
16
|
+
preference :operator, :string, default: 'gt'
|
17
|
+
|
18
|
+
# The list of allowed operators names mapped to their symbols.
|
19
|
+
def self.operators_map
|
20
|
+
{
|
21
|
+
gte: :>=,
|
22
|
+
gt: :>,
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.operator_options
|
27
|
+
operators_map.map do |name, _method|
|
28
|
+
[I18n.t(name, scope: 'spree.item_total_rule.operators'), name]
|
29
|
+
end
|
30
|
+
end
|
12
31
|
|
13
|
-
|
32
|
+
# @deprecated
|
33
|
+
OPERATORS = operators_map.keys.map(&:to_s)
|
34
|
+
deprecate_constant(
|
35
|
+
:OPERATORS,
|
36
|
+
:operators_map,
|
37
|
+
message: "OPERATORS is deprecated! Use `operators_map.keys.map(&:to_s)` instead.",
|
38
|
+
deprecator: Spree::Deprecation,
|
39
|
+
)
|
14
40
|
|
15
41
|
def applicable?(promotable)
|
16
42
|
promotable.is_a?(Spree::Order)
|
@@ -18,8 +44,8 @@ module Spree
|
|
18
44
|
|
19
45
|
def eligible?(order, _options = {})
|
20
46
|
return false unless order.currency == preferred_currency
|
21
|
-
|
22
|
-
unless
|
47
|
+
|
48
|
+
unless total_for_order(order).send(operator, threshold)
|
23
49
|
eligibility_errors.add(:base, ineligible_message, error_code: ineligible_error_code)
|
24
50
|
end
|
25
51
|
|
@@ -28,15 +54,33 @@ module Spree
|
|
28
54
|
|
29
55
|
private
|
30
56
|
|
57
|
+
def operator
|
58
|
+
self.class.operators_map.fetch(
|
59
|
+
preferred_operator.to_sym,
|
60
|
+
preferred_operator_default,
|
61
|
+
)
|
62
|
+
end
|
63
|
+
|
64
|
+
def total_for_order(order)
|
65
|
+
order.item_total
|
66
|
+
end
|
67
|
+
|
68
|
+
def threshold
|
69
|
+
BigDecimal(preferred_amount.to_s)
|
70
|
+
end
|
71
|
+
|
31
72
|
def formatted_amount
|
32
73
|
Spree::Money.new(preferred_amount, currency: preferred_currency).to_s
|
33
74
|
end
|
34
75
|
|
35
76
|
def ineligible_message
|
36
|
-
|
77
|
+
case preferred_operator.to_s
|
78
|
+
when 'gte'
|
37
79
|
eligibility_error_message(:item_total_less_than, amount: formatted_amount)
|
38
|
-
|
80
|
+
when 'gt'
|
39
81
|
eligibility_error_message(:item_total_less_than_or_equal, amount: formatted_amount)
|
82
|
+
else
|
83
|
+
eligibility_error_message(:item_total_doesnt_match_with_operator, amount: formatted_amount, operator: preferred_operator)
|
40
84
|
end
|
41
85
|
end
|
42
86
|
|
@@ -12,6 +12,10 @@ module Spree
|
|
12
12
|
class_name: 'Spree::ProductPromotionRule'
|
13
13
|
has_many :products, class_name: 'Spree::Product', through: :product_promotion_rules
|
14
14
|
|
15
|
+
def preload_relations
|
16
|
+
[:products]
|
17
|
+
end
|
18
|
+
|
15
19
|
MATCH_POLICIES = %w(any all none)
|
16
20
|
|
17
21
|
validates_inclusion_of :preferred_match_policy, in: MATCH_POLICIES
|
@@ -31,17 +35,19 @@ module Spree
|
|
31
35
|
return true if eligible_products.empty?
|
32
36
|
|
33
37
|
case preferred_match_policy
|
34
|
-
when
|
35
|
-
unless eligible_products.all? { |product| order.
|
38
|
+
when "all"
|
39
|
+
unless eligible_products.all? { |product| order_products(order).include?(product) }
|
36
40
|
eligibility_errors.add(:base, eligibility_error_message(:missing_product), error_code: :missing_product)
|
37
41
|
end
|
38
|
-
when
|
39
|
-
unless order.
|
40
|
-
eligibility_errors.add(:base, eligibility_error_message(:no_applicable_products),
|
42
|
+
when "any"
|
43
|
+
unless order_products(order).any? { |product| eligible_products.include?(product) }
|
44
|
+
eligibility_errors.add(:base, eligibility_error_message(:no_applicable_products),
|
45
|
+
error_code: :no_applicable_products)
|
41
46
|
end
|
42
|
-
when
|
43
|
-
unless order.
|
44
|
-
eligibility_errors.add(:base, eligibility_error_message(:has_excluded_product),
|
47
|
+
when "none"
|
48
|
+
unless order_products(order).none? { |product| eligible_products.include?(product) }
|
49
|
+
eligibility_errors.add(:base, eligibility_error_message(:has_excluded_product),
|
50
|
+
error_code: :has_excluded_product)
|
45
51
|
end
|
46
52
|
else
|
47
53
|
raise "unexpected match policy: #{preferred_match_policy.inspect}"
|
@@ -68,6 +74,12 @@ module Spree
|
|
68
74
|
def product_ids_string=(product_ids)
|
69
75
|
self.product_ids = product_ids.to_s.split(',').map(&:strip)
|
70
76
|
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def order_products(order)
|
81
|
+
order.line_items.map(&:variant).map(&:product)
|
82
|
+
end
|
71
83
|
end
|
72
84
|
end
|
73
85
|
end
|
@@ -8,6 +8,10 @@ module Spree
|
|
8
8
|
dependent: :destroy
|
9
9
|
has_many :taxons, through: :promotion_rule_taxons, class_name: 'Spree::Taxon'
|
10
10
|
|
11
|
+
def preload_relations
|
12
|
+
[:taxons]
|
13
|
+
end
|
14
|
+
|
11
15
|
MATCH_POLICIES = %w(any all none)
|
12
16
|
|
13
17
|
validates_inclusion_of :preferred_match_policy, in: MATCH_POLICIES
|
@@ -38,11 +42,7 @@ module Spree
|
|
38
42
|
eligibility_errors.add(:base, eligibility_error_message(:has_excluded_taxon), error_code: :has_excluded_taxon)
|
39
43
|
end
|
40
44
|
else
|
41
|
-
|
42
|
-
warn_invalid_match_policy(assume: 'any')
|
43
|
-
unless order_taxons.where(id: rule_taxon_ids_with_children).exists?
|
44
|
-
eligibility_errors.add(:base, eligibility_error_message(:no_matching_taxons), error_code: :no_matching_taxons)
|
45
|
-
end
|
45
|
+
raise "unexpected match policy: #{preferred_match_policy.inspect}"
|
46
46
|
end
|
47
47
|
|
48
48
|
eligibility_errors.empty?
|
@@ -60,9 +60,7 @@ module Spree
|
|
60
60
|
when 'none'
|
61
61
|
!found
|
62
62
|
else
|
63
|
-
|
64
|
-
warn_invalid_match_policy(assume: 'any')
|
65
|
-
found
|
63
|
+
raise "unexpected match policy: #{preferred_match_policy.inspect}"
|
66
64
|
end
|
67
65
|
end
|
68
66
|
|
@@ -77,13 +75,6 @@ module Spree
|
|
77
75
|
|
78
76
|
private
|
79
77
|
|
80
|
-
def warn_invalid_match_policy(assume:)
|
81
|
-
Spree::Deprecation.warn(
|
82
|
-
"#{self.class.name} id=#{id} has unexpected match policy #{preferred_match_policy.inspect}. "\
|
83
|
-
"Interpreting it as '#{assume}'."
|
84
|
-
)
|
85
|
-
end
|
86
|
-
|
87
78
|
# All taxons in an order
|
88
79
|
def taxons_in_order(order)
|
89
80
|
Spree::Taxon.joins(products: { variants_including_master: :line_items })
|
@@ -3,6 +3,7 @@
|
|
3
3
|
module Spree
|
4
4
|
class Promotion < Spree::Base
|
5
5
|
MATCH_POLICIES = %w(all any)
|
6
|
+
|
6
7
|
UNACTIVATABLE_ORDER_STATES = ["complete", "awaiting_return", "returned"]
|
7
8
|
|
8
9
|
attr_reader :eligibility_errors
|
@@ -38,11 +39,7 @@ module Spree
|
|
38
39
|
|
39
40
|
scope :coupons, -> { joins(:codes).distinct }
|
40
41
|
scope :advertised, -> { where(advertise: true) }
|
41
|
-
scope :active, ->
|
42
|
-
return started_and_unexpired if Spree::Config.consider_actionless_promotion_active == true
|
43
|
-
|
44
|
-
has_actions.started_and_unexpired
|
45
|
-
end
|
42
|
+
scope :active, -> { has_actions.started_and_unexpired }
|
46
43
|
scope :started_and_unexpired, -> do
|
47
44
|
table = arel_table
|
48
45
|
time = Time.current
|
@@ -51,12 +48,12 @@ module Spree
|
|
51
48
|
where(table[:expires_at].eq(nil).or(table[:expires_at].gt(time)))
|
52
49
|
end
|
53
50
|
scope :has_actions, -> do
|
54
|
-
joins(:promotion_actions)
|
51
|
+
joins(:promotion_actions).distinct
|
55
52
|
end
|
56
53
|
scope :applied, -> { joins(:order_promotions).distinct }
|
57
54
|
|
58
|
-
self.
|
59
|
-
self.
|
55
|
+
self.allowed_ransackable_associations = ['codes']
|
56
|
+
self.allowed_ransackable_attributes = %w[name path promotion_category_id]
|
60
57
|
def self.ransackable_scopes(*)
|
61
58
|
%i(active)
|
62
59
|
end
|
@@ -71,6 +68,19 @@ module Spree
|
|
71
68
|
).first
|
72
69
|
end
|
73
70
|
|
71
|
+
# All orders that have been discounted using this promotion
|
72
|
+
def discounted_orders
|
73
|
+
Spree::Order.
|
74
|
+
joins(:all_adjustments).
|
75
|
+
where(
|
76
|
+
spree_adjustments: {
|
77
|
+
source_type: "Spree::PromotionAction",
|
78
|
+
source_id: actions.map(&:id),
|
79
|
+
eligible: true
|
80
|
+
}
|
81
|
+
).distinct
|
82
|
+
end
|
83
|
+
|
74
84
|
def as_json(options = {})
|
75
85
|
options[:except] ||= :code
|
76
86
|
super
|
@@ -93,7 +103,7 @@ module Spree
|
|
93
103
|
end
|
94
104
|
|
95
105
|
def active?
|
96
|
-
started? && not_expired? &&
|
106
|
+
started? && not_expired? && actions.present?
|
97
107
|
end
|
98
108
|
|
99
109
|
def inactive?
|
@@ -155,7 +165,7 @@ module Spree
|
|
155
165
|
return [] if rules.none?
|
156
166
|
|
157
167
|
eligible = lambda { |rule| rule.eligible?(promotable, options) }
|
158
|
-
specific_rules = rules.
|
168
|
+
specific_rules = rules.select { |rule| rule.applicable?(promotable) }
|
159
169
|
return [] if specific_rules.none?
|
160
170
|
|
161
171
|
if match_all?
|
@@ -167,6 +177,12 @@ module Spree
|
|
167
177
|
end
|
168
178
|
specific_rules
|
169
179
|
else
|
180
|
+
Spree::Deprecation.warn(
|
181
|
+
<<~WARN
|
182
|
+
Your promotion "#{name}" with ID #{id} has a match_policy of 'any'.
|
183
|
+
This is deprecated, please split the promotion into separate promotions for each rule.
|
184
|
+
WARN
|
185
|
+
)
|
170
186
|
unless specific_rules.any?(&eligible)
|
171
187
|
@eligibility_errors = specific_rules.map(&:eligibility_errors).detect(&:present?)
|
172
188
|
return nil
|
@@ -194,11 +210,11 @@ module Spree
|
|
194
210
|
# @param excluded_orders [Array<Spree::Order>] Orders to exclude from usage count
|
195
211
|
# @return [Integer] usage count
|
196
212
|
def usage_count(excluded_orders: [])
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
where(
|
201
|
-
count
|
213
|
+
discounted_orders.
|
214
|
+
complete.
|
215
|
+
where.not(id: [excluded_orders.map(&:id)]).
|
216
|
+
where.not(spree_orders: { state: :canceled }).
|
217
|
+
count
|
202
218
|
end
|
203
219
|
|
204
220
|
def line_item_actionable?(order, line_item, promotion_code: nil)
|
@@ -219,21 +235,12 @@ module Spree
|
|
219
235
|
end
|
220
236
|
|
221
237
|
def used_by?(user, excluded_orders = [])
|
222
|
-
|
223
|
-
|
224
|
-
:
|
225
|
-
:
|
226
|
-
|
227
|
-
|
228
|
-
spree_adjustments: {
|
229
|
-
source_type: "Spree::PromotionAction",
|
230
|
-
source_id: actions.map(&:id),
|
231
|
-
eligible: true
|
232
|
-
}
|
233
|
-
).where.not(
|
234
|
-
id: excluded_orders.map(&:id)
|
235
|
-
).any?
|
236
|
-
end
|
238
|
+
discounted_orders.
|
239
|
+
complete.
|
240
|
+
where.not(id: excluded_orders.map(&:id)).
|
241
|
+
where(user: user).
|
242
|
+
where.not(spree_orders: { state: :canceled }).
|
243
|
+
exists?
|
237
244
|
end
|
238
245
|
|
239
246
|
# Removes a promotion and any adjustments or other side effects from an
|
@@ -255,9 +262,9 @@ module Spree
|
|
255
262
|
def blacklisted?(promotable)
|
256
263
|
case promotable
|
257
264
|
when Spree::LineItem
|
258
|
-
!promotable.product.promotionable?
|
265
|
+
!promotable.variant.product.promotionable?
|
259
266
|
when Spree::Order
|
260
|
-
promotable.line_items.
|
267
|
+
promotable.line_items.any? { |line_item| !line_item.variant.product.promotionable? }
|
261
268
|
end
|
262
269
|
end
|
263
270
|
|
@@ -16,6 +16,10 @@ module Spree
|
|
16
16
|
scope :of_type, ->(type) { where(type: Array.wrap(type).map(&:to_s)) }
|
17
17
|
scope :shipping, -> { of_type(Spree::Config.environment.promotions.shipping_actions.to_a) }
|
18
18
|
|
19
|
+
def preload_relations
|
20
|
+
[]
|
21
|
+
end
|
22
|
+
|
19
23
|
# Updates the state of the order or performs some other action depending on
|
20
24
|
# the subclass options will contain the payload from the event that
|
21
25
|
# activated the promotion. This will include the key :user which allows
|
@@ -32,15 +36,8 @@ module Spree
|
|
32
36
|
#
|
33
37
|
# @param order [Spree::Order] the order to remove the action from
|
34
38
|
# @return [void]
|
35
|
-
def remove_from(
|
36
|
-
|
37
|
-
[order, *order.line_items, *order.shipments].each do |item|
|
38
|
-
item.adjustments.each do |adjustment|
|
39
|
-
if adjustment.source == self
|
40
|
-
item.adjustments.destroy(adjustment)
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
39
|
+
def remove_from(_order)
|
40
|
+
raise 'remove_from should be implemented in a sub-class of PromotionAction'
|
44
41
|
end
|
45
42
|
|
46
43
|
def to_partial_path
|