feat(03-02): Collector service, remittance service, report service, APIs, and tests

- collector-service.ts: recordCollection (FIFO, zone enforcement, DR 1030/CR 1100 JE),
  voidCollection (reversing JE), getCollectionHistory
- remittance-service.ts: createRemittance, verifyRemittance (DR 1010/CR 1030, variance
  non-blocking), listRemittances
- collection-report-service.ts: getDailyCollectionSummary, getCollectorCollectionDetail
- 6 API routes: POST/GET /collections, GET /collections/[id],
  POST /collections/[id]/void, POST/GET /remittances,
  POST /remittances/[id]/verify, GET /reports/collections
- 26 integration tests: 13 collector (FIFO, zone enforcement, JE verification, void,
  cross-tenant) + 13 remittance (variance, JE accounts, double-verify rejection)
- All 26 tests pass
This commit is contained in:
kevin-asprec
2026-03-05 07:54:39 +08:00
parent 0967fc23fd
commit a72aaa987d
11 changed files with 2412 additions and 0 deletions

View File

@@ -0,0 +1,49 @@
/**
* POST /api/remittances/[id]/verify — Verify a remittance (office staff counts total)
*/
import { NextRequest, NextResponse } from "next/server";
import { withPermission } from "@/lib/middleware/authorize";
import { withTenantContext } from "@/lib/prisma-tenant";
import { verifyRemittance } from "@/lib/services/remittance-service";
export function POST(req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
return withPermission("update", "Subscriber")(
async (innerReq: NextRequest, { user }) => {
if (!user.tenantId) {
return NextResponse.json(
{ error: "No tenant context — super-admins must use the admin API" },
{ status: 400 }
);
}
const { id } = await params;
let body: unknown;
try {
body = await innerReq.json();
} catch {
return NextResponse.json({ error: "Invalid JSON body" }, { status: 400 });
}
const { verifiedTotal, notes } = body as Record<string, unknown>;
if (verifiedTotal === undefined || verifiedTotal === null) {
return NextResponse.json({ error: "verifiedTotal is required" }, { status: 400 });
}
const tenantPrisma = withTenantContext(user.tenantId);
try {
const result = await verifyRemittance(tenantPrisma, user.tenantId, id, {
verifiedById: user.id,
verifiedTotal: verifiedTotal as string | number,
notes: notes as string | undefined,
});
return NextResponse.json(result);
} catch (err) {
const message = err instanceof Error ? err.message : "Failed to verify remittance";
return NextResponse.json({ error: message }, { status: 400 });
}
}
)(req);
}