mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-02-26 01:33:22 +00:00
Place backorders for DFC products
This commit is contained in:
@@ -1,6 +1,14 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class BackorderJob < ApplicationJob
|
||||
FDC_BASE_URL = "https://env-0105831.jcloud-ver-jpe.ik-server.com/api/dfc/Enterprises/test-hodmedod"
|
||||
FDC_CATALOG_URL = "#{FDC_BASE_URL}/SuppliedProducts".freeze
|
||||
FDC_ORDERS_URL = "#{FDC_BASE_URL}/Orders".freeze
|
||||
|
||||
# The FDC implementation needs special ids for new objects:
|
||||
FDC_NEW_ORDER_URL = "#{FDC_ORDERS_URL}/#".freeze
|
||||
FDC_ORDER_LINES_URL = "#{FDC_ORDERS_URL}/#/OrderLines".freeze
|
||||
|
||||
queue_as :default
|
||||
|
||||
def self.check_stock(order)
|
||||
@@ -14,14 +22,85 @@ class BackorderJob < ApplicationJob
|
||||
variant.semantic_links.present?
|
||||
end
|
||||
|
||||
return if linked_variants.empty?
|
||||
|
||||
# At this point we want to move to the background with perform_later.
|
||||
# But while this is in development I'll perform the backordering
|
||||
# immediately. It should ease debugging for now.
|
||||
place_backorder(order, linked_variants)
|
||||
end
|
||||
|
||||
def self.place_backorder(order, linked_variants)
|
||||
backorder = build_order(order)
|
||||
catalog = load_catalog(order.distributor.owner)
|
||||
|
||||
linked_variants.each_with_index do |variant, index|
|
||||
needed_quantity = -1 * variant.on_hand
|
||||
offer = best_offer(catalog, variant)
|
||||
|
||||
# Order lines are enumerated in the FDC API:
|
||||
line = build_order_line(offer, needed_quantity)
|
||||
line.semanticId = "#{FDC_ORDER_LINES_URL}/#{index}"
|
||||
backorder.lines << line
|
||||
end
|
||||
|
||||
lines = backorder.lines
|
||||
offers = lines.map(&:offer)
|
||||
products = offers.map(&:offeredItem)
|
||||
json = DfcIo.export(backorder, *lines, *offers, *products, build_sale_session)
|
||||
|
||||
api = DfcRequest.new(order.distributor.owner)
|
||||
|
||||
# Create order via POST:
|
||||
api.call(FDC_ORDERS_URL, json)
|
||||
|
||||
# Once we have transformations and know the quantities in bulk products
|
||||
# we will need to increase on_hand by the ordered quantity.
|
||||
linked_variants.each do |variant|
|
||||
# needed_quantity = -1 * variant.on_hand
|
||||
# create DFC Order
|
||||
# post order to endpoint
|
||||
variant.on_hand = 0
|
||||
end
|
||||
end
|
||||
|
||||
# This needs to find an existing order when the API is available.
|
||||
def self.build_order(ofn_order)
|
||||
OrderBuilder.new_order(ofn_order, FDC_NEW_ORDER_URL)
|
||||
end
|
||||
|
||||
def self.build_order_line(offer, quantity)
|
||||
OrderLineBuilder.build(offer, quantity)
|
||||
end
|
||||
|
||||
def self.build_sale_session
|
||||
DataFoodConsortium::Connector::SaleSession.new(
|
||||
"https://env-0105831.jcloud-ver-jpe.ik-server.com/api/dfc/Enterprises/test-hodmedod/SalesSession/#",
|
||||
beginDate: "Tue Aug 20 2024 08:02:23 GMT+0000 (Coordinated Universal Time)",
|
||||
endDate: "Tue Aug 27 2024 08:02:23 GMT+0000 (Coordinated Universal Time)",
|
||||
)
|
||||
end
|
||||
|
||||
def self.best_offer(catalog, variant)
|
||||
link = variant.semantic_links[0]
|
||||
|
||||
return unless link
|
||||
|
||||
product = catalog.find { |item| item.semanticId == link.semantic_id }
|
||||
offer_of(product)
|
||||
end
|
||||
|
||||
def self.offer_of(product)
|
||||
product&.catalogItems&.first&.offers&.first&.tap do |offer|
|
||||
# Unfortunately, the imported catalog doesn't provide the reverse link:
|
||||
offer.offeredItem = product
|
||||
end
|
||||
end
|
||||
|
||||
def self.load_catalog(user)
|
||||
api = DfcRequest.new(user)
|
||||
catalog_json = api.call(FDC_CATALOG_URL)
|
||||
DfcIo.import(catalog_json)
|
||||
end
|
||||
|
||||
def perform(*args)
|
||||
# Do something later
|
||||
# The ordering logic will live here later.
|
||||
end
|
||||
end
|
||||
|
||||
11
engines/dfc_provider/app/services/order_builder.rb
Normal file
11
engines/dfc_provider/app/services/order_builder.rb
Normal file
@@ -0,0 +1,11 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class OrderBuilder < DfcBuilder
|
||||
def self.new_order(ofn_order, id = nil)
|
||||
DataFoodConsortium::Connector::Order.new(
|
||||
id,
|
||||
client: urls.enterprise_url(ofn_order.distributor_id),
|
||||
orderStatus: "dfc-v:Held",
|
||||
)
|
||||
end
|
||||
end
|
||||
9
engines/dfc_provider/app/services/order_line_builder.rb
Normal file
9
engines/dfc_provider/app/services/order_line_builder.rb
Normal file
@@ -0,0 +1,9 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class OrderLineBuilder < DfcBuilder
|
||||
def self.build(offer, quantity)
|
||||
DataFoodConsortium::Connector::OrderLine.new(
|
||||
nil, offer:, quantity:,
|
||||
)
|
||||
end
|
||||
end
|
||||
21
engines/dfc_provider/spec/services/order_builder_spec.rb
Normal file
21
engines/dfc_provider/spec/services/order_builder_spec.rb
Normal file
@@ -0,0 +1,21 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative "../spec_helper"
|
||||
|
||||
RSpec.describe OrderBuilder do
|
||||
describe ".new_order" do
|
||||
subject(:result) { described_class.new_order(ofn_order) }
|
||||
let(:ofn_order) {
|
||||
create(
|
||||
:completed_order_with_totals,
|
||||
id: 1,
|
||||
)
|
||||
}
|
||||
|
||||
it "builds a new order" do
|
||||
expect(result.semanticId).to eq nil
|
||||
expect(result.lines.count).to eq 0
|
||||
expect(result.orderStatus).to eq "dfc-v:Held"
|
||||
end
|
||||
end
|
||||
end
|
||||
198
spec/fixtures/vcr_cassettes/BackorderJob/_best_offer/finds_a_linked_offer.yml
vendored
Normal file
198
spec/fixtures/vcr_cassettes/BackorderJob/_best_offer/finds_a_linked_offer.yml
vendored
Normal file
File diff suppressed because one or more lines are too long
272
spec/fixtures/vcr_cassettes/BackorderJob/_check_stock/places_an_order.yml
vendored
Normal file
272
spec/fixtures/vcr_cassettes/BackorderJob/_check_stock/places_an_order.yml
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -4,10 +4,47 @@ require 'spec_helper'
|
||||
|
||||
RSpec.describe BackorderJob do
|
||||
let(:order) { create(:completed_order_with_totals) }
|
||||
let(:variant) { order.variants.first }
|
||||
let(:user) { order.distributor.owner }
|
||||
let(:product_link) {
|
||||
"https://env-0105831.jcloud-ver-jpe.ik-server.com/api/dfc/Enterprises/test-hodmedod/SuppliedProducts/44519466467635"
|
||||
}
|
||||
|
||||
before do
|
||||
user.oidc_account = OidcAccount.new(
|
||||
uid: "testdfc@protonmail.com",
|
||||
refresh_token: ENV.fetch("OPENID_REFRESH_TOKEN"),
|
||||
updated_at: 1.day.ago,
|
||||
)
|
||||
end
|
||||
|
||||
describe ".check_stock" do
|
||||
it "ignores products without semantic link" do
|
||||
BackorderJob.check_stock(order) # and not web requests were made
|
||||
end
|
||||
|
||||
it "places an order", vcr: true do
|
||||
variant.on_demand = true
|
||||
variant.on_hand = -3
|
||||
variant.semantic_links << SemanticLink.new(
|
||||
semantic_id: product_link
|
||||
)
|
||||
BackorderJob.check_stock(order)
|
||||
end
|
||||
end
|
||||
|
||||
describe ".best_offer" do
|
||||
it "finds a linked offer", vcr: true do
|
||||
variant = order.line_items[0].variant
|
||||
|
||||
variant.semantic_links << SemanticLink.new(
|
||||
semantic_id: product_link
|
||||
)
|
||||
|
||||
catalog = BackorderJob.load_catalog(user)
|
||||
offer = BackorderJob.best_offer(catalog, variant)
|
||||
|
||||
expect(offer.semanticId).to eq "#{product_link}/Offer"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user