From a59a246fdcfe8a159b62ba690bbf60286dc7a2e7 Mon Sep 17 00:00:00 2001 From: kevin-asprec Date: Thu, 5 Mar 2026 18:28:07 +0800 Subject: [PATCH] fix(05-06): add Collection and Remittance CASL subjects, update routes - Add Collection and Remittance to AppSubjects union type - Grant OFFICE_STAFF manage:Collection and manage:Remittance - Grant COLLECTOR create/read:Collection and create/read:Remittance - Update all 8 collection/remittance route handlers from Subscriber to their dedicated CASL subjects (Collection or Remittance) - Update JSDoc comments in route files to reflect new subject names Co-Authored-By: Claude Opus 4.6 --- src/app/api/collections/[id]/route.ts | 4 ++-- src/app/api/collections/[id]/void/route.ts | 4 ++-- src/app/api/collections/route.ts | 8 ++++---- src/app/api/remittances/[id]/verify/route.ts | 4 ++-- src/app/api/remittances/route.ts | 8 ++++---- src/app/api/reports/collections/route.ts | 4 ++-- src/lib/casl/permissions.ts | 9 +++++++++ src/lib/casl/types.ts | 2 ++ 8 files changed, 27 insertions(+), 16 deletions(-) diff --git a/src/app/api/collections/[id]/route.ts b/src/app/api/collections/[id]/route.ts index a3452aa..08a81db 100644 --- a/src/app/api/collections/[id]/route.ts +++ b/src/app/api/collections/[id]/route.ts @@ -1,12 +1,12 @@ /** - * GET /api/collections/[id] — Get a single collection with allocations + * GET /api/collections/[id] — Get a single collection with allocations (requires read:Collection) */ import { NextRequest, NextResponse } from "next/server"; import { withPermission } from "@/lib/middleware/authorize"; import { withTenantContext } from "@/lib/prisma-tenant"; export function GET(req: NextRequest, { params }: { params: Promise<{ id: string }> }) { - return withPermission("read", "Subscriber")( + return withPermission("read", "Collection")( async (_req: NextRequest, { user }) => { if (!user.tenantId) { return NextResponse.json( diff --git a/src/app/api/collections/[id]/void/route.ts b/src/app/api/collections/[id]/void/route.ts index 0c93c94..3d3ffed 100644 --- a/src/app/api/collections/[id]/void/route.ts +++ b/src/app/api/collections/[id]/void/route.ts @@ -1,5 +1,5 @@ /** - * POST /api/collections/[id]/void — Void a collection (reversing JE) + * POST /api/collections/[id]/void — Void a collection (requires update:Collection) */ import { NextRequest, NextResponse } from "next/server"; import { withPermission } from "@/lib/middleware/authorize"; @@ -7,7 +7,7 @@ import { withTenantContext } from "@/lib/prisma-tenant"; import { voidCollection } from "@/lib/services/collector-service"; export function POST(req: NextRequest, { params }: { params: Promise<{ id: string }> }) { - return withPermission("update", "Subscriber")( + return withPermission("update", "Collection")( async (_req: NextRequest, { user }) => { if (!user.tenantId) { return NextResponse.json( diff --git a/src/app/api/collections/route.ts b/src/app/api/collections/route.ts index 69b38cd..4637031 100644 --- a/src/app/api/collections/route.ts +++ b/src/app/api/collections/route.ts @@ -1,13 +1,13 @@ /** - * POST /api/collections — Record a new cash collection - * GET /api/collections — Get collection history (filtered by subscriberId or collectorId) + * POST /api/collections — Record a new cash collection (requires create:Collection) + * GET /api/collections — Get collection history (requires read:Collection) */ import { NextRequest, NextResponse } from "next/server"; import { withPermission } from "@/lib/middleware/authorize"; import { withTenantContext } from "@/lib/prisma-tenant"; import { recordCollection, getCollectionHistory } from "@/lib/services/collector-service"; -export const POST = withPermission("create", "Subscriber")( +export const POST = withPermission("create", "Collection")( async (req: NextRequest, { user }) => { if (!user.tenantId) { return NextResponse.json( @@ -53,7 +53,7 @@ export const POST = withPermission("create", "Subscriber")( } ); -export const GET = withPermission("read", "Subscriber")( +export const GET = withPermission("read", "Collection")( async (req: NextRequest, { user }) => { if (!user.tenantId) { return NextResponse.json( diff --git a/src/app/api/remittances/[id]/verify/route.ts b/src/app/api/remittances/[id]/verify/route.ts index 1a52046..f4f2a4f 100644 --- a/src/app/api/remittances/[id]/verify/route.ts +++ b/src/app/api/remittances/[id]/verify/route.ts @@ -1,5 +1,5 @@ /** - * POST /api/remittances/[id]/verify — Verify a remittance (office staff counts total) + * POST /api/remittances/[id]/verify — Verify a remittance (requires update:Remittance) */ import { NextRequest, NextResponse } from "next/server"; import { withPermission } from "@/lib/middleware/authorize"; @@ -7,7 +7,7 @@ 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")( + return withPermission("update", "Remittance")( async (innerReq: NextRequest, { user }) => { if (!user.tenantId) { return NextResponse.json( diff --git a/src/app/api/remittances/route.ts b/src/app/api/remittances/route.ts index cb054c0..bb31467 100644 --- a/src/app/api/remittances/route.ts +++ b/src/app/api/remittances/route.ts @@ -1,6 +1,6 @@ /** - * POST /api/remittances — Create a new remittance (collector declares total) - * GET /api/remittances — List remittances with optional filtering + * POST /api/remittances — Create a new remittance (requires create:Remittance) + * GET /api/remittances — List remittances with optional filtering (requires read:Remittance) */ import { NextRequest, NextResponse } from "next/server"; import { withPermission } from "@/lib/middleware/authorize"; @@ -8,7 +8,7 @@ import { withTenantContext } from "@/lib/prisma-tenant"; import { createRemittance, listRemittances } from "@/lib/services/remittance-service"; import { RemittanceStatus } from "@prisma/client"; -export const POST = withPermission("create", "Subscriber")( +export const POST = withPermission("create", "Remittance")( async (req: NextRequest, { user }) => { if (!user.tenantId) { return NextResponse.json( @@ -50,7 +50,7 @@ export const POST = withPermission("create", "Subscriber")( } ); -export const GET = withPermission("read", "Subscriber")( +export const GET = withPermission("read", "Remittance")( async (req: NextRequest, { user }) => { if (!user.tenantId) { return NextResponse.json( diff --git a/src/app/api/reports/collections/route.ts b/src/app/api/reports/collections/route.ts index 1ace0ac..565adcf 100644 --- a/src/app/api/reports/collections/route.ts +++ b/src/app/api/reports/collections/route.ts @@ -1,5 +1,5 @@ /** - * GET /api/reports/collections — Daily collection summary report + * GET /api/reports/collections — Daily collection summary report (requires read:Collection) * * Query params: * date — ISO date string (defaults to today) @@ -10,7 +10,7 @@ import { withPermission } from "@/lib/middleware/authorize"; import { withTenantContext } from "@/lib/prisma-tenant"; import { getDailyCollectionSummary, getCollectorCollectionDetail } from "@/lib/services/collection-report-service"; -export const GET = withPermission("read", "Subscriber")( +export const GET = withPermission("read", "Collection")( async (req: NextRequest, { user }) => { if (!user.tenantId) { return NextResponse.json( diff --git a/src/lib/casl/permissions.ts b/src/lib/casl/permissions.ts index 3485316..2859962 100644 --- a/src/lib/casl/permissions.ts +++ b/src/lib/casl/permissions.ts @@ -66,6 +66,9 @@ export function definePermissionsFor( can("manage", "Expense"); // Vendor management (CRUD) can("manage", "Vendor"); + // Collection and remittance management + can("manage", "Collection"); + can("manage", "Remittance"); // Job type rates (read-only for office staff — admin configures rates) can("read", "JobTypeRate"); // View financial reports (read-only) @@ -88,6 +91,12 @@ export function definePermissionsFor( can("create", "Payment"); // View payment history can("read", "Payment"); + // Can create and view collections + can("create", "Collection"); + can("read", "Collection"); + // Can create and view remittances + can("create", "Remittance"); + can("read", "Remittance"); // NOTE: No explicit cannot() needed — Collector simply has no rules for // Invoice, User management, or Reports. Absence of a rule = no access. break; diff --git a/src/lib/casl/types.ts b/src/lib/casl/types.ts index e63378d..faaf02b 100644 --- a/src/lib/casl/types.ts +++ b/src/lib/casl/types.ts @@ -13,6 +13,8 @@ export type AppSubjects = | "Subscriber" | "Invoice" | "Payment" + | "Collection" + | "Remittance" | "Zone" | "Ticket" | "JobOrder"