-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
/
Copy pathadjustment.rb
168 lines (145 loc) · 6.48 KB
/
adjustment.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# frozen_string_literal: true
module Spree
# Adjustments represent a change to the +item_total+ of an Order. Each
# adjustment has an +amount+ that can be either positive or negative.
#
# Adjustments can be "opened" or "closed". Once an adjustment is closed, it
# will not be automatically updated.
#
# == Boolean attributes
#
# 1. *eligible?*
#
# This boolean attributes stores whether this adjustment is currently
# eligible for its order. Only eligible adjustments count towards the
# order's adjustment total. This allows an adjustment to be preserved if
# it becomes ineligible so it might be reinstated.
class Adjustment < Spree::Base
belongs_to :adjustable, polymorphic: true, touch: true
belongs_to :source, polymorphic: true
belongs_to :order, class_name: 'Spree::Order', inverse_of: :all_adjustments
belongs_to :promotion_code, class_name: 'Spree::PromotionCode'
belongs_to :adjustment_reason, class_name: 'Spree::AdjustmentReason', inverse_of: :adjustments
validates :adjustable, presence: true
validates :order, presence: true
validates :label, presence: true
validates :amount, numericality: true
validates :promotion_code, presence: true, if: :require_promotion_code?
# We need to use `after_commit` here because otherwise it's too early to
# tell if any repair is needed.
after_commit :repair_adjustments_associations_on_create, on: [:create]
after_commit :repair_adjustments_associations_on_destroy, on: [:destroy]
scope :not_finalized, -> { where(finalized: false) }
scope :finalized, -> { where(finalized: true) }
scope :cancellation, -> { where(source_type: 'Spree::UnitCancel') }
scope :tax, -> { where(source_type: 'Spree::TaxRate') }
scope :non_tax, -> do
source_type = arel_table[:source_type]
where(source_type.not_eq('Spree::TaxRate').or(source_type.eq(nil)))
end
scope :price, -> { where(adjustable_type: 'Spree::LineItem') }
scope :shipping, -> { where(adjustable_type: 'Spree::Shipment') }
scope :eligible, -> { where(eligible: true) }
scope :charge, -> { where("#{quoted_table_name}.amount >= 0") }
scope :credit, -> { where("#{quoted_table_name}.amount < 0") }
scope :nonzero, -> { where("#{quoted_table_name}.amount != 0") }
scope :promotion, -> { where(source_type: 'Spree::PromotionAction') }
scope :non_promotion, -> { where.not(source_type: 'Spree::PromotionAction') }
scope :return_authorization, -> { where(source_type: "Spree::ReturnAuthorization") }
scope :is_included, -> { where(included: true) }
scope :additional, -> { where(included: false) }
extend DisplayMoney
money_methods :amount
def finalize!
update_attributes!(finalized: true)
end
def unfinalize!
update_attributes!(finalized: false)
end
def finalize
update_attributes(finalized: true)
end
def unfinalize
update_attributes(finalized: false)
end
def currency
adjustable ? adjustable.currency : Spree::Config[:currency]
end
# @return [Boolean] true when this is a promotion adjustment (Promotion adjustments have a {PromotionAction} source)
def promotion?
source_type == 'Spree::PromotionAction'
end
# @return [Boolean] true when this is a tax adjustment (Tax adjustments have a {TaxRate} source)
def tax?
source_type == 'Spree::TaxRate'
end
# @return [Boolean] true when this is a cancellation adjustment (Cancellation adjustments have a {UnitCancel} source)
def cancellation?
source_type == 'Spree::UnitCancel'
end
# Recalculate and persist the amount from this adjustment's source based on
# the adjustable ({Order}, {Shipment}, or {LineItem})
#
# If the adjustment has no source (such as when created manually from the
# admin) or is closed, this is a noop.
#
# @return [BigDecimal] New amount of this adjustment
def recalculate
if finalized? && !tax?
return amount
end
# If the adjustment has no source, do not attempt to re-calculate the
# amount.
# Some scenarios where this happens:
# - Adjustments that are manually created via the admin backend
# - PromotionAction adjustments where the PromotionAction was deleted
# after the order was completed.
if source.present?
self.amount = source.compute_amount(adjustable)
if promotion?
self.eligible = calculate_eligibility
end
# Persist only if changed
# This is only not a save! to avoid the extra queries to load the order
# (for validations) and to touch the adjustment.
update_columns(eligible: eligible, amount: amount, updated_at: Time.current) if changed?
end
amount
end
def update!(*args)
if args.empty?
Spree::Deprecation.warn "Calling adjustment.update! with no arguments to recalculate amounts and eligibility is deprecated, since it conflicts with AR::Base#update! Please use adjustment.recalculate instead"
recalculate
else
super
end
end
# Calculates based on attached promotion (if this is a promotion
# adjustment) whether this promotion is still eligible.
# @api private
# @return [true,false] Whether this adjustment is eligible
def calculate_eligibility
if !finalized? && source && promotion?
source.promotion.eligible?(adjustable, promotion_code: promotion_code)
else
eligible?
end
end
private
def require_promotion_code?
promotion? && source.promotion.codes.any?
end
def repair_adjustments_associations_on_create
if adjustable.adjustments.loaded? && !adjustable.adjustments.include?(self) && !destroyed?
Spree::Deprecation.warn("Adjustment #{id} was not added to #{adjustable.class} #{adjustable.id}. Add adjustments via `adjustable.adjustments.create!`. Partial call stack: #{caller.select { |line| line =~ %r(/(app|spec)/) }}.", caller)
adjustable.adjustments.proxy_association.add_to_target(self)
end
end
def repair_adjustments_associations_on_destroy
if adjustable.adjustments.loaded? && adjustable.adjustments.include?(self)
Spree::Deprecation.warn("Adjustment #{id} was not removed from #{adjustable.class} #{adjustable.id}. Remove adjustments via `adjustable.adjustments.destroy`. Partial call stack: #{caller.select { |line| line =~ %r(/(app|spec)/) }}.", caller)
adjustable.adjustments.proxy_association.target.delete(self)
end
end
end
end