From ce829a009d3b83742b43cd3f659639de153543a6 Mon Sep 17 00:00:00 2001 From: kevin-asprec Date: Mon, 16 Mar 2026 13:50:58 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20fix=20phase=20READMEs=20and=20STATE.md?= =?UTF-8?q?=20=E2=80=94=20add=20full=20scope/plan=20to=20phases=209-15,=20?= =?UTF-8?q?fix=20stale=20last-commit=20field?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .paul/STATE.md | 2 +- .paul/phases/09-email-dispatcher/README.md | 60 ++++++++++++++ .paul/phases/10-support-tickets/README.md | 61 +++++++++++++- .paul/phases/11-monthly-reports/README.md | 63 +++++++++++++- .paul/phases/12-onprem-report-pull/README.md | 63 +++++++++++++- .paul/phases/13-feature-flags/README.md | 50 ++++++++++- .paul/phases/14-onboarding-wizard/README.md | 65 ++++++++++++++- .paul/phases/15-ux-polish/README.md | 87 +++++++++++++++++++- 8 files changed, 428 insertions(+), 23 deletions(-) diff --git a/.paul/STATE.md b/.paul/STATE.md index 66ef561..2ed8227 100644 --- a/.paul/STATE.md +++ b/.paul/STATE.md @@ -44,7 +44,7 @@ Resume file: .paul/ROADMAP.md → Phase 9 Remote: TBD (new Gitea repo) Branch: master -Last commit: initial scaffold +Last commit: feat(phase-8): billing engine + invoice PDF + mark-paid + overdue escalation ## Tech Stack diff --git a/.paul/phases/09-email-dispatcher/README.md b/.paul/phases/09-email-dispatcher/README.md index e69de29..2079db8 100644 --- a/.paul/phases/09-email-dispatcher/README.md +++ b/.paul/phases/09-email-dispatcher/README.md @@ -0,0 +1,60 @@ +# Phase 09: Email Dispatcher + +**Status:** Not started +**Depends on:** Phase 8 (Billing Engine) + +## Goal + +Ensure all automated emails (invoices, low credits, license expiry, monthly reports, welcome) +send correctly via SMTP with proper HTML templates. Add an email delivery log visible to +super admin, and a test-email endpoint to verify SMTP config. + +## Planned Scope + +### Backend + +**Jinja2 HTML email templates** (`backend/app/templates/email/`) +- `invoice.html` — branded invoice notification (already plain text in Phase 8; upgrade to HTML) +- `low_credit_alert.html` — low credit warning with credit meter visual +- `license_expiry.html` — license expiry countdown with days remaining +- `monthly_report.html` — monthly report summary email (stub, filled in Phase 11) +- `welcome.html` — welcome email with license key + setup instructions (used in Phase 14) +- Base layout template with consistent header/footer + +**`email_logs` table** (new model `app/models/email_log.py`) +- `id`, `school_id` (nullable), `to_email`, `subject`, `email_type`, `status` (sent/failed), + `sent_at`, `error_message`, `created_at` + +**`app/services/email.py`** — enhanced +- Accept HTML template name + context instead of raw body string +- Log every send attempt to `email_logs` table +- Retry on transient failure (up to 3 attempts) + +**`backend/app/routers/email.py`** (new) +- `GET /api/email-logs` — super admin: paginated email delivery history with filters +- `POST /api/email/test` — send a test email to verify SMTP config (super admin) + +**Update all tasks** that send email (billing, sms, license) to use HTML templates + +### Frontend + +**EmailLogsPage or section** in super admin +- Table: to, subject, type, status badge, sent_at, error message +- Filter by email type + status + +**api.ts additions** +- `getEmailLogs(params)` — list email logs +- `sendTestEmail(to)` — trigger test email + +## Key Files to Create/Modify + +- `backend/app/models/email_log.py` (new) +- `backend/app/templates/email/*.html` (new — 5 templates) +- `backend/app/services/email.py` (enhance) +- `backend/app/routers/email.py` (new) +- `backend/app/main.py` — include email router +- `backend/migrations/versions/002_email_logs.py` (new migration) +- `frontend/src/pages/EmailLogsPage.vue` (new or section in existing page) +- `frontend/src/lib/api.ts` — add email log functions +- `frontend/src/router/index.ts` — add email logs route +- `frontend/src/components/sidebar/AppSidebar.vue` — add nav item diff --git a/.paul/phases/10-support-tickets/README.md b/.paul/phases/10-support-tickets/README.md index d4205c0..76ea798 100644 --- a/.paul/phases/10-support-tickets/README.md +++ b/.paul/phases/10-support-tickets/README.md @@ -1,9 +1,64 @@ # Phase 10: Support Ticket System **Status:** Not started +**Depends on:** Phase 9 (Email Dispatcher — for email notifications) ## Goal -Email notifications on tickets, SLA tracking, internal notes, priority escalation. -## Plans -- [ ] TBD — run /paul:plan when Phase 9 is complete +Polish and harden the existing ticket system with SLA tracking display, email notifications +on ticket create/reply, priority auto-escalation, and ticket assignment. + +## Planned Scope + +### Backend + +**Email notifications** (using Phase 9 HTML templates) +- New ticket created → email to super admin(s) +- Super admin replies → email to school admin +- School admin replies → email to super admin(s) +- Template: `ticket_notification.html` + +**SLA display** +- `first_response_at` already stored — compute SLA status in list endpoint: + - `sla_status`: `on_track` | `at_risk` (>24h no response) | `breached` (>48h no response) +- Add `sla_status` to ticket list response + +**Priority auto-escalation** (new Celery task `tickets.escalate_stale`) +- Scheduled every hour +- Tickets open > 48h with `priority = normal` → set `priority = high` +- Tickets open > 72h with no reply → set `priority = urgent` + +**Ticket assignment** +- `assigned_to: str | None` column already on model +- `PUT /api/tickets/{id}` already accepts `assigned_to` +- Add `GET /api/users?role=super_admin` filter for assignee picker + +**Bulk actions** +- `POST /api/tickets/bulk-close` — close list of resolved ticket IDs (super admin) + +### Frontend + +**TicketsPage.vue** — super admin view enhancements +- SLA status column (green/amber/red badge) +- Assignee column + inline assign dropdown +- Priority badge +- Bulk close: checkbox selection + "Close Selected" button +- Status filter tabs (All / Open / In Progress / Resolved / Closed) + +**TicketDetailPage.vue** — enhancements +- Assignee selector +- Priority selector +- SLA timer display (hours since opened, color-coded) +- Internal notes clearly marked with lock icon (super admin only) + +**worker.py** — add `tickets.escalate_stale` to beat schedule (every hour) + +## Key Files to Modify + +- `backend/app/routers/tickets.py` — SLA status in response, bulk-close endpoint +- `backend/app/tasks/tickets.py` (new) — escalate_stale task +- `backend/app/worker.py` — add escalation task to beat schedule +- `backend/app/templates/email/ticket_notification.html` (new) +- `frontend/src/pages/TicketsPage.vue` — SLA, assign, bulk close +- `frontend/src/pages/TicketDetailPage.vue` — SLA timer, assign, priority +- `frontend/src/lib/api.ts` — bulkCloseTickets diff --git a/.paul/phases/11-monthly-reports/README.md b/.paul/phases/11-monthly-reports/README.md index 18306db..34bcad0 100644 --- a/.paul/phases/11-monthly-reports/README.md +++ b/.paul/phases/11-monthly-reports/README.md @@ -1,9 +1,64 @@ -# Phase 11: Monthly Report Generation +# Phase 11: Monthly Report Generation + Email **Status:** Not started +**Depends on:** Phase 9 (Email), Phase 12 (On-Prem Data Pull) ## Goal -Auto monthly report email to schools with attendance summary and SMS usage. -## Plans -- [ ] TBD — run /paul:plan when Phase 10 is complete +On the 1st of each month, automatically generate and email a comprehensive report to each +active school summarising their SMS usage, credit consumption, and invoice for the period. +Super admin can manually trigger reports. School portal shows report history. + +## Planned Scope + +### Backend + +**Enhance `reports.send_monthly_reports` Celery task** (currently a stub) +- For each active school: + 1. Fetch SMS stats for prior month (sent, failed, delivery rate, credit burn) + 2. Fetch attendance data from `school_monthly_stats` (populated by Phase 12) + 3. Fetch invoice for the period (if exists) + 4. Render `monthly_report.html` Jinja2 template + 5. Send email to `school.billing_email` + 6. Store report record in `monthly_reports` table + +**`monthly_reports` table** (new model) +- `id`, `school_id`, `report_month` (YYYY-MM), `email_sent_at`, `report_data` (JSON), + `created_at` + +**Manual trigger endpoint** +- `POST /api/reports/send/{school_id}` — super admin triggers report for a specific school + +**School portal: report history** +- `GET /api/portal/reports` — school admin lists their past reports +- `GET /api/portal/reports/{id}` — view report detail (JSON data rendered as HTML) + +**`monthly_report.html`** Jinja2 email template +- School name + period header +- SMS stats: sent, failed, delivery rate, credit burn +- Attendance summary (if available from Phase 12, else "data unavailable") +- Invoice summary for the period +- Credit balance as of report date + +### Frontend + +**PortalReportsPage.vue** (new portal page) +- List of past monthly reports (month, email sent date, SMS count, status) +- Click to view report detail + +**Super admin** — manual report trigger button in SchoolDetailPage + +**Router + sidebar** — add /portal/reports route and nav item + +## Key Files to Create/Modify + +- `backend/app/models/report.py` (new — monthly_reports table) +- `backend/app/tasks/reports.py` — enhance from stub +- `backend/app/routers/reports.py` (new) +- `backend/app/templates/email/monthly_report.html` (new) +- `backend/app/main.py` — include reports router +- `backend/migrations/versions/003_monthly_reports.py` (new) +- `frontend/src/pages/portal/PortalReportsPage.vue` (new) +- `frontend/src/router/index.ts` — add /portal/reports +- `frontend/src/components/sidebar/PortalSidebar.vue` — add Reports nav item +- `frontend/src/lib/api.ts` — portal report functions diff --git a/.paul/phases/12-onprem-report-pull/README.md b/.paul/phases/12-onprem-report-pull/README.md index 415a19a..3a0d243 100644 --- a/.paul/phases/12-onprem-report-pull/README.md +++ b/.paul/phases/12-onprem-report-pull/README.md @@ -1,9 +1,66 @@ # Phase 12: On-Prem Monthly Report Pull **Status:** Not started +**Depends on:** Phase 11 (Monthly Reports) ## Goal -Hub pulls attendance data from each TapTrack instance for monthly reports. -## Plans -- [ ] TBD — run /paul:plan when Phase 11 is complete +Hub pulls attendance summary data from each on-prem TapTrack instance on the 1st of the month +to populate monthly report data. Fallback: if on-prem is unreachable, report shows +"attendance data unavailable" for that section. + +## Planned Scope + +### On-Prem Side (TapTrack — separate repo, documented here for reference) + +**New endpoint on TapTrack on-prem:** +`GET /api/hub/monthly-report?key={license_key}&month={YYYY-MM}` + +Response: +```json +{ + "month": "2026-02", + "total_students": 450, + "school_days": 20, + "present_days_total": 8100, + "absent_days_total": 900, + "late_days_total": 200, + "avg_attendance_rate": 90.0, + "sms_sent": 342 +} +``` + +### Hub Side + +**`school_monthly_stats` table** (new model) +- `id`, `school_id`, `report_month` (YYYY-MM date), `total_students`, `school_days`, + `present_days_total`, `absent_days_total`, `late_days_total`, `avg_attendance_rate`, + `sms_sent`, `pulled_at`, `pull_status` (success/failed/unavailable) + +**New Celery task: `reports.pull_monthly_stats`** +- Runs on the 1st at 5:00am (before `send_monthly_reports` at 7:00am) +- For each active school with a `last_seen_ip` and `hub_base_url`: + - GET `{school.hub_base_url}/api/hub/monthly-report?key={license_key}&month={YYYY-MM}` + - On success: upsert `school_monthly_stats` with pull_status=success + - On failure/timeout: create record with pull_status=unavailable +- Timeout: 10 seconds per school + +**School model** — add `hub_base_url: str | None` column (the on-prem instance URL) +- Set automatically from `last_seen_ip` or configured manually by super admin + +**Super admin UI** — `hub_base_url` field in SchoolDetailPage edit panel + +### Frontend + +No new pages needed — data surfaces through monthly reports (Phase 11). + +**SchoolDetailPage.vue** — show `hub_base_url` field in school info + edit modal + +## Key Files to Create/Modify + +- `backend/app/models/school.py` — add `hub_base_url` column +- `backend/app/models/report.py` — add `SchoolMonthlyStats` model +- `backend/app/tasks/reports.py` — add `pull_monthly_stats` task +- `backend/app/worker.py` — schedule pull task at 5am on 1st +- `backend/migrations/versions/004_school_monthly_stats.py` (new) +- `frontend/src/pages/SchoolDetailPage.vue` — hub_base_url field diff --git a/.paul/phases/13-feature-flags/README.md b/.paul/phases/13-feature-flags/README.md index 687ed9b..60ec910 100644 --- a/.paul/phases/13-feature-flags/README.md +++ b/.paul/phases/13-feature-flags/README.md @@ -1,9 +1,51 @@ -# Phase 13: Feature Flags + Suspension +# Phase 13: Feature Flags + Service Suspension Logic **Status:** Not started +**Depends on:** Phase 3 (License Validation), Phase 8 (Billing / Overdue Suspension) ## Goal -Per-school feature flag overrides; suspension propagation to on-prem. -## Plans -- [ ] TBD — run /paul:plan when Phase 12 is complete +Hub controls which features each school's on-prem can use via tier-based + per-school +feature flag overrides. Suspension from billing (Phase 8) propagates to on-prem via +the sync poll. Super admin can enable beta features per school. + +## Planned Scope + +### Backend + +**`feature_overrides` column on School** (new) +- `feature_overrides: dict | None` — JSON column for per-school flag overrides +- Example: `{"webhooks": true, "beta_reports": true}` to enable premium features on a basic tier + +**`/api/sync/poll` config response** — already returns `feature_flags` (Phase 5) +- Enhance: merge tier-based flags with `school.feature_overrides` +- Add `suspension_reason: str | None` to config (e.g. "overdue_invoice") + +**New endpoint: `PUT /api/schools/{id}/feature-overrides`** +- Super admin sets per-school feature flag overrides +- Validates keys against allowed feature set + +**License validate response** — also returns merged feature flags (already does, enhance) + +### Frontend + +**SchoolDetailPage.vue** — Feature Flags panel (super admin only) +- Shows current effective flags (tier base + overrides) +- Toggle switches for each overrideable feature: + - `api_keys`, `webhooks`, `bulk_enrollment`, `beta_reports`, `multi_terminal` +- Save button → calls PUT /api/schools/{id}/feature-overrides +- Clear overrides button + +**On-prem side** (documented for reference) +- `sync/poll` response: `suspended: true` + `suspension_reason` +- On-prem shows banner: "Account suspended — {reason}. SMS disabled." +- Feature flags stored in Redis with 5-min TTL for fast checks + +## Key Files to Create/Modify + +- `backend/app/models/school.py` — add `feature_overrides` JSON column +- `backend/app/routers/schools.py` — add PUT /{id}/feature-overrides endpoint +- `backend/app/routers/sync.py` — merge feature_overrides into poll config +- `backend/migrations/versions/005_feature_overrides.py` (new) +- `frontend/src/pages/SchoolDetailPage.vue` — feature flags panel +- `frontend/src/lib/api.ts` — updateFeatureOverrides(id, data) diff --git a/.paul/phases/14-onboarding-wizard/README.md b/.paul/phases/14-onboarding-wizard/README.md index 8396f5f..8d01bea 100644 --- a/.paul/phases/14-onboarding-wizard/README.md +++ b/.paul/phases/14-onboarding-wizard/README.md @@ -1,9 +1,66 @@ -# Phase 14: Onboarding Wizard +# Phase 14: Onboarding Wizard + Welcome Email **Status:** Not started +**Depends on:** Phase 9 (Email — welcome email template) ## Goal -Welcome email with license key; onboarding checklist UI in SchoolDetailPage. -## Plans -- [ ] TBD — run /paul:plan when Phase 13 is complete +When a new school is registered, automatically send a welcome email with the license key +and setup instructions. Super admin has a visual onboarding checklist in SchoolDetailPage +to track setup completion and activate the school. + +## Planned Scope + +### Backend + +**Welcome email auto-send on school creation** +- `POST /api/schools` — after school + license are created, fire `onboarding.send_welcome_email` task +- Template `welcome.html` — school name, license key (formatted), setup URL, portal login URL + +**New Celery task: `onboarding.send_welcome_email(school_id)`** +- Renders `welcome.html` with school + license data +- Sends to `school.billing_email` or `school.contact_email` +- Logs to `email_logs` (Phase 9) + +**`onboarding_completed_at` column on School** +- Set when super admin clicks "Complete Onboarding" / "Activate School" + +**New endpoint: `POST /api/schools/{id}/activate`** +- Sets `school.status = active` + `school.onboarding_completed_at = now()` +- Validates that license exists and billing plan is set + +**"Resend welcome email" endpoint** +- `POST /api/schools/{id}/resend-welcome` — re-fires the welcome email task + +### Frontend + +**SchoolDetailPage.vue** — Onboarding Checklist panel +- Visible when `school.status == pending` +- Steps with checkmarks: + 1. School created ✓ (always done) + 2. License issued (check if license exists) + 3. Billing plan set (check if subscription exists) + 4. SMS credits added (check if sms_credits > 0) + 5. Welcome email sent (check if email_logs has a welcome email for this school) + 6. School admin account created (check if any school_admin users linked) +- "Resend Welcome Email" button +- "Activate School" button (calls POST /api/schools/{id}/activate) + - Disabled until steps 1–3 are complete + +**welcome.html** Jinja2 email template +- School name, license key (large monospace display) +- Step-by-step setup instructions +- Link to Hub portal: `{HUB_BASE_URL}/login` +- Contact support link + +## Key Files to Create/Modify + +- `backend/app/models/school.py` — add `onboarding_completed_at` column +- `backend/app/tasks/onboarding.py` (new) — send_welcome_email task +- `backend/app/worker.py` — include onboarding tasks +- `backend/app/routers/schools.py` — auto-trigger welcome email on create, + add POST /{id}/activate, POST /{id}/resend-welcome +- `backend/app/templates/email/welcome.html` (new) +- `backend/migrations/versions/006_onboarding_fields.py` (new) +- `frontend/src/pages/SchoolDetailPage.vue` — onboarding checklist panel +- `frontend/src/lib/api.ts` — activateSchool, resendWelcomeEmail diff --git a/.paul/phases/15-ux-polish/README.md b/.paul/phases/15-ux-polish/README.md index 0c00616..e2c8052 100644 --- a/.paul/phases/15-ux-polish/README.md +++ b/.paul/phases/15-ux-polish/README.md @@ -1,9 +1,88 @@ -# Phase 15: UX Polish + Ops Tools +# Phase 15: UX Polish + Super Admin Ops Tools **Status:** Not started +**Depends on:** All previous phases ## Goal -Audit log viewer, global search, bulk export, mobile responsive portal, production hardening. -## Plans -- [ ] TBD — run /paul:plan when Phase 14 is complete +Production-ready polish: audit log viewer, global search, bulk operations, mobile-responsive +portal, keyboard shortcuts, error boundaries, and production security hardening. + +## Planned Scope + +### Backend + +**Audit log viewer endpoint** +- `GET /api/audit-logs` — paginated, filterable by school, action, actor, date range +- Already have `audit_logs` table — just need the endpoint + frontend + +**Global search endpoint** +- `GET /api/search?q={query}` — searches across schools, invoices, tickets +- Returns grouped results: `{ schools: [...], invoices: [...], tickets: [...] }` +- Limit 5 results per category + +**Bulk school actions** +- `POST /api/schools/export-csv` — export school list as CSV (name, status, tier, credits, license) +- `POST /api/schools/bulk-status` — change status for multiple school IDs (super admin) + +**Rate limiting on auth endpoints** +- Add `slowapi` middleware: `POST /auth/login` — max 10 req/min per IP +- Return `429 Too Many Requests` with Retry-After header + +**HTTPS redirect** +- Add redirect middleware in `main.py` (X-Forwarded-Proto check) +- Update `nginx.conf` to pass X-Forwarded-Proto header + +### Frontend + +**Audit log page** (`AuditLogPage.vue` — new or sub-section in admin) +- Table: timestamp, actor, action, entity type, entity name, IP +- Filters: date range, actor, action type + +**Global search** (keyboard shortcut `/`) +- `SearchModal.vue` — command palette style +- Press `/` anywhere → modal opens with search input +- Results grouped by type, click navigates to entity + +**Keyboard shortcuts** +- `/` — open global search +- `N` — new school (when on /schools) +- `Esc` — close any open modal + +**Mobile-responsive portal** +- PortalSidebar: collapsible on mobile (hamburger menu) +- PortalLayout: responsive header +- All portal pages: responsive table → card layout on mobile + +**Error boundary component** (`ErrorBoundary.vue`) +- Wraps page content in AppLayout/PortalLayout +- On uncaught error: shows friendly "Something went wrong" card with retry button + +**Empty state illustrations** +- SchoolsPage: "No schools yet" with Add School CTA +- TicketsPage: "No tickets" with Submit Ticket CTA +- BillingPage: "No invoices yet" with Generate Invoices CTA + +**Dashboard refresh UX** +- Auto-refresh every 5 minutes (setInterval) +- Show "Data may be stale" banner after 10 minutes without refresh + +**CSV export button** in SchoolsPage toolbar + +## Key Files to Create/Modify + +- `backend/app/routers/search.py` (new) +- `backend/app/routers/audit.py` (new — expose existing audit_logs table) +- `backend/app/routers/schools.py` — bulk-status + export-csv endpoints +- `backend/app/main.py` — include search + audit routers, add rate limit + HTTPS middleware +- `backend/requirements.txt` — add `slowapi` +- `frontend/src/pages/AuditLogPage.vue` (new) +- `frontend/src/components/ui/SearchModal.vue` (new) +- `frontend/src/components/ui/ErrorBoundary.vue` (new) +- `frontend/src/layouts/AppLayout.vue` — global keyboard shortcut handler +- `frontend/src/layouts/PortalLayout.vue` — mobile responsive +- `frontend/src/components/sidebar/PortalSidebar.vue` — mobile collapsible +- `frontend/src/pages/SchoolsPage.vue` — bulk actions, CSV export, empty state +- `frontend/src/router/index.ts` — /audit-logs route +- `frontend/src/components/sidebar/AppSidebar.vue` — audit logs nav item +- `frontend/src/lib/api.ts` — globalSearch, getAuditLogs, exportSchoolsCsv, bulkSchoolStatus