docs: fix phase READMEs and STATE.md — add full scope/plan to phases 9-15, fix stale last-commit field

This commit is contained in:
kevin-asprec
2026-03-16 13:50:58 +08:00
parent 0e0803e417
commit ce829a009d
8 changed files with 428 additions and 23 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 13 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

View File

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