import { uuidv7 } from '@sims/domain' import { writeAudit } from './audit' import type { DB } from './db' import { getClient } from './repos-clients' /** Interaction log — the "memories into the system" table (D12 plain-repo pattern). */ export interface InteractionType { code: string; label: string } export function listInteractionTypes(db: DB): InteractionType[] { return db.prepare(`SELECT code, label FROM interaction_type ORDER BY label`).all() as InteractionType[] } export function createInteractionType(db: DB, userId: string, input: { code: string; label: string }): InteractionType { if (input.code.trim() === '' || input.label.trim() === '') throw new Error('code and label are required') db.prepare(`INSERT INTO interaction_type (code, label) VALUES (?, ?)`).run(input.code.trim(), input.label.trim()) writeAudit(db, userId, 'create', 'interaction_type', input.code, undefined, input) return { code: input.code.trim(), label: input.label.trim() } } export type Outcome = 'positive' | 'neutral' | 'negative' export interface Interaction { id: string; clientId: string; typeCode: string; onDate: string; staffId: string notes: string; outcome: Outcome | null; followUpOn: string | null; createdAt: string } interface InteractionRow { id: string; client_id: string; type_code: string; on_date: string; staff_id: string notes: string; outcome: string | null; follow_up_on: string | null; created_at: string } function toInteraction(r: InteractionRow): Interaction { return { id: r.id, clientId: r.client_id, typeCode: r.type_code, onDate: r.on_date, staffId: r.staff_id, notes: r.notes, outcome: r.outcome as Outcome | null, followUpOn: r.follow_up_on, createdAt: r.created_at, } } export function getInteraction(db: DB, id: string): Interaction | null { const row = db.prepare(`SELECT * FROM interaction WHERE id=?`).get(id) as InteractionRow | undefined return row === undefined ? null : toInteraction(row) } export function listInteractions(db: DB, clientId: string): Interaction[] { const rows = db.prepare( `SELECT * FROM interaction WHERE client_id=? ORDER BY on_date DESC, id DESC`, ).all(clientId) as InteractionRow[] return rows.map(toInteraction) } export interface CreateInteractionInput { clientId: string; typeCode: string; onDate: string notes?: string; outcome?: Outcome; followUpOn?: string | null } const OUTCOMES: Outcome[] = ['positive', 'neutral', 'negative'] export function createInteraction(db: DB, userId: string, input: CreateInteractionInput): Interaction { if (getClient(db, input.clientId) === null) throw new Error('Client not found') const type = db.prepare(`SELECT code FROM interaction_type WHERE code=?`).get(input.typeCode) if (type === undefined) throw new Error(`Unknown interaction type: ${input.typeCode}`) if (input.outcome !== undefined && !OUTCOMES.includes(input.outcome)) throw new Error(`Unknown outcome: ${input.outcome}`) const id = uuidv7() db.prepare( `INSERT INTO interaction (id, client_id, type_code, on_date, staff_id, notes, outcome, follow_up_on, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, ).run( id, input.clientId, input.typeCode, input.onDate, userId, input.notes ?? '', input.outcome ?? null, input.followUpOn ?? null, new Date().toISOString(), ) const interaction = getInteraction(db, id)! writeAudit(db, userId, 'create', 'interaction', id, undefined, interaction) return interaction } export interface InteractionPatch { notes?: string; outcome?: Outcome | null; followUpOn?: string | null } export function updateInteraction(db: DB, userId: string, id: string, patch: InteractionPatch): Interaction { const before = getInteraction(db, id) if (before === null) throw new Error('Interaction not found') if (patch.outcome !== undefined && patch.outcome !== null && !OUTCOMES.includes(patch.outcome)) { throw new Error(`Unknown outcome: ${patch.outcome}`) } const sets: string[] = [] const args: unknown[] = [] if (patch.notes !== undefined) { sets.push('notes=?'); args.push(patch.notes) } if (patch.outcome !== undefined) { sets.push('outcome=?'); args.push(patch.outcome) } if (patch.followUpOn !== undefined) { sets.push('follow_up_on=?'); args.push(patch.followUpOn) } if (sets.length > 0) { args.push(id) db.prepare(`UPDATE interaction SET ${sets.join(', ')} WHERE id=?`).run(...args) } const after = getInteraction(db, id)! writeAudit(db, userId, 'update', 'interaction', id, before, after) return after } /** Interactions with a follow-up date on or before the given date — the follow-ups queue. */ export function listOpenFollowUps(db: DB, onOrBefore: string): (Interaction & { clientName: string })[] { const rows = db.prepare( `SELECT i.*, c.name AS client_name FROM interaction i JOIN client c ON c.id = i.client_id WHERE i.follow_up_on IS NOT NULL AND i.follow_up_on <= ? ORDER BY i.follow_up_on DESC, i.id DESC`, ).all(onOrBefore) as (InteractionRow & { client_name: string })[] return rows.map((r) => ({ ...toInteraction(r), clientName: r.client_name })) }