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,16 @@
1
+ module Spree
2
+ class ReimbursementType < Spree::Base
3
+ include Spree::NamedType
4
+
5
+ ORIGINAL = 'original'
6
+
7
+ has_many :return_items
8
+
9
+ # This method will reimburse the return items based on however it child implements it
10
+ # By default it takes a reimbursement, the return items it needs to reimburse, and if
11
+ # it is a simulation or a real reimbursement. This should return an array
12
+ def self.reimburse(reimbursement, return_items, simulate)
13
+ raise "Implement me"
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,13 @@
1
+ module Spree
2
+ class ReimbursementType::Credit < Spree::ReimbursementType
3
+ extend Spree::ReimbursementType::ReimbursementHelpers
4
+
5
+ class << self
6
+ def reimburse(reimbursement, return_items, simulate)
7
+ unpaid_amount = return_items.sum(&:total).round(2)
8
+ reimbursement_list, unpaid_amount = create_credits(reimbursement, unpaid_amount, simulate)
9
+ reimbursement_list
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,9 @@
1
+ class Spree::ReimbursementType::Exchange < Spree::ReimbursementType
2
+ def self.reimburse(reimbursement, return_items, simulate)
3
+ return [] unless return_items.present?
4
+
5
+ exchange = Spree::Exchange.new(reimbursement.order, return_items)
6
+ exchange.perform! unless simulate
7
+ [exchange]
8
+ end
9
+ end
@@ -0,0 +1,13 @@
1
+ class Spree::ReimbursementType::OriginalPayment < Spree::ReimbursementType
2
+ extend Spree::ReimbursementType::ReimbursementHelpers
3
+
4
+ class << self
5
+ def reimburse(reimbursement, return_items, simulate)
6
+ unpaid_amount = return_items.sum(&:total).round(2)
7
+ payments = reimbursement.order.payments.completed
8
+
9
+ refund_list, unpaid_amount = create_refunds(reimbursement, payments, unpaid_amount, simulate)
10
+ refund_list
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,50 @@
1
+ module Spree
2
+ module ReimbursementType::ReimbursementHelpers
3
+ def create_refunds(reimbursement, payments, unpaid_amount, simulate, reimbursement_list = [])
4
+ payments.map do |payment|
5
+ break if unpaid_amount <= 0
6
+ next unless payment.can_credit?
7
+
8
+ amount = [unpaid_amount, payment.credit_allowed].min
9
+ reimbursement_list << create_refund(reimbursement, payment, amount, simulate)
10
+ unpaid_amount -= amount
11
+ end
12
+
13
+ return reimbursement_list, unpaid_amount
14
+ end
15
+
16
+ def create_credits(reimbursement, unpaid_amount, simulate, reimbursement_list = [])
17
+ credits = [create_credit(reimbursement, unpaid_amount, simulate)]
18
+ unpaid_amount -= credits.sum(&:amount)
19
+ reimbursement_list += credits
20
+
21
+ return reimbursement_list, unpaid_amount
22
+ end
23
+
24
+ private
25
+
26
+ def create_refund(reimbursement, payment, amount, simulate)
27
+ refund = reimbursement.refunds.build({
28
+ payment: payment,
29
+ amount: amount,
30
+ reason: Spree::RefundReason.return_processing_reason,
31
+ })
32
+
33
+ simulate ? refund.readonly! : refund.save!
34
+ refund
35
+ end
36
+
37
+ # If you have multiple methods of crediting a customer, overwrite this method
38
+ # Must return an array of objects the respond to #description, #display_amount
39
+ def create_credit(reimbursement, unpaid_amount, simulate)
40
+ creditable = create_creditable(reimbursement, unpaid_amount)
41
+ credit = reimbursement.credits.build(creditable: creditable, amount: unpaid_amount)
42
+ simulate ? credit.readonly! : credit.save!
43
+ credit
44
+ end
45
+
46
+ def create_creditable(reimbursement, unpaid_amount)
47
+ Spree::Reimbursement::Credit.default_creditable_class.new(reimbursement: reimbursement, amount: unpaid_amount)
48
+ end
49
+ end
50
+ end
@@ -1,114 +1,98 @@
1
1
  module Spree
