v1.18.0: backorders, notifications, dispatcher reliability
Backorders, notifications, and dispatcher reliability
Three threads. A partial-fulfillment workflow lets an operator ship what's pickable and spin the shortfall into a backorder that waits for stock and rejoins the picking queue on its own when a receipt makes it whole. Per-warehouse Microsoft Teams notifications fire on the backorder lifecycle so the floor hears about shorts and restocks without watching a screen. And the outbound webhook dispatcher is hardened against the failure mode where one stuck delivery silently blocks an entire subscription.
Mobile. Zero mobile/ diffs. The v1.14.0 build (versionCode 8) remains current; no new APK.
Added
- Partial-fulfillment + backorders (#384):
POST /admin/sales-orders/<id>/partial-fulfillshorts the unshippable lines and creates a backorder SO (order_type='backorder',status=WAITING_STOCK,parent_so_idback-pointer, full address copy, NULL totals); the original continues through pack/ship, and a SHORT inventory adjustment is written when on-hand stock exists for a shorted line. BO chaining is capped at one level; refund SOs are rejected.POST .../cancel-backordercancels a BO with a reason; the shared cancel service gained a parent-cancel cascade. The receiving loop matchesWAITING_STOCKbackorders on every receipt and flips a fully-satisfiable one to OPEN. A/backordersdashboard (Waiting / Ready-to-ship) and a "Partially Fulfill" sub-modal front the workflow, gated by a newbackorderspage key. Eventsbackorder.opened/backorder.fulfillable/backorder.cancelledregister at v1. - Teams notification webhooks (#385): per-warehouse Microsoft Teams destinations for the
backorder.*family. A MessageCard builder (one variant per event, with an "Open in Sentry" deep-link), an SSRF-guarded sender, admin CRUD (list / create / PATCH / rotate-url / delete / test-send) gated by a newnotificationspage key, and a/notificationsadmin page. URLs are Fernet-encrypted at rest and never returned in plaintext. Each emit dispatches its card after commit on a daemon thread; failures are isolated per-webhook and the outbox row persists for replay. Cards carry item names alongside SKUs.
Fixed
- Webhook dispatcher reliability (#386): four hardening fixes against silent head-of-line blocking. (1) Dispatch-time DNS resolution moved inside the wall-clock watchdog so a hung
getaddrinfofails as a timeout instead of wedging a deliveryin_flightforever. (2) A heartbeat-driven reaper reclaims rows stuckin_flightpastDISPATCHER_INFLIGHT_RECLAIM_S(default 120s), not just at boot. (3) Permanent failures (4xx except 408/429, ssrf_rejected) fast-DLQ on attempt 1 instead of burning the full ~15-hour retry schedule; transient failures keep the schedule. (4) A dispatcher self-monitor posts straight to Teams when a delivery stalls, the DLQ grows, a subscription lags, or an auto-pause fires; opt-in, de-spammed by transition + cooldown. NewDISPATCHER_INFLIGHT_RECLAIM_SandDISPATCHER_HEALTH_*env vars.
Migrations
- 067 --
sales_ordersgainsbackorder_opened_at,backorder_fulfillable_at,cancellation_reason;order_typeCHECK adds'backorder';WAITING_STOCKstatus +SHORTadjustment reason documented (app-enforced, no DB CHECK);ix_sales_orders_waiting_stockpartial index. - 068 --
notification_webhookstable: per-warehouse Teams destinations with a Fernet-encrypted URL.
Upgrade notes
- Apply migrations 067 and 068.
- New optional dispatcher env vars (
DISPATCHER_INFLIGHT_RECLAIM_S,DISPATCHER_HEALTH_*) have safe defaults; see.env.example. The self-monitor is inert until adispatcher.*Teams channel is configured.