spree_core 2.1.12 → 2.2.0

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 (172) hide show
  1. checksums.yaml +4 -4
  2. data/app/helpers/spree/base_helper.rb +4 -7
  3. data/app/helpers/spree/products_helper.rb +11 -9
  4. data/app/helpers/spree/store_helper.rb +5 -0
  5. data/app/models/spree/ability.rb +4 -0
  6. data/app/models/spree/address.rb +6 -6
  7. data/app/models/spree/adjustment.rb +40 -61
  8. data/app/models/spree/app_configuration.rb +1 -14
  9. data/app/models/spree/calculator.rb +12 -4
  10. data/app/models/spree/calculator/default_tax.rb +42 -38
  11. data/app/models/spree/calculator/flat_percent_item_total.rb +2 -4
  12. data/app/models/spree/calculator/free_shipping.rb +5 -2
  13. data/app/models/spree/calculator/percent_on_line_item.rb +15 -0
  14. data/app/models/spree/calculator/percent_per_item.rb +3 -0
  15. data/app/models/spree/classification.rb +3 -2
  16. data/app/models/spree/credit_card.rb +7 -25
  17. data/app/models/spree/gateway/bogus.rb +5 -5
  18. data/app/models/spree/gateway/bogus_simple.rb +0 -8
  19. data/app/models/spree/image.rb +0 -9
  20. data/app/models/spree/inventory_unit.rb +10 -4
  21. data/app/models/spree/item_adjustments.rb +65 -0
  22. data/app/models/spree/legacy_user.rb +1 -0
  23. data/app/models/spree/line_item.rb +33 -13
  24. data/app/models/spree/option_type.rb +2 -2
  25. data/app/models/spree/option_value.rb +1 -1
  26. data/app/models/spree/order.rb +109 -89
  27. data/app/models/spree/order/checkout.rb +48 -0
  28. data/app/models/spree/order_contents.rb +72 -37
  29. data/app/models/spree/order_inventory.rb +65 -68
  30. data/app/models/spree/order_populator.rb +3 -17
  31. data/app/models/spree/order_updater.rb +63 -44
  32. data/app/models/spree/payment.rb +20 -5
  33. data/app/models/spree/payment/processing.rb +19 -25
  34. data/app/models/spree/payment_capture_event.rb +9 -0
  35. data/app/models/spree/payment_method/check.rb +0 -2
  36. data/app/models/spree/price.rb +1 -1
  37. data/app/models/spree/product.rb +14 -16
  38. data/app/models/spree/product/scopes.rb +4 -6
  39. data/app/models/spree/product_option_type.rb +2 -2
  40. data/app/models/spree/product_property.rb +2 -2
  41. data/app/models/spree/promotion.rb +71 -50
  42. data/app/models/spree/promotion/actions/create_adjustment.rb +31 -32
  43. data/app/models/spree/promotion/actions/create_item_adjustments.rb +83 -0
  44. data/app/models/spree/promotion/actions/free_shipping.rb +36 -0
  45. data/app/models/spree/promotion/rules/first_order.rb +4 -0
  46. data/app/models/spree/promotion/rules/item_total.rb +5 -1
  47. data/app/models/spree/promotion/rules/product.rb +4 -0
  48. data/app/models/spree/promotion/rules/user.rb +5 -6
  49. data/app/models/spree/promotion/rules/user_logged_in.rb +4 -0
  50. data/app/models/spree/promotion_action.rb +1 -5
  51. data/app/models/spree/promotion_handler/cart.rb +38 -0
  52. data/app/models/spree/promotion_handler/coupon.rb +76 -0
  53. data/app/models/spree/promotion_handler/free_shipping.rb +31 -0
  54. data/app/models/spree/promotion_handler/page.rb +24 -0
  55. data/app/models/spree/promotion_rule.rb +15 -7
  56. data/app/models/spree/property.rb +1 -1
  57. data/app/models/spree/return_authorization.rb +7 -1
  58. data/app/models/spree/shipment.rb +113 -49
  59. data/app/models/spree/shipping_calculator.rb +4 -5
  60. data/app/models/spree/shipping_category.rb +2 -2
  61. data/app/models/spree/shipping_method.rb +12 -6
  62. data/app/models/spree/shipping_rate.rb +27 -7
  63. data/app/models/spree/stock/availability_validator.rb +1 -1
  64. data/app/models/spree/stock/estimator.rb +13 -1
  65. data/app/models/spree/stock/package.rb +11 -7
  66. data/app/models/spree/stock/packer.rb +3 -3
  67. data/app/models/spree/stock/quantifier.rb +9 -1
  68. data/app/models/spree/stock_item.rb +11 -6
  69. data/app/models/spree/stock_movement.rb +1 -2
  70. data/app/models/spree/tax_category.rb +6 -1
  71. data/app/models/spree/tax_rate.rb +57 -49
  72. data/app/models/spree/taxon.rb +10 -5
  73. data/app/models/spree/taxonomy.rb +5 -2
  74. data/app/models/spree/variant.rb +33 -16
  75. data/app/models/spree/zone.rb +24 -24
  76. data/app/views/spree/shared/_routes.html.erb +3 -0
  77. data/config/locales/en.yml +42 -26
  78. data/db/migrate/20130213191427_create_default_stock.rb +3 -3
  79. data/db/migrate/20130413230529_add_name_to_spree_credit_cards.rb +5 -0
  80. data/db/migrate/20130414000512_update_name_fields_on_spree_credit_cards.rb +13 -0
  81. data/db/migrate/20130417120035_update_adjustment_states.rb +2 -2
  82. data/db/migrate/20130417123427_add_shipping_rates_to_shipments.rb +1 -1
  83. data/db/migrate/20130509115210_add_number_to_stock_transfer.rb +1 -1
  84. data/db/migrate/20130611054351_rename_shipping_methods_zones_to_spree_shipping_methods_zones.rb +0 -5
  85. data/db/migrate/20130802022321_migrate_tax_categories_to_line_items.rb +7 -5
  86. data/db/migrate/20130807024301_upgrade_adjustments.rb +39 -0
  87. data/db/migrate/20130807024302_rename_adjustment_fields.rb +17 -0
  88. data/db/migrate/20130813004002_add_shipment_total_to_spree_orders.rb +5 -0
  89. data/db/migrate/20130813232134_rename_activators_to_promotions.rb +5 -0
  90. data/db/migrate/20130815000406_add_adjustment_total_to_line_items.rb +5 -0
  91. data/db/migrate/20130815024413_add_adjustment_total_to_shipments.rb +5 -0
  92. data/db/migrate/20130828234942_add_tax_total_to_line_items_shipments_and_orders.rb +8 -0
  93. data/db/migrate/20130830001159_migrate_old_shipping_calculators.rb +1 -1
  94. data/db/migrate/20130903183026_add_code_to_spree_promotion_rules.rb +5 -0
  95. data/db/migrate/20130917024658_remove_promotions_event_name_field.rb +5 -0
  96. data/db/migrate/20130924040529_add_promo_total_to_line_items_and_shipments_and_orders.rb +7 -0
  97. data/db/migrate/20131001013410_remove_unused_credit_card_fields.rb +7 -3
  98. data/db/migrate/20131107132123_add_tax_category_to_variants.rb +6 -0
  99. data/db/migrate/20131118043959_add_included_to_adjustments.rb +5 -0
  100. data/db/migrate/20131118050234_rename_tax_total_fields.rb +11 -0
  101. data/db/migrate/20131118183431_add_line_item_id_to_spree_inventory_units.rb +21 -0
  102. data/db/migrate/20131127001002_add_position_to_classifications.rb +5 -0
  103. data/db/migrate/20131211112807_create_spree_orders_promotions.rb +8 -0
  104. data/db/migrate/20131218054603_add_item_count_to_spree_orders.rb +5 -0
  105. data/db/migrate/20140106224208_rename_permalink_to_slug_for_products.rb +5 -0
  106. data/db/migrate/20140124023232_rename_activator_id_in_rules_and_actions_to_promotion_id.rb +6 -0
  107. data/db/migrate/20140203161722_add_approver_id_and_approved_at_to_orders.rb +6 -0
  108. data/db/migrate/20140204115338_add_confirmation_delivered_to_spree_orders.rb +5 -0
  109. data/db/migrate/20140205120320_create_spree_payment_capture_events.rb +12 -0
  110. data/db/migrate/20140205144710_add_uncaptured_amount_to_payments.rb +5 -0
  111. data/db/migrate/20140207085910_add_tax_category_id_to_shipping_methods.rb +5 -0
  112. data/db/migrate/20140207093021_add_tax_rate_id_to_shipping_rates.rb +5 -0
  113. data/db/migrate/20140211040159_add_pre_tax_amount_to_line_items_and_shipments.rb +6 -0
  114. data/db/migrate/20140213184916_add_more_indexes.rb +13 -0
  115. data/db/migrate/20140219060952_add_considered_risky_to_orders.rb +5 -0
  116. data/lib/generators/spree/dummy/dummy_generator.rb +1 -6
  117. data/lib/generators/spree/install/install_generator.rb +6 -6
  118. data/lib/generators/spree/install/templates/{app/assets/javascripts/admin → vendor/assets/javascripts/spree/backend}/all.js +3 -3
  119. data/lib/generators/spree/install/templates/{app/assets/javascripts/store → vendor/assets/javascripts/spree/frontend}/all.js +3 -3
  120. data/lib/generators/spree/install/templates/{app/assets/stylesheets/store → vendor/assets/stylesheets/spree/backend}/all.css +3 -3
  121. data/lib/generators/spree/install/templates/{app/assets/stylesheets/admin → vendor/assets/stylesheets/spree/frontend}/all.css +3 -3
  122. data/lib/spree/core.rb +21 -8
  123. data/lib/spree/core/calculated_adjustments.rb +0 -40
  124. data/lib/spree/core/controller_helpers.rb +5 -0
  125. data/lib/spree/core/controller_helpers/auth.rb +2 -2
  126. data/lib/spree/core/controller_helpers/common.rb +0 -5
  127. data/lib/spree/core/controller_helpers/order.rb +8 -9
  128. data/lib/spree/core/engine.rb +10 -17
  129. data/lib/spree/core/permalinks.rb +1 -1
  130. data/lib/spree/core/product_duplicator.rb +3 -8
  131. data/lib/spree/core/user_address.rb +1 -1
  132. data/lib/spree/core/validators/email.rb +23 -1
  133. data/lib/spree/core/version.rb +1 -1
  134. data/lib/spree/money.rb +1 -1
  135. data/lib/spree/permitted_attributes.rb +2 -2
  136. data/lib/spree/testing_support/caching.rb +47 -0
  137. data/lib/spree/testing_support/factories/adjustment_factory.rb +11 -2
  138. data/lib/spree/testing_support/factories/credit_card_factory.rb +2 -1
  139. data/lib/spree/testing_support/factories/order_factory.rb +10 -5
  140. data/lib/spree/testing_support/factories/payment_factory.rb +2 -2
  141. data/lib/spree/testing_support/factories/payment_method_factory.rb +3 -3
  142. data/lib/spree/testing_support/factories/promotion_factory.rb +16 -1
  143. data/lib/spree/testing_support/factories/shipment_factory.rb +8 -4
  144. data/lib/spree/testing_support/factories/shipping_method_factory.rb +1 -3
  145. data/lib/spree/testing_support/factories/stock_factory.rb +1 -1
  146. data/lib/spree/testing_support/factories/tax_rate_factory.rb +2 -2
  147. data/lib/spree/testing_support/order_walkthrough.rb +1 -1
  148. data/lib/tasks/core.rake +2 -2
  149. data/vendor/assets/fonts/FontAwesome.otf +0 -0
  150. data/vendor/assets/fonts/fontawesome-webfont.eot +0 -0
  151. data/vendor/assets/fonts/fontawesome-webfont.svg +399 -0
  152. data/vendor/assets/fonts/fontawesome-webfont.ttf +0 -0
  153. data/vendor/assets/fonts/fontawesome-webfont.woff +0 -0
  154. data/vendor/assets/stylesheets/font-awesome.scss +1475 -0
  155. metadata +73 -44
  156. data/app/assets/javascripts/admin/handlebar_extensions.js +0 -9
  157. data/app/helpers/spree/admin/adjustments_helper.rb +0 -26
  158. data/app/helpers/spree/admin/images_helper.rb +0 -18
  159. data/app/helpers/spree/promotion_rules_helper.rb +0 -13
  160. data/app/models/spree/activator.rb +0 -29
  161. data/app/models/spree/calculator/per_item.rb +0 -41
  162. data/app/models/spree/stock/remaining_packer.rb +0 -22
  163. data/app/views/spree/payments/_payment.html.erb +0 -18
  164. data/db/migrate/20131118041203_add_tax_total_to_spree_orders.rb +0 -5
  165. data/db/migrate/20131118043021_add_order_id_to_spree_adjustments.rb +0 -6
  166. data/db/migrate/20131118074808_add_included_to_spree_adjustments.rb +0 -5
  167. data/db/migrate/20140415041315_add_user_id_created_by_id_index_to_order.rb +0 -5
  168. data/lib/spree/core/gateway_error.rb +0 -5
  169. data/lib/spree/core/preference_rescue.rb +0 -25
  170. data/lib/spree/core/s3_support.rb +0 -25
  171. data/lib/spree/promo/coupon_applicator.rb +0 -71
  172. data/lib/spree/testing_support/factories/activator_factory.rb +0 -8
