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.
- checksums.yaml +4 -4
- data/app/assets/javascripts/spree.js.coffee.erb +1 -5
- data/app/helpers/spree/base_helper.rb +22 -11
- data/app/helpers/spree/products_helper.rb +8 -7
- data/app/mailers/spree/base_mailer.rb +1 -0
- data/app/mailers/spree/reimbursement_mailer.rb +10 -0
- data/app/mailers/spree/test_mailer.rb +2 -3
- data/app/models/concerns/spree/adjustment_source.rb +24 -0
- data/app/models/concerns/spree/calculated_adjustments.rb +33 -0
- data/app/models/concerns/spree/named_type.rb +12 -0
- data/app/models/concerns/spree/user_address.rb +30 -0
- data/app/models/concerns/spree/user_payment_source.rb +19 -0
- data/app/models/spree/address.rb +13 -6
- data/app/models/spree/adjustment.rb +5 -5
- data/app/models/spree/app_configuration.rb +8 -4
- data/app/models/spree/asset.rb +1 -1
- data/app/models/spree/base.rb +0 -3
- data/app/models/spree/calculator/flat_rate.rb +1 -5
- data/app/models/spree/calculator/returns/default_refund_amount.rb +36 -0
- data/app/models/spree/classification.rb +1 -1
- data/app/models/spree/credit_card.rb +18 -22
- data/app/models/spree/customer_return.rb +70 -0
- data/app/models/spree/exchange.rb +42 -0
- data/app/models/spree/gateway/bogus.rb +3 -3
- data/app/models/spree/image.rb +1 -1
- data/app/models/spree/inventory_unit.rb +32 -8
- data/app/models/spree/item_adjustments.rb +7 -11
- data/app/models/spree/legacy_user.rb +2 -2
- data/app/models/spree/line_item.rb +25 -12
- data/app/models/spree/option_type.rb +1 -1
- data/app/models/spree/option_value.rb +1 -8
- data/app/models/spree/order.rb +163 -145
- data/app/models/spree/order/checkout.rb +35 -23
- data/app/models/spree/order/payments.rb +66 -0
- data/app/models/spree/order_contents.rb +34 -24
- data/app/models/spree/order_populator.rb +6 -4
- data/app/models/spree/order_updater.rb +10 -1
- data/app/models/spree/payment.rb +19 -16
- data/app/models/spree/payment/processing.rb +40 -72
- data/app/models/spree/payment_method.rb +1 -1
- data/app/models/spree/payment_method/check.rb +0 -2
- data/app/models/spree/preference.rb +1 -1
- data/app/models/spree/preferences/preferable.rb +20 -0
- data/app/models/spree/price.rb +13 -3
- data/app/models/spree/product.rb +24 -29
- data/app/models/spree/product_property.rb +0 -2
- data/app/models/spree/promotion.rb +66 -24
- data/app/models/spree/promotion/actions/create_adjustment.rb +2 -2
- data/app/models/spree/promotion/actions/create_item_adjustments.rb +15 -11
- data/app/models/spree/promotion/actions/create_line_items.rb +2 -12
- data/app/models/spree/promotion/rules/first_order.rb +6 -2
- data/app/models/spree/promotion/rules/item_total.rb +42 -4
- data/app/models/spree/promotion/rules/one_use_per_user.rb +24 -0
- data/app/models/spree/promotion/rules/product.rb +13 -11
- data/app/models/spree/promotion/rules/taxon.rb +61 -0
- data/app/models/spree/promotion/rules/user.rb +1 -1
- data/app/models/spree/promotion/rules/user_logged_in.rb +4 -1
- data/app/models/spree/promotion_category.rb +6 -0
- data/app/models/spree/promotion_handler/cart.rb +14 -18
- data/app/models/spree/promotion_handler/coupon.rb +25 -16
- data/app/models/spree/promotion_rule.rb +13 -0
- data/app/models/spree/property.rb +1 -3
- data/app/models/spree/refund.rb +91 -0
- data/app/models/spree/refund_reason.rb +13 -0
- data/app/models/spree/reimbursement.rb +148 -0
- data/app/models/spree/reimbursement/credit.rb +25 -0
- data/app/models/spree/reimbursement/reimbursement_type_engine.rb +56 -0
- data/app/models/spree/reimbursement/reimbursement_type_validator.rb +12 -0
- data/app/models/spree/reimbursement_performer.rb +43 -0
- data/app/models/spree/reimbursement_tax_calculator.rb +38 -0
- data/app/models/spree/reimbursement_type.rb +16 -0
- data/app/models/spree/reimbursement_type/credit.rb +13 -0
- data/app/models/spree/reimbursement_type/exchange.rb +9 -0
- data/app/models/spree/reimbursement_type/original_payment.rb +13 -0
- data/app/models/spree/reimbursement_type/reimbursement_helpers.rb +50 -0
- data/app/models/spree/return_authorization.rb +52 -68
- data/app/models/spree/return_authorization_reason.rb +7 -0
- data/app/models/spree/return_item.rb +230 -0
- data/app/models/spree/return_item/default_eligibility_validator.rb +27 -0
- data/app/models/spree/return_item/eligibility_validator/base_validator.rb +24 -0
- data/app/models/spree/return_item/eligibility_validator/rma_required.rb +17 -0
- data/app/models/spree/return_item/eligibility_validator/time_since_purchase.rb +16 -0
- data/app/models/spree/return_item/exchange_variant_eligibility/same_option_value.rb +34 -0
- data/app/models/spree/return_item/exchange_variant_eligibility/same_product.rb +10 -0
- data/app/models/spree/returns_calculator.rb +8 -0
- data/app/models/spree/shipment.rb +209 -154
- data/app/models/spree/shipment_handler.rb +43 -0
- data/app/models/spree/shipping_calculator.rb +1 -1
- data/app/models/spree/shipping_category.rb +2 -2
- data/app/models/spree/shipping_method.rb +1 -1
- data/app/models/spree/shipping_method_category.rb +1 -1
- data/app/models/spree/shipping_rate.rb +4 -0
- data/app/models/spree/stock/adjuster.rb +10 -11
- data/app/models/spree/stock/availability_validator.rb +6 -10
- data/app/models/spree/stock/content_item.rb +48 -0
- data/app/models/spree/stock/coordinator.rb +14 -7
- data/app/models/spree/stock/estimator.rb +1 -1
- data/app/models/spree/stock/inventory_unit_builder.rb +21 -0
- data/app/models/spree/stock/package.rb +38 -51
- data/app/models/spree/stock/packer.rb +13 -11
- data/app/models/spree/stock/prioritizer.rb +7 -7
- data/app/models/spree/stock/splitter/base.rb +2 -2
- data/app/models/spree/stock_item.rb +6 -9
- data/app/models/spree/stock_location.rb +17 -25
- data/app/models/spree/stock_movement.rb +1 -8
- data/app/models/spree/stock_transfer.rb +0 -2
- data/app/models/spree/store.rb +1 -1
- data/app/models/spree/tax_category.rb +2 -2
- data/app/models/spree/tax_rate.rb +16 -22
- data/app/models/spree/taxon.rb +1 -1
- data/app/models/spree/variant.rb +45 -23
- data/app/models/spree/zone.rb +17 -17
- data/app/models/spree/zone_member.rb +1 -1
- data/app/views/layouts/spree/base_mailer.html.erb +784 -0
- data/app/views/spree/order_mailer/cancel_email.html.erb +45 -0
- data/app/views/spree/order_mailer/cancel_email.text.erb +2 -2
- data/app/views/spree/order_mailer/confirm_email.html.erb +84 -0
- data/app/views/spree/order_mailer/confirm_email.text.erb +2 -2
- data/app/views/spree/reimbursement_mailer/reimbursement_email.text.erb +22 -0
- data/app/views/spree/shared/_base_mailer_footer.html.erb +20 -0
- data/app/views/spree/shared/_base_mailer_header.html.erb +31 -0
- data/app/views/spree/shipment_mailer/shipped_email.html.erb +34 -0
- data/app/views/spree/test_mailer/test_email.html.erb +40 -0
- data/app/views/spree/test_mailer/test_email.text.erb +2 -2
- data/config/initializers/friendly_id.rb +88 -0
- data/config/initializers/premailer_assets.rb +1 -0
- data/config/initializers/user_class_extensions.rb +9 -22
- data/config/locales/en.yml +180 -12
- data/db/default/spree/states.rb +73 -55
- data/db/migrate/20130213191427_create_default_stock.rb +1 -0
- data/db/migrate/20130807024301_upgrade_adjustments.rb +4 -5
- data/db/migrate/20140309033438_create_store_from_preferences.rb +0 -7
- data/db/migrate/20140318191500_create_spree_taxons_promotion_rules.rb +8 -0
- data/db/migrate/20140530024945_move_order_token_from_tokenized_permission.rb +1 -1
- data/db/migrate/20140601011216_set_shipment_total_for_users_upgrading.rb +5 -3
- data/db/migrate/20140625214618_create_spree_refunds.rb +12 -0
- data/db/migrate/20140702140656_create_spree_return_authorization_inventory_unit.rb +12 -0
- data/db/migrate/20140707125621_rename_return_authorization_inventory_unit_to_return_items.rb +5 -0
- data/db/migrate/20140709160534_backfill_line_item_pre_tax_amount.rb +10 -0
- data/db/migrate/20140710041921_recreate_spree_return_authorizations.rb +55 -0
- data/db/migrate/20140710181204_add_amount_fields_to_return_items.rb +7 -0
- data/db/migrate/20140710190048_drop_return_authorization_amount.rb +5 -0
- data/db/migrate/20140713140455_create_spree_return_authorization_reasons.rb +28 -0
- data/db/migrate/20140713140527_create_spree_refund_reasons.rb +14 -0
- data/db/migrate/20140713142214_rename_return_authorization_reason.rb +5 -0
- data/db/migrate/20140715182625_create_spree_promotion_categories.rb +11 -0
- data/db/migrate/20140716204111_drop_received_at_on_return_items.rb +9 -0
- data/db/migrate/20140716212330_add_reception_and_acceptance_status_to_return_items.rb +6 -0
- data/db/migrate/20140717155155_create_default_refund_reason.rb +9 -0
- data/db/migrate/20140717185932_add_default_to_spree_stock_locations.rb +5 -0
- data/db/migrate/20140718133010_create_spree_customer_returns.rb +9 -0
- data/db/migrate/20140718133349_add_customer_return_id_to_return_item.rb +6 -0
- data/db/migrate/20140718195325_create_friendly_id_slugs.rb +15 -0
- data/db/migrate/20140723004419_rename_spree_refund_return_authorization_id.rb +5 -0
- data/db/migrate/20140723152808_increase_return_item_pre_tax_amount_precision.rb +13 -0
- data/db/migrate/20140723214541_copy_product_slugs_to_slug_history.rb +15 -0
- data/db/migrate/20140725131539_create_spree_reimbursements.rb +21 -0
- data/db/migrate/20140728225422_add_promotionable_to_spree_products.rb +5 -0
- data/db/migrate/20140729133613_add_exchange_inventory_unit_foreign_keys.rb +7 -0
- data/db/migrate/20140730155938_add_acceptance_status_errors_to_return_item.rb +5 -0
- data/db/migrate/20140731150017_create_spree_reimbursement_types.rb +20 -0
- data/db/migrate/20140805171035_add_default_to_spree_credit_cards.rb +5 -0
- data/db/migrate/20140805171219_make_existing_credit_cards_default.rb +10 -0
- data/db/migrate/20140806144901_add_type_to_reimbursement_type.rb +9 -0
- data/db/migrate/20140808184039_create_spree_reimbursement_credits.rb +10 -0
- data/db/migrate/20140827170513_add_meta_title_to_spree_products.rb +7 -0
- data/db/migrate/20140924164824_add_code_to_spree_tax_categories.rb +5 -0
- data/db/migrate/20141002191113_add_code_to_spree_shipping_methods.rb +5 -0
- data/db/migrate/20141007230328_add_cancel_audit_fields_to_spree_orders.rb +6 -0
- data/db/migrate/20141009204607_add_store_id_to_orders.rb +8 -0
- data/lib/generators/spree/install/install_generator.rb +7 -3
- data/lib/spree/core.rb +11 -10
- data/lib/spree/core/controller_helpers/common.rb +3 -10
- data/lib/spree/core/controller_helpers/order.rb +15 -12
- data/lib/spree/core/controller_helpers/strong_parameters.rb +9 -9
- data/lib/spree/core/engine.rb +8 -1
- data/lib/spree/core/importer/order.rb +5 -17
- data/lib/spree/core/search/base.rb +1 -1
- data/lib/spree/core/validators/email.rb +1 -1
- data/lib/spree/core/version.rb +1 -1
- data/lib/spree/instrumentation.rb +41 -0
- data/lib/spree/migrations.rb +3 -7
- data/lib/spree/money.rb +2 -2
- data/lib/spree/permitted_attributes.rb +10 -9
- data/lib/spree/testing_support/ability_helpers.rb +25 -25
- data/lib/spree/testing_support/authorization_helpers.rb +3 -5
- data/lib/spree/testing_support/capybara_ext.rb +2 -2
- data/lib/spree/testing_support/factories/calculator_factory.rb +0 -8
- data/lib/spree/testing_support/factories/credit_card_factory.rb +1 -1
- data/lib/spree/testing_support/factories/customer_return_factory.rb +31 -0
- data/lib/spree/testing_support/factories/inventory_unit_factory.rb +1 -0
- data/lib/spree/testing_support/factories/line_item_factory.rb +2 -1
- data/lib/spree/testing_support/factories/order_factory.rb +11 -6
- data/lib/spree/testing_support/factories/promotion_category_factory.rb +6 -0
- data/lib/spree/testing_support/factories/promotion_factory.rb +9 -7
- data/lib/spree/testing_support/factories/refund_factory.rb +14 -0
- data/lib/spree/testing_support/factories/reimbursement_factory.rb +16 -0
- data/lib/spree/testing_support/factories/reimbursement_type_factory.rb +7 -0
- data/lib/spree/testing_support/factories/return_authorization_factory.rb +9 -3
- data/lib/spree/testing_support/factories/return_item_factory.rb +10 -0
- data/lib/spree/testing_support/factories/shipment_factory.rb +1 -0
- data/lib/spree/testing_support/factories/shipping_method_factory.rb +3 -2
- data/lib/spree/testing_support/factories/stock_factory.rb +12 -11
- data/lib/spree/testing_support/flash.rb +2 -2
- data/lib/tasks/email.rake +7 -0
- data/lib/tasks/exchanges.rake +70 -0
- data/vendor/assets/javascripts/jquery.validate/localization/messages_et.js +23 -0
- data/vendor/assets/javascripts/jquery.validate/localization/messages_eu.js +25 -0
- data/vendor/assets/javascripts/jquery.validate/localization/messages_hr.js +25 -0
- data/vendor/assets/javascripts/jquery.validate/localization/messages_ka.js +25 -0
- data/vendor/assets/javascripts/jquery.validate/localization/messages_ko.js +25 -0
- data/vendor/assets/javascripts/jquery.validate/localization/messages_my.js +25 -0
- data/vendor/assets/javascripts/jquery.validate/localization/messages_pt_BR.js +26 -0
- data/vendor/assets/javascripts/jquery.validate/localization/messages_pt_PT.js +26 -0
- data/vendor/assets/javascripts/jquery.validate/localization/messages_sl.js +25 -0
- data/vendor/assets/javascripts/jquery.validate/localization/messages_sv.js +23 -0
- data/vendor/assets/javascripts/jquery.validate/localization/messages_uk.js +25 -0
- data/vendor/assets/javascripts/jquery.validate/localization/messages_zh.js +25 -0
- data/vendor/assets/javascripts/jquery.validate/localization/messages_zh_TW.js +26 -0
- metadata +163 -47
- data/app/models/concerns/spree/ransackable_attributes.rb +0 -19
- data/db/migrate/20141021194502_add_state_lock_version_to_order.rb +0 -5
- data/db/migrate/20141101231208_fix_adjustment_order_presence.rb +0 -13
- data/db/migrate/20141105213646_update_classifications_positions.rb +0 -9
- data/db/migrate/20141120135441_add_guest_token_index_to_spree_orders.rb +0 -5
- data/db/migrate/20150515211137_fix_adjustment_order_id.rb +0 -70
- data/lib/spree/core/adjustment_source.rb +0 -26
- data/lib/spree/core/calculated_adjustments.rb +0 -35
- data/lib/spree/core/controller_helpers.rb +0 -20
- data/lib/spree/core/user_address.rb +0 -32
- data/lib/spree/core/user_payment_source.rb +0 -20
- 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'
|
|
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
|
-
|
|
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 :
|
|
12
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
32
|
-
|
|
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
|
|
40
|
-
|
|
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
|
|
68
|
-
order.
|
|
49
|
+
def refundable_amount
|
|
50
|
+
order.pre_tax_item_amount + order.promo_total
|
|
69
51
|
end
|
|
70
52
|
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
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 = "
|
|
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
|
|
90
|
-
|
|
91
|
-
|
|
72
|
+
def cancel_return_items
|
|
73
|
+
return_items.each(&:cancel!)
|
|
74
|
+
end
|
|
92
75
|
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
101
|
-
|
|
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
|
-
|
|
104
|
-
end
|
|
83
|
+
return if items_to_exchange.blank?
|
|
105
84
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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,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
|