2
2
  class ReturnAuthorization < Spree::Base
3
- belongs_to :order, class_name: 'Spree::Order', inverse_of: :return_authorizations
3
+ belongs_to :order, class_name: 'Spree::Order'
4
+
5
+ has_many :return_items, inverse_of: :return_authorization, dependent: :destroy
6
+ has_many :inventory_units, through: :return_items
7
+ has_many :customer_returns, through: :return_items
4
8
 
5
- has_many :inventory_units, dependent: :nullify, inverse_of: :return_authorization
6
9
  belongs_to :stock_location
10
+ belongs_to :reason, class_name: 'Spree::ReturnAuthorizationReason', foreign_key: :return_authorization_reason_id
7
11
  before_create :generate_number
8
- before_save :force_positive_amount
12
+
13
+ after_save :generate_expedited_exchange_reimbursements
14
+
15
+ accepts_nested_attributes_for :return_items, allow_destroy: true
9
16
 
10
17
  validates :order, presence: true
11
- validates :amount, numericality: true
12
- validate :must_have_shipped_units
18
+ validates :reason, presence: true
19
+ validates :stock_location, presence: true
20
+ validate :must_have_shipped_units, on: :create
21
+
22
+
23
+ # These are called prior to generating expedited exchanges shipments.
24
+ # Should respond to a "call" method that takes the list of return items
25
+ class_attribute :pre_expedited_exchange_hooks
26
+ self.pre_expedited_exchange_hooks = []
13
27
 
14
28
  state_machine initial: :authorized do
15
- after_transition to: :received, do: :process_return
29
+ before_transition to: :canceled, do: :cancel_return_items
16
30
 
17
- event :receive do
18
- transition to: :received, from: :authorized, if: :allow_receive?
19
- end
20
31
  event :cancel do
21
32
  transition to: :canceled, from: :authorized
22
33
  end
23
- end
24
34
 
25
- self.whitelisted_ransackable_attributes = ['reason']
35
+ end
26
36
 
27
37
  def pre_tax_total
28
38
  return_items.sum(:pre_tax_amount)
29
39
  end
30
40
 
31
- def currency
32
- order.nil? ? Spree::Config[:currency] : order.currency
33
- end
34
-
35
- def display_amount
36
- Spree::Money.new(amount, { currency: currency })
41
+ def display_pre_tax_total
42
+ Spree::Money.new(pre_tax_total, { currency: currency })
37
43
  end
38
44
 
39
- def add_variant(variant_id, quantity)
40
- order_units = returnable_inventory.group_by(&:variant_id)
41
- returned_units = inventory_units.group_by(&:variant_id)
42
- return false if order_units.empty?
43
-
44
- count = 0
45
-
46
- if returned_units[variant_id].nil? || returned_units[variant_id].size < quantity
47
- count = returned_units[variant_id].nil? ? 0 : returned_units[variant_id].size
48
-
49
- order_units[variant_id].each do |inventory_unit|
50
- next unless inventory_unit.return_authorization.nil? && count < quantity
51
-
52
- inventory_unit.return_authorization = self
53
- inventory_unit.save!
54
-
55
- count += 1
56
- end
57
- elsif returned_units[variant_id].size > quantity
58
- (returned_units[variant_id].size - quantity).times do |i|
59
- returned_units[variant_id][i].return_authorization_id = nil
60
- returned_units[variant_id][i].save!
61
- end
62
- end
63
-
64
- order.authorize_return! if inventory_units.reload.size > 0 && !order.awaiting_return?
45
+ def currency
46
+ order.nil? ? Spree::Config[:currency] : order.currency
65
47
  end
66
48
 
67
- def returnable_inventory
68
- order.shipped_shipments.collect{|s| s.inventory_units.to_a}.flatten
49
+ def refundable_amount
50
+ order.pre_tax_item_amount + order.promo_total
69
51
  end
70
52
 
71
- # Used when Adjustment#update! wants to update the related adjustment
72
- def compute_amount(*args)
73
- amount.abs * -1
53
+ def customer_returned_items?
54
+ customer_returns.exists?
74
55
  end
75
56
 
76
57
  private
