mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-01-31 21:37:16 +00:00
142 lines
5.1 KiB
Ruby
142 lines
5.1 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
module Spree
|
|
class OrderUpdater
|
|
attr_reader :order
|
|
delegate :payments, :line_items, :adjustments, :shipments, :update_hooks, 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_totals
|
|
|
|
if order.completed?
|
|
update_payment_state
|
|
|
|
# give each of the shipments a chance to update themselves
|
|
shipments.each { |shipment| shipment.update!(order) }
|
|
update_shipment_state
|
|
end
|
|
|
|
update_all_adjustments
|
|
# update totals a second time in case updated adjustments have an effect on the total
|
|
update_totals
|
|
|
|
order.update_attributes_without_callbacks(
|
|
{
|
|
payment_state: order.payment_state,
|
|
shipment_state: order.shipment_state,
|
|
item_total: order.item_total,
|
|
adjustment_total: order.adjustment_total,
|
|
payment_total: order.payment_total,
|
|
total: order.total
|
|
}
|
|
)
|
|
|
|
run_hooks
|
|
end
|
|
|
|
def run_hooks
|
|
update_hooks.each { |hook| order.__send__(hook) }
|
|
end
|
|
|
|
# Updates the following Order total values:
|
|
#
|
|
# - payment_total - the total value of all finalized Payments (excluding non-finalized Payments)
|
|
# - item_total - the total value of all LineItems
|
|
# - adjustment_total - the total value of all adjustments
|
|
# - total - the "order total". This is equivalent to item_total plus adjustment_total
|
|
def update_totals
|
|
order.payment_total = payments.completed.map(&:amount).sum
|
|
order.item_total = line_items.map(&:amount).sum
|
|
order.adjustment_total = adjustments.eligible.map(&:amount).sum
|
|
order.total = order.item_total + order.adjustment_total
|
|
end
|
|
|
|
# Updates the +shipment_state+ attribute according to the following logic:
|
|
#
|
|
# - shipped - when all Shipments are in the "shipped" state
|
|
# - partial - when 1. at least one Shipment has a state of "shipped"
|
|
# and there is another Shipment with a state other than "shipped"
|
|
# or 2. 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')
|
|
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
|
|
# line_items are empty when user empties cart
|
|
if line_items.empty? || round_money(order.payment_total) < round_money(order.total)
|
|
order.payment_state = if payments.present? && payments.last.state == 'failed'
|
|
'failed'
|
|
else
|
|
'balance_due'
|
|
end
|
|
elsif round_money(order.payment_total) > round_money(order.total)
|
|
order.payment_state = 'credit_owed'
|
|
else
|
|
order.payment_state = 'paid'
|
|
end
|
|
|
|
order.state_changed('payment')
|
|
end
|
|
|
|
def update_all_adjustments
|
|
order.adjustments.reload.each(&:update!)
|
|
end
|
|
|
|
def before_save_hook
|
|
# no op
|
|
end
|
|
|
|
private
|
|
|
|
def round_money(value)
|
|
(value * 100).round / 100.0
|
|
end
|
|
end
|
|
end
|