Build the supplier: reorder.requested -> decide -> deliver
The service's own supplier, simulated: a fixed lead time or a 15% chance of a stockout is the whole model, on purpose — this simulates that fulfilment can fail, not why a given supplier would. One service, two capacities: this workflow both raises reorder.requested (the periodic run) and answers its own request (this workflow), under the same Kafka account and Keycloak client, per the 2026-08-31 decision that réapprovisionnement stays one service. The delivery write goes through the front door — patch-product, under a new restocker role that may PATCH a product and nothing else — so the resulting product.stock_changed event is the only stock-update fact. No second, n8n-emitted event claims a delivery happened with nothing to certify it. Shipped with, and fixed, a bug where the delivery-application node spread the wrong upstream , silently dropping reorder_id/productcode/quantity two nodes before the failure surfaced. Verified separately: the write+event chain (a real bug, now fixed) and the state machine (persists correctly on a real trigger; a manual test run does not persist staticData, which is a testing limitation, not a production one). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -21,6 +21,81 @@ POST /rest/workflows/<id>/activate {"versionId": "<current>"}
|
||||
`PATCH {"active": true}` returns 200 and leaves the workflow inactive, silently:
|
||||
activation has its own endpoint.
|
||||
|
||||
## supply-procurement
|
||||
|
||||
The service's own supplier, simulated. Reacts to `reorder.requested` (emitted
|
||||
by the periodic run below, once it finds a reference under threshold),
|
||||
decides — a fixed lead time, or a 15% chance the supplier cannot fulfil —
|
||||
answers with `reorder.confirmed`/`reorder.rejected`, and on the confirmed
|
||||
delivery date writes the stock itself.
|
||||
|
||||
```
|
||||
reorder.requested {productcode, quantity, requested_at}
|
||||
→ reorder.confirmed {deliver_on} — 85% of the time
|
||||
→ reorder.rejected {reason: "supplier stockout"} — 15%
|
||||
```
|
||||
|
||||
No purchase-order entity, no supplier entity, no negotiation — the whole model
|
||||
is one probability and one lead time, on purpose: this simulates *that*
|
||||
fulfilment can fail, not *why* a given supplier would.
|
||||
|
||||
### One service, two capacities — not two identities
|
||||
|
||||
This is still the supply agent, not a separate "procurement" component. It
|
||||
both requests (the periodic run) and answers its own request (this workflow),
|
||||
under the same Kafka account and the same Keycloak client — the 2026-08-31
|
||||
decision that réapprovisionnement is one service, not a split into a demand
|
||||
side and a supply side.
|
||||
|
||||
### The write goes through the front door
|
||||
|
||||
On the delivery date, this workflow reads the product's current stock and
|
||||
PATCHes it — through Kong, through the same `patch-product` MCP tool a human
|
||||
product manager would use, under a **new, narrow role**: `restocker` may PATCH
|
||||
a product and nothing else, not create, not replace, not delete. It sends
|
||||
`X-Stock-Change-Reason: delivery:<reorder-id>`, which `classic-models-api`
|
||||
only honours in that exact shape; anything else falls back to
|
||||
`manual-adjustment`. The resulting `product.stock_changed` event is the *only*
|
||||
stock-update event — there is deliberately no second, n8n-emitted fact
|
||||
claiming a delivery happened, because nothing would certify it against the
|
||||
one that actually changed the database. See `classic-models-api`'s commit
|
||||
"Accept a delivery reason on a stock PATCH" for the backend side.
|
||||
|
||||
### Two triggers, one shared state
|
||||
|
||||
`$getWorkflowStaticData('global').pendingDeliveries`, keyed by reorder id —
|
||||
the same pattern the stock-watch workflow already uses for its watchlist, now
|
||||
shared across **two** trigger chains in this one workflow: the Kafka-triggered
|
||||
decision writes an entry when the supplier confirms, and a daily schedule
|
||||
(06:15, deliberately after the periodic run's 06:00) reads what is due and
|
||||
applies it. A pending entry is deleted only once the PATCH is confirmed by the
|
||||
API's own response — a failed write leaves it in place so tomorrow's run
|
||||
retries it, rather than losing a delivery nobody applied.
|
||||
|
||||
### A bug this shipped with, and how it was found
|
||||
|
||||
`Apply delivery` sits between `Read current stock` and `Update stock`, and its
|
||||
first version wrote `{ ...$json, new_quantity }` — but `$json` at that point is
|
||||
`Read current stock`'s own output, not the due-delivery record two nodes
|
||||
earlier. `reorder_id`/`productcode`/`quantity` were silently dropped, and
|
||||
`Update stock` failed two nodes later with `"path_productcode" is a required
|
||||
property` — a symptom far from its cause. Fixed by naming the source
|
||||
explicitly: `$('Due deliveries').item.json` for what the delivery *is*, `$json`
|
||||
for what the catalog *says now*. The same rule `check_availability` already
|
||||
follows for `Recent movement` — an implicit `$json` is only ever the
|
||||
*immediately preceding* node, never "whichever upstream node has the field I
|
||||
want."
|
||||
|
||||
### Manual test runs do not persist static data
|
||||
|
||||
Verified the hard way: triggering this workflow via `/rest/workflows/{id}/run`
|
||||
executes for real (the write happens, the event is genuine) but the
|
||||
`pendingDeliveries` mutation is **not** saved back to the workflow — the entry
|
||||
reappears on the next check. Only a genuine trigger firing (a real Kafka
|
||||
message, the real schedule) persists it. A manual run is fine for proving the
|
||||
logic; it is not fine for proving the state machine, and the two were verified
|
||||
separately here.
|
||||
|
||||
## supply-check-availability
|
||||
|
||||
Working. `POST /webhook/check-availability` with the end user's token in
|
||||
|
||||
Reference in New Issue
Block a user