spree_core 2.2.4 → 2.2.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/app/helpers/spree/base_helper.rb +1 -1
  3. data/app/models/spree/credit_card.rb +7 -3
  4. data/app/models/spree/inventory_unit.rb +1 -0
  5. data/app/models/spree/item_adjustments.rb +3 -2
  6. data/app/models/spree/line_item.rb +1 -1
  7. data/app/models/spree/order.rb +24 -16
  8. data/app/models/spree/order/checkout.rb +6 -3
  9. data/app/models/spree/order_contents.rb +3 -6
  10. data/app/models/spree/order_populator.rb +1 -1
  11. data/app/models/spree/order_updater.rb +12 -1
  12. data/app/models/spree/payment/processing.rb +5 -1
  13. data/app/models/spree/product.rb +78 -54
  14. data/app/models/spree/promotion/actions/create_adjustment.rb +2 -11
  15. data/app/models/spree/promotion/actions/create_item_adjustments.rb +2 -19
  16. data/app/models/spree/promotion_handler/cart.rb +14 -2
  17. data/app/models/spree/promotion_handler/coupon.rb +8 -2
  18. data/app/models/spree/return_authorization.rb +2 -2
  19. data/app/models/spree/shipment.rb +30 -0
  20. data/app/models/spree/shipping_rate.rb +2 -2
  21. data/app/models/spree/stock/availability_validator.rb +3 -7
  22. data/app/models/spree/stock/package.rb +1 -0
  23. data/app/models/spree/stock_item.rb +6 -1
  24. data/app/models/spree/stock_location.rb +4 -0
  25. data/app/models/spree/tax_rate.rb +15 -2
  26. data/app/models/spree/variant.rb +5 -1
  27. data/config/locales/en.yml +6 -0
  28. data/db/default/spree/countries.rb +2 -1
  29. data/db/migrate/20130807024302_rename_adjustment_fields.rb +2 -5
  30. data/db/migrate/20140804185157_add_default_to_shipment_cost.rb +10 -0
  31. data/lib/generators/spree/install/install_generator.rb +8 -0
  32. data/lib/spree/core.rb +1 -0
  33. data/lib/spree/core/adjustment_source.rb +26 -0
  34. data/lib/spree/core/controller_helpers/order.rb +1 -1
  35. data/lib/spree/core/importer/order.rb +15 -8
  36. data/lib/spree/core/version.rb +1 -1
  37. metadata +34 -18
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 92fd29fe255bb2c31f173def5f8f34ea17620370
4
- data.tar.gz: bfb2e716ea91a48d83f18df9fb25a1e926ddbb27
3
+ metadata.gz: 88ab16ad8f605d308074b823d9a7c70f6504bb08
4
+ data.tar.gz: 6bcb7afca77e7006f6eb12e4ba1177b66bd3a585
5
5
  SHA512:
6
- metadata.gz: 6940b7c73074114e90bf31b084ca912ce042b2c6dc1d3b84e1fdebe4373f348363c5ef2fad136ed8e398d7f4d53b26d16e50336b0838a24e8029b9a683c3f410
7
- data.tar.gz: 32dff2ab726e1acd863fbaaa7a7f439ffffb07ed343db015e0f223d653f225b2ba2dd5237d4ba20b38a84697022dc9f9a7848cbe5c88fd6298402908797c579d
6
+ metadata.gz: 91fa2acf65367622c4d6d526beb7d696dad6b2a143ee03a376c4045741620840c60b01f749feb8e771ca9fd618695872b37498d08369c35052c6b1d10cc2c176
7
+ data.tar.gz: 8e1596b38affe2af7d32a8dfefceea76e9626ad4b1540bf8ba3655d0d787fc6834544476ebfc0f7529413aac35ac132690574a6fbeb6afc83d333df7d7f253fa
@@ -118,7 +118,7 @@ module Spree
118
118
  countries.collect do |country|
119
119
  country.name = Spree.t(country.iso, scope: 'country_names', default: country.name)
120
120
  country
121
- end.sort { |a, b| a.name.parameterize <=> b.name.parameterize }
121
+ end.sort_by { |c| c.name.parameterize }
122
122
  end
123
123
 
124
124
  def seo_url(taxon)
@@ -132,9 +132,13 @@ module Spree
132
132
 
133
133
  def expiry_not_in_the_past
134
134
  if year.present? && month.present?
135
- time = "#{year}-#{month}-1".to_time
136
- if time < Time.zone.now.to_time.beginning_of_month
137
- errors.add(:base, :card_expired)
135
+ if month.to_i < 1 || month.to_i > 12
136
+ errors.add(:base, :expiry_invalid)
137
+ else
138
+ time = Time.zone.parse("#{year}-#{month}-1")
139
+ if time < Time.zone.now.to_time.beginning_of_month
140
+ errors.add(:base, :card_expired)
141
+ end
138
142
  end
139
143
  end
140
144
  end
@@ -70,6 +70,7 @@ module Spree
70
70
  end
71
71
 
72
72
  def update_order
73
+ self.reload
73
74
  order.update!
74
75
  end
75
76
  end
@@ -47,8 +47,9 @@ module Spree
47
47
  included_tax_total = 0
48
48
  additional_tax_total = 0
