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:
@@ -342,10 +342,54 @@ export class StatementsService {
|
||||
if (doc.status === "POSTED") {
|
||||
throw new BadRequestException("Este documento ya fue registrado.");
|
||||
}
|
||||
return this.prisma.statementDocument.update({
|
||||
const updated = await this.prisma.statementDocument.update({
|
||||
where: { id },
|
||||
data: { status: "REJECTED", reviewedById, reviewedAt: new Date() },
|
||||
});
|
||||
// Rejecting the last open page settles the batch just as posting it would
|
||||
// — without this, a fully-rejected batch sat in READY_FOR_REVIEW forever
|
||||
// because only confirmBatch() ever closed one.
|
||||
await this.closeIfDone(doc.batchId);
|
||||
return updated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw away a whole batch that is pending review: every page that has not
|
||||
* been posted is marked REJECTED and the batch itself becomes DISCARDED.
|
||||
*
|
||||
* Refuses once any page is POSTED — those pages already wrote ledger rows
|
||||
* against a check, and a "discarded" label on the batch would leave those
|
||||
* charges unexplained. Reject the remaining pages individually instead.
|
||||
*/
|
||||
async discardBatch(batchId: string, reviewedById: string) {
|
||||
const batch = await this.prisma.statementBatch.findUnique({
|
||||
where: { id: batchId },
|
||||
});
|
||||
if (!batch) throw new NotFoundException("Lote no encontrado.");
|
||||
if (batch.status === "DISCARDED") {
|
||||
throw new BadRequestException("Este lote ya fue descartado.");
|
||||
}
|
||||
|
||||
const posted = await this.prisma.statementDocument.count({
|
||||
where: { batchId, status: "POSTED" },
|
||||
});
|
||||
if (posted > 0) {
|
||||
throw new BadRequestException(
|
||||
`No se puede descartar: ${posted} página(s) ya se registraron en el estado de cuenta.`,
|
||||
);
|
||||
}
|
||||
|
||||
const { count } = await this.prisma.statementDocument.updateMany({
|
||||
where: { batchId, status: { notIn: ["POSTED", "REJECTED"] } },
|
||||
data: { status: "REJECTED", reviewedById, reviewedAt: new Date() },
|
||||
});
|
||||
|
||||
await this.prisma.statementBatch.update({
|
||||
where: { id: batchId },
|
||||
data: { status: "DISCARDED", completedAt: new Date() },
|
||||
});
|
||||
|
||||
return { batchId, rejected: count };
|
||||
}
|
||||
|
||||
// --- posting --------------------------------------------------------------
|
||||
@@ -461,8 +505,10 @@ export class StatementsService {
|
||||
where: { batchId, status: { in: OPEN } },
|
||||
});
|
||||
if (open === 0) {
|
||||
await this.prisma.statementBatch.update({
|
||||
where: { id: batchId },
|
||||
await this.prisma.statementBatch.updateMany({
|
||||
// `updateMany` + a status filter so a discarded batch is never quietly
|
||||
// relabelled COMPLETED by a late reject on one of its pages.
|
||||
where: { id: batchId, status: { not: "DISCARDED" } },
|
||||
data: { status: "COMPLETED", completedAt: new Date() },
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user