diff --git a/CLAUDE.md b/CLAUDE.md index 3ea8d30..33c8e5a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -319,6 +319,17 @@ support_ticket_replies: id, ticket_id (FK→support_tickets CASCADE), user_id (F body TEXT, created_at DATETIME ``` +### SupportChatSession / SupportChatMessage (Phase 37) + +``` +support_chat_sessions: id, customer_id (FK→users CASCADE, indexed), + created_at, updated_at (indexed) +support_chat_messages: id, session_id (FK→support_chat_sessions CASCADE, indexed), + role ('user'|'assistant'), content TEXT, created_at +``` + +Persists the customer AI support chat. `chat_message()` writes both the user turn and the assistant reply into the session (creating one lazily on the first message; `session.updated_at` bumped each turn). `chat()` reloads the customer's **most recent** session into the chat window for continuity (unless `?new=1`). Read-only history views exist for the customer (`/support/my-conversations`) and staff (`/support/admin/conversations`). `SupportChatSession.preview` = first user message; `.message_count` for list views. See §18 "Support Chat". + **Flow:** - Customer submits ticket via chat page modal → status `open` → admins notified (in-app + email) - Admin replies → status auto-advances to `answered` → customer notified (in-app + email, link to `/support/my-tickets/`) @@ -454,7 +465,7 @@ Management (`/scheduled-inspections/new|edit|delete`) is `@project_manager_requi | `reports` | `/reports` | index, facility report, scorecard, CSV/PDF/Excel export, issues-aging, sla-compliance, followup-closure, facility summary PDF | | `scheduled_reports` | `/scheduled-reports` | CRUD + manual trigger (accessible via Reports sub-nav) | | `scheduled_inspections` | `/scheduled-inspections` | list, new/edit/delete (PM+), `GET //start` (assigned inspector or manager → creates linked inspection), `POST /run` (cron reminders, `token=DIGEST_SECRET`) | -| `support` | `/support` | `GET /chat`, `POST /chat/message` (AJAX→Groq), `POST /tickets`, `GET /my-tickets`, `GET/POST /my-tickets/`, `GET /admin/tickets`, `GET/POST /admin/tickets/` | +| `support` | `/support` | `GET /chat` (loads latest saved session; `?new=1` to start fresh), `POST /chat/message` (AJAX→Groq; **persists** user+assistant turns, returns `session_id`), `GET /my-conversations`, `GET /my-conversations/` (customer chat history), `GET /admin/conversations`, `GET /admin/conversations/` (staff, read-only), `POST /tickets`, `GET /my-tickets`, `GET/POST /my-tickets/`, `GET /admin/tickets`, `GET/POST /admin/tickets/` | | `broadcast` | `/admin/broadcast` | `GET /` (compose + history), `POST /send` (admin-only; fans out one Notification per targeted user) | | `devices` | `/admin/devices` | `GET /` (device list from `api_device_tokens`), `POST /notify` (admin-only) | | `api` | `/api/v1` | parent blueprint | @@ -761,7 +772,8 @@ phase1_projects_roles → phase6_features → phase7_mobile_api → phase8_notif → phase33_contract_recipients → phase34_facility_qr → phase35_issue_handler - → phase36_scheduled_insp ← HEAD + → phase36_scheduled_insp + → phase37_support_chat ← HEAD ``` ### phase21_performance_indexes @@ -880,6 +892,16 @@ sudo systemctl restart gunicorn # -d "token=YOUR_DIGEST_SECRET" ``` +### phase37_support_chat + +Revision id `phase37_support_chat`. Creates `support_chat_sessions` + `support_chat_messages` so the customer AI support-chat is persisted (see §5 and §18 "Support Chat"). Table existence checks — safe to re-run. + +**Deploy order:** +```bash +flask db upgrade +sudo systemctl restart gunicorn +``` + **Deploy order for phases 24–32:** ```bash flask db upgrade @@ -1097,9 +1119,10 @@ A **PDF Summary** button was added to `reports/scorecard.html` alongside the exi `GET /support/chat` — customer only. Renders: - Greeting message with `current_user.display_name` (injected via `var userName = {{ current_user.display_name | tojson }}` — use `tojson` not inline interpolation to prevent XSS/quote breaks). -- FAQ quick-reply chips: text stored in `data-faq="..."` HTML attribute (HTML-escaped with `| e`), read in JS via `btn.dataset.faq`. **Never use `| tojson` in an `onclick=""` attribute** — it emits double-quoted JSON inside a double-quoted attribute, breaking HTML parsing and truncating the `