49
49
  run_callbacks :tax_adjustments do
50
- included_tax_total = adjustments.tax.included.reload.map(&:update!).compact.sum
51
- additional_tax_total = adjustments.tax.additional.reload.map(&:update!).compact.sum
50
+ tax = (item.respond_to?(:all_adjustments) ? item.all_adjustments : item.adjustments).tax
51
+ included_tax_total = tax.included.reload.map(&:update!).compact.sum
52
+ additional_tax_total = tax.additional.reload.map(&:update!).compact.sum
52
53
  end
53
54
 
54
55
  item.update_columns(
@@ -123,7 +123,7 @@ module Spree
123
123
 
124
124
  def ensure_proper_currency
125
125
  unless currency == order.currency
126
- errors.add(:currency, t(:must_match_order_currency))
126
+ errors.add(:currency, :must_match_order_currency)
127
127
  end
128
128
  end
129
129
  end
@@ -3,16 +3,13 @@ require 'spree/order/checkout'
3
3
 
4
4
  module Spree
5
5
  class Order < ActiveRecord::Base
6
- include Checkout
7
- include CurrencyUpdater
6
+ include Spree::Order::Checkout
7
+ include Spree::Order::CurrencyUpdater
8
8
 
9
9
  checkout_flow do
10
10
  go_to_state :address
11
11
  go_to_state :delivery
12
- go_to_state :payment, if: ->(order) do
13
- order.set_shipments_cost if order.shipments.any?
14
- order.payment_required?
15
- end
12
+ go_to_state :payment, if: ->(order) { order.payment_required? }
16
13
  go_to_state :confirm, if: ->(order) { order.confirmation_required? }
17
14
  go_to_state :complete
18
15
  remove_transition from: :delivery, to: :confirm
@@ -76,6 +73,7 @@ module Spree
76
73
 
77
74
  validates :email, presence: true, if: :require_email
78
75
  validates :email, email: true, if: :require_email, allow_blank: true
76
+ validates :number, uniqueness: true
79
77
  validate :has_available_shipment
80
78
 
81
79
  make_permalink field: :number
@@ -91,6 +89,7 @@ module Spree
91
89
 
92
90
  scope :created_between, ->(start_date, end_date) { where(created_at: start_date..end_date) }
93
91
  scope :completed_between, ->(start_date, end_date) { where(completed_at: start_date..end_date) }
92
+ scope :reverse_chronological, -> { order(created_at: :desc) }
94
93
 
95
94
  def self.between(start_date, end_date)
96
95
  ActiveSupport::Deprecation.warn("Order#between will be deprecated in Spree 2.3, please use either Order#created_between or Order#completed_between instead.")
@@ -268,15 +267,18 @@ module Spree
268
267
  end
269
268
  end
270
269
 
271
- # FIXME refactor this method and implement validation using validates_* utilities
272
- def generate_order_number
273
- record = true
274
- while record
275
- random = "R#{Array.new(9){rand(9)}.join}"
276
- record = self.class.where(number: random).first
277
- end
278
- self.number = random if self.number.blank?
279
- self.number
270
+ def generate_order_number(digits = 9)
271
+ self.number ||= loop do
272
+ # Make a random number.
273
+ random = "R#{Array.new(digits){rand(10)}.join}"
274
+ # Use the random number if no other order exists with it.
275
+ if self.class.exists?(number: random)
276
+ # If over half of all possible options are taken add another digit.
277
+ digits += 1 if self.class.count > (10 ** digits / 2)
278
+ else
279
+ break random
280
+ end
281
+ end
280
282
  end
281
283
 
282
284
  def shipped_shipments
@@ -412,6 +414,12 @@ module Spree
412
414
  line_items.select(&:insufficient_stock?)
413
415
  end
414
416
 
417
+ def ensure_line_items_are_in_stock
418
+ if insufficient_stock_lines.present?
419
+ errors.add(:base, Spree.t(:insufficient_stock_lines_present)) and return false
420
+ end
421
+ end
422
+
415
423
  def merge!(order, user = nil)
416
424
  order.line_items.each do |line_item|
417
425
  next unless line_item.currency == currency
@@ -582,7 +590,7 @@ module Spree
582
590
  self.ensure_updated_shipments
583
591
  end
584
592
 
585
- def reload
593
+ def reload(options=nil)
586
594
  remove_instance_variable(:@tax_zone) if defined?(@tax_zone)
587
595
  super
588
596
  end
@@ -92,9 +92,11 @@ module Spree
92
92
  before_transition :from => :delivery, :do => :apply_free_shipping_promotions
93
93
  end
94
94
 
95
- after_transition :to => :complete, :do => :finalize!
96
- after_transition :to => :resumed, :do => :after_resume
97
- after_transition :to => :canceled, :do => :after_cancel
95
+ before_transition to: :resumed, do: :ensure_line_items_are_in_stock
96
+
97
+ after_transition to: :complete, do: :finalize!
98
+ after_transition to: :resumed, do: :after_resume
99
+ after_transition to: :canceled, do: :after_cancel
98
100
 
99
101
  after_transition :from => any - :cart, :to => any - [:confirm, :complete] do |order|
100
102
  order.update_totals
@@ -230,6 +232,7 @@ module Spree
230
232
  end
231
233
 
232
234
  success = self.update_attributes(attributes)
235
+ set_shipments_cost if self.shipments.any?
233
236
  end
234
237
  @updating_params = nil
235
238
  success
@@ -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.update_item_total
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
 
@@ -9,9 +9,9 @@ module Spree
9
9
  @errors = ActiveModel::Errors.new(self)
10
10
  end
11
11
 
12
-
13
12
  def populate(variant_id, quantity)
14
13
  attempt_cart_add(variant_id, quantity)
14
+ order.ensure_updated_shipments
15
15
  valid?
16
16
  end
17
17
 
@@ -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
  order.payment_total = payments.completed.sum(:amount)
@@ -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 { |shipment| shipment.update!(order) }
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_shipment_total
@@ -69,6 +75,10 @@ module Spree
69
75
  order.included_tax_total = line_items.sum(:included_tax_total) + shipments.sum(:included_tax_total)
70
76
  order.additional_tax_total = line_items.sum(:additional_tax_total) + shipments.sum(:additional_tax_total)
71
77
 
78
+ order.promo_total = line_items.sum(:promo_total) +
79
+ shipments.sum(:promo_total) +
80
+ adjustments.promotion.eligible.sum(:amount)
81
+
72
82
  update_order_total
73
83
  end
74
84
 
@@ -92,6 +102,7 @@ module Spree
92
102
  additional_tax_total: order.additional_tax_total,
93
103
  payment_total: order.payment_total,
94
104
  shipment_total: order.shipment_total,
105
+ promo_total: order.promo_total,
95
106
  total: order.total,
96
107
  updated_at: Time.now,
97
108
  )
