/** * Mutaciones de alto nivel. Cada una: * 1. (updates) escribe optimistamente en la BD local para reflejar el cambio ya, * 2. encola la operación en el outbox para enviarla cuando haya red. * * El servidor SIEMPRE fija user_id/reported_by/project_id y valida permisos: * el cliente nunca los envía (ver brief §5.1). */ import { getDb } from '../db/database'; import { enqueueOperation } from '../db/outbox'; import { IssuePriority, IssueStatus, IssueType, } from '../api/types'; // ----- progress (append-only en servidor) ----- export function recordProgressUpdate(input: { phase_id: number; progress: number; comment?: string; location?: { lat: number; lng: number }; }): Promise { return enqueueOperation('progress_update', 'create', { ...input }); } // ----- feature.update (editable, last-write-wins) ----- export async function updateFeature(input: { id: number; status?: string; progress?: number; responsible?: string; }): Promise { const db = await getDb(); const sets: string[] = []; const params: unknown[] = []; if (input.status !== undefined) { sets.push('status = ?'); params.push(input.status); } if (input.progress !== undefined) { sets.push('progress = ?'); params.push(input.progress); } if (input.responsible !== undefined) { sets.push('responsible = ?'); params.push(input.responsible); } if (sets.length) { params.push(input.id); await db.runAsync( `UPDATE features SET ${sets.join(', ')}, dirty = 1 WHERE id = ?`, ...(params as never[]), ); } return enqueueOperation('feature', 'update', { ...input }); } // ----- inspection.create (append-only) ----- export function createInspection(input: { feature_id: number; template_id?: number; data?: Record; status?: string; result?: string; notes?: string; }): Promise { return enqueueOperation('inspection', 'create', { ...input }); } // ----- issues ----- export function createIssue(input: { project_id: number; feature_id?: number; title: string; description?: string; priority?: IssuePriority; status?: IssueStatus; type?: IssueType; }): Promise { return enqueueOperation('issue', 'create', { ...input }); } export async function updateIssue(input: { id: number; title?: string; description?: string; priority?: IssuePriority; status?: IssueStatus; type?: IssueType; assigned_to?: number; resolution_notes?: string; }): Promise { const db = await getDb(); const cols: Record = { title: input.title, description: input.description, priority: input.priority, status: input.status, type: input.type, assigned_to: input.assigned_to, resolution_notes: input.resolution_notes, }; const sets: string[] = []; const params: unknown[] = []; for (const [k, v] of Object.entries(cols)) { if (v !== undefined) { sets.push(`${k} = ?`); params.push(v); } } if (sets.length) { params.push(input.id); await db.runAsync( `UPDATE issues SET ${sets.join(', ')}, dirty = 1 WHERE id = ?`, ...(params as never[]), ); } return enqueueOperation('issue', 'update', { ...input }); } // ----- issue tasks ----- export function createIssueTask(input: { issue_id: number; title: string; assigned_to?: number; due_date?: string; is_done?: boolean; }): Promise { return enqueueOperation('issue_task', 'create', { ...input }); } export async function updateIssueTask(input: { id: number; title?: string; assigned_to?: number; due_date?: string; is_done?: boolean; }): Promise { const db = await getDb(); if (input.is_done !== undefined) { await db.runAsync( 'UPDATE issue_tasks SET is_done = ?, dirty = 1 WHERE id = ?', input.is_done ? 1 : 0, input.id, ); } return enqueueOperation('issue_task', 'update', { ...input }); } // ----- issue comments (append-only) ----- export function createIssueComment(input: { issue_id: number; body: string; }): Promise { return enqueueOperation('issue_comment', 'create', { ...input }); }