feat(ocr): discard abandoned capture batches
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
This commit is contained in:
@@ -4,8 +4,10 @@ import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { AppShell } from "@/components/AppShell";
|
||||
import { CustomerPicker } from "@/components/CustomerPicker";
|
||||
import { DiscardBatchCard } from "@/components/DiscardBatchCard";
|
||||
import {
|
||||
confirmStatementBatch,
|
||||
discardStatementBatch,
|
||||
getStatementBatch,
|
||||
listStatementDocuments,
|
||||
rejectStatementDocument,
|
||||
@@ -66,6 +68,7 @@ function BatchReview({ id }: { id: string }) {
|
||||
const [docs, setDocs] = useState<StatementDocument[]>([]);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [discarding, setDiscarding] = useState(false);
|
||||
|
||||
const load = useCallback(async () => {
|
||||
try {
|
||||
@@ -108,9 +111,31 @@ function BatchReview({ id }: { id: string }) {
|
||||
(d) => d.status === "MATCHED" && d.matchedCustomer,
|
||||
).length;
|
||||
|
||||
async function discard() {
|
||||
setDiscarding(true);
|
||||
setError(null);
|
||||
try {
|
||||
await discardStatementBatch(id);
|
||||
await load();
|
||||
} catch (e) {
|
||||
setError((e as Error)?.message ?? "No se pudo descartar el lote.");
|
||||
} finally {
|
||||
setDiscarding(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (loading) return <div className="state-box">Cargando…</div>;
|
||||
if (!batch) return <div className="state-box state-error">{error ?? "No encontrado."}</div>;
|
||||
|
||||
const postedCount = batch.byStatus.POSTED ?? 0;
|
||||
// Discarding is only offered while the batch can still be abandoned whole:
|
||||
// nothing posted to the ledger yet, and not already settled.
|
||||
const canDiscard =
|
||||
canReview &&
|
||||
batch.status !== "DISCARDED" &&
|
||||
batch.status !== "COMPLETED" &&
|
||||
postedCount === 0;
|
||||
|
||||
return (
|
||||
<div className="stack">
|
||||
<header className="page-head">
|
||||
@@ -146,6 +171,15 @@ function BatchReview({ id }: { id: string }) {
|
||||
/>
|
||||
)}
|
||||
|
||||
{canDiscard && (
|
||||
<DiscardBatchCard
|
||||
busy={discarding}
|
||||
onDiscard={discard}
|
||||
pageCount={docs.length}
|
||||
what="recibo"
|
||||
/>
|
||||
)}
|
||||
|
||||
<section className="stack">
|
||||
{sorted.map((doc) => (
|
||||
<DocumentRow
|
||||
@@ -166,6 +200,7 @@ const STATUS_LABEL_BATCH: Record<string, string> = {
|
||||
READY_FOR_REVIEW: "Listo para revisar",
|
||||
COMPLETED: "Registrado",
|
||||
FAILED: "Falló",
|
||||
DISCARDED: "Descartado",
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user