@@ -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
@@ -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
@@ -65,7 +65,8 @@ module Spree
65
65
  after_create :add_properties_and_option_types_from_prototype
66
66
  after_create :build_variants_from_option_values_hash, if: :option_values_hash
67
67
  after_save :save_master
68
- after_save :touch
68
+ after_save :run_touch_callbacks, if: :anything_changed?
69
+ after_save :reset_nested_changes
69
70
  after_touch :touch_taxons
70
71
 
71
72
  delegate :images, to: :master, prefix: true
@@ -201,10 +202,10 @@ module Spree
201
202
  end
202
203
 
203
204
  def total_on_hand
204
- if self.variants_including_master.any? { |v| !v.should_track_inventory? }
205
+ if any_variants_not_track_inventory?
205
206
  Float::INFINITY
206
207
  else
207
- self.stock_items.to_a.sum(&:count_on_hand)
208
+ stock_items.sum(:count_on_hand)
208
209
  end
209
210
  end
210
211
 
@@ -217,70 +218,93 @@ module Spree
217
218
 
218
219
  private
219
220
 
220
- def normalize_slug
221
- self.slug = normalize_friendly_id(slug)
222
- end
223
-
224
- # Builds variants from a hash of option types & values
225
- def build_variants_from_option_values_hash
226
- ensure_option_types_exist_for_values_hash
227
- values = option_values_hash.values
228
- values = values.inject(values.shift) { |memo, value| memo.product(value).map(&:flatten) }
229
-
230
- values.each do |ids|
231
- variant = variants.create(
232
- option_value_ids: ids,
233
- price: master.price
234
- )
221
+ def add_properties_and_option_types_from_prototype
222
+ if prototype_id && prototype = Spree::Prototype.find_by(id: prototype_id)
223
+ prototype.properties.each do |property|
224
+ product_properties.create(property: property)
235
225
  end
236
- save
226
+ self.option_types = prototype.option_types
237
227
  end
228
+ end
238
229
 
239
- def add_properties_and_option_types_from_prototype
240
- if prototype_id && prototype = Spree::Prototype.find_by(id: prototype_id)
241
- prototype.properties.each do |property|
242
- product_properties.create(property: property)
243
- end
244
- self.option_types = prototype.option_types
245
- end
230
+ def any_variants_not_track_inventory?
231
+ if variants_including_master.loaded?
232
+ variants_including_master.any? { |v| !v.should_track_inventory? }
233
+ else
234
+ !Spree::Config.track_inventory_levels || variants_including_master.where(track_inventory: false).any?
246
235
  end
236
+ end
247
237
 
248
- # ensures the master variant is flagged as such
249
- def set_master_variant_defaults
250
- master.is_master = true
238
+ # Builds variants from a hash of option types & values
239
+ def build_variants_from_option_values_hash
240
+ ensure_option_types_exist_for_values_hash
241
+ values = option_values_hash.values
242
+ values = values.inject(values.shift) { |memo, value| memo.product(value).map(&:flatten) }
243
+
244
+ values.each do |ids|
245
+ variant = variants.create(
246
+ option_value_ids: ids,
247
+ price: master.price
248
+ )
251
249
  end
250
+ save
251
+ end
252
252
 
253
- # there's a weird quirk with the delegate stuff that does not automatically save the delegate object
254
- # when saving so we force a save using a hook.
255
- def save_master
256
- master.save if master && (master.changed? || master.new_record? || (master.default_price && (master.default_price.changed? || master.default_price.new_record?)))
257
- end
253
+ def ensure_master
254
+ return unless new_record?
255
+ self.master ||= Variant.new
256
+ end
258
257
 