77
58
 
78
59
  def must_have_shipped_units
79
- errors.add(:order, Spree.t(:has_no_shipped_units)) if order.nil? || !order.shipped_shipments.any?
60
+ if order.nil? || order.inventory_units.shipped.none?
61
+ errors.add(:order, Spree.t(:has_no_shipped_units))
62
+ end
80
63
  end
81
64
 
82
65
  def generate_number
83
66
  self.number ||= loop do
84
- random = "RMA#{Array.new(9){rand(9)}.join}"
67
+ random = "RA#{Array.new(9){rand(9)}.join}"
85
68
  break random unless self.class.exists?(number: random)
86
69
  end
87
70
  end
88
71
 
89
- def process_return
90
- inventory_units(include: :variant).each do |iu|
91
- iu.return!
72
+ def cancel_return_items
73
+ return_items.each(&:cancel!)
74
+ end
92
75
 
93
- if iu.variant.should_track_inventory?
94
- if stock_item = Spree::StockItem.find_by(variant_id: iu.variant_id, stock_location_id: stock_location_id)
95
- Spree::StockMovement.create!(stock_item_id: stock_item.id, quantity: 1)
96
- end
97
- end
98
- end
76
+ def generate_expedited_exchange_reimbursements
77
+ return unless Spree::Config[:expedited_exchanges]
99
78
 
100
- Adjustment.create(adjustable: order, amount: compute_amount, label: Spree.t(:rma_credit), order: order, source: self)
101
- order.update!
79
+ items_to_exchange = return_items.select(&:exchange_required?)
80
+ items_to_exchange.each(&:attempt_accept)
81
+ items_to_exchange.select!(&:accepted?)
102
82
 
103
- order.return if inventory_units.all?(&:returned?)
104
- end
83
+ return if items_to_exchange.blank?
105
84
 
106
- def allow_receive?
107
- !inventory_units.empty?
108
- end
85
+ pre_expedited_exchange_hooks.each { |h| h.call items_to_exchange }
86
+
87
+ reimbursement = Reimbursement.new(return_items: items_to_exchange, order: order)
88
+
89
+ if reimbursement.save
90
+ reimbursement.perform!
91
+ else
92
+ errors.add(:base, reimbursement.errors.full_messages)
93
+ raise ActiveRecord::RecordInvalid.new(self)
94
+ end
109
95
 
110
- def force_positive_amount
111
- self.amount = amount.abs
112
96
  end
113
97
  end
114
98
  end