@@ -1,3 +1,7 @@
1
+ # TODO: Deprecate this class.
2
+ # This calculator will be removed in future versions of Spree.
3
+ # The only case where it was used was for Free Shipping Promotions.
4
+ # There is now a Promotion Action which deals with these types of promotions instead.
1
5
  module Spree
2
6
  class Calculator::FreeShipping < Calculator
3
7
  def self.description
@@ -15,5 +19,4 @@ module Spree
15
19
  order.ship_total
16
20
  end
17
21
  end
18
- end
19
-
22
+ end
@@ -0,0 +1,15 @@
1
+ module Spree
2
+ class Calculator
3
+ class PercentOnLineItem < Calculator
4
+ preference :percent, :decimal, default: 0
5
+
6
+ def self.description
7
+ Spree.t(:percent_per_item)
8
+ end
9
+
10
+ def compute(object)
11
+ ((object.price * object.quantity) * preferred_percent) / 100
12
+ end
13
+ end
14
+ end
15
+ end
@@ -4,6 +4,9 @@ module Spree
4
4
  # for all matching products in an order. This should not be used as a
5
5
  # shipping calculator since it would be the same thing as a flat percent
6
6
  # off the entire order.
7
+ #
8
+ #
9
+ # TODO Should be deprecated now that we have adjustments at the line item level in spree core
7
10
 
