solidus_core 1.3.2 → 1.4.0.beta1

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