spree_core 2.3.13 → 2.4.0.rc1

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 (232) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/spree.js.coffee.erb +1 -5
  3. data/app/helpers/spree/base_helper.rb +22 -11
  4. data/app/helpers/spree/products_helper.rb +8 -7
  5. data/app/mailers/spree/base_mailer.rb +1 -0
  6. data/app/mailers/spree/reimbursement_mailer.rb +10 -0
  7. data/app/mailers/spree/test_mailer.rb +2 -3
  8. data/app/models/concerns/spree/adjustment_source.rb +24 -0
  9. data/app/models/concerns/spree/calculated_adjustments.rb +33 -0
  10. data/app/models/concerns/spree/named_type.rb +12 -0
  11. data/app/models/concerns/spree/user_address.rb +30 -0
  12. data/app/models/concerns/spree/user_payment_source.rb +19 -0
  13. data/app/models/spree/address.rb +13 -6
  14. data/app/models/spree/adjustment.rb +5 -5
  15. data/app/models/spree/app_configuration.rb +8 -4
  16. data/app/models/spree/asset.rb +1 -1
  17. data/app/models/spree/base.rb +0 -3
  18. data/app/models/spree/calculator/flat_rate.rb +1 -5
  19. data/app/models/spree/calculator/returns/default_refund_amount.rb +36 -0
  20. data/app/models/spree/classification.rb +1 -1
  21. data/app/models/spree/credit_card.rb +18 -22
  22. data/app/models/spree/customer_return.rb +70 -0
  23. data/app/models/spree/exchange.rb +42 -0
  24. data/app/models/spree/gateway/bogus.rb +3 -3
  25. data/app/models/spree/image.rb +1 -1
  26. data/app/models/spree/inventory_unit.rb +32 -8
  27. data/app/models/spree/item_adjustments.rb +7 -11
  28. data/app/models/spree/legacy_user.rb +2 -2
  29. data/app/models/spree/line_item.rb +25 -12
  30. data/app/models/spree/option_type.rb +1 -1
  31. data/app/models/spree/option_value.rb +1 -8
  32. data/app/models/spree/order.rb +163 -145
  33. data/app/models/spree/order/checkout.rb +35 -23
  34. data/app/models/spree/order/payments.rb +66 -0
  35. data/app/models/spree/order_contents.rb +34 -24
  36. data/app/models/spree/order_populator.rb +6 -4
  37. data/app/models/spree/order_updater.rb +10 -1
  38. data/app/models/spree/payment.rb +19 -16
  39. data/app/models/spree/payment/processing.rb +40 -72
  40. data/app/models/spree/payment_method.rb +1 -1
  41. data/app/models/spree/payment_method/check.rb +0 -2
  42. data/app/models/spree/preference.rb +1 -1
  43. data/app/models/spree/preferences/preferable.rb +20 -0
  44. data/app/models/spree/price.rb +13 -3
  45. data/app/models/spree/product.rb +24 -29
  46. data/app/models/spree/product_property.rb +0 -2
  47. data/app/models/spree/promotion.rb +66 -24
  48. data/app/models/spree/promotion/actions/create_adjustment.rb +2 -2
  49. data/app/models/spree/promotion/actions/create_item_adjustments.rb +15 -11
  50. data/app/models/spree/promotion/actions/create_line_items.rb +2 -12
  51. data/app/models/spree/promotion/rules/first_order.rb +6 -2
  52. data/app/models/spree/promotion/rules/item_total.rb +42 -4
  53. data/app/models/spree/promotion/rules/one_use_per_user.rb +24 -0
  54. data/app/models/spree/promotion/rules/product.rb +13 -11
  55. data/app/models/spree/promotion/rules/taxon.rb +61 -0
  56. data/app/models/spree/promotion/rules/user.rb +1 -1
  57. data/app/models/spree/promotion/rules/user_logged_in.rb +4 -1
  58. data/app/models/spree/promotion_category.rb +6 -0
  59. data/app/models/spree/promotion_handler/cart.rb +14 -18
  60. data/app/models/spree/promotion_handler/coupon.rb +25 -16
  61. data/app/models/spree/promotion_rule.rb +13 -0
  62. data/app/models/spree/property.rb +1 -3
  63. data/app/models/spree/refund.rb +91 -0
  64. data/app/models/spree/refund_reason.rb +13 -0
  65. data/app/models/spree/reimbursement.rb +148 -0
  66. data/app/models/spree/reimbursement/credit.rb +25 -0
  67. data/app/models/spree/reimbursement/reimbursement_type_engine.rb +56 -0
  68. data/app/models/spree/reimbursement/reimbursement_type_validator.rb +12 -0
  69. data/app/models/spree/reimbursement_performer.rb +43 -0
  70. data/app/models/spree/reimbursement_tax_calculator.rb +38 -0
  71. data/app/models/spree/reimbursement_type.rb +16 -0
  72. data/app/models/spree/reimbursement_type/credit.rb +13 -0
  73. data/app/models/spree/reimbursement_type/exchange.rb +9 -0
  74. data/app/models/spree/reimbursement_type/original_payment.rb +13 -0
  75. data/app/models/spree/reimbursement_type/reimbursement_helpers.rb +50 -0
  76. data/app/models/spree/return_authorization.rb +52 -68
  77. data/app/models/spree/return_authorization_reason.rb +7 -0
  78. data/app/models/spree/return_item.rb +230 -0
  79. data/app/models/spree/return_item/default_eligibility_validator.rb +27 -0
  80. data/app/models/spree/return_item/eligibility_validator/base_validator.rb +24 -0
  81. data/app/models/spree/return_item/eligibility_validator/rma_required.rb +17 -0
  82. data/app/models/spree/return_item/eligibility_validator/time_since_purchase.rb +16 -0
  83. data/app/models/spree/return_item/exchange_variant_eligibility/same_option_value.rb +34 -0
  84. data/app/models/spree/return_item/exchange_variant_eligibility/same_product.rb +10 -0
  85. data/app/models/spree/returns_calculator.rb +8 -0
  86. data/app/models/spree/shipment.rb +209 -154
  87. data/app/models/spree/shipment_handler.rb +43 -0
  88. data/app/models/spree/shipping_calculator.rb +1 -1
  89. data/app/models/spree/shipping_category.rb +2 -2
  90. data/app/models/spree/shipping_method.rb +1 -1
  91. data/app/models/spree/shipping_method_category.rb +1 -1
  92. data/app/models/spree/shipping_rate.rb +4 -0
  93. data/app/models/spree/stock/adjuster.rb +10 -11
  94. data/app/models/spree/stock/availability_validator.rb +6 -10
  95. data/app/models/spree/stock/content_item.rb +48 -0
  96. data/app/models/spree/stock/coordinator.rb +14 -7
  97. data/app/models/spree/stock/estimator.rb +1 -1
  98. data/app/models/spree/stock/inventory_unit_builder.rb +21 -0
  99. data/app/models/spree/stock/package.rb +38 -51
  100. data/app/models/spree/stock/packer.rb +13 -11
  101. data/app/models/spree/stock/prioritizer.rb +7 -7
  102. data/app/models/spree/stock/splitter/base.rb +2 -2
  103. data/app/models/spree/stock_item.rb +6 -9
  104. data/app/models/spree/stock_location.rb +17 -25
  105. data/app/models/spree/stock_movement.rb +1 -8
  106. data/app/models/spree/stock_transfer.rb +0 -2
  107. data/app/models/spree/store.rb +1 -1
  108. data/app/models/spree/tax_category.rb +2 -2
  109. data/app/models/spree/tax_rate.rb +16 -22
  110. data/app/models/spree/taxon.rb +1 -1
  111. data/app/models/spree/variant.rb +45 -23
  112. data/app/models/spree/zone.rb +17 -17
  113. data/app/models/spree/zone_member.rb +1 -1
  114. data/app/views/layouts/spree/base_mailer.html.erb +784 -0
  115. data/app/views/spree/order_mailer/cancel_email.html.erb +45 -0
  116. data/app/views/spree/order_mailer/cancel_email.text.erb +2 -2
  117. data/app/views/spree/order_mailer/confirm_email.html.erb +84 -0
  118. data/app/views/spree/order_mailer/confirm_email.text.erb +2 -2
  119. data/app/views/spree/reimbursement_mailer/reimbursement_email.text.erb +22 -0
  120. data/app/views/spree/shared/_base_mailer_footer.html.erb +20 -0
  121. data/app/views/spree/shared/_base_mailer_header.html.erb +31 -0
  122. data/app/views/spree/shipment_mailer/shipped_email.html.erb +34 -0
  123. data/app/views/spree/test_mailer/test_email.html.erb +40 -0
  124. data/app/views/spree/test_mailer/test_email.text.erb +2 -2
  125. data/config/initializers/friendly_id.rb +88 -0
  126. data/config/initializers/premailer_assets.rb +1 -0
  127. data/config/initializers/user_class_extensions.rb +9 -22
  128. data/config/locales/en.yml +180 -12
  129. data/db/default/spree/states.rb +73 -55
  130. data/db/migrate/20130213191427_create_default_stock.rb +1 -0
  131. data/db/migrate/20130807024301_upgrade_adjustments.rb +4 -5
  132. data/db/migrate/20140309033438_create_store_from_preferences.rb +0 -7
  133. data/db/migrate/20140318191500_create_spree_taxons_promotion_rules.rb +8 -0
  134. data/db/migrate/20140530024945_move_order_token_from_tokenized_permission.rb +1 -1
  135. data/db/migrate/20140601011216_set_shipment_total_for_users_upgrading.rb +5 -3
  136. data/db/migrate/20140625214618_create_spree_refunds.rb +12 -0
  137. data/db/migrate/20140702140656_create_spree_return_authorization_inventory_unit.rb +12 -0
  138. data/db/migrate/20140707125621_rename_return_authorization_inventory_unit_to_return_items.rb +5 -0
  139. data/db/migrate/20140709160534_backfill_line_item_pre_tax_amount.rb +10 -0
  140. data/db/migrate/20140710041921_recreate_spree_return_authorizations.rb +55 -0
  141. data/db/migrate/20140710181204_add_amount_fields_to_return_items.rb +7 -0
  142. data/db/migrate/20140710190048_drop_return_authorization_amount.rb +5 -0
  143. data/db/migrate/20140713140455_create_spree_return_authorization_reasons.rb +28 -0
  144. data/db/migrate/20140713140527_create_spree_refund_reasons.rb +14 -0
  145. data/db/migrate/20140713142214_rename_return_authorization_reason.rb +5 -0
  146. data/db/migrate/20140715182625_create_spree_promotion_categories.rb +11 -0
  147. data/db/migrate/20140716204111_drop_received_at_on_return_items.rb +9 -0
  148. data/db/migrate/20140716212330_add_reception_and_acceptance_status_to_return_items.rb +6 -0
  149. data/db/migrate/20140717155155_create_default_refund_reason.rb +9 -0
  150. data/db/migrate/20140717185932_add_default_to_spree_stock_locations.rb +5 -0
  151. data/db/migrate/20140718133010_create_spree_customer_returns.rb +9 -0
  152. data/db/migrate/20140718133349_add_customer_return_id_to_return_item.rb +6 -0
  153. data/db/migrate/20140718195325_create_friendly_id_slugs.rb +15 -0
  154. data/db/migrate/20140723004419_rename_spree_refund_return_authorization_id.rb +5 -0
  155. data/db/migrate/20140723152808_increase_return_item_pre_tax_amount_precision.rb +13 -0
  156. data/db/migrate/20140723214541_copy_product_slugs_to_slug_history.rb +15 -0
  157. data/db/migrate/20140725131539_create_spree_reimbursements.rb +21 -0
  158. data/db/migrate/20140728225422_add_promotionable_to_spree_products.rb +5 -0
  159. data/db/migrate/20140729133613_add_exchange_inventory_unit_foreign_keys.rb +7 -0
  160. data/db/migrate/20140730155938_add_acceptance_status_errors_to_return_item.rb +5 -0
  161. data/db/migrate/20140731150017_create_spree_reimbursement_types.rb +20 -0
  162. data/db/migrate/20140805171035_add_default_to_spree_credit_cards.rb +5 -0
  163. data/db/migrate/20140805171219_make_existing_credit_cards_default.rb +10 -0
  164. data/db/migrate/20140806144901_add_type_to_reimbursement_type.rb +9 -0
  165. data/db/migrate/20140808184039_create_spree_reimbursement_credits.rb +10 -0
  166. data/db/migrate/20140827170513_add_meta_title_to_spree_products.rb +7 -0
  167. data/db/migrate/20140924164824_add_code_to_spree_tax_categories.rb +5 -0
  168. data/db/migrate/20141002191113_add_code_to_spree_shipping_methods.rb +5 -0
  169. data/db/migrate/20141007230328_add_cancel_audit_fields_to_spree_orders.rb +6 -0
  170. data/db/migrate/20141009204607_add_store_id_to_orders.rb +8 -0
  171. data/lib/generators/spree/install/install_generator.rb +7 -3
  172. data/lib/spree/core.rb +11 -10
  173. data/lib/spree/core/controller_helpers/common.rb +3 -10
  174. data/lib/spree/core/controller_helpers/order.rb +15 -12
  175. data/lib/spree/core/controller_helpers/strong_parameters.rb +9 -9
  176. data/lib/spree/core/engine.rb +8 -1
  177. data/lib/spree/core/importer/order.rb +5 -17
  178. data/lib/spree/core/search/base.rb +1 -1
  179. data/lib/spree/core/validators/email.rb +1 -1
  180. data/lib/spree/core/version.rb +1 -1
  181. data/lib/spree/instrumentation.rb +41 -0
  182. data/lib/spree/migrations.rb +3 -7
  183. data/lib/spree/money.rb +2 -2
  184. data/lib/spree/permitted_attributes.rb +10 -9
  185. data/lib/spree/testing_support/ability_helpers.rb +25 -25
  186. data/lib/spree/testing_support/authorization_helpers.rb +3 -5
  187. data/lib/spree/testing_support/capybara_ext.rb +2 -2
  188. data/lib/spree/testing_support/factories/calculator_factory.rb +0 -8
  189. data/lib/spree/testing_support/factories/credit_card_factory.rb +1 -1
  190. data/lib/spree/testing_support/factories/customer_return_factory.rb +31 -0
  191. data/lib/spree/testing_support/factories/inventory_unit_factory.rb +1 -0
  192. data/lib/spree/testing_support/factories/line_item_factory.rb +2 -1
  193. data/lib/spree/testing_support/factories/order_factory.rb +11 -6
  194. data/lib/spree/testing_support/factories/promotion_category_factory.rb +6 -0
  195. data/lib/spree/testing_support/factories/promotion_factory.rb +9 -7
  196. data/lib/spree/testing_support/factories/refund_factory.rb +14 -0
  197. data/lib/spree/testing_support/factories/reimbursement_factory.rb +16 -0
  198. data/lib/spree/testing_support/factories/reimbursement_type_factory.rb +7 -0
  199. data/lib/spree/testing_support/factories/return_authorization_factory.rb +9 -3
  200. data/lib/spree/testing_support/factories/return_item_factory.rb +10 -0
  201. data/lib/spree/testing_support/factories/shipment_factory.rb +1 -0
  202. data/lib/spree/testing_support/factories/shipping_method_factory.rb +3 -2
  203. data/lib/spree/testing_support/factories/stock_factory.rb +12 -11
  204. data/lib/spree/testing_support/flash.rb +2 -2
  205. data/lib/tasks/email.rake +7 -0
  206. data/lib/tasks/exchanges.rake +70 -0
  207. data/vendor/assets/javascripts/jquery.validate/localization/messages_et.js +23 -0
  208. data/vendor/assets/javascripts/jquery.validate/localization/messages_eu.js +25 -0
  209. data/vendor/assets/javascripts/jquery.validate/localization/messages_hr.js +25 -0
  210. data/vendor/assets/javascripts/jquery.validate/localization/messages_ka.js +25 -0
  211. data/vendor/assets/javascripts/jquery.validate/localization/messages_ko.js +25 -0
  212. data/vendor/assets/javascripts/jquery.validate/localization/messages_my.js +25 -0
  213. data/vendor/assets/javascripts/jquery.validate/localization/messages_pt_BR.js +26 -0
  214. data/vendor/assets/javascripts/jquery.validate/localization/messages_pt_PT.js +26 -0
  215. data/vendor/assets/javascripts/jquery.validate/localization/messages_sl.js +25 -0
  216. data/vendor/assets/javascripts/jquery.validate/localization/messages_sv.js +23 -0
  217. data/vendor/assets/javascripts/jquery.validate/localization/messages_uk.js +25 -0
  218. data/vendor/assets/javascripts/jquery.validate/localization/messages_zh.js +25 -0
  219. data/vendor/assets/javascripts/jquery.validate/localization/messages_zh_TW.js +26 -0
  220. metadata +163 -47
  221. data/app/models/concerns/spree/ransackable_attributes.rb +0 -19
  222. data/db/migrate/20141021194502_add_state_lock_version_to_order.rb +0 -5
  223. data/db/migrate/20141101231208_fix_adjustment_order_presence.rb +0 -13
  224. data/db/migrate/20141105213646_update_classifications_positions.rb +0 -9
  225. data/db/migrate/20141120135441_add_guest_token_index_to_spree_orders.rb +0 -5
  226. data/db/migrate/20150515211137_fix_adjustment_order_id.rb +0 -70
  227. data/lib/spree/core/adjustment_source.rb +0 -26
  228. data/lib/spree/core/calculated_adjustments.rb +0 -35
  229. data/lib/spree/core/controller_helpers.rb +0 -20
  230. data/lib/spree/core/user_address.rb +0 -32
  231. data/lib/spree/core/user_payment_source.rb +0 -20
  232. data/lib/spree/localized_number.rb +0 -20
