mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-01-24 20:36:49 +00:00
Build API URLs to work with any FDC Shopify shop
We can extend this service class when there are other APIs. And hopefully the DFC will provide a standard for this discovery at some point.
This commit is contained in:
@@ -1,10 +1,6 @@
|
||||
# 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
|
||||
|
||||
# In the current FDC project, one shop wants to review and adjust orders
|
||||
# before finalising. They also run a market stall and need to adjust stock
|
||||
# levels after the market. This should be done within four hours.
|
||||
@@ -36,9 +32,14 @@ class BackorderJob < ApplicationJob
|
||||
|
||||
def self.place_backorder(order, linked_variants)
|
||||
user = order.distributor.owner
|
||||
orderer = FdcBackorderer.new(user)
|
||||
|
||||
# We are assuming that all variants are linked to the same wholesale
|
||||
# shop and its catalog:
|
||||
urls = FdcUrlBuilder.new(linked_variants[0].semantic_links[0].semantic_id)
|
||||
orderer = FdcBackorderer.new(user, urls)
|
||||
|
||||
backorder = orderer.find_or_build_order(order)
|
||||
broker = load_broker(order.distributor.owner)
|
||||
broker = load_broker(order.distributor.owner, urls)
|
||||
ordered_quantities = {}
|
||||
|
||||
linked_variants.each do |variant|
|
||||
@@ -73,13 +74,13 @@ class BackorderJob < ApplicationJob
|
||||
retail_quantity
|
||||
end
|
||||
|
||||
def self.load_broker(user)
|
||||
FdcOfferBroker.new(load_catalog(user))
|
||||
def self.load_broker(user, urls)
|
||||
FdcOfferBroker.new(load_catalog(user, urls))
|
||||
end
|
||||
|
||||
def self.load_catalog(user)
|
||||
def self.load_catalog(user, urls)
|
||||
api = DfcRequest.new(user)
|
||||
catalog_json = api.call(FDC_CATALOG_URL)
|
||||
catalog_json = api.call(urls.catalog_url)
|
||||
DfcIo.import(catalog_json)
|
||||
end
|
||||
|
||||
|
||||
@@ -16,13 +16,13 @@ class CompleteBackorderJob < ApplicationJob
|
||||
# Having the id makes sure that we don't accidentally finalise
|
||||
# someone else's order.
|
||||
def perform(user, distributor, order_cycle, order_id)
|
||||
service = FdcBackorderer.new(user)
|
||||
order = service.find_order(order_id)
|
||||
order = FdcBackorderer.new(user, nil).find_order(order_id)
|
||||
urls = FdcUrlBuilder.new(order.lines[0].offer.offeredItem.semanticId)
|
||||
|
||||
variants = order_cycle.variants_distributed_by(distributor)
|
||||
adjust_quantities(user, order, variants)
|
||||
adjust_quantities(user, order, urls, variants)
|
||||
|
||||
service.complete_order(order)
|
||||
FdcBackorderer.new(user, urls).complete_order(order)
|
||||
end
|
||||
|
||||
# Check if we have enough stock to reduce the backorder.
|
||||
@@ -30,8 +30,8 @@ class CompleteBackorderJob < ApplicationJob
|
||||
# Our local stock can increase when users cancel their orders.
|
||||
# But stock levels could also have been adjusted manually. So we review all
|
||||
# quantities before finalising the order.
|
||||
def adjust_quantities(user, order, variants)
|
||||
broker = FdcOfferBroker.new(BackorderJob.load_catalog(user))
|
||||
def adjust_quantities(user, order, urls, variants)
|
||||
broker = FdcOfferBroker.new(BackorderJob.load_catalog(user, urls))
|
||||
|
||||
order.lines.each do |line|
|
||||
line.quantity = line.quantity.to_i
|
||||
|
||||
@@ -2,14 +2,11 @@
|
||||
|
||||
# Place and update orders based on missing stock.
|
||||
class FdcBackorderer
|
||||
FDC_BASE_URL = "https://env-0105831.jcloud-ver-jpe.ik-server.com/api/dfc/Enterprises/test-hodmedod"
|
||||
FDC_ORDERS_URL = "#{FDC_BASE_URL}/Orders".freeze
|
||||
FDC_SALE_SESSION_URL = "#{FDC_BASE_URL}/SalesSession/#".freeze
|
||||
attr_reader :user, :urls
|
||||
|
||||
attr_reader :user
|
||||
|
||||
def initialize(user)
|
||||
def initialize(user, urls)
|
||||
@user = user
|
||||
@urls = urls
|
||||
end
|
||||
|
||||
def find_or_build_order(ofn_order)
|
||||
@@ -17,13 +14,13 @@ class FdcBackorderer
|
||||
end
|
||||
|
||||
def build_new_order(ofn_order)
|
||||
OrderBuilder.new_order(ofn_order, FDC_ORDERS_URL).tap do |order|
|
||||
OrderBuilder.new_order(ofn_order, urls.orders_url).tap do |order|
|
||||
order.saleSession = build_sale_session(ofn_order)
|
||||
end
|
||||
end
|
||||
|
||||
def find_open_order
|
||||
graph = import(FDC_ORDERS_URL)
|
||||
graph = import(urls.orders_url)
|
||||
open_orders = graph&.select do |o|
|
||||
o.semanticType == "dfc-b:Order" && o.orderStatus[:path] == "Held"
|
||||
end
|
||||
@@ -125,12 +122,12 @@ class FdcBackorderer
|
||||
end
|
||||
|
||||
def new?(order)
|
||||
order.semanticId == FDC_ORDERS_URL
|
||||
order.semanticId == urls.orders_url
|
||||
end
|
||||
|
||||
def build_sale_session(order)
|
||||
SaleSessionBuilder.build(order.order_cycle).tap do |session|
|
||||
session.semanticId = FDC_SALE_SESSION_URL
|
||||
session.semanticId = urls.sale_session_url
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
16
app/services/fdc_url_builder.rb
Normal file
16
app/services/fdc_url_builder.rb
Normal file
@@ -0,0 +1,16 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# The DFC standard doesn't include endpoint discovery yet.
|
||||
# So for now we are guessing URLs based on our FDC pilot project.
|
||||
class FdcUrlBuilder
|
||||
attr_reader :catalog_url, :orders_url, :sale_session_url
|
||||
|
||||
# At the moment, we start with a product link like this:
|
||||
#
|
||||
# https://env-0105831.jcloud-ver-jpe.ik-server.com/api/dfc/Enterprises/test-hodmedod/SuppliedProducts/44519466467635
|
||||
def initialize(semantic_id)
|
||||
@catalog_url, _slash, _id = semantic_id.rpartition("/")
|
||||
@orders_url = @catalog_url.sub("/SuppliedProducts", "/Orders")
|
||||
@sale_session_url = @catalog_url.sub("/SuppliedProducts", "/SalesSession/#")
|
||||
end
|
||||
end
|
||||
@@ -57,7 +57,8 @@ RSpec.describe BackorderJob do
|
||||
)
|
||||
completion_time = Date.tomorrow.noon + 4.hours
|
||||
|
||||
orderer = FdcBackorderer.new(user)
|
||||
urls = FdcUrlBuilder.new(product_link)
|
||||
orderer = FdcBackorderer.new(user, urls)
|
||||
backorder = orderer.build_new_order(order)
|
||||
backorder.client = "https://openfoodnetwork.org.uk/api/dfc/enterprises/203468"
|
||||
|
||||
|
||||
@@ -4,7 +4,11 @@ require 'spec_helper'
|
||||
|
||||
RSpec.describe CompleteBackorderJob do
|
||||
let(:user) { build(:testdfc_user) }
|
||||
let(:catalog) { BackorderJob.load_catalog(user) }
|
||||
let(:catalog) { BackorderJob.load_catalog(user, urls) }
|
||||
let(:urls) { FdcUrlBuilder.new(product_link) }
|
||||
let(:product_link) {
|
||||
"https://env-0105831.jcloud-ver-jpe.ik-server.com/api/dfc/Enterprises/test-hodmedod/SuppliedProducts/44519466467635"
|
||||
}
|
||||
let(:retail_product) {
|
||||
catalog.find { |item| item.semanticType == "dfc-b:SuppliedProduct" }
|
||||
}
|
||||
@@ -12,7 +16,7 @@ RSpec.describe CompleteBackorderJob do
|
||||
flow = catalog.find { |item| item.semanticType == "dfc-b:AsPlannedProductionFlow" }
|
||||
catalog.find { |item| item.semanticId == flow.product }
|
||||
}
|
||||
let(:orderer) { FdcBackorderer.new(user) }
|
||||
let(:orderer) { FdcBackorderer.new(user, urls) }
|
||||
let(:order) {
|
||||
backorder = orderer.find_or_build_order(ofn_order)
|
||||
broker = FdcOfferBroker.new(catalog)
|
||||
|
||||
@@ -3,7 +3,11 @@
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe FdcBackorderer do
|
||||
let(:subject) { FdcBackorderer.new(order.distributor.owner) }
|
||||
let(:subject) { FdcBackorderer.new(order.distributor.owner, urls) }
|
||||
let(:urls) { FdcUrlBuilder.new(product_link) }
|
||||
let(:product_link) {
|
||||
"https://env-0105831.jcloud-ver-jpe.ik-server.com/api/dfc/Enterprises/test-hodmedod/SuppliedProducts/44519466467635"
|
||||
}
|
||||
let(:order) { create(:completed_order_with_totals) }
|
||||
let(:account) {
|
||||
OidcAccount.new(
|
||||
@@ -25,11 +29,11 @@ RSpec.describe FdcBackorderer do
|
||||
# Build a new order when no open one is found:
|
||||
order.order_cycle = build(:order_cycle)
|
||||
backorder = subject.find_or_build_order(order)
|
||||
expect(backorder.semanticId).to eq FdcBackorderer::FDC_ORDERS_URL
|
||||
expect(backorder.semanticId).to eq urls.orders_url
|
||||
expect(backorder.lines).to eq []
|
||||
|
||||
# Add items and place the new order:
|
||||
catalog = BackorderJob.load_catalog(order.distributor.owner)
|
||||
catalog = BackorderJob.load_catalog(order.distributor.owner, urls)
|
||||
product = catalog.find { |i| i.semanticType == "dfc-b:SuppliedProduct" }
|
||||
offer = FdcOfferBroker.new(nil).offer_of(product)
|
||||
line = subject.find_or_build_order_line(backorder, offer)
|
||||
@@ -55,19 +59,19 @@ RSpec.describe FdcBackorderer do
|
||||
describe "#find_or_build_order" do
|
||||
it "builds an order object" do
|
||||
account.updated_at = Time.zone.now
|
||||
stub_request(:get, FdcBackorderer::FDC_ORDERS_URL)
|
||||
stub_request(:get, urls.orders_url)
|
||||
.to_return(status: 200, body: "{}")
|
||||
|
||||
backorder = subject.find_or_build_order(order)
|
||||
|
||||
expect(backorder.semanticId).to eq FdcBackorderer::FDC_ORDERS_URL
|
||||
expect(backorder.semanticId).to eq urls.orders_url
|
||||
expect(backorder.lines).to eq []
|
||||
end
|
||||
end
|
||||
|
||||
describe "#find_or_build_order_line" do
|
||||
it "add quantity to an existing line item", vcr: true do
|
||||
catalog = BackorderJob.load_catalog(order.distributor.owner)
|
||||
catalog = BackorderJob.load_catalog(order.distributor.owner, urls)
|
||||
backorder = subject.find_or_build_order(order)
|
||||
existing_line = backorder.lines[0]
|
||||
|
||||
|
||||
@@ -4,7 +4,11 @@ require 'spec_helper'
|
||||
|
||||
RSpec.describe FdcOfferBroker do
|
||||
subject { FdcOfferBroker.new(catalog) }
|
||||
let(:catalog) { BackorderJob.load_catalog(user) }
|
||||
let(:catalog) { BackorderJob.load_catalog(user, urls) }
|
||||
let(:urls) { FdcUrlBuilder.new(product_link) }
|
||||
let(:product_link) {
|
||||
"https://env-0105831.jcloud-ver-jpe.ik-server.com/api/dfc/Enterprises/test-hodmedod/SuppliedProducts/44519466467635"
|
||||
}
|
||||
let(:user) { build(:testdfc_user) }
|
||||
let(:product) {
|
||||
catalog.find { |item| item.semanticType == "dfc-b:SuppliedProduct" }
|
||||
|
||||
16
spec/services/fdc_url_builder_spec.rb
Normal file
16
spec/services/fdc_url_builder_spec.rb
Normal file
@@ -0,0 +1,16 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe FdcUrlBuilder do
|
||||
subject(:urls) { FdcUrlBuilder.new(product_link) }
|
||||
let(:product_link) {
|
||||
"https://env-0105831.jcloud-ver-jpe.ik-server.com/api/dfc/Enterprises/test-hodmedod/SuppliedProducts/44519466467635"
|
||||
}
|
||||
|
||||
it "knows the right URLs" do
|
||||
expect(subject.catalog_url).to eq "https://env-0105831.jcloud-ver-jpe.ik-server.com/api/dfc/Enterprises/test-hodmedod/SuppliedProducts"
|
||||
expect(subject.orders_url).to eq "https://env-0105831.jcloud-ver-jpe.ik-server.com/api/dfc/Enterprises/test-hodmedod/Orders"
|
||||
expect(subject.sale_session_url).to eq "https://env-0105831.jcloud-ver-jpe.ik-server.com/api/dfc/Enterprises/test-hodmedod/SalesSession/#"
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user