jws-backend/src/controllers/04-invoice-controller.ts

242 lines
6.5 KiB
TypeScript
Raw Normal View History

2024-10-25 13:58:29 +07:00
import { Prisma } from "@prisma/client";
import {
Body,
Controller,
Get,
OperationId,
Path,
Post,
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";
2024-12-20 13:15:30 +07:00
import { PaymentStatus } from "../generated/kysely/types";
2024-10-25 13:58:29 +07:00
type InvoicePayload = {
quotationId: string;
amount: number;
installmentNo: number[];
2024-10-25 13:58:29 +07:00
};
const MANAGE_ROLES = ["system", "head_of_admin", "admin", "head_of_accountant", "accountant"];
2024-10-25 13:58:29 +07:00
function globalAllow(user: RequestWithUser["user"]) {
const allowList = ["system", "head_of_admin", "head_of_accountant"];
2024-10-25 13:58:29 +07:00
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 {
2024-12-20 11:59:55 +07:00
@Get("stats")
@OperationId("getInvoiceStats")
@Security("keycloak")
async getInvoiceStats(
@Request() req: RequestWithUser,
@Query() quotationOnly: boolean = true,
@Query() quotationId?: string,
@Query() debitNoteId?: string,
@Query() debitNoteOnly?: boolean,
) {
2024-12-20 11:59:55 +07:00
const where = {
quotation: {
id: quotationId,
isDebitNote: debitNoteId || debitNoteOnly ? true : quotationOnly ? false : undefined,
2024-12-20 11:59:55 +07:00
registeredBranch: {
OR: permissionCondCompany(req.user),
},
},
} satisfies Prisma.InvoiceWhereInput;
2024-12-20 11:59:55 +07:00
const [pay, notPay] = await prisma.$transaction([
2024-12-20 13:15:30 +07:00
prisma.invoice.count({
where: {
...where,
2024-12-20 13:42:47 +07:00
payment: { paymentStatus: PaymentStatus.PaymentSuccess },
2024-12-20 13:15:30 +07:00
},
}),
prisma.invoice.count({
where: {
...where,
2024-12-20 13:42:47 +07:00
payment: { paymentStatus: { not: PaymentStatus.PaymentSuccess } },
2024-12-20 13:15:30 +07:00
},
}),
2024-12-20 11:59:55 +07:00
]);
2024-12-20 13:42:47 +07:00
return {
[PaymentStatus.PaymentSuccess]: pay,
[PaymentStatus.PaymentWait]: notPay,
};
2024-12-20 11:59:55 +07:00
}
2024-10-25 13:58:29 +07:00
@Get()
@OperationId("getInvoiceList")
@Security("keycloak")
async getInvoiceList(
@Request() req: RequestWithUser,
@Query() page: number = 1,
@Query() pageSize: number = 30,
@Query() query: string = "",
@Query() quotationOnly: boolean = true,
@Query() debitNoteOnly?: boolean,
2024-10-25 13:58:29 +07:00
@Query() quotationId?: string,
@Query() debitNoteId?: string,
2024-12-20 09:31:00 +07:00
@Query() pay?: boolean,
2024-10-25 13:58:29 +07:00
) {
const where: Prisma.InvoiceWhereInput = {
OR: [
{ code: { contains: query, mode: "insensitive" } },
{ quotation: { workName: { contains: query } } },
{
quotation: {
customerBranch: {
OR: [
{ code: { contains: query, mode: "insensitive" } },
{ customerName: { contains: query } },
{ registerName: { contains: query } },
{ registerNameEN: { contains: query } },
{ firstName: { contains: query } },
{ firstNameEN: { contains: query } },
{ lastName: { contains: query } },
{ lastNameEN: { contains: query } },
],
},
},
},
],
2025-01-20 13:27:44 +07:00
payment:
pay !== undefined
? {
paymentStatus: pay
? PaymentStatus.PaymentSuccess
: { not: PaymentStatus.PaymentSuccess },
}
: undefined,
2024-10-25 13:58:29 +07:00
quotation: {
id: quotationId || debitNoteId,
isDebitNote: debitNoteId || debitNoteOnly ? true : quotationOnly ? false : undefined,
2024-10-25 13:58:29 +07:00
registeredBranch: {
OR: permissionCondCompany(req.user),
},
},
};
const [result, total] = await prisma.$transaction([
prisma.invoice.findMany({
where,
include: {
installments: true,
2024-12-18 15:02:00 +07:00
quotation: {
include: {
customerBranch: {
include: { customer: true },
},
},
},
2024-12-18 15:32:31 +07:00
payment: true,
2024-10-25 13:58:29 +07:00
createdBy: true,
},
orderBy: { createdAt: "asc" },
2025-02-03 11:31:39 +07:00
take: pageSize,
skip: (page - 1) * pageSize,
2024-10-25 13:58:29 +07:00
}),
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: {
installments: true,
2024-10-25 13:58:29 +07:00
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] = await prisma.$transaction([
2024-10-25 13:58:29 +07:00
prisma.quotation.findUnique({
where: { id: body.quotationId },
include: { registeredBranch: { include: branchRelationPermInclude(req.user) } },
}),
]);
if (!quotation) throw notFoundError("Quotation");
await permissionCheck(req.user, quotation.registeredBranch);
2024-10-25 16:37:59 +07:00
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 = await tx.runningNo.upsert({
where: {
key: `INVOICE_${year}${month}`,
},
create: {
key: `INVOICE_${year}${month}`,
value: 1,
},
update: { value: { increment: 1 } },
});
const record = await tx.quotation.update({
include: {
paySplit: {
where: { no: { in: body.installmentNo } },
},
},
2024-10-25 16:37:59 +07:00
where: { id: body.quotationId },
2024-10-29 09:49:02 +07:00
data: {
quotationStatus: "PaymentInProcess",
},
2024-10-25 16:37:59 +07:00
});
return await tx.invoice.create({
data: {
quotationId: body.quotationId,
2024-11-07 10:38:04 +07:00
code: `IV${year}${month}${last.value.toString().padStart(6, "0")}`,
2024-10-25 16:37:59 +07:00
amount: body.amount,
installments: {
connect: record.paySplit.map((v) => ({ id: v.id })),
},
2024-10-25 16:37:59 +07:00
payment: {
create: {
paymentStatus: "PaymentWait",
amount: body.amount,
},
2024-10-25 13:58:29 +07:00
},
2024-10-25 16:37:59 +07:00
createdByUserId: req.user.sub,
2024-10-25 13:58:29 +07:00
},
2024-10-25 16:37:59 +07:00
});
2024-10-25 13:58:29 +07:00
});
}
}