@@ -0,0 +1,27 @@
1
+ module Spree
2
+ class ReturnItem::DefaultEligibilityValidator < Spree::ReturnItem::EligibilityValidator::BaseValidator
3
+ class_attribute :permitted_eligibility_validators
4
+ self.permitted_eligibility_validators = [
5
+ ReturnItem::EligibilityValidator::TimeSincePurchase,
6
+ ReturnItem::EligibilityValidator::RMARequired,
7
+ ]
8
+
9
+ def eligible_for_return?
10
+ validators.all? {|v| v.eligible_for_return? }
11
+ end
12
+
13
+ def requires_manual_intervention?
14
+ validators.any? {|v| v.requires_manual_intervention? }
15
+ end
16
+
17
+ def errors
18
+ validators.map(&:errors).reduce({}, :merge)
19
+ end
20
+
21
+ private
22
+
23
+ def validators
24
+ @validators ||= permitted_eligibility_validators.map{|v| v.new(@return_item) }
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,24 @@
1
+ module Spree
2
+ class Spree::ReturnItem::EligibilityValidator::BaseValidator
3
+ attr_reader :errors
4
+
5
+ def initialize(return_item)
6
+ @return_item = return_item
7
+ @errors = {}
8
+ end
9
+
10
+ def eligible_for_return?
11
+ raise 'Implement me'
12
+ end
13
+
14
+ def requires_manual_intervention?
15
+ raise 'Implement me'
16
+ end
17
+
18
+ private
19
+
20
+ def add_error(key, error)
21
+ @errors[key] = error
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,17 @@
1
+ module Spree
2
+ class ReturnItem::EligibilityValidator::RMARequired < Spree::ReturnItem::EligibilityValidator::BaseValidator
3
+ def eligible_for_return?
4
+ if @return_item.return_authorization.present?
5
+ return true
6
+ else
7
+ add_error(:rma_required, Spree.t('return_item_rma_ineligible'))
8
+ return false
9
+ end
10
+ end
11
+
12
+ def requires_manual_intervention?
13
+ false
14
+ end
15
+ end
16
+ end
17
+
@@ -0,0 +1,16 @@
1
+ module Spree
2
+ class ReturnItem::EligibilityValidator::TimeSincePurchase < Spree::ReturnItem::EligibilityValidator::BaseValidator
3
+ def eligible_for_return?
4
+ if (@return_item.inventory_unit.created_at + Spree::Config[:return_eligibility_number_of_days].days) > Time.now
5
+ return true
6
+ else
7
+ add_error(:number_of_days, Spree.t('return_item_time_period_ineligible'))
8
+ return false
9
+ end
10
+ end
11
+
12
+ def requires_manual_intervention?
13
+ false
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,34 @@
1
+ module Spree
2
+ module ReturnItem::ExchangeVariantEligibility
3
+ class SameOptionValue
4
+ class_attribute :option_type_restrictions
5
+ self.option_type_restrictions = []
6
+ # This can be configured in an initializer, e.g.:
7
+ # Spree::ReturnItem::ExchangeVariantEligibility::SameOptionValue.option_type_restrictions = ["size", "color"]
8
+ #
9
+ # This restriction causes only variants that share the same option value for the
10
+ # specified option types to be returned. e.g.:
11
+ #
12
+ # option_type_restrictions = ["color", "waist"]
13
+ # Variant: blue pants with 32 waist and 30 inseam
14
+ #
15
+ # can be exchanged for:
16
+ # blue pants with 32 waist and 31 inseam
17
+ #
18
+ # cannot be exchanged for:
19
+ # green pants with 32 waist and 30 inseam
20
+ # blue pants with 34 waist and 32 inseam
21
+
22
+ def self.eligible_variants(variant)
23
+ product_variants = SameProduct.eligible_variants(variant).includes(option_values: :option_type)
24
+
25
+ relevant_option_values = variant.option_values.select { |ov| option_type_restrictions.include? ov.option_type.name }
26
+ if relevant_option_values.present?
27
+ product_variants.select { |v| (relevant_option_values & v.option_values) == relevant_option_values }
28
+ else
29
+ product_variants
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,10 @@
1
+ module Spree
2
+ module ReturnItem::ExchangeVariantEligibility
3
+ class SameProduct
4
+
5
+ def self.eligible_variants(variant)
6
+ Spree::Variant.where.not(id: variant.id).where(product_id: variant.product_id, is_master: false)
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,8 @@
1
+ module Spree
2
+ class ReturnsCalculator < Calculator
3
+
4
+ def compute(return_item)
5
+ raise NotImplementedError, "Please implement 'compute(return_item)' in your calculator: #{self.class.name}"
6
+ end
7
+ end
8
+ end
@@ -2,15 +2,15 @@ require 'ostruct'
2
2
 
