Architettura OMS
Runtime, isolamento tenant, permessi, superficie API, idempotenza e outbox dell'OMS.
L'OMS FireFeed è un modulo TypeScript/Next.js integrato nella piattaforma, non un servizio o una dashboard separata. Gestisce il ciclo operativo successivo all'acquisizione dell'ordine e delega gli effetti remoti affidabili ai worker tramite outbox.
Questa pagina è il catalogo tecnico manuale dell'API OMS. Il generatore OpenAPI del repository copre attualmente soltanto le definizioni del Feed Manager web e del worker; le route
packages/oms/app/api/**non entrano ancora negli spec generati.
Runtime e proprietà dei dati
Browser / client OMS
|
v
Next.js OMS: pagine + route API
|--- schema pubblico: identità, company, entitlement, progetti, connessioni
|--- schema oms_<companyId>: ordini, stock, spedizioni, resi, eventi, outbox
|
+--- commit dominio + outbox --- BullMQ (wake-up/scheduling) ---> worker
| |
v v
provider operationsPostgreSQL è la fonte di verità. BullMQ trasporta il segnale di lavoro e la pianificazione, ma non sostituisce lo stato persistente dell'evento.
Ogni richiesta autenticata risolve sessione, company attiva, entitlement al modulo OMS e permesso di lettura dell'area. Alla prima necessità lo schema tenant oms_<companyId> viene creato o verificato. I dati dinamici di prodotto del Feed Manager restano invece nello schema company_<companyId>.
Disponibilità dei provider
La sola esistenza di un adapter non rende disponibile una connessione. Il resolver combina:
- implementazione e stato di readiness (
ready,controlled,beta,blocked); - entitlement del tenant e feature flag o allowlist;
- capability dichiarate dal provider;
- capability effettive della connessione.
Le API di creazione non permettono di attivare subito un canale. L'attivazione richiede un connection check riuscito e ancora valido; modificare configurazione o API key disabilita il canale e invalida l'health precedente.
Autorizzazione e capability
La lettura richiede accesso all'area OMS. Ogni mutazione richiede anche il permesso write; il ruolo o la capability non possono aggirare un'area in sola lettura.
| Capability | Ruolo minimo | Operazione protetta |
|---|---|---|
order.confirm | Member | Conferma ordine |
order.ship | Member | Spedizione ordine |
order.send_supplier | Member | Invio al fornitore |
order.edit | Admin | Modifica ordine |
order.cancel | Admin | Annullamento ordine |
return.receive | Member | Ricezione reso |
return.approve | Admin | Approvazione reso |
return.reject | Admin | Rifiuto reso |
return.refund | Admin | Rimborso |
return.configure_portal | Admin | Configurazione portale resi |
inventory.configure | Admin | Policy e binding di pubblicazione stock sui canali |
automation.configure | Admin | Configurazione automazioni |
Le mutazioni prive di una capability dedicata usano il controllo OMS write generale. Per
esempio, i movimenti fisici di magazzino richiedono OMS write ma non
inventory.configure, che protegge la configurazione outbound del canale. Gli Owner
hanno il rango più alto. Gli endpoint pubblici del portale resi e i webhook inbound sono
eccezioni ristrette: usano token o segreti della connessione, non una sessione utente.
Superficie API
Le route sono sotto /api nell'app OMS. La tabella raggruppa la superficie funzionale, senza elencare ogni parametro o variante dinamica.
| Area | Route principali | Responsabilità |
|---|---|---|
| Canali | /channels, /channels/[id], /sync, /test, /inbound-webhook, /inventory-policy, /inventory-bindings, /connections | Configurazione, health, attivazione, sync e policy stock. |
| Webhook commerce | /webhooks/commerce/[endpointId] | Ingresso pubblico verificato per eventi del provider. |
| Ordini | /orders, /orders/stats, /orders/export, /orders/[id], /confirm, /cancel, /ship, /label, /tracking/refresh, /send-supplier | Query, transizioni ordine e comandi provider. |
| Fulfillment | /orders/[id]/fulfillment, /fulfillment/steps/[stepId]/scan, /complete | Routing e avanzamento picking/packing. |
| Clienti | /customers, /customers/[id], /customers/import/preview, /customers/import/[batchId]/commit | Anagrafica e import controllato. |
| Inventario | /warehouses, /inventory, /inventory/[id]/mutations, /inventory/transfers, /reservations, /stocktakes | Ledger, movimenti, trasferimenti, reservation e inventari. |
| Documenti magazzino | /warehouse-documents, /warehouse-documents/[id]/pdf | Registrazioni operative PZ, WZ e MM. |
| Spedizioni | /shipments, /shipments/export, /shipments/push-store, /shipments/[id], /label, /op, /recover-label | Colli, etichette, tracking e recovery corriere. |
| Resi | /returns, /returns/export, /returns/portal-settings, /returns/[id], /approve, /reject, /receive, /refund, /refund-resolution | Workflow reso, stock e rimborso. |
| Portale resi | /public/returns/lookup, /public/returns, /public/returns/[company]/[token] | Lookup, apertura e consultazione token-scoped. |
| Automazioni | /automations, /automations/[id], /dry-run, /events, /runs | Regole revisionate, simulazione ed esecuzioni. |
| Operations | /operations/blockers/[id], /operations/outbox/[id]/retry e route di risoluzione marketplace/corriere | Retry ammessi, riconciliazione e chiusura blocker. |
Le route validate restituiscono errori coerenti con il controllo applicato: 402 per modulo non incluso, 403 per accesso o capability insufficiente e 409 per conflitti di stato, fingerprint o concorrenza.
Idempotenza e concorrenza
L'idempotenza è definita per comando, non affidata a un retry generico:
- la creazione canale richiede un header
idempotency-keyUUID e salva il fingerprint della richiesta; lo stesso payload restituisce la risorsa esistente, un payload diverso produce409; - le mutazioni inventario richiedono una chiave nel body e applicano ledger e documento di magazzino nella stessa transazione; la risposta segnala se è un replay;
- i rimborsi normalizzano il payload e ne confrontano il fingerprint; un replay equivalente non duplica l'effetto, uno incompatibile viene rifiutato;
- le transizioni già raggiunte, come la conferma ordine, rispondono in modo idempotente;
- le automazioni usano
clientRequestId, revisione immutabile e lock token per evitare aggiornamenti persi.
Quando una route accetta una chiave, il client deve riutilizzare la stessa chiave soltanto per lo stesso comando logico. Riutilizzarla con un payload diverso è un conflitto, non una richiesta di overwrite.
Outbox e riconciliazione
Per gli effetti importanti, la transazione applicativa salva insieme stato dominio, evento e record outbox. Il worker reclama il record con token e lease, registra i tentativi e applica la tassonomia di retry del provider.
comando API
-> transazione: aggregate + domain event + outbox
-> wake-up worker
-> claim con lease
-> chiamata provider
-> conferma: stato terminale
-> errore retryable: nuovo tentativo pianificato
-> esito ambiguo: UNKNOWN / blocker -> riconciliazione operativaUn esito UNKNOWN non viene ritentato alla cieca: una seconda scrittura potrebbe duplicare spedizione, rimborso o aggiornamento marketplace. Le route operations consentono retry solo per eventi eleggibili e registrano le risoluzioni manuali.
OpenAPI e fonte di verità
Il comando pnpm gen:openapi carica oggi soltanto:
packages/web/lib/openapi/**/*.tsper lo spec web;packages/worker/src/openapi/**/*.tsper lo spec interno worker.
Gli output sono packages/docs/public/openapi/web.json e packages/docs/public/openapi/worker.json. Fino all'estensione del generatore, il catalogo OMS in questa pagina è mantenuto manualmente e l'implementazione delle route sotto packages/oms/app/api/** resta la fonte di verità per payload e comportamento puntuale.
Limiti intenzionali e debito noto
- Il catalogo prodotto appartiene al Feed Manager e non viene replicato come sottosistema OMS.
- I documenti PZ/WZ/MM non hanno valore fiscale.
- La disponibilità generale di un adapter richiede ancora test contrattuali con sandbox o credenziali live, anche quando il flusso applicativo è implementato.
- La generazione OpenAPI OMS e alcune viste di dettaglio operative sono consolidamenti ancora aperti.
- I casi multi-unità e le ricezioni parziali richiedono comandi e aggregate più granulari prima di essere estesi.