This plan adds a Production Schedule tab to the existing delivery tracker, built around a day-by-day cut board with the shop's real capacity: two poles per day, plus a third slot for overflow and overtime. It captures date flexibility and rush status at order entry, and pushes Teams notifications for both production changes and delivery tracker events. It is written so either Jordan or Claude can build it directly from this document. Rev 2 (Alex's feedback, Aug 20): week grid replaced with the daily slot board, and Teams notifications extended to cover the whole delivery tracker plus a morning digest.
The core architecture call: one database, two views. No sync.
The delivery tracker is a Cloudflare Worker with a D1 database and a single-file frontend. The right way to "sync" a production schedule with it is to not build a second system at all. The Production Schedule becomes a new tab inside the same app, reading and writing the same order records. There is exactly one copy of every order, so the tracker and the schedule can never disagree.
The app already refreshes itself every 30 seconds on every open screen, so when production changes a date, every sales and warehouse screen updates within half a minute with no extra plumbing. The new work is the tab itself, four new order fields, and a notification layer, not a sync engine.
What already exists that we build on
- Pole line items already carry pole type (Stationary, Revolving, Winch, Cam Cleat, Reinforced Winch, Reinforced Cam Cleat), so the tool can tell fabrication work from pick-and-pack automatically. SKU entry auto-fills these from the embedded catalog.
- A "Scheduled Production Date" field already exists on every order but no screen currently uses it. It becomes a derived value (the order's first cut slot) once the cut board lands, so the Deliveries tab shows it automatically.
- A status ladder already exists: New Order, Acknowledged, In Production, Ready, Shipped, Issue.
- A change-flag mechanism already exists (orders edited after a confirmed ship date get a "Changed" badge with an acknowledge button). We extend it rather than invent a new one.
- Sales notes and warehouse notes streams already exist on every order.
How an order flows through the system
Sales and warehouse keep living in the Deliveries tab. Production lives in the Production tab. Nobody changes how they work today except for the three new inputs at order entry.
What counts as production work (the filter rule)
| Pole type on the order line | Production schedule? | Why |
|---|---|---|
| Stationary (external halyard) | No | Pick and pack only, no fabrication |
| Revolving (external halyard) | No | Pick and pack only, no fabrication |
| Winch (internal halyard) | Yes | Door cut, internal assembly |
| Cam Cleat (internal halyard) | Yes | Door cut, internal assembly |
| Reinforced Winch | Yes | Door cut, reinforcement, assembly |
| Reinforced Cam Cleat | Yes | Door cut, reinforcement, assembly |
- Dropship (vendor-direct) orders are excluded entirely. The vendor builds them; there is nothing for the Acworth shop to cut.
- Beacons do not create production work for this schedule. They ship with the order and are installed on site.
- Mixed orders (say one Reinforced Winch plus two Stationary poles) appear on the production schedule because the whole order ships together. The fabricated lines are shown bold as cut work; the pick-and-pack lines are listed muted so the shop sees the complete order without treating those lines as jobs.
- An order drops off the production schedule when its status reaches Ready (it moves to warehouse territory), and stays visible in a collapsed "Recently completed" strip for a week.
Order form changes: flexibility and rush
Two additions to the existing New Delivery form (nothing else on the form moves). The date flexibility question is required, so sales must consciously answer "how firm is this date?" on every order. That one answer is what lets production safely move flexible orders around and protect the firm ones.
The three flexibility answers
| Answer | Badge | What it tells production |
|---|---|---|
| Hard date, must arrive on this date | HARD DATE | Crane booked, ceremony scheduled, penalty clause. Do not move. Every rush order is automatically a hard date. |
| Deliver by, this date or earlier is fine | BY SEP 30 | The GC's "just get it here by end of September." Production can cut it early to fill slack weeks, but the date is still a real deadline. |
| Flexible, coordinate with customer | FLEXIBLE | Customer is easygoing or the site is not ready. These are the orders production moves first when a crunch hits. Notes say who to call. |
The Production Schedule tab
A new tab between Deliveries and Beacon Returns, showing only orders with fabrication work. The heart of it is a day-by-day cut board built on the shop's real capacity: two poles per normal day, plus a third slot for overflow and overtime. Every fabricated pole becomes one cut job that occupies one slot on one day (a line with quantity 2 becomes two jobs), so the board answers the question the shop asks every morning: what are we cutting today?
Deliver Sep 4 · Ship by Aug 28
Deliver Aug 28 · Ship by Aug 21
"GC says anytime before end of Sept"
How the cut board works
- One pole, one slot. Every fabricated pole on an order becomes a cut job. A 2-pole line becomes two jobs shown as "(1 of 2)" and "(2 of 2)". Multi-pole orders naturally span slots and days, and the board stays an honest picture of what the shop can actually do.
- Slots 1 and 2 are the normal day. Slot 3 is amber and labeled OT: it exists so overflow and overtime are a visible, deliberate decision instead of a surprise. The board never hides overbooking.
- Scheduling is click-based: click an open slot and pick from the queue, or click "Assign slot" on a queued pole and it suggests the earliest open slot that makes its date. Click a scheduled job to move it, unschedule it, or mark it cut. (Drag and drop is a phase 2 polish, not a phase 1 requirement.)
- Marking work done: a cut job turns green when marked cut. When all of an order's jobs are cut, the app prompts to advance the order status (In Production to Ready). The order's "Scheduled Production Date" field is auto-derived from its first slot so the Deliveries tab stays informative with zero extra data entry.
- The queue ("Poles needing a slot") is sorted by latest safe cut date and shows at-risk chips, the flexibility badge, and the customer's own words from the flexibility notes. The card header always shows the next open slot.
- Rush jobs are red on the board and the order stays pinned in the rush section until it ships.
- Paging: previous week, this week, next week. Saturday is hidden by default and can be toggled on for a week when the shop plans weekend work (decision 5 below).
- Filters: search box plus a pole-type filter (All, Winch, Cam Cleat, Reinforced).
The date math (all constants configurable)
- must_ship_by = confirmed ship date if set, otherwise requested delivery date minus transit buffer (default 5 business days for freight, 0 for pickup and local delivery).
- latest_safe_cut_date = must_ship_by minus fab lead time (default: needs Marshall's number, placeholder 3 working days).
- At risk = an unscheduled pole whose latest safe cut date is earlier than the earliest open slot (or already past), or a scheduled job sitting later than its latest safe cut date.
- For "Deliver by" orders the dates are deadlines with room in front; for "Flexible" the math is advisory and the badge signals that the customer can absorb movement.
- Capacity constants (2 normal slots, 1 OT slot, working days Mon to Fri) live in one config block so they can change without a redesign if the shop adds capacity.
Delivery tracker changes (what sales and warehouse see)
- Rush orders: red left edge and a RUSH badge on the dashboard weekly rows and All Orders, plus a rush count KPI. Impossible to miss.
- Flexibility badge (HARD DATE BY SEP 30 FLEXIBLE) next to the requested delivery date everywhere it appears, with the note on hover and in the order detail.
- Date-change badges get before and after values. Today the badge says only "Changed: bookedShipDate." It will say Ship date: Aug 26 → Sep 2 with the reason, who changed it, and the existing acknowledge button. Unacknowledged changes stay highlighted for sales.
- No changes to Beacon Returns, All Orders columns, export and import, or the warehouse workflow. Everything here is additive.
Proactive notifications
The tracker today only shows changes to whoever happens to open it. The new layer pushes updates for the whole tracker: production changes, new orders, shipments, and a morning digest. Recommended channel: Microsoft Teams, since FlagCo already lives in M365 and it needs no new accounts. Jordan creates a "Post to channel when a webhook request is received" workflow on a #flagco-orders channel (sales, warehouse, Marshall, Alex all members) and the Worker posts to that URL. Per-salesperson email is a clean phase 2 (needs an email API such as Resend).
| Event | Who cares | Delivery |
|---|---|---|
| New order entered | Warehouse + production see incoming work in real time | Instant post (compact): SO, customer, poles, method, requested date with flexibility badge |
| Rush order created, or an existing order upgraded to rush | Production (Marshall) + warehouse | Instant post, red, + pinned rush section in the app |
| Confirmed ship date changed (the headline feature) | Salesperson + warehouse | Instant post with before and after, who, and the reason + in-app badge until acknowledged |
| Order shipped (status to Shipped or tracking added) | Salesperson, who can forward tracking to the customer immediately | Instant post with tracking number |
| Status set to Issue | Salesperson | Instant post with the issue note (today this is in-app only) |
| Ship date first set, expected delivery set, delivery complete | Routine progress | Morning digest only, to keep the channel quiet |
| Cut slot assigned or moved | Shop-internal detail | Order history log + morning digest only |
| Late order (past booked ship date, not shipped) | Everyone | Morning digest, red section |
| Morning digest, weekdays 7:00 AM | The whole team's stand-up view | One post: cutting today (with open slots), shipping today, late orders, poles needing a slot, rush queue |
How a date change is captured
- Someone edits the confirmed ship date (from either tab).
- The app asks for a one-line reason. Recommended rule: required when the date moves later on a confirmed order, optional when it moves earlier.
- The Worker compares old and new values on the server, writes an event row (who, field, before, after, reason, timestamp), and posts to the Teams webhook. Doing the diff server-side means it works no matter which screen, or future tool, makes the change.
- The order gets the before-and-after badge until sales acknowledges it, exactly like today's change flags.
Who is "who": identity
The app currently has no login, so it cannot know who made a change. Two-step fix:
- Phase 1, lightweight: a "Working as" name picker in the top bar (same names as the salesperson dropdown, plus Production and Warehouse), remembered per browser. Zero friction, good enough for attribution.
- Phase 2, real: Cloudflare Access in front of the app restricted to @flagco.com (already recommended in the deployment doc, free at this team size, no code for a login screen). The Worker then reads the authenticated email from a header and attribution becomes automatic and trustworthy.
Data model and code changes (for whoever builds it)
D1 migration
-- migration-production-schedule.sql ALTER TABLE deliveries ADD COLUMN date_flexibility TEXT DEFAULT ''; -- 'hard' | 'deliver_by' | 'flexible' ALTER TABLE deliveries ADD COLUMN flexibility_notes TEXT DEFAULT ''; ALTER TABLE deliveries ADD COLUMN is_rush INTEGER DEFAULT 0; ALTER TABLE deliveries ADD COLUMN rush_reason TEXT DEFAULT ''; -- Order event log: powers notifications, before/after badges, and an audit trail CREATE TABLE IF NOT EXISTS order_events ( id TEXT PRIMARY KEY, delivery_id TEXT NOT NULL, so_number TEXT DEFAULT '', event_type TEXT NOT NULL, -- 'ship_date_changed' | 'rush_set' | 'rush_cleared' | 'issue' | 'production_date_changed' | 'status_changed' old_value TEXT DEFAULT '', new_value TEXT DEFAULT '', reason TEXT DEFAULT '', actor TEXT DEFAULT '', notified INTEGER DEFAULT 0, created_at TEXT NOT NULL DEFAULT (datetime('now')) ); CREATE INDEX IF NOT EXISTS idx_events_delivery ON order_events(delivery_id); -- Cut jobs: one row per fabricated pole unit, drives the day board CREATE TABLE IF NOT EXISTS cut_jobs ( id TEXT PRIMARY KEY, delivery_id TEXT NOT NULL, so_number TEXT DEFAULT '', pole_line_index INTEGER DEFAULT 0, -- which line on the order unit_index INTEGER DEFAULT 1, -- 1..qty for multi-quantity lines pole_desc TEXT DEFAULT '', -- denormalized: "35' Winch, 6 x 0.188, Satin" cut_date TEXT DEFAULT '', -- empty = in the "needs a slot" queue slot INTEGER DEFAULT 0, -- 1, 2 = normal day; 3 = overtime done INTEGER DEFAULT 0, created_at TEXT NOT NULL DEFAULT (datetime('now')), updated_at TEXT NOT NULL DEFAULT (datetime('now')) ); CREATE INDEX IF NOT EXISTS idx_cutjobs_date ON cut_jobs(cut_date); CREATE INDEX IF NOT EXISTS idx_cutjobs_delivery ON cut_jobs(delivery_id);
Worker changes (src/worker.js)
- Add the four new columns to the existing INSERT, UPDATE, import, and rowToDelivery mappings.
- In the PUT handler, read the existing row first and diff booked_ship_date, is_rush, pole_status, and tracking against the incoming body. Write order_events rows for real changes; POST of a new order writes a created event.
- Cut job lifecycle: on order create or edit, sync cut_jobs to the fabricated pole lines (create jobs for new units, keep scheduled ones untouched, and flag rather than silently delete a scheduled job whose pole line was removed). New endpoint PUT /api/cutjobs/:id assigns date and slot or marks done, with a server-side conflict check so two jobs can never hold the same slot. GET /api/state grows a cutJobs array.
- On notifiable events (per the trigger table and config map), fetch() the Teams workflow URL stored as a Worker secret (TEAMS_WEBHOOK_URL, set with wrangler secret put). Wrap in try/catch so a Teams outage never blocks a save.
- Morning digest: a scheduled cron in wrangler.toml (crons = ["0 11 * * 1-5"], UTC, about 7:00 AM Eastern) queries D1 and posts the digest card.
- New endpoint GET /api/events?delivery_id= for the order history panel.
- Accept an X-Actor header from the frontend (the "Working as" name) for event attribution.
Frontend changes (public/index.html)
- New Production tab: nav button, view section, renderProduction() with the day cut board (5 day columns, 3 slots each, click-to-assign and mark-cut), the rush section, and the needs-a-slot queue. Fab filter: poleType in Winch, Cam Cleat, Reinforced Winch, Reinforced Cam Cleat; skip dropship orders.
- Derive the order's productionDate from its earliest cut job so the Deliveries tab and existing form field stay accurate without manual entry.
- Form: rush toggle (with required reason when on), required "How firm is this date?" select, flexibility notes input next to the requested date.
- Badges: rush, flexibility, and before-and-after change badges on dashboard rows, All Orders, and the production tab. Extend detectChanges() to store {field: {from, to}} instead of field names only.
- Reason prompt when the confirmed ship date changes on an order that already had one.
- "Working as" picker in the top bar, stored in localStorage, sent as X-Actor.
- Help tab: add a short "Production Schedule" section mirroring the existing help style.
Build plan and phases
| Phase | Contents | Notes |
|---|---|---|
| 0. Sync with Jordan | Pull the live index.html + worker.js from Jordan's deployment, diff against the shared-folder copy, agree on the handoff (Claude builds on a copy and Jordan reviews and deploys, or Jordan builds from this spec). | Jordan owns the live app; the shared-folder copy is a few months old. |
| 1. Core build | Migration, form fields (rush + flexibility), Production tab with the daily cut board (2 + 1 OT slots), badges on the tracker, server-side event log, Teams notifications including the morning digest, "Working as" picker. | Everything in sections 4 through 8. Testable locally with wrangler dev before touching production data. |
| 2. Nice-to-haves | Drag-and-drop on the cut board, per-salesperson email notifications, printable daily cut sheet for the shop floor, Cloudflare Access login, Saturday planning controls, slot weighting if some poles turn out to be half-day or multi-day jobs. | Only after phase 1 has run for a few weeks and Marshall's team has feedback. |
| Explicitly out of scope | Business Central integration. The tracker stays manual entry, in parallel with BC, exactly as today. A BC order feed is a separate future conversation. | Keeps this project small and shippable. |