import { Body, Controller, Delete, Get, Head, Path, Put, Query, Request, Route, Security, Tags, } from "tsoa"; import { PaymentStatus, Prisma } from "@prisma/client"; import prisma from "../db"; import { notFoundError } from "../utils/error"; import { deleteFile, fileLocation, getFile, getPresigned, listFile, setFile } from "../utils/minio"; import { RequestWithUser } from "../interfaces/user"; import { branchRelationPermInclude, createPermCheck, createPermCondition, } from "../services/permission"; 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); @Tags("Payment") @Route("api/v1/payment") export class QuotationPayment extends Controller { @Get() @Security("keycloak") async getPaymentList( @Request() req: RequestWithUser, @Query() page: number = 1, @Query() pageSize: number = 30, @Query() quotationId?: string, ) { const where: Prisma.PaymentWhereInput = { invoice: { quotationId, quotation: { registeredBranch: { OR: permissionCondCompany(req.user), }, }, }, }; const [result, total] = await prisma.$transaction([ prisma.payment.findMany({ where, include: { invoice: { include: { installments: true, quotation: true, createdBy: true, }, }, }, orderBy: { createdAt: "asc" }, }), prisma.payment.count({ where }), ]); return { result, page, pageSize, total }; } @Get("{paymentId}") @Security("keycloak") async getPayment(@Path() paymentId: string) { const record = await prisma.payment.findFirst({ where: { id: paymentId }, include: { invoice: { include: { installments: true, quotation: true, createdBy: true, }, }, }, }); return record; } @Put("{paymentId}") @Security("keycloak", MANAGE_ROLES) async updatePayment( @Path() paymentId: string, @Body() body: { amount?: number; date?: Date; paymentStatus?: PaymentStatus }, ) { const record = await prisma.payment.findUnique({ where: { id: paymentId }, include: { invoice: { include: { quotation: { include: { _count: { select: { paySplit: true }, }, worker: true, productServiceList: { include: { worker: true, work: true, service: true, product: true, }, }, }, }, }, }, }, }); if (!record) throw notFoundError("Payment"); return await prisma.$transaction(async (tx) => { const quotation = record.invoice.quotation; const payment = await tx.payment.update({ where: { id: paymentId, invoice: { quotationId: quotation.id } }, data: body, }); const paymentSum = await prisma.payment.aggregate({ _sum: { amount: true }, where: { invoice: { quotationId: quotation.id, payment: { paymentStatus: "PaymentSuccess" }, }, }, }); if (body.paymentStatus === "PaymentSuccess") { paymentSum._sum.amount = (paymentSum._sum.amount || 0) + payment.amount; } await tx.quotation.update({ where: { id: quotation.id }, data: { quotationStatus: paymentSum._sum.amount || 0 >= quotation.finalPrice ? "PaymentSuccess" : "PaymentInProcess", requestData: quotation.quotationStatus === "PaymentPending" ? { create: quotation.worker.map((v) => ({ employeeId: v.employeeId, requestWork: { create: quotation.productServiceList.flatMap((item) => item.worker.findIndex((w) => w.employeeId === v.employeeId) !== -1 ? { productServiceId: item.id } : [], ), }, })), } : undefined, }, }); return payment; }); } } @Route("api/v1/payment/{paymentId}/attachment") @Tags("Payment") export class PaymentController extends Controller { private async checkPermission(user: RequestWithUser["user"], id: string) { const data = await prisma.payment.findUnique({ include: { invoice: { include: { quotation: { include: { registeredBranch: { include: branchRelationPermInclude(user), }, }, }, }, }, }, where: { id }, }); if (!data) throw notFoundError("Payment"); await permissionCheck(user, data.invoice.quotation.registeredBranch); return { paymentId: id, quotationId: data.invoice.quotationId }; } @Get() @Security("keycloak") async listAttachment(@Request() req: RequestWithUser, @Path() paymentId: string) { const { quotationId } = await this.checkPermission(req.user, paymentId); return await listFile(fileLocation.quotation.payment(quotationId, paymentId)); } @Head("{name}") async headAttachment( @Request() req: RequestWithUser, @Path() paymentId: string, @Path() name: string, ) { const data = await prisma.payment.findUnique({ where: { id: paymentId }, include: { invoice: true }, }); if (!data) throw notFoundError("Payment"); return req.res?.redirect( await getPresigned( "head", fileLocation.quotation.payment(data.invoice.quotationId, paymentId, name), ), ); } @Get("{name}") async getAttachment( @Request() req: RequestWithUser, @Path() paymentId: string, @Path() name: string, ) { const data = await prisma.payment.findUnique({ where: { id: paymentId }, include: { invoice: true }, }); if (!data) throw notFoundError("Payment"); return req.res?.redirect( await getFile(fileLocation.quotation.payment(data.invoice.quotationId, paymentId, name)), ); } @Put("{name}") @Security("keycloak", MANAGE_ROLES) async putAttachment( @Request() req: RequestWithUser, @Path() paymentId: string, @Path() name: string, ) { const { quotationId } = await this.checkPermission(req.user, paymentId); return await setFile(fileLocation.quotation.payment(quotationId, paymentId, name)); } @Delete("{name}") @Security("keycloak", MANAGE_ROLES) async deleteAttachment( @Request() req: RequestWithUser, @Path() paymentId: string, @Path() name: string, ) { const { quotationId } = await this.checkPermission(req.user, paymentId); return await deleteFile(fileLocation.quotation.payment(quotationId, paymentId, name)); } }