8
11
  class Calculator::PercentPerItem < Calculator
9
12
  preference :percent, :decimal, default: 0
@@ -1,8 +1,9 @@
1
1
  module Spree
2
2
  class Classification < ActiveRecord::Base
3
3
  self.table_name = 'spree_products_taxons'
4
- belongs_to :product, class_name: "Spree::Product"
5
- belongs_to :taxon, class_name: "Spree::Taxon"
4
+ acts_as_list
5
+ belongs_to :product, class_name: "Spree::Product", inverse_of: :classifications
6
+ belongs_to :taxon, class_name: "Spree::Taxon", inverse_of: :classifications
6
7
 
7
8
  # For #3494
8
9
  validates_uniqueness_of :taxon_id, :scope => :product_id, :message => :already_linked
@@ -8,6 +8,7 @@ module Spree
8
8
 
9
9
  validates :month, :year, numericality: { only_integer: true }, unless: :has_payment_profile?
10
10
  validates :number, presence: true, unless: :has_payment_profile?, on: :create
11
+ validates :name, presence: true
11
12
  validates :verification_value, presence: true, unless: :has_payment_profile?, on: :create
12
13
  validate :expiry_not_in_the_past
13
14
 
@@ -26,19 +27,12 @@ module Spree
26
27
  }
27
28
 
28
29
  def expiry=(expiry)
29
- return unless expiry.present?
30
-
31
- self[:month], self[:year] =
32
- if expiry.match(/\d\s?\/\s?\d/) # will match mm/yy and mm / yyyy
33
- expiry.delete(' ').split('/')
34
- elsif match = expiry.match(/(\d{2})(\d{2,4})/) # will match mmyy and mmyyyy
35
- [match[1], match[2]]
36
- end
37
- if self[:year]
30
+ if expiry.present?
31
+ self[:month], self[:year] = expiry.delete(' ').split('/')
38
32
  self[:year] = "20" + self[:year] if self[:year].length == 2
39
33
  self[:year] = self[:year].to_i
34
+ self[:month] = self[:month].to_i
40
35
  end
41
- self[:month] = self[:month].to_i
42
36
  end
43
37
 
44
38
  def number=(num)
@@ -68,14 +62,6 @@ module Spree
68
62
  CARD_TYPES.find{|type, pattern| return type.to_s if numbers =~ pattern}.to_s
69
63
  end
70
64
 
71
- def name?
72
- first_name? && last_name?
73
- end
74
-
75
- def name
76
- "#{first_name} #{last_name}"
77
- end
78
-
79
65
  def verification_value?
80
66
  verification_value.present?
81
67
  end
@@ -126,13 +112,9 @@ module Spree
126
112
 
127
113
  def expiry_not_in_the_past
128
114
  if year.present? && month.present?
