Order Flow Process
Sign in
→ Login page → Users (admin)Every page now sits behind a 4-digit PIN. shared/header.js redirects unauthenticated browsers to /login.html; signed-in users get their role's home page (admin → /, sales → Sales Station, cutting → Material Cutting Station, stock → Warehouse Station). Roles restrict which pages a user can navigate to — the allow-list lives in ROLE_PAGES.
Actor name on every audit event (stock_in/out, order_log, serial allocations) is the signed-in user — the legacy x-sarkar-employee header is overwritten server-side so a rogue client can't spoof identity.
Stored in: auth_users (name + PIN hash + role), signed HttpOnly cookie whose secret lives in auth_meta (survives restart).
Customer & Enquiry
→ Customers pageCustomer profile is created or selected. Address, payment terms, currency, Incoterms and tax ID are stored. This feeds directly into the CIPL generator (Step 9) — consignee + bill-to auto-fill from the customer.
If the order ships to our Plano warehouse (default for any P-prefix job), the Sarkar Tactical Inc. (Plano) contact — Sanjay Sood, 542 Haggard St — is already in the customers + suppliers list.
Create Order
→ New Order pageA new order is created as a draft. Client name, delivery date, currency, Incoterms, payment terms, export licence. No job ref burnt yet.
Export compliance: new Licence number field (ECJU SIEL / OGL) flows onto the CIPL in Step 9. Ship to Plano checkbox auto-ticks for P-prefix job refs — when on, CIPL swaps the consignee to our Plano warehouse address automatically.
Add line items from the catalogue — each line has item, size, colour, quantity, unit price. The catalogue cascading dropdowns prioritise items with stock, and Quick-add item has Category → Subcategory → Item filtering plus a currency picker (£ / $ / €) for the unit price. The SKU-reuse check uses (category, item, size, colour, variant) — the DB unique key — so you don't hit "already exists" errors when typing a new manufacturer.
Line-item qty edits no longer lose focus (the inner table rebuild was fixed); size / colour dropdowns are datalists so you can pick existing values or type anything new (3X-Large, 4XL, any custom colour) — order lines import into the carrier / ballistic job sheets even when the exact variant isn't in the catalogue.
When ready, Save & allocate job ref → picks G (Glasgow) or P (Plano) series → draft becomes an open order with a permanent job ref like G26-029.
Item library prep (one-time per SKU)
→ Catalogue → Data AuditBefore an order can generate sheets automatically, each item involved must have complete data. Every missing field below shows up on the Data Audit page.
Stored in:
catalogue (per variant) — price_currency, warranty_months, warranty_notes are all self-migrated columns.Helpers: ✎ inline edit on category / subcategory / item name straight from the catalogue list, ↔ Copy / Merge folds in imported duplicates, ↔ Resolve duplicates finds pairs to merge, 📦 Open in Stock Manager (green outline on the item profile) jumps to Inventory filtered to this item.
Zero-stock variants get a small delete button on the Inventory variant table so you can prune duplicates without leaving the page.
sap_sqm_per_layer, Fabric Recipe link, Trim Sheet link, preferred SAP recipe, BOM lines to SAP Parts.Stored in:
catalogue.ballistic_front_sqm / .ballistic_back_sqm, catalogue.pattern_sheet_id / .trim_sheet_id, item_sap_solutions, item_bom.UI entry points: ✎ Edit item for the flags, 🧵 Link sheets for the recipe / trim links, the Ballistic card for the preferred SAP recipe.
Inputs: name, product link (from production_products OR any catalogue item), sizes, lines = material × qtyBySize (non-trim: Cordura, Mesh, Polycotton, Riverseal, Foam, Lining), size uplift % (compounded per size-step when an oversized line has no explicit qty — e.g. recipe has 2XL=0.86, order needs 4XL, uplift=5 → 0.86 × 1.05² = 0.949).
Stored in:
pattern_sheets.data (JSON).Seeded from: CARRIER COST v2.xlsx — one recipe per vest/carrier.
Stored in:
sap_configs, sap_variants, sap_layers.Creates real catalogue Parts via Generate Parts — inserts rows in
catalogue (category=Parts, subcategory=SAPs) and writes an item_bom line linking the FG to the part.
Stored in:
sap_item_cost (776 rows, 16 recipes × 32 profiles).Seeded from: Ballistic Costs.xlsx.
Editable from: the Ballistic Recipes page → recipe detail → "Items that use this recipe" → per-size grid editor.
Stored in:
trim_sheets.data (JSON) + optional trim_sheets.product_id FK.Two PDFs: PDF · Table (structured grid) + Sewing Room PDF (one trim per line, human-readable). The "+ Create section" toolbar accepts preset locations or lets you type a custom one; "+ Add trim" button adds line rows.
Generate job sheets from the order
On the order page, the Attached job sheets & documents card has "+" buttons that launch each sheet generator pre-filled (?orderId=N), plus a + Upload file button for PDFs / Word / Excel / images (up to 30 MB). Every sheet can be Sent to Order so the snapshot lives under the order file.
Every sheet has a Preview & download dialog — a modal that mirrors the PDF layout so you can eyeball the finished job + edit any free-text blocks (notes, materials, patterns, composition) before committing to the PDF. Consistent across Carrier, Helmet, Ballistic, and CIPL.
ballistic_front_sqm + ballistic_back_sqm (falling back to legacy sap_sqm_per_layer × panel count) + BOM-linked SAP Part → SAP recipe (via sap_variant_id) + panel (F / B / F&B).Recipe picker: when a line has no favourite recipe set and you pick one, an "Apply to all N other lines" button fans the recipe across the whole sheet in one click.
Calculation: when front/back sqm is set:
(front + back per panel choice) × SAP layer count × qty per material. Otherwise legacy sqmPerLayer × panels × qty.Outputs: Specifications (Item # / Model / Size / Qty / Panel / Construction) + Construction (Materials / Patterns) + per-recipe SAP name — Construction (STRIKE FACE layers table) + SAP name — Stitching + combined Batch & Lot (Material / In stock / Theoretical / Actual / Final / Batch # / Lot # / Other — hand-filled on the floor).
Saved to:
order_attachments (kind=ballistic-job-sheet).
Calculation:
recipe.qtyBySize[line.size] × line.qty per material with the canonSize aliaser (Large ↔ L, 2X-Large ↔ 2XL) + recipe-level size uplift % extrapolation for sizes past the recipe's largest explicit key.Inline "link recipe" picker on any line whose item has no
patternSheetId set — one dropdown applies the chosen recipe to every variant of that item.Outputs: Specification (Item / Model / Size / Qty / Panel / Construction) + Construction (bullet-listed Materials + Patterns labelled "Fabric recipe = X" / "Trim Sheet = Y") + optional Composition (Strike Face + Stitching — editable in the preview) + Batch & Lot + compact Issued sign-off (Operations / Cutting / Sewing). Preview & download PDF wraps everything in a WYSIWYG editor.
Saved to:
order_attachments (kind=carrier-job-sheet).
Auto-serial: when a Job Number is set, every entry gets
{jobRefNoDash}.{yyww}-HEL-{NNN} with a cumulative running seq. Reordering renumbers.Outputs: Preview & download PDFs opens a modal showing the full job summary + editable Notes; footer has Download Job Sheet (transposed Attribute × Style, Serial Range) and Download Stock List (Configurations + Accessories & Components grouped by category).
Saved to:
order_attachments (kind=helmet-job-sheet).
order_items.sku_snapshot + catalogue hydration (ML rating, HS code, item code, unit price per variant with three-tier fallback) + customer profile.Behaviour: picking an order from the dropdown auto-loads — no separate button click. Consignee + bill-to populate from the client; when Ship to Plano is on, the consignee block is overwritten with Sarkar Tactical Inc's Plano address (Sanjay Sood) and destination = United States.
Outputs: Preview & download PDF modal — two-page layout (Commercial Invoice + Packing List) with editable free-text blocks and a Save-to-order button inside the footer.
Saved to:
order_attachments as kind='user-upload' so the generated PDF lives alongside every other order doc.
materials.stock_on_hand) + Trims Stock (trims_stock.stock_on_hand).Calculation:
to order = max(0, needed − in stock), grouped by supplier.Outputs: one table per supplier with Needed / In stock / To order.
Saved to:
order_attachments (kind=order-sheet).
Stored: file on disk at
backend/uploads/order-attachments/ + row in order_attachments with kind='user-upload', file_path, original_name, mime_type, size_bytes.Rendered: clickable
📎 filename link in the attachments table. Delete row → file also removed from disk.
Allocate serials
→ Serial NumbersGenerate Serial Numbers on the order page runs through every order line and maps the item category to a pool code. Lines can be drag-reordered on the order page — drop to renumber from 001 in the new sequence. The refresh semantic purges every serial matching jobRef.* (not just order-linked ones) so orphans left by manual allocations don't shift counts.
Inputs: order id, job ref, line items, pool code per item category (or a per-catalogue-item serial_pool_code override for things like detectors / cable reels / EOD kits).
Calculation: next unused sequence for the (pool, jobRef, yyww) prefix, or a user-typed prefix override for back-filling historical orders.
Custom serials: on the Labels page you can + Add custom serial with any string — useful for back-filling legacy or one-off re-prints.
Manage types: ⚙️ Manage types dialog on /serials.html adds, renames, or archives pool codes (HEL / CAR / SAP / HAP / DET / EOD / …). Codes are immutable once created (already-allocated serials embed them).
Outputs: serials like G26-029.2616-CAR-001 for CAR, HEL (×1 label), SAP (×3 labels — front/back/spare), HAP (×1 label). Printable label sheet PDF.
Saved to: serial_allocations (linked to order_id, with optional label_category_override for the labels grid). Event logged to order_log as serials_allocated.
Label print tracking
→ Labels gridDense orders × label-kinds grid: rows are orders, columns split into SEW ON ribbon (CAR Paper) and STICK ON ribbon → BIG Labels (HAP, BIG SAP) + SMALL Labels (HEL, HEL w/ Visor, SMALL SAP). Auto-classifier routes allocations to the right cell from pool code + assigned-to keywords; per-allocation label_category_override lets operators reclassify.
Per-cell: serial range + total labels (factoring label_repetition), a top-right tick to toggle printed (turns green) without opening a dialog, and a click-through detail popup where you can edit category / assigned-to / repetition, delete serials, or add custom serials. Notes field records "ribbon smudged, re-print" etc.
Top-bar buttons: ✓ ALL PRINTED marks every visible cell printed (respects active filters), 📄 PDF — Print queue exports unprinted cells, 📄 PDF — Printed log exports printed cells (records / archive).
Stored in: order_label_prints (order_id × category → printed, notes, updated_by).
Procurement execution
→ SuppliersThe Order Sheet from Step 4 tells you exactly which suppliers to contact and what quantities to request. One PDF per supplier.
Inputs: Order Sheet's procurement rows (from order_attachments).
Stored: supplier profiles in suppliers — contact details, MOQ, payment terms, category tags.
When stock arrives: log via Warehouse Station (/warehouse-station.html) or Material Cutting Station. Writes to stock_in (for items) or material_deliveries (for fabrics). v_dashboard re-aggregates live.
Production + material usage
The cutting / sewing / assembly teams work from the PDFs generated in Step 4. Materials get drawn from stock as production progresses.
Inputs: Ballistic Job Sheet PDF (for cutting room) + Carrier Job Sheet PDF (for sewing) + Helmet Job Sheet PDF (for assembly) + Serial labels.
Stock out events:
- Factory floor logs cuts in Material Cutting Station → writes
material_usagerows (per-size breakdown), deducts frommaterials.stock_on_hand. - Warehouse logs dispatch of FG parts via Warehouse Station → writes
stock_out.
Every action against the order (attachment added, serials allocated, status change) appends a row to order_log and surfaces in the Order log card on the order page.
Quality & Packing (future)
Hooks are in place; dedicated pages still to build.
- Stock check list per order — compare order lines vs
v_dashboardstock on hand. Warehouse ticks off each item. - Finished goods check list — every serial from
serial_allocationschecked against physical labels. - Net weight per item — already stored in
catalogue.net_weight, feeds packing declarations.
Commercial documents & dispatch
→ CIPL generatorCommercial Invoice + Packing List is now live as a single two-page PDF — see Step 4's CIPL sub-item. Everything auto-fills from the order header + order_items + catalogue hydration, so a typical shipment is: pick order → preview → download. Shipments through the Plano warehouse get Sanjay's address on the consignee block automatically from the Ship-to-Plano flag.
Still manual: box allocation (pcs/ctn, net/gross weights, dimensions) — the CIPL seeds one carton per line item and the operator fills in actual weights as boxes get packed.
Order status moves: draft → open → in_production → shipped → closed. Every status change is logged in order_log.
Year-end tally: the Shipped Orders page (/completed.html) rolls live shipped orders + legacy CSV imports into a per-category / per-item tally. Grouping toggles, category filter, CSV export, year picker.
Data lineage — what feeds what
Customer → Order
From: customers. Into: orders.customer_id, .client_name, .currency. → typed client names auto-create/link a customer row.
Order → All 4 job sheets
From: orders + order_items.sku_snapshot. Filter: ballistic lines → Ballistic / Carrier / Order sheets; helmet lines → Helmet sheet. Saved: order_attachments with snapshot JSON.
Fabric Recipe → Carrier + Order sheets
Editor: Fabric Recipes. Linked via: catalogue.pattern_sheet_id. Stored: pattern_sheets.data (sizes + lines[{materialId, qtyBySize}]). Used by: Carrier Job Sheet rollup, Order Sheet procurement. Legacy item_fabric still read as a fallback when no recipe is linked.
SAP recipe → Ballistic sheet
From: sap_configs + sap_variants + sap_layers. Linked via: catalogue.sap_variant_id on generated SAP Parts → item_bom from FG to part. Cross-linked: recipe detail shows every item that uses it; catalogue item's Ballistic-costs card links back to the recipe.
Ballistic costs → Catalogue + Recipe pages
Stored: sap_item_cost (recipe_name × item_profile × size → sqm / kg / £ / labour). Seeded from: Ballistic Costs.xlsx (776 rows). Shown on: the catalogue item's Ballistic costs card (with sort / filter / compare), and the Ballistic Recipes detail's "Items that use this recipe" editor.
Ballistic front/back sqm → Ballistic sheet
Stored: per-variant catalogue.ballistic_front_sqm + .ballistic_back_sqm. Used by: Ballistic Job Sheet formula (front + back per panel choice) × SAP layer count × qty. Falls back to legacy sap_sqm_per_layer × panel multiplier.
Trim sheet → Carrier + Trim Calculator
Linked via: item_trim_sheets (many-to-many) + legacy catalogue.trim_sheet_id primary. Stored: trim_sheets.data (JSON of sections × lines × per-size lengths). Sheets are now colour-agnostic — colour is applied at calc time via trim_colour_defaults (garment colour → trim colour mapping; e.g. MTP → Olive Green). Rolled up by (type, width, colour, size) on the Trim Calculator.
Trim Calculator → Costing + new sheets
Reads: orders.items.sku (or manual rows), item_trim_sheets, trim_sheets.data, trim_colour_defaults, trims_stock. Outputs: per-bucket length / pieces, vs-stock cost. Save as new sheet writes back to trim_sheets with per-line overrides baked into lengthBySize. Length-based + count-based trims handled separately (Buckle/Button/etc. roll up qty rather than length).
Materials Stock + Trims Stock → Order Sheet
Stock from: materials.stock_on_hand + materials.supplier / trims_stock.stock_on_hand + .supplier. Calc: to order = max(0, needed − in stock), grouped by supplier.
Order → Serials
From: orders.job_ref, order_items.sku_snapshot.category (→ pool code). Stored: serial_allocations. Logs: order_log event serials_allocated.
Attachments → Order log
Every Send-to-Order writes a JSON snapshot to order_attachments. + Upload file writes a binary + metadata row (kind=user-upload) and the file lands under backend/uploads/order-attachments/. Every add/remove appends a row to order_log. Finalise logs finalised. Serials log serials_allocated.
Helmet Job Sheet → Two PDFs
Entries: per-physical-helmet configuration with auto-generated serials ({jobRefNoDash}.{yyww}-HEL-{NNN}, cumulative seq). PDF 1 — Job Sheet: transposed Attribute × Style table, Serial Range row. PDF 2 — Stock List: Configurations + Accessories & Components rolled up for the warehouse. Drag-to-reorder reshuffles serial assignment. Saved profiles enable quick-add of common configurations (Leicester Police, UNICEF, …).
Catalogue → Stock dashboard
Every SKU variant → aggregated in v_dashboard. stock_in / stock_out transactions → live on-hand counts.
Data Audit → Order-readiness
Reads: catalogue, item_bom, item_images. Flags: missing HS/ML/price/weight/fabric-matrix/SAP-BOM/trim-sheet. Target = 100% before trusting auto-sheet generation.
Three weights → Export docs
Per-variant fabric_weight_kg (non-trim fabric only, from CARRIER COST v2), net_weight (complete product without packaging), and gross_weight_kg (product + packaging). Net feeds export "Net mass"; gross feeds packing lists.
Snapshots → Future costings
Every sheet stores its computed rows inside order_attachments.snapshot. Costings will query these + material prices without re-running calculations.
Infrastructure & operations
Hosting
Node + MySQL on the Dell (192.168.1.31:3001). Public access via a Cloudflare Quick Tunnel — cloudflared runs in a detached tmux session + a @reboot crontab entry so it auto-starts. URL rotates on restart (no domain needed).
Backups
USB stick auto-mounted at /mnt/backup-usb. Cron runs ~/backup-db.sh hourly at 0 * * * * — retention is 48 hourly + 14 daily + 12 monthly (rolling). Logs to ~/backup-db.log.
Deploy
./push.sh from the Mac repo root does rsync + npm install --omit=dev (only on package.json change) + sudo -n systemctl restart sarkar-stock + a /api/health curl smoke-test. Idempotent, safe to re-run.
Auth & roles
4-digit PIN login per user; signed HttpOnly cookie (secret in auth_meta). Roles: admin (full) · sales (Sales Station only) · cutting (Materials Station + Cutting Log) · stock (Warehouse Station) · user (admin-equivalent, no user-admin UI).
Mobile
Two-tier responsive stack (≤900 px + ≤640 px). Page headers stack, form-grids collapse to one column, tables get an overflow-scroll swipe hint, dialogs become full-screen sheets on phones. 40 px tap-target minimum on buttons. iOS-safe input font-size (15 px+) stops auto-zoom.
Steps still to build
Costings engine on top of attachments
Read order_attachments.snapshot (kind=ballistic/carrier/order-sheet) + materials.unit_price + trims_stock.unit_price → cost per line + total cost. All input data already captured.
Auto stock-out on job-sheet "Send to Order"
When a carrier sheet is attached, optionally write material_usage rows for its fabric rollup. One flag on the sheet to toggle.
Stock check list per order
Printable checklist: order lines vs v_dashboard. Warehouse ticks off each item. One view query away.
Server-side role gating
Role allow-lists are client-side only today — the API is still wide-open to any signed-in user. Wire the allow-list as middleware on /api/* routes.
Stable public URL + Access gate
Cloudflare Quick Tunnels rotate URL on each restart. Upgrade to a named tunnel + Cloudflare Access (email-OTP) when a domain's available — needs a £~8/yr domain on Cloudflare.
Purchase order generator
Read an Order Sheet snapshot → emit one PO PDF per supplier with their procurement row totals + MOQ checks + payment terms pulled from suppliers.