jws-backend/src/controllers/05-payment-controller.ts
2024-11-14 13:10:08 +07:00

290 lines
8 KiB
TypeScript

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_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);
@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");
if (record.paymentStatus === "PaymentSuccess") return record;
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 =
body.paymentStatus === "PaymentSuccess" && record.paymentStatus !== "PaymentSuccess"
? await tx.runningNo.upsert({
where: {
key: `RECEIPT_${year}${month}`,
},
create: {
key: `RECEIPT_${year}${month}`,
value: 1,
},
update: { value: { increment: 1 } },
})
: null;
const quotation = record.invoice.quotation;
const payment = await tx.payment.update({
where: { id: paymentId, invoice: { quotationId: quotation.id } },
data: {
...body,
code: last ? `RE${year}${month}${last.value.toString().padStart(6, "0")}` : undefined,
},
});
const paymentSum = await tx.payment.aggregate({
_sum: { amount: true },
where: {
invoice: {
quotationId: quotation.id,
payment: { paymentStatus: "PaymentSuccess" },
},
},
});
await tx.quotation.update({
where: { id: quotation.id },
data: {
quotationStatus:
paymentSum._sum.amount || 0 >= quotation.finalPrice
? "PaymentSuccess"
: "PaymentInProcess",
requestData:
body.paymentStatus === "PaymentSuccess" &&
(paymentSum._sum.amount || 0) - payment.amount <= 0
? {
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 PaymentFileController 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));
}
}