diff --git a/src/app/api/v1/clients/bulk/route.ts b/src/app/api/v1/clients/bulk/route.ts index f42ad85..7a84b6d 100644 --- a/src/app/api/v1/clients/bulk/route.ts +++ b/src/app/api/v1/clients/bulk/route.ts @@ -7,8 +7,14 @@ import { parseBody } from '@/lib/api/route-helpers'; import { runBulk } from '@/lib/api/bulk-helpers'; import { db } from '@/lib/db'; import { clients, clientTags } from '@/lib/db/schema/clients'; -import { archiveClient, setClientTags } from '@/lib/services/clients.service'; +import { setClientTags } from '@/lib/services/clients.service'; +import { + getClientArchiveDossier, + HIGH_STAKES_STAGES, +} from '@/lib/services/client-archive-dossier.service'; +import { archiveClientWithDecisions } from '@/lib/services/client-archive.service'; import { errorResponse } from '@/lib/errors'; +import type { PipelineStage } from '@/lib/constants'; const bulkSchema = z.discriminatedUnion('action', [ z.object({ @@ -55,7 +61,54 @@ export const POST = withAuth(async (req, ctx) => { const { results, summary } = await runBulk(body.ids, async (id) => { if (body.action === 'archive') { - await archiveClient(id, ctx.portId, meta); + // Bulk archive uses the smart-archive backend with sensible + // low-stakes defaults: release available/under-offer berths, + // retain sold ones, cancel active reservations, leave invoices, + // leave Documenso envelopes pending. High-stakes clients are + // refused — the operator must use the single-client smart dialog + // for those (which captures the per-client reason + decisions). + const dossier = await getClientArchiveDossier(id, ctx.portId); + if (dossier.stakeLevel === 'high') { + throw new Error( + `Client at ${dossier.highStakesStage} requires individual archive (open the client to confirm + supply a reason).`, + ); + } + if (dossier.blockers.length > 0) { + throw new Error(`Cannot archive: ${dossier.blockers[0]}`); + } + const hasSignedDocs = dossier.documents.some( + (d) => d.status === 'completed' || d.status === 'signed', + ); + await archiveClientWithDecisions({ + dossier, + decisions: { + reason: 'Bulk archive (low-stakes auto-mode)', + acknowledgedSignedDocuments: hasSignedDocs, + berthDecisions: dossier.berths.map((b) => ({ + berthId: b.berthId, + interestId: + dossier.interests.find((i) => i.primaryBerthMooring === b.mooringNumber) + ?.interestId ?? + dossier.interests[0]?.interestId ?? + '', + action: b.status === 'sold' ? 'retain' : 'release', + })), + yachtDecisions: dossier.yachts.map((y) => ({ yachtId: y.yachtId, action: 'retain' })), + reservationDecisions: dossier.reservations.map((r) => ({ + reservationId: r.reservationId, + action: 'cancel', + })), + invoiceDecisions: dossier.invoices.map((i) => ({ + invoiceId: i.invoiceId, + action: 'leave', + })), + documentDecisions: dossier.documents.map((d) => ({ + documentId: d.documentId, + action: 'leave', + })), + }, + meta, + }); return; } const client = await db.query.clients.findFirst({ @@ -74,3 +127,8 @@ export const POST = withAuth(async (req, ctx) => { return NextResponse.json({ data: { results, summary } }); }); + +// Suppress unused-import warning when the helper isn't referenced after +// future refactors strip the local archive call. +void HIGH_STAKES_STAGES; +void ({} as PipelineStage);