// src/lib/document-status.ts /** * Turns a parse outcome into the status fields written back onto an * uploaded document row. * * This exists because the three synchronous importers used to create * their audit row with `status: "processed"` hardcoded *before* a single * data row was inserted, and never updated it again. Production ended up * with 19 plant_hours batches marked processed against one actual log * row, and no way for anyone to notice. * * Pure: no PocketBase, no clock, no I/O. The caller supplies `now`. */ import type { DocumentStatus, RowError } from "./pb-types"; /** How many per-row errors we persist. Enough to fix a sheet, bounded. */ export const MAX_ROW_ERRORS = 200; export type ParseOutcome = { /** Rows the parser found in the file, before validation. */ rowsTotal: number; /** Rows that actually became records. */ rowsCreated: number; rowErrors: RowError[]; }; export type DocumentOutcomeFields = { processing_status: DocumentStatus; processed_at: string; rows_total: number; rows_created: number; rows_failed: number; row_errors: RowError[]; last_error: string; }; export function resolveDocumentOutcome( outcome: ParseOutcome, now: Date, ): DocumentOutcomeFields { const { rowsTotal, rowsCreated, rowErrors } = outcome; const rowsFailed = rowErrors.length; let processing_status: DocumentStatus; let last_error: string; if (rowsTotal === 0) { // A file we could open but that held no data rows. Almost always the // wrong file or the wrong sheet — the operator needs to know. processing_status = "failed"; last_error = "no data rows found in file"; } else if (rowsCreated === 0) { // Parsed fine, landed nothing. Never call this processed. processing_status = "failed"; last_error = rowErrors[0]?.reason ?? "no rows could be imported"; } else if (rowsFailed > 0) { // Partial. The rows that landed are real, so this is processed — but // the failures are recorded and surfaced. processing_status = "processed"; last_error = `${rowsFailed} of ${rowsTotal} rows failed`; } else { processing_status = "processed"; last_error = ""; } return { processing_status, processed_at: now.toISOString(), rows_total: rowsTotal, rows_created: rowsCreated, rows_failed: rowsFailed, row_errors: rowErrors.slice(0, MAX_ROW_ERRORS), last_error, }; }