@@ -0,0 +1,7 @@
1
+ module Spree
2
+ class ReturnAuthorizationReason < Spree::Base
3
+ include Spree::NamedType
4
+
5
+ has_many :return_authorizations
6
+ end
7
+ end
@@ -0,0 +1,230 @@
1
+ module Spree
2
+ class ReturnItem < Spree::Base
3
+ COMPLETED_RECEPTION_STATUSES = %w(received given_to_customer)
4
+
5
+ class_attribute :return_eligibility_validator
6
+ self.return_eligibility_validator = ReturnItem::EligibilityValidator::DefaultEligibilityValidator
7
+
8
+ class_attribute :exchange_variant_engine
9
+ self.exchange_variant_engine = ReturnItem::ExchangeVariantEligibility::SameProduct
10
+
11
+ class_attribute :refund_amount_calculator
12
+ self.refund_amount_calculator = Calculator::Returns::DefaultRefundAmount
13
+
14
+ belongs_to :return_authorization, inverse_of: :return_items
15
+ belongs_to :inventory_unit, inverse_of: :return_items
16
+ belongs_to :exchange_variant, class_name: 'Spree::Variant'
17
+ belongs_to :exchange_inventory_unit, class_name: 'Spree::InventoryUnit', inverse_of: :original_return_item
18
+ belongs_to :customer_return, inverse_of: :return_items
19
+ belongs_to :reimbursement, inverse_of: :return_items
20
+ belongs_to :preferred_reimbursement_type, class_name: 'Spree::ReimbursementType'
21
+ belongs_to :override_reimbursement_type, class_name: 'Spree::ReimbursementType'
22
+
23
+ validate :belongs_to_same_customer_order
24
+ validate :validate_acceptance_status_for_reimbursement
25
+ validates :inventory_unit, presence: true
26
+ validate :validate_no_other_completed_return_items, on: :create
27
+
28
+ after_create :cancel_others, unless: :cancelled?
29
+
30
+ scope :awaiting_return, -> { where(reception_status: 'awaiting') }
31
+ scope :not_cancelled, -> { where.not(reception_status: 'cancelled') }
32
+ scope :pending, -> { where(acceptance_status: 'pending') }
33
+ scope :accepted, -> { where(acceptance_status: 'accepted') }
34
+ scope :rejected, -> { where(acceptance_status: 'rejected') }
35
+ scope :manual_intervention_required, -> { where(acceptance_status: 'manual_intervention_required') }
36
+ scope :undecided, -> { where(acceptance_status: %w(pending manual_intervention_required)) }
37
+ scope :decided, -> { where.not(acceptance_status: %w(pending manual_intervention_required)) }
38
+ scope :reimbursed, -> { where.not(reimbursement_id: nil) }
39
+ scope :not_reimbursed, -> { where(reimbursement_id: nil) }
40
+ scope :exchange_requested, -> { where.not(exchange_variant: nil) }
41
+ scope :exchange_processed, -> { where.not(exchange_inventory_unit: nil) }
42
+ scope :exchange_required, -> { exchange_requested.where(exchange_inventory_unit: nil) }
43
+
44
+ serialize :acceptance_status_errors
45
+
46
+ delegate :eligible_for_return?, :requires_manual_intervention?, to: :validator
47
+ delegate :variant, to: :inventory_unit
48
+ delegate :shipment, to: :inventory_unit
49
+
50
+ before_create :set_default_pre_tax_amount, unless: :pre_tax_amount_changed?
51
+ before_save :set_exchange_pre_tax_amount
52
+
53
+ state_machine :reception_status, initial: :awaiting do
54
+ before_transition to: :received, do: :process_inventory_unit!
55
+ after_transition to: :received, do: :attempt_accept
56
+
57
+ event :receive do
58
+ transition to: :received, from: :awaiting
59
+ end
60
+
61
+ event :cancel do
62
+ transition to: :cancelled, from: :awaiting
63
+ end
64
+
65
+ event :give do
66
+ transition to: :given_to_customer, from: :awaiting
67
+ end
68
+ end
69
+
70
+ def reception_completed?
71
+ COMPLETED_RECEPTION_STATUSES.include?(reception_status)
72
+ end
73
+
74
+ state_machine :acceptance_status, initial: :pending do
75
+ event :attempt_accept do
76
+ transition to: :accepted, from: :accepted
77
+ transition to: :accepted, from: :pending, if: ->(return_item) { return_item.eligible_for_return? }
78
+ transition to: :manual_intervention_required, from: :pending, if: ->(return_item) { return_item.requires_manual_intervention? }
79
+ transition to: :rejected, from: :pending
80
+ end
81
+
82
+ # bypasses eligibility checks
83
+ event :accept do
84
+ transition to: :accepted, from: [:accepted, :pending, :manual_intervention_required]
85
+ end
86
+
87
+ # bypasses eligibility checks
88
+ event :reject do
89
+ transition to: :rejected, from: [:accepted, :pending, :manual_intervention_required]
90
+ end
91
+
92
+ # bypasses eligibility checks
93
+ event :require_manual_intervention do
94
+ transition to: :manual_intervention_required, from: [:accepted, :pending, :manual_intervention_required]
95
+ end
96
+
97
+ after_transition any => any, :do => :persist_acceptance_status_errors
98
+ end
99
+
100
+ def self.from_inventory_unit(inventory_unit)
101
+ not_cancelled.find_by(inventory_unit: inventory_unit) ||
102
+ new(inventory_unit: inventory_unit).tap(&:set_default_pre_tax_amount)
103
+ end
104
+
105
+ def exchange_requested?
106
+ exchange_variant.present?
107
+ end
108
+
109
+ def exchange_processed?
110
+ exchange_inventory_unit.present?
111
+ end
112
+
113
+ def exchange_required?
114
+ exchange_requested? && !exchange_processed?
115
+ end
116
+
117
+ def display_pre_tax_amount
118
+ Spree::Money.new(pre_tax_amount, { currency: currency })
119
+ end
120
+
121
+ def total
122
+ pre_tax_amount + additional_tax_total
123
+ end
124
+
125
+ def display_total
126
+ Spree::Money.new(total, { currency: currency })
127
+ end
128
+
129
+ def eligible_exchange_variants
130
+ exchange_variant_engine.eligible_variants(variant)
131
+ end
132
+
133
+ def build_exchange_inventory_unit
134
+ # The inventory unit needs to have the new variant
135
+ # but it also needs to know the original line item
136
+ # for pricing information for if the inventory unit is
137
+ # ever returned. This means that the inventory unit's line_item
138
+ # will have a different variant than the inventory unit itself
139
+ super(variant: exchange_variant, line_item: inventory_unit.line_item, order: inventory_unit.order) if exchange_required?
140
+ end
141
+
142
+ def exchange_shipment
143
+ exchange_inventory_unit.try(:shipment)
144
+ end
145
+
146
+ def set_default_pre_tax_amount
147
+ self.pre_tax_amount = refund_amount_calculator.new.compute(self)
148
+ end
149
+
150
+ private
151
+
152
+ def persist_acceptance_status_errors
153
+ self.update_attributes(acceptance_status_errors: validator.errors)
154
+ end
155
+
156
+ def stock_item
157
+ return unless customer_return
158
+
159
+ Spree::StockItem.find_by({
160
+ variant_id: inventory_unit.variant_id,
161
+ stock_location_id: customer_return.stock_location_id,
162
+ })
163
+ end
164
+
165
+ def currency
166
+ return_authorization.try(:currency) || Spree::Config[:currency]
167
+ end
168
+
169
+ def process_inventory_unit!
170
+ inventory_unit.return!
171
+
172
+ Spree::StockMovement.create!(stock_item_id: stock_item.id, quantity: 1) if should_restock?
173
+ end
174
+
175
+ # This logic is also present in the customer return. The reason for the
176
+ # duplication and not having a validates_associated on the customer_return
177
+ # is that it would lead to duplicate error messages for the customer return.
178
+ # Not specifying a stock location for example would add an error message about
179
+ # the mandatory field when validating the customer return and again when saving
180
+ # the associated return items.
181
+ def belongs_to_same_customer_order
182
+ return unless customer_return && inventory_unit
183
+
184
+ if customer_return.order_id != inventory_unit.order_id
185
+ errors.add(:base, Spree.t(:return_items_cannot_be_associated_with_multiple_orders))
186
+ end
187
+ end
188
+
189
+ def validator
190
+ @validator ||= return_eligibility_validator.new(self)
191
+ end
192
+
193
+ def validate_acceptance_status_for_reimbursement
194
+ if reimbursement && !accepted?
195
+ errors.add(:reimbursement, :cannot_be_associated_unless_accepted)
196
+ end
197
+ end
198
+
199
+ def set_exchange_pre_tax_amount
200
+ self.pre_tax_amount = 0.0.to_d if exchange_requested?
201
+ end
202
+
203
+ def validate_no_other_completed_return_items
204
+ other_return_item = Spree::ReturnItem.where({
205
+ inventory_unit_id: inventory_unit_id,
206
+ reception_status: COMPLETED_RECEPTION_STATUSES,
207
+ }).first
208
+
209
+ if other_return_item
210
+ errors.add(:inventory_unit, :other_completed_return_item_exists, {
211
+ inventory_unit_id: inventory_unit_id,
212
+ return_item_id: other_return_item.id,
213
+ })
214
+ end
215
+ end
216
+
217
+ def cancel_others
218
+ Spree::ReturnItem.where(inventory_unit_id: inventory_unit_id)
219
+ .where.not(id: id)
220
+ .where.not(reception_status: 'cancelled')
221
+ .each do |return_item|
222
+ return_item.cancel!
223
+ end
224
+ end
225
+
226
+ def should_restock?
227
+ variant.should_track_inventory? && stock_item && Spree::Config[:restock_inventory]
228
+ end
229
+ end
230
+ end