FlagCo
Flagpole Production Schedule
Feature plan for the Delivery Tracker (deliveries.flagco.com)
DRAFT REV 2 · AUG 20, 2026

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.

1

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.

Why this matters: a separate tool with two-way sync is the classic way this kind of project fails (conflicting edits, stale copies, "which one is right?"). One database makes the hard problem disappear.

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.
Code drift note: the live site is deployed and owned by Jordan and may differ slightly from the copy in the shared folder (the live URL structure already hints at small changes). Step one of implementation is pulling Jordan's current index.html and worker.js and building against those, not the local April-June copy.
2

How an order flows through the system

Sales
Enters the order with requested delivery date, date flexibility + notes, and the rush toggle if applicable.
System
Order appears on the Deliveries dashboard and posts to the Teams channel. If it contains fabricated poles, it also lands in the Production tab's "needs a slot" queue. Rush orders get an immediate red alert.
Production
Assigns each pole to a cut slot (two per day, third slot for overtime), level-loads the week using the flexibility badges, and marks jobs cut as work happens.
System
If production changes a confirmed ship date, it records who, what, and why, posts a Teams notification to sales and warehouse, and badges the order on every screen.
Warehouse
Works the delivery exactly as today: books freight, sets tracking, marks shipped and complete.

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.

3

What counts as production work (the filter rule)

Pole type on the order lineProduction schedule?Why
Stationary (external halyard)NoPick and pack only, no fabrication
Revolving (external halyard)NoPick and pack only, no fabrication
Winch (internal halyard)YesDoor cut, internal assembly
Cam Cleat (internal halyard)YesDoor cut, internal assembly
Reinforced WinchYesDoor cut, reinforcement, assembly
Reinforced Cam CleatYesDoor 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.
4

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.

Mockup 1 of 3: New Delivery form, order details section (sample data for illustration)
Order details
RUSH ORDER, no flexibility
Pins this order to the top of the production schedule, marks it red everywhere, and alerts the shop immediately. Date flexibility locks to Hard date.
Grand opening ceremony Sep 12, GC penalty clause on late delivery
2026-09-04
When the customer needs it on site
Hard date (must arrive on this date)
Other options: "Deliver by this date, earlier is fine" and "Flexible, coordinate with customer"
e.g. "GC said anytime before end of September" or "Site closed Fridays"

The three flexibility answers

AnswerBadgeWhat it tells production
Hard date, must arrive on this dateHARD DATECrane booked, ceremony scheduled, penalty clause. Do not move. Every rush order is automatically a hard date.
Deliver by, this date or earlier is fineBY SEP 30The 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 customerFLEXIBLECustomer is easygoing or the site is not ready. These are the orders production moves first when a crunch hits. Notes say who to call.
Why a dropdown plus notes, not just notes: the structured answer powers sorting, badges, and "which orders can I move?" filtering. The free-text note carries the human detail ("GC says end of September, call Robin first"). Production gets both at a glance.
5

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?

Mockup 2 of 3: Production Schedule tab (sample data for illustration)
FlagCoFlagpole Deliveries
Deliveries
Production
Beacon Returns
All Orders
Help
+ New Delivery
1
Rush orders in queue
2
Poles needing a slot
8/10
Slots filled this week (+1 OT)
1
At risk of missing ship date
RUSH ORDERS, always pinned on top
SO-024187Entered Aug 18
Example General ContractingMarietta, GA · Kayla
2 × 35' Winch · 6" × 0.188" · Satin
Rush reason: grand opening Sep 12, penalty clause
RUSH HARD DATE
Deliver Sep 4 · Ship by Aug 28
Cutting today: slots 1 + 2 Status: In Production ▾
Poles needing a slot (2), sorted by latest safe cut date · next open slot: Fri Aug 21, slot 2
SO-024199 AT RISK
Sample ResortSavannah, GA · Andrew
1 × 45' Winch · 8" × 0.250" · Satin
Latest safe cut date has passed. Use today's OT slot or call the customer.
HARD DATE
Deliver Aug 28 · Ship by Aug 21
Assign slot...
SO-024201
Sample School DistrictRome, GA · Mitch
1 × 40' Reinforced Winch · 8" × 0.250" · Satin
2 × 25' Stationary (pick and pack, ships with order)
BY SEP 30
"GC says anytime before end of Sept"
Assign slot...
Cut board  ‹ Week of Aug 17 ›  2 poles per day · slot 3 is overtime
MONAug 17
SO-024150 ✓
60' Winch · 10"×0.250"
Sample University
SO-024162 ✓
30' Cam Cleat (1 of 2)
Sample Municipality
OT not used
TUEAug 18
SO-024162 ✓
30' Cam Cleat (2 of 2)
Sample Municipality
SO-024171 ✓
20' Cam Cleat · 4"×0.125"
Sample Bank
SO-024168 ✓OT
25' Winch · 5"×0.156"
Sample HOA
WEDAug 19
SO-024173 ✓
30' Winch · 6"×0.188"
Sample Auto Group
Not used
OT not used
THU · TODAYAug 20
SO-024187 RUSH
35' Winch (1 of 2)
Example General Contracting
SO-024187 RUSH
35' Winch (2 of 2)
Example General Contracting
+ OT slot
FRIAug 21
SO-024195
50' Winch · 10"×0.250"
Sample Car Dealership
+ Open slot
+ OT slot
Legend: green check = cut · red outline = rush · amber = overtime slot · dashed = open. Click an open slot to fill it from the queue; click a job to move it, unschedule it, or mark it cut.

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).
Bonus for sales: "next open slot" is a live, honest answer to "how fast can you get me one?" Sales can glance at the Production tab and quote a realistic date without calling the shop. And this settles rev 1's open question about per-order vs per-pole scheduling: slots only work per pole, so it is per pole.

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.
6

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.
7

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).

