Files
bellsystems-cp/.claude/crm-step-04.md

3.7 KiB
Raw Blame History

CRM Step 04 — Backend: Comms Log + Media (SQLite)

Context

Read .claude/crm-build-plan.md for full schema, conventions, and IMPORTANT NOTES. Steps 0103 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:

  • 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