solidus_core 1.3.2 → 1.4.0.beta1
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/README.md +15 -0
- data/app/helpers/spree/base_helper.rb +6 -2
- data/app/mailers/spree/base_mailer.rb +1 -1
- data/app/mailers/spree/carton_mailer.rb +1 -1
- data/app/mailers/spree/order_mailer.rb +2 -2
- data/app/mailers/spree/reimbursement_mailer.rb +7 -7
- data/app/mailers/spree/test_mailer.rb +3 -2
- data/app/models/concerns/spree/user_payment_source.rb +1 -1
- data/app/models/spree/address.rb +14 -4
- data/app/models/spree/adjustment.rb +11 -19
- data/app/models/spree/app_configuration.rb +23 -1
- data/app/models/spree/base.rb +9 -0
- data/app/models/spree/country.rb +2 -2
- data/app/models/spree/exchange.rb +1 -1
- data/app/models/spree/gateway.rb +1 -1
- data/app/models/spree/item_adjustments.rb +7 -0
- data/app/models/spree/line_item.rb +1 -13
- data/app/models/spree/order/checkout.rb +19 -16
- data/app/models/spree/order/payments.rb +0 -2
- data/app/models/spree/order.rb +7 -21
- data/app/models/spree/order_contents.rb +60 -4
- data/app/models/spree/order_merger.rb +2 -4
- data/app/models/spree/order_shipping.rb +1 -1
- data/app/models/spree/order_update_attributes.rb +0 -2
- data/app/models/spree/order_updater.rb +14 -11
- data/app/models/spree/payment.rb +2 -3
- data/app/models/spree/payment_create.rb +5 -2
- data/app/models/spree/payment_method/store_credit.rb +6 -5
- data/app/models/spree/product/scopes.rb +2 -1
- data/app/models/spree/product.rb +2 -5
- data/app/models/spree/promotion/actions/create_item_adjustments.rb +5 -3
- data/app/models/spree/promotion/actions/free_shipping.rb +1 -1
- data/app/models/spree/promotion/rules/option_value.rb +2 -3
- data/app/models/spree/promotion/rules/product.rb +9 -3
- data/app/models/spree/promotion/rules/taxon.rb +33 -7
- data/app/models/spree/promotion/rules/user.rb +2 -3
- data/app/models/spree/promotion.rb +16 -6
- data/app/models/spree/promotion_handler/coupon.rb +1 -1
- data/app/models/spree/shipment.rb +12 -16
- data/app/models/spree/stock/estimator.rb +1 -1
- data/app/models/spree/stock/package.rb +0 -1
- data/app/models/spree/stock_item.rb +2 -6
- data/app/models/spree/store.rb +2 -1
- data/app/models/spree/tax_rate.rb +1 -1
- data/app/models/spree/wallet/add_payment_sources_to_wallet.rb +29 -0
- data/app/models/spree/wallet/default_payment_builder.rb +26 -0
- data/app/views/spree/reimbursement_mailer/reimbursement_email.html.erb +46 -0
- data/app/views/spree/reimbursement_mailer/reimbursement_email.text.erb +1 -1
- data/app/views/spree/shared/_base_mailer_footer.html.erb +1 -20
- data/app/views/spree/shared/_base_mailer_header.html.erb +1 -31
- data/config/initializers/spree_user.rb +1 -1
- data/config/locales/en.yml +58 -34
- data/db/migrate/20130414000512_update_name_fields_on_spree_credit_cards.rb +1 -1
- data/db/migrate/20140601011216_set_shipment_total_for_users_upgrading.rb +15 -3
- data/db/migrate/20160527070401_rename_shipment_address_field.rb +7 -0
- data/db/migrate/20160616232103_remove_user_id_from_promotion_rules.rb +11 -0
- data/db/migrate/20160718205341_add_payment_id_index_to_spree_refunds.rb +5 -0
- data/db/migrate/20160718205859_add_reimbursement_id_index_to_spree_refunds.rb +5 -0
- data/lib/generators/spree/dummy/dummy_generator.rb +1 -1
- data/lib/generators/spree/dummy/templates/rails/database.yml +3 -1
- data/lib/generators/spree/dummy/templates/rails/test.rb +4 -2
- data/lib/generators/spree/install/install_generator.rb +6 -16
- data/lib/spree/core/controller_helpers/auth.rb +1 -1
- data/lib/spree/core/controller_helpers/common.rb +1 -1
- data/lib/spree/core/controller_helpers/order.rb +2 -2
- data/lib/spree/core/current_store.rb +5 -1
- data/lib/spree/core/delegate_belongs_to.rb +1 -1
- data/lib/spree/core/routes.rb +6 -32
- data/lib/spree/core/search/base.rb +2 -2
- data/lib/spree/core/stock_configuration.rb +6 -0
- data/lib/spree/core/version.rb +2 -2
- data/lib/spree/core.rb +2 -1
- data/lib/spree/i18n.rb +7 -0
- data/lib/spree/mailer_previews/carton_preview.rb +2 -1
- data/lib/spree/mailer_previews/order_preview.rb +8 -3
- data/lib/spree/mailer_previews/reimbursement_preview.rb +11 -0
- data/lib/spree/migrations.rb +13 -7
- data/lib/spree/permitted_attributes.rb +1 -1
- data/lib/spree/testing_support/capybara_ext.rb +6 -1
- data/lib/spree/testing_support/common_rake.rb +3 -7
- data/lib/spree/testing_support/controller_requests.rb +8 -0
- data/lib/spree/testing_support/factories/order_factory.rb +30 -8
- data/lib/spree/testing_support/factories/payment_factory.rb +1 -1
- data/lib/spree/testing_support/factories/product_factory.rb +6 -0
- data/lib/spree/testing_support/factories/reimbursement_factory.rb +1 -0
- data/lib/tasks/migrations/copy_shipped_shipments_to_cartons.rake +3 -1
- data/solidus_core.gemspec +3 -3
- data/spec/helpers/base_helper_spec.rb +18 -2
- data/spec/lib/i18n_spec.rb +4 -0
- data/spec/lib/spree/core/controller_helpers/order_spec.rb +16 -4
- data/spec/lib/spree/core/controller_helpers/payment_parameters_spec.rb +75 -59
- data/spec/lib/spree/core/delegate_belongs_to_spec.rb +1 -1
- data/spec/lib/spree/core/importer/order_spec.rb +4 -2
- data/spec/lib/spree/core/stock_configuration_spec.rb +17 -0
- data/spec/lib/spree/core/testing_support/factories/order_factory_spec.rb +195 -0
- data/spec/lib/spree/core/testing_support/factories/payment_factory_spec.rb +6 -0
- data/spec/lib/spree/core/testing_support/factories/reimbursement_factory_spec.rb +6 -0
- data/spec/lib/spree/migrations_spec.rb +3 -9
- data/spec/lib/tasks/migrations/copy_shipped_shipments_to_cartons_spec.rb +1 -1
- data/spec/mailers/carton_mailer_spec.rb +1 -1
- data/spec/mailers/order_mailer_spec.rb +2 -2
- data/spec/mailers/reimbursement_mailer_spec.rb +1 -1
- data/spec/models/spree/address_spec.rb +22 -0
- data/spec/models/spree/adjustment_spec.rb +0 -10
- data/spec/models/spree/carton_spec.rb +1 -1
- data/spec/models/spree/country_spec.rb +9 -1
- data/spec/models/spree/gateway/bogus_spec.rb +1 -1
- data/spec/models/spree/item_adjustments_spec.rb +33 -0
- data/spec/models/spree/line_item_spec.rb +2 -21
- data/spec/models/spree/order/checkout_spec.rb +32 -45
- data/spec/models/spree/order/payment_spec.rb +1 -16
- data/spec/models/spree/order_cancellations_spec.rb +8 -3
- data/spec/models/spree/order_contents_spec.rb +48 -0
- data/spec/models/spree/order_merger_spec.rb +8 -5
- data/spec/models/spree/order_shipping_spec.rb +3 -4
- data/spec/models/spree/order_spec.rb +23 -59
- data/spec/models/spree/order_update_attributes_spec.rb +1 -5
- data/spec/models/spree/order_updater_spec.rb +39 -11
- data/spec/models/spree/payment_create_spec.rb +61 -0
- data/spec/models/spree/payment_method/store_credit_spec.rb +23 -10
- data/spec/models/spree/payment_spec.rb +17 -4
- data/spec/models/spree/product_spec.rb +1 -1
- data/spec/models/spree/promotion/actions/create_item_adjustments_spec.rb +8 -13
- data/spec/models/spree/promotion/rules/product_spec.rb +33 -0
- data/spec/models/spree/promotion/rules/taxon_spec.rb +52 -8
- data/spec/models/spree/promotion_spec.rb +18 -12
- data/spec/models/spree/return_authorization_spec.rb +0 -16
- data/spec/models/spree/shipment_spec.rb +57 -36
- data/spec/models/spree/stock/coordinator_spec.rb +0 -5
- data/spec/models/spree/stock/package_spec.rb +0 -1
- data/spec/models/spree/stock_item_spec.rb +35 -145
- data/spec/models/spree/store_spec.rb +24 -10
- data/spec/models/spree/tax_rate_spec.rb +2 -2
- data/spec/models/spree/user_spec.rb +1 -1
- data/spec/models/spree/validations/db_maximum_length_validator_spec.rb +20 -12
- data/spec/support/concerns/working_factories.rb +4 -0
- metadata +30 -10
- data/config/routes.rb +0 -1
|
@@ -6,6 +6,19 @@ module Spree
|
|
|
6
6
|
@order = order
|
|
7
7
|
end
|
|
8
8
|
|
|
9
|
+
# Add a line items to the order if there is inventory to do so
|
|
10
|
+
# and populate Promotions
|
|
11
|
+
#
|
|
12
|
+
# @params [Spree::Variant] :variant The variant the line_item should
|
|
13
|
+
# be associated with
|
|
14
|
+
# @params [Integer] :quantity The line_item quantity
|
|
15
|
+
# @param [Hash] :options Options for the adding proccess
|
|
16
|
+
# Valid options:
|
|
17
|
+
# shipment: [Spree::Shipment] LineItem target shipment
|
|
18
|
+
# stock_location_quantities:
|
|
19
|
+
# stock_location_id: The stock location to source from
|
|
20
|
+
#
|
|
21
|
+
# @return [Spree::LineItem]
|
|
9
22
|
def add(variant, quantity = 1, options = {})
|
|
10
23
|
line_item = add_to_line_item(variant, quantity, options)
|
|
11
24
|
after_add_or_remove(line_item, options)
|
|
@@ -17,12 +30,26 @@ module Spree
|
|
|
17
30
|
end
|
|
18
31
|
|
|
19
32
|
def remove_line_item(line_item, options = {})
|
|
20
|
-
|
|
33
|
+
order.line_items.destroy(line_item)
|
|
21
34
|
after_add_or_remove(line_item, options)
|
|
22
35
|
end
|
|
23
36
|
|
|
24
37
|
def update_cart(params)
|
|
38
|
+
# We need old_tax_address / new_tax_address because we can't rely on methods
|
|
39
|
+
# offered by ActiveRecord::Dirty to determine if tax_address was updated
|
|
40
|
+
# because if we update the address, a new record will be created
|
|
41
|
+
# by the Address.factory instead of the old record being updated
|
|
42
|
+
|
|
43
|
+
old_tax_address = order.tax_address
|
|
44
|
+
|
|
25
45
|
if order.update_attributes(params)
|
|
46
|
+
|
|
47
|
+
new_tax_address = order.tax_address
|
|
48
|
+
|
|
49
|
+
if should_recalculate_taxes?(old_tax_address, new_tax_address)
|
|
50
|
+
order.create_tax_charge!
|
|
51
|
+
end
|
|
52
|
+
|
|
26
53
|
unless order.completed?
|
|
27
54
|
order.line_items = order.line_items.select { |li| li.quantity > 0 }
|
|
28
55
|
# Update totals, then check if the order is eligible for any cart promotions.
|
|
@@ -57,6 +84,36 @@ module Spree
|
|
|
57
84
|
|
|
58
85
|
private
|
|
59
86
|
|
|
87
|
+
def should_recalculate_taxes?(old_address, new_address)
|
|
88
|
+
# Related to Solidus issue #894
|
|
89
|
+
# This is needed because if you update the shipping_address
|
|
90
|
+
# from the backend on an order that completed checkout,
|
|
91
|
+
# Taxes were not being recalculated if the Order tax zone
|
|
92
|
+
# was updated
|
|
93
|
+
#
|
|
94
|
+
# Possible cases:
|
|
95
|
+
#
|
|
96
|
+
# Case 1:
|
|
97
|
+
#
|
|
98
|
+
# If old_address is a TaxLocation it means that the order has not passed
|
|
99
|
+
# the address checkout state so taxes will be computed by the Order
|
|
100
|
+
# state machine, so we do not calculate taxes here.
|
|
101
|
+
#
|
|
102
|
+
# Case 2 :
|
|
103
|
+
# If new_address is a TaxLocation, but old_address is not, it means that
|
|
104
|
+
# an order has somehow lost his TaxAddress. Since it's not supposed to happen,
|
|
105
|
+
# we do not compute taxes.
|
|
106
|
+
#
|
|
107
|
+
# Case 3
|
|
108
|
+
# Both old_address and new_address are Spree::Address so the order
|
|
109
|
+
# has completed the checkout or that a registered user has updated his
|
|
110
|
+
# default addresses. We need to recalculate the taxes.
|
|
111
|
+
|
|
112
|
+
return if old_address.is_a?(Spree::Tax::TaxLocation) || new_address.is_a?(Spree::Tax::TaxLocation)
|
|
113
|
+
|
|
114
|
+
old_address.try!(:taxation_attributes) != new_address.try!(:taxation_attributes)
|
|
115
|
+
end
|
|
116
|
+
|
|
60
117
|
def after_add_or_remove(line_item, options = {})
|
|
61
118
|
reload_totals
|
|
62
119
|
shipment = options[:shipment]
|
|
@@ -73,7 +130,6 @@ module Spree
|
|
|
73
130
|
|
|
74
131
|
def reload_totals
|
|
75
132
|
order_updater.update
|
|
76
|
-
order.reload
|
|
77
133
|
end
|
|
78
134
|
|
|
79
135
|
def add_to_line_item(variant, quantity, options = {})
|
|
@@ -86,7 +142,7 @@ module Spree
|
|
|
86
142
|
)
|
|
87
143
|
|
|
88
144
|
line_item.quantity += quantity.to_i
|
|
89
|
-
line_item.options = ActionController::Parameters.new(options).permit(PermittedAttributes.line_item_attributes)
|
|
145
|
+
line_item.options = ActionController::Parameters.new(options).permit(PermittedAttributes.line_item_attributes).to_h
|
|
90
146
|
|
|
91
147
|
if line_item.new_record?
|
|
92
148
|
create_order_stock_locations(line_item, options[:stock_location_quantities])
|
|
@@ -103,7 +159,7 @@ module Spree
|
|
|
103
159
|
line_item.target_shipment = options[:shipment]
|
|
104
160
|
|
|
105
161
|
if line_item.quantity == 0
|
|
106
|
-
|
|
162
|
+
order.line_items.destroy(line_item)
|
|
107
163
|
else
|
|
108
164
|
line_item.save!
|
|
109
165
|
end
|
|
@@ -111,7 +111,7 @@ module Spree
|
|
|
111
111
|
current_line_item.quantity += other_order_line_item.quantity
|
|
112
112
|
handle_error(current_line_item) unless current_line_item.save
|
|
113
113
|
else
|
|
114
|
-
|
|
114
|
+
order.line_items << other_order_line_item
|
|
115
115
|
handle_error(other_order_line_item) unless other_order_line_item.save
|
|
116
116
|
end
|
|
117
117
|
end
|
|
@@ -135,9 +135,7 @@ module Spree
|
|
|
135
135
|
# @api private
|
|
136
136
|
# @return [void]
|
|
137
137
|
def persist_merge
|
|
138
|
-
updater.
|
|
139
|
-
updater.update_item_total
|
|
140
|
-
updater.persist_totals
|
|
138
|
+
updater.update
|
|
141
139
|
end
|
|
142
140
|
end
|
|
143
141
|
end
|
|
@@ -17,7 +17,7 @@ class Spree::OrderShipping
|
|
|
17
17
|
ship(
|
|
18
18
|
inventory_units: shipment.inventory_units.shippable,
|
|
19
19
|
stock_location: shipment.stock_location,
|
|
20
|
-
address: shipment.
|
|
20
|
+
address: shipment.order.ship_address,
|
|
21
21
|
shipping_method: shipment.shipping_method,
|
|
22
22
|
shipped_at: Time.current,
|
|
23
23
|
external_number: external_number,
|
|
@@ -14,8 +14,6 @@ module Spree
|
|
|
14
14
|
# Assign the attributes to the order and save the order
|
|
15
15
|
# @return true if saved, otherwise false and errors will be set on the order
|
|
16
16
|
def apply
|
|
17
|
-
order.validate_payments_attributes(@payments_attributes)
|
|
18
|
-
|
|
19
17
|
assign_order_attributes
|
|
20
18
|
assign_payments_attributes
|
|
21
19
|
|
|
@@ -31,7 +31,11 @@ module Spree
|
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
def recalculate_adjustments
|
|
34
|
-
|
|
34
|
+
adjustables = [*line_items, *shipments, order]
|
|
35
|
+
|
|
36
|
+
adjustables.each do |adjustable|
|
|
37
|
+
Spree::ItemAdjustments.new(adjustable).update
|
|
38
|
+
end
|
|
35
39
|
end
|
|
36
40
|
|
|
37
41
|
# Updates the following Order total values:
|
|
@@ -63,7 +67,7 @@ module Spree
|
|
|
63
67
|
end
|
|
64
68
|
|
|
65
69
|
def update_shipment_total
|
|
66
|
-
order.shipment_total = shipments.sum(
|
|
70
|
+
order.shipment_total = shipments.to_a.sum(&:cost)
|
|
67
71
|
update_order_total
|
|
68
72
|
end
|
|
69
73
|
|
|
@@ -73,15 +77,14 @@ module Spree
|
|
|
73
77
|
|
|
74
78
|
def update_adjustment_total
|
|
75
79
|
recalculate_adjustments
|
|
76
|
-
order.adjustment_total = line_items.sum(:adjustment_total) +
|
|
77
|
-
shipments.sum(:adjustment_total) +
|
|
78
|
-
adjustments.eligible.sum(:amount)
|
|
79
|
-
order.included_tax_total = line_items.sum(:included_tax_total) + shipments.sum(:included_tax_total)
|
|
80
|
-
order.additional_tax_total = line_items.sum(:additional_tax_total) + shipments.sum(:additional_tax_total)
|
|
81
80
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
adjustments.
|
|
81
|
+
all_items = line_items + shipments
|
|
82
|
+
|
|
83
|
+
order.adjustment_total = all_items.sum(&:adjustment_total) + adjustments.eligible.sum(:amount)
|
|
84
|
+
order.included_tax_total = all_items.sum(&:included_tax_total)
|
|
85
|
+
order.additional_tax_total = all_items.sum(&:additional_tax_total)
|
|
86
|
+
|
|
87
|
+
order.promo_total = all_items.sum(&:promo_total) + adjustments.promotion.eligible.sum(:amount)
|
|
85
88
|
|
|
86
89
|
update_order_total
|
|
87
90
|
end
|
|
@@ -91,7 +94,7 @@ module Spree
|
|
|
91
94
|
end
|
|
92
95
|
|
|
93
96
|
def update_item_total
|
|
94
|
-
order.item_total = line_items.sum(
|
|
97
|
+
order.item_total = line_items.to_a.sum(&:amount)
|
|
95
98
|
update_order_total
|
|
96
99
|
end
|
|
97
100
|
|
data/app/models/spree/payment.rb
CHANGED
|
@@ -11,7 +11,7 @@ module Spree
|
|
|
11
11
|
|
|
12
12
|
belongs_to :order, class_name: 'Spree::Order', touch: true, inverse_of: :payments
|
|
13
13
|
belongs_to :source, polymorphic: true
|
|
14
|
-
belongs_to :payment_method, class_name: 'Spree::PaymentMethod', inverse_of: :payments
|
|
14
|
+
belongs_to :payment_method, -> { with_deleted }, class_name: 'Spree::PaymentMethod', inverse_of: :payments
|
|
15
15
|
|
|
16
16
|
has_many :offsets, -> { offset_payment }, class_name: "Spree::Payment", foreign_key: :source_id
|
|
17
17
|
has_many :log_entries, as: :source
|
|
@@ -39,7 +39,6 @@ module Spree
|
|
|
39
39
|
|
|
40
40
|
validates :amount, numericality: true
|
|
41
41
|
validates :source, presence: true, if: :source_required?
|
|
42
|
-
validates :payment_method, presence: true
|
|
43
42
|
|
|
44
43
|
default_scope -> { order(:created_at) }
|
|
45
44
|
|
|
@@ -156,7 +155,7 @@ module Spree
|
|
|
156
155
|
return unless new_record?
|
|
157
156
|
return if source_attributes.blank?
|
|
158
157
|
|
|
159
|
-
|
|
158
|
+
Spree::Deprecation.warn(<<WARN.squish)
|
|
160
159
|
Building payment sources by assigning source_attributes on payments is
|
|
161
160
|
deprecated. Instead use either the PaymentCreate class or the
|
|
162
161
|
OrderUpdateAttributes class.
|
|
@@ -2,7 +2,7 @@ module Spree
|
|
|
2
2
|
# Service object for creating new payments on an Order
|
|
3
3
|
class PaymentCreate
|
|
4
4
|
# @param order [Order] The order for the new payment
|
|
5
|
-
# @param attributes [Hash] attributes which are assigned to the new payment
|
|
5
|
+
# @param attributes [Hash,ActionController::Parameters] attributes which are assigned to the new payment
|
|
6
6
|
# * :payment_method_id Id of payment method used for this payment
|
|
7
7
|
# * :source_attributes Attributes used to build the source of this payment. Usually a {CreditCard}
|
|
8
8
|
# * :existing_card_id (Integer) The id of an existing {CreditCard} object to use
|
|
@@ -11,7 +11,10 @@ module Spree
|
|
|
11
11
|
def initialize(order, attributes, payment: nil, request_env: {})
|
|
12
12
|
@order = order
|
|
13
13
|
@payment = payment
|
|
14
|
-
|
|
14
|
+
|
|
15
|
+
# If AC::Params are passed in, attributes.to_h gives us a hash of only
|
|
16
|
+
# the permitted attributes.
|
|
17
|
+
@attributes = attributes.to_h.with_indifferent_access
|
|
15
18
|
@source_attributes = @attributes.delete(:source_attributes) || {}
|
|
16
19
|
@request_env = request_env
|
|
17
20
|
end
|
|
@@ -61,7 +61,7 @@ module Spree
|
|
|
61
61
|
handle_action(action, :void, auth_code)
|
|
62
62
|
end
|
|
63
63
|
|
|
64
|
-
def credit(amount_in_cents, auth_code, gateway_options)
|
|
64
|
+
def credit(amount_in_cents, auth_code, gateway_options = {})
|
|
65
65
|
action = -> (store_credit) do
|
|
66
66
|
currency = gateway_options[:currency] || store_credit.currency
|
|
67
67
|
originator = gateway_options[:originator]
|
|
@@ -77,13 +77,14 @@ module Spree
|
|
|
77
77
|
store_credit = store_credit_event.try(:store_credit)
|
|
78
78
|
|
|
79
79
|
if store_credit_event.nil? || store_credit.nil?
|
|
80
|
-
|
|
80
|
+
ActiveMerchant::Billing::Response.new(false, '', {}, {})
|
|
81
81
|
elsif store_credit_event.capture_action?
|
|
82
|
-
|
|
82
|
+
amount_in_cents = (store_credit_event.amount * 100).round
|
|
83
|
+
credit(amount_in_cents, auth_code)
|
|
83
84
|
elsif store_credit_event.authorization_action?
|
|
84
|
-
|
|
85
|
+
void(auth_code)
|
|
85
86
|
else
|
|
86
|
-
|
|
87
|
+
ActiveMerchant::Billing::Response.new(false, '', {}, {})
|
|
87
88
|
end
|
|
88
89
|
end
|
|
89
90
|
|
|
@@ -172,7 +172,8 @@ module Spree
|
|
|
172
172
|
end
|
|
173
173
|
|
|
174
174
|
# Can't use add_search_scope for this as it needs a default argument
|
|
175
|
-
def self.available(available_on = nil,
|
|
175
|
+
def self.available(available_on = nil, currency = nil)
|
|
176
|
+
Spree::Deprecation.warn("The second currency argument on Product.available has no effect, and is deprecated", caller) if currency
|
|
176
177
|
joins(master: :prices).where("#{Product.quoted_table_name}.available_on <= ?", available_on || Time.current)
|
|
177
178
|
end
|
|
178
179
|
search_scopes << :available
|
data/app/models/spree/product.rb
CHANGED
|
@@ -132,10 +132,7 @@ module Spree
|
|
|
132
132
|
def ensure_option_types_exist_for_values_hash
|
|
133
133
|
return if option_values_hash.nil?
|
|
134
134
|
required_option_type_ids = option_values_hash.keys.map(&:to_i)
|
|
135
|
-
|
|
136
|
-
missing_option_type_ids.each do |id|
|
|
137
|
-
product_option_types.create(option_type_id: id)
|
|
138
|
-
end
|
|
135
|
+
self.option_type_ids |= required_option_type_ids
|
|
139
136
|
end
|
|
140
137
|
|
|
141
138
|
# Creates a new product with the same attributes, variants, etc.
|
|
@@ -259,7 +256,7 @@ module Spree
|
|
|
259
256
|
# @return [Array] all advertised and not-rejected promotions
|
|
260
257
|
def possible_promotions
|
|
261
258
|
promotion_ids = promotion_rules.map(&:promotion_id).uniq
|
|
262
|
-
Spree::Promotion.advertised.where(id: promotion_ids).reject(&:
|
|
259
|
+
Spree::Promotion.advertised.where(id: promotion_ids).reject(&:inactive?)
|
|
263
260
|
end
|
|
264
261
|
|
|
265
262
|
# The number of on-hand stock items; Infinity if any variant does not track
|
|
@@ -68,10 +68,12 @@ module Spree
|
|
|
68
68
|
def line_items_to_adjust(promotion, order)
|
|
69
69
|
excluded_ids = adjustments.
|
|
70
70
|
where(adjustable_id: order.line_items.pluck(:id), adjustable_type: 'Spree::LineItem').
|
|
71
|
-
pluck(:adjustable_id)
|
|
71
|
+
pluck(:adjustable_id).
|
|
72
|
+
to_set
|
|
72
73
|
|
|
73
|
-
order.line_items.
|
|
74
|
-
|
|
74
|
+
order.line_items.select do |line_item|
|
|
75
|
+
!excluded_ids.include?(line_item.id) &&
|
|
76
|
+
promotion.line_item_actionable?(order, line_item)
|
|
75
77
|
end
|
|
76
78
|
end
|
|
77
79
|
end
|
|
@@ -24,15 +24,14 @@ module Spree
|
|
|
24
24
|
product_ids.include?(pid) && (value_ids(pid) & ovids).present?
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
-
def
|
|
28
|
-
values =
|
|
27
|
+
def preferred_eligible_values
|
|
28
|
+
values = preferences[:eligible_values] || {}
|
|
29
29
|
Hash[values.keys.map(&:to_i).zip(
|
|
30
30
|
values.values.map do |v|
|
|
31
31
|
(v.is_a?(Array) ? v : v.split(",")).map(&:to_i)
|
|
32
32
|
end
|
|
33
33
|
)]
|
|
34
34
|
end
|
|
35
|
-
alias_method_chain :preferred_eligible_values, :numerification
|
|
36
35
|
|
|
37
36
|
private
|
|
38
37
|
|
|
@@ -11,6 +11,9 @@ module Spree
|
|
|
11
11
|
has_many :products, class_name: 'Spree::Product', through: :product_promotion_rules
|
|
12
12
|
|
|
13
13
|
MATCH_POLICIES = %w(any all none)
|
|
14
|
+
|
|
15
|
+
validates_inclusion_of :preferred_match_policy, in: MATCH_POLICIES
|
|
16
|
+
|
|
14
17
|
preference :match_policy, :string, default: MATCH_POLICIES.first
|
|
15
18
|
|
|
16
19
|
# scope/association that is used to test eligibility
|
|
@@ -25,18 +28,21 @@ module Spree
|
|
|
25
28
|
def eligible?(order, _options = {})
|
|
26
29
|
return true if eligible_products.empty?
|
|
27
30
|
|
|
28
|
-
|
|
31
|
+
case preferred_match_policy
|
|
32
|
+
when 'all'
|
|
29
33
|
unless eligible_products.all? { |p| order.products.include?(p) }
|
|
30
34
|
eligibility_errors.add(:base, eligibility_error_message(:missing_product))
|
|
31
35
|
end
|
|
32
|
-
|
|
36
|
+
when 'any'
|
|
33
37
|
unless order.products.any? { |p| eligible_products.include?(p) }
|
|
34
38
|
eligibility_errors.add(:base, eligibility_error_message(:no_applicable_products))
|
|
35
39
|
end
|
|
36
|
-
|
|
40
|
+
when 'none'
|
|
37
41
|
unless order.products.none? { |p| eligible_products.include?(p) }
|
|
38
42
|
eligibility_errors.add(:base, eligibility_error_message(:has_excluded_product))
|
|
39
43
|
end
|
|
44
|
+
else
|
|
45
|
+
raise "unexpected match policy: #{preferred_match_policy.inspect}"
|
|
40
46
|
end
|
|
41
47
|
|
|
42
48
|
eligibility_errors.empty?
|
|
@@ -2,33 +2,52 @@ module Spree
|
|
|
2
2
|
class Promotion
|
|
3
3
|
module Rules
|
|
4
4
|
class Taxon < PromotionRule
|
|
5
|
-
has_many :promotion_rule_taxons, class_name: 'Spree::PromotionRuleTaxon', foreign_key: :promotion_rule_id
|
|
5
|
+
has_many :promotion_rule_taxons, class_name: 'Spree::PromotionRuleTaxon', foreign_key: :promotion_rule_id,
|
|
6
|
+
dependent: :destroy
|
|
6
7
|
has_many :taxons, through: :promotion_rule_taxons, class_name: 'Spree::Taxon'
|
|
7
8
|
|
|
8
9
|
MATCH_POLICIES = %w(any all)
|
|
9
|
-
|
|
10
|
+
|
|
11
|
+
validates_inclusion_of :preferred_match_policy, in: MATCH_POLICIES
|
|
12
|
+
|
|
13
|
+
preference :match_policy, :string, default: MATCH_POLICIES.first
|
|
10
14
|
|
|
11
15
|
def applicable?(promotable)
|
|
12
16
|
promotable.is_a?(Spree::Order)
|
|
13
17
|
end
|
|
14
18
|
|
|
15
19
|
def eligible?(order, _options = {})
|
|
16
|
-
|
|
17
|
-
|
|
20
|
+
order_taxons = taxons_in_order_including_parents(order)
|
|
21
|
+
|
|
22
|
+
case preferred_match_policy
|
|
23
|
+
when 'all'
|
|
24
|
+
unless (taxons.to_a - order_taxons).empty?
|
|
18
25
|
eligibility_errors.add(:base, eligibility_error_message(:missing_taxon))
|
|
19
26
|
end
|
|
20
|
-
|
|
21
|
-
order_taxons = taxons_in_order_including_parents(order)
|
|
27
|
+
when 'any'
|
|
22
28
|
unless taxons.any?{ |taxon| order_taxons.include? taxon }
|
|
23
29
|
eligibility_errors.add(:base, eligibility_error_message(:no_matching_taxons))
|
|
24
30
|
end
|
|
31
|
+
else
|
|
32
|
+
# Change this to an exception in a future version of Solidus
|
|
33
|
+
warn_invalid_match_policy(assume: 'any')
|
|
34
|
+
unless taxons.any? { |taxon| order_taxons.include? taxon }
|
|
35
|
+
eligibility_errors.add(:base, eligibility_error_message(:no_matching_taxons))
|
|
36
|
+
end
|
|
25
37
|
end
|
|
26
38
|
|
|
27
39
|
eligibility_errors.empty?
|
|
28
40
|
end
|
|
29
41
|
|
|
30
42
|
def actionable?(line_item)
|
|
31
|
-
|
|
43
|
+
case preferred_match_policy
|
|
44
|
+
when 'any', 'all'
|
|
45
|
+
taxon_product_ids.include?(line_item.variant.product_id)
|
|
46
|
+
else
|
|
47
|
+
# Change this to an exception in a future version of Solidus
|
|
48
|
+
warn_invalid_match_policy(assume: 'any')
|
|
49
|
+
taxon_product_ids.include?(line_item.variant.product_id)
|
|
50
|
+
end
|
|
32
51
|
end
|
|
33
52
|
|
|
34
53
|
def taxon_ids_string
|
|
@@ -42,6 +61,13 @@ module Spree
|
|
|
42
61
|
|
|
43
62
|
private
|
|
44
63
|
|
|
64
|
+
def warn_invalid_match_policy(assume:)
|
|
65
|
+
Spree::Deprecation.warn(
|
|
66
|
+
"#{self.class.name} id=#{id} has unexpected match policy #{preferred_match_policy.inspect}. "\
|
|
67
|
+
"Interpreting it as '#{assume}'."
|
|
68
|
+
)
|
|
69
|
+
end
|
|
70
|
+
|
|
45
71
|
# All taxons in an order
|
|
46
72
|
def order_taxons(order)
|
|
47
73
|
Spree::Taxon.joins(products: { variants_including_master: :line_items }).where(spree_line_items: { order_id: order.id }).distinct
|
|
@@ -2,10 +2,9 @@ module Spree
|
|
|
2
2
|
class Promotion
|
|
3
3
|
module Rules
|
|
4
4
|
class User < PromotionRule
|
|
5
|
-
belongs_to :user, class_name: Spree::UserClassHandle.new
|
|
6
|
-
|
|
7
5
|
has_many :promotion_rule_users, class_name: 'Spree::PromotionRuleUser',
|
|
8
|
-
foreign_key: :promotion_rule_id
|
|
6
|
+
foreign_key: :promotion_rule_id,
|
|
7
|
+
dependent: :destroy
|
|
9
8
|
has_many :users, through: :promotion_rule_users, class_name: Spree::UserClassHandle.new
|
|
10
9
|
|
|
11
10
|
def applicable?(promotable)
|
|
@@ -73,15 +73,24 @@ module Spree
|
|
|
73
73
|
super
|
|
74
74
|
end
|
|
75
75
|
|
|
76
|
-
def expired?
|
|
77
|
-
!active?
|
|
78
|
-
end
|
|
79
|
-
|
|
80
76
|
def active?
|
|
81
77
|
(starts_at.nil? || starts_at < Time.current) &&
|
|
82
78
|
(expires_at.nil? || expires_at > Time.current)
|
|
83
79
|
end
|
|
84
80
|
|
|
81
|
+
def inactive?
|
|
82
|
+
!active?
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def expired?
|
|
86
|
+
Spree::Deprecation.warn <<-WARN.squish, caller
|
|
87
|
+
#expired? is deprecated, and will be removed in Solidus 2.0.
|
|
88
|
+
Please use #inactive? instead.
|
|
89
|
+
WARN
|
|
90
|
+
|
|
91
|
+
inactive?
|
|
92
|
+
end
|
|
93
|
+
|
|
85
94
|
def activate(order:, line_item: nil, user: nil, path: nil, promotion_code: nil)
|
|
86
95
|
return unless self.class.order_activatable?(order)
|
|
87
96
|
|
|
@@ -116,7 +125,7 @@ module Spree
|
|
|
116
125
|
|
|
117
126
|
# called anytime order.update! happens
|
|
118
127
|
def eligible?(promotable, promotion_code: nil)
|
|
119
|
-
return false if
|
|
128
|
+
return false if inactive?
|
|
120
129
|
return false if usage_limit_exceeded?
|
|
121
130
|
return false if promotion_code && promotion_code.usage_limit_exceeded?
|
|
122
131
|
return false if blacklisted?(promotable)
|
|
@@ -176,8 +185,9 @@ module Spree
|
|
|
176
185
|
count(:order_id)
|
|
177
186
|
end
|
|
178
187
|
|
|
179
|
-
# TODO: specs
|
|
180
188
|
def line_item_actionable?(order, line_item, promotion_code: nil)
|
|
189
|
+
return false if blacklisted?(line_item)
|
|
190
|
+
|
|
181
191
|
if eligible?(order, promotion_code: promotion_code)
|
|
182
192
|
rules = eligible_rules(order)
|
|
183
193
|
if rules.blank?
|
|
@@ -12,7 +12,7 @@ module Spree
|
|
|
12
12
|
if order.coupon_code.present?
|
|
13
13
|
if promotion.present? && promotion.actions.exists?
|
|
14
14
|
handle_present_promotion(promotion)
|
|
15
|
-
elsif promotion_code && promotion_code.promotion.
|
|
15
|
+
elsif promotion_code && promotion_code.promotion.inactive?
|
|
16
16
|
set_error_code :coupon_code_expired
|
|
17
17
|
else
|
|
18
18
|
set_error_code :coupon_code_not_found
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
module Spree
|
|
2
2
|
class Shipment < Spree::Base
|
|
3
3
|
belongs_to :order, class_name: 'Spree::Order', touch: true, inverse_of: :shipments
|
|
4
|
-
belongs_to :address, class_name: 'Spree::Address'
|
|
5
4
|
belongs_to :stock_location, class_name: 'Spree::StockLocation'
|
|
6
5
|
|
|
7
6
|
has_many :adjustments, as: :adjustable, inverse_of: :adjustable, dependent: :delete_all
|
|
@@ -11,8 +10,6 @@ module Spree
|
|
|
11
10
|
has_many :state_changes, as: :stateful
|
|
12
11
|
has_many :cartons, -> { uniq }, through: :inventory_units
|
|
13
12
|
|
|
14
|
-
after_save :update_adjustments
|
|
15
|
-
|
|
16
13
|
before_validation :set_cost_zero_when_nil
|
|
17
14
|
|
|
18
15
|
before_destroy :ensure_can_destroy
|
|
@@ -21,7 +18,6 @@ module Spree
|
|
|
21
18
|
# from outside of the state machine and can actually pass variables through.
|
|
22
19
|
attr_accessor :special_instructions, :suppress_mailer
|
|
23
20
|
|
|
24
|
-
accepts_nested_attributes_for :address
|
|
25
21
|
accepts_nested_attributes_for :inventory_units
|
|
26
22
|
|
|
27
23
|
make_permalink field: :number, length: 11, prefix: 'H'
|
|
@@ -210,8 +206,13 @@ module Spree
|
|
|
210
206
|
end
|
|
211
207
|
|
|
212
208
|
def selected_shipping_rate_id=(id)
|
|
213
|
-
|
|
214
|
-
shipping_rates.
|
|
209
|
+
selected_shipping_rate.update(selected: false) if selected_shipping_rate
|
|
210
|
+
new_rate = shipping_rates.detect { |rate| rate.id == id.to_i }
|
|
211
|
+
fail(
|
|
212
|
+
ArgumentError,
|
|
213
|
+
"Could not find shipping rate id #{id} for shipment #{number}"
|
|
214
|
+
) unless new_rate
|
|
215
|
+
new_rate.update(selected: true)
|
|
215
216
|
save!
|
|
216
217
|
end
|
|
217
218
|
|
|
@@ -372,6 +373,11 @@ module Spree
|
|
|
372
373
|
!stock_location || stock_location.fulfillable?
|
|
373
374
|
end
|
|
374
375
|
|
|
376
|
+
def address
|
|
377
|
+
Spree::Deprecation.warn("Calling Shipment#address is deprecated. Use Order#ship_address instead", caller)
|
|
378
|
+
order.ship_address if order
|
|
379
|
+
end
|
|
380
|
+
|
|
375
381
|
private
|
|
376
382
|
|
|
377
383
|
def after_ship
|
|
@@ -396,20 +402,10 @@ module Spree
|
|
|
396
402
|
stock_location.unstock item.variant, item.quantity, self
|
|
397
403
|
end
|
|
398
404
|
|
|
399
|
-
def recalculate_adjustments
|
|
400
|
-
Spree::ItemAdjustments.new(self).update
|
|
401
|
-
end
|
|
402
|
-
|
|
403
405
|
def set_cost_zero_when_nil
|
|
404
406
|
self.cost = 0 unless cost
|
|
405
407
|
end
|
|
406
408
|
|
|
407
|
-
def update_adjustments
|
|
408
|
-
if cost_changed? && state != 'shipped'
|
|
409
|
-
recalculate_adjustments
|
|
410
|
-
end
|
|
411
|
-
end
|
|
412
|
-
|
|
413
409
|
def ensure_can_destroy
|
|
414
410
|
unless pending?
|
|
415
411
|
errors.add(:state, :cannot_destroy, state: state)
|
|
@@ -45,7 +45,7 @@ module Spree
|
|
|
45
45
|
|
|
46
46
|
def shipping_methods(package)
|
|
47
47
|
package.shipping_methods
|
|
48
|
-
.available_for_address(package.shipment.
|
|
48
|
+
.available_for_address(package.shipment.order.ship_address)
|
|
49
49
|
.includes(:calculator, tax_category: :tax_rates)
|
|
50
50
|
.to_a
|
|
51
51
|
.select do |ship_method|
|
|
@@ -8,7 +8,7 @@ module Spree
|
|
|
8
8
|
|
|
9
9
|
validates :stock_location, :variant, presence: true
|
|
10
10
|
validates :variant_id, uniqueness: { scope: [:stock_location_id, :deleted_at] }, allow_blank: true, unless: :deleted_at
|
|
11
|
-
validates :count_on_hand, numericality: { greater_than_or_equal_to: 0 },
|
|
11
|
+
validates :count_on_hand, numericality: { greater_than_or_equal_to: 0 }, unless: :backorderable?
|
|
12
12
|
|
|
13
13
|
delegate :weight, :should_track_inventory?, to: :variant
|
|
14
14
|
|
|
@@ -83,10 +83,6 @@ module Spree
|
|
|
83
83
|
|
|
84
84
|
private
|
|
85
85
|
|
|
86
|
-
def verify_count_on_hand?
|
|
87
|
-
count_on_hand_changed? && !backorderable? && (count_on_hand < count_on_hand_was) && (count_on_hand < 0)
|
|
88
|
-
end
|
|
89
|
-
|
|
90
86
|
def count_on_hand=(value)
|
|
91
87
|
write_attribute(:count_on_hand, value)
|
|
92
88
|
end
|
|
@@ -114,7 +110,7 @@ module Spree
|
|
|
114
110
|
def inventory_cache_threshold
|
|
115
111
|
# only warn if store is setting binary_inventory_cache (default = false)
|
|
116
112
|
@cache_threshold ||= if Spree::Config.binary_inventory_cache
|
|
117
|
-
|
|
113
|
+
Spree::Deprecation.warn "Spree::Config.binary_inventory_cache=true is DEPRECATED. Instead use Spree::Config.inventory_cache_threshold=1"
|
|
118
114
|
1
|
|
119
115
|
else
|
|
120
116
|
Spree::Config.inventory_cache_threshold
|