3
3
  module Spree
4
4
  class Shipment < Spree::Base
5
- belongs_to :order, class_name: 'Spree::Order', touch: true, inverse_of: :shipments
6
5
  belongs_to :address, class_name: 'Spree::Address', inverse_of: :shipments
6
+ belongs_to :order, class_name: 'Spree::Order', touch: true, inverse_of: :shipments
7
7
  belongs_to :stock_location, class_name: 'Spree::StockLocation'
8
8
 
9
+ has_many :adjustments, as: :adjustable, dependent: :delete_all
10
+ has_many :inventory_units, dependent: :delete_all, inverse_of: :shipment
9
11
  has_many :shipping_rates, -> { order('cost ASC') }, dependent: :delete_all
10
12
  has_many :shipping_methods, through: :shipping_rates
11
13
  has_many :state_changes, as: :stateful
12
- has_many :inventory_units, dependent: :delete_all, inverse_of: :shipment
13
- has_many :adjustments, as: :adjustable, dependent: :delete_all
14
14
 
15
15
  after_save :update_adjustments
16
16
 
@@ -23,11 +23,13 @@ module Spree
23
23
 
24
24
  make_permalink field: :number, length: 11, prefix: 'H'
25
25
 
26
- scope :shipped, -> { with_state('shipped') }
27
- scope :ready, -> { with_state('ready') }
28
26
  scope :pending, -> { with_state('pending') }
