spree_core 2.3.13 → 2.4.0.rc1

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