import { Prisma } from "@prisma/client"; import { Body, Controller, Delete, Get, OperationId, Path, Post, Put, 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; // NOTE: For individual list that will be include in the quotation productServiceListId?: string[]; // NOTE: Will be pulled from quotation installmentNo?: number[]; }; const MANAGE_ROLES = ["system", "head_of_admin", "admin", "head_of_account", "account"]; function globalAllow(user: RequestWithUser["user"]) { const allowList = ["system", "head_of_admin", "head_of_account"]; 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() quotationId?: string, ) { const where: Prisma.InvoiceWhereInput = { quotationId, quotation: { registeredBranch: { OR: permissionCondCompany(req.user), }, }, }; const [result, total] = await prisma.$transaction([ prisma.invoice.findMany({ where, include: { productServiceList: { include: { worker: true, service: true, work: true, product: true, }, }, quotation: 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: { productServiceList: { include: { worker: true, service: true, work: true, product: 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, productServiceList] = await prisma.$transaction([ prisma.quotation.findUnique({ where: { id: body.quotationId }, include: { registeredBranch: { include: branchRelationPermInclude(req.user) } }, }), prisma.quotationProductServiceList.findMany({ where: { OR: [ { id: { in: body.productServiceListId }, invoiceId: null }, { installmentNo: { in: body.installmentNo }, invoiceId: null }, ], }, }), ]); if (!quotation) throw notFoundError("Quotation"); await permissionCheck(req.user, quotation.registeredBranch); return await prisma.$transaction(async (tx) => { await tx.quotation.update({ where: { id: body.quotationId }, data: { quotationStatus: "PaymentInProcess" }, }); return await tx.invoice.create({ data: { productServiceList: { connect: productServiceList.map((v) => ({ id: v.id })) }, quotationId: body.quotationId, amount: body.amount, payment: { create: { paymentStatus: "PaymentWait", amount: body.amount, }, }, createdByUserId: req.user.sub, }, }); }); } @Put("{invoiceId}") @OperationId("updateInvoice") @Security("keycloak", MANAGE_ROLES) async updateInvoice( @Request() req: RequestWithUser, @Body() body: InvoicePayload, @Path() invoiceId: string, ) { const [record, quotation, productServiceList] = await prisma.$transaction([ prisma.invoice.findUnique({ where: { id: invoiceId }, include: { productServiceList: { where: { id: { notIn: body.productServiceListId }, installmentNo: { notIn: body.installmentNo }, }, }, }, }), prisma.quotation.findUnique({ where: { id: body.quotationId }, include: { registeredBranch: { include: branchRelationPermInclude(req.user) } }, }), prisma.quotationProductServiceList.findMany({ where: { OR: [ { id: { in: body.productServiceListId }, invoiceId: null }, { installmentNo: { in: body.installmentNo }, invoiceId: null }, ], }, }), ]); if (!record) throw notFoundError("Invoice"); if (!quotation) throw notFoundError("Quotation"); await permissionCheck(req.user, quotation.registeredBranch); return await prisma.$transaction(async (tx) => { return await tx.invoice.update({ where: { id: invoiceId }, data: { productServiceList: { disconnect: record.productServiceList.map((v) => ({ id: v.id })), connect: productServiceList.map((v) => ({ id: v.id })), }, }, }); }); } @Delete("{invoiceId}") @OperationId("deleteInvoice") @Security("keycloak", MANAGE_ROLES) async deleteInvoice(@Request() req: RequestWithUser, @Path() invoiceId: string) { return await prisma.$transaction(async (tx) => { const record = await tx.invoice.delete({ where: { id: invoiceId }, include: { productServiceList: { include: { worker: true, service: true, work: true, product: true, }, }, quotation: { include: { registeredBranch: { include: branchRelationPermInclude(req.user) } }, }, createdBy: true, }, }); if (!record) throw notFoundError("Invoice"); await permissionCheck(req.user, record.quotation.registeredBranch); return record; }); } }