29
- scope :with_state, ->(*s) { where(state: s) }
27
+ scope :ready, -> { with_state('ready') }
28
+ scope :shipped, -> { with_state('shipped') }
30
29
  scope :trackable, -> { where("tracking IS NOT NULL AND tracking != ''") }
30
+ scope :with_state, ->(*s) { where(state: s) }
31
+ # sort by most recent shipped_at, falling back to created_at. add "id desc" to make specs that involve this scope more deterministic.
32
+ scope :reverse_chronological, -> { order('coalesce(spree_shipments.shipped_at, spree_shipments.created_at) desc', id: :desc) }
31
33
 
32
34
  # shipment state machine (see http://github.com/pluginaweek/state_machine/tree/master for details)
33
35
  state_machine initial: :pending, use_transactions: false do
@@ -43,7 +45,7 @@ module Spree
43
45
  end
44
46
 
45
47
  event :ship do
46
- transition from: :ready, to: :shipped
48
+ transition from: [:ready, :canceled], to: :shipped
47
49
  end
48
50
  after_transition to: :shipped, do: :after_ship
49
51
 
@@ -61,7 +63,7 @@ module Spree
61
63
  }
62
64
  transition from: :canceled, to: :pending
63
65
  end
64
- after_transition from: :canceled, to: [:pending, :ready], do: :after_resume
66
+ after_transition from: :canceled, to: [:pending, :ready, :shipped], do: :after_resume
65
67
 