129
- if month.to_i < 1 || month.to_i > 12
130
- errors.add(:base, :expiry_invalid)
131
- else
132
- current = Time.current
133
- if year.to_i < current.year or (year.to_i == current.year and month.to_i < current.month)
134
- errors.add(:base, :card_expired)
135
- end
115
+ time = "#{year}-#{month}-1".to_time
116
+ if time < Time.zone.now.to_time.beginning_of_month
117
+ errors.add(:base, :card_expired)
136
118
  end
137
119
  end
138
120
  end
@@ -26,7 +26,7 @@ module Spree
26
26
  def authorize(money, credit_card, options = {})
27
27
  profile_id = credit_card.gateway_customer_profile_id
28
28
  if VALID_CCS.include? credit_card.number or (profile_id and profile_id.starts_with? 'BGS-')
29
- ActiveMerchant::Billing::Response.new(true, 'Bogus Gateway: Forced success', {}, :test => true, :authorization => '12345', :avs_result => { :code => 'A' })
29
+ ActiveMerchant::Billing::Response.new(true, 'Bogus Gateway: Forced success', {}, :test => true, :authorization => '12345', :avs_result => { :code => 'D' })
30
30
  else
31
31
  ActiveMerchant::Billing::Response.new(false, 'Bogus Gateway: Forced failure', { :message => 'Bogus Gateway: Forced failure' }, :test => true)
32
32
  end
@@ -35,7 +35,7 @@ module Spree
35
35
  def purchase(money, credit_card, options = {})
36
36
  profile_id = credit_card.gateway_customer_profile_id
37
37
  if VALID_CCS.include? credit_card.number or (profile_id and profile_id.starts_with? 'BGS-')
38
- ActiveMerchant::Billing::Response.new(true, 'Bogus Gateway: Forced success', {}, :test => true, :authorization => '12345', :avs_result => { :code => 'A' })
38
+ ActiveMerchant::Billing::Response.new(true, 'Bogus Gateway: Forced success', {}, :test => true, :authorization => '12345', :avs_result => { :code => 'M' })
39
39
  else
40
40
  ActiveMerchant::Billing::Response.new(false, 'Bogus Gateway: Forced failure', :message => 'Bogus Gateway: Forced failure', :test => true)
41
41
  end
@@ -45,9 +45,9 @@ module Spree
45
45
  ActiveMerchant::Billing::Response.new(true, 'Bogus Gateway: Forced success', {}, :test => true, :authorization => '12345')
46
46
  end
47
47
 
48
- def capture(authorization, credit_card, gateway_options)
49
- if authorization.response_code == '12345'
50
- ActiveMerchant::Billing::Response.new(true, 'Bogus Gateway: Forced success', {}, :test => true, :authorization => '67890')
48
+ def capture(money, authorization, gateway_options)
49
+ if authorization == '12345'
50
+ ActiveMerchant::Billing::Response.new(true, 'Bogus Gateway: Forced success', {}, :test => true)
51
51
  else
52
52
  ActiveMerchant::Billing::Response.new(false, 'Bogus Gateway: Forced failure', :error => 'Bogus Gateway: Forced failure', :test => true)
53
53
  end
@@ -6,14 +6,6 @@ module Spree
6
6
  false
7
7
  end
8
8
 
9
- def capture(money, response_code, options = {})
10
- if response_code == '12345'
11
- ActiveMerchant::Billing::Response.new(true, 'Bogus Gateway: Forced success', {}, :test => true, :authorization => '67890')
12
- else
13
- ActiveMerchant::Billing::Response.new(false, 'Bogus Gateway: Forced failure', :error => 'Bogus Gateway: Forced failure', :test => true)
14
- end
15
- end
16
-
17
9
  def authorize(money, credit_card, options = {})
18
10
  if VALID_CCS.include? credit_card.number
19
11
  ActiveMerchant::Billing::Response.new(true, 'Bogus Gateway: Forced success', {}, :test => true, :authorization => '12345', :avs_result => { :code => 'A' })
@@ -14,15 +14,6 @@ module Spree
14
14
  # we need to look at the write-queue for images which have not been saved yet
15
15
  after_post_process :find_dimensions
16
16
 
17
- include Spree::Core::S3Support
18
- supports_s3 :attachment
19
-
20
- Spree::Image.attachment_definitions[:attachment][:styles] = ActiveSupport::JSON.decode(Spree::Config[:attachment_styles]).symbolize_keys!
21
- Spree::Image.attachment_definitions[:attachment][:path] = Spree::Config[:attachment_path]
22
- Spree::Image.attachment_definitions[:attachment][:url] = Spree::Config[:attachment_url]
23
- Spree::Image.attachment_definitions[:attachment][:default_url] = Spree::Config[:attachment_default_url]
24
- Spree::Image.attachment_definitions[:attachment][:default_style] = Spree::Config[:attachment_default_style]
25
-
26
17
  #used by admin products autocomplete
27
18
  def mini_url
28
19
  attachment.url(:mini, false)
@@ -1,9 +1,10 @@
1
1
  module Spree
2
2
  class InventoryUnit < ActiveRecord::Base
3
- belongs_to :variant, class_name: "Spree::Variant"
4
- belongs_to :order, class_name: "Spree::Order"
5
- belongs_to :shipment, class_name: "Spree::Shipment", touch: true
3
+ belongs_to :variant, class_name: "Spree::Variant", inverse_of: :inventory_units
4
+ belongs_to :order, class_name: "Spree::Order", inverse_of: :inventory_units
5
+ belongs_to :shipment, class_name: "Spree::Shipment", touch: true, inverse_of: :inventory_units
6
6
  belongs_to :return_authorization, class_name: "Spree::ReturnAuthorization"
7
+ belongs_to :line_item, class_name: "Spree::LineItem", inverse_of: :inventory_units
7
8
 
8
9
  scope :backordered, -> { where state: 'backordered' }
9
10
  scope :shipped, -> { where state: 'shipped' }