259
- def ensure_master
260
- return unless new_record?
261
- self.master ||= Variant.new
262
- end
258
+ def normalize_slug
259
+ self.slug = normalize_friendly_id(slug)
260
+ end
263
261
 
264
- # Iterate through this products taxons and taxonomies and touch their timestamps in a batch
265
- def touch_taxons
266
- taxons_to_touch = taxons.map(&:self_and_ancestors).flatten.uniq
267
- Spree::Taxon.where(id: taxons_to_touch.map(&:id)).update_all(updated_at: Time.current)
262
+ def punch_slug
263
+ update_column :slug, "#{Time.now.to_i}_#{slug}" # punch slug with date prefix to allow reuse of original
264
+ end
268
265
 
269
- taxonomy_ids_to_touch = taxons_to_touch.map(&:taxonomy_id).flatten.uniq
270
- Spree::Taxonomy.where(id: taxonomy_ids_to_touch).update_all(updated_at: Time.current)
271
- end
266
+ def anything_changed?
267
+ changed? || @nested_changes
268
+ end
272
269
 
273
- # Try building a slug based on the following fields in increasing order of specificity.
274
- def slug_candidates
275
- [
276
- :name,
277
- [:name, :sku]
278
- ]
279
- end
270
+ def reset_nested_changes
271
+ @nested_changes = false
272
+ end
280
273
 
281
- def punch_slug
282
- update(slug: "#{Time.now.to_i}_#{slug}") # punch slug with date prefix to allow reuse of original
274
+ # there's a weird quirk with the delegate stuff that does not automatically save the delegate object
275
+ # when saving so we force a save using a hook.
276
+ def save_master
277
+ if master && (master.changed? || master.new_record? || (master.default_price && (master.default_price.changed? || master.default_price.new_record?)))
278
+ master.save
279
+ @nested_changes = true
283
280
  end
281
+ end
282
+
283
+ # ensures the master variant is flagged as such
284
+ def set_master_variant_defaults
285
+ master.is_master = true
286
+ end
287
+
288
+ # Try building a slug based on the following fields in increasing order of specificity.
289
+ def slug_candidates
290
+ [
291
+ :name,
292
+ [:name, :sku]
293
+ ]
294
+ end
295
+
296
+ def run_touch_callbacks
297
+ run_callbacks(:touch)
298
+ end
299
+
300
+ # Iterate through this products taxons and taxonomies and touch their timestamps in a batch
301
+ def touch_taxons
302
+ taxons_to_touch = taxons.map(&:self_and_ancestors).flatten.uniq
303
+ Spree::Taxon.where(id: taxons_to_touch.map(&:id)).update_all(updated_at: Time.current)
304
+
305
+ taxonomy_ids_to_touch = taxons_to_touch.map(&:taxonomy_id).flatten.uniq
306
+ Spree::Taxonomy.where(id: taxonomy_ids_to_touch).update_all(updated_at: Time.current)
307
+ end
284
308
 
285
309
  end
286
310
  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 :deals_with_adjustments
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 :deals_with_adjustments
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.active.includes(:promotion_rules).where(:code => nil, :path => nil)
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
@@ -81,8 +81,14 @@ module Spree
81
81
  order.persist_totals
82
82
  self.success = Spree.t(:coupon_code_applied)
83
83
  else
84
- # if the promotion was created after the order
85
- self.error = Spree.t(:coupon_code_not_found)
84
+ # if the promotion exists on an order, but wasn't found above,
85
+ # we've already selected a better promotion
86
+ if order.promotions.with_coupon_code(order.coupon_code)
87
+ self.error = Spree.t(:coupon_code_better_exists)
88
+ else
89
+ # if the promotion was created after the order
90
+ self.error = Spree.t(:coupon_code_not_found)
91
+ end
86
92
  end
87
93
  end
88
94
  end
@@ -2,7 +2,7 @@ module Spree
2
2
  class ReturnAuthorization < ActiveRecord::Base
3
3
  belongs_to :order, class_name: 'Spree::Order'
4
4
 
5
- has_many :inventory_units
5
+ has_many :inventory_units, dependent: :nullify
6
6
  belongs_to :stock_location
7
7
  before_create :generate_number
8
8
  before_save :force_positive_amount
@@ -62,7 +62,7 @@ module Spree
62
62
  order.shipped_shipments.collect{|s| s.inventory_units.to_a}.flatten
63
63
  end
64
64
 
65
- # Used when Adjustment#update! wants to update the related adjustmenrt
65
+ # Used when Adjustment#update! wants to update the related adjustment
66
66
  def compute_amount(*args)
67
67
  amount.abs * -1
68
68
  end
@@ -287,6 +287,36 @@ module Spree
287
287
  end
288
288
  end
289
289
 
