97 lines
3.7 KiB
Markdown
97 lines
3.7 KiB
Markdown
# CRM Step 04 — Backend: Comms Log + Media (SQLite)
|
||
|
||
## Context
|
||
Read `.claude/crm-build-plan.md` for full schema, conventions, and IMPORTANT NOTES.
|
||
Steps 01–03 must be complete.
|
||
|
||
## Task
|
||
Add `crm_comms_log` and `crm_media` tables to the existing SQLite DB, plus CRUD endpoints.
|
||
|
||
## What to build
|
||
|
||
### 1. Add tables to `backend/mqtt/database.py`
|
||
Inside `init_db()`, add these CREATE TABLE IF NOT EXISTS statements alongside existing tables:
|
||
|
||
```sql
|
||
CREATE TABLE IF NOT EXISTS crm_comms_log (
|
||
id TEXT PRIMARY KEY,
|
||
customer_id TEXT NOT NULL,
|
||
type TEXT NOT NULL,
|
||
direction TEXT NOT NULL,
|
||
subject TEXT,
|
||
body TEXT,
|
||
attachments TEXT DEFAULT '[]',
|
||
ext_message_id TEXT,
|
||
logged_by TEXT,
|
||
occurred_at TEXT NOT NULL,
|
||
created_at TEXT NOT NULL
|
||
);
|
||
|
||
CREATE TABLE IF NOT EXISTS crm_media (
|
||
id TEXT PRIMARY KEY,
|
||
customer_id TEXT,
|
||
order_id TEXT,
|
||
filename TEXT NOT NULL,
|
||
nextcloud_path TEXT NOT NULL,
|
||
mime_type TEXT,
|
||
direction TEXT,
|
||
tags TEXT DEFAULT '[]',
|
||
uploaded_by TEXT,
|
||
created_at TEXT NOT NULL
|
||
);
|
||
```
|
||
|
||
### 2. Add to `backend/crm/models.py`
|
||
|
||
**Comms:**
|
||
- `CommType` enum — email, whatsapp, call, sms, note, in_person
|
||
- `CommDirection` enum — inbound, outbound, internal
|
||
- `CommAttachment` — filename (str), nextcloud_path (str)
|
||
- `CommCreate` — customer_id, type (CommType), direction (CommDirection), subject (Optional[str]), body (Optional[str]), attachments (List[CommAttachment] default []), ext_message_id (Optional[str]), logged_by (Optional[str]), occurred_at (str ISO — default to now if not provided)
|
||
- `CommUpdate` — subject, body, occurred_at all Optional
|
||
- `CommInDB` — all fields + id, created_at
|
||
- `CommListResponse` — entries: List[CommInDB], total: int
|
||
|
||
**Media:**
|
||
- `MediaDirection` enum — received, sent, internal
|
||
- `MediaCreate` — customer_id (Optional[str]), order_id (Optional[str]), filename, nextcloud_path, mime_type (Optional), direction (MediaDirection optional), tags (List[str] default []), uploaded_by (Optional[str])
|
||
- `MediaInDB` — all fields + id, created_at
|
||
- `MediaListResponse` — items: List[MediaInDB], total: int
|
||
|
||
### 3. Add to `backend/crm/service.py`
|
||
Import `from mqtt import database as mqtt_db` for aiosqlite access.
|
||
|
||
**Comms functions (all async):**
|
||
- `list_comms(customer_id, type=None, direction=None, limit=100) -> List[CommInDB]`
|
||
— SELECT ... WHERE customer_id=? ORDER BY occurred_at DESC
|
||
- `get_comm(comm_id) -> CommInDB` — 404 if not found
|
||
- `create_comm(data: CommCreate) -> CommInDB` — uuid id, created_at now, store attachments as JSON string
|
||
- `update_comm(comm_id, data: CommUpdate) -> CommInDB`
|
||
- `delete_comm(comm_id) -> None`
|
||
|
||
**Media functions (all async):**
|
||
- `list_media(customer_id=None, order_id=None) -> List[MediaInDB]`
|
||
- `create_media(data: MediaCreate) -> MediaInDB`
|
||
- `delete_media(media_id) -> None`
|
||
|
||
Parse `attachments` and `tags` JSON strings back to lists when returning models.
|
||
|
||
### 4. Add to `backend/crm/router.py`
|
||
Prefix `/api/crm/comms`:
|
||
- `GET /` — list_comms (query: customer_id required, type, direction)
|
||
- `POST /` — create_comm
|
||
- `PUT /{comm_id}` — update_comm
|
||
- `DELETE /{comm_id}` — delete_comm
|
||
|
||
Prefix `/api/crm/media`:
|
||
- `GET /` — list_media (query: customer_id or order_id)
|
||
- `POST /` — create_media (metadata only — no file upload here, that's Step 9)
|
||
- `DELETE /{media_id}` — delete_media
|
||
|
||
Register both in `backend/main.py`.
|
||
|
||
## Notes
|
||
- Use `mqtt_db.db` — it is an aiosqlite connection, use `async with mqtt_db.db.execute(...)` pattern
|
||
- Look at `backend/mqtt/database.py` for exact aiosqlite usage pattern
|
||
- attachments and tags are stored as JSON strings in SQLite, deserialized to lists in the Pydantic model
|