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
@@ -20,6 +20,16 @@ module Spree
20
20
  raise 'eligible? should be implemented in a sub-class of Spree::PromotionRule'
21
21
  end
22
22
 
23
+ # This states if a promotion can be applied to the specified line item
24
+ # It is true by default, but can be overridden by promotion rules to provide conditions
25
+ def actionable?(line_item)
26
+ true
27
+ end
28
+
29
+ def eligibility_errors
30
+ @eligibility_errors ||= ActiveModel::Errors.new(self)
31
+ end
32
+
23
33
  private
24
34
  def unique_per_promotion
25
35
  if Spree::PromotionRule.exists?(promotion_id: promotion_id, type: self.class.name)
@@ -27,5 +37,8 @@ module Spree
27
37
  end
28
38
  end
29
39
 
40
+ def eligibility_error_message(key, options = {})
41
+ Spree.t(key, Hash[scope: [:eligibility_errors, :messages]].merge(options))
42
+ end
30
43
  end
31
44
  end
@@ -11,8 +11,6 @@ module Spree
11
11
 
12
12
  after_touch :touch_all_products
13
13
 
14
- self.whitelisted_ransackable_attributes = ['presentation']
15
-
16
14
  def self.find_all_by_prototype(prototype)
17
15
  id = prototype
18
16
  id = prototype.id if prototype.class == Prototype
@@ -23,7 +21,7 @@ module Spree
23
21
  private
24
22
 
25
23
  def touch_all_products
26
- products.each(&:touch)
24
+ products.update_all(updated_at: Time.current)
27
25
  end
28
26
  end
29
27
  end
