spree_core 3.0.1 → 3.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/app/models/friendly_id/slug_decorator.rb +3 -0
  3. data/app/models/spree/adjustment.rb +1 -1
  4. data/app/models/spree/calculator.rb +5 -0
  5. data/app/models/spree/customer_return.rb +22 -16
  6. data/app/models/spree/gateway/bogus.rb +4 -0
  7. data/app/models/spree/line_item.rb +1 -1
  8. data/app/models/spree/order.rb +22 -48
  9. data/app/models/spree/order/checkout.rb +270 -255
  10. data/app/models/spree/order_contents.rb +64 -61
  11. data/app/models/spree/order_merger.rb +65 -0
  12. data/app/models/spree/payment.rb +5 -0
  13. data/app/models/spree/payment/processing.rb +2 -1
  14. data/app/models/spree/payment_method/check.rb +12 -2
  15. data/app/models/spree/product.rb +5 -0
  16. data/app/models/spree/promotion_handler/cart.rb +18 -14
  17. data/app/models/spree/shipment.rb +1 -1
  18. data/app/models/spree/stock/availability_validator.rb +10 -9
  19. data/app/models/spree/stock/content_item.rb +8 -0
  20. data/app/models/spree/stock/package.rb +8 -0
  21. data/app/models/spree/stock_item.rb +5 -1
  22. data/app/models/spree/stock_movement.rb +6 -1
  23. data/app/models/spree/variant.rb +18 -15
  24. data/app/models/spree/zone.rb +39 -29
  25. data/app/views/spree/order_mailer/cancel_email.html.erb +1 -1
  26. data/app/views/spree/order_mailer/cancel_email.text.erb +1 -1
  27. data/app/views/spree/order_mailer/confirm_email.html.erb +1 -1
  28. data/app/views/spree/order_mailer/confirm_email.text.erb +1 -1
  29. data/config/initializers/user_class_extensions.rb +4 -0
  30. data/config/locales/en.yml +4 -3
  31. data/db/default/spree/default_reimbursement_type.rb +1 -0
  32. data/db/migrate/20150515211137_fix_adjustment_order_id.rb +70 -0
  33. data/db/migrate/20150522181728_add_deleted_at_to_friendly_id_slugs.rb +6 -0
  34. data/db/migrate/20150609093816_increase_scale_on_pre_tax_amounts.rb +16 -0
  35. data/db/migrate/20150707204155_enable_acts_as_paranoid_on_calculators.rb +6 -0
  36. data/lib/spree/core/controller_helpers/auth.rb +1 -1
  37. data/lib/spree/core/engine.rb +7 -0
  38. data/lib/spree/core/validators/email.rb +7 -3
  39. data/lib/spree/core/version.rb +1 -1
  40. data/lib/spree/permitted_attributes.rb +2 -2
  41. data/lib/spree/testing_support/common_rake.rb +3 -8
  42. data/lib/spree/testing_support/factories.rb +1 -1
  43. data/lib/spree/testing_support/factories/order_factory.rb +11 -0
  44. data/lib/spree/testing_support/order_walkthrough.rb +1 -1
  45. metadata +11 -4
@@ -7,7 +7,9 @@ module Spree
7
7
  end
8
8
 
9
9
  def add(variant, quantity = 1, options = {})
10
+ timestamp = Time.now
10
11
  line_item = add_to_line_item(variant, quantity, options)
12
+ options[:line_item_created] = true if timestamp <= line_item.created_at
11
13
  after_add_or_remove(line_item, options)
12
14
  end
13
15
 
@@ -22,10 +24,10 @@ module Spree
22
24
  # Update totals, then check if the order is eligible for any cart promotions.
23
25
  # If we do not update first, then the item total will be wrong and ItemTotal
24
26
  # promotion rules would not be triggered.
25
- reload_totals
27
+ persist_totals
26
28
  PromotionHandler::Cart.new(order).activate
27
29
  order.ensure_updated_shipments
28
- reload_totals
30
+ persist_totals
29
31
  true
30
32
  else
31
33
  false
@@ -33,80 +35,81 @@ module Spree
33
35
  end
34
36
 
35
37
  private
36
- def after_add_or_remove(line_item, options = {})
37
- reload_totals
38
- shipment = options[:shipment]
39
- shipment.present? ? shipment.update_amounts : order.ensure_updated_shipments
40
- PromotionHandler::Cart.new(order, line_item).activate
41
- Adjustable::AdjustmentsUpdater.update(line_item)
42
- reload_totals
43
- line_item
44
- end
45
38
 
