import { NextRequest, NextResponse } from 'next/server'; import { withAuth } from '@/lib/api-guards'; // GET: Fetch a single plan by ID export const GET = withAuth(async (req, { prisma, params }) => { const unwrappedParams = await params; const id = parseInt(unwrappedParams.id); if (isNaN(id)) { return NextResponse.json({ error: 'Invalid ID' }, { status: 400 }); } const plan = await prisma.compensationPlan.findUnique({ where: { id }, include: { rules: true } }); if (!plan) { return NextResponse.json({ error: 'Plan not found' }, { status: 404 }); } return NextResponse.json({ plan }); }); // PUT: Update or Version a plan (restricted to ADMIN and DIRECTOR) export const PUT = withAuth(async (req, { session, prisma, params }) => { const unwrappedParams = await params; const id = parseInt(unwrappedParams.id); const body = await req.json(); if (isNaN(id)) { return NextResponse.json({ error: 'Invalid ID' }, { status: 400 }); } const plan = await prisma.compensationPlan.findUnique({ where: { id } }); if (!plan) { return NextResponse.json({ error: 'Plan not found' }, { status: 404 }); } // Versioning replication if the plan is currently ACTIVE if (plan.status === 'ACTIVE') { const newPlan = await prisma.$transaction(async (tx: any) => { // Set RLS variables directly on the transaction client context await tx.$executeRawUnsafe(`SET LOCAL app.current_user_id = '${session.userId}';`); await tx.$executeRawUnsafe(`SET LOCAL app.current_user_role = '${session.role}';`); await tx.$executeRawUnsafe(`SET LOCAL app.current_hotel_id = '${session.hotelId}';`); await tx.$executeRawUnsafe(`SET LOCAL app.current_region_id = '${session.regionId}';`); // 1. Mark current plan version as INACTIVE await tx.compensationPlan.update({ where: { id }, data: { status: 'INACTIVE', validityEnd: new Date() } }); // 2. Clone to a new version record const clonedPlan = await tx.compensationPlan.create({ data: { name: body.name || plan.name, code: body.code || plan.code, validityStart: new Date(), // starts now validityEnd: body.validityEnd ? new Date(body.validityEnd) : null, type: body.type || plan.type, formula: body.formula !== undefined ? body.formula : plan.formula, metaAmount: body.metaAmount !== undefined ? parseFloat(body.metaAmount) : plan.metaAmount, percentageRate: body.percentageRate !== undefined ? parseFloat(body.percentageRate) : plan.percentageRate, maxCap: body.maxCap !== undefined ? parseFloat(body.maxCap) : plan.maxCap, status: 'ACTIVE', version: plan.version + 1, createdBy: session.userId } }); // 3. Clone rules associated with the original plan const originalRules = await tx.calculationRule.findMany({ where: { planId: id } }); if (originalRules.length > 0) { await tx.calculationRule.createMany({ data: originalRules.map((r: any) => ({ planId: clonedPlan.id, type: r.type, minAchievement: r.minAchievement, maxAchievement: r.maxAchievement, rate: r.rate, payoutAmount: r.payoutAmount })) }); } return clonedPlan; }); return NextResponse.json({ plan: newPlan, versioned: true }); } else { // In-place update for DRAFT / INACTIVE plans const updatedPlan = await prisma.compensationPlan.update({ where: { id }, data: { name: body.name, code: body.code, validityStart: body.validityStart ? new Date(body.validityStart) : undefined, validityEnd: body.validityEnd !== undefined ? (body.validityEnd ? new Date(body.validityEnd) : null) : undefined, type: body.type, formula: body.formula, metaAmount: body.metaAmount !== undefined ? (body.metaAmount ? parseFloat(body.metaAmount) : null) : undefined, percentageRate: body.percentageRate !== undefined ? (body.percentageRate ? parseFloat(body.percentageRate) : null) : undefined, maxCap: body.maxCap !== undefined ? (body.maxCap ? parseFloat(body.maxCap) : null) : undefined, status: body.status } }); return NextResponse.json({ plan: updatedPlan, versioned: false }); } }, ['ADMIN', 'DIRECTOR']);