import { Prisma } from "@prisma/client"; import { Body, Controller, Get, OperationId, Path, Post, Query, Request, Route, Security, Tags, } from "tsoa"; import prisma from "../db"; import { notFoundError } from "../utils/error"; import { RequestWithUser } from "../interfaces/user"; import { branchRelationPermInclude, createPermCheck, createPermCondition, } from "../services/permission"; type InvoicePayload = { quotationId: string; amount: number; installmentNo: number[]; }; const MANAGE_ROLES = ["system", "head_of_admin", "admin", "head_of_accountant", "accountant"]; function globalAllow(user: RequestWithUser["user"]) { const allowList = ["system", "head_of_admin", "head_of_accountant"]; return allowList.some((v) => user.roles?.includes(v)); } const permissionCondCompany = createPermCondition((_) => true); const permissionCheck = createPermCheck(globalAllow); @Route("/api/v1/invoice") @Tags("Invoice") export class InvoiceController extends Controller { @Get() @OperationId("getInvoiceList") @Security("keycloak") async getInvoiceList( @Request() req: RequestWithUser, @Query() page: number = 1, @Query() pageSize: number = 30, @Query() query: string = "", @Query() quotationId?: string, @Query() pay?: boolean, ) { const where: Prisma.InvoiceWhereInput = { OR: [ { code: { contains: query, mode: "insensitive" } }, { quotation: { workName: { contains: query } } }, { quotation: { customerBranch: { OR: [ { code: { contains: query, mode: "insensitive" } }, { customerName: { contains: query } }, { registerName: { contains: query } }, { registerNameEN: { contains: query } }, { firstName: { contains: query } }, { firstNameEN: { contains: query } }, { lastName: { contains: query } }, { lastNameEN: { contains: query } }, ], }, }, }, ], payment: pay === undefined ? undefined : pay ? { isNot: null } : null, quotationId, quotation: { registeredBranch: { OR: permissionCondCompany(req.user), }, }, }; const [result, total] = await prisma.$transaction([ prisma.invoice.findMany({ where, include: { installments: true, quotation: { include: { customerBranch: { include: { customer: true }, }, }, }, payment: true, createdBy: true, }, orderBy: { createdAt: "asc" }, }), prisma.invoice.count({ where }), ]); return { result, page, pageSize, total }; } @Get("{invoiceId}") @OperationId("getInvoice") @Security("keycloak") async getInvoice(@Path() invoiceId: string) { const record = await prisma.invoice.findFirst({ where: { id: invoiceId }, include: { installments: true, quotation: true, createdBy: true, }, orderBy: { createdAt: "asc" }, }); if (!record) throw notFoundError("Invoice"); return record; } @Post() @OperationId("createInvoice") @Security("keycloak", MANAGE_ROLES) async createInvoice(@Request() req: RequestWithUser, @Body() body: InvoicePayload) { const [quotation] = await prisma.$transaction([ prisma.quotation.findUnique({ where: { id: body.quotationId }, include: { registeredBranch: { include: branchRelationPermInclude(req.user) } }, }), ]); if (!quotation) throw notFoundError("Quotation"); await permissionCheck(req.user, quotation.registeredBranch); return await prisma.$transaction(async (tx) => { const current = new Date(); const year = `${current.getFullYear()}`.slice(-2).padStart(2, "0"); const month = `${current.getMonth() + 1}`.padStart(2, "0"); const last = await tx.runningNo.upsert({ where: { key: `INVOICE_${year}${month}`, }, create: { key: `INVOICE_${year}${month}`, value: 1, }, update: { value: { increment: 1 } }, }); const record = await tx.quotation.update({ include: { paySplit: { where: { no: { in: body.installmentNo } }, }, }, where: { id: body.quotationId }, data: { quotationStatus: "PaymentInProcess", }, }); return await tx.invoice.create({ data: { quotationId: body.quotationId, code: `IV${year}${month}${last.value.toString().padStart(6, "0")}`, amount: body.amount, installments: { connect: record.paySplit.map((v) => ({ id: v.id })), }, payment: { create: { paymentStatus: "PaymentWait", amount: body.amount, }, }, createdByUserId: req.user.sub, }, }); }); } }