46
- def filter_order_items(params)
47
- filtered_params = params.symbolize_keys
48
- return filtered_params if filtered_params[:line_items_attributes].nil? || filtered_params[:line_items_attributes][:id]
39
+ def after_add_or_remove(line_item, options = {})
40
+ persist_totals
41
+ shipment = options[:shipment]
42
+ shipment.present? ? shipment.update_amounts : order.ensure_updated_shipments
43
+ PromotionHandler::Cart.new(order, line_item).activate
44
+ Adjustable::AdjustmentsUpdater.update(line_item)
45
+ TaxRate.adjust(order, [line_item]) if options[:line_item_created]
46
+ persist_totals
47
+ line_item
48
+ end
49
+
50
+ def filter_order_items(params)
51
+ filtered_params = params.symbolize_keys
52
+ return filtered_params if filtered_params[:line_items_attributes].nil? || filtered_params[:line_items_attributes][:id]
49
53
 
50
- line_item_ids = order.line_items.pluck(:id)
54
+ line_item_ids = order.line_items.pluck(:id)
51
55
 
52
- params[:line_items_attributes].each_pair do |id, value|
53
- unless line_item_ids.include?(value[:id].to_i) || value[:variant_id].present?
54
- filtered_params[:line_items_attributes].delete(id)
55
- end
56
+ params[:line_items_attributes].each_pair do |id, value|
57
+ unless line_item_ids.include?(value[:id].to_i) || value[:variant_id].present?
58
+ filtered_params[:line_items_attributes].delete(id)
56
59
  end
57
- filtered_params
58
60
  end
61
+ filtered_params
62
+ end
59
63
 
60
- def order_updater
61
- @updater ||= OrderUpdater.new(order)
62
- end
64
+ def order_updater
65
+ @updater ||= OrderUpdater.new(order)
66
+ end
63
67
 
64
- def reload_totals
65
- order_updater.update_item_count
66
- order_updater.update
67
- order.reload
68
- end
68
+ def persist_totals
69
+ order_updater.update_item_count
70
+ order_updater.update
71
+ end
69
72
 
70
- def add_to_line_item(variant, quantity, options = {})
71
- line_item = grab_line_item_by_variant(variant, false, options)
72
-
73
- if line_item
74
- line_item.quantity += quantity.to_i
75
- line_item.currency = currency unless currency.nil?
76
- else
77
- opts = { currency: order.currency }.merge ActionController::Parameters.new(options).
78
- permit(PermittedAttributes.line_item_attributes)
79
- line_item = order.line_items.new(quantity: quantity,
80
- variant: variant,
81
- options: opts)
82
- end
83
- line_item.target_shipment = options[:shipment] if options.has_key? :shipment
84
- line_item.save!
85
- line_item
86
- end
73
+ def add_to_line_item(variant, quantity, options = {})
74
+ line_item = grab_line_item_by_variant(variant, false, options)
87
75
 
88
- def remove_from_line_item(variant, quantity, options = {})
89
- line_item = grab_line_item_by_variant(variant, true, options)
90
- line_item.quantity -= quantity
91
- line_item.target_shipment= options[:shipment]
76
+ if line_item
77
+ line_item.quantity += quantity.to_i
78
+ line_item.currency = currency unless currency.nil?
79
+ else
80
+ opts = { currency: order.currency }.merge ActionController::Parameters.new(options).
81
+ permit(PermittedAttributes.line_item_attributes)
82
+ line_item = order.line_items.new(quantity: quantity,
83
+ variant: variant,
84
+ options: opts)
85
+ end
86
+ line_item.target_shipment = options[:shipment] if options.has_key? :shipment
87
+ line_item.save!
88
+ line_item
89
+ end
92
90
 
93
- if line_item.quantity == 0
94
- line_item.destroy
95
- else
96
- line_item.save!
97
- end
91
+ def remove_from_line_item(variant, quantity, options = {})
92
+ line_item = grab_line_item_by_variant(variant, true, options)
93
+ line_item.quantity -= quantity
94
+ line_item.target_shipment= options[:shipment]
98
95
 
99
- line_item
96
+ if line_item.quantity.zero?
97
+ order.line_items.destroy(line_item)
98
+ else
99
+ line_item.save!
100
100
  end
101
101
 
102
- def grab_line_item_by_variant(variant, raise_error = false, options = {})
103
- line_item = order.find_line_item_by_variant(variant, options)
102
+ line_item
103
+ end
104
104
 
105
- if !line_item.present? && raise_error
106
- raise ActiveRecord::RecordNotFound, "Line item not found for variant #{variant.sku}"
107
- end
105
+ def grab_line_item_by_variant(variant, raise_error = false, options = {})
106
+ line_item = order.find_line_item_by_variant(variant, options)
108
107
 
109
- line_item
108
+ if !line_item.present? && raise_error
109
+ raise ActiveRecord::RecordNotFound, "Line item not found for variant #{variant.sku}"
110
110
  end
