spree_core 2.1.12 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
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