A bad scan, the wrong PDFs or a duplicate upload used to leave a batch sitting in READY_FOR_REVIEW forever, because the only exits were confirm (posts to the books) or rejecting every page one at a time. Add a DISCARDED terminal status to both OCR domains and a single endpoint per domain that rejects every page still pending in one shot. Discarding is refused once anything has landed: statements once a page is POSTED, policies once a page is APPLIED. Those batches did real work and have to be settled page by page. - POST /statements/batches/:id/discard - POST /policy-ocr/batches/:id/discard - shared DiscardBatchCard on both review screens, gated the same way
81 lines
2.4 KiB
TypeScript
81 lines
2.4 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
|
|
/**
|
|
* "Throw this batch away" control, shared by both OCR review queues
|
|
* (recibos and pólizas).
|
|
*
|
|
* Confirmation is a two-step inline swap rather than `window.confirm`: the
|
|
* dialog would block the page, and an accidental discard is not undoable from
|
|
* the UI — the reviewer should read what they are about to lose, not dismiss
|
|
* a modal reflexively.
|
|
*
|
|
* The card is only rendered when the batch is still discardable; the API
|
|
* refuses again on its own (a page posted between render and click).
|
|
*/
|
|
export function DiscardBatchCard({
|
|
busy,
|
|
onDiscard,
|
|
pageCount,
|
|
what,
|
|
}: {
|
|
busy: boolean;
|
|
onDiscard: () => void;
|
|
pageCount: number;
|
|
/** Singular noun for what a page becomes — "recibo" / "póliza". */
|
|
what: string;
|
|
}) {
|
|
const [armed, setArmed] = useState(false);
|
|
|
|
return (
|
|
<section className="card" style={{ padding: 16 }}>
|
|
<h2 className="section-title" style={{ marginTop: 0 }}>
|
|
Descartar lote
|
|
</h2>
|
|
{armed ? (
|
|
<>
|
|
<p className="page-sub" style={{ marginBottom: 12 }}>
|
|
Se descartarán las {pageCount} página(s) de este lote y no se
|
|
creará ninguna {what}. Esto no se puede deshacer desde aquí; para
|
|
volver a intentarlo hay que subir los PDFs otra vez.
|
|
</p>
|
|
<div className="inline-form" style={{ gap: 8 }}>
|
|
<button
|
|
type="button"
|
|
className="btn btn-danger"
|
|
disabled={busy}
|
|
onClick={onDiscard}
|
|
>
|
|
{busy ? "Descartando…" : "Sí, descartar el lote"}
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className="btn btn-ghost"
|
|
disabled={busy}
|
|
onClick={() => setArmed(false)}
|
|
>
|
|
Cancelar
|
|
</button>
|
|
</div>
|
|
</>
|
|
) : (
|
|
<>
|
|
<p className="page-sub" style={{ marginBottom: 12 }}>
|
|
Si el lote quedó mal (escaneo ilegible, PDFs equivocados, subida
|
|
duplicada), descártelo para sacarlo de la cola de revisión.
|
|
</p>
|
|
<button
|
|
type="button"
|
|
className="btn btn-ghost"
|
|
disabled={busy}
|
|
onClick={() => setArmed(true)}
|
|
>
|
|
Descartar lote
|
|
</button>
|
|
</>
|
|
)}
|
|
</section>
|
|
);
|
|
}
|