@@ -44,7 +45,12 @@ module Spree
44
45
  end
45
46
 
46
47
  def self.finalize_units!(inventory_units)
47
- inventory_units.map { |iu| iu.update_column(:pending, false) }
48
+ inventory_units.map do |iu|
49
+ iu.update_columns(
50
+ pending: false,
51
+ updated_at: Time.now,
52
+ )
53
+ end
48
54
  end
49
55
 
50
56
  def find_stock_item
@@ -0,0 +1,65 @@
1
+ module Spree
2
+ # Manage (recalculate) item (LineItem or Shipment) adjustments
3
+ class ItemAdjustments
4
+ attr_reader :item
5
+
6
+ delegate :adjustments, :order, to: :item
7
+
8
+ def initialize(item)
9
+ @item = item
10
+ end
11
+
12
+ def update
13
+ update_adjustments if item.persisted?
14
+ item
15
+ end
16
+
17
+ # TODO this should be probably the place to calculate proper item taxes
18
+ # values after promotions are applied
19
+ def update_adjustments
20
+ # Promotion adjustments must be applied first, then tax adjustments.
21
+ # This fits the criteria for VAT tax as outlined here:
22
+ # http://www.hmrc.gov.uk/vat/managing/charging/discounts-etc.htm#1
23
+ #
24
+ # It also fits the criteria for sales tax as outlined here:
25
+ # http://www.boe.ca.gov/formspubs/pub113/
26
+ #
27
+ # Tax adjustments come in not one but *two* exciting flavours:
28
+ # Included & additional
29
+
30
+ # Included tax adjustments are those which are included in the price.
31
+ # These ones should not effect the eventual total price.
32
+ #
33
+ # Additional tax adjustments are the opposite; effecting the final total.
34
+ promotion_total = adjustments.promotion.reload.map(&:update!).compact.sum
35
+ unless promotion_total == 0
36
+ choose_best_promotion_adjustment
37
+ end
38
+ promo_total = best_promotion_adjustment.try(:amount).to_f
39
+ included_tax_total = adjustments.tax.included.reload.map(&:update!).compact.sum
40
+ additional_tax_total = adjustments.tax.additional.reload.map(&:update!).compact.sum
41
+
42
+ item.update_columns(
43
+ :promo_total => promo_total,
44
+ :included_tax_total => included_tax_total,
45
+ :additional_tax_total => additional_tax_total,
46
+ :adjustment_total => promo_total + additional_tax_total,
47
+ :updated_at => Time.now,
48
+ )
49
+ end
50
+
51
+ # Picks one (and only one) promotion to be eligible for this order
52
+ # This promotion provides the most discount, and if two promotions
53
+ # have the same amount, then it will pick the latest one.
54
+ def choose_best_promotion_adjustment
55
+ if best_promotion_adjustment
56
+ other_promotions = self.adjustments.promotion.where("id NOT IN (?)", best_promotion_adjustment.id)
57
+ other_promotions.update_all(:eligible => false)
58
+ end
59
+ end
60
+
61
+ def best_promotion_adjustment
62
+ @best_promotion_adjustment ||= adjustments.promotion.eligible.reorder("amount ASC, created_at DESC").first
63
+ end
64
+ end
65
+ end
@@ -4,6 +4,7 @@ module Spree
4
4
  include Core::UserAddress
5
5
 
6
6
  self.table_name = 'spree_users'
7
+
7
8
  has_many :orders, foreign_key: :user_id
8
9
 
9
10
  before_destroy :check_completed_orders
@@ -1,12 +1,14 @@
1
1
  module Spree
2
2
  class LineItem < ActiveRecord::Base
3
3
  before_validation :adjust_quantity
4
- belongs_to :order, class_name: "Spree::Order", touch: true
5
- belongs_to :variant, class_name: "Spree::Variant"
4
+ belongs_to :order, class_name: "Spree::Order", inverse_of: :line_items
5
+ belongs_to :variant, class_name: "Spree::Variant", inverse_of: :line_items
6
6
  belongs_to :tax_category, class_name: "Spree::TaxCategory"
7
7
 
8
8
  has_one :product, through: :variant
9
+
9
10
  has_many :adjustments, as: :adjustable, dependent: :destroy
11
+ has_many :inventory_units, inverse_of: :line_item
10
12
 
11
13
  before_validation :copy_price
12
14
  before_validation :copy_tax_category
@@ -20,9 +22,12 @@ module Spree
20
22
  validates :price, numericality: true
21
23
  validates_with Stock::AvailabilityValidator
22
24
 
25
+ before_destroy :update_inventory
26
+
23
27
  after_save :update_inventory
24
- after_save :update_order
25
- after_destroy :update_order
28
+ after_save :update_adjustments
29
+
30
+ after_create :create_tax_charge
26
31
 
27
32
  delegate :name, :description, :should_track_inventory?, to: :variant
28
33
 
@@ -38,14 +43,23 @@ module Spree
38
43
 
39
44
  def copy_tax_category
40
45
  if variant
41
- self.tax_category = variant.product.tax_category
46
+ self.tax_category = variant.tax_category
42
47
  end
43
48
  end
44
49
 
45
50
  def amount
46
51
  price * quantity
47
52
  end
48
- alias total amount
53
+ alias subtotal amount
54
+
55
+ def discounted_amount
56
+ amount + promo_total
57
+ end
58
+
59
+ def final_amount
60
+ amount + adjustment_total.to_f
61
+ end
62
+ alias total final_amount
49
63
 
50
64
  def single_money
51
65
  Spree::Money.new(price, { currency: currency })
@@ -63,7 +77,7 @@ module Spree
63
77
  end
64
78
 
65
79
  def sufficient_stock?
66
- Stock::Quantifier.new(variant).can_supply? quantity
80
+ Stock::Quantifier.new(variant_id).can_supply? quantity
67
81
  end
68
82
 
69
83
  def insufficient_stock?
