3.7 KiB
3.7 KiB
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:
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:
CommTypeenum — email, whatsapp, call, sms, note, in_personCommDirectionenum — inbound, outbound, internalCommAttachment— 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 OptionalCommInDB— all fields + id, created_atCommListResponse— entries: List[CommInDB], total: int
Media:
MediaDirectionenum — received, sent, internalMediaCreate— 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_atMediaListResponse— 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 DESCget_comm(comm_id) -> CommInDB— 404 if not foundcreate_comm(data: CommCreate) -> CommInDB— uuid id, created_at now, store attachments as JSON stringupdate_comm(comm_id, data: CommUpdate) -> CommInDBdelete_comm(comm_id) -> None
Media functions (all async):
list_media(customer_id=None, order_id=None) -> List[MediaInDB]create_media(data: MediaCreate) -> MediaInDBdelete_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_commPUT /{comm_id}— update_commDELETE /{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, useasync with mqtt_db.db.execute(...)pattern - Look at
backend/mqtt/database.pyfor exact aiosqlite usage pattern - attachments and tags are stored as JSON strings in SQLite, deserialized to lists in the Pydantic model