spree_core 2.3.1 → 2.3.2
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/app/helpers/spree/base_helper.rb +3 -3
- data/app/models/spree/ability.rb +1 -0
- data/app/models/spree/app_configuration.rb +0 -1
- data/app/models/spree/base.rb +6 -0
- data/app/models/spree/calculator/flat_percent_item_total.rb +9 -3
- data/app/models/spree/calculator/flexi_rate.rb +1 -1
- data/app/models/spree/calculator/percent_on_line_item.rb +1 -1
- data/app/models/spree/calculator/tiered_flat_rate.rb +37 -0
- data/app/models/spree/calculator/tiered_percent.rb +44 -0
- data/app/models/spree/credit_card.rb +35 -14
- data/app/models/spree/inventory_unit.rb +1 -0
- data/app/models/spree/item_adjustments.rb +3 -2
- data/app/models/spree/line_item.rb +2 -2
- data/app/models/spree/order.rb +36 -20
- data/app/models/spree/order/checkout.rb +60 -24
- data/app/models/spree/order_contents.rb +3 -6
- data/app/models/spree/order_populator.rb +1 -1
- data/app/models/spree/order_updater.rb +19 -4
- data/app/models/spree/payment.rb +4 -0
- data/app/models/spree/payment/processing.rb +6 -2
- data/app/models/spree/price.rb +10 -0
- data/app/models/spree/product.rb +81 -54
- data/app/models/spree/promotion/actions/create_adjustment.rb +2 -11
- data/app/models/spree/promotion/actions/create_item_adjustments.rb +2 -19
- data/app/models/spree/promotion_handler/cart.rb +14 -2
- data/app/models/spree/promotion_handler/coupon.rb +8 -2
- data/app/models/spree/return_authorization.rb +2 -2
- data/app/models/spree/shipping_rate.rb +2 -2
- data/app/models/spree/stock/availability_validator.rb +3 -7
- data/app/models/spree/stock/estimator.rb +1 -1
- data/app/models/spree/stock/package.rb +1 -0
- data/app/models/spree/stock_item.rb +6 -1
- data/app/models/spree/stock_location.rb +4 -0
- data/app/models/spree/tax_rate.rb +15 -2
- data/app/models/spree/variant.rb +8 -3
- data/app/models/spree/zone.rb +2 -2
- data/config/locales/en.yml +33 -3
- data/db/default/spree/countries.rb +2 -1
- data/db/migrate/20130807024302_rename_adjustment_fields.rb +2 -5
- data/db/migrate/20140804185157_add_default_to_shipment_cost.rb +10 -0
- data/lib/generators/spree/custom_user/templates/authentication_helpers.rb.tt +12 -4
- data/lib/generators/spree/install/install_generator.rb +8 -0
- data/lib/spree/core.rb +1 -0
- data/lib/spree/core/adjustment_source.rb +26 -0
- data/lib/spree/core/controller_helpers.rb +10 -9
- data/lib/spree/core/controller_helpers/order.rb +18 -5
- data/lib/spree/core/engine.rb +6 -2
- data/lib/spree/core/importer/order.rb +52 -9
- data/lib/spree/core/version.rb +1 -1
- data/lib/spree/permitted_attributes.rb +4 -4
- data/lib/spree/testing_support/authorization_helpers.rb +1 -1
- data/lib/spree/testing_support/factories/product_factory.rb +1 -1
- metadata +27 -37
@@ -9,6 +9,7 @@ module Spree
|
|
9
9
|
def add(variant, quantity = 1, currency = nil, shipment = nil)
|
10
10
|
line_item = add_to_line_item(variant, quantity, currency, shipment)
|
11
11
|
reload_totals
|
12
|
+
shipment.present? ? shipment.update_amounts : order.ensure_updated_shipments
|
12
13
|
PromotionHandler::Cart.new(order, line_item).activate
|
13
14
|
ItemAdjustments.new(line_item).update
|
14
15
|
reload_totals
|
@@ -18,6 +19,7 @@ module Spree
|
|
18
19
|
def remove(variant, quantity = 1, shipment = nil)
|
19
20
|
line_item = remove_from_line_item(variant, quantity, shipment)
|
20
21
|
reload_totals
|
22
|
+
shipment.present? ? shipment.update_amounts : order.ensure_updated_shipments
|
21
23
|
PromotionHandler::Cart.new(order, line_item).activate
|
22
24
|
ItemAdjustments.new(line_item).update
|
23
25
|
reload_totals
|
@@ -47,12 +49,7 @@ module Spree
|
|
47
49
|
|
48
50
|
def reload_totals
|
49
51
|
order_updater.update_item_count
|
50
|
-
order_updater.
|
51
|
-
order_updater.update_adjustment_total
|
52
|
-
|
53
|
-
order_updater.update_payment_state if order.completed?
|
54
|
-
order_updater.persist_totals
|
55
|
-
|
52
|
+
order_updater.update
|
56
53
|
order.reload
|
57
54
|
end
|
58
55
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module Spree
|
2
2
|
class OrderUpdater
|
3
3
|
attr_reader :order
|
4
|
-
delegate :payments, :line_items, :adjustments, :all_adjustments, :shipments, :update_hooks, to: :order
|
4
|
+
delegate :payments, :line_items, :adjustments, :all_adjustments, :shipments, :update_hooks, :quantity, to: :order
|
5
5
|
|
6
6
|
def initialize(order)
|
7
7
|
@order = order
|
@@ -38,6 +38,7 @@ module Spree
|
|
38
38
|
# +payment_total+ The total value of all finalized Payments (NOTE: non-finalized Payments are excluded)
|
39
39
|
# +item_total+ The total value of all LineItems
|
40
40
|
# +adjustment_total+ The total value of all adjustments (promotions, credits, etc.)
|
41
|
+
# +promo_total+ The total value of all promotion adjustments
|
41
42
|
# +total+ The so-called "order total." This is equivalent to +item_total+ plus +adjustment_total+.
|
42
43
|
def update_totals
|
43
44
|
update_payment_total
|
@@ -49,7 +50,12 @@ module Spree
|
|
49
50
|
|
50
51
|
# give each of the shipments a chance to update themselves
|
51
52
|
def update_shipments
|
52
|
-
shipments.each
|
53
|
+
shipments.each do |shipment|
|
54
|
+
next unless shipment.persisted?
|
55
|
+
shipment.update!(order)
|
56
|
+
shipment.refresh_rates
|
57
|
+
shipment.update_amounts
|
58
|
+
end
|
53
59
|
end
|
54
60
|
|
55
61
|
def update_payment_total
|
@@ -73,11 +79,15 @@ module Spree
|
|
73
79
|
order.included_tax_total = line_items.sum(:included_tax_total) + shipments.sum(:included_tax_total)
|
74
80
|
order.additional_tax_total = line_items.sum(:additional_tax_total) + shipments.sum(:additional_tax_total)
|
75
81
|
|
82
|
+
order.promo_total = line_items.sum(:promo_total) +
|
83
|
+
shipments.sum(:promo_total) +
|
84
|
+
adjustments.promotion.eligible.sum(:amount)
|
85
|
+
|
76
86
|
update_order_total
|
77
87
|
end
|
78
88
|
|
79
89
|
def update_item_count
|
80
|
-
order.item_count =
|
90
|
+
order.item_count = quantity
|
81
91
|
end
|
82
92
|
|
83
93
|
def update_item_total
|
@@ -96,6 +106,7 @@ module Spree
|
|
96
106
|
additional_tax_total: order.additional_tax_total,
|
97
107
|
payment_total: order.payment_total,
|
98
108
|
shipment_total: order.shipment_total,
|
109
|
+
promo_total: order.promo_total,
|
99
110
|
total: order.total,
|
100
111
|
updated_at: Time.now,
|
101
112
|
)
|
@@ -145,8 +156,12 @@ module Spree
|
|
145
156
|
# The +payment_state+ value helps with reporting, etc. since it provides a quick and easy way to locate Orders needing attention.
|
146
157
|
def update_payment_state
|
147
158
|
last_state = order.payment_state
|
148
|
-
if payments.present? && payments.
|
159
|
+
if payments.present? && payments.valid.size == 0
|
149
160
|
order.payment_state = 'failed'
|
161
|
+
elsif !payments.present? && order.state == 'canceled'
|
162
|
+
order.payment_state = 'void'
|
163
|
+
elsif order.state == 'canceled' && order.payment_total == 0 && payments.completed.size > 0
|
164
|
+
order.payment_state = 'void'
|
150
165
|
else
|
151
166
|
order.payment_state = 'balance_due' if order.outstanding_balance > 0
|
152
167
|
order.payment_state = 'credit_owed' if order.outstanding_balance < 0
|
data/app/models/spree/payment.rb
CHANGED
@@ -30,6 +30,8 @@ module Spree
|
|
30
30
|
|
31
31
|
after_initialize :build_source
|
32
32
|
|
33
|
+
default_scope -> { order("#{self.table_name}.created_at") }
|
34
|
+
|
33
35
|
scope :from_credit_card, -> { where(source_type: 'Spree::CreditCard') }
|
34
36
|
scope :with_state, ->(s) { where(state: s.to_s) }
|
35
37
|
scope :completed, -> { with_state('completed') }
|
@@ -173,6 +175,8 @@ module Spree
|
|
173
175
|
|
174
176
|
def create_payment_profile
|
175
177
|
return unless source.respond_to?(:has_payment_profile?) && !source.has_payment_profile?
|
178
|
+
# Imported payments shouldn't create a payment profile.
|
179
|
+
return if source.imported
|
176
180
|
|
177
181
|
payment_method.create_profile(self)
|
178
182
|
rescue ActiveMerchant::ConnectionError => e
|
@@ -5,7 +5,7 @@ module Spree
|
|
5
5
|
if payment_method && payment_method.source_required?
|
6
6
|
if source
|
7
7
|
if !processing?
|
8
|
-
if payment_method.supports?(source)
|
8
|
+
if payment_method.supports?(source) || token_based?
|
9
9
|
if payment_method.auto_capture?
|
10
10
|
purchase!
|
11
11
|
else
|
@@ -115,7 +115,7 @@ module Spree
|
|
115
115
|
if payment_method.respond_to?(:cancel)
|
116
116
|
payment_method.cancel(response_code)
|
117
117
|
else
|
118
|
-
credit!
|
118
|
+
credit!(credit_allowed.abs)
|
119
119
|
end
|
120
120
|
end
|
121
121
|
|
@@ -219,6 +219,10 @@ module Spree
|
|
219
219
|
def gateway_order_id
|
220
220
|
"#{order.number}-#{self.identifier}"
|
221
221
|
end
|
222
|
+
|
223
|
+
def token_based?
|
224
|
+
source.gateway_customer_profile_id.present? || source.gateway_payment_profile_id.present?
|
225
|
+
end
|
222
226
|
end
|
223
227
|
end
|
224
228
|
end
|
data/app/models/spree/price.rb
CHANGED
@@ -5,6 +5,7 @@ module Spree
|
|
5
5
|
|
6
6
|
validate :check_price
|
7
7
|
validates :amount, numericality: { greater_than_or_equal_to: 0 }, allow_nil: true
|
8
|
+
validate :validate_amount_maximum
|
8
9
|
|
9
10
|
def display_amount
|
10
11
|
money
|
@@ -50,5 +51,14 @@ module Spree
|
|
50
51
|
price.to_d
|
51
52
|
end
|
52
53
|
|
54
|
+
def maximum_amount
|
55
|
+
BigDecimal '999999.99'
|
56
|
+
end
|
57
|
+
|
58
|
+
def validate_amount_maximum
|
59
|
+
if amount && amount > maximum_amount
|
60
|
+
errors.add :amount, I18n.t('errors.messages.less_than_or_equal_to', count: maximum_amount)
|
61
|
+
end
|
62
|
+
end
|
53
63
|
end
|
54
64
|
end
|
data/app/models/spree/product.rb
CHANGED
@@ -57,6 +57,9 @@ module Spree
|
|
57
57
|
has_many :prices, -> { order('spree_variants.position, spree_variants.id, currency') }, through: :variants
|
58
58
|
|
59
59
|
has_many :stock_items, through: :variants_including_master
|
60
|
+
|
61
|
+
has_many :line_items, through: :variants_including_master
|
62
|
+
has_many :orders, through: :line_items
|
60
63
|
|
61
64
|
delegate_belongs_to :master, :sku, :price, :currency, :display_amount, :display_price, :weight, :height, :width, :depth, :is_master, :has_default_price?, :cost_currency, :price_in, :amount_in
|
62
65
|
|
@@ -67,7 +70,8 @@ module Spree
|
|
67
70
|
after_create :build_variants_from_option_values_hash, if: :option_values_hash
|
68
71
|
|
69
72
|
after_save :save_master
|
70
|
-
after_save :
|
73
|
+
after_save :run_touch_callbacks, if: :anything_changed?
|
74
|
+
after_save :reset_nested_changes
|
71
75
|
after_touch :touch_taxons
|
72
76
|
|
73
77
|
delegate :images, to: :master, prefix: true
|
@@ -199,10 +203,10 @@ module Spree
|
|
199
203
|
end
|
200
204
|
|
201
205
|
def total_on_hand
|
202
|
-
if
|
206
|
+
if any_variants_not_track_inventory?
|
203
207
|
Float::INFINITY
|
204
208
|
else
|
205
|
-
|
209
|
+
stock_items.sum(:count_on_hand)
|
206
210
|
end
|
207
211
|
end
|
208
212
|
|
@@ -215,70 +219,93 @@ module Spree
|
|
215
219
|
|
216
220
|
private
|
217
221
|
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
# Builds variants from a hash of option types & values
|
223
|
-
def build_variants_from_option_values_hash
|
224
|
-
ensure_option_types_exist_for_values_hash
|
225
|
-
values = option_values_hash.values
|
226
|
-
values = values.inject(values.shift) { |memo, value| memo.product(value).map(&:flatten) }
|
227
|
-
|
228
|
-
values.each do |ids|
|
229
|
-
variant = variants.create(
|
230
|
-
option_value_ids: ids,
|
231
|
-
price: master.price
|
232
|
-
)
|
222
|
+
def add_properties_and_option_types_from_prototype
|
223
|
+
if prototype_id && prototype = Spree::Prototype.find_by(id: prototype_id)
|
224
|
+
prototype.properties.each do |property|
|
225
|
+
product_properties.create(property: property)
|
233
226
|
end
|
234
|
-
|
227
|
+
self.option_types = prototype.option_types
|
235
228
|
end
|
229
|
+
end
|
236
230
|
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
self.option_types = prototype.option_types
|
243
|
-
end
|
231
|
+
def any_variants_not_track_inventory?
|
232
|
+
if variants_including_master.loaded?
|
233
|
+
variants_including_master.any? { |v| !v.should_track_inventory? }
|
234
|
+
else
|
235
|
+
!Spree::Config.track_inventory_levels || variants_including_master.where(track_inventory: false).any?
|
244
236
|
end
|
237
|
+
end
|
245
238
|
|
246
|
-
|
247
|
-
|
248
|
-
|
239
|
+
# Builds variants from a hash of option types & values
|
240
|
+
def build_variants_from_option_values_hash
|
241
|
+
ensure_option_types_exist_for_values_hash
|
242
|
+
values = option_values_hash.values
|
243
|
+
values = values.inject(values.shift) { |memo, value| memo.product(value).map(&:flatten) }
|
244
|
+
|
245
|
+
values.each do |ids|
|
246
|
+
variant = variants.create(
|
247
|
+
option_value_ids: ids,
|
248
|
+
price: master.price
|
249
|
+
)
|
249
250
|
end
|
251
|
+
save
|
252
|
+
end
|
250
253
|
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
end
|
254
|
+
def ensure_master
|
255
|
+
return unless new_record?
|
256
|
+
self.master ||= Variant.new
|
257
|
+
end
|
256
258
|
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
end
|
259
|
+
def normalize_slug
|
260
|
+
self.slug = normalize_friendly_id(slug)
|
261
|
+
end
|
261
262
|
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
Spree::Taxon.where(id: taxons_to_touch.map(&:id)).update_all(updated_at: Time.current)
|
263
|
+
def punch_slug
|
264
|
+
update_column :slug, "#{Time.now.to_i}_#{slug}" # punch slug with date prefix to allow reuse of original
|
265
|
+
end
|
266
266
|
|
267
|
-
|
268
|
-
|
269
|
-
|
267
|
+
def anything_changed?
|
268
|
+
changed? || @nested_changes
|
269
|
+
end
|
270
270
|
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
:name,
|
275
|
-
[:name, :sku]
|
276
|
-
]
|
277
|
-
end
|
271
|
+
def reset_nested_changes
|
272
|
+
@nested_changes = false
|
273
|
+
end
|
278
274
|
|
279
|
-
|
280
|
-
|
275
|
+
# there's a weird quirk with the delegate stuff that does not automatically save the delegate object
|
276
|
+
# when saving so we force a save using a hook.
|
277
|
+
def save_master
|
278
|
+
if master && (master.changed? || master.new_record? || (master.default_price && (master.default_price.changed? || master.default_price.new_record?)))
|
279
|
+
master.save
|
280
|
+
@nested_changes = true
|
281
281
|
end
|
282
|
+
end
|
283
|
+
|
284
|
+
# ensures the master variant is flagged as such
|
285
|
+
def set_master_variant_defaults
|
286
|
+
master.is_master = true
|
287
|
+
end
|
288
|
+
|
289
|
+
# Try building a slug based on the following fields in increasing order of specificity.
|
290
|
+
def slug_candidates
|
291
|
+
[
|
292
|
+
:name,
|
293
|
+
[:name, :sku]
|
294
|
+
]
|
295
|
+
end
|
296
|
+
|
297
|
+
def run_touch_callbacks
|
298
|
+
run_callbacks(:touch)
|
299
|
+
end
|
300
|
+
|
301
|
+
# Iterate through this products taxons and taxonomies and touch their timestamps in a batch
|
302
|
+
def touch_taxons
|
303
|
+
taxons_to_touch = taxons.map(&:self_and_ancestors).flatten.uniq
|
304
|
+
Spree::Taxon.where(id: taxons_to_touch.map(&:id)).update_all(updated_at: Time.current)
|
305
|
+
|
306
|
+
taxonomy_ids_to_touch = taxons_to_touch.map(&:taxonomy_id).flatten.uniq
|
307
|
+
Spree::Taxonomy.where(id: taxonomy_ids_to_touch).update_all(updated_at: Time.current)
|
308
|
+
end
|
282
309
|
|
283
310
|
end
|
284
311
|
end
|
@@ -3,13 +3,14 @@ module Spree
|
|
3
3
|
module Actions
|
4
4
|
class CreateAdjustment < PromotionAction
|
5
5
|
include Spree::Core::CalculatedAdjustments
|
6
|
+
include Spree::Core::AdjustmentSource
|
6
7
|
|
7
8
|
has_many :adjustments, as: :source
|
8
9
|
|
9
10
|
delegate :eligible?, to: :promotion
|
10
11
|
|
11
12
|
before_validation :ensure_action_has_calculator
|
12
|
-
before_destroy :
|
13
|
+
before_destroy :deals_with_adjustments_for_deleted_source
|
13
14
|
|
14
15
|
# Creates the adjustment related to a promotion for the order passed
|
15
16
|
# through options hash
|
@@ -55,16 +56,6 @@ module Spree
|
|
55
56
|
self.calculator = Calculator::FlatPercentItemTotal.new
|
56
57
|
end
|
57
58
|
|
58
|
-
def deals_with_adjustments
|
59
|
-
adjustment_scope = self.adjustments.joins("LEFT OUTER JOIN spree_orders ON spree_orders.id = spree_adjustments.adjustable_id")
|
60
|
-
# For incomplete orders, remove the adjustment completely.
|
61
|
-
adjustment_scope.where("spree_orders.completed_at IS NULL").readonly(false).destroy_all
|
62
|
-
|
63
|
-
# For complete orders, the source will be invalid.
|
64
|
-
# Therefore we nullify the source_id, leaving the adjustment in place.
|
65
|
-
# This would mean that the order's total is not altered at all.
|
66
|
-
adjustment_scope.where("spree_orders.completed_at IS NOT NULL").update_all("source_id = NULL")
|
67
|
-
end
|
68
59
|
end
|
69
60
|
end
|
70
61
|
end
|
@@ -3,13 +3,14 @@ module Spree
|
|
3
3
|
module Actions
|
4
4
|
class CreateItemAdjustments < PromotionAction
|
5
5
|
include Spree::Core::CalculatedAdjustments
|
6
|
+
include Spree::Core::AdjustmentSource
|
6
7
|
|
7
8
|
has_many :adjustments, as: :source
|
8
9
|
|
9
10
|
delegate :eligible?, to: :promotion
|
10
11
|
|
11
12
|
before_validation :ensure_action_has_calculator
|
12
|
-
before_destroy :
|
13
|
+
before_destroy :deals_with_adjustments_for_deleted_source
|
13
14
|
|
14
15
|
def perform(payload = {})
|
15
16
|
order = payload[:order]
|
@@ -62,24 +63,6 @@ module Spree
|
|
62
63
|
self.calculator = Calculator::PercentOnLineItem.new
|
63
64
|
end
|
64
65
|
|
65
|
-
def deals_with_adjustments
|
66
|
-
adjustment_scope = self.adjustments.includes(:order).references(:spree_orders)
|
67
|
-
|
68
|
-
# For incomplete orders, remove the adjustment completely.
|
69
|
-
adjustment_scope.where("spree_orders.completed_at IS NULL").each do |adjustment|
|
70
|
-
adjustment.destroy
|
71
|
-
end
|
72
|
-
|
73
|
-
# For complete orders, the source will be invalid.
|
74
|
-
# Therefore we nullify the source_id, leaving the adjustment in place.
|
75
|
-
# This would mean that the order's total is not altered at all.
|
76
|
-
adjustment_scope.where("spree_orders.completed_at IS NOT NULL").each do |adjustment|
|
77
|
-
adjustment.update_columns(
|
78
|
-
source_id: nil,
|
79
|
-
updated_at: Time.now,
|
80
|
-
)
|
81
|
-
end
|
82
|
-
end
|
83
66
|
end
|
84
67
|
end
|
85
68
|
end
|
@@ -29,9 +29,21 @@ module Spree
|
|
29
29
|
end
|
30
30
|
|
31
31
|
private
|
32
|
-
|
33
32
|
def promotions
|
34
|
-
Promotion.
|
33
|
+
promo_table = Promotion.arel_table
|
34
|
+
join_table = Arel::Table.new(:spree_orders_promotions)
|
35
|
+
|
36
|
+
join_condition = promo_table.join(join_table, Arel::Nodes::OuterJoin).on(
|
37
|
+
promo_table[:id].eq(join_table[:promotion_id])
|
38
|
+
).join_sources
|
39
|
+
|
40
|
+
Promotion.active.includes(:promotion_rules).
|
41
|
+
joins(join_condition).
|
42
|
+
where(
|
43
|
+
promo_table[:code].eq(nil).and(
|
44
|
+
promo_table[:path].eq(nil)
|
45
|
+
).or(join_table[:order_id].eq(order.id))
|
46
|
+
).distinct
|
35
47
|
end
|
36
48
|
end
|
37
49
|
end
|