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:
@@ -3,8 +3,10 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { CustomerPicker } from "@/components/CustomerPicker";
|
||||
import { DiscardBatchCard } from "@/components/DiscardBatchCard";
|
||||
import {
|
||||
confirmPolicyOcrBatch,
|
||||
discardPolicyOcrBatch,
|
||||
getPolicyOcrBatch,
|
||||
listCustomers,
|
||||
listPolicyOcrDocuments,
|
||||
@@ -23,6 +25,8 @@ import type {
|
||||
PolicyOcrReviewInput,
|
||||
} from "@/lib/types";
|
||||
|
||||
/** Document and batch statuses share this map — the two enums have no
|
||||
* overlapping members, and the header renders a batch status through it. */
|
||||
const STATUS_LABEL: Record<string, string> = {
|
||||
PENDING_OCR: "Pendiente",
|
||||
OCR_FAILED: "Falló OCR",
|
||||
@@ -31,6 +35,12 @@ const STATUS_LABEL: Record<string, string> = {
|
||||
CONFIRMED: "Confirmado",
|
||||
POSTED: "Aplicado",
|
||||
REJECTED: "Rechazado",
|
||||
UPLOADED: "Recibido",
|
||||
PROCESSING: "Procesando…",
|
||||
READY_FOR_REVIEW: "Listo para revisar",
|
||||
COMPLETED: "Aplicado",
|
||||
FAILED: "Falló",
|
||||
DISCARDED: "Descartado",
|
||||
};
|
||||
|
||||
const OPEN_FIRST = [
|
||||
@@ -54,6 +64,7 @@ export function PolicyOcrReview({ id }: { id: string }) {
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [discarding, setDiscarding] = useState(false);
|
||||
|
||||
const load = useCallback(async () => {
|
||||
try {
|
||||
@@ -129,9 +140,33 @@ export function PolicyOcrReview({ id }: { id: string }) {
|
||||
}
|
||||
}
|
||||
|
||||
async function onDiscard() {
|
||||
if (!batch) return;
|
||||
setDiscarding(true);
|
||||
setError(null);
|
||||
try {
|
||||
await discardPolicyOcrBatch(batch.id);
|
||||
setEdits({});
|
||||
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 appliedCount = docs.filter((d) => d.status === "POSTED").length;
|
||||
// Discarding is only offered while the batch can still be abandoned whole:
|
||||
// nothing applied yet, and not already discarded.
|
||||
const canDiscard =
|
||||
canReview &&
|
||||
batch.status !== "DISCARDED" &&
|
||||
batch.status !== "COMPLETED" &&
|
||||
appliedCount === 0;
|
||||
|
||||
return (
|
||||
<div className="stack">
|
||||
<header className="page-head">
|
||||
@@ -175,6 +210,15 @@ export function PolicyOcrReview({ id }: { id: string }) {
|
||||
</section>
|
||||
)}
|
||||
|
||||
{canDiscard && (
|
||||
<DiscardBatchCard
|
||||
busy={discarding}
|
||||
onDiscard={onDiscard}
|
||||
pageCount={docs.length}
|
||||
what="póliza"
|
||||
/>
|
||||
)}
|
||||
|
||||
<section className="stack">
|
||||
{sorted.map((doc) => (
|
||||
<DocumentRow
|
||||
|
||||
Reference in New Issue
Block a user