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.
- 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
|