111
+
112
+ line_item
113
+ end
111
114
  end
112
115
  end
@@ -0,0 +1,65 @@
1
+ module Spree
2
+ class OrderMerger
3
+ attr_accessor :order
4
+ delegate :updater, to: :order
5
+
6
+ def initialize(order)
7
+ @order = order
8
+ end
9
+
10
+ def merge!(other_order, user = nil)
11
+ other_order.line_items.each do |other_order_line_item|
12
+ next unless other_order_line_item.currency == order.currency
13
+
14
+ current_line_item = find_matching_line_item(other_order_line_item)
15
+ handle_merge(current_line_item, other_order_line_item)
16
+ end
17
+
18
+ set_user(user)
19
+ persist_merge
20
+
21
+ # So that the destroy doesn't take out line items which may have been re-assigned
22
+ other_order.line_items.reload
23
+ other_order.destroy
24
+ end
25
+
26
+ # Compare the line item of the other order with mine.
27
+ # Make sure you allow any extensions to chime in on whether or
28
+ # not the extension-specific parts of the line item match
29
+ def find_matching_line_item(other_order_line_item)
30
+ order.line_items.detect do |my_li|
31
+ my_li.variant == other_order_line_item.variant &&
32
+ order.line_item_comparison_hooks.all? do |hook|
33
+ order.send(hook, my_li, other_order_line_item.serializable_hash)
34
+ end
35
+ end
36
+ end
37
+
38
+ def set_user(user = nil)
39
+ order.associate_user!(user) if !order.user && !user.blank?
40
+ end
41
+
42
+ # The idea is the end developer can choose to override the merge
43
+ # to their own choosing. Default is merge with errors.
44
+ def handle_merge(current_line_item, other_order_line_item)
45
+ if current_line_item
46
+ current_line_item.quantity += other_order_line_item.quantity
47
+ handle_error(current_line_item) unless current_line_item.save
48
+ else
49
+ other_order_line_item.order_id = order.id
50
+ handle_error(other_order_line_item) unless other_order_line_item.save
51
+ end
52
+ end
53
+
54
+ # Change the error messages as you choose.
55
+ def handle_error(line_item)
56
+ order.errors[:base] << line_item.errors.full_messages
57
+ end
58
+
59
+ def persist_merge
60
+ updater.update_item_count
61
+ updater.update_item_total
62
+ updater.persist_totals
63
+ end
64
+ end
65
+ end
@@ -26,6 +26,7 @@ module Spree
26
26
  has_many :capture_events, class_name: 'Spree::PaymentCaptureEvent'
27
27
  has_many :refunds, inverse_of: :payment
28
28
 
29
+ validates_presence_of :payment_method
29
30
  before_validation :validate_source
30
31
 
31
32
  after_save :create_payment_profile, if: :profiles_supported?
@@ -173,6 +174,10 @@ module Spree
173
174
  amount - captured_amount
174
175
  end
175
176
 
177
+ def editable?
178
+ checkout? || pending?
179
+ end
180
+
176
181
  private
177
182
 
178
183
  def validate_source
@@ -69,7 +69,8 @@ module Spree
69
69
  end
70
70
 
71
71
  def cancel!
72
- payment_method.cancel(response_code)
72
+ response = payment_method.cancel(response_code)
73
+ handle_response(response, :void, :failure)
73
74
  end
74
75
 
75
76
  def gateway_options
@@ -15,17 +15,27 @@ module Spree
15
15
  end
16
16
 
17
17
  def capture(*args)
18
- ActiveMerchant::Billing::Response.new(true, "", {}, {})
18
+ simulated_successful_billing_response
19
19
  end
20
20
 
21
21
  def cancel(response); end
22
22
 
23
23
  def void(*args)
24
- ActiveMerchant::Billing::Response.new(true, "", {}, {})
24
+ simulated_successful_billing_response
25
25
  end
26
26
 
27
27
  def source_required?
28
28
  false
29
29
  end
30
+
31
+ def credit(*args)
32
+ simulated_successful_billing_response
33
+ end
34
+
35
+ private
36
+
37
+ def simulated_successful_billing_response
38
+ ActiveMerchant::Billing::Response.new(true, "", {}, {})
39
+ end
30
40
  end
31
41
  end
@@ -74,6 +74,7 @@ module Spree
74
74
  after_create :build_variants_from_option_values_hash, if: :option_values_hash
75
75
 
76
76
  after_destroy :punch_slug
77
+ after_restore :update_slug_history
77
78
 
78
79
  after_initialize :ensure_master
79
80
 
@@ -267,6 +268,10 @@ module Spree
267
268
  update_column :slug, "#{Time.now.to_i}_#{slug}"[0..254] unless frozen?
