solidus_core 2.11.10 → 3.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|