290
+ # Update Shipment and make sure Order states follow the shipment changes
291
+ def update_attributes_and_order(params = {})
292
+ if self.update_attributes params
293
+ if params.has_key? :selected_shipping_rate_id
294
+ # Changing the selected Shipping Rate won't update the cost (for now)
295
+ # so we persist the Shipment#cost before calculating order shipment
296
+ # total and updating payment state (given a change in shipment cost
297
+ # might change the Order#payment_state)
298
+ self.update_amounts
299
+
300
+ order.updater.update_shipment_total
301
+ order.updater.update_payment_state
302
+
303
+ # Update shipment state only after order total is updated because it
304
+ # (via Order#paid?) affects the shipment state (YAY)
305
+ self.update_columns(
306
+ state: determine_state(order),
307
+ updated_at: Time.now
308
+ )
309
+
310
+ # And then it's time to update shipment states and finally persist
311
+ # order changes
312
+ order.updater.update_shipment_state
313
+ order.updater.persist_totals
314
+ end
315
+
316
+ true
317
+ end
318
+ end
319
+
290
320
  private
291
321
 
292
322
  def manifest_unstock(item)
@@ -23,10 +23,10 @@ module Spree
23
23
  if tax_rate.included_in_price?
24
24
  if tax_amount > 0
25
25
  amount = "#{display_tax_amount(tax_amount)} #{tax_rate.name}"
26
- price += " (incl. #{amount})"
26
+ price += " (#{Spree.t(:incl)} #{amount})"
27
27
  else
28
28
  amount = "#{display_tax_amount(tax_amount*-1)} #{tax_rate.name}"
29
- price += " (excl. #{amount})"
29
+ price += " (#{Spree.t(:excl)} #{amount})"
30
30
  end
31
31
  else
32
32
  amount = "#{display_tax_amount(tax_amount)} #{tax_rate.name}"
@@ -2,13 +2,9 @@ module Spree
2
2
  module Stock
3
3
  class AvailabilityValidator < ActiveModel::Validator
4
4
  def validate(line_item)
5
- if shipment = line_item.target_shipment
6
- units = shipment.inventory_units_for(line_item.variant)
7
- return if units.count > line_item.quantity
8
- quantity = line_item.quantity - units.count
9
- else
10
- quantity = line_item.quantity
11
- end
5
+ unit_count = line_item.inventory_units.size
6
+ return if unit_count >= line_item.quantity
7
+ quantity = line_item.quantity - unit_count
12
8
 
13
9
  quantifier = Stock::Quantifier.new(line_item.variant)
14
10
 
@@ -98,6 +98,7 @@ module Spree
98
98
 
99
99
  def to_shipment
100
100
  shipment = Spree::Shipment.new
101
+ shipment.address = order.ship_address
101
102
  shipment.order = order
102
103
  shipment.stock_location = stock_location
103
104
  shipment.shipping_rates = shipping_rates
@@ -8,10 +8,11 @@ module Spree
8
8
 
9
9
  validates_presence_of :stock_location, :variant
10
10
  validates_uniqueness_of :variant_id, scope: [:stock_location_id, :deleted_at]
11
+ validates :count_on_hand, numericality: { greater_than_or_equal_to: 0 }, if: :verify_count_on_hand?
11
12
 
12
13
  delegate :weight, :should_track_inventory?, to: :variant
13
14
 
14
- after_save :conditional_variant_touch
15
+ after_save :conditional_variant_touch, if: :changed?
15
16
  after_touch { variant.touch }
16
17
 
17
18
  def backordered_inventory_units
@@ -52,6 +53,10 @@ module Spree
52
53
  end
53
54
 
54
55
  private
56
+ def verify_count_on_hand?
57
+ count_on_hand_changed? && !backorderable? && (count_on_hand < count_on_hand_was) && (count_on_hand < 0)
58
+ end
59
+
55
60
  def count_on_hand=(value)
56
61
  write_attribute(:count_on_hand, value)
57
62
  end
@@ -13,6 +13,10 @@ module Spree
13
13
 
14
14
  after_create :create_stock_items, :if => "self.propagate_all_variants?"
15
15
 
16
+ def state_text
17
+ state.try(:abbr) || state.try(:name) || state_name
18
+ end
19
+
16
20
  # Wrapper for creating a new stock item respecting the backorderable config
17
21
  def propagate_variant(variant)
18
22
  self.stock_items.create!(variant: variant, backorderable: self.backorderable_default)
@@ -12,15 +12,18 @@ module Spree
12
12
  class TaxRate < ActiveRecord::Base
13
13
  acts_as_paranoid
14
14
  include Spree::Core::CalculatedAdjustments
15
+ include Spree::Core::AdjustmentSource
15
16
  belongs_to :zone, class_name: "Spree::Zone"
16
17
  belongs_to :tax_category, class_name: "Spree::TaxCategory"
17
18
 
18
- has_many :adjustments, as: :source, dependent: :destroy
19
+ has_many :adjustments, as: :source
19
20
 
20
21
  validates :amount, presence: true, numericality: true
21
22
  validates :tax_category_id, presence: true
22
23
  validates_with DefaultTaxZoneValidator
23
24
 
25
+ before_destroy :deals_with_adjustments_for_deleted_source
26
+
24
27
  scope :by_zone, ->(zone) { where(zone_id: zone) }
25
28
 
26
29
  # Gets the array of TaxRates appropriate for the specified order
@@ -72,7 +75,7 @@ module Spree
72
75
  def self.adjust(order, items)
73
76
  rates = self.match(order)
74
77
  tax_categories = rates.map(&:tax_category)
