import { PrismaClientKnownRequestError } from "@prisma/client/runtime/library"; import { NextResponse } from "next/server"; import { AGENT_BALANCE_DIRECTIONS, AGENT_BALANCE_SOURCES, AGENT_BALANCE_TYPES, createAgentBalanceMutation } from "@/features/agents/lib/balance-mutations"; import { serializeAgent } from "@/features/agents/lib/serialize-agent"; import { agentInputSchema } from "@/features/agents/schemas/agent.schema"; import { createAuditTrailSafe } from "@/lib/audit-trail"; import { resolveMasterCode } from "@/lib/master-code"; import { prisma } from "@/lib/prisma"; import { requireApiAccess } from "@/lib/authorization"; export async function GET(request: Request) { const auth = requireApiAccess(request); if (!auth.ok) return auth.response; const data = await prisma.agent.findMany({ include: { profitShareScheme: true, bankAccounts: { include: { bank: true } } }, orderBy: [{ createdAt: "desc" }] }); return NextResponse.json({ data: data.map(serializeAgent) }); } export async function POST(request: Request) { const auth = requireApiAccess(request); if (!auth.ok) return auth.response; const parsed = agentInputSchema.safeParse(await request.json()); if (!parsed.success) { return NextResponse.json( { message: "Validasi gagal", errors: parsed.error.flatten().fieldErrors }, { status: 400 } ); } try { const profitShareSchemeId = BigInt(parsed.data.profit_share_scheme_id); const bankIds = parsed.data.bank_accounts.map((account) => BigInt(account.bank_id)); const [scheme, banks] = await Promise.all([ prisma.profitShareScheme.findFirst({ where: { id: profitShareSchemeId, status: "ACTIVE" } }), prisma.bank.findMany({ where: { id: { in: bankIds }, status: "ACTIVE" } }) ]); if (!scheme) { return NextResponse.json( { message: "Validasi gagal", errors: { profit_share_scheme_id: ["Skema bagi hasil harus dipilih dari master aktif"] } }, { status: 400 } ); } if (banks.length !== bankIds.length) { return NextResponse.json( { message: "Validasi gagal", errors: { bank_accounts: ["Semua rekening harus memilih bank aktif dari master"] } }, { status: 400 } ); } const resolvedCode = await resolveMasterCode({ role: auth.user.role, prefix: "AGT", requestedCode: parsed.data.code, countExisting: () => prisma.agent.count({ where: { code: { startsWith: "AGT" } } }), exists: async (code) => (await prisma.agent.count({ where: { code } })) > 0 }); if (!resolvedCode.ok) { return NextResponse.json( { message: "Validasi gagal", errors: { code: [resolvedCode.message] } }, { status: 400 } ); } const joinDate = new Date(parsed.data.join_date); const agent = await prisma.$transaction(async (tx) => { const created = await tx.agent.create({ data: { code: resolvedCode.code, name: parsed.data.name, identityType: parsed.data.identity_type, identityNumber: parsed.data.identity_number, mobilePhone: parsed.data.mobile_phone || null, email: parsed.data.email || null, address: parsed.data.address || null, notes: parsed.data.notes || null, joinDate, profitShareSchemeId, currentBalance: parsed.data.profit_share_balance, capitalBalance: parsed.data.capital_balance, bankAccounts: { create: parsed.data.bank_accounts.map((account) => ({ bankId: BigInt(account.bank_id), accountNumber: account.account_number })) } }, include: { profitShareScheme: true, bankAccounts: { include: { bank: true } } } }); if (parsed.data.profit_share_balance > 0) { await createAgentBalanceMutation(tx, { agentId: created.id, balanceType: AGENT_BALANCE_TYPES.PROFIT_SHARE, direction: AGENT_BALANCE_DIRECTIONS.IN, source: AGENT_BALANCE_SOURCES.OPENING_BALANCE, amount: parsed.data.profit_share_balance, balanceAfter: parsed.data.profit_share_balance, effectiveDate: joinDate, notes: "Saldo bagi hasil awal agent" }); } if (parsed.data.capital_balance > 0) { await createAgentBalanceMutation(tx, { agentId: created.id, balanceType: AGENT_BALANCE_TYPES.CAPITAL, direction: AGENT_BALANCE_DIRECTIONS.IN, source: AGENT_BALANCE_SOURCES.OPENING_BALANCE, amount: parsed.data.capital_balance, balanceAfter: parsed.data.capital_balance, effectiveDate: joinDate, notes: "Saldo modal awal agent" }); } return created; }); await createAuditTrailSafe({ userId: auth.user.id, action: "AGENT_CREATED", entityType: "AGENT", entityId: agent.id, method: request.method, pathname: new URL(request.url).pathname, statusCode: 201, summary: `Agent ${agent.code} dibuat` }); return NextResponse.json({ data: serializeAgent(agent) }, { status: 201 }); } catch (error) { if (error instanceof PrismaClientKnownRequestError && error.code === "P2002") { return NextResponse.json( { message: "Validasi gagal", errors: { identity_number: ["Kode agen atau identitas sudah dipakai"] } }, { status: 409 } ); } throw error; } }