import { Body, Controller, Delete, Get, Head, Path, Post, Put, Query, Request, Route, Tags, } from "tsoa"; import express from "express"; import { PaymentStatus } from "@prisma/client"; import prisma from "../db"; import { notFoundError } from "../utils/error"; import HttpError from "../interfaces/http-error"; import HttpStatus from "../interfaces/http-status"; import { deleteFile, fileLocation, getFile, getPresigned, listFile, setFile } from "../utils/minio"; @Tags("Payment") @Route("api/v1/quotation/{quotationId}/payment") export class QuotationPayment extends Controller { @Get() async getPayment(@Path() quotationId: string) { const record = await prisma.quotation.findFirst({ where: { id: quotationId }, include: { quotationPaymentData: { orderBy: { date: "asc" }, }, createdBy: true, updatedBy: true, }, }); return record; } @Post() async addPayment( @Path() quotationId: string, @Body() body: { amount: number; date: Date; remark: string; paymentStatus?: PaymentStatus }, ) { const record = await prisma.quotation.findUnique({ where: { id: quotationId }, }); if (!record) throw notFoundError("Quotation"); if (!body.paymentStatus && record.quotationStatus !== "PaymentPending") { // NOTE: The quotation must be in waiting for payment or waiting for payment confirmation (re-submit payment) throw new HttpError( HttpStatus.PRECONDITION_FAILED, "Cannot submit payment info of this quotation", "quotationStatusWrong", ); } return await prisma.quotation.update({ where: { id: quotationId }, include: { quotationPaymentData: true }, data: { quotationStatus: "PaymentInProcess", quotationPaymentData: { create: { paymentStatus: "PaymentWait", ...body, }, }, }, }); } @Put("{paymentId}") async updatePayment( @Path() quotationId: string, @Path() paymentId: string, @Body() body: { amount?: number; date?: Date; remark?: string; paymentStatus?: PaymentStatus }, ) { const record = await prisma.quotationPayment.findUnique({ where: { id: paymentId, quotationId }, }); if (!record) throw notFoundError("Quotation Payment"); return await prisma.quotationPayment.update({ where: { id: paymentId, quotationId }, data: body, }); } @Get("{paymentId}/attachment") async listPaymentFile(@Path() quotationId: string, @Path() paymentId: string) { return await listFile(fileLocation.quotation.payment(quotationId, paymentId)); } @Head("{paymentId}/attachment/{name}") async headPaymentFile( @Request() req: express.Request, @Path() quotationId: string, @Path() paymentId: string, @Path() name: string, ) { return req.res?.redirect( await getPresigned("head", fileLocation.quotation.payment(quotationId, paymentId, name)), ); } @Get("{paymentId}/attachment/{name}") async getPaymentFile( @Request() req: express.Request, @Path() quotationId: string, @Path() paymentId: string, @Path() name: string, ) { return req.res?.redirect( await getFile(fileLocation.quotation.payment(quotationId, paymentId, name)), ); } @Put("{paymentId}/attachment/{name}") async uploadPayment( @Path() quotationId: string, @Path() paymentId: string, @Path() name: string, ) { const record = await prisma.quotation.findUnique({ where: { id: quotationId }, }); if (!record) throw notFoundError("Quotation"); if (record.quotationStatus !== "PaymentPending") { // NOTE: The quotation must be in waiting for payment or waiting for payment confirmation (re-submit payment) throw new HttpError( HttpStatus.PRECONDITION_FAILED, "Cannot submit payment info of this quotation", "quotationStatusWrong", ); } return await setFile(fileLocation.quotation.payment(quotationId, paymentId, name)); } @Delete("{paymentId}/attachment/{name}") async deletePayment( @Path() quotationId: string, @Path() paymentId: string, @Path() name: string, ) { const record = await prisma.quotation.findUnique({ where: { id: quotationId }, }); if (!record) throw notFoundError("Quotation"); return await deleteFile(fileLocation.quotation.payment(quotationId, paymentId, name)); } @Post("confirm") async confirmPayment(@Path() quotationId: string, @Query() paymentId?: string) { const record = await prisma.quotation.findUnique({ include: { _count: { select: { quotationPaymentData: true, paySplit: true, }, }, worker: true, quotationPaymentData: true, productServiceList: { include: { worker: true, work: true, service: true, product: true, }, }, }, where: { id: quotationId }, }); if (!record) throw notFoundError("Quotation"); await prisma.$transaction(async (tx) => { await tx.quotation.update({ where: { id: quotationId }, data: { quotationStatus: record.payCondition === "Full" || record.payCondition === "BillFull" || record._count.paySplit === record._count.quotationPaymentData ? "PaymentSuccess" : undefined, quotationPaymentData: { update: paymentId ? { where: { id: paymentId }, data: { paymentStatus: "PaymentSuccess" }, } : undefined, }, requestData: record.quotationStatus === "PaymentPending" ? { create: record.worker.map((v) => ({ employeeId: v.employeeId, requestWork: { create: record.productServiceList.flatMap((item) => item.worker.findIndex((w) => w.employeeId === v.employeeId) !== -1 ? { productServiceId: item.id } : [], ), }, })), } : undefined, }, }); }); } }