@@ -87,17 +101,23 @@ module Spree
87
101
  private
88
102
  def update_inventory
89
103
  if changed?
90
- Spree::OrderInventory.new(self.order).verify(self, target_shipment)
104
+ Spree::OrderInventory.new(self.order, self).verify(target_shipment)
91
105
  end
92
106
  end
93
107
 
94
- def update_order
95
- if changed? || destroyed?
96
- # update the order totals, etc.
97
- order.create_tax_charge!
98
- order.update!
108
+ def update_adjustments
109
+ if quantity_changed?
110
+ recalculate_adjustments
99
111
  end
100
112
  end
113
+
114
+ def recalculate_adjustments
115
+ Spree::ItemAdjustments.new(self).update
116
+ end
117
+
118
+ def create_tax_charge
119
+ Spree::TaxRate.adjust(order, [self])
120
+ end
101
121
  end
102
122
  end
103
123
 
@@ -1,7 +1,7 @@
1
1
  module Spree
2
2
  class OptionType < ActiveRecord::Base
3
- has_many :option_values, -> { order(:position) }, dependent: :destroy
4
- has_many :product_option_types, dependent: :destroy
3
+ has_many :option_values, -> { order(:position) }, dependent: :destroy, inverse_of: :option_type
4
+ has_many :product_option_types, dependent: :destroy, inverse_of: :option_type
5
5
  has_many :products, through: :product_option_types
6
6
  has_and_belongs_to_many :prototypes, join_table: 'spree_option_types_prototypes'
7
7
 
@@ -1,6 +1,6 @@
1
1
  module Spree
2
2
  class OptionValue < ActiveRecord::Base
3
- belongs_to :option_type, :class_name => 'Spree::OptionType', :touch => true
3
+ belongs_to :option_type, class_name: 'Spree::OptionType', touch: true, inverse_of: :option_values
4
4
  acts_as_list scope: :option_type
5
5
  has_and_belongs_to_many :variants, join_table: 'spree_option_values_variants', class_name: "Spree::Variant"
6
6
 
@@ -9,7 +9,6 @@ module Spree
9
9
  go_to_state :address
10
10
  go_to_state :delivery
11
11
  go_to_state :payment, if: ->(order) {
12
- order.update_totals
13
12
  order.payment_required?
14
13
  }
15
14
  go_to_state :confirm, if: ->(order) { order.confirmation_required? }
@@ -24,9 +23,11 @@ module Spree
24
23
  if Spree.user_class
25
24
  belongs_to :user, class_name: Spree.user_class.to_s
26
25
  belongs_to :created_by, class_name: Spree.user_class.to_s
26
+ belongs_to :approver, class_name: Spree.user_class.to_s
27
27
  else
28
28
  belongs_to :user
29
29
  belongs_to :created_by
30
+ belongs_to :approver
30
31
  end
31
32
 
32
33
  belongs_to :bill_address, foreign_key: :bill_address_id, class_name: 'Spree::Address'
@@ -35,16 +36,20 @@ module Spree
35
36
  belongs_to :ship_address, foreign_key: :ship_address_id, class_name: 'Spree::Address'
36
37
  alias_attribute :shipping_address, :ship_address
37
38
 
39
+ alias_attribute :ship_total, :shipment_total
40
+
38
41
  has_many :state_changes, as: :stateful
39
- has_many :line_items, -> { order('created_at ASC') }, dependent: :destroy
42
+ has_many :line_items, -> { order('created_at ASC') }, dependent: :destroy, inverse_of: :order
40
43
  has_many :payments, dependent: :destroy
41
44
  has_many :return_authorizations, dependent: :destroy
42
45
  has_many :adjustments, -> { order("#{Adjustment.table_name}.created_at ASC") }, as: :adjustable, dependent: :destroy
43
46
  has_many :line_item_adjustments, through: :line_items, source: :adjustments
44
- has_many :all_adjustments, class_name: 'Spree::Adjustment'
45
- has_many :inventory_units
47
+ has_many :shipment_adjustments, through: :shipments, source: :adjustments
48
+ has_many :inventory_units, inverse_of: :order
49
+
50
+ has_and_belongs_to_many :promotions, join_table: 'spree_orders_promotions'
46
51
 
47
- has_many :shipments, dependent: :destroy do
52
+ has_many :shipments, dependent: :destroy, inverse_of: :order do
48
53
  def states
49
54
  pluck(:state).uniq
50
55
  end
@@ -63,16 +68,15 @@ module Spree
63
68
  attr_accessor :use_billing
64
69
 
65
70
  before_create :link_by_email
66
- after_create :create_tax_charge!
67
71
 
68
72
  validates :email, presence: true, if: :require_email
69
73
  validates :email, email: true, if: :require_email, allow_blank: true
70
- validates :number, uniqueness: true
71
74
  validate :has_available_shipment
72
- validate :has_available_payment
73
75
 
74
76
  make_permalink field: :number
75
77
 
78
+ delegate :update_totals, :persist_totals, :to => :updater
79
+
76
80
  class_attribute :update_hooks
77
81
  self.update_hooks = Set.new
78
82
 
@@ -80,12 +84,8 @@ module Spree
80
84
  where(number: number)
81
85
  end
82
86
 
83
- scope :created_between, ->(start_date, end_date) { where(created_at: start_date..end_date) }
84
- scope :completed_between, ->(start_date, end_date) { where(completed_at: start_date..end_date) }
85
-
86
87
  def self.between(start_date, end_date)
87
- ActiveSupport::Deprecation.warn("Order#between will be deprecated in Spree 2.3, please use either Order#created_between or Order#completed_between instead.")
88
- self.created_between(start_date, end_date)
88
+ where(created_at: start_date..end_date)
89
89
  end
90
90
 
91
91
  def self.by_customer(customer)
@@ -110,6 +110,10 @@ module Spree
110
110
  self.update_hooks.add(hook)
111
111
  end
112
112
 