75
- relevant_items = items.select { |item| tax_categories.include?(item.tax_category) }
78
+ relevant_items, non_relevant_items = items.partition { |item| tax_categories.include?(item.tax_category) }
76
79
  relevant_items.each do |item|
77
80
  item.adjustments.tax.delete_all
78
81
  relevant_rates = rates.select { |rate| rate.tax_category == item.tax_category }
@@ -81,6 +84,13 @@ module Spree
81
84
  rate.adjust(order, item)
82
85
  end
83
86
  end
87
+ non_relevant_items.each do |item|
88
+ if item.adjustments.tax.present?
89
+ item.adjustments.tax.delete_all
90
+ item.update_column(:pre_tax_amount, nil)
91
+ Spree::ItemAdjustments.new(item).update
92
+ end
93
+ end
84
94
  end
85
95
 
86
96
  # For Vat the default rate is the rate that is configured for the default category
@@ -191,6 +201,9 @@ module Spree
191
201
  label = ""
192
202
  label << (name.present? ? name : tax_category.name) + " "
193
203
  label << (show_rate_in_label? ? "#{amount * 100}%" : "")
204
+ label << " (#{Spree.t(:included_in_price)})" if included_in_price?
205
+ label
194
206
  end
207
+
195
208
  end
196
209
  end
@@ -202,8 +202,12 @@ module Spree
202
202
  end
203
203
  end
204
204
 
205
+ def default_price_changed?
206
+ default_price && (default_price.changed? || default_price.new_record?)
207
+ end
208
+
205
209
  def save_default_price
206
- default_price.save if default_price && (default_price.changed? || default_price.new_record?)
210
+ default_price.save if default_price_changed?
207
211
  end
208
212
 
209
213
  def set_cost_currency
@@ -232,6 +232,7 @@ en:
232
232
  attributes:
233
233
  base:
234
234
  card_expired: "Card has expired"
235
+ expiry_invalid: "Card expiration is invalid"
235
236
  spree/line_item:
236
237
  attributes:
237
238
  currency:
@@ -404,6 +405,7 @@ en:
404
405
  back_to_prototypes_list: Back To Prototypes List
405
406
  back_to_reports_list: Back To Reports List
406
407
  back_to_shipping_categories: Back To Shipping Categories
408
+ back_to_shipping_categories_list: Back To Shipping Categories List
407
409
  back_to_shipping_methods_list: Back To Shipping Methods List
408
410
  back_to_states_list: Back To States List
409
411
  back_to_stock_locations_list: Back to Stock Locations List
@@ -449,6 +451,7 @@ en:
449
451
  choose_dashboard_locale: Choose Dashboard Locale
450
452
  choose_location: Choose location
451
453
  city: City
454
+ click_and_drag_on_the_products_to_sort_them: 'Click &amp; drag on the products to sort them.'
452
455
  clone: Clone
453
456
  close: Close
454
457
  close_all_adjustments: Close All Adjustments
@@ -588,6 +591,7 @@ en:
588
591
  signup: User signup
589
592
  exceptions:
590
593
  count_on_hand_setter: Cannot set count_on_hand manually, as it is set automatically by the recalculate_count_on_hand callback. Please use `update_column(:count_on_hand, value)` instead.
594
+ excl: excl.
591
595
  expiration: Expiration
592
596
  extension: Extension
593
597
  existing_shipments: Existing shipments
@@ -637,10 +641,12 @@ en:
637
641
  image: Image
638
642
  images: Images
639
643
  inactive: Inactive
644
+ incl: incl.
640
645
  included_in_price: Included in Price
641
646
  included_price_validation: cannot be selected unless you have set a Default Tax Zone
642
647
  instructions_to_reset_password: Please enter your email on the form below
643
648
  insufficient_stock: Insufficient stock available, only %{on_hand} remaining
649
+ insufficient_stock_lines_present: Some line items in this order have insufficient quantity.
644
650
  intercept_email_address: Intercept Email Address
645
651
  intercept_email_instructions: Override email recipient and replace with this address.
646
652
  internal_name: Internal Name
@@ -226,6 +226,7 @@ Spree::Country.create!([
226
226
  { name: "New Zealand", iso3: "NZL", iso: "NZ", iso_name: "NEW ZEALAND", numcode: "554" },
227
227
  { name: "Saint Kitts and Nevis", iso3: "KNA", iso: "KN", iso_name: "SAINT KITTS AND NEVIS", numcode: "659", states_required: true },
228
228
  { name: "Serbia", iso3: "SRB", iso: "RS", "iso_name" => "SERBIA", numcode: "999" },
229
- { name: "Montenegro", iso3: "MNE", iso: "ME", iso_name: "MONTENEGRO", numcode: "499" }
229
+ { name: "Montenegro", iso3: "MNE", iso: "ME", iso_name: "MONTENEGRO", numcode: "499" },
230
+ { name: "Jersey", iso3: "JEY", iso: "JE", iso_name: "JERSEY", numcode: "44" }
230
231
  ])
231
232
  Spree::Config[:default_country_id] = Spree::Country.find_by(name: "United States").id
@@ -7,11 +7,8 @@ class RenameAdjustmentFields < ActiveRecord::Migration
7
7
 
8
8
  # This enables the Spree::Order#all_adjustments association to work correctly
