jws-backend/src/controllers/05-payment-controller.ts
2024-12-27 09:05:53 +07:00

370 lines
11 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";
import flowAccount from "../services/flowaccount";
import HttpError from "../interfaces/http-error";
import HttpStatus from "../interfaces/http-status";
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 lastReceipt =
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: lastReceipt
? `RE${year}${month}${lastReceipt.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: await (async () => {
if (
body.paymentStatus === "PaymentSuccess" &&
(paymentSum._sum.amount || 0) - payment.amount <= 0
) {
const lastRequest = await tx.runningNo.upsert({
where: {
key: `REQUEST_${year}${month}`,
},
create: {
key: `REQUEST_${year}${month}`,
value: quotation.worker.length,
},
update: { value: { increment: quotation.worker.length } },
});
return {
create: quotation.worker.flatMap((v, i) => {
const productEmployee = quotation.productServiceList.flatMap((item) =>
item.worker.findIndex((w) => w.employeeId === v.employeeId) !== -1
? { productServiceId: item.id }
: [],
);
if (productEmployee.length <= 0) return [];
return {
code: `TR${year}${month}${(lastRequest.value - quotation.worker.length + i + 1).toString().padStart(6, "0")}`,
employeeId: v.employeeId,
requestWork: {
create: quotation.productServiceList.flatMap((item) =>
item.worker.findIndex((w) => w.employeeId === v.employeeId) !== -1
? { productServiceId: item.id }
: [],
),
},
};
}),
};
}
})(),
},
});
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));
}
}
@Route("api/v1/payment/{paymentId}/flow-account")
@Tags("Payment")
export class FlowAccountController extends Controller {
@Get()
async getDocument(@Path() paymentId: string) {
const payment = await prisma.payment.findFirst({
where: {
id: paymentId,
},
include: { invoice: true },
});
if (!payment) throw notFoundError("Payment");
if (payment.paymentStatus !== PaymentStatus.PaymentSuccess)
throw new HttpError(
HttpStatus.PRECONDITION_FAILED,
"Payment not success",
"paymentNotSuccess",
);
if (!payment.invoice.flowAccountRecordId) {
const result = await flowAccount.issueInvoice(payment.invoice.id);
const documentId = result?.body?.data?.documentId;
if (!documentId) {
throw new HttpError(
HttpStatus.INTERNAL_SERVER_ERROR,
"FlowAccount error",
"flowAccountError",
);
}
await prisma.invoice.update({
where: { id: payment.invoice.id },
data: { flowAccountRecordId: String(documentId) },
});
if (documentId) {
return flowAccount.getInvoiceDocument(documentId);
} else {
throw new HttpError(
HttpStatus.INTERNAL_SERVER_ERROR,
"Failed to issue invoice/receipt document.",
"InvoiceReceiptIssueFailed",
);
}
} else {
return flowAccount.getInvoiceDocument(payment.invoice.flowAccountRecordId);
}
}
}