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. Rev 3 (Alex, Aug 27): sales-facing estimated ship dates and expedite headroom, computed live from the cut board.
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) | Yes, as prep | Pick and pack on the day's prep lane; consumes no cut slot (Alex, Aug 27: all poles pass through production) |
| Revolving (external halyard) | Yes, as prep | Pick and pack on the day's prep lane; consumes no cut slot |
| 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 and solar kits build on their own beacon bench (see the beacon bench section below): variable-time assembly on a flow board, separate from the pole slots. They still install on site; the bench covers the shop assembly only.
- All poles pass through production (Alex, Aug 27). Internal halyard poles are cut jobs that consume the 2-per-day slots. External halyard poles are prep jobs on each day's prep lane: production schedules and completes them, but they never consume a cut slot, so cut capacity stays honest. Both kinds get their ship-by set by production, both drag and drop, and the tool refuses cross-kind drops (a prep pole cannot land on a cut slot and vice versa).
- Future orders stay parked (Alex, Aug 27): orders with a requested delivery beyond the four-week window and no ship-by yet sit in a "Future orders, tracking only" card on the Deliveries page, create no production jobs, and enter the queue when they near or get a ship-by.
- Fiberglass poles are never fabrication work (Alex, Aug 27: FlagCo does not cut fiberglass). The fiberglass line is resold vendor product, so fiberglass orders route like dropship: tracked for delivery, no cut jobs, regardless of halyard type. BC hygiene note: 12 fiberglass shafts currently sit in the FLAGPOLE SHAFT item category; they are excluded from the material catalog and flagged for cleanup.
- 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.
Import from BC: kill the double entry (verified feasible Aug 27)
Sales already types every one of these facts into Business Central when they enter the SO. The form should not make them type it twice. A "Pull from BC" button next to the SO number field fetches the order from BC's published Sales_Order and SalesLines services and prefills: customer, salesperson, the full ship-to block (name, address, city, state, zip, contact, phone, and even the custom ship-to email field), customer PO, requested delivery date when BC has one, and every pole line item (the BC item numbers are the same SKUs the tracker's auto-fill catalog already maps to height, type, diameter, wall, and finish). Non-pole lines like flags and drawings are skipped. Live worked example: SO-024309 prefilled 12 of the form's 15 fields plus its pole line (a 40 ft x 8 in x 0.250 Internal Winch) from a single lookup.
- What sales still answers, because BC cannot know it: how firm the date is, the flexibility notes, the rush toggle, and the delivery method when the BC shipment method is blank. That is the whole point: the human questions survive, the typing dies.
- Requested delivery date: only about a quarter of recent BC orders carry one (14 of the last 60), so the field prefills when present and stays required either way.
- Salesperson codes (BELINDA, KCABE, ...) map to tracker names through a small table.
- The import review inbox (Alex, Aug 28: trust but verify): orders found by the BC watch never enter the schedule silently. They land in an amber "Imported from Business Central, awaiting sales review" inbox at the top of the New Delivery page, with a count badge on the nav tab. Each row shows the raw BC lines and the work description; "Review and confirm" opens the standard form fully prefilled with an import banner showing the complete BC work description. Sales verifies every field, answers the questions BC cannot know (date firmness, rush), and Save is the human attestation that enters the order into the schedule. The morning digest nags the count with SO numbers until the inbox is empty. This makes review a proactive daily habit instead of quiet trust in the sync. (Demonstrated in the mockup with three real BC orders currently missing from the tracker.)
- Phase 2 upgrade: a watch job that polls BC for newly released orders containing pole items not yet in the tracker and feeds the review inbox above. Phase 1 is the button; the watch can follow once the button proves itself.
- Uses the same read-only BC credential as the shaft availability check, so both features cost one setup step.
Dropship poles and Concord or Eder SKUs (verified against live orders Aug 27)
FlagCo sometimes fabricates poles sold under Concord or Eder item numbers, and sometimes dropships those same SKUs, so the brand on the SKU can never decide the routing. BC carries two explicit signals instead: the line-level Drop_Shipment flag and the DROPSHIP routing tag on the order header. On the current open book they agree perfectly (every dropship line carries both, every house order carries neither), but historical discipline cannot be measured from the published services, so the import trusts the signals without depending on them:
- BC explicitly says dropship (line flag or header tag): imported as vendor-direct. Tracked for delivery, no cut jobs, chip reads "Dropship per BC." One click reverses it if BC was wrong.
- No dropship signal: imported as fabrication work with cut jobs. Never silently excluded, because the failure asymmetry is brutal: a wrong "fab" costs production one click, a wrong "dropship" silently deletes a pole FlagCo owes from the cut schedule.
- Eder or Concord SKU with no dropship signal: imported as fab plus an amber chip: "Eder SKU, no dropship flag in BC. Are we fabricating this one?" Two one-click answers: We make it (keeps the cut jobs) or Dropship, ignore (removes them, logs who said so). The weekly digest counts these confirmations, which doubles as a nudge to fix the flag in BC.
- Pole-line detection uses the SKU catalog plus the Eder (EC, EV) and Concord (ICC, ISC, IRC, IWW, IRW, ISW, XIRW, ESR, ESS, NSM) model-code prefixes, with an exclusion list for lighting and mount items whose descriptions mention flagpoles (PolePal solar lights, wall mounts). Fiberglass pole SKUs are excluded from cut-job creation entirely and import as vendor-direct. Verified detection lesson (Aug 27): Concord and Eder model codes often live in the item DESCRIPTION on FlagCo-numbered items ("Monarch Series 30' Satin Flagpole - ICC30D61" on item 206426), so the detector matches series names and model codes in descriptions, not just item-number prefixes. Internal Concord codes (ICC, ISC, IWW, ISW) are fabrication; IRW, IRC, ESR, ESS are external.
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 drag and drop, with click as the fallback (Alex, Aug 27): drag a pole from the queue onto any open slot, or drag a scheduled job to a different slot or day; open slots highlight as you hover. Dragging a job out of the frozen window still triggers the reason gate. Clicking still works everywhere: click an open slot to pick from the queue, click "Assign slot" for the suggested earliest slot that makes the date, click a job to move, unschedule, or mark cut.
- 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.
- Flexibility is visible on the board itself (Alex, Aug 27): every scheduled job carries its date-flexibility chip (HARD, BY date, FLEX) on the slot card, so when production needs to reshuffle they see at a glance which jobs the customer said could move. Orders where sales never captured flexibility show a gray "FLEX ?" chip, which doubles as the nudge to start capturing it. Every flexibility chip is click-to-edit (Alex, Aug 27): clicking any badge, anywhere (portal row, queue, rush card, or the mini chip on a board slot), opens a three-option editor with a notes field, so backfilling the existing book is a two-minute clickthrough while sales confirms dates with customers. Routine flexibility updates roll into the digest rather than posting instantly.
- Saturday overtime, on demand only (Alex, Aug 27): every week row has a slim "+ Sat OT" control that opens a Saturday overtime slot for that week. Saturdays are never used by automatic scheduling, the queue suggestions, or the sales estimates; work lands there only when someone deliberately drags or assigns it. An empty Saturday can be hidden again with one click. Saturdays carry both an OT cut slot and a prep lane, so external poles can take Saturday overtime too.
- Frozen window (decided Aug 27, widened same day): today and the next two working days are locked. Filling an open slot stays free, but moving or unscheduling a scheduled job inside the window asks for a reason, logs it, and the weekly digest counts these moves. Rushes come in through the guardrail and the OT slot, never by silently reshuffling frozen work. Completing work is "Mark complete", covering both cut and prep jobs.
- Seeded from Darrell's real schedule (Aug 31): the board is synced from the live "Pole Production Schedule.ods" in SharePoint, so it shows the shop's actual plan rather than a generated one. His sheet maps cleanly onto this model: production date (single days and ranges), sales order, customer, item number, qty, install type (Winch / Cam / External, which is exactly the cut-versus-prep split), ship or pickup date, and status. Two structural lessons came out of the sync: he schedules externals alongside cut work (confirming all poles belong on the schedule), and he books some jobs as multi-day ranges, including one 40 ft two-piece winch pole spanning two days, which a strict one-pole-one-slot model cannot express. Phase 1 shows the range as a chip on the card; if multi-day poles are common, slot weighting (a big pole consuming two slots) is the phase 2 answer.
- The sync check earns the tool its keep: comparing his sheet, the delivery tracker, and Business Central surfaced six real differences on day one, shown in a "Sync check" card above the queue. In the real build these differences stop existing, because there is one schedule instead of three.
- Rolling four-week calendar (Alex, Aug 27): the board shows the current week plus the next three, stacked like a calendar with a per-week header and scheduled-pole count. The pager slides the four-week window a week at a time (back for history, forward to plan ahead). Saturday stays hidden by default with a per-week toggle when the shop plans weekend work (decision 5 below).
- Filters (Alex, Aug 27): the three pole categories are clickable tabs (Winch, Cam Cleat, Reinforced) plus an All tab, not a dropdown. The active tab filters the queue and dims non-matching jobs on the board. A search box rides alongside.
The beacon bench: its own Beacon Production page (Alex, Aug 28)
Beacons and solar kits are assembly work with genuinely variable build times, so they get their own tool with a different model: a three-stage flow board (To build, On the bench, Built) instead of per-day slots. There is no daily capacity number to game; the discipline is working the earliest due dates first, and the board makes the backlog visible.
- One card per order, listing every beacon line with quantities. Solar builds carry a gold SOLAR chip and the card reminds the builder to stage the solar power package with the unit. Paint lines (the $40 adder) and cable preinstallation lines attach to their beacon's card as build steps rather than floating as separate jobs.
- Due date = the order's production-set ship-by when it exists, else the BC requested delivery date. Cards sort due-first; a card within a day of due and not built goes AT RISK. Future-parked orders (the same beyond-four-weeks rule as poles) stay off the bench.
- Priority swim lanes (Alex, Aug 28): the bench is a grid of priority rows (RUSH, HIGH, MEDIUM, LOW) crossed with the three build stages. Cards default sensibly: rush-tagged goes RUSH; warranty, damaged, repair, and at-risk work goes HIGH; samples, demos, and no-charge units go LOW; everything else lands MEDIUM (near dues promote to HIGH). Production re-prioritizes by dragging a card vertically between lanes or with priority buttons in the card popup, and the digest calls out open rush-or-high work.
- Drag between stages or click a card for Start assembly / Mark built. Beacon cards and pole jobs cannot cross into each other's boards. Bench movement is digest-level noise, not instant Teams posts, and the morning digest reports the bench (units to build, on the bench, built).
- Flagpole height is a standard card field (Alex, Aug 28): every card carries a navy FPH chip (the pole height the beacon mounts on, which sets the wire cut length), sourced in priority order from the sales note's FPH shorthand, then from pole lines on the same order. Missing heights show an amber "FPH ?" chip so the gap is visible before the build starts. Live coverage: 32 of 42 cards resolved automatically; the 10 unknowns are mostly the sample sets, which have no install pole.
- Cards carry the order's context for at-a-glance triage (Alex, Aug 28): each card shows classification chips (SAMPLE, DEMO, WARRANTY, REPAIR, REPLACEMENT, DAMAGED, NO CHARGE, RUSH) plus the sales order's work-description notes clamped to two lines, with the full text in the card's popup. The chips come from three verified BC signals: the header WorkDescription field, free-text comment lines between items, and 100-percent-discounted or zero-price beacon lines (which reliably mark samples and warranty units). Live yield on today's book: 10 no-charge orders including 5 sample-and-demo sets and one damaged-wire warranty case.
- Data comes from BC beacon and solar lines on open orders (verified live Aug 28: 42 active orders, 74 outstanding beacon units, 5 with paint or cable steps), excluding dropship-flagged lines, PolePal vendor lights, and visors. Notable data finding: five 12V solar beacons are on open orders with zero standalone Solar Power Package lines anywhere on the book, worth checking the kit-attach rule with sales.
- Beacon-only orders mostly never enter the delivery tracker today (they ship without poles), which is why the bench pulls from BC rather than the tracker, and one more argument for the BC auto-import.
The date math (all constants configurable)
- ship_by = the confirmed ship date, set by the production and warehouse team from experience (Alex, Aug 27: no automatic transit math for now; smarter rules can come later). Setting it is the first step of production intake: until an order has a ship-by, its poles sort to the end of the queue with a "ship-by not set" prompt and cannot go at-risk.
- latest_safe_cut_date = ship_by minus fab lead time of 1 working day (Alex, Aug 27: post-cut work is trivial next to cutting, so poles cut one day are ready to ship the next).
- 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. Slot time includes changeover (Alex, Aug 27), so there is no separate setup accounting anywhere in the model.
- Estimated ship date (sales-facing, rev 3) = take the next open slots for the order's pole count, assume the earliest cut is the next business day, then add the 1-day fab lead. The tool does not estimate delivery: freight time sits on top, and production owns the ship-by call. Standard estimates use normal slots only, so overtime stays reserved as rush headroom; rush estimates may use overtime slots. Advisory only: production still assigns the real slots.
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.
- Pages follow the process (Alex, Aug 27 to 28): the portal's pages run in workflow order: New Delivery (its own page, not a modal, with the schedule headroom card beside the form), then Flagpole Production (queue, cut board, prep lanes), then Beacon Production (the beacon bench on its own page with its own KPI row), then Deliveries. Saving an order with fabricated poles lands the user on Flagpole Production where the new poles sit in the queue.
- Future orders card (Alex, Aug 27): the Deliveries page ends with "Future orders, tracking only" for far-out work (requested delivery beyond the four-week window, no ship-by yet): visible so nothing gets forgotten for a year, but off the production schedule until it nears. First real occupant: a fall 2027 school order.
- Portal-style layout (Alex, Aug 27): the Deliveries tab mirrors the live portal's structure so the demo shows how the two sides interact: a red LATE card (past ship-by, not shipped), an amber "Awaiting a ship-by date from production" card, and shipping-week cards on a rolling four-week calendar, all with a shared column grid (SO, customer, poles, requested delivery, ship by, cut, status) so every row aligns. The pole-type tabs live only on the Production tab, not here.
- Ship-date ownership in the UI (Alex, Aug 27): the ship-by date is EDITED only on the Production tab (on queue rows, the rush card, and the cut-job popup), because production knows when a pole will be ready to ship. The delivery side shows it read-only with a small "production" tag, and shipping coordinates the logistics (carrier, tracking, status) from it. Changes still go through the reason gate and Teams post.
- Schedule headroom card (rev 3): the Deliveries tab shows what a new order entered today would do: cut-by and earliest-ship dates for a chosen pole count (1 to 4), an expedited version using overtime slots, and the expedite room over the next 5 working days (open overtime and normal slots). Sales can quote a realistic date, and see how much rush the schedule can absorb, without calling the shop. The card recalculates every time the board changes.
- Live schedule check on the order form (rev 3): as pole lines are added, the form shows the earliest cut and ship dates for that exact order, switches to overtime-slot math when the rush toggle is on, warns in red when the requested delivery date lands on or before the earliest possible ship date, and otherwise states how many days the date leaves for freight.
- 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.
- BC order prefill (verified feasible Aug 27): new endpoint GET /api/bc/so/:number reads the ODataV4 Sales_Order header and SalesLines, filters lines to catalog pole SKUs, and returns prefill JSON for the form. Shares the BC credential with the shaft check below.
- BC shaft availability (verified feasible Aug 27): a scheduled pull of the 140 FLAGPOLE SHAFT items from the Business Central API (read-only client credentials as a Worker secret), cached in D1. Each cut job maps to its shaft through the pole item's BC assembly BOM (verified: the published Assembly_BOM service returns the exact shaft item per pole SKU), with spec matching (overall length = exposed height + set depth, butt, wall, finish, -RD for reinforced) as the fallback and shows real availability, not just on-hand: the published ODataV4 Items page service exposes Qty_on_Sales_Order, Qty_on_Assembly_Component, and Qty_on_Purch_Order, so free-now = on hand minus commitments, with inbound POs shown separately (verified live: 49 of 140 shafts carry commitments or inbound). Pole items are Assemble-to-Order, so BC creates the linked assembly order, and the shaft commitment, automatically at sales entry: the commitment numbers are live from the moment an order exists. Manual override kept for edge cases. The shaft catalog excludes clear-anodized shafts, fiberglass shafts, and blocked items (Alex, Aug 27: 109 of the 140 category items qualify; 12 fiberglass shafts were found hiding in the FLAGPOLE SHAFT category), and each shaft carries its BC reorder point and reorder quantity so the view can flag stock at or below reorder. Clear-finish pole orders therefore fall back to the manual material check. See recommendation R1 in the research page for the verification details.
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, "Pull from BC" order prefill, BC shaft availability badges. | Everything in sections 4 through 8. Testable locally with wrangler dev before touching production data. |
| 2. Nice-to-haves | 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. (Drag and drop moved into phase 1: already built and demonstrated in the mockup.) | 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. |