9
9
  Spree::Adjustment.reset_column_information
10
- Spree::Adjustment.find_each do |adjustment|
11
- if adjustment.adjustable.is_a?(Spree::Order)
12
- adjustment.order = adjustment.adjustable
13
- adjustment.save
14
- end
10
+ Spree::Adjustment.where(adjustable_type: "Spree::Order").find_each do |adjustment|
11
+ adjustment.update_column(:order_id, adjustment.adjustable_id)
15
12
  end
16
13
  end
17
14
  end
@@ -0,0 +1,10 @@
1
+ class AddDefaultToShipmentCost < ActiveRecord::Migration
2
+ def up
3
+ change_column :spree_shipments, :cost, :decimal, precision: 10, scale: 2, default: 0.0
4
+ Spree::Shipment.where(cost: nil).update_all(cost: 0)
5
+ end
6
+
7
+ def down
8
+ change_column :spree_shipments, :cost, :decimal, precision: 10, scale: 2
9
+ end
10
+ end
@@ -13,6 +13,7 @@ module Spree
13
13
  class_option :admin_email, :type => :string
14
14
  class_option :admin_password, :type => :string
15
15
  class_option :lib_name, :type => :string, :default => 'spree'
16
+ class_option :enforce_available_locales, :type => :boolean, :default => nil
16
17
 
17
18
  def self.source_paths
18
19
  paths = self.superclass.source_paths
@@ -104,6 +105,13 @@ Disallow: /account
104
105
  end
105
106
  end
106
107
  APP
108
+
109
+ if !options[:enforce_available_locales].nil?
110
+ application <<-APP
111
+ # Prevent this deprecation message: https://github.com/svenfuchs/i18n/commit/3b6e56e
112
+ I18n.enforce_available_locales = #{options[:enforce_available_locales]}
113
+ APP
114
+ end
107
115
  end
108
116
 
109
117
  def include_seed_data
data/lib/spree/core.rb CHANGED
@@ -69,6 +69,7 @@ require 'spree/core/delegate_belongs_to'
69
69
  require 'spree/core/permalinks'
70
70
  require 'spree/core/token_resource'
71
71
  require 'spree/core/calculated_adjustments'
72
+ require 'spree/core/adjustment_source'
72
73
  require 'spree/core/product_duplicator'
73
74
  require 'spree/core/controller_helpers'
74
75
  require 'spree/core/controller_helpers/strong_parameters'
@@ -0,0 +1,26 @@
1
+ module Spree
2
+ module Core
3
+ module AdjustmentSource
4
+ def self.included(klass)
5
+ klass.class_eval do
6
+ def deals_with_adjustments_for_deleted_source
7
+ adjustment_scope = self.adjustments.includes(:order).references(:spree_orders)
8
+
9
+ # For incomplete orders, remove the adjustment completely.
10
+ adjustment_scope.where("spree_orders.completed_at IS NULL").destroy_all
11
+
12
+ # For complete orders, the source will be invalid.
13
+ # Therefore we nullify the source_id, leaving the adjustment in place.
14
+ # This would mean that the order's total is not altered at all.
15
+ adjustment_scope.where("spree_orders.completed_at IS NOT NULL").each do |adjustment|
16
+ adjustment.update_columns(
17
+ source_id: nil,
18
+ updated_at: Time.now,
19
+ )
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -13,7 +13,7 @@ module Spree
13
13
 
14
14
  # Used in the link_to_cart helper.
15
15
  def simple_current_order
16
- @order ||= Spree::Order.find_by(id: session[:order_id], currency: current_currency)
16
+ @simple_current_order ||= Spree::Order.find_by(id: session[:order_id], currency: current_currency)
17
17
  end
18
18
 
19
19
  # The current incomplete order from the session for use in cart and during checkout
@@ -18,8 +18,7 @@ module Spree
18
18
  create_adjustments_from_params(params.delete(:adjustments_attributes), order)
19
19
  create_payments_from_params(params.delete(:payments_attributes), order)
20
20
 
21
-
22
- if(completed_at = params.delete(:completed_at))
21
+ if completed_at = params.delete(:completed_at)
23
22
  order.completed_at = completed_at
24
23
  order.state = 'complete'
25
24
  end
@@ -30,9 +29,9 @@ module Spree
30
29
  end
31
30
 
32
31
  order.update_attributes!(params)
33
- # Really ensure that the order totals are correct
34
- order.update_totals
35
- order.persist_totals
32
+
33
+ # Really ensure that the order totals & states are correct
34
+ order.updater.update
36
35
  order.reload
37
36
  rescue Exception => e
38
37
  order.destroy if order && order.persisted?
@@ -45,9 +44,14 @@ module Spree
45
44
  shipments_hash.each do |s|
46
45
  begin
47
46
  shipment = order.shipments.build
48
- shipment.tracking = s[:tracking]
47
+ shipment.tracking = s[:tracking]
49
48
  shipment.stock_location = Spree::StockLocation.find_by_name!(s[:stock_location])
50
49
 
50
+ if s[:shipped_at].present?
51
+ shipment.shipped_at = s[:shipped_at]
52
+ shipment.state = 'shipped'
53
+ end
54
+
51
55
  inventory_units = s[:inventory_units] || []