113
+ def all_adjustments
114
+ Adjustment.where("order_id = :order_id OR adjustable_id = :order_id", :order_id => self.id)
115
+ end
116
+
113
117
  # For compatiblity with Calculator::PriceSack
114
118
  def amount
115
119
  line_items.inject(0.0) { |sum, li| sum + li.amount }
@@ -131,24 +135,33 @@ module Spree
131
135
  Spree::Money.new(adjustment_total, { currency: currency })
132
136
  end
133
137
 
134
- def display_tax_total
135
- Spree::Money.new(tax_total, { currency: currency })
138
+ def display_included_tax_total
139
+ Spree::Money.new(included_tax_total, { currency: currency })
140
+ end
141
+
142
+ def display_additional_tax_total
143
+ Spree::Money.new(additional_tax_total, { currency: currency })
136
144
  end
137
145
 
138
- def display_ship_total
139
- Spree::Money.new(ship_total, { currency: currency })
146
+ def display_shipment_total
147
+ Spree::Money.new(shipment_total, { currency: currency })
140
148
  end
149
+ alias :display_ship_total :display_shipment_total
141
150
 
142
151
  def display_total
143
152
  Spree::Money.new(total, { currency: currency })
144
153
  end
145
154
 
155
+ def shipping_discount
156
+ shipment_adjustments.eligible.sum(:amount) * - 1
157
+ end
158
+
146
159
  def to_param
147
160
  number.to_s.to_url.upcase
148
161
  end
149
162
 
150
163
  def completed?
151
- completed_at.present? || complete?
164
+ completed_at.present?
152
165
  end
153
166
 
154
167
  # Indicates whether or not the user is allowed to proceed to checkout.
@@ -174,11 +187,6 @@ module Spree
174
187
  state == 'confirm'
175
188
  end
176
189
 
177
- # Indicates the number of items in the order
178
- def item_count
179
- line_items.inject(0) { |sum, li| sum + li.quantity }
180
- end
181
-
182
190
  def backordered?
183
191
  shipments.any?(&:backordered?)
184
192
  end
@@ -202,16 +210,6 @@ module Spree
202
210
  Spree::Config[:tax_using_ship_address] ? ship_address : bill_address
203
211
  end
204
212
 
205
- # Array of totals grouped by Adjustment#label. Useful for displaying line item
206
- # adjustments on an invoice. For example, you can display tax breakout for
207
- # cases where tax is included in price.
208
- def line_item_adjustment_totals
209
- Hash[self.line_item_adjustments.eligible.group_by(&:label).map do |label, adjustments|
210
- total = adjustments.sum(&:amount)
211
- [label, Spree::Money.new(total, { currency: currency })]
212
- end]
213
- end
214
-
215
213
  def updater
216
214
  @updater ||= OrderUpdater.new(self)
217
215
  end
@@ -220,10 +218,6 @@ module Spree
220
218
  updater.update
221
219
  end
222
220
 
223
- def update_totals
224
- updater.update_totals
225
- end
226
-
227
221
  def clone_billing_address
228
222
  if bill_address and self.ship_address.nil?
229
223
  self.ship_address = bill_address.clone
@@ -258,11 +252,15 @@ module Spree
258
252
  end
259
253
  end
260
254
 
255
+ # FIXME refactor this method and implement validation using validates_* utilities
261
256
  def generate_order_number
262
- self.number ||= loop do
257
+ record = true
258
+ while record
263
259
  random = "R#{Array.new(9){rand(9)}.join}"
264
- break random unless self.class.exists?(number: random)
260
+ record = self.class.where(number: random).first
265
261
  end
262
+ self.number = random if self.number.blank?
263
+ self.number
266
264
  end
267
265
 
268
266
  def shipped_shipments
@@ -282,14 +280,11 @@ module Spree
282
280
  line_items.detect { |line_item| line_item.variant_id == variant.id }
283
281
  end
284
282
 
285
- def ship_total
286
- adjustments.shipping.sum(:amount)
287
- end
288
-
289
283
  # Creates new tax charges if there are any applicable rates. If prices already
290
284
  # include taxes then price adjustments are created instead.
291
285
  def create_tax_charge!
292
- Spree::TaxRate.adjust(self)
286
+ Spree::TaxRate.adjust(self, line_items)
287
+ Spree::TaxRate.adjust(self, shipments) if shipments.any?
293
288
  end
294
289
 
295
290
  def outstanding_balance
@@ -318,10 +313,8 @@ module Spree
318
313
  # Finalizes an in progress order after checkout is complete.
319
314
  # Called after transition to complete state when payments will have been processed
320
315
  def finalize!
321
- touch :completed_at
322
-
323
316
  # lock all adjustments (coupon promotions, etc.)
324
- adjustments.update_all state: 'closed'
317
+ all_adjustments.each{|a| a.close}
325
318
 
326
319
  # update payment and shipment(s) states, and save
327
320
  updater.update_payment_state
@@ -334,16 +327,16 @@ module Spree
334
327
  save
335
328
  updater.run_hooks
336
329
 
337
- deliver_order_confirmation_email
330
+ touch :completed_at
331
+
332
+ deliver_order_confirmation_email unless confirmation_delivered?
333
+
334
+ consider_risk
338
335
  end
339
336
 
340
337
  def deliver_order_confirmation_email
341
- begin
342
- OrderMailer.confirm_email(self.id).deliver
343
- rescue Exception => e
344
- logger.error("#{e.class.name}: #{e.message}")
345
- logger.error(e.backtrace * "\n")
346
- end
338
+ OrderMailer.confirm_email(self.id).deliver
339
+ update_column(:confirmation_delivered, true)
347
340
  end
348
341
 
349
342
  # Helper methods for checkout steps
@@ -356,7 +349,7 @@ module Spree
356
349
  end
357
350
 
358
351
  def pending_payments
359
- payments.select(&:checkout?)
352
+ payments.select { |payment| payment.checkout? || payment.pending? }
360
353
  end