EventWho caresDelivery
New order enteredWarehouse + production see incoming work in real timeInstant post (compact): SO, customer, poles, method, requested date with flexibility badge
Rush order created, or an existing order upgraded to rushProduction (Marshall) + warehouseInstant post, red, + pinned rush section in the app
Confirmed ship date changed (the headline feature)Salesperson + warehouseInstant 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 immediatelyInstant post with tracking number
Status set to IssueSalespersonInstant post with the issue note (today this is in-app only)
Ship date first set, expected delivery set, delivery completeRoutine progressMorning digest only, to keep the channel quiet
Cut slot assigned or movedShop-internal detailOrder history log + morning digest only
Late order (past booked ship date, not shipped)EveryoneMorning digest, red section
Morning digest, weekdays 7:00 AMThe whole team's stand-up viewOne post: cutting today (with open slots), shipping today, late orders, poles needing a slot, rush queue
Noise control: a channel that posts everything gets muted. Every event type routes to instant post, morning digest, or log-only through one small config map in the Worker, so retuning is a one-line change, and event types can later point at different channels (say #production vs #deliveries) just by adding a second webhook URL.
Mockup 3 of 3: what lands in the Teams channel (sample data for illustration)
FlagCo Delivery Tracker
Ship date changed: SO-024178, Sample Church (Dallas, GA)
Confirmed ship date: Aug 26 → Sep 2 (6 days later)
Changed by: Production (Marshall) · Reason: shaft on backorder
Requested delivery Sep 15 · Flexibility: Flexible, "site not ready, call before shipping"
Salesperson: Randie · 1 × 25' Winch, Bronze
FlagCo Delivery Tracker
RUSH ORDER: SO-024187, Example General Contracting (Marietta, GA)
2 × 35' Winch · Deliver by Sep 4 (hard date) · Ship by Aug 28
Reason: grand opening Sep 12, penalty clause · Entered by: Kayla
FlagCo Delivery Tracker · weekday 7:00 AM digest
Thursday, Aug 20: 2 cutting, 3 shipping, 1 late, 2 need slots
Cutting today: SO-024187 (2 × 35' Winch, RUSH) · OT slot open
Shipping today: SO-024155, SO-024160, SO-024166
Late: SO-024142 (booked Aug 18, not shipped)
Needs a cut slot: SO-024199 (AT RISK, hard date Aug 28), SO-024201 (by Sep 30)

How a date change is captured

  1. Someone edits the confirmed ship date (from either tab).
  2. 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.
  3. 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.
  4. 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.
8

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.
Scope check: one migration file, a handful of bounded edits to the Worker plus one cron trigger, and one new view plus form tweaks in the frontend. No new services except the Teams webhook. The existing 30-second polling, export and import, and localStorage fallback all keep working untouched.
9

Build plan and phases

PhaseContentsNotes
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.
10

Decisions needed from Alex (and one number from Marshall)

Resolved in rev 2 (Alex, Aug 20): the schedule is a daily cut board with per-pole slots, capacity 2 poles per normal day plus a third overtime slot, and Teams notifications cover the whole delivery tracker, not just production changes.
1. Notification channel and destination. Teams channel, email, or both to start? And which channel?
RECOMMEND One Teams channel (#flagco-orders, or an existing ops channel) for everything in phase 1, instant posts plus the 7:00 AM digest. Jordan creates the webhook. Splitting channels or adding per-salesperson email later is a config change, not a rebuild.
2. Reason required when a confirmed ship date slips?
RECOMMEND Required when the date moves later, optional when earlier. Cheap discipline, and the reason is the most useful line in the notification.
3. Can any salesperson set rush, or does it need approval?
RECOMMEND Anyone can set it, reason required, name logged. If rush gets overused that is a management conversation, not a software gate.
4. The two remaining scheduling constants. Standard fab lead time (cut to ready-to-ship) and freight transit buffer. Cut capacity itself is set: 2 poles per day plus 1 OT slot.
ASK MARSHALL Plan placeholders: 3 working days fab, 5 business days freight transit. These drive the "at risk" flag and the queue sort, and are easy to tune later.
5. Saturdays on the cut board? Always shown, toggle-on when planned, or never?
RECOMMEND Hidden by default with a per-week toggle, so the board matches reality without cluttering normal weeks. The OT slot covers most overflow anyway.
6. Confirm the fabrication list. Winch, Cam Cleat, Reinforced Winch, Reinforced Cam Cleat in; Stationary and Revolving out; dropship excluded.
Matches "external halyard is pick and pack only." Confirm there is no edge case (for example, does a beacon on an external pole ever create shop work worth scheduling?).
7. Who builds phase 1?
RECOMMEND Claude builds against Jordan's current code and hands Jordan a reviewed diff to deploy, since he owns the live Worker. Alternative: Jordan builds straight from this spec.
8. Bless the notification matrix in section 7: which events post instantly vs. wait for the morning digest.
RECOMMEND As drawn: instant for new orders, rush, ship date changes, shipped, and issues; digest for everything routine. Each event type is one line in a config map, so retuning after a week of real use is trivial.