Initial import of AbelBirdNest Stock
This commit is contained in:
169
src/app/api/v1/agents/route.ts
Normal file
169
src/app/api/v1/agents/route.ts
Normal file
@ -0,0 +1,169 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user