52
56
  inventory_units.each do |iu|
53
57
  ensure_variant_id_from_params(iu)
@@ -63,6 +67,7 @@ module Spree
63
67
  rate = shipment.shipping_rates.create!(:shipping_method => shipping_method,
64
68
  :cost => s[:cost])
65
69
  shipment.selected_shipping_rate_id = rate.id
70
+ shipment.update_amounts
66
71
 
67
72
  rescue Exception => e
68
73
  raise "Order import shipments: #{e.message} #{s}"
@@ -104,9 +109,11 @@ module Spree
104
109
  return [] unless payments_hash
105
110
  payments_hash.each do |p|
106
111
  begin
107
- payment = order.payments.build
112
+ payment = order.payments.build order: order
108
113
  payment.amount = p[:amount].to_f
109
- payment.state = p.fetch(:state, 'completed')
114
+ # Order API should be using state as that's the normal payment field.
115
+ # spree_wombat serializes payment state as status so imported orders should fall back to status field.
116
+ payment.state = p[:state] || p[:status] || 'completed'
110
117
  payment.payment_method = Spree::PaymentMethod.find_by_name!(p[:payment_method])
111
118
  payment.save!
112
119
  rescue Exception => e
@@ -1,5 +1,5 @@
1
1
  module Spree
2
2
  def self.version
3
- "2.2.4"
3
+ "2.2.5"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spree_core
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.4
4
+ version: 2.2.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sean Schofield
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-07-01 00:00:00.000000000 Z
11
+ date: 2014-08-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemerchant
@@ -108,6 +108,20 @@ dependencies:
108
108
  - - "~>"
109
109
  - !ruby/object:Gem::Version
110
110
  version: '1.16'
111
+ - !ruby/object:Gem::Dependency
112
+ name: font-awesome-rails
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '4.0'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '4.0'
111
125
  - !ruby/object:Gem::Dependency
112
126
  name: friendly_id
113
127
  requirement: !ruby/object:Gem::Requirement
@@ -150,6 +164,20 @@ dependencies:
150
164
  - - "~>"
151
165
  - !ruby/object:Gem::Version
152
166
  version: '0.11'
167
+ - !ruby/object:Gem::Dependency
168
+ name: i18n
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - '='
172
+ - !ruby/object:Gem::Version
173
+ version: 0.6.9
174
+ type: :runtime
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - '='
179
+ - !ruby/object:Gem::Version
180
+ version: 0.6.9
153
181
  - !ruby/object:Gem::Dependency
154
182
  name: json
155
183
  requirement: !ruby/object:Gem::Requirement
@@ -226,14 +254,14 @@ dependencies:
226
254
  requirements:
227
255
  - - "~>"
228
256
  - !ruby/object:Gem::Version
229
- version: 4.0.6
257
+ version: 4.0.8
230
258
  type: :runtime
231
259
  prerelease: false
232
260
  version_requirements: !ruby/object:Gem::Requirement
233
261
  requirements:
234
262
  - - "~>"
235
263
  - !ruby/object:Gem::Version
236
- version: 4.0.6
264
+ version: 4.0.8
237
265
  - !ruby/object:Gem::Dependency
238
266
  name: ransack
239
267
  requirement: !ruby/object:Gem::Requirement
@@ -290,20 +318,6 @@ dependencies:
290
318
  - - '='
291
319
  - !ruby/object:Gem::Version
292
320
  version: 0.9.2
293
- - !ruby/object:Gem::Dependency
294
- name: font-awesome-rails
295
- requirement: !ruby/object:Gem::Requirement
296
- requirements:
297
- - - "~>"
298
- - !ruby/object:Gem::Version
299
- version: '4.0'
300
- type: :runtime
301
- prerelease: false
302
- version_requirements: !ruby/object:Gem::Requirement
303
- requirements:
304
- - - "~>"
305
- - !ruby/object:Gem::Version
306
- version: '4.0'
307
321
  description: The bare bones necessary for Spree.
308
322
  email: sean@spreecommerce.com
309
323
  executables: []
@@ -593,6 +607,7 @@ files:
593
607
  - db/migrate/20140601011216_set_shipment_total_for_users_upgrading.rb
594
608
  - db/migrate/20140609201656_add_deleted_at_to_spree_promotion_actions.rb
595
609
  - db/migrate/20140616202624_remove_uncaptured_amount_from_spree_payments.rb
610
+ - db/migrate/20140804185157_add_default_to_shipment_cost.rb
596
611
  - db/seeds.rb
597
612
  - lib/generators/spree/custom_user/custom_user_generator.rb
598
613
  - lib/generators/spree/custom_user/templates/authentication_helpers.rb.tt
@@ -613,6 +628,7 @@ files:
613
628
  - lib/generators/spree/install/templates/vendor/assets/stylesheets/spree/backend/all.css
614
629
  - lib/generators/spree/install/templates/vendor/assets/stylesheets/spree/frontend/all.css
615
630
  - lib/spree/core.rb
631
+ - lib/spree/core/adjustment_source.rb
616
632
  - lib/spree/core/calculated_adjustments.rb
617
633
  - lib/spree/core/controller_helpers.rb
618
634
  - lib/spree/core/controller_helpers/auth.rb