Files
openfoodnetwork/app/services/fdc_backorderer.rb
2024-12-17 13:48:03 +11:00

131 lines
3.5 KiB
Ruby

# frozen_string_literal: true
# Place and update orders based on missing stock.
class FdcBackorderer
attr_reader :user, :urls
def initialize(user, urls)
@user = user
@urls = urls
end
def find_or_build_order(ofn_order)
find_open_order(ofn_order) || build_new_order(ofn_order)
end
def build_new_order(ofn_order)
OrderBuilder.new_order(ofn_order, urls.orders_url).tap do |order|
order.saleSession = build_sale_session(ofn_order)
end
end
# Try the new method and fall back to old method.
def find_open_order(ofn_order)
lookup_open_order(ofn_order)
end
def lookup_open_order(ofn_order)
# There should be only one link at the moment but we may support
# ordering from multiple suppliers one day.
semantic_ids = ofn_order.semantic_links.pluck(:semantic_id)
semantic_ids.lazy
# Make sure we select an order from the right supplier:
.select { |id| id.starts_with?(urls.orders_url) }
# Fetch the order from the remote DFC server, lazily:
.map { |id| find_order(id) }
.compact
# Just in case someone completed the order without updating our database:
.select { |o| o.orderStatus == order_status.HELD }
.first
# The DFC Connector doesn't recognise status values properly yet.
# So we are overriding the value with something that can be exported.
&.tap { |o| o.orderStatus = "dfc-v:Held" }
end
def find_order(semantic_id)
find_subject(import(semantic_id), "dfc-b:Order")
end
def find_or_build_order_line(order, offer)
find_order_line(order, offer) || build_order_line(order, offer)
end
def build_order_line(order, offer)
# Order lines are enumerated in the FDC API and we must assign a unique
# semantic id. We need to look at current ids to avoid collisions.
# existing_ids = order.lines.map do |line|
# line.semanticId.match(/[0-9]+$/).to_s.to_i
# end
# next_id = existing_ids.max.to_i + 1
# Suggested by FDC team:
next_id = order.lines.count + 1
OrderLineBuilder.build(offer, 0).tap do |line|
line.semanticId = "#{order.semanticId}/OrderLines/#{next_id}"
order.lines << line
end
end
def find_order_line(order, offer)
order.lines.find do |line|
line.offer.offeredItem.semanticId == offer.offeredItem.semanticId
end
end
def find_subject(object_or_graph, type)
if object_or_graph.is_a?(Array)
object_or_graph.find { |i| i.semanticType == type }
else
object_or_graph
end
end
def import(url)
api = DfcRequest.new(user)
json = api.call(url)
DfcIo.import(json)
end
def send_order(backorder)
lines = backorder.lines
offers = lines.map(&:offer)
products = offers.map(&:offeredItem)
sessions = [backorder.saleSession].compact
json = DfcIo.export(backorder, *lines, *offers, *products, *sessions)
api = DfcRequest.new(user)
method = if new?(backorder)
:post # -> create
else
:put # -> update
end
result = api.call(backorder.semanticId, json, method:)
find_subject(DfcIo.import(result), "dfc-b:Order")
end
def complete_order(backorder)
backorder.orderStatus = "dfc-v:Complete"
send_order(backorder)
end
def new?(order)
order.semanticId == urls&.orders_url
end
def build_sale_session(order)
SaleSessionBuilder.build(order.order_cycle).tap do |session|
session.semanticId = urls.sale_session_url
end
end
private
def order_status
DfcLoader.vocabulary("vocabulary").STATES.ORDERSTATE
end
end