@@ -0,0 +1,91 @@
1
+ module Spree
2
+ class Refund < Spree::Base
3
+ belongs_to :payment, inverse_of: :refunds
4
+ belongs_to :reason, class_name: 'Spree::RefundReason', foreign_key: :refund_reason_id
5
+ belongs_to :reimbursement, inverse_of: :refunds
6
+
7
+ has_many :log_entries, as: :source
8
+
9
+ validates :payment, presence: true
10
+ validates :reason, presence: true
11
+ validates :transaction_id, presence: true, on: :update # can't require this on create because the before_create needs to run first
12
+ validates :amount, presence: true, numericality: {greater_than: 0}
13
+
14
+ validate :check_payment_environment, on: :create, if: :payment
15
+ validate :amount_is_less_than_or_equal_to_allowed_amount, on: :create
16
+
17
+ after_create :perform!
18
+ after_create :create_log_entry
19
+
20
+ scope :non_reimbursement, -> { where(reimbursement_id: nil) }
21
+
22
+ def money
23
+ Spree::Money.new(amount, { currency: payment.currency })
24
+ end
25
+ alias display_amount money
26
+
27
+ class << self
28
+ def total_amount_reimbursed_for(reimbursement)
29
+ reimbursement.refunds.to_a.sum(&:amount)
30
+ end
31
+ end
32
+
33
+ def description
34
+ payment.payment_method.name
35
+ end
36
+
37
+ private
38
+
39
+ # attempts to perform the refund.
40
+ # raises an error if the refund fails.
41
+ def perform!
42
+ return true if transaction_id.present?
43
+
44
+ credit_cents = Spree::Money.new(amount.to_f, currency: payment.currency).money.cents
45
+
46
+ @response = process!(credit_cents)
47
+
48
+ self.transaction_id = @response.authorization
49
+ update_columns(transaction_id: transaction_id)
50
+ end
51
+
52
+ # return an activemerchant response object if successful or else raise an error
53
+ def process!(credit_cents)
54
+ response = if payment.payment_method.payment_profiles_supported?
55
+ payment.payment_method.credit(credit_cents, payment.source, payment.transaction_id, {originator: self})
56
+ else
57
+ payment.payment_method.credit(credit_cents, payment.transaction_id, {originator: self})
58
+ end
59
+
60
+ if !response.success?
61
+ logger.error(Spree.t(:gateway_error) + " #{response.to_yaml}")
62
+ text = response.params['message'] || response.params['response_reason_text'] || response.message
63
+ raise Core::GatewayError.new(text)
64
+ end
65
+
66
+ response
67
+ rescue ActiveMerchant::ConnectionError => e
68
+ logger.error(Spree.t(:gateway_error) + " #{e.inspect}")
69
+ raise Core::GatewayError.new(Spree.t(:unable_to_connect_to_gateway))
70
+ end
71
+
72
+ # Saftey check to make sure we're not accidentally performing operations on a live gateway.
73
+ # Ex. When testing in staging environment with a copy of production data.
74
+ def check_payment_environment
75
+ if payment.payment_method.environment != Rails.env
76
+ message = Spree.t(:gateway_config_unavailable) + " - #{Rails.env}"
77
+ errors.add(:base, message)
78
+ end
79
+ end
80
+
81
+ def create_log_entry
82
+ log_entries.create!(details: @response.to_yaml)
83
+ end
84
+
85
+ def amount_is_less_than_or_equal_to_allowed_amount
86
+ if amount > payment.credit_allowed
87
+ errors.add(:amount, :greater_than_allowed)
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,13 @@
1
+ module Spree
2
+ class RefundReason < Spree::Base
3
+ include Spree::NamedType
4
+
5
+ RETURN_PROCESSING_REASON = 'Return processing'
6
+
7
+ has_many :refunds
8
+
9
+ def self.return_processing_reason
10
+ find_by!(name: RETURN_PROCESSING_REASON, mutable: false)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,148 @@
1
+ module Spree
2
+ class Reimbursement < Spree::Base
3
+ class IncompleteReimbursement < StandardError; end
4
+
5
+ belongs_to :order, inverse_of: :reimbursements
6
+ belongs_to :customer_return, inverse_of: :reimbursements, touch: true
7
+
8
+ has_many :refunds, inverse_of: :reimbursement
9
+ has_many :credits, inverse_of: :reimbursement, class_name: 'Spree::Reimbursement::Credit'
10
+
11
+ has_many :return_items, inverse_of: :reimbursement
12
+
13
+ validates :order, presence: true
14
+ validate :validate_return_items_belong_to_same_order
15
+
16
+ accepts_nested_attributes_for :return_items, allow_destroy: true
17
+
18
+ before_create :generate_number
19
+
20
+ # The reimbursement_tax_calculator property should be set to an object that responds to "call"
21
+ # and accepts a reimbursement object. Invoking "call" should update the tax fields on the
22
+ # associated ReturnItems.
23
+ # This allows a store to easily integrate with third party tax services.
24
+ class_attribute :reimbursement_tax_calculator
25
+ self.reimbursement_tax_calculator = ReimbursementTaxCalculator
26
+ # A separate attribute here allows you to use a more performant calculator for estimates
27
+ # and a different one (e.g. one that hits a 3rd party API) for the final caluclations.
28
+ class_attribute :reimbursement_simulator_tax_calculator
29
+ self.reimbursement_simulator_tax_calculator = ReimbursementTaxCalculator
30
+
31
+ # The reimbursement_models property should contain an array of all models that provide
32
+ # reimbursements.
33
+ # This allows a store to incorporate custom reimbursement methods that Spree doesn't know about.
34
+ # Each model must implement a "total_amount_reimbursed_for" method.
35
+ # Example:
36
+ # Refund.total_amount_reimbursed_for(reimbursement)
37
+ # See the `reimbursement_generator` property regarding the generation of custom reimbursements.
38
+ class_attribute :reimbursement_models
39
+ self.reimbursement_models = [Refund]
40
+
41
+ # The reimbursement_performer property should be set to an object that responds to the following methods:
42
+ # - #perform
43
+ # - #simulate
44
+ # see ReimbursementPerformer for details.
45
+ # This allows a store to customize their reimbursement methods and logic.
46
+ class_attribute :reimbursement_performer
47
+ self.reimbursement_performer = ReimbursementPerformer
48
+
49
+ # These are called if the call to "reimburse!" succeeds.
50
+ class_attribute :reimbursement_success_hooks
51
+ self.reimbursement_success_hooks = []
52
+
53
+ # These are called if the call to "reimburse!" fails.
54
+ class_attribute :reimbursement_failure_hooks
55
+ self.reimbursement_failure_hooks = []
56
+
57
+ state_machine :reimbursement_status, initial: :pending do
58
+ after_transition to: :reimbursed, do: :send_reimbursement_email
59
+
60
+ event :errored do
61
+ transition to: :errored, from: :pending
62
+ end
63
+
64
+ event :reimbursed do
65
+ transition to: :reimbursed, from: [:pending, :errored]
66
+ end
67
+
68
+ end
69
+
70
+ class << self
71
+ def build_from_customer_return(customer_return)
72
+ order = customer_return.order
73
+ order.reimbursements.build({
74
+ customer_return: customer_return,
75
+ return_items: customer_return.return_items.accepted.not_reimbursed,
76
+ })
77
+ end
78
+ end
79
+
80
+ def display_total
81
+ Spree::Money.new(total, { currency: order.currency })
82
+ end
83
+
84
+ def calculated_total
85
+ # rounding down to handle edge cases for consecutive partial returns where rounding
86
+ # might cause us to try to reimburse more than was originally billed
87
+ return_items.to_a.sum(&:total).to_d.round(2, :down)
88
+ end
89
+
90
+ def paid_amount
91
+ reimbursement_models.sum do |model|
92
+ model.total_amount_reimbursed_for(self)
93
+ end
94
+ end
95
+
96
+ def unpaid_amount
97
+ total - paid_amount
98
+ end
99
+
100
+ def perform!
101
+ reimbursement_tax_calculator.call(self)
102
+ reload
103
+ update!(total: calculated_total)
104
+
105
+ reimbursement_performer.perform(self)
106
+
107
+ if unpaid_amount.zero?
108
+ reimbursed!
109
+ reimbursement_success_hooks.each { |h| h.call self }
110
+ else
111
+ errored!
112
+ reimbursement_failure_hooks.each { |h| h.call self }
113
+ raise IncompleteReimbursement, Spree.t("validation.unpaid_amount_not_zero", amount: unpaid_amount)
114
+ end
115
+ end
116
+
117
+ def simulate
118
+ reimbursement_simulator_tax_calculator.call(self)
119
+ reload
120
+ update!(total: calculated_total)
121
+
122
+ reimbursement_performer.simulate(self)
123
+ end
124
+
125
+ def return_items_requiring_exchange
126
+ return_items.select(&:exchange_required?)
127
+ end
128
+
129
+ private
130
+
131
+ def generate_number
132
+ self.number ||= loop do
133
+ random = "RI#{Array.new(9){rand(9)}.join}"
134
+ break random unless self.class.exists?(number: random)
135
+ end
136
+ end
137
+
138
+ def validate_return_items_belong_to_same_order
139
+ if return_items.any? { |ri| ri.inventory_unit.order_id != order_id }
140
+ errors.add(:base, :return_items_order_id_does_not_match)
141
+ end
142
+ end
143
+
144
+ def send_reimbursement_email
145
+ Spree::ReimbursementMailer.reimbursement_email(self.id).deliver
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,25 @@
1
+ module Spree
2
+ class Reimbursement::Credit < Spree::Base
3
+ class_attribute :default_creditable_class
4
+ self.default_creditable_class = nil
5
+
6
+ belongs_to :reimbursement, inverse_of: :credits
7
+ belongs_to :creditable, polymorphic: true
8
+
9
+ validates :creditable, presence: true
10
+
11
+ class << self
12
+ def total_amount_reimbursed_for(reimbursement)
13
+ reimbursement.credits.to_a.sum(&:amount)
14
+ end
15
+ end
16
+
17
+ def description
18
+ creditable.class.name.demodulize
19
+ end
20
+
21
+ def display_amount
22
+ Spree::Money.new(amount, { currency: creditable.try(:currency) || "USD" })
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,56 @@
1
+ module Spree
2
+ class Reimbursement::ReimbursementTypeEngine
3
+ include Spree::Reimbursement::ReimbursementTypeValidator
4
+
5
+ class_attribute :refund_time_constraint
6
+ self.refund_time_constraint = 90.days
7
+
8
+ class_attribute :default_reimbursement_type
9
+ self.default_reimbursement_type = Spree::ReimbursementType::OriginalPayment
10
+
11
+ class_attribute :expired_reimbursement_type
12
+ self.expired_reimbursement_type = Spree::ReimbursementType::OriginalPayment
13
+
14
+ class_attribute :exchange_reimbursement_type
15
+ self.exchange_reimbursement_type = Spree::ReimbursementType::Exchange
16
+
17
+ def initialize(return_items)
18
+ @return_items = return_items
19
+ @reimbursement_type_hash = Hash.new {|h,k| h[k] = Array.new }
20
+ end
21
+
22
+ def calculate_reimbursement_types
23
+ @return_items.each do |return_item|
24
+ reimbursement_type = calculate_reimbursement_type(return_item)
25
+ add_reimbursement_type(return_item, reimbursement_type)
26
+ end
27
+
28
+ @reimbursement_type_hash
29
+ end
30
+
31
+ private
32
+
33
+ def calculate_reimbursement_type(return_item)
34
+ if return_item.exchange_required?
35
+ exchange_reimbursement_type
36
+ elsif return_item.override_reimbursement_type.present?
37
+ return_item.override_reimbursement_type.class
38
+ elsif return_item.preferred_reimbursement_type.present?
39
+ if valid_preferred_reimbursement_type?(return_item)
40
+ return_item.preferred_reimbursement_type.class
41
+ else
42
+ nil
43
+ end
44
+ elsif past_reimbursable_time_period?(return_item)
45
+ expired_reimbursement_type
46
+ else
47
+ default_reimbursement_type
48
+ end
49
+ end
50
+
51
+ def add_reimbursement_type(return_item, reimbursement_type)
52
+ return unless reimbursement_type
53
+ @reimbursement_type_hash[reimbursement_type] << return_item
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,12 @@
1
+ module Spree
2
+ module Reimbursement::ReimbursementTypeValidator
3
+ def valid_preferred_reimbursement_type?(return_item)
4
+ !past_reimbursable_time_period?(return_item) || return_item.preferred_reimbursement_type == expired_reimbursement_type
5
+ end
6
+
7
+ def past_reimbursable_time_period?(return_item)
8
+ shipped_at = return_item.inventory_unit.shipment.shipped_at
9
+ shipped_at && shipped_at < refund_time_constraint.ago
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,43 @@
1
+ module Spree
2
+
3
+ class ReimbursementPerformer
4
+
5
+ class << self
6
+ class_attribute :reimbursement_type_engine
7
+ self.reimbursement_type_engine = Spree::Reimbursement::ReimbursementTypeEngine
8
+
9
+ # Simulate performing the reimbursement without actually saving anything or refunding money, etc.
10
+ # This must return an array of objects that respond to the following methods:
11
+ # - #description
12
+ # - #display_amount
13
+ # so they can be displayed in the Admin UI appropriately.
14
+ def simulate(reimbursement)
15
+ execute(reimbursement, true)
16
+ end
17
+
18
+ # Actually perform the reimbursement
19
+ def perform(reimbursement)
20
+ execute(reimbursement, false)
21
+ end
22
+
23
+ private
24
+
25
+ def execute(reimbursement, simulate)
26
+ reimbursement_type_hash = calculate_reimbursement_types(reimbursement)
27
+
28
+ reimbursement_type_hash.flat_map do |reimbursement_type, return_items|
29
+ reimbursement_type.reimburse(reimbursement, return_items, simulate)
30
+ end
31
+ end
32
+
33
+ def calculate_reimbursement_types(reimbursement)
34
+ # Engine returns hash of preferred reimbursement types pointing at return items
35
+ # {Spree::ReimbursementType::OriginalPayment => [ReturnItem, ...], Spree::ReimbursementType::Exchange => [ReturnItem, ...]}
36
+ reimbursement_type_engine.new(reimbursement.return_items).calculate_reimbursement_types
37
+ end
38
+
39
+ end
40
+
41
+ end
42
+
43
+ end
@@ -0,0 +1,38 @@
1
+ module Spree
2
+
3
+ # Tax calculation is broken out at this level to allow easy integration with 3rd party
4
+ # taxation systems. Those systems are usually geared toward calculating all items at once
5
+ # rather than one at a time.
6
+ #
7
+ # To use an alternative tax calculator do this:
8
+ # Spree::ReturnAuthorization.reimbursement_tax_calculator = calculator_object
9
+ # where `calculator_object` is an object that responds to "call" and accepts a reimbursement object
10
+
11
+ class ReimbursementTaxCalculator
12
+
13
+ class << self
14
+
15
+ def call(reimbursement)
16
+ reimbursement.return_items.includes(:inventory_unit).each do |return_item|
17
+ set_tax!(return_item)
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def set_tax!(return_item)
24
+ percent_of_tax = (return_item.pre_tax_amount <= 0) ? 0 : return_item.pre_tax_amount / Spree::ReturnItem.refund_amount_calculator.new.compute(return_item)
25
+
26
+ additional_tax_total = percent_of_tax * return_item.inventory_unit.additional_tax_total
27
+ included_tax_total = percent_of_tax * return_item.inventory_unit.included_tax_total
28
+
29
+ return_item.update_attributes!({
30
+ additional_tax_total: additional_tax_total,
31
+ included_tax_total: included_tax_total,
32
+ })
33
+ end
34
+ end
35
+
36
+ end
37
+
38
+ end