268
269
  end
269
270
 
271
+ def update_slug_history
272
+ self.save!
273
+ end
274
+
270
275
  def anything_changed?
271
276
  changed? || @nested_changes
272
277
  end
@@ -29,22 +29,26 @@ module Spree
29
29
  end
30
30
 
31
31
  private
32
- def promotions
33
- promo_table = Promotion.arel_table
34
- join_table = Arel::Table.new(:spree_orders_promotions)
35
32
 
36
- join_condition = promo_table.join(join_table, Arel::Nodes::OuterJoin).on(
37
- promo_table[:id].eq(join_table[:promotion_id])
38
- ).join_sources
33
+ def promotions
34
+ # AR cannot bind raw ASTs to prepared statements. There always must be a manager around.
35
+ # Also Postgresql requires an aliased table for `SELECT * FROM (subexpression) AS alias`.
36
+ # And Sqlite3 cannot work on outher parenthesis from `(left UNION right)`.
37
+ # So this construct makes both happy.
38
+ select = Arel::SelectManager.new(
39
+ Promotion,
40
+ Promotion.arel_table.create_table_alias(
41
+ order.promotions.active.union(Promotion.active.where(code: nil, path: nil)),
42
+ Promotion.table_name
43
+ ),
44
+ )
45
+ select.project(Arel.star)
39
46
 
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
47
- end
47
+ Promotion.find_by_sql(
48
+ select,
49
+ order.promotions.bind_values
50
+ )
51
+ end
48
52
  end
49
53
  end
50
54
  end
@@ -157,7 +157,7 @@ module Spree
157
157
  end
158
158
 
159
159
  def item_cost
160
- line_items.map(&:final_amount).sum
160
+ manifest.map { |m| (m.line_item.price + (m.line_item.adjustment_total / m.line_item.quantity)) * m.quantity }.sum
161
161
  end
162
162
 
163
163
  def line_items
@@ -5,19 +5,20 @@ module Spree
5
5
  unit_count = line_item.inventory_units.size
6
6
  return if unit_count >= line_item.quantity
7
7
  quantity = line_item.quantity - unit_count
8
+ return if quantity.zero?
8
9
 
9
10
  quantifier = Stock::Quantifier.new(line_item.variant)
10
11
 
11
- unless quantifier.can_supply? quantity
12
- variant = line_item.variant
13
- display_name = %Q{#{variant.name}}
14
- display_name += %Q{ (#{variant.options_text})} unless variant.options_text.blank?
12
+ return if quantifier.can_supply?(quantity)
15
13
 
16
- line_item.errors[:quantity] << Spree.t(
17
- :selected_quantity_not_available,
18
- item: display_name.inspect
19
- )
20
- end
14
+ variant = line_item.variant
15
+ display_name = "#{variant.name}"
16
+ display_name += " (#{variant.options_text})" unless variant.options_text.blank?
17
+
18
+ line_item.errors[:quantity] << Spree.t(
19
+ :selected_quantity_not_available,
20
+ item: display_name.inspect
21
+ )
21
22
  end
22
23
  end
23
24
  end
@@ -43,6 +43,14 @@ module Spree
43
43
  # but this massively simplifies things for now
44
44
  1
45
45
  end
46
+
47
+ def volume
48
+ variant.volume * quantity
49
+ end
50
+
51
+ def dimension
52
+ variant.dimension * quantity
53
+ end
46
54
  end
47
55
  end
48
56
  end
@@ -91,6 +91,14 @@ module Spree
91
91
  def contents_by_weight
92
92
  contents.sort { |x, y| y.weight <=> x.weight }
93
93
  end
94
+
95
+ def volume
96
+ contents.sum(&:volume)
97
+ end
98
+
99
+ def dimension
100
+ contents.sum(&:dimension)
101
+ end
94
102
  end
95
103
  end
96
104
  end
@@ -8,7 +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
+ validates_numericality_of :count_on_hand,
13
+ greater_than_or_equal_to: 0,
14
+ less_than_or_equal_to: 2**31 - 1,
15
+ only_integer: true, if: :verify_count_on_hand?
12
16
 
13
17
  delegate :weight, :should_track_inventory?, to: :variant
14
18
 
@@ -6,7 +6,12 @@ module Spree
6
6
  after_create :update_stock_item_quantity
7
7
 
8
8
  validates :stock_item, presence: true
9
- validates :quantity, presence: true
9
+ validates :quantity, presence: true, numericality: {
10
+ greater_than_or_equal_to: -2**31,
11
+ less_than_or_equal_to: 2**31-1,
12
+ only_integer: true,
13
+ allow_nil: true
14
+ }
10
15
 
11
16
  scope :recent, -> { order('created_at DESC') }
12
17