"""Audit trail helpers. Audit rows are append-only: the application exposes no endpoint that updates or deletes them.""" from typing import Any from sqlalchemy.ext.asyncio import AsyncSession from app.models import AuditLog, Ncr def _fmt(value: Any) -> str | None: if value is None: return None if isinstance(value, bool): return "true" if value else "false" return str(value) def audit_event( db: AsyncSession, *, user_id: int, action: str, ncr_id: int | None = None, field_name: str | None = None, old_value: Any = None, new_value: Any = None, detail: str | None = None, ) -> None: db.add( AuditLog( ncr_id=ncr_id, user_id=user_id, action=action, field_name=field_name, old_value=_fmt(old_value), new_value=_fmt(new_value), detail=detail, ) ) def apply_field_updates( db: AsyncSession, ncr: Ncr, user_id: int, updates: dict[str, Any], action: str = "update", ) -> dict[str, tuple[Any, Any]]: """Set attributes on the NCR, writing one audit row per actually-changed field. Returns {field: (old, new)} for the fields that changed.""" changes: dict[str, tuple[Any, Any]] = {} for field, new_value in updates.items(): old_value = getattr(ncr, field) if old_value == new_value: continue setattr(ncr, field, new_value) changes[field] = (old_value, new_value) audit_event( db, ncr_id=ncr.id, user_id=user_id, action=action, field_name=field, old_value=old_value, new_value=new_value, ) return changes