66
68
  after_transition do |shipment, transition|
67
69
  shipment.state_changes.create!(
@@ -72,113 +74,96 @@ module Spree
72
74
  end
73
75
  end
74
76
 
75
- def to_param
76
- number
77
- end
78
-
79
- def backordered?
80
- inventory_units.any? { |inventory_unit| inventory_unit.backordered? }
77
+ def add_shipping_method(shipping_method, selected = false)
78
+ shipping_rates.create(shipping_method: shipping_method, selected: selected, cost: cost)
81
79
  end
82
80
 
83
- def ready_or_pending?
84
- self.ready? || self.pending?
81
+ def after_cancel
82
+ manifest.each { |item| manifest_restock(item) }
85
83
  end
86
84
 
87
- def shipped=(value)
88
- return unless value == '1' && shipped_at.nil?
89
- self.shipped_at = Time.now
85
+ def after_resume
86
+ manifest.each { |item| manifest_unstock(item) }
90
87
  end
91
88
 
92
- def shipping_method
93
- selected_shipping_rate.try(:shipping_method) || shipping_rates.first.try(:shipping_method)
89
+ def backordered?
90
+ inventory_units.any? { |inventory_unit| inventory_unit.backordered? }
94
91
  end
95
92
 
96
- self.whitelisted_ransackable_attributes = ['number']
97
-
98
- def add_shipping_method(shipping_method, selected = false)
99
- shipping_rates.create(shipping_method: shipping_method, selected: selected, cost: cost)
93
+ def currency
94
+ order ? order.currency : Spree::Config[:currency]
100
95
  end
101
96
 
102
- def selected_shipping_rate
103
- shipping_rates.where(selected: true).first
97
+ # Determines the appropriate +state+ according to the following logic:
98
+ #
99
+ # pending unless order is complete and +order.payment_state+ is +paid+
100
+ # shipped if already shipped (ie. does not change the state)
101
+ # ready all other cases
102
+ def determine_state(order)
103
+ return 'canceled' if order.canceled?
104
+ return 'pending' unless order.can_ship?
105
+ return 'pending' if inventory_units.any? &:backordered?
106
+ return 'shipped' if state == 'shipped'
107
+ order.paid? || Spree::Config[:auto_capture_on_dispatch] ? 'ready' : 'pending'
104
108
  end
105
109
 
106
- def selected_shipping_rate_id
107
- selected_shipping_rate.try(:id)
110
+ def discounted_cost
111
+ cost + promo_total
108
112
  end
113
+ alias discounted_amount discounted_cost
109
114
 
110
- def selected_shipping_rate_id=(id)
111
- shipping_rates.update_all(selected: false)
112
- shipping_rates.update(id, selected: true)
113
- self.save!
115
+ def display_cost
116
+ Spree::Money.new(cost, { currency: currency })
114
117
  end
118
+ alias display_amount display_cost
115
119
 
116
- def tax_category
117
- selected_shipping_rate.try(:tax_rate).try(:tax_category)
120
+ def display_discounted_cost
121
+ Spree::Money.new(discounted_cost, { currency: currency })
118
122
  end
119
123
 
120
- def refresh_rates
121
- return shipping_rates if shipped?
122
- return [] unless can_get_rates?
123
-
124
- # StockEstimator.new assigment below will replace the current shipping_method
125
- original_shipping_method_id = shipping_method.try(:id)
126
-
127
- self.shipping_rates = Stock::Estimator.new(order).shipping_rates(to_package)
128
-
129
- if shipping_method
130
- selected_rate = shipping_rates.detect { |rate|
131
- rate.shipping_method_id == original_shipping_method_id
132
- }
133
- self.selected_shipping_rate_id = selected_rate.id if selected_rate
134
- end
135
-
136
- shipping_rates
124
+ def display_final_price
125
+ Spree::Money.new(final_price, { currency: currency })
137
126
  end
138
127
 
139
- def currency
140
- order ? order.currency : Spree::Config[:currency]
128
+ def display_item_cost
129
+ Spree::Money.new(item_cost, { currency: currency })
141
130
  end
142
131
 
143
- def display_cost
144
- Spree::Money.new(cost, { currency: currency })
132
+ def editable_by?(user)
133
+ !shipped?
145
134
  end
146
- alias display_amount display_cost
147
135
 
148
- def item_cost
149
- line_items.map(&:amount).sum
136
+ def final_price
137
+ cost + adjustment_total
150
138
  end
151
139
 
152
- def discounted_cost
153
- cost + promo_total
140
+ def final_price_with_items
141
+ item_cost + final_price
154
142
  end
155
- alias discounted_amount discounted_cost
156
143
 
157
- # Only one of either included_tax_total or additional_tax_total is set
158
- # This method returns the total of the two. Saves having to check if
159
- # tax is included or additional.
160
- def tax_total
161
- included_tax_total + additional_tax_total
144
+ def finalize!
145
+ InventoryUnit.finalize_units!(inventory_units)
146
+ manifest.each { |item| manifest_unstock(item) }
162
147
  end
163
148
 
164
- def final_price
165
- cost + adjustment_total
149
+ def include?(variant)
150
+ inventory_units_for(variant).present?
166
151
  end
167
152
 
168
- def display_discounted_cost
169
- Spree::Money.new(discounted_cost, { currency: currency })
153
+ def inventory_units_for(variant)
154
+ inventory_units.where(variant_id: variant.id)
170
155
  end
171
156
 
172
- def display_final_price
173
- Spree::Money.new(final_price, { currency: currency })
157
+ def inventory_units_for_item(line_item, variant = nil)
158
+ inventory_units.where(line_item_id: line_item.id, variant_id: line_item.variant.id || variant.id)
174
159
  end
175
160
 
176
- def display_item_cost
177
- Spree::Money.new(item_cost, { currency: currency })
161
+ def item_cost
162
+ line_items.map(&:amount).sum
178
163
  end
179
164
 
180
- def editable_by?(user)
181
- !shipped?
165
+ def line_items
166
+ inventory_units.includes(:line_item).map(&:line_item).uniq
182
167
  end
183
168
 
184
169
  ManifestItem = Struct.new(:line_item, :variant, :quantity, :states)
@@ -199,84 +184,119 @@ module Spree
199
184
  end.flatten
200
185
  end
201
186
 
202
- def line_items
203
- inventory_units.includes(:line_item).map(&:line_item).uniq
187
+ def process_order_payments
188
+ pending_payments = order.pending_payments
189
+ .sort_by(&:uncaptured_amount).reverse
190
+
191
+ # NOTE Do we really need to force orders to have pending payments on dispatch?
192
+ if pending_payments.empty?
193
+ raise Spree::Core::GatewayError, Spree.t(:no_pending_payments)
194
+ else
195
+ shipment_to_pay = final_price_with_items
196
+ payments_amount = 0
197
+
198
+ payments_pool = pending_payments.each_with_object([]) do |payment, pool|
199
+ next if payments_amount >= shipment_to_pay
200
+ payments_amount += payment.uncaptured_amount
201
+ pool << payment
202
+ end
203
+
204
+ payments_pool.each do |payment|
205
+ capturable_amount = if payment.amount >= shipment_to_pay
206
+ shipment_to_pay
207
+ else
208
+ payment.amount
209
+ end
210
+ cents = (capturable_amount * 100).to_i
211
+ payment.capture!(cents)
212
+ shipment_to_pay -= capturable_amount
213
+ end
214
+ end
215
+ rescue Spree::Core::GatewayError => e
216
+ errors.add(:base, e.message)
217
+ return !!Spree::Config[:allow_checkout_on_gateway_error]
204
218
  end
205
219
 
206
- def finalize!
207
- InventoryUnit.finalize_units!(inventory_units)
208
- manifest.each { |item| manifest_unstock(item) }
220
+ def ready_or_pending?
221
+ self.ready? || self.pending?
209
222
  end
210
223
 
211
- def after_cancel
212
- manifest.each { |item| manifest_restock(item) }
224
+ def refresh_rates
225
+ return shipping_rates if shipped?
226
+ return [] unless can_get_rates?
227
+
228
+ # StockEstimator.new assigment below will replace the current shipping_method
229
+ original_shipping_method_id = shipping_method.try(:id)
230
+
231
+ self.shipping_rates = Stock::Estimator.new(order).shipping_rates(to_package)
232
+
233
+ if shipping_method
234
+ selected_rate = shipping_rates.detect { |rate|
235
+ rate.shipping_method_id == original_shipping_method_id
236
+ }
237
+ self.selected_shipping_rate_id = selected_rate.id if selected_rate
238
+ end
239
+
240
+ shipping_rates
213
241
  end
214
242
 
215
- def after_resume
216
- manifest.each { |item| manifest_unstock(item) }
243
+ def selected_shipping_rate
244
+ shipping_rates.where(selected: true).first
217
245
  end
218
246
 
219
- # Updates various aspects of the Shipment while bypassing any callbacks. Note that this method takes an explicit reference to the
220
- # Order object. This is necessary because the association actually has a stale (and unsaved) copy of the Order and so it will not
221
- # yield the correct results.
222
- def update!(order)
223
- old_state = state
224
- new_state = determine_state(order)
225
- update_columns(
226
- state: new_state,
227
- updated_at: Time.now,
228
- )
229
- after_ship if new_state == 'shipped' and old_state != 'shipped'
247
+ def selected_shipping_rate_id
248
+ selected_shipping_rate.try(:id)
230
249
  end
231
250
 
232
- # Determines the appropriate +state+ according to the following logic:
233
- #
234
- # pending unless order is complete and +order.payment_state+ is +paid+
235
- # shipped if already shipped (ie. does not change the state)
236
- # ready all other cases
237
- def determine_state(order)
238
- return 'canceled' if order.canceled?
239
- return 'pending' unless order.can_ship?
240
- return 'pending' if inventory_units.any? &:backordered?
241
- return 'shipped' if state == 'shipped'
242
- order.paid? ? 'ready' : 'pending'
251
+ def selected_shipping_rate_id=(id)
252
+ shipping_rates.update_all(selected: false)
253
+ shipping_rates.update(id, selected: true)
254
+ self.save!
243
255
  end
244
256
 
245
- def tracking_url
246
- @tracking_url ||= shipping_method.build_tracking_url(tracking)
257
+ def set_up_inventory(state, variant, order, line_item)
258
+ self.inventory_units.create(
259
+ state: state,
260
+ variant_id: variant.id,
261
+ order_id: order.id,
262
+ line_item_id: line_item.id
263
+ )
247
264
  end
248
265
 
249
- def include?(variant)
250
- inventory_units_for(variant).present?
266
+ def shipped=(value)
267
+ return unless value == '1' && shipped_at.nil?
268
+ self.shipped_at = Time.now
251
269
  end
252
270
 
253
- def inventory_units_for(variant)
254
- inventory_units.where(variant_id: variant.id)
271
+ def shipping_method
272
+ selected_shipping_rate.try(:shipping_method) || shipping_rates.first.try(:shipping_method)
255
273
  end
256
274
 
257
- def inventory_units_for_item(line_item, variant = nil)
258
- inventory_units.where(line_item_id: line_item.id, variant_id: line_item.variant.id || variant.id)
275
+ def tax_category
276
+ selected_shipping_rate.try(:tax_rate).try(:tax_category)
259
277
  end
260
278
 
261
- def to_package
262
- package = Stock::Package.new(stock_location, order)
263
- grouped_inventory_units = inventory_units.includes(:line_item).group_by do |iu|
264
- [iu.line_item, iu.state_name]
265
- end
279
+ # Only one of either included_tax_total or additional_tax_total is set
280
+ # This method returns the total of the two. Saves having to check if
281
+ # tax is included or additional.
282
+ def tax_total
283
+ included_tax_total + additional_tax_total
284
+ end
266
285
 
267
- grouped_inventory_units.each do |(line_item, state_name), inventory_units|
268
- package.add line_item, inventory_units.count, state_name
286
+ def to_package
287
+ package = Stock::Package.new(stock_location)
288
+ inventory_units.group_by(&:state).each do |state, state_inventory_units|
289
+ package.add_multiple state_inventory_units, state.to_sym
269
290
  end
270
291
  package
271
292
  end
272
293
 
273
- def set_up_inventory(state, variant, order, line_item)
274
- self.inventory_units.create(
275
- state: state,
276
- variant_id: variant.id,
277
- order_id: order.id,
278
- line_item_id: line_item.id
279
- )
294
+ def to_param
295
+ number
296
+ end
297
+
298
+ def tracking_url
299
+ @tracking_url ||= shipping_method.build_tracking_url(tracking)
280
300
  end
281
301
 
282
302
  def update_amounts
@@ -319,10 +339,63 @@ module Spree
319
339
  end
320
340
  end
321
341
 
342
+ # Updates various aspects of the Shipment while bypassing any callbacks. Note that this method takes an explicit reference to the
343
+ # Order object. This is necessary because the association actually has a stale (and unsaved) copy of the Order and so it will not
344
+ # yield the correct results.
345
+ def update!(order)
346
+ old_state = state
347
+ new_state = determine_state(order)
348
+ update_columns(
349
+ state: new_state,
350
+ updated_at: Time.now,
351
+ )
352
+ after_ship if new_state == 'shipped' and old_state != 'shipped'
353
+ end
354
+
355
+ def transfer_to_location(variant, quantity, stock_location)
356
+ if quantity <= 0
357
+ raise ArgumentError
358
+ end
359
+
360
+ transaction do
361
+ new_shipment = order.shipments.create!(stock_location: stock_location)
362
+
363
+ order.contents.remove(variant, quantity, {shipment: self})
364
+ order.contents.add(variant, quantity, {shipment: new_shipment})
365
+
366
+ refresh_rates
367
+ save!
368
+ new_shipment.save!
369
+ end
370
+ end
371
+
372
+ def transfer_to_shipment(variant, quantity, shipment_to_transfer_to)
373
+ quantity_already_shipment_to_transfer_to = shipment_to_transfer_to.manifest.find{|mi| mi.line_item.variant == variant}.try(:quantity) || 0
374
+ final_quantity = quantity + quantity_already_shipment_to_transfer_to
375
+
376
+ if (quantity <= 0 || self == shipment_to_transfer_to)
377
+ raise ArgumentError
378
+ end
379
+
380
+ transaction do
381
+ order.contents.remove(variant, quantity, {shipment: self})
382
+ order.contents.add(variant, quantity, {shipment: shipment_to_transfer_to})
383
+
384
+ refresh_rates
385
+ save!
386
+ shipment_to_transfer_to.refresh_rates
387
+ shipment_to_transfer_to.save!
388
+ end
389
+ end
390
+
322
391
  private
323
392
 
324
- def manifest_unstock(item)
325
- stock_location.unstock item.variant, item.quantity, self
393
+ def after_ship
394
+ ShipmentHandler.factory(self).perform
395
+ end
396
+
397
+ def can_get_rates?
398
+ order.ship_address && order.ship_address.valid?
326
399
  end
327
400
 
328
401
  def manifest_restock(item)
@@ -335,23 +408,12 @@ module Spree
335
408
  end
336
409
  end
337
410
 
338
- def description_for_shipping_charge
339
- "#{Spree.t(:shipping)} (#{shipping_method.name})"
340
- end
341
-
342
- def after_ship
343
- inventory_units.each &:ship!
344
- send_shipped_email
345
- touch :shipped_at
346
- update_order_shipment_state
411
+ def manifest_unstock(item)
412
+ stock_location.unstock item.variant, item.quantity, self
347
413
  end
348
414
 
349
- def update_order_shipment_state
350
- new_state = OrderUpdater.new(order).update_shipment_state
351
- order.update_columns(
352
- shipment_state: new_state,
353
- updated_at: Time.now,
354
- )
415
+ def recalculate_adjustments
416
+ Spree::ItemAdjustments.new(self).update
355
417
  end
356
418
 
357
419
  def send_shipped_email
@@ -368,12 +430,5 @@ module Spree
368
430
  end
369
431
  end
370
432
 
371
- def recalculate_adjustments
372
- Spree::ItemAdjustments.new(self).update
373
- end
374
-
375
- def can_get_rates?
376
- order.ship_address && order.ship_address.valid?
377
- end
378
433
  end
379
434
  end