spree_core 2.2.4 → 2.2.5

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.
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