From 86ffce07b0658225af8b9406818690734a367d7c Mon Sep 17 00:00:00 2001 From: Sven laptop Date: Tue, 12 May 2026 12:05:34 +0200 Subject: [PATCH] Enhance schedule stop progress; add scheduled_passed_date column and update unique constraints --- .gitignore | 1 + handoff.md | 38 ++++++++++++++++++++++-------------- transit_api/index.js | 46 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index 6e131c8..0235fb0 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ ADMIN_DEPLOYMENT_SUMMARY.md CI_CD_PORTAINER_GUIDE.md DEPLOYMENT.md GETTING_STARTED.md +handoff.md INTERNAL_*.md SETUP_*.md DEPLOY_*.md diff --git a/handoff.md b/handoff.md index 83db4c8..1f128dc 100644 --- a/handoff.md +++ b/handoff.md @@ -1,30 +1,40 @@ # Session Handoff -**Goal:** [What I'm trying to achieve] +**Goal:** Make schedules work correctly across recurring and one-time events, with accurate per-day status/progress in the API and calendar views in the admin UI. **Current Status:** -- [What is already done] -- [What is in progress] -- [What still needs to happen] +- `transit_api/index.js` now materializes recurring schedules into concrete occurrences for `/schedules/overview`. +- `transit.schedule_stop_progress` is now occurrence-aware via `scheduled_passed_date`; the API backfills old rows and writes/reads stop progress per date. +- The API check passed with `node --check transit_api/index.js`. +- The admin UI still needs to be wired to use the range-aware overview endpoint in its calendar data source. **Important Context:** -- [Audience, tone, constraints, preferences] -- [Key files, links, notes, or source material] -- [Deadline or success criteria] +- The schedule template lives in the database as a row with `weekdays`; recurring events are not stored as separate rows. +- The backend should be the source of truth for date-specific materialization and live progress/status. +- Key files: `transit_api/index.js`, `admin_ui/src/components/ScheduleMaker.jsx`, `transit_api/README.md`. +- The admin UI build already succeeded earlier with `npm --prefix admin_ui run build`. **Decisions Already Made:** -- [Decision + short reason] +- Recurring schedules are materialized on read instead of creating one DB row per occurrence. This keeps the DB simple and makes the calendar/date-range API deterministic. +- Progress must be keyed by occurrence date, not only by schedule id and stop order, otherwise a recurring schedule reuses yesterday’s stop state. +- `/schedules/overview` should support both single-date and date-range requests so month/week views can render all relevant occurrences with one call. **What to Avoid:** -- [Failed attempts, wrong direction, out-of-scope items] +- Do not treat recurring schedules as a single mutable live schedule row for progress/status. +- Do not reuse the old `schedule_id + stop_order` uniqueness for stop progress. +- Avoid reworking unrelated API routes or admin screens unless they block the calendar flow. **Open Questions or Blockers:** -- [What is unresolved] +- The admin UI calendar source still needs to be confirmed and wired to call `/schedules/overview?fromDate=...&toDate=...`. +- If the admin calendar view expects a different event shape, map the overview response there rather than changing the backend again. **Next Best Step:** -- [What you should do first] +1. Inspect the calendar/timetable code path in `admin_ui/src/components/ScheduleMaker.jsx` and any adjacent component that renders the calendar range. +2. Wire that view to request `/schedules/overview` with `fromDate` and `toDate`. +3. Map returned schedules into calendar events and surface the current status/progress fields. +4. Run a focused admin UI build to confirm the change compiles. **How to Respond:** -- Start with a short summary of your understanding. -- Continue from here instead of restarting from scratch. -- If anything critical is missing, ask only the minimum questions needed. \ No newline at end of file +- Start by summarizing the current state in one short paragraph. +- Continue from the existing implementation instead of re-discovering the schedule model. +- If anything critical is missing, ask only the minimum question needed to proceed. \ No newline at end of file diff --git a/transit_api/index.js b/transit_api/index.js index e08e950..c62aea9 100644 --- a/transit_api/index.js +++ b/transit_api/index.js @@ -582,6 +582,52 @@ async function ensureScheduleStopProgressTable() { ) `); + await db.query(` + ALTER TABLE transit.schedule_stop_progress + ADD COLUMN IF NOT EXISTS scheduled_passed_date DATE + `); + + await db.query(` + UPDATE transit.schedule_stop_progress + SET scheduled_passed_date = scheduled_passed_at::date + WHERE scheduled_passed_date IS NULL + `); + + await db.query(` + ALTER TABLE transit.schedule_stop_progress + ALTER COLUMN scheduled_passed_date SET NOT NULL + `); + + await db.query(` + DO $$ + DECLARE + constraint_name text; + BEGIN + SELECT tc.constraint_name + INTO constraint_name + FROM information_schema.table_constraints tc + JOIN information_schema.key_column_usage kcu + ON tc.constraint_name = kcu.constraint_name + AND tc.table_schema = kcu.table_schema + AND tc.table_name = kcu.table_name + WHERE tc.table_schema = 'transit' + AND tc.table_name = 'schedule_stop_progress' + AND tc.constraint_type = 'UNIQUE' + GROUP BY tc.constraint_name + HAVING array_agg(kcu.column_name ORDER BY kcu.ordinal_position) = ARRAY['schedule_id', 'stop_order'] + LIMIT 1; + + IF constraint_name IS NOT NULL THEN + EXECUTE format('ALTER TABLE transit.schedule_stop_progress DROP CONSTRAINT %I', constraint_name); + END IF; + END $$; + `); + + await db.query(` + CREATE UNIQUE INDEX IF NOT EXISTS idx_schedule_stop_progress_schedule_order_date + ON transit.schedule_stop_progress (schedule_id, stop_order, scheduled_passed_date) + `); + await db.query(` CREATE INDEX IF NOT EXISTS idx_schedule_stop_progress_schedule_order ON transit.schedule_stop_progress (schedule_id, stop_order, scheduled_passed_date)