from __future__ import annotations import json from typing import Iterable from app.db import Database from app.models import ( ApplicationCreate, ApplicationEventRecord, ApplicationRecord, ExternalSyncStatus, utc_now_iso, ) class ApplicationRepository: def __init__(self, db: Database) -> None: self._db = db def create(self, payload: ApplicationCreate) -> ApplicationRecord: now = utc_now_iso() with self._db.connect() as conn: cursor = conn.execute( """ INSERT INTO applications ( customer_id, customer_name, chat_id, category, description, attachments_json, status, external_sync_status, source, created_at, updated_at ) VALUES (?, ?, ?, ?, ?, ?, 'new', ?, ?, ?, ?) """, ( payload.customer_id, payload.customer_name, payload.chat_id, payload.category, payload.description, payload.attachments_json, ExternalSyncStatus.NOT_SENT.value, payload.source, now, now, ), ) app_id = cursor.lastrowid self.add_event( conn=conn, application_id=app_id, event_type="created", actor=payload.source, details={"category": payload.category}, ) row = conn.execute("SELECT * FROM applications WHERE id = ?", (app_id,)).fetchone() conn.commit() return ApplicationRecord(**dict(row)) def get(self, application_id: int) -> ApplicationRecord | None: with self._db.connect() as conn: row = conn.execute("SELECT * FROM applications WHERE id = ?", (application_id,)).fetchone() return ApplicationRecord(**dict(row)) if row else None def list( self, status: str | None = None, search: str | None = None, limit: int = 100, offset: int = 0, ) -> list[ApplicationRecord]: clauses: list[str] = [] params: list[object] = [] if status: clauses.append("status = ?") params.append(status) if search: clauses.append("(customer_name LIKE ? OR description LIKE ? OR CAST(id AS TEXT) LIKE ?)") like = f"%{search}%" params.extend([like, like, like]) where_sql = f"WHERE {' AND '.join(clauses)}" if clauses else "" params.extend([limit, offset]) sql = f""" SELECT * FROM applications {where_sql} ORDER BY created_at DESC LIMIT ? OFFSET ? """ with self._db.connect() as conn: rows = conn.execute(sql, params).fetchall() return [ApplicationRecord(**dict(row)) for row in rows] def update_status( self, application_id: int, status: str, actor: str, reason: str | None = None, ) -> ApplicationRecord | None: now = utc_now_iso() with self._db.connect() as conn: exists = conn.execute("SELECT id FROM applications WHERE id = ?", (application_id,)).fetchone() if not exists: return None conn.execute( "UPDATE applications SET status = ?, updated_at = ? WHERE id = ?", (status, now, application_id), ) self.add_event( conn=conn, application_id=application_id, event_type="status_changed", actor=actor, details={"status": status, "reason": reason}, ) row = conn.execute("SELECT * FROM applications WHERE id = ?", (application_id,)).fetchone() conn.commit() return ApplicationRecord(**dict(row)) def update_external_sync( self, application_id: int, sync_status: str, external_id: str | None, external_error: str | None, ) -> None: now = utc_now_iso() with self._db.connect() as conn: conn.execute( """ UPDATE applications SET external_sync_status = ?, external_id = ?, external_error = ?, updated_at = ? WHERE id = ? """, (sync_status, external_id, external_error, now, application_id), ) self.add_event( conn=conn, application_id=application_id, event_type="external_sync", actor="external_api", details={ "sync_status": sync_status, "external_id": external_id, "external_error": external_error, }, ) conn.commit() def get_events(self, application_id: int) -> list[ApplicationEventRecord]: with self._db.connect() as conn: rows = conn.execute( """ SELECT * FROM application_events WHERE application_id = ? ORDER BY created_at DESC """, (application_id,), ).fetchall() return [ApplicationEventRecord(**dict(row)) for row in rows] @staticmethod def add_event( conn, application_id: int, event_type: str, actor: str, details: dict[str, object] | None = None, ) -> None: conn.execute( """ INSERT INTO application_events (application_id, event_type, actor, details_json, created_at) VALUES (?, ?, ?, ?, ?) """, ( application_id, event_type, actor, json.dumps(details or {}, ensure_ascii=False), utc_now_iso(), ), )