361
354
 
362
355
  # processes any pending payments and must return a boolean as it's
@@ -408,7 +401,7 @@ module Spree
408
401
  end
409
402
 
410
403
  def insufficient_stock_lines
411
- line_items.select &:insufficient_stock?
404
+ @insufficient_stock_lines ||= line_items.select(&:insufficient_stock?)
412
405
  end
413
406
 
414
407
  def merge!(order, user = nil)
@@ -432,13 +425,10 @@ module Spree
432
425
  end
433
426
 
434
427
  def empty!
435
- adjustments.destroy_all
436
428
  line_items.destroy_all
437
- end
438
-
439
- def clear_adjustments!
440
- self.adjustments.destroy_all
441
- self.line_item_adjustments.destroy_all
429
+ adjustments.destroy_all
430
+ update_totals
431
+ persist_totals
442
432
  end
443
433
 
444
434
  def has_step?(step)
@@ -465,27 +455,10 @@ module Spree
465
455
  @coupon_code = code.strip.downcase rescue nil
466
456
  end
467
457
 
468
- # Tells us if there if the specified promotion is already associated with the order
469
- # regardless of whether or not its currently eligible. Useful because generally
470
- # you would only want a promotion action to apply to order no more than once.
471
- #
472
- # Receives an adjustment +originator+ (here a PromotionAction object) and tells
473
- # if the order has adjustments from that already
474
- def promotion_credit_exists?(originator)
475
- !! adjustments.includes(:originator).promotion.reload.detect { |credit| credit.originator.id == originator.id }
476
- end
477
-
478
- def promo_total
479
- adjustments.eligible.promotion.sum(:amount)
458
+ def can_add_coupon?
459
+ Spree::Promotion.order_activatable?(self)
480
460
  end
481
461
 
482
- def manual_adjustment_total
483
- adjustments.eligible.manual.sum(:amount)
484
- end
485
-
486
- def discount_total
487
- promo_total + manual_adjustment_total
488
- end
489
462
 
490
463
  def shipped?
491
464
  %w(partial shipped).include?(shipment_state)
@@ -503,20 +476,31 @@ module Spree
503
476
  shipments
504
477
  end
505
478
 
479
+ def apply_free_shipping_promotions
480
+ Spree::PromotionHandler::FreeShipping.new(self).activate
481
+ shipments.each { |shipment| ItemAdjustments.new(shipment).update }
482
+ updater.update_shipment_total
483
+ persist_totals
484
+ end
485
+
506
486
  # Clean shipments and make order back to address state
507
487
  #
508
488
  # At some point the might need to force the order to transition from address
509
489
  # to delivery again so that proper updated shipments are created.
510
490
  # e.g. customer goes back from payment step and changes order items
511
491
  def ensure_updated_shipments
512
- if shipments.any? && !self.completed?
492
+ if shipments.any?
513
493
  self.shipments.destroy_all
494
+ self.update_column(:shipment_total, 0)
514
495
  restart_checkout_flow
515
496
  end
516
497
  end
517
498
 
518
499
  def restart_checkout_flow
519
- self.update_column(:state, checkout_steps.first)
500
+ self.update_columns(
501
+ state: checkout_steps.first,
502
+ updated_at: Time.now,
503
+ )
520
504
  end
521
505
 
522
506
  def refresh_shipment_rates
@@ -527,6 +511,12 @@ module Spree
527
511
  (bill_address.empty? && ship_address.empty?) || bill_address.same_as?(ship_address)
528
512
  end
529
513
 
514
+ def set_shipments_cost
515
+ shipments.each(&:update_amounts)
516
+ updater.update_shipment_total
517
+ persist_totals
518
+ end
519
+
530
520
  def is_risky?
531
521
  self.payments.where(%{
532
522
  (avs_response IS NOT NULL and avs_response != '' and avs_response != 'D' and avs_response != 'M') or
@@ -536,6 +526,39 @@ module Spree
536
526
  }.squish!).uniq.count > 0
537
527
  end
538
528
 
529
+ def approved_by(user)
530
+ self.transaction do
531
+ approve!
532
+ self.update_columns(
533
+ approver_id: user.id,
534
+ approved_at: Time.now,
535
+ considered_risky: false,
536
+ )
537
+ end
538
+ end
539
+
540
+ def approved?
541
+ !!self.approved_at
542
+ end
543
+
544
+ def can_approve?
545
+ !approved?
546
+ end
547
+
548
+ def consider_risk
549
+ if is_risky? && !approved?
550
+ considered_risky!
551
+ end
552
+ end
553
+
554
+ def considered_risky!
555
+ update_column(:considered_risky, true)
556
+ end
557
+
558
+ def approve!
559
+ update_column(:considered_risky, false)
560
+ end
561
+
539
562
  private
540
563
 
541
564
  def link_by_email
@@ -566,14 +589,9 @@ module Spree
566
589
  end
567
590
  end
568
591
 
569
- def has_available_payment
570
- return unless has_step?("delivery") && delivery?
571
- # errors.add(:base, :no_payment_methods_available) if available_payment_methods.empty?
572
- end
573
-
574
592
  def after_cancel
575
593
  shipments.each { |shipment| shipment.cancel! }
576
- payments.completed.each { |payment| payment.cancel! }
594
+ payments.completed.each { |payment| payment.credit! }
577
595
 
578
596
  send_cancel_email
579
597
  self.update_column(:payment_state, 'credit_owed') unless shipped?
@@ -585,6 +603,7 @@ module Spree
585
603
 
586
604
  def after_resume
587
605
  shipments.each { |shipment| shipment.resume! }
606
+ consider_risk
588
607
  end
589
608
 
590
609
  def use_billing?
@@ -594,5 +613,6 @@ module Spree
594
613
  def set_currency
595
614
  self.currency = Spree::Config[:currency] if self[:currency].nil?
596
615
  end
616
+
597
617
  end
598
618
  end