-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
/
Copy pathorder_updater.rb
178 lines (158 loc) · 6.71 KB
/
order_updater.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
169
170
171
172
173
174
175
176
177
178
module Spree
class OrderUpdater
attr_reader :order
delegate :payments, :line_items, :adjustments, :all_adjustments, :shipments, :update_hooks, :quantity, to: :order
def initialize(order)
@order = order
end
# This is a multi-purpose method for processing logic related to changes in the Order.
# It is meant to be called from various observers so that the Order is aware of changes
# that affect totals and other values stored in the Order.
#
# This method should never do anything to the Order that results in a save call on the
# object with callbacks (otherwise you will end up in an infinite recursion as the
# associations try to save and then in turn try to call +update!+ again.)
def update
update_item_count
update_totals
if order.completed?
update_payment_state
update_shipments
update_shipment_state
end
run_hooks
persist_totals
end
def run_hooks
update_hooks.each { |hook| order.send hook }
end
def recalculate_adjustments
all_adjustments.includes(:adjustable).map(&:adjustable).uniq.each { |adjustable| Spree::ItemAdjustments.new(adjustable).update }
end
# Updates the following Order total values:
#
# +payment_total+ The total value of all finalized Payments (NOTE: non-finalized Payments are excluded)
# +item_total+ The total value of all LineItems
# +adjustment_total+ The total value of all adjustments (promotions, credits, etc.)
# +promo_total+ The total value of all promotion adjustments
# +total+ The so-called "order total." This is equivalent to +item_total+ plus +adjustment_total+.
def update_totals
update_payment_total
update_item_total
update_shipment_total
update_adjustment_total
end
# give each of the shipments a chance to update themselves
def update_shipments
shipments.each do |shipment|
next unless shipment.persisted?
shipment.update!(order)
shipment.refresh_rates
shipment.update_amounts
end
end
def update_payment_total
order.payment_total = payments.completed.includes(:refunds).inject(0) { |sum, payment| sum + payment.amount - payment.refunds.sum(:amount) }
end
def update_shipment_total
order.shipment_total = shipments.sum(:cost)
update_order_total
end
def update_order_total
order.total = order.item_total + order.shipment_total + order.adjustment_total
end
def update_adjustment_total
recalculate_adjustments
order.adjustment_total = line_items.sum(:adjustment_total) +
shipments.sum(:adjustment_total) +
adjustments.eligible.sum(:amount)
order.included_tax_total = line_items.sum(:included_tax_total) + shipments.sum(:included_tax_total)
order.additional_tax_total = line_items.sum(:additional_tax_total) + shipments.sum(:additional_tax_total)
order.promo_total = line_items.sum(:promo_total) +
shipments.sum(:promo_total) +
adjustments.promotion.eligible.sum(:amount)
update_order_total
end
def update_item_count
order.item_count = quantity
end
def update_item_total
order.item_total = line_items.sum('price * quantity')
update_order_total
end
def persist_totals
order.update_columns(
payment_state: order.payment_state,
shipment_state: order.shipment_state,
item_total: order.item_total,
item_count: order.item_count,
adjustment_total: order.adjustment_total,
included_tax_total: order.included_tax_total,
additional_tax_total: order.additional_tax_total,
payment_total: order.payment_total,
shipment_total: order.shipment_total,
promo_total: order.promo_total,
total: order.total,
updated_at: Time.current,
)
end
# Updates the +shipment_state+ attribute according to the following logic:
#
# shipped when all Shipments are in the "shipped" state
# partial when at least one Shipment has a state of "shipped" and there is another Shipment with a state other than "shipped"
# or there are InventoryUnits associated with the order that have a state of "sold" but are not associated with a Shipment.
# ready when all Shipments are in the "ready" state
# backorder when there is backordered inventory associated with an order
# pending when all Shipments are in the "pending" state
#
# The +shipment_state+ value helps with reporting, etc. since it provides a quick and easy way to locate Orders needing attention.
def update_shipment_state
if order.backordered?
order.shipment_state = 'backorder'
else
# get all the shipment states for this order
shipment_states = shipments.states
if shipment_states.size > 1
# multiple shiment states means it's most likely partially shipped
order.shipment_state = 'partial'
else
# will return nil if no shipments are found
order.shipment_state = shipment_states.first
# TODO inventory unit states?
# if order.shipment_state && order.inventory_units.where(:shipment_id => nil).exists?
# shipments exist but there are unassigned inventory units
# order.shipment_state = 'partial'
# end
end
end
order.state_changed('shipment')
order.shipment_state
end
# Updates the +payment_state+ attribute according to the following logic:
#
# paid when +payment_total+ is equal to +total+
# balance_due when +payment_total+ is less than +total+
# credit_owed when +payment_total+ is greater than +total+
# failed when most recent payment is in the failed state
#
# The +payment_state+ value helps with reporting, etc. since it provides a quick and easy way to locate Orders needing attention.
def update_payment_state
last_state = order.payment_state
if payments.present? && payments.valid.size == 0 && order.outstanding_balance != 0
order.payment_state = 'failed'
elsif order.state == 'canceled' && order.payment_total == 0
order.payment_state = 'void'
else
order.payment_state = 'balance_due' if order.outstanding_balance > 0
order.payment_state = 'credit_owed' if order.outstanding_balance < 0
order.payment_state = 'paid' if !order.outstanding_balance?
end
order.state_changed('payment') if last_state != order.payment_state
order.payment_state
end
private
def round_money(n)
(n * 100).round / 100.0
end
end
end