From 7e937333dc96241cb6249a99c009db1c8fce3323 Mon Sep 17 00:00:00 2001 From: Kanjana Date: Wed, 9 Apr 2025 09:51:52 +0700 Subject: [PATCH 001/167] add payment attachment --- src/controllers/09-line-controller.ts | 67 +++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/src/controllers/09-line-controller.ts b/src/controllers/09-line-controller.ts index cf8d7f3..93996c7 100644 --- a/src/controllers/09-line-controller.ts +++ b/src/controllers/09-line-controller.ts @@ -1368,3 +1368,70 @@ export class LineQuotationFileController extends Controller { return await deleteFile(fileLocation.quotation.attachment(quotationId, name)); } } + +@Route("api/v1/line/payment/{paymentId}/attachment") +@Tags("Line") +export class PaymentFileLineController extends Controller { + private async checkPermission(_user: RequestWithUser["user"], id: string) { + const data = await prisma.payment.findUnique({ + include: { + invoice: { + include: { + quotation: true, + }, + }, + }, + where: { id }, + }); + console.log("data", data); + + if (!data) throw notFoundError("Payment"); + return { paymentId: id, quotationId: data.invoice.quotationId }; + } + + @Get() + @Security("line") + async listAttachment(@Request() req: RequestWithUser, @Path() paymentId: string) { + console.log("req", req); + + const { quotationId } = await this.checkPermission(req.user, paymentId); + console.log("quotationId", quotationId); + + 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)), + ); + } +} From 743fde5493abc62bbb53b99a896964a05fc4aff6 Mon Sep 17 00:00:00 2001 From: Kanjana Date: Wed, 9 Apr 2025 11:54:52 +0700 Subject: [PATCH 002/167] add mode: "insensitive" --- src/controllers/00-notification-controller.ts | 4 +- src/controllers/01-branch-controller.ts | 24 ++++----- src/controllers/01-branch-user-controller.ts | 16 +++--- src/controllers/02-user-controller.ts | 12 ++--- .../03-customer-branch-controller.ts | 26 ++++----- src/controllers/03-customer-controller.ts | 16 +++--- src/controllers/03-employee-controller.ts | 20 +++---- .../04-flow-template-controller.ts | 4 +- src/controllers/04-institution-controller.ts | 2 +- src/controllers/04-invoice-controller.ts | 16 +++--- src/controllers/04-product-controller.ts | 4 +- .../04-product-group-controller.ts | 4 +- src/controllers/04-properties-controller.ts | 5 +- src/controllers/04-service-controller.ts | 4 +- src/controllers/05-quotation-controller.ts | 12 ++--- src/controllers/06-request-list-controller.ts | 24 ++++----- src/controllers/07-task-controller.ts | 12 ++--- src/controllers/08-credit-note-controller.ts | 22 ++++---- src/controllers/09-debit-note-controller.ts | 12 ++--- src/controllers/09-line-controller.ts | 53 +++++++++---------- 20 files changed, 145 insertions(+), 147 deletions(-) diff --git a/src/controllers/00-notification-controller.ts b/src/controllers/00-notification-controller.ts index 2bc4bf0..da40b93 100644 --- a/src/controllers/00-notification-controller.ts +++ b/src/controllers/00-notification-controller.ts @@ -36,8 +36,8 @@ export class NotificationController extends Controller { AND: [ { OR: queryOrNot<(typeof where)[]>(query, [ - { title: { contains: query } }, - { detail: { contains: query } }, + { title: { contains: query, mode: "insensitive" } }, + { detail: { contains: query, mode: "insensitive" } }, ]), }, { diff --git a/src/controllers/01-branch-controller.ts b/src/controllers/01-branch-controller.ts index acb204f..f0cc512 100644 --- a/src/controllers/01-branch-controller.ts +++ b/src/controllers/01-branch-controller.ts @@ -265,20 +265,20 @@ export class BranchController extends Controller { }, OR: queryOrNot(query, [ { code: { contains: query, mode: "insensitive" } }, - { nameEN: { contains: query } }, - { name: { contains: query } }, - { email: { contains: query } }, - { telephoneNo: { contains: query } }, + { nameEN: { contains: query, mode: "insensitive" } }, + { name: { contains: query, mode: "insensitive" } }, + { email: { contains: query, mode: "insensitive" } }, + { telephoneNo: { contains: query, mode: "insensitive" } }, ...whereAddressQuery(query), { branch: { some: { OR: [ { code: { contains: query, mode: "insensitive" } }, - { nameEN: { contains: query } }, - { name: { contains: query } }, - { email: { contains: query } }, - { telephoneNo: { contains: query } }, + { nameEN: { contains: query, mode: "insensitive" } }, + { name: { contains: query, mode: "insensitive" } }, + { email: { contains: query, mode: "insensitive" } }, + { telephoneNo: { contains: query, mode: "insensitive" } }, ...whereAddressQuery(query), ], }, @@ -309,10 +309,10 @@ export class BranchController extends Controller { where: { AND: { OR: permissionCond(req.user) }, OR: [ - { nameEN: { contains: query } }, - { name: { contains: query } }, - { email: { contains: query } }, - { telephoneNo: { contains: query } }, + { nameEN: { contains: query, mode: "insensitive" } }, + { name: { contains: query, mode: "insensitive" } }, + { email: { contains: query, mode: "insensitive" } }, + { telephoneNo: { contains: query, mode: "insensitive" } }, ...whereAddressQuery(query), ], }, diff --git a/src/controllers/01-branch-user-controller.ts b/src/controllers/01-branch-user-controller.ts index 2b0ec23..99d77a4 100644 --- a/src/controllers/01-branch-user-controller.ts +++ b/src/controllers/01-branch-user-controller.ts @@ -104,8 +104,8 @@ export class UserBranchController extends Controller { userId, }, OR: queryOrNot(query, [ - { branch: { name: { contains: query } } }, - { branch: { nameEN: { contains: query } } }, + { branch: { name: { contains: query, mode: "insensitive" } } }, + { branch: { nameEN: { contains: query, mode: "insensitive" } } }, ]), } satisfies Prisma.BranchUserWhereInput; @@ -157,12 +157,12 @@ export class BranchUserController extends Controller { branchId, }, OR: [ - { user: { firstName: { contains: query } } }, - { user: { firstNameEN: { contains: query } } }, - { user: { lastName: { contains: query } } }, - { user: { lastNameEN: { contains: query } } }, - { user: { email: { contains: query } } }, - { user: { telephoneNo: { contains: query } } }, + { user: { firstName: { contains: query, mode: "insensitive" } } }, + { user: { firstNameEN: { contains: query, mode: "insensitive" } } }, + { user: { lastName: { contains: query, mode: "insensitive" } } }, + { user: { lastNameEN: { contains: query, mode: "insensitive" } } }, + { user: { email: { contains: query, mode: "insensitive" } } }, + { user: { telephoneNo: { contains: query, mode: "insensitive" } } }, ], } satisfies Prisma.BranchUserWhereInput; diff --git a/src/controllers/02-user-controller.ts b/src/controllers/02-user-controller.ts index f1dca32..7ba34e6 100644 --- a/src/controllers/02-user-controller.ts +++ b/src/controllers/02-user-controller.ts @@ -324,12 +324,12 @@ export class UserController extends Controller { const where = { OR: queryOrNot(query, [ { code: { contains: query, mode: "insensitive" } }, - { firstName: { contains: query } }, - { firstNameEN: { contains: query } }, - { lastName: { contains: query } }, - { lastNameEN: { contains: query } }, - { email: { contains: query } }, - { telephoneNo: { contains: query } }, + { firstName: { contains: query, mode: "insensitive" } }, + { firstNameEN: { contains: query, mode: "insensitive" } }, + { lastName: { contains: query, mode: "insensitive" } }, + { lastNameEN: { contains: query, mode: "insensitive" } }, + { email: { contains: query, mode: "insensitive" } }, + { telephoneNo: { contains: query, mode: "insensitive" } }, ...whereAddressQuery(query), ]), AND: { diff --git a/src/controllers/03-customer-branch-controller.ts b/src/controllers/03-customer-branch-controller.ts index 772c4df..7818bef 100644 --- a/src/controllers/03-customer-branch-controller.ts +++ b/src/controllers/03-customer-branch-controller.ts @@ -198,15 +198,15 @@ export class CustomerBranchController extends Controller { ) { const where = { OR: queryOrNot(query, [ - { customerName: { contains: query } }, - { registerName: { contains: query } }, - { registerNameEN: { contains: query } }, - { email: { contains: query } }, - { code: { contains: query } }, - { firstName: { contains: query } }, - { firstNameEN: { contains: query } }, - { lastName: { contains: query } }, - { lastNameEN: { contains: query } }, + { customerName: { contains: query, mode: "insensitive" } }, + { registerName: { contains: query, mode: "insensitive" } }, + { registerNameEN: { contains: query, mode: "insensitive" } }, + { email: { contains: query, mode: "insensitive" } }, + { code: { contains: query, mode: "insensitive" } }, + { firstName: { contains: query, mode: "insensitive" } }, + { firstNameEN: { contains: query, mode: "insensitive" } }, + { lastName: { contains: query, mode: "insensitive" } }, + { lastNameEN: { contains: query, mode: "insensitive" } }, ...whereAddressQuery(query), ]), AND: { @@ -288,10 +288,10 @@ export class CustomerBranchController extends Controller { ) { const where = { OR: queryOrNot(query, [ - { firstName: { contains: query } }, - { firstNameEN: { contains: query } }, - { lastName: { contains: query } }, - { lastNameEN: { contains: query } }, + { firstName: { contains: query, mode: "insensitive" } }, + { firstNameEN: { contains: query, mode: "insensitive" } }, + { lastName: { contains: query, mode: "insensitive" } }, + { lastNameEN: { contains: query, mode: "insensitive" } }, ...whereAddressQuery(query), ]), AND: { diff --git a/src/controllers/03-customer-controller.ts b/src/controllers/03-customer-controller.ts index bef51e3..8cac3d9 100644 --- a/src/controllers/03-customer-controller.ts +++ b/src/controllers/03-customer-controller.ts @@ -168,14 +168,14 @@ export class CustomerController extends Controller { ) { const where = { OR: queryOrNot(query, [ - { branch: { some: { namePrefix: { contains: query } } } }, - { branch: { some: { customerName: { contains: query } } } }, - { branch: { some: { registerName: { contains: query } } } }, - { branch: { some: { registerNameEN: { contains: query } } } }, - { branch: { some: { firstName: { contains: query } } } }, - { branch: { some: { firstNameEN: { contains: query } } } }, - { branch: { some: { lastName: { contains: query } } } }, - { branch: { some: { lastNameEN: { contains: query } } } }, + { branch: { some: { namePrefix: { contains: query, mode: "insensitive" } } } }, + { branch: { some: { customerName: { contains: query, mode: "insensitive" } } } }, + { branch: { some: { registerName: { contains: query, mode: "insensitive" } } } }, + { branch: { some: { registerNameEN: { contains: query, mode: "insensitive" } } } }, + { branch: { some: { firstName: { contains: query, mode: "insensitive" } } } }, + { branch: { some: { firstNameEN: { contains: query, mode: "insensitive" } } } }, + { branch: { some: { lastName: { contains: query, mode: "insensitive" } } } }, + { branch: { some: { lastNameEN: { contains: query, mode: "insensitive" } } } }, ]), AND: { customerType, diff --git a/src/controllers/03-employee-controller.ts b/src/controllers/03-employee-controller.ts index eb08db7..44629c1 100644 --- a/src/controllers/03-employee-controller.ts +++ b/src/controllers/03-employee-controller.ts @@ -163,13 +163,13 @@ export class EmployeeController extends Controller { OR: queryOrNot(query, [ { employeePassport: { - some: { number: { contains: query } }, + some: { number: { contains: query, mode: "insensitive" } }, }, }, - { firstName: { contains: query } }, - { firstNameEN: { contains: query } }, - { lastName: { contains: query } }, - { lastNameEN: { contains: query } }, + { firstName: { contains: query, mode: "insensitive" } }, + { firstNameEN: { contains: query, mode: "insensitive" } }, + { lastName: { contains: query, mode: "insensitive" } }, + { lastNameEN: { contains: query, mode: "insensitive" } }, ...whereAddressQuery(query), ]), AND: { @@ -252,13 +252,13 @@ export class EmployeeController extends Controller { ...(queryOrNot(query, [ { employeePassport: { - some: { number: { contains: query } }, + some: { number: { contains: query, mode: "insensitive" } }, }, }, - { firstName: { contains: query } }, - { firstNameEN: { contains: query } }, - { lastName: { contains: query } }, - { lastNameEN: { contains: query } }, + { firstName: { contains: query, mode: "insensitive" } }, + { firstNameEN: { contains: query, mode: "insensitive" } }, + { lastName: { contains: query, mode: "insensitive" } }, + { lastNameEN: { contains: query, mode: "insensitive" } }, ...whereAddressQuery(query), ]) ?? []), ...(queryOrNot(!!body, [ diff --git a/src/controllers/04-flow-template-controller.ts b/src/controllers/04-flow-template-controller.ts index 54510d5..0f80d61 100644 --- a/src/controllers/04-flow-template-controller.ts +++ b/src/controllers/04-flow-template-controller.ts @@ -61,10 +61,10 @@ export class FlowTemplateController extends Controller { ) { const where = { OR: queryOrNot(query, [ - { name: { contains: query } }, + { name: { contains: query, mode: "insensitive" } }, { step: { - some: { name: { contains: query } }, + some: { name: { contains: query, mode: "insensitive" } }, }, }, ]), diff --git a/src/controllers/04-institution-controller.ts b/src/controllers/04-institution-controller.ts index 5e21445..76c0fc4 100644 --- a/src/controllers/04-institution-controller.ts +++ b/src/controllers/04-institution-controller.ts @@ -131,7 +131,7 @@ export class InstitutionController extends Controller { ...filterStatus(activeOnly ? Status.ACTIVE : status), group: body?.group ? { in: body.group } : group, OR: queryOrNot(query, [ - { name: { contains: query } }, + { name: { contains: query, mode: "insensitive" } }, { code: { contains: query, mode: "insensitive" } }, ]), } satisfies Prisma.InstitutionWhereInput; diff --git a/src/controllers/04-invoice-controller.ts b/src/controllers/04-invoice-controller.ts index 20dd91a..64efee2 100644 --- a/src/controllers/04-invoice-controller.ts +++ b/src/controllers/04-invoice-controller.ts @@ -99,19 +99,19 @@ export class InvoiceController extends Controller { const where: Prisma.InvoiceWhereInput = { OR: [ { code: { contains: query, mode: "insensitive" } }, - { quotation: { workName: { contains: query } } }, + { quotation: { workName: { contains: query, mode: "insensitive" } } }, { 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 } }, + { customerName: { contains: query, mode: "insensitive" } }, + { registerName: { contains: query, mode: "insensitive" } }, + { registerNameEN: { contains: query, mode: "insensitive" } }, + { firstName: { contains: query, mode: "insensitive" } }, + { firstNameEN: { contains: query, mode: "insensitive" } }, + { lastName: { contains: query, mode: "insensitive" } }, + { lastNameEN: { contains: query, mode: "insensitive" } }, ], }, }, diff --git a/src/controllers/04-product-controller.ts b/src/controllers/04-product-controller.ts index 3646479..8cb8c3e 100644 --- a/src/controllers/04-product-controller.ts +++ b/src/controllers/04-product-controller.ts @@ -154,8 +154,8 @@ export class ProductController extends Controller { const where = { OR: queryOrNot(query, [ - { name: { contains: query } }, - { detail: { contains: query } }, + { name: { contains: query, mode: "insensitive" } }, + { detail: { contains: query, mode: "insensitive" } }, { code: { contains: query, mode: "insensitive" } }, ]), AND: { diff --git a/src/controllers/04-product-group-controller.ts b/src/controllers/04-product-group-controller.ts index 2fdcb50..ee27265 100644 --- a/src/controllers/04-product-group-controller.ts +++ b/src/controllers/04-product-group-controller.ts @@ -93,8 +93,8 @@ export class ProductGroup extends Controller { ) { const where = { OR: queryOrNot(query, [ - { name: { contains: query } }, - { detail: { contains: query } }, + { name: { contains: query, mode: "insensitive" } }, + { detail: { contains: query, mode: "insensitive" } }, { code: { contains: query, mode: "insensitive" } }, ]), AND: [ diff --git a/src/controllers/04-properties-controller.ts b/src/controllers/04-properties-controller.ts index 46cea13..35f94a8 100644 --- a/src/controllers/04-properties-controller.ts +++ b/src/controllers/04-properties-controller.ts @@ -51,7 +51,10 @@ export class PropertiesController extends Controller { @Query() activeOnly?: boolean, ) { const where = { - OR: queryOrNot(query, [{ name: { contains: query } }, { nameEN: { contains: query } }]), + OR: queryOrNot(query, [ + { name: { contains: query, mode: "insensitive" } }, + { nameEN: { contains: query, mode: "insensitive" } }, + ]), AND: { ...filterStatus(activeOnly ? Status.ACTIVE : status), registeredBranch: { diff --git a/src/controllers/04-service-controller.ts b/src/controllers/04-service-controller.ts index a60c555..c193fbd 100644 --- a/src/controllers/04-service-controller.ts +++ b/src/controllers/04-service-controller.ts @@ -179,8 +179,8 @@ export class ServiceController extends Controller { const where = { OR: queryOrNot(query, [ - { name: { contains: query } }, - { detail: { contains: query } }, + { name: { contains: query, mode: "insensitive" } }, + { detail: { contains: query, mode: "insensitive" } }, { code: { contains: query, mode: "insensitive" } }, ]), AND: { diff --git a/src/controllers/05-quotation-controller.ts b/src/controllers/05-quotation-controller.ts index 0369627..fa3d6b3 100644 --- a/src/controllers/05-quotation-controller.ts +++ b/src/controllers/05-quotation-controller.ts @@ -210,16 +210,16 @@ export class QuotationController extends Controller { const where = { OR: queryOrNot(query, [ { code: { contains: query, mode: "insensitive" } }, - { workName: { contains: query } }, + { workName: { contains: query, mode: "insensitive" } }, { customerBranch: { OR: [ { code: { contains: query, mode: "insensitive" } }, - { customerName: { contains: query } }, - { firstName: { contains: query } }, - { firstNameEN: { contains: query } }, - { lastName: { contains: query } }, - { lastNameEN: { contains: query } }, + { customerName: { contains: query, mode: "insensitive" } }, + { firstName: { contains: query, mode: "insensitive" } }, + { firstNameEN: { contains: query, mode: "insensitive" } }, + { lastName: { contains: query, mode: "insensitive" } }, + { lastNameEN: { contains: query, mode: "insensitive" } }, ], }, }, diff --git a/src/controllers/06-request-list-controller.ts b/src/controllers/06-request-list-controller.ts index 4484ce3..464b5fc 100644 --- a/src/controllers/06-request-list-controller.ts +++ b/src/controllers/06-request-list-controller.ts @@ -91,13 +91,13 @@ export class RequestDataController extends Controller { 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 } }, + { customerName: { contains: query, mode: "insensitive" } }, + { registerName: { contains: query, mode: "insensitive" } }, + { registerNameEN: { contains: query, mode: "insensitive" } }, + { firstName: { contains: query, mode: "insensitive" } }, + { firstNameEN: { contains: query, mode: "insensitive" } }, + { lastName: { contains: query, mode: "insensitive" } }, + { lastNameEN: { contains: query, mode: "insensitive" } }, ], }, }, @@ -105,14 +105,14 @@ export class RequestDataController extends Controller { OR: [ { employeePassport: { - some: { number: { contains: query } }, + some: { number: { contains: query, mode: "insensitive" } }, }, }, { code: { contains: query, mode: "insensitive" } }, - { firstName: { contains: query } }, - { firstNameEN: { contains: query } }, - { lastName: { contains: query } }, - { lastNameEN: { contains: query } }, + { firstName: { contains: query, mode: "insensitive" } }, + { firstNameEN: { contains: query, mode: "insensitive" } }, + { lastName: { contains: query, mode: "insensitive" } }, + { lastNameEN: { contains: query, mode: "insensitive" } }, ], }, }, diff --git a/src/controllers/07-task-controller.ts b/src/controllers/07-task-controller.ts index 2da73b2..03009b3 100644 --- a/src/controllers/07-task-controller.ts +++ b/src/controllers/07-task-controller.ts @@ -121,9 +121,9 @@ export class TaskController extends Controller { code: body?.code ? { in: body.code } : undefined, OR: queryOrNot(query, [ { code: { contains: query, mode: "insensitive" } }, - { taskName: { contains: query } }, - { contactName: { contains: query } }, - { contactTel: { contains: query } }, + { taskName: { contains: query, mode: "insensitive" } }, + { contactName: { contains: query, mode: "insensitive" } }, + { contactTel: { contains: query, mode: "insensitive" } }, ]), } satisfies Prisma.TaskOrderWhereInput; @@ -1021,9 +1021,9 @@ export class UserTaskController extends Controller { : undefined, OR: queryOrNot(query, [ { code: { contains: query, mode: "insensitive" } }, - { taskName: { contains: query } }, - { contactName: { contains: query } }, - { contactTel: { contains: query } }, + { taskName: { contains: query, mode: "insensitive" } }, + { contactName: { contains: query, mode: "insensitive" } }, + { contactTel: { contains: query, mode: "insensitive" } }, ]), } satisfies Prisma.TaskOrderWhereInput; diff --git a/src/controllers/08-credit-note-controller.ts b/src/controllers/08-credit-note-controller.ts index e5d2759..5fda3eb 100644 --- a/src/controllers/08-credit-note-controller.ts +++ b/src/controllers/08-credit-note-controller.ts @@ -153,17 +153,17 @@ export class CreditNoteController extends Controller { request: { OR: queryOrNot(query, [ { quotation: { code: { contains: query, mode: "insensitive" } } }, - { quotation: { workName: { contains: query } } }, + { quotation: { workName: { contains: query, mode: "insensitive" } } }, { quotation: { customerBranch: { OR: [ { code: { contains: query, mode: "insensitive" } }, - { customerName: { contains: query } }, - { firstName: { contains: query } }, - { firstNameEN: { contains: query } }, - { lastName: { contains: query } }, - { lastNameEN: { contains: query } }, + { customerName: { contains: query, mode: "insensitive" } }, + { firstName: { contains: query, mode: "insensitive" } }, + { firstNameEN: { contains: query, mode: "insensitive" } }, + { lastName: { contains: query, mode: "insensitive" } }, + { lastNameEN: { contains: query, mode: "insensitive" } }, ], }, }, @@ -171,14 +171,14 @@ export class CreditNoteController extends Controller { OR: [ { employeePassport: { - some: { number: { contains: query } }, + some: { number: { contains: query, mode: "insensitive" } }, }, }, { code: { contains: query, mode: "insensitive" } }, - { firstName: { contains: query } }, - { firstNameEN: { contains: query } }, - { lastName: { contains: query } }, - { lastNameEN: { contains: query } }, + { firstName: { contains: query, mode: "insensitive" } }, + { firstNameEN: { contains: query, mode: "insensitive" } }, + { lastName: { contains: query, mode: "insensitive" } }, + { lastNameEN: { contains: query, mode: "insensitive" } }, ], }, }, diff --git a/src/controllers/09-debit-note-controller.ts b/src/controllers/09-debit-note-controller.ts index bca4057..a499f88 100644 --- a/src/controllers/09-debit-note-controller.ts +++ b/src/controllers/09-debit-note-controller.ts @@ -200,16 +200,16 @@ export class DebitNoteController extends Controller { const where = { OR: queryOrNot(query, [ { code: { contains: query, mode: "insensitive" } }, - { workName: { contains: query } }, + { workName: { contains: query, mode: "insensitive" } }, { customerBranch: { OR: [ { code: { contains: query, mode: "insensitive" } }, - { customerName: { contains: query } }, - { firstName: { contains: query } }, - { firstNameEN: { contains: query } }, - { lastName: { contains: query } }, - { lastNameEN: { contains: query } }, + { customerName: { contains: query, mode: "insensitive" } }, + { firstName: { contains: query, mode: "insensitive" } }, + { firstNameEN: { contains: query, mode: "insensitive" } }, + { lastName: { contains: query, mode: "insensitive" } }, + { lastNameEN: { contains: query, mode: "insensitive" } }, ], }, }, diff --git a/src/controllers/09-line-controller.ts b/src/controllers/09-line-controller.ts index 93996c7..0a6ea3c 100644 --- a/src/controllers/09-line-controller.ts +++ b/src/controllers/09-line-controller.ts @@ -58,13 +58,13 @@ export class LineController extends Controller { ...(queryOrNot(query, [ { employeePassport: { - some: { number: { contains: query } }, + some: { number: { contains: query, mode: "insensitive" } }, }, }, - { firstName: { contains: query } }, - { firstNameEN: { contains: query } }, - { lastName: { contains: query } }, - { lastNameEN: { contains: query } }, + { firstName: { contains: query, mode: "insensitive" } }, + { firstNameEN: { contains: query, mode: "insensitive" } }, + { lastName: { contains: query, mode: "insensitive" } }, + { lastNameEN: { contains: query, mode: "insensitive" } }, ...whereAddressQuery(query), ]) ?? []), ] @@ -178,19 +178,19 @@ export class LineController extends Controller { OR: queryOrNot(query, [ { code: { contains: query, mode: "insensitive" } }, { quotation: { code: { contains: query, mode: "insensitive" } } }, - { quotation: { workName: { contains: query } } }, + { quotation: { workName: { contains: query, mode: "insensitive" } } }, { 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 } }, + { customerName: { contains: query, mode: "insensitive" } }, + { registerName: { contains: query, mode: "insensitive" } }, + { registerNameEN: { contains: query, mode: "insensitive" } }, + { firstName: { contains: query, mode: "insensitive" } }, + { firstNameEN: { contains: query, mode: "insensitive" } }, + { lastName: { contains: query, mode: "insensitive" } }, + { lastNameEN: { contains: query, mode: "insensitive" } }, ], }, }, @@ -198,14 +198,14 @@ export class LineController extends Controller { OR: [ { employeePassport: { - some: { number: { contains: query } }, + some: { number: { contains: query, mode: "insensitive" } }, }, }, { code: { contains: query, mode: "insensitive" } }, - { firstName: { contains: query } }, - { firstNameEN: { contains: query } }, - { lastName: { contains: query } }, - { lastNameEN: { contains: query } }, + { firstName: { contains: query, mode: "insensitive" } }, + { firstNameEN: { contains: query, mode: "insensitive" } }, + { lastName: { contains: query, mode: "insensitive" } }, + { lastNameEN: { contains: query, mode: "insensitive" } }, ], }, }, @@ -611,16 +611,16 @@ export class LineController extends Controller { ? [ ...(queryOrNot(query, [ { code: { contains: query, mode: "insensitive" } }, - { workName: { contains: query } }, + { workName: { contains: query, mode: "insensitive" } }, { customerBranch: { OR: [ { code: { contains: query, mode: "insensitive" } }, - { customerName: { contains: query } }, - { firstName: { contains: query } }, - { firstNameEN: { contains: query } }, - { lastName: { contains: query } }, - { lastNameEN: { contains: query } }, + { customerName: { contains: query, mode: "insensitive" } }, + { firstName: { contains: query, mode: "insensitive" } }, + { firstNameEN: { contains: query, mode: "insensitive" } }, + { lastName: { contains: query, mode: "insensitive" } }, + { lastNameEN: { contains: query, mode: "insensitive" } }, ], }, }, @@ -1383,7 +1383,6 @@ export class PaymentFileLineController extends Controller { }, where: { id }, }); - console.log("data", data); if (!data) throw notFoundError("Payment"); return { paymentId: id, quotationId: data.invoice.quotationId }; @@ -1392,11 +1391,7 @@ export class PaymentFileLineController extends Controller { @Get() @Security("line") async listAttachment(@Request() req: RequestWithUser, @Path() paymentId: string) { - console.log("req", req); - const { quotationId } = await this.checkPermission(req.user, paymentId); - console.log("quotationId", quotationId); - return await listFile(fileLocation.quotation.payment(quotationId, paymentId)); } From afe54b1a4e7658cce4bca61bd2149df2963ace60 Mon Sep 17 00:00:00 2001 From: Kanjana Date: Wed, 9 Apr 2025 14:05:05 +0700 Subject: [PATCH 003/167] add insensitive search --- src/controllers/00-employment-office-controller.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/controllers/00-employment-office-controller.ts b/src/controllers/00-employment-office-controller.ts index e7efc24..95adc5a 100644 --- a/src/controllers/00-employment-office-controller.ts +++ b/src/controllers/00-employment-office-controller.ts @@ -2,6 +2,7 @@ import { Body, Controller, Get, Path, Post, Query, Route, Tags } from "tsoa"; import prisma from "../db"; import { queryOrNot } from "../utils/relation"; import { notFoundError } from "../utils/error"; +import { Prisma } from "@prisma/client"; @Route("/api/v1/employment-office") @Tags("Employment Office") @@ -40,11 +41,14 @@ export class EmploymentOfficeController extends Controller { ], [], ), - ...queryOrNot( + ...(queryOrNot( query, - [{ name: { contains: query } }, { nameEN: { contains: query } }], + [ + { name: { contains: query, mode: "insensitive" } }, + { nameEN: { contains: query, mode: "insensitive" } }, + ], [], - ), + ) satisfies Prisma.EmploymentOfficeWhereInput["OR"]), ...queryOrNot(!!body?.id, [{ id: { in: body?.id } }], []), ] : undefined, From 2e71c86b363c971753231fdbfe7cb94f3284cf7b Mon Sep 17 00:00:00 2001 From: Kanjana Date: Thu, 10 Apr 2025 15:01:18 +0700 Subject: [PATCH 004/167] add incomplete in RequestData --- src/controllers/06-request-list-controller.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/controllers/06-request-list-controller.ts b/src/controllers/06-request-list-controller.ts index 464b5fc..62f594a 100644 --- a/src/controllers/06-request-list-controller.ts +++ b/src/controllers/06-request-list-controller.ts @@ -80,6 +80,7 @@ export class RequestDataController extends Controller { @Query() requestDataStatus?: RequestDataStatus, @Query() quotationId?: string, @Query() code?: string, + @Query() incomplete?: boolean, ) { const where = { OR: queryOrNot(query, [ @@ -118,7 +119,11 @@ export class RequestDataController extends Controller { }, ]), code, - requestDataStatus, + requestDataStatus: incomplete + ? { + notIn: [RequestDataStatus.Completed, RequestDataStatus.Canceled], + } + : requestDataStatus, requestWork: responsibleOnly ? { some: { From 7b28ddd2b2b1a14abc80018f16c66c9e987940d6 Mon Sep 17 00:00:00 2001 From: Kanjana Date: Thu, 10 Apr 2025 15:38:14 +0700 Subject: [PATCH 005/167] change update --- src/controllers/06-request-list-controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/06-request-list-controller.ts b/src/controllers/06-request-list-controller.ts index 62f594a..ce0e03f 100644 --- a/src/controllers/06-request-list-controller.ts +++ b/src/controllers/06-request-list-controller.ts @@ -236,7 +236,7 @@ export class RequestDataController extends Controller { return record; } - @Post("updata-messenger") + @Post("update-messenger") @Security("keycloak") async updateRequestData( @Request() req: RequestWithUser, From a06d5514fc44cf82c69b075ec69845f7cdcca5ef Mon Sep 17 00:00:00 2001 From: Kanjana Date: Fri, 11 Apr 2025 11:28:24 +0700 Subject: [PATCH 006/167] updata user add employmentOffice --- .../20250410102415_add/migration.sql | 3 ++ .../20250410104307_change/migration.sql | 3 ++ prisma/schema.prisma | 7 ++-- src/controllers/02-user-controller.ts | 18 ++++++---- src/controllers/06-request-list-controller.ts | 33 ++++++++++++++++++- 5 files changed, 55 insertions(+), 9 deletions(-) create mode 100644 prisma/migrations/20250410102415_add/migration.sql create mode 100644 prisma/migrations/20250410104307_change/migration.sql diff --git a/prisma/migrations/20250410102415_add/migration.sql b/prisma/migrations/20250410102415_add/migration.sql new file mode 100644 index 0000000..eaf0561 --- /dev/null +++ b/prisma/migrations/20250410102415_add/migration.sql @@ -0,0 +1,3 @@ +-- AlterTable +ALTER TABLE "User" ADD COLUMN "contactName" TEXT, +ADD COLUMN "contactTel" TEXT; diff --git a/prisma/migrations/20250410104307_change/migration.sql b/prisma/migrations/20250410104307_change/migration.sql new file mode 100644 index 0000000..3239108 --- /dev/null +++ b/prisma/migrations/20250410104307_change/migration.sql @@ -0,0 +1,3 @@ +-- AlterTable +ALTER TABLE "User" ALTER COLUMN "firstName" DROP NOT NULL, +ALTER COLUMN "lastName" DROP NOT NULL; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index cb38c88..d40957e 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -371,11 +371,11 @@ model User { code String? namePrefix String? - firstName String + firstName String? firstNameEN String middleName String? middleNameEN String? - lastName String + lastName String? lastNameEN String username String gender String @@ -497,6 +497,9 @@ model User { remark String? agencyStatus String? + + contactName String? + contactTel String? } model UserResponsibleArea { diff --git a/src/controllers/02-user-controller.ts b/src/controllers/02-user-controller.ts index 7ba34e6..6639511 100644 --- a/src/controllers/02-user-controller.ts +++ b/src/controllers/02-user-controller.ts @@ -79,11 +79,11 @@ type UserCreate = { citizenExpire?: Date | null; namePrefix?: string | null; - firstName: string; + firstName?: string; firstNameEN: string; middleName?: string | null; middleNameEN?: string | null; - lastName: string; + lastName?: string; lastNameEN: string; gender: string; @@ -123,6 +123,9 @@ type UserCreate = { remark?: string; agencyStatus?: string; + + contactName?: string; + contactTel?: string; }; type UserUpdate = { @@ -139,9 +142,9 @@ type UserUpdate = { namePrefix?: string | null; firstName?: string; - firstNameEN?: string; + firstNameEN: string; middleName?: string | null; - middleNameEN?: string | null; + middleNameEN: string | null; lastName?: string; lastNameEN?: string; gender?: string; @@ -182,6 +185,9 @@ type UserUpdate = { remark?: string; agencyStatus?: string; + + contactName?: string; + contactTel?: string; }; const permissionCondCompany = createPermCondition((_) => true); @@ -477,8 +483,8 @@ export class UserController extends Controller { } const userId = await createUser(username, username, { - firstName: body.firstName, - lastName: body.lastName, + firstName: body.firstNameEN, + lastName: body.lastNameEN, email: body.email, requiredActions: ["UPDATE_PASSWORD"], enabled: rest.status !== "INACTIVE", diff --git a/src/controllers/06-request-list-controller.ts b/src/controllers/06-request-list-controller.ts index ce0e03f..9df79b3 100644 --- a/src/controllers/06-request-list-controller.ts +++ b/src/controllers/06-request-list-controller.ts @@ -187,6 +187,16 @@ export class RequestDataController extends Controller { employeePassport: { orderBy: { expireDate: "desc" }, }, + province: { + include: { + employmentOffice: true, + }, + }, + district: { + include: { + employmentOffice: true, + }, + }, }, }, }, @@ -197,7 +207,28 @@ export class RequestDataController extends Controller { prisma.requestData.count({ where }), ]); - return { result, page, pageSize, total }; + const dataRequestData = result.map((item) => { + const employee = item.employee; + const dataOffice = + item.employee.provinceId === "10" + ? employee.district?.employmentOffice + : employee.province?.employmentOffice; + + return { + ...item, + employee: { + ...employee, + dataOffice, + }, + }; + }); + + return { + result: dataRequestData, + page, + pageSize, + total, + }; } @Get("{requestDataId}") From 62def572de360a598bf76353171392de43edc65d Mon Sep 17 00:00:00 2001 From: Kanjana Date: Fri, 11 Apr 2025 12:06:11 +0700 Subject: [PATCH 007/167] Pull data with CustomerBranch --- src/controllers/06-request-list-controller.ts | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/controllers/06-request-list-controller.ts b/src/controllers/06-request-list-controller.ts index 9df79b3..1a0c7c6 100644 --- a/src/controllers/06-request-list-controller.ts +++ b/src/controllers/06-request-list-controller.ts @@ -187,14 +187,18 @@ export class RequestDataController extends Controller { employeePassport: { orderBy: { expireDate: "desc" }, }, - province: { + customerBranch: { include: { - employmentOffice: true, - }, - }, - district: { - include: { - employmentOffice: true, + province: { + include: { + employmentOffice: true, + }, + }, + district: { + include: { + employmentOffice: true, + }, + }, }, }, }, @@ -210,9 +214,8 @@ export class RequestDataController extends Controller { const dataRequestData = result.map((item) => { const employee = item.employee; const dataOffice = - item.employee.provinceId === "10" - ? employee.district?.employmentOffice - : employee.province?.employmentOffice; + employee.customerBranch.district?.employmentOffice.at(0) ?? + employee.customerBranch.province?.employmentOffice.at(0); return { ...item, From ee610c56867a458acfb17aa8a672cdb0a30d1384 Mon Sep 17 00:00:00 2001 From: Kanjana Date: Fri, 11 Apr 2025 12:50:44 +0700 Subject: [PATCH 008/167] change position --- src/controllers/06-request-list-controller.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/controllers/06-request-list-controller.ts b/src/controllers/06-request-list-controller.ts index 1a0c7c6..e1da5f2 100644 --- a/src/controllers/06-request-list-controller.ts +++ b/src/controllers/06-request-list-controller.ts @@ -219,10 +219,7 @@ export class RequestDataController extends Controller { return { ...item, - employee: { - ...employee, - dataOffice, - }, + dataOffice, }; }); From f2d0c20ece942c271c62cd8b14ea2861c02b5315 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Thu, 17 Apr 2025 12:56:31 +0700 Subject: [PATCH 009/167] feat: add endpoint for get same office district --- .../00-employment-office-controller.ts | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/controllers/00-employment-office-controller.ts b/src/controllers/00-employment-office-controller.ts index 95adc5a..9c6995b 100644 --- a/src/controllers/00-employment-office-controller.ts +++ b/src/controllers/00-employment-office-controller.ts @@ -12,6 +12,39 @@ export class EmploymentOfficeController extends Controller { return this.getEmploymentOfficeListByCriteria(districtId, query); } + @Post("list-same-office-area") + async getSameOfficeArea(@Body() body: { districtId: string }) { + const office = await prisma.employmentOffice.findFirst({ + include: { + province: { + include: { + district: true, + }, + }, + district: true, + }, + where: { + OR: [ + { + province: { district: { some: { id: body.districtId } } }, + district: { none: {} }, + }, + { + district: { + some: { districtId: body.districtId }, + }, + }, + ], + }, + }); + if (!office) return []; + + return [ + ...office.district.map((v) => v.districtId), + ...office.province.district.map((v) => v.id), + ]; + } + @Post("list") async getEmploymentOfficeListByCriteria( @Query() districtId?: string, From 0aba9f9865b9ffd1f3b95051da0e7a5dd6f0726f Mon Sep 17 00:00:00 2001 From: Kanjana Date: Thu, 17 Apr 2025 13:41:22 +0700 Subject: [PATCH 010/167] search startDate and endDate --- src/controllers/01-branch-controller.ts | 5 ++ src/controllers/01-branch-user-controller.ts | 8 +++- src/controllers/02-user-controller.ts | 4 ++ .../03-customer-branch-controller.ts | 7 +++ src/controllers/03-customer-controller.ts | 5 +- src/controllers/03-employee-controller.ts | 7 +++ .../04-flow-template-controller.ts | 5 +- src/controllers/04-invoice-controller.ts | 4 ++ src/controllers/04-product-controller.ts | 5 +- .../04-product-group-controller.ts | 5 +- src/controllers/04-properties-controller.ts | 5 +- src/controllers/04-service-controller.ts | 5 +- src/controllers/04-work-controller.ts | 4 ++ src/controllers/05-quotation-controller.ts | 5 +- src/controllers/06-request-list-controller.ts | 5 +- src/controllers/07-task-controller.ts | 8 +++- src/controllers/08-credit-note-controller.ts | 5 +- src/controllers/09-debit-note-controller.ts | 5 +- src/controllers/09-line-controller.ts | 11 ++++- src/utils/relation.ts | 47 +++++++++++-------- 20 files changed, 123 insertions(+), 32 deletions(-) diff --git a/src/controllers/01-branch-controller.ts b/src/controllers/01-branch-controller.ts index f0cc512..003c4cd 100644 --- a/src/controllers/01-branch-controller.ts +++ b/src/controllers/01-branch-controller.ts @@ -39,6 +39,7 @@ import { connectOrNot, queryOrNot, whereAddressQuery, + whereDateQuery, } from "../utils/relation"; import { isUsedError, notFoundError, relationError } from "../utils/error"; @@ -250,6 +251,8 @@ export class BranchController extends Controller { @Query() query: string = "", @Query() page: number = 1, @Query() pageSize: number = 30, + @Query() startDate?: Date, + @Query() endDate?: Date, ) { const where = { AND: { @@ -285,6 +288,7 @@ export class BranchController extends Controller { }, }, ]), + ...whereDateQuery(startDate, endDate), } satisfies Prisma.BranchWhereInput; const [result, total] = await prisma.$transaction([ @@ -321,6 +325,7 @@ export class BranchController extends Controller { district: true, subDistrict: true, }, + ...whereDateQuery(startDate, endDate), orderBy: { code: "asc" }, } : false, diff --git a/src/controllers/01-branch-user-controller.ts b/src/controllers/01-branch-user-controller.ts index 99d77a4..05177d2 100644 --- a/src/controllers/01-branch-user-controller.ts +++ b/src/controllers/01-branch-user-controller.ts @@ -18,7 +18,7 @@ import HttpError from "../interfaces/http-error"; import HttpStatus from "../interfaces/http-status"; import { RequestWithUser } from "../interfaces/user"; import { branchRelationPermInclude, createPermCheck } from "../services/permission"; -import { queryOrNot } from "../utils/relation"; +import { queryOrNot, whereDateQuery } from "../utils/relation"; const MANAGE_ROLES = ["system", "head_of_admin", "admin", "branch_manager"]; @@ -97,6 +97,8 @@ export class UserBranchController extends Controller { @Query() query: string = "", @Query() page: number = 1, @Query() pageSize: number = 30, + @Query() startDate?: Date, + @Query() endDate?: Date, ) { const where = { AND: { @@ -107,6 +109,7 @@ export class UserBranchController extends Controller { { branch: { name: { contains: query, mode: "insensitive" } } }, { branch: { nameEN: { contains: query, mode: "insensitive" } } }, ]), + ...whereDateQuery(startDate, endDate), } satisfies Prisma.BranchUserWhereInput; const [result, total] = await prisma.$transaction([ @@ -150,6 +153,8 @@ export class BranchUserController extends Controller { @Query() query: string = "", @Query() page: number = 1, @Query() pageSize: number = 30, + @Query() startDate?: Date, + @Query() endDate?: Date, ) { const where = { AND: { @@ -164,6 +169,7 @@ export class BranchUserController extends Controller { { user: { email: { contains: query, mode: "insensitive" } } }, { user: { telephoneNo: { contains: query, mode: "insensitive" } } }, ], + ...whereDateQuery(startDate, endDate), } satisfies Prisma.BranchUserWhereInput; const [result, total] = await prisma.$transaction([ diff --git a/src/controllers/02-user-controller.ts b/src/controllers/02-user-controller.ts index 6639511..17630eb 100644 --- a/src/controllers/02-user-controller.ts +++ b/src/controllers/02-user-controller.ts @@ -51,6 +51,7 @@ import { connectOrNot, queryOrNot, whereAddressQuery, + whereDateQuery, } from "../utils/relation"; import { isUsedError, notFoundError, relationError } from "../utils/error"; import { retry } from "../utils/func"; @@ -305,6 +306,8 @@ export class UserController extends Controller { @Query() status?: Status, @Query() responsibleDistrictId?: string, @Query() activeBranchOnly?: boolean, + @Query() startDate?: Date, + @Query() endDate?: Date, @Body() body?: { userId?: string[]; @@ -368,6 +371,7 @@ export class UserController extends Controller { }, }, }, + ...whereDateQuery(startDate, endDate), } satisfies Prisma.UserWhereInput; const [result, total] = await prisma.$transaction([ diff --git a/src/controllers/03-customer-branch-controller.ts b/src/controllers/03-customer-branch-controller.ts index 7818bef..5c94a2f 100644 --- a/src/controllers/03-customer-branch-controller.ts +++ b/src/controllers/03-customer-branch-controller.ts @@ -30,6 +30,7 @@ import { connectOrNot, queryOrNot, whereAddressQuery, + whereDateQuery, } from "../utils/relation"; import { isUsedError, notFoundError, relationError } from "../utils/error"; import { @@ -195,6 +196,8 @@ export class CustomerBranchController extends Controller { @Query() page: number = 1, @Query() pageSize: number = 30, @Query() activeRegisBranchOnly?: boolean, + @Query() startDate?: Date, + @Query() endDate?: Date, ) { const where = { OR: queryOrNot(query, [ @@ -229,6 +232,7 @@ export class CustomerBranchController extends Controller { subDistrict: zipCode ? { zipCode } : undefined, ...filterStatus(activeRegisBranchOnly ? Status.ACTIVE : status), }, + ...whereDateQuery(startDate, endDate), } satisfies Prisma.CustomerBranchWhereInput; const [result, total] = await prisma.$transaction([ @@ -285,6 +289,8 @@ export class CustomerBranchController extends Controller { @Query() visa?: boolean, @Query() page: number = 1, @Query() pageSize: number = 30, + @Query() startDate?: Date, + @Query() endDate?: Date, ) { const where = { OR: queryOrNot(query, [ @@ -300,6 +306,7 @@ export class CustomerBranchController extends Controller { subDistrict: zipCode ? { zipCode } : undefined, gender, }, + ...whereDateQuery(startDate, endDate), } satisfies Prisma.EmployeeWhereInput; const [result, total] = await prisma.$transaction([ diff --git a/src/controllers/03-customer-controller.ts b/src/controllers/03-customer-controller.ts index 8cac3d9..122726f 100644 --- a/src/controllers/03-customer-controller.ts +++ b/src/controllers/03-customer-controller.ts @@ -36,7 +36,7 @@ import { setFile, } from "../utils/minio"; import { isUsedError, notFoundError, relationError } from "../utils/error"; -import { connectOrNot, queryOrNot } from "../utils/relation"; +import { connectOrNot, queryOrNot, whereDateQuery } from "../utils/relation"; const MANAGE_ROLES = [ "system", @@ -165,6 +165,8 @@ export class CustomerController extends Controller { @Query() includeBranch: boolean = false, @Query() company: boolean = false, @Query() activeBranchOnly?: boolean, + @Query() startDate?: Date, + @Query() endDate?: Date, ) { const where = { OR: queryOrNot(query, [ @@ -188,6 +190,7 @@ export class CustomerController extends Controller { : permissionCond(req.user, { activeOnly: activeBranchOnly }), }, }, + ...whereDateQuery(startDate, endDate), } satisfies Prisma.CustomerWhereInput; const [result, total] = await prisma.$transaction([ diff --git a/src/controllers/03-employee-controller.ts b/src/controllers/03-employee-controller.ts index 44629c1..5f8a1d4 100644 --- a/src/controllers/03-employee-controller.ts +++ b/src/controllers/03-employee-controller.ts @@ -30,6 +30,7 @@ import { connectOrNot, queryOrNot, whereAddressQuery, + whereDateQuery, } from "../utils/relation"; import { isUsedError, notFoundError, relationError } from "../utils/error"; import { @@ -154,6 +155,8 @@ export class EmployeeController extends Controller { @Query() customerBranchId?: string, @Query() status?: Status, @Query() query: string = "", + @Query() startDate?: Date, + @Query() endDate?: Date, ) { return await prisma.employee .groupBy({ @@ -183,6 +186,7 @@ export class EmployeeController extends Controller { }, }, }, + ...whereDateQuery(startDate, endDate), }, }) .then((res) => @@ -240,6 +244,8 @@ export class EmployeeController extends Controller { @Query() page: number = 1, @Query() pageSize: number = 30, @Query() activeOnly?: boolean, + @Query() startDate?: Date, + @Query() endDate?: Date, @Body() body?: { passport?: string[]; @@ -288,6 +294,7 @@ export class EmployeeController extends Controller { subDistrict: zipCode ? { zipCode } : undefined, gender, }, + ...whereDateQuery(startDate, endDate), } satisfies Prisma.EmployeeWhereInput; const [result, total] = await prisma.$transaction([ diff --git a/src/controllers/04-flow-template-controller.ts b/src/controllers/04-flow-template-controller.ts index 0f80d61..97bd1b9 100644 --- a/src/controllers/04-flow-template-controller.ts +++ b/src/controllers/04-flow-template-controller.ts @@ -24,7 +24,7 @@ import HttpError from "../interfaces/http-error"; import HttpStatus from "../interfaces/http-status"; import { notFoundError } from "../utils/error"; import { filterStatus } from "../services/prisma"; -import { queryOrNot } from "../utils/relation"; +import { queryOrNot, whereDateQuery } from "../utils/relation"; type WorkflowPayload = { name: string; @@ -58,6 +58,8 @@ export class FlowTemplateController extends Controller { @Query() status?: Status, @Query() query = "", @Query() activeOnly?: boolean, + @Query() startDate?: Date, + @Query() endDate?: Date, ) { const where = { OR: queryOrNot(query, [ @@ -74,6 +76,7 @@ export class FlowTemplateController extends Controller { OR: permissionCondCompany(req.user, { activeOnly: true }), }, }, + ...whereDateQuery(startDate, endDate), } satisfies Prisma.WorkflowTemplateWhereInput; const [result, total] = await prisma.$transaction([ prisma.workflowTemplate.findMany({ diff --git a/src/controllers/04-invoice-controller.ts b/src/controllers/04-invoice-controller.ts index 64efee2..fb3fdf0 100644 --- a/src/controllers/04-invoice-controller.ts +++ b/src/controllers/04-invoice-controller.ts @@ -21,6 +21,7 @@ import { createPermCondition, } from "../services/permission"; import { PaymentStatus } from "../generated/kysely/types"; +import { whereDateQuery } from "../utils/relation"; type InvoicePayload = { quotationId: string; @@ -95,6 +96,8 @@ export class InvoiceController extends Controller { @Query() quotationId?: string, @Query() debitNoteId?: string, @Query() pay?: boolean, + @Query() startDate?: Date, + @Query() endDate?: Date, ) { const where: Prisma.InvoiceWhereInput = { OR: [ @@ -132,6 +135,7 @@ export class InvoiceController extends Controller { OR: permissionCondCompany(req.user), }, }, + ...whereDateQuery(startDate, endDate), }; const [result, total] = await prisma.$transaction([ diff --git a/src/controllers/04-product-controller.ts b/src/controllers/04-product-controller.ts index 8cb8c3e..09dce42 100644 --- a/src/controllers/04-product-controller.ts +++ b/src/controllers/04-product-controller.ts @@ -27,7 +27,7 @@ import { isSystem } from "../utils/keycloak"; import { filterStatus } from "../services/prisma"; import { deleteFile, deleteFolder, fileLocation, getFile, listFile, setFile } from "../utils/minio"; import { isUsedError, notFoundError, relationError } from "../utils/error"; -import { queryOrNot } from "../utils/relation"; +import { queryOrNot, whereDateQuery } from "../utils/relation"; const MANAGE_ROLES = [ "system", @@ -139,6 +139,8 @@ export class ProductController extends Controller { @Query() orderField?: keyof Product, @Query() orderBy?: "asc" | "desc", @Query() activeOnly?: boolean, + @Query() startDate?: Date, + @Query() endDate?: Date, ) { // NOTE: will be used to scope product within product group that is shared between branch but not company when select shared product if user is system const targetGroup = @@ -194,6 +196,7 @@ export class ProductController extends Controller { : []), ], }, + ...whereDateQuery(startDate, endDate), } satisfies Prisma.ProductWhereInput; const [result, total] = await prisma.$transaction([ diff --git a/src/controllers/04-product-group-controller.ts b/src/controllers/04-product-group-controller.ts index ee27265..ecd4ac6 100644 --- a/src/controllers/04-product-group-controller.ts +++ b/src/controllers/04-product-group-controller.ts @@ -27,7 +27,7 @@ import { } from "../services/permission"; import { filterStatus } from "../services/prisma"; import { isUsedError, notFoundError, relationError } from "../utils/error"; -import { queryOrNot } from "../utils/relation"; +import { queryOrNot, whereDateQuery } from "../utils/relation"; type ProductGroupCreate = { name: string; @@ -90,6 +90,8 @@ export class ProductGroup extends Controller { @Query() page: number = 1, @Query() pageSize: number = 30, @Query() activeOnly?: boolean, + @Query() startDate?: Date, + @Query() endDate?: Date, ) { const where = { OR: queryOrNot(query, [ @@ -105,6 +107,7 @@ export class ProductGroup extends Controller { : { OR: permissionCond(req.user, { activeOnly }) }, }, ], + ...whereDateQuery(startDate, endDate), } satisfies Prisma.ProductGroupWhereInput; const [result, total] = await prisma.$transaction([ diff --git a/src/controllers/04-properties-controller.ts b/src/controllers/04-properties-controller.ts index 35f94a8..6203ec6 100644 --- a/src/controllers/04-properties-controller.ts +++ b/src/controllers/04-properties-controller.ts @@ -24,7 +24,7 @@ import HttpError from "../interfaces/http-error"; import HttpStatus from "../interfaces/http-status"; import { notFoundError } from "../utils/error"; import { filterStatus } from "../services/prisma"; -import { queryOrNot } from "../utils/relation"; +import { queryOrNot, whereDateQuery } from "../utils/relation"; type PropertyPayload = { name: string; @@ -49,6 +49,8 @@ export class PropertiesController extends Controller { @Query() status?: Status, @Query() query = "", @Query() activeOnly?: boolean, + @Query() startDate?: Date, + @Query() endDate?: Date, ) { const where = { OR: queryOrNot(query, [ @@ -61,6 +63,7 @@ export class PropertiesController extends Controller { OR: permissionCondCompany(req.user, { activeOnly: true }), }, }, + ...whereDateQuery(startDate, endDate), } satisfies Prisma.PropertyWhereInput; const [result, total] = await prisma.$transaction([ prisma.property.findMany({ diff --git a/src/controllers/04-service-controller.ts b/src/controllers/04-service-controller.ts index c193fbd..ed46c18 100644 --- a/src/controllers/04-service-controller.ts +++ b/src/controllers/04-service-controller.ts @@ -36,7 +36,7 @@ import { listFile, setFile, } from "../utils/minio"; -import { queryOrNot } from "../utils/relation"; +import { queryOrNot, whereDateQuery } from "../utils/relation"; const MANAGE_ROLES = [ "system", @@ -164,6 +164,8 @@ export class ServiceController extends Controller { @Query() fullDetail?: boolean, @Query() activeOnly?: boolean, @Query() shared?: boolean, + @Query() startDate?: Date, + @Query() endDate?: Date, ) { // NOTE: will be used to scope product within product group that is shared between branch but not company when select shared product if user is system const targetGroup = @@ -219,6 +221,7 @@ export class ServiceController extends Controller { : []), ], }, + ...whereDateQuery(startDate, endDate), } satisfies Prisma.ServiceWhereInput; const [result, total] = await prisma.$transaction([ diff --git a/src/controllers/04-work-controller.ts b/src/controllers/04-work-controller.ts index 9b875b6..c8a140b 100644 --- a/src/controllers/04-work-controller.ts +++ b/src/controllers/04-work-controller.ts @@ -18,6 +18,7 @@ import prisma from "../db"; import { RequestWithUser } from "../interfaces/user"; import HttpStatus from "../interfaces/http-status"; import { isUsedError, notFoundError } from "../utils/error"; +import { whereDateQuery } from "../utils/relation"; type WorkCreate = { order: number; @@ -45,9 +46,12 @@ export class WorkController extends Controller { @Query() query: string = "", @Query() page: number = 1, @Query() pageSize: number = 30, + @Query() startDate?: Date, + @Query() endDate?: Date, ) { const where = { OR: [{ name: { contains: query }, serviceId: baseOnly ? null : undefined }], + ...whereDateQuery(startDate, endDate), } satisfies Prisma.WorkWhereInput; const [result, total] = await prisma.$transaction([ diff --git a/src/controllers/05-quotation-controller.ts b/src/controllers/05-quotation-controller.ts index fa3d6b3..b454db3 100644 --- a/src/controllers/05-quotation-controller.ts +++ b/src/controllers/05-quotation-controller.ts @@ -25,7 +25,7 @@ import { import { isSystem } from "../utils/keycloak"; import { isUsedError, notFoundError, relationError } from "../utils/error"; import { precisionRound } from "../utils/arithmetic"; -import { queryOrNot } from "../utils/relation"; +import { queryOrNot, whereDateQuery } from "../utils/relation"; import { deleteFile, fileLocation, getFile, getPresigned, listFile, setFile } from "../utils/minio"; import HttpError from "../interfaces/http-error"; import HttpStatus from "../interfaces/http-status"; @@ -206,6 +206,8 @@ export class QuotationController extends Controller { @Query() forDebitNote?: boolean, @Query() code?: string, @Query() query = "", + @Query() startDate?: Date, + @Query() endDate?: Date, ) { const where = { OR: queryOrNot(query, [ @@ -253,6 +255,7 @@ export class QuotationController extends Controller { }, } : undefined, + ...whereDateQuery(startDate, endDate), } satisfies Prisma.QuotationWhereInput; const [result, total] = await prisma.$transaction([ diff --git a/src/controllers/06-request-list-controller.ts b/src/controllers/06-request-list-controller.ts index e1da5f2..6b5e5f8 100644 --- a/src/controllers/06-request-list-controller.ts +++ b/src/controllers/06-request-list-controller.ts @@ -27,7 +27,7 @@ import { createPermCheck, createPermCondition, } from "../services/permission"; -import { queryOrNot } from "../utils/relation"; +import { queryOrNot, whereDateQuery } from "../utils/relation"; import { notFoundError } from "../utils/error"; import { deleteFile, fileLocation, getFile, getPresigned, listFile, setFile } from "../utils/minio"; import HttpError from "../interfaces/http-error"; @@ -81,6 +81,8 @@ export class RequestDataController extends Controller { @Query() quotationId?: string, @Query() code?: string, @Query() incomplete?: boolean, + @Query() startDate?: Date, + @Query() endDate?: Date, ) { const where = { OR: queryOrNot(query, [ @@ -147,6 +149,7 @@ export class RequestDataController extends Controller { id: quotationId, registeredBranch: { OR: permissionCond(req.user) }, }, + ...whereDateQuery(startDate, endDate), } satisfies Prisma.RequestDataWhereInput; const [result, total] = await prisma.$transaction([ diff --git a/src/controllers/07-task-controller.ts b/src/controllers/07-task-controller.ts index 03009b3..fd5c4a2 100644 --- a/src/controllers/07-task-controller.ts +++ b/src/controllers/07-task-controller.ts @@ -42,7 +42,7 @@ import { listFile, setFile, } from "../utils/minio"; -import { queryOrNot } from "../utils/relation"; +import { queryOrNot, whereDateQuery } from "../utils/relation"; const MANAGE_ROLES = ["system", "head_of_admin", "admin", "document_checker"]; @@ -107,6 +107,8 @@ export class TaskController extends Controller { @Query() assignedUserId?: string, @Query() taskOrderStatus?: TaskOrderStatus, @Body() body?: { code?: string[] }, + @Query() startDate?: Date, + @Query() endDate?: Date, ) { const where = { taskOrderStatus, @@ -125,6 +127,7 @@ export class TaskController extends Controller { { contactName: { contains: query, mode: "insensitive" } }, { contactTel: { contains: query, mode: "insensitive" } }, ]), + ...whereDateQuery(startDate, endDate), } satisfies Prisma.TaskOrderWhereInput; const [result, total] = await prisma.$transaction([ @@ -979,6 +982,8 @@ export class UserTaskController extends Controller { @Query() page = 1, @Query() pageSize = 30, @Query() userTaskStatus?: UserTaskStatus, + @Query() startDate?: Date, + @Query() endDate?: Date, ) { const where = { taskList: { @@ -1025,6 +1030,7 @@ export class UserTaskController extends Controller { { contactName: { contains: query, mode: "insensitive" } }, { contactTel: { contains: query, mode: "insensitive" } }, ]), + ...whereDateQuery(startDate, endDate), } satisfies Prisma.TaskOrderWhereInput; const [result, total] = await prisma.$transaction([ diff --git a/src/controllers/08-credit-note-controller.ts b/src/controllers/08-credit-note-controller.ts index 5fda3eb..2e80575 100644 --- a/src/controllers/08-credit-note-controller.ts +++ b/src/controllers/08-credit-note-controller.ts @@ -35,7 +35,7 @@ import { } from "../utils/minio"; import { notFoundError } from "../utils/error"; import { CreditNotePaybackType, CreditNoteStatus, Prisma, RequestDataStatus } from "@prisma/client"; -import { queryOrNot } from "../utils/relation"; +import { queryOrNot, whereDateQuery } from "../utils/relation"; import { PaybackStatus, RequestWorkStatus } from "../generated/kysely/types"; const MANAGE_ROLES = [ @@ -143,6 +143,8 @@ export class CreditNoteController extends Controller { @Query() quotationId?: string, @Query() creditNoteStatus?: CreditNoteStatus, @Body() body?: {}, + @Query() startDate?: Date, + @Query() endDate?: Date, ) { const where = { OR: queryOrNot(query, [ @@ -199,6 +201,7 @@ export class CreditNoteController extends Controller { }, }, }, + ...whereDateQuery(startDate, endDate), } satisfies Prisma.CreditNoteWhereInput; const [result, total] = await prisma.$transaction([ diff --git a/src/controllers/09-debit-note-controller.ts b/src/controllers/09-debit-note-controller.ts index a499f88..993037a 100644 --- a/src/controllers/09-debit-note-controller.ts +++ b/src/controllers/09-debit-note-controller.ts @@ -36,7 +36,7 @@ import { setFile, } from "../utils/minio"; import { isUsedError, notFoundError, relationError } from "../utils/error"; -import { queryOrNot } from "../utils/relation"; +import { queryOrNot, whereDateQuery } from "../utils/relation"; import { isSystem } from "../utils/keycloak"; import { precisionRound } from "../utils/arithmetic"; @@ -196,6 +196,8 @@ export class DebitNoteController extends Controller { @Query() includeRegisteredBranch?: boolean, @Query() code?: string, @Body() body?: {}, + @Query() startDate?: Date, + @Query() endDate?: Date, ) { const where = { OR: queryOrNot(query, [ @@ -220,6 +222,7 @@ export class DebitNoteController extends Controller { debitNoteQuotationId: quotationId, registeredBranch: isSystem(req.user) ? undefined : { OR: permissionCond(req.user) }, quotationStatus: status, + ...whereDateQuery(startDate, endDate), } satisfies Prisma.QuotationWhereInput; const [result, total] = await prisma.$transaction([ diff --git a/src/controllers/09-line-controller.ts b/src/controllers/09-line-controller.ts index 0a6ea3c..91c06bb 100644 --- a/src/controllers/09-line-controller.ts +++ b/src/controllers/09-line-controller.ts @@ -25,7 +25,7 @@ import { TaskStatus, RequestWorkStatus, } from "@prisma/client"; -import { queryOrNot, whereAddressQuery } from "../utils/relation"; +import { queryOrNot, whereAddressQuery, whereDateQuery } from "../utils/relation"; import { filterStatus } from "../services/prisma"; // import { RequestWorkStatus } from "../generated/kysely/types"; import { deleteFile, fileLocation, getFile, getPresigned, listFile, setFile } from "../utils/minio"; @@ -51,6 +51,8 @@ export class LineController extends Controller { @Query() page: number = 1, @Query() pageSize: number = 30, @Query() activeOnly?: boolean, + @Query() startDate?: Date, + @Query() endDate?: Date, ) { const where = { OR: !!query @@ -87,6 +89,7 @@ export class LineController extends Controller { subDistrict: zipCode ? { zipCode } : undefined, gender, }, + ...whereDateQuery(startDate, endDate), } satisfies Prisma.EmployeeWhereInput; const [result, total] = await prisma.$transaction([ @@ -173,6 +176,8 @@ export class LineController extends Controller { @Query() requestDataStatus?: RequestDataStatus, @Query() quotationId?: string, @Query() code?: string, + @Query() startDate?: Date, + @Query() endDate?: Date, ) { const where = { OR: queryOrNot(query, [ @@ -247,6 +252,7 @@ export class LineController extends Controller { ], }, }, + ...whereDateQuery(startDate, endDate), } satisfies Prisma.RequestDataWhereInput; const [result, total] = await prisma.$transaction([ @@ -604,6 +610,8 @@ export class LineController extends Controller { @Query() includeRegisteredBranch?: boolean, @Query() code?: string, @Query() query = "", + @Query() startDate?: Date, + @Query() endDate?: Date, ) { const where = { OR: @@ -660,6 +668,7 @@ export class LineController extends Controller { }, } : undefined, + ...whereDateQuery(startDate, endDate), } satisfies Prisma.QuotationWhereInput; const [result, total] = await prisma.$transaction([ diff --git a/src/utils/relation.ts b/src/utils/relation.ts index 3e96f11..aebb744 100644 --- a/src/utils/relation.ts +++ b/src/utils/relation.ts @@ -10,26 +10,35 @@ export function connectOrDisconnect(id?: string | null) { export function whereAddressQuery(query: string) { return [ - { address: { contains: query } }, - { addressEN: { contains: query } }, - { soi: { contains: query } }, - { soiEN: { contains: query } }, - { moo: { contains: query } }, - { mooEN: { contains: query } }, - { street: { contains: query } }, - { streetEN: { contains: query } }, - { province: { name: { contains: query } } }, - { province: { nameEN: { contains: query } } }, - { district: { name: { contains: query } } }, - { district: { nameEN: { contains: query } } }, - { subDistrict: { name: { contains: query } } }, - { subDistrict: { nameEN: { contains: query } } }, - { subDistrict: { zipCode: { contains: query } } }, - ]; + { address: { contains: query, mode: "insensitive" } }, + { addressEN: { contains: query, mode: "insensitive" } }, + { soi: { contains: query, mode: "insensitive" } }, + { soiEN: { contains: query, mode: "insensitive" } }, + { moo: { contains: query, mode: "insensitive" } }, + { mooEN: { contains: query, mode: "insensitive" } }, + { street: { contains: query, mode: "insensitive" } }, + { streetEN: { contains: query, mode: "insensitive" } }, + { province: { name: { contains: query, mode: "insensitive" } } }, + { province: { nameEN: { contains: query, mode: "insensitive" } } }, + { district: { name: { contains: query, mode: "insensitive" } } }, + { district: { nameEN: { contains: query, mode: "insensitive" } } }, + { subDistrict: { name: { contains: query, mode: "insensitive" } } }, + { subDistrict: { nameEN: { contains: query, mode: "insensitive" } } }, + { subDistrict: { zipCode: { contains: query, mode: "insensitive" } } }, + ] as const; } -export function queryOrNot(query: string | boolean, where: T): T | undefined; -export function queryOrNot(query: string | boolean, where: T, fallback: U): T | U; -export function queryOrNot(query: string | boolean, where: T, fallback?: U) { +export function queryOrNot(query: any, where: T): T | undefined; +export function queryOrNot(query: any, where: T, fallback: U): T | U; +export function queryOrNot(query: any, where: T, fallback?: U) { return !!query ? where : fallback; } + +export function whereDateQuery(startDate: Date | undefined, endDate: Date | undefined) { + return { + createdAt: { + gte: startDate, + lte: endDate, + }, + }; +} From 27d3ce6573d9be857a4069ed153cf339079d1611 Mon Sep 17 00:00:00 2001 From: Kanjana Date: Thu, 17 Apr 2025 16:21:05 +0700 Subject: [PATCH 011/167] change whereDateQuery in branch --- src/controllers/01-branch-controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/01-branch-controller.ts b/src/controllers/01-branch-controller.ts index 003c4cd..da4c958 100644 --- a/src/controllers/01-branch-controller.ts +++ b/src/controllers/01-branch-controller.ts @@ -319,13 +319,13 @@ export class BranchController extends Controller { { telephoneNo: { contains: query, mode: "insensitive" } }, ...whereAddressQuery(query), ], + ...whereDateQuery(startDate, endDate), }, include: { province: true, district: true, subDistrict: true, }, - ...whereDateQuery(startDate, endDate), orderBy: { code: "asc" }, } : false, From d52680c23f94a9a81504183e10087bf984f9c7bc Mon Sep 17 00:00:00 2001 From: Kanjana Date: Thu, 17 Apr 2025 17:05:19 +0700 Subject: [PATCH 012/167] add startDate, endDate --- src/controllers/03-employee-controller.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/controllers/03-employee-controller.ts b/src/controllers/03-employee-controller.ts index 5f8a1d4..9699b6c 100644 --- a/src/controllers/03-employee-controller.ts +++ b/src/controllers/03-employee-controller.ts @@ -212,6 +212,8 @@ export class EmployeeController extends Controller { @Query() page: number = 1, @Query() pageSize: number = 30, @Query() activeOnly?: boolean, + @Query() startDate?: Date, + @Query() endDate?: Date, ) { return this.listByCriteria( req, @@ -226,6 +228,8 @@ export class EmployeeController extends Controller { page, pageSize, activeOnly, + startDate, + endDate, ); } From fd7833a5925a30c7672ae48bace0d1fd61ee71c8 Mon Sep 17 00:00:00 2001 From: Kanjana Date: Thu, 17 Apr 2025 17:56:55 +0700 Subject: [PATCH 013/167] add endDate,startDate --- src/controllers/02-user-controller.ts | 4 ++++ src/controllers/07-task-controller.ts | 6 +++++- src/controllers/08-credit-note-controller.ts | 6 +++++- src/controllers/09-debit-note-controller.ts | 6 +++++- 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/controllers/02-user-controller.ts b/src/controllers/02-user-controller.ts index 17630eb..20848be 100644 --- a/src/controllers/02-user-controller.ts +++ b/src/controllers/02-user-controller.ts @@ -280,6 +280,8 @@ export class UserController extends Controller { @Query() status?: Status, @Query() responsibleDistrictId?: string, @Query() activeBranchOnly?: boolean, + @Query() startDate?: Date, + @Query() endDate?: Date, ) { return this.getUserByCriteria( req, @@ -291,6 +293,8 @@ export class UserController extends Controller { status, responsibleDistrictId, activeBranchOnly, + startDate, + endDate, ); } diff --git a/src/controllers/07-task-controller.ts b/src/controllers/07-task-controller.ts index fd5c4a2..58acbbe 100644 --- a/src/controllers/07-task-controller.ts +++ b/src/controllers/07-task-controller.ts @@ -86,6 +86,8 @@ export class TaskController extends Controller { @Query() pageSize = 30, @Query() assignedByUserId?: string, @Query() taskOrderStatus?: TaskOrderStatus, + @Query() startDate?: Date, + @Query() endDate?: Date, ) { return this.getTaskOrderListByCriteria( req, @@ -94,6 +96,8 @@ export class TaskController extends Controller { pageSize, assignedByUserId, taskOrderStatus, + startDate, + endDate, ); } @@ -106,9 +110,9 @@ export class TaskController extends Controller { @Query() pageSize = 30, @Query() assignedUserId?: string, @Query() taskOrderStatus?: TaskOrderStatus, - @Body() body?: { code?: string[] }, @Query() startDate?: Date, @Query() endDate?: Date, + @Body() body?: { code?: string[] }, ) { const where = { taskOrderStatus, diff --git a/src/controllers/08-credit-note-controller.ts b/src/controllers/08-credit-note-controller.ts index 2e80575..4a6f622 100644 --- a/src/controllers/08-credit-note-controller.ts +++ b/src/controllers/08-credit-note-controller.ts @@ -121,6 +121,8 @@ export class CreditNoteController extends Controller { @Query() query: string = "", @Query() quotationId?: string, @Query() creditNoteStatus?: CreditNoteStatus, + @Query() startDate?: Date, + @Query() endDate?: Date, ) { return await this.getCreditNoteListByCriteria( req, @@ -129,6 +131,8 @@ export class CreditNoteController extends Controller { query, quotationId, creditNoteStatus, + startDate, + endDate, ); } @@ -142,9 +146,9 @@ export class CreditNoteController extends Controller { @Query() query: string = "", @Query() quotationId?: string, @Query() creditNoteStatus?: CreditNoteStatus, - @Body() body?: {}, @Query() startDate?: Date, @Query() endDate?: Date, + @Body() body?: {}, ) { const where = { OR: queryOrNot(query, [ diff --git a/src/controllers/09-debit-note-controller.ts b/src/controllers/09-debit-note-controller.ts index 993037a..8ff5d94 100644 --- a/src/controllers/09-debit-note-controller.ts +++ b/src/controllers/09-debit-note-controller.ts @@ -168,6 +168,8 @@ export class DebitNoteController extends Controller { @Query() payCondition?: PayCondition, @Query() includeRegisteredBranch?: boolean, @Query() code?: string, + @Query() startDate?: Date, + @Query() endDate?: Date, ) { return await this.getDebitNoteListByCriteria( req, @@ -179,6 +181,8 @@ export class DebitNoteController extends Controller { payCondition, includeRegisteredBranch, code, + startDate, + endDate, ); } @@ -195,9 +199,9 @@ export class DebitNoteController extends Controller { @Query() payCondition?: PayCondition, @Query() includeRegisteredBranch?: boolean, @Query() code?: string, - @Body() body?: {}, @Query() startDate?: Date, @Query() endDate?: Date, + @Body() body?: {}, ) { const where = { OR: queryOrNot(query, [ From 05d16f22de2da7baa72b5e9816c89b58ca76619d Mon Sep 17 00:00:00 2001 From: Kanjana Date: Fri, 18 Apr 2025 15:39:02 +0700 Subject: [PATCH 014/167] add import file product --- package.json | 3 + src/controllers/04-product-controller.ts | 128 +++++++++++++++++++++++ src/utils/spreadsheet.ts | 105 +++++++++++++++++++ 3 files changed, 236 insertions(+) create mode 100644 src/utils/spreadsheet.ts diff --git a/package.json b/package.json index b576e46..6294f71 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "@types/cors": "^2.8.17", "@types/express": "^4.17.21", "@types/morgan": "^1.9.9", + "@types/multer": "^1.4.12", "@types/node": "^20.17.10", "@types/nodemailer": "^6.4.17", "nodemon": "^3.1.9", @@ -46,12 +47,14 @@ "dayjs-plugin-utc": "^0.1.2", "docx-templates": "^4.13.0", "dotenv": "^16.4.7", + "exceljs": "^4.4.0", "express": "^4.21.2", "fast-jwt": "^5.0.5", "json-2-csv": "^5.5.8", "kysely": "^0.27.5", "minio": "^8.0.2", "morgan": "^1.10.0", + "multer": "^1.4.5-lts.2", "nodemailer": "^6.10.0", "prisma-extension-kysely": "^3.0.0", "promise.any": "^2.0.6", diff --git a/src/controllers/04-product-controller.ts b/src/controllers/04-product-controller.ts index 09dce42..146f6d1 100644 --- a/src/controllers/04-product-controller.ts +++ b/src/controllers/04-product-controller.ts @@ -11,6 +11,7 @@ import { Security, Tags, Query, + UploadedFile, } from "tsoa"; import { Prisma, Product, Status } from "@prisma/client"; @@ -28,6 +29,7 @@ import { filterStatus } from "../services/prisma"; import { deleteFile, deleteFolder, fileLocation, getFile, listFile, setFile } from "../utils/minio"; import { isUsedError, notFoundError, relationError } from "../utils/error"; import { queryOrNot, whereDateQuery } from "../utils/relation"; +import spreadsheet from "../utils/spreadsheet"; const MANAGE_ROLES = [ "system", @@ -447,6 +449,132 @@ export class ProductController extends Controller { where: { id: productId }, }); } + + @Post("uploadedFile") + @Security("keycloak", MANAGE_ROLES) + async importProduct( + @Request() req: RequestWithUser, + @UploadedFile() file: Express.Multer.File, + @Query() productGroupId: string, + ) { + if (!file?.buffer) throw notFoundError("File"); + + const buffer = new Uint8Array(file.buffer).buffer; + const dataFile = await spreadsheet.readExcel(buffer, { + header: true, + worksheet: "Sheet1", + }); + + let dataName: string[] = []; + const data = await dataFile.map((item: any) => { + dataName.push(item.name); + return { + ...item, + expenseType: + item.expenseType === "ค่าธรรมเนียม" + ? "fee" + : item.expenseType === "ค่าบริการ" + ? "serviceFee" + : "processingFee", + shared: item.shared === "ใช่" ? true : false, + calcVat: item.calcVat === "ใช่" ? true : false, + vatIncluded: item.vatIncluded === "รวม" ? true : false, + agentPriceCalcVat: item.agentPriceCalcVat === "ใช่" ? true : false, + agentPriceVatIncluded: item.agentPriceVatIncluded === "รวม" ? true : false, + serviceChargeCalcVat: item.serviceChargeCalcVat === "ใช่" ? true : false, + serviceChargeVatIncluded: item.serviceChargeVatIncluded === "รวม" ? true : false, + }; + }); + + const [productGroup, productSameName] = await prisma.$transaction([ + prisma.productGroup.findFirst({ + include: { + registeredBranch: { + include: branchRelationPermInclude(req.user), + }, + createdBy: true, + updatedBy: true, + }, + where: { id: productGroupId }, + }), + prisma.product.findMany({ + where: { + productGroup: { + registeredBranch: { + OR: permissionCondCompany(req.user), + }, + }, + name: { in: dataName }, + }, + }), + ]); + + if (!productGroup) throw relationError("Product Group"); + + await permissionCheck(req.user, productGroup.registeredBranch); + let dataProduct: ProductCreate[] = []; + + const record = await prisma.$transaction( + async (tx) => { + const branch = productGroup.registeredBranch; + const company = (branch.headOffice || branch).code; + console.log(branch, company); + for (const item of data) { + const dataDuplicate = productSameName.some( + (v) => v.code.slice(0, -3) === item.code.toUpperCase() && v.name === item.name, + ); + + if (!dataDuplicate) { + const last = await tx.runningNo.upsert({ + where: { + key: `PRODUCT_${company}_${item.code.toLocaleUpperCase()}`, + }, + create: { + key: `PRODUCT_${company}_${item.code.toLocaleUpperCase()}`, + value: 1, + }, + update: { value: { increment: 1 } }, + }); + + dataProduct.push({ + ...item, + code: `${item.code.toLocaleUpperCase()}${last.value.toString().padStart(3, "0")}`, + createdByUserId: req.user.sub, + updatedByUserId: req.user.sub, + productGroupId: productGroupId, + }); + } + } + console.log("dataProduct", dataProduct); + + return await prisma.product.createManyAndReturn({ + data: dataProduct, + include: { + createdBy: true, + updatedBy: true, + }, + }); + }, + { + isolationLevel: Prisma.TransactionIsolationLevel.Serializable, + }, + ); + + if (productGroup.status === "CREATED") { + await prisma.productGroup.update({ + include: { + createdBy: true, + updatedBy: true, + }, + where: { id: productGroupId }, + data: { status: Status.ACTIVE }, + }); + } + + this.setStatus(HttpStatus.CREATED); + + return record; + } } @Route("api/v1/product/{productId}") diff --git a/src/utils/spreadsheet.ts b/src/utils/spreadsheet.ts new file mode 100644 index 0000000..b04a4f2 --- /dev/null +++ b/src/utils/spreadsheet.ts @@ -0,0 +1,105 @@ +import Excel from "exceljs"; + +export default class spreadsheet { + static async readCsv() { + // TODO: read csv + } + + /** + * This function read data from excel file. + * + * @param buffer - Excel file. + * @param opts.header - Interprets the first row as the names of the fields. + * @param opts.worksheet - Specifies the worksheet to read. Can be the worksheet's name or its 1-based index. + * + * @returns + */ + static async readExcel( + buffer: Excel.Buffer, + opts?: { header?: boolean; worksheet?: number | string }, + ): Promise { + const workbook = new Excel.Workbook(); + await workbook.xlsx.load(buffer); + const worksheet = workbook.getWorksheet(opts?.worksheet ?? 1); + + if (!worksheet) return []; + + const header: Record = {}; + const values: any[] = []; + + worksheet.eachRow((row, rowId) => { + if (rowId === 1 && opts?.header !== false) { + row.eachCell((cell, cellId) => { + if (typeof cell.value === "string") { + header[cellId] = nameValue(cell.value); + } else { + header[cellId] = cellId.toString(); + } + }); + } else { + const data: Record = {}; + row.eachCell((cell, cellId) => { + data[opts?.header !== false ? header[cellId] : cellId - 1] = cell.value; + }); + values.push(opts?.header !== false ? data : Object.values(data)); + } + }); + + return values; + } +} + +function nameValue(value: string) { + let code: string; + switch (value) { + case "ชื่อสินค้าและบริการ": + code = "name"; + break; + case "ระยะเวลาดำเนินการ": + code = "process"; + break; + case "ประเภทค่าใช้จ่าย": + code = "expenseType"; + break; + case "รายละเอียด": + code = "detail"; + break; + case "หมายเหตุ": + code = "remark"; + break; + case "ใช้งานร่วมกัน": + code = "shared"; + break; + case "คำนวณภาษีราคาขาย": + code = "calcVat"; + break; + case "รวม VAT ราคาขาย": + code = "vatIncluded"; + break; + case "ราคาต่อหน่วย (บาท) ราคาขาย": + code = "price"; + break; + case "คำนวณภาษีราคาตัวแทน": + code = "agentPriceCalcVat"; + break; + case "รวม VAT ราคาตัวแทน": + code = "agentPriceVatIncluded"; + break; + case "ราคาต่อหน่วย (บาท) ราคาตัวแทน": + code = "agentPrice"; + break; + case "คำนวณภาษีราคาดำเนินการ": + code = "serviceChargeCalcVat"; + break; + case "รวม VAT ราคาดำเนินการ": + code = "serviceChargeVatIncluded"; + break; + case "ราคาต่อหน่วย (บาท) ราคาดำเนินการ": + code = "serviceCharge"; + break; + default: + code = "code"; + break; + } + return code; +} From e42b772dcf5eb60e8b5e386d8f7bfa24b01033d3 Mon Sep 17 00:00:00 2001 From: Kanjana Date: Fri, 18 Apr 2025 16:23:02 +0700 Subject: [PATCH 015/167] use await Promise.all in uploadedFile Product --- src/controllers/04-product-controller.ts | 53 ++++++++++++------------ 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/src/controllers/04-product-controller.ts b/src/controllers/04-product-controller.ts index 146f6d1..1035beb 100644 --- a/src/controllers/04-product-controller.ts +++ b/src/controllers/04-product-controller.ts @@ -518,34 +518,35 @@ export class ProductController extends Controller { async (tx) => { const branch = productGroup.registeredBranch; const company = (branch.headOffice || branch).code; - console.log(branch, company); - for (const item of data) { - const dataDuplicate = productSameName.some( - (v) => v.code.slice(0, -3) === item.code.toUpperCase() && v.name === item.name, - ); - if (!dataDuplicate) { - const last = await tx.runningNo.upsert({ - where: { - key: `PRODUCT_${company}_${item.code.toLocaleUpperCase()}`, - }, - create: { - key: `PRODUCT_${company}_${item.code.toLocaleUpperCase()}`, - value: 1, - }, - update: { value: { increment: 1 } }, - }); + await Promise.all( + data.map(async (item) => { + const dataDuplicate = productSameName.some( + (v) => v.code.slice(0, -3) === item.code.toUpperCase() && v.name === item.name, + ); - dataProduct.push({ - ...item, - code: `${item.code.toLocaleUpperCase()}${last.value.toString().padStart(3, "0")}`, - createdByUserId: req.user.sub, - updatedByUserId: req.user.sub, - productGroupId: productGroupId, - }); - } - } - console.log("dataProduct", dataProduct); + if (!dataDuplicate) { + const last = await tx.runningNo.upsert({ + where: { + key: `PRODUCT_${company}_${item.code.toLocaleUpperCase()}`, + }, + create: { + key: `PRODUCT_${company}_${item.code.toLocaleUpperCase()}`, + value: 1, + }, + update: { value: { increment: 1 } }, + }); + + dataProduct.push({ + ...item, + code: `${item.code.toLocaleUpperCase()}${last.value.toString().padStart(3, "0")}`, + createdByUserId: req.user.sub, + updatedByUserId: req.user.sub, + productGroupId: productGroupId, + }); + } + }), + ); return await prisma.product.createManyAndReturn({ data: dataProduct, From a25968786d46fc98608bd5858132411bcb7adde8 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Fri, 18 Apr 2025 16:32:05 +0700 Subject: [PATCH 016/167] chore: update lock file --- pnpm-lock.yaml | 254 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 254 insertions(+) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bb7ddd5..9ad8f82 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -44,6 +44,9 @@ importers: dotenv: specifier: ^16.4.7 version: 16.4.7 + exceljs: + specifier: ^4.4.0 + version: 4.4.0 express: specifier: ^4.21.2 version: 4.21.2 @@ -62,6 +65,9 @@ importers: morgan: specifier: ^1.10.0 version: 1.10.0 + multer: + specifier: ^1.4.5-lts.2 + version: 1.4.5-lts.2 nodemailer: specifier: ^6.10.0 version: 6.10.0 @@ -99,6 +105,9 @@ importers: '@types/morgan': specifier: ^1.9.9 version: 1.9.9 + '@types/multer': + specifier: ^1.4.12 + version: 1.4.12 '@types/node': specifier: ^20.17.10 version: 20.17.10 @@ -177,6 +186,12 @@ packages: resolution: {integrity: sha512-jasKNQeOb1vNf9aEYg+8zXmetaFjApDTSCC4QTl6aTixvyiRiSLcCiB8P6Q0lY9JIII/BhqNl8WbpFnsKitntw==} engines: {node: '>=18'} + '@fast-csv/format@4.3.5': + resolution: {integrity: sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A==} + + '@fast-csv/parse@4.3.6': + resolution: {integrity: sha512-uRsLYksqpbDmWaSmzvJcuApSEe38+6NQZBUsuAyMZKqHxH0g1wcJgsKUvN3WC8tewaqFjBMMGrkHmC+T7k8LvA==} + '@fast-csv/parse@5.0.2': resolution: {integrity: sha512-gMu1Btmm99TP+wc0tZnlH30E/F1Gw1Tah3oMDBHNPe9W8S68ixVHjt89Wg5lh7d9RuQMtwN+sGl5kxR891+fzw==} @@ -492,6 +507,9 @@ packages: '@types/multer@1.4.12': resolution: {integrity: sha512-pQ2hoqvXiJt2FP9WQVLPRO+AmiIm/ZYkavPlIQnx282u4ZrVdztx0pkh3jjpQt0Kz+YI0YhSG264y08UJKoUQg==} + '@types/node@14.18.63': + resolution: {integrity: sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==} + '@types/node@20.17.10': resolution: {integrity: sha512-/jrvh5h6NXhEauFFexRin69nA0uHJ5gwk4iDivp/DeoEua3uwCUto6PC86IpRITBOs4+6i2I56K5x5b6WYGXHA==} @@ -587,6 +605,9 @@ packages: resolution: {integrity: sha512-v/ShMp57iBnBp4lDgV8Jx3d3Q5/Hac25FWmQ98eMahUiHPXcvwIMKJD0hBIgclm/FCG+LwPkAKtkRO1O/W0YGg==} hasBin: true + append-field@1.0.0: + resolution: {integrity: sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==} + archiver-utils@2.1.0: resolution: {integrity: sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==} engines: {node: '>= 6'} @@ -682,6 +703,10 @@ packages: resolution: {integrity: sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==} engines: {node: '>= 0.8'} + big-integer@1.6.52: + resolution: {integrity: sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==} + engines: {node: '>=0.6'} + binary-extensions@2.3.0: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} @@ -689,12 +714,18 @@ packages: binary-search@1.3.6: resolution: {integrity: sha512-nbE1WxOTTrUWIfsfZ4aHGYu5DOuNkbxGokjV6Z2kxfJK3uaAb8zNK1muzOeipoLHZjInT4Br88BHpzevc681xA==} + binary@0.3.0: + resolution: {integrity: sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==} + bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} block-stream2@2.1.0: resolution: {integrity: sha512-suhjmLI57Ewpmq00qaygS8UgEq2ly2PCItenIyhMqVjo4t4pGzqMvfgJuX8iWTeSDdfSSqS6j38fL4ToNL7Pfg==} + bluebird@3.4.7: + resolution: {integrity: sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==} + bn.js@4.12.1: resolution: {integrity: sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==} @@ -725,9 +756,24 @@ packages: resolution: {integrity: sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==} engines: {node: '>=8.0.0'} + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + buffer-indexof-polyfill@1.0.2: + resolution: {integrity: sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==} + engines: {node: '>=0.10'} + buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + buffers@0.1.1: + resolution: {integrity: sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==} + engines: {node: '>=0.2.0'} + + busboy@1.6.0: + resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} + engines: {node: '>=10.16.0'} + bytes@3.1.2: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} @@ -744,6 +790,9 @@ packages: resolution: {integrity: sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==} engines: {node: '>= 0.4'} + chainsaw@0.1.0: + resolution: {integrity: sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==} + chalk-template@0.4.0: resolution: {integrity: sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==} engines: {node: '>=12'} @@ -821,6 +870,10 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + concat-stream@1.6.2: + resolution: {integrity: sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==} + engines: {'0': node >= 0.8} + console-log-level@1.4.1: resolution: {integrity: sha512-VZzbIORbP+PPcN/gg3DXClTLPLg5Slwd5fL2MIc+o1qZ4BXBvWyc6QxPk6T/Mkr6IVjRpoAGf32XxP3ZWMVRcQ==} @@ -985,6 +1038,9 @@ packages: resolution: {integrity: sha512-9+Sj30DIu+4KvHqMfLUGLFYL2PkURSYMVXJyXe92nFRvlYq5hBjLEhblKB+vkd/WVlUYMWigiY07T91Fkk0+4A==} engines: {node: '>= 0.4'} + duplexer2@0.1.4: + resolution: {integrity: sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==} + eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} @@ -1087,6 +1143,10 @@ packages: eventemitter3@5.0.1: resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} + exceljs@4.4.0: + resolution: {integrity: sha512-XctvKaEMaj1Ii9oDOqbW/6e1gXknSY4g/aLCDicOXqBE4M0nRWkUu0PTp++UPNzoFY12BNHMfs/VadKIS6llvg==} + engines: {node: '>=8.3.0'} + execa@5.1.1: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} @@ -1095,6 +1155,10 @@ packages: resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==} engines: {node: '>= 0.10.0'} + fast-csv@4.3.6: + resolution: {integrity: sha512-2RNSpuwwsJGP0frGsOmTb9oUF+VkFSM4SyLTDgwf2ciHWTarN0lQTC+F2f/t5J9QjW+c65VFIAAu85GsvMIusw==} + engines: {node: '>=10.0.0'} + fast-glob@3.3.2: resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} engines: {node: '>=8.6.0'} @@ -1203,6 +1267,11 @@ packages: engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] + fstream@1.0.12: + resolution: {integrity: sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==} + engines: {node: '>=0.6'} + deprecated: This package is no longer supported. + function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} @@ -1623,6 +1692,9 @@ packages: lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + listenercount@1.0.1: + resolution: {integrity: sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==} + locate-path@5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} @@ -1649,6 +1721,13 @@ packages: lodash.groupby@4.6.0: resolution: {integrity: sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==} + lodash.isboolean@3.0.3: + resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} + + lodash.isequal@4.5.0: + resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} + deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead. + lodash.isfunction@3.0.9: resolution: {integrity: sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==} @@ -1796,6 +1875,10 @@ packages: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} + mkdirp@0.5.6: + resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} + hasBin: true + mnemonist@0.39.8: resolution: {integrity: sha512-vyWo2K3fjrUw8YeeZ1zF0fy6Mu59RHokURlld8ymdUPjMlD9EC9ov1/YPqTgqRvUN9nTr3Gqfz29LYAmu0PHPQ==} @@ -1818,6 +1901,10 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + multer@1.4.5-lts.2: + resolution: {integrity: sha512-VzGiVigcG9zUAoCNU+xShztrlr1auZOlurXynNvO9GiWD1/mTBbUljOKY+qMeazBqXgRnjzeEgJI/wyjJUHg9A==} + engines: {node: '>= 6.0.0'} + negotiator@0.6.3: resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} engines: {node: '>= 0.6'} @@ -2190,6 +2277,11 @@ packages: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + rimraf@2.7.1: + resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + rimraf@3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} deprecated: Rimraf versions prior to v4 are no longer supported @@ -2225,6 +2317,10 @@ packages: sax@1.4.1: resolution: {integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==} + saxes@5.0.1: + resolution: {integrity: sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==} + engines: {node: '>=10'} + secure-json-parse@2.7.0: resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==} @@ -2372,6 +2468,10 @@ packages: resolution: {integrity: sha512-LsvisgE3iThboRqA+XLmtnY9ktPLVPOj3zZxXMhlezeCcAh0RhomquXJgB8H+lb/RR/pPcbNVGHVKFUwjpoRtw==} engines: {node: '>= 0.8'} + streamsearch@1.1.0: + resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} + engines: {node: '>=10.0.0'} + strict-uri-encode@2.0.0: resolution: {integrity: sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==} engines: {node: '>=4'} @@ -2498,6 +2598,9 @@ packages: tr46@1.0.1: resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} + traverse@0.3.9: + resolution: {integrity: sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==} + triple-beam@1.4.1: resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==} engines: {node: '>= 14.0.0'} @@ -2567,6 +2670,9 @@ packages: resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} engines: {node: '>= 0.4'} + typedarray@0.0.6: + resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} + typescript@5.7.2: resolution: {integrity: sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==} engines: {node: '>=14.17'} @@ -2616,6 +2722,9 @@ packages: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} + unzipper@0.10.14: + resolution: {integrity: sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g==} + util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -2626,6 +2735,10 @@ packages: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} engines: {node: '>= 0.4.0'} + uuid@8.3.2: + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + hasBin: true + uuid@9.0.0: resolution: {integrity: sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==} hasBin: true @@ -2718,6 +2831,13 @@ packages: resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==} engines: {node: '>=4.0'} + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + + xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} @@ -2826,6 +2946,25 @@ snapshots: transitivePeerDependencies: - supports-color + '@fast-csv/format@4.3.5': + dependencies: + '@types/node': 14.18.63 + lodash.escaperegexp: 4.1.2 + lodash.isboolean: 3.0.3 + lodash.isequal: 4.5.0 + lodash.isfunction: 3.0.9 + lodash.isnil: 4.0.0 + + '@fast-csv/parse@4.3.6': + dependencies: + '@types/node': 14.18.63 + lodash.escaperegexp: 4.1.2 + lodash.groupby: 4.6.0 + lodash.isfunction: 3.0.9 + lodash.isnil: 4.0.0 + lodash.isundefined: 3.0.1 + lodash.uniq: 4.5.0 + '@fast-csv/parse@5.0.2': dependencies: lodash.escaperegexp: 4.1.2 @@ -3341,6 +3480,8 @@ snapshots: dependencies: '@types/express': 4.17.21 + '@types/node@14.18.63': {} + '@types/node@20.17.10': dependencies: undici-types: 6.19.8 @@ -3440,6 +3581,8 @@ snapshots: json-bignum: 0.0.3 tslib: 2.8.1 + append-field@1.0.0: {} + archiver-utils@2.1.0: dependencies: glob: 7.2.3 @@ -3563,11 +3706,18 @@ snapshots: dependencies: safe-buffer: 5.1.2 + big-integer@1.6.52: {} + binary-extensions@2.3.0: {} binary-search@1.3.6: optional: true + binary@0.3.0: + dependencies: + buffers: 0.1.1 + chainsaw: 0.1.0 + bl@4.1.0: dependencies: buffer: 5.7.1 @@ -3578,6 +3728,8 @@ snapshots: dependencies: readable-stream: 3.6.2 + bluebird@3.4.7: {} + bn.js@4.12.1: {} body-parser@1.20.3: @@ -3621,11 +3773,21 @@ snapshots: buffer-crc32@1.0.0: {} + buffer-from@1.1.2: {} + + buffer-indexof-polyfill@1.0.2: {} + buffer@5.7.1: dependencies: base64-js: 1.5.1 ieee754: 1.2.1 + buffers@0.1.1: {} + + busboy@1.6.0: + dependencies: + streamsearch: 1.1.0 + bytes@3.1.2: {} call-bind-apply-helpers@1.0.1: @@ -3645,6 +3807,10 @@ snapshots: call-bind-apply-helpers: 1.0.1 get-intrinsic: 1.2.6 + chainsaw@0.1.0: + dependencies: + traverse: 0.3.9 + chalk-template@0.4.0: dependencies: chalk: 4.1.2 @@ -3756,6 +3922,13 @@ snapshots: concat-map@0.0.1: {} + concat-stream@1.6.2: + dependencies: + buffer-from: 1.1.2 + inherits: 2.0.4 + readable-stream: 2.3.8 + typedarray: 0.0.6 + console-log-level@1.4.1: optional: true @@ -3899,6 +4072,10 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 + duplexer2@0.1.4: + dependencies: + readable-stream: 2.3.8 + eastasianwidth@0.2.0: {} ecdsa-sig-formatter@1.0.11: @@ -4088,6 +4265,18 @@ snapshots: eventemitter3@5.0.1: {} + exceljs@4.4.0: + dependencies: + archiver: 5.3.2 + dayjs: 1.11.13 + fast-csv: 4.3.6 + jszip: 3.10.1 + readable-stream: 3.6.2 + saxes: 5.0.1 + tmp: 0.2.1 + unzipper: 0.10.14 + uuid: 8.3.2 + execa@5.1.1: dependencies: cross-spawn: 7.0.6 @@ -4136,6 +4325,11 @@ snapshots: transitivePeerDependencies: - supports-color + fast-csv@4.3.6: + dependencies: + '@fast-csv/format': 4.3.5 + '@fast-csv/parse': 4.3.6 + fast-glob@3.3.2: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -4258,6 +4452,13 @@ snapshots: fsevents@2.3.3: optional: true + fstream@1.0.12: + dependencies: + graceful-fs: 4.2.11 + inherits: 2.0.4 + mkdirp: 0.5.6 + rimraf: 2.7.1 + function-bind@1.1.2: {} function.prototype.name@1.1.7: @@ -4693,6 +4894,8 @@ snapshots: lines-and-columns@1.2.4: {} + listenercount@1.0.1: {} + locate-path@5.0.0: dependencies: p-locate: 4.1.0 @@ -4713,6 +4916,10 @@ snapshots: lodash.groupby@4.6.0: {} + lodash.isboolean@3.0.3: {} + + lodash.isequal@4.5.0: {} + lodash.isfunction@3.0.9: {} lodash.isnil@4.0.0: {} @@ -4853,6 +5060,10 @@ snapshots: minipass@7.1.2: {} + mkdirp@0.5.6: + dependencies: + minimist: 1.2.8 + mnemonist@0.39.8: dependencies: obliterator: 2.0.4 @@ -4879,6 +5090,16 @@ snapshots: ms@2.1.3: {} + multer@1.4.5-lts.2: + dependencies: + append-field: 1.0.0 + busboy: 1.6.0 + concat-stream: 1.6.2 + mkdirp: 0.5.6 + object-assign: 4.1.1 + type-is: 1.6.18 + xtend: 4.0.2 + negotiator@0.6.3: {} neo-async@2.6.2: {} @@ -5273,6 +5494,10 @@ snapshots: reusify@1.0.4: {} + rimraf@2.7.1: + dependencies: + glob: 7.2.3 + rimraf@3.0.2: dependencies: glob: 7.2.3 @@ -5307,6 +5532,10 @@ snapshots: sax@1.4.1: {} + saxes@5.0.1: + dependencies: + xmlchars: 2.2.0 + secure-json-parse@2.7.0: {} semver@5.7.2: {} @@ -5478,6 +5707,8 @@ snapshots: stream-to-buffer@0.0.1: {} + streamsearch@1.1.0: {} + strict-uri-encode@2.0.0: {} string-width@4.2.3: @@ -5618,6 +5849,8 @@ snapshots: punycode: 2.3.1 optional: true + traverse@0.3.9: {} + triple-beam@1.4.1: {} ts-deepmerge@7.0.2: {} @@ -5697,6 +5930,8 @@ snapshots: possible-typed-array-names: 1.0.0 reflect.getprototypeof: 1.0.8 + typedarray@0.0.6: {} + typescript@5.7.2: {} typical@4.0.0: {} @@ -5736,6 +5971,19 @@ snapshots: unpipe@1.0.0: {} + unzipper@0.10.14: + dependencies: + big-integer: 1.6.52 + binary: 0.3.0 + bluebird: 3.4.7 + buffer-indexof-polyfill: 1.0.2 + duplexer2: 0.1.4 + fstream: 1.0.12 + graceful-fs: 4.2.11 + listenercount: 1.0.1 + readable-stream: 2.3.8 + setimmediate: 1.0.5 + util-deprecate@1.0.2: {} util@0.12.5: @@ -5748,6 +5996,8 @@ snapshots: utils-merge@1.0.1: {} + uuid@8.3.2: {} + uuid@9.0.0: {} v8-compile-cache-lib@3.0.1: {} @@ -5888,6 +6138,10 @@ snapshots: xmlbuilder@11.0.1: {} + xmlchars@2.2.0: {} + + xtend@4.0.2: {} + y18n@5.0.8: {} yallist@2.1.2: From f98371132a09a480be85020774090283cd4774aa Mon Sep 17 00:00:00 2001 From: Kanjana Date: Mon, 21 Apr 2025 11:07:06 +0700 Subject: [PATCH 017/167] add startDate endDate in instition and receipt , codeProductRecieve in taskOrder --- .../20250418095201_add/migration.sql | 2 ++ .../20250418103300_add/migration.sql | 18 +++++++++++++++ prisma/schema.prisma | 21 ++++++++++++++--- src/controllers/04-institution-controller.ts | 23 +++++++++++++++++-- src/controllers/04-receipt-controller.ts | 4 ++++ src/controllers/05-payment-controller.ts | 2 ++ src/controllers/07-task-controller.ts | 19 +++++++++++++++ 7 files changed, 84 insertions(+), 5 deletions(-) create mode 100644 prisma/migrations/20250418095201_add/migration.sql create mode 100644 prisma/migrations/20250418103300_add/migration.sql diff --git a/prisma/migrations/20250418095201_add/migration.sql b/prisma/migrations/20250418095201_add/migration.sql new file mode 100644 index 0000000..be3e4d0 --- /dev/null +++ b/prisma/migrations/20250418095201_add/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "TaskOrder" ADD COLUMN "codeProductReceived" TEXT; diff --git a/prisma/migrations/20250418103300_add/migration.sql b/prisma/migrations/20250418103300_add/migration.sql new file mode 100644 index 0000000..2e63034 --- /dev/null +++ b/prisma/migrations/20250418103300_add/migration.sql @@ -0,0 +1,18 @@ +-- AlterTable +ALTER TABLE "Institution" ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, +ADD COLUMN "createdByUserId" TEXT, +ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, +ADD COLUMN "updatedByUserId" TEXT; + +-- AlterTable +ALTER TABLE "Payment" ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, +ADD COLUMN "updatedByUserId" TEXT; + +-- AddForeignKey +ALTER TABLE "Institution" ADD CONSTRAINT "Institution_createdByUserId_fkey" FOREIGN KEY ("createdByUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Institution" ADD CONSTRAINT "Institution_updatedByUserId_fkey" FOREIGN KEY ("updatedByUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Payment" ADD CONSTRAINT "Payment_updatedByUserId_fkey" FOREIGN KEY ("updatedByUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index d40957e..f51e7fa 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -484,12 +484,15 @@ model User { flowCreated WorkflowTemplate[] @relation("FlowCreatedByUser") flowUpdated WorkflowTemplate[] @relation("FlowUpdatedByUser") invoiceCreated Invoice[] - paymentCreated Payment[] + paymentCreated Payment[] @relation("PaymentCreatedByUser") + paymentUpdated Payment[] @relation("PaymentUpdatedByUser") notificationReceive Notification[] @relation("NotificationReceiver") notificationRead Notification[] @relation("NotificationRead") notificationDelete Notification[] @relation("NotificationDelete") taskOrderCreated TaskOrder[] @relation("TaskOrderCreatedByUser") creditNoteCreated CreditNote[] @relation("CreditNoteCreatedByUser") + institutionCreated Institution[] @relation("InstitutionCreatedByUser") + institutionUpdated Institution[] @relation("InstitutionUpdatedByUser") requestWorkStepStatus RequestWorkStepStatus[] userTask UserTask[] @@ -1015,6 +1018,13 @@ model Institution { contactEmail String? contactTel String? + createdAt DateTime @default(now()) + createdBy User? @relation(name: "InstitutionCreatedByUser", fields: [createdByUserId], references: [id], onDelete: SetNull) + createdByUserId String? + updatedAt DateTime @default(now()) @updatedAt + updatedBy User? @relation(name: "InstitutionUpdatedByUser", fields: [updatedByUserId], references: [id], onDelete: SetNull) + updatedByUserId String? + bank InstitutionBank[] } @@ -1463,8 +1473,12 @@ model Payment { date DateTime? createdAt DateTime @default(now()) - createdBy User? @relation(fields: [createdByUserId], references: [id], onDelete: SetNull) + createdBy User? @relation(name: "PaymentCreatedByUser", fields: [createdByUserId], references: [id], onDelete: SetNull) createdByUserId String? + + updatedAt DateTime @default(now()) @updatedAt + updatedBy User? @relation(name: "PaymentUpdatedByUser", fields: [updatedByUserId], references: [id], onDelete: SetNull) + updatedByUserId String? } enum RequestDataStatus { @@ -1617,7 +1631,8 @@ model TaskProduct { model TaskOrder { id String @id @default(cuid()) - code String + code String + codeProductReceived String? taskName String taskOrderStatus TaskOrderStatus @default(Pending) diff --git a/src/controllers/04-institution-controller.ts b/src/controllers/04-institution-controller.ts index 76c0fc4..21611a7 100644 --- a/src/controllers/04-institution-controller.ts +++ b/src/controllers/04-institution-controller.ts @@ -17,7 +17,7 @@ import { } from "tsoa"; import prisma from "../db"; import { isUsedError, notFoundError } from "../utils/error"; -import { queryOrNot } from "../utils/relation"; +import { queryOrNot, whereDateQuery } from "../utils/relation"; import { RequestWithUser } from "../interfaces/user"; import { deleteFile, fileLocation, getFile, getPresigned, listFile, setFile } from "../utils/minio"; import HttpError from "../interfaces/http-error"; @@ -108,8 +108,19 @@ export class InstitutionController extends Controller { @Query() status?: Status, @Query() activeOnly?: boolean, @Query() group?: string, + @Query() startDate?: Date, + @Query() endDate?: Date, ) { - return this.getInstitutionListByCriteria(query, page, pageSize, status, activeOnly, group); + return this.getInstitutionListByCriteria( + query, + page, + pageSize, + status, + activeOnly, + group, + startDate, + endDate, + ); } @Post("list") @@ -122,6 +133,8 @@ export class InstitutionController extends Controller { @Query() status?: Status, @Query() activeOnly?: boolean, @Query() group?: string, + @Query() startDate?: Date, + @Query() endDate?: Date, @Body() body?: { group?: string[]; @@ -134,6 +147,7 @@ export class InstitutionController extends Controller { { name: { contains: query, mode: "insensitive" } }, { code: { contains: query, mode: "insensitive" } }, ]), + ...whereDateQuery(startDate, endDate), } satisfies Prisma.InstitutionWhereInput; const [result, total] = await prisma.$transaction([ @@ -178,6 +192,7 @@ export class InstitutionController extends Controller { body: InstitutionPayload & { status?: Status; }, + @Request() req: RequestWithUser, ) { return await prisma.$transaction(async (tx) => { const last = await tx.runningNo.upsert({ @@ -194,6 +209,8 @@ export class InstitutionController extends Controller { return await tx.institution.create({ include: { bank: true, + createdBy: true, + updatedBy: true, }, data: { ...body, @@ -204,6 +221,8 @@ export class InstitutionController extends Controller { data: body.bank ?? [], }, }, + createdByUserId: req.user.sub, + updatedByUserId: req.user.sub, }, }); }); diff --git a/src/controllers/04-receipt-controller.ts b/src/controllers/04-receipt-controller.ts index caad04b..55a52c6 100644 --- a/src/controllers/04-receipt-controller.ts +++ b/src/controllers/04-receipt-controller.ts @@ -4,6 +4,7 @@ import { Prisma } from "@prisma/client"; import { notFoundError } from "../utils/error"; import { RequestWithUser } from "../interfaces/user"; import { createPermCondition } from "../services/permission"; +import { whereDateQuery } from "../utils/relation"; const permissionCondCompany = createPermCondition((_) => true); @@ -21,6 +22,8 @@ export class ReceiptController extends Controller { @Query() quotationId?: string, @Query() debitNoteId?: string, @Query() debitNoteOnly?: boolean, + @Query() startDate?: Date, + @Query() endDate?: Date, ) { const where: Prisma.PaymentWhereInput = { paymentStatus: "PaymentSuccess", @@ -33,6 +36,7 @@ export class ReceiptController extends Controller { }, }, }, + ...whereDateQuery(startDate, endDate), }; const [result, total] = await prisma.$transaction([ diff --git a/src/controllers/05-payment-controller.ts b/src/controllers/05-payment-controller.ts index a4b7339..e5bf0a4 100644 --- a/src/controllers/05-payment-controller.ts +++ b/src/controllers/05-payment-controller.ts @@ -105,6 +105,7 @@ export class QuotationPayment extends Controller { async updatePayment( @Path() paymentId: string, @Body() body: { amount?: number; date?: Date; paymentStatus?: PaymentStatus }, + @Request() req: RequestWithUser, ) { const record = await prisma.payment.findUnique({ where: { id: paymentId }, @@ -164,6 +165,7 @@ export class QuotationPayment extends Controller { code: lastReceipt ? `RE${year}${month}${lastReceipt.value.toString().padStart(6, "0")}` : undefined, + updatedByUserId: req.user.sub, }, }); diff --git a/src/controllers/07-task-controller.ts b/src/controllers/07-task-controller.ts index 58acbbe..92fc696 100644 --- a/src/controllers/07-task-controller.ts +++ b/src/controllers/07-task-controller.ts @@ -697,12 +697,31 @@ export class TaskActionController extends Controller { if (!record) throw notFoundError("Task Order"); await prisma.$transaction(async (tx) => { + const last = await tx.runningNo.upsert({ + where: { + key: "TASK_RI", + }, + create: { + key: "TASK_RI", + value: 1, + }, + update: { + value: { increment: 1 }, + }, + }); + const current = new Date(); + const year = `${current.getFullYear()}`.padStart(2, "0"); + const month = `${current.getMonth() + 1}`.padStart(2, "0"); + + const code = `RI${year}${month}${last.value.toString().padStart(6, "0")}`; + await Promise.all([ tx.taskOrder.update({ where: { id: taskOrderId }, data: { urgent: false, taskOrderStatus: TaskOrderStatus.Complete, + codeProductReceived: code, userTask: { updateMany: { where: { taskOrderId }, From 209ef05d3de74cb5098a2f874c58fa0aafa3eb24 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Tue, 22 Apr 2025 09:37:10 +0700 Subject: [PATCH 018/167] chore: change endpoint --- src/controllers/04-product-controller.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/controllers/04-product-controller.ts b/src/controllers/04-product-controller.ts index 1035beb..ed4d401 100644 --- a/src/controllers/04-product-controller.ts +++ b/src/controllers/04-product-controller.ts @@ -450,7 +450,7 @@ export class ProductController extends Controller { }); } - @Post("uploadedFile") + @Post("import-product") @Security("keycloak", MANAGE_ROLES) async importProduct( @Request() req: RequestWithUser, @@ -466,7 +466,7 @@ export class ProductController extends Controller { }); let dataName: string[] = []; - const data = await dataFile.map((item: any) => { + const data = dataFile.map((item: any) => { dataName.push(item.name); return { ...item, From 7bd1f57c329bbdac6f2f484848873fc3ae175d3a Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Tue, 22 Apr 2025 09:47:17 +0700 Subject: [PATCH 019/167] feat: change employee name requirement --- prisma/schema.prisma | 2 +- src/controllers/03-employee-controller.ts | 2 +- src/controllers/05-quotation-controller.ts | 8 ++++---- src/controllers/09-debit-note-controller.ts | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index f51e7fa..ab8414b 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -777,7 +777,7 @@ model Employee { middleName String? middleNameEN String? lastName String? - lastNameEN String + lastNameEN String? dateOfBirth DateTime? @db.Date gender String diff --git a/src/controllers/03-employee-controller.ts b/src/controllers/03-employee-controller.ts index 9699b6c..fe4c57b 100644 --- a/src/controllers/03-employee-controller.ts +++ b/src/controllers/03-employee-controller.ts @@ -117,7 +117,7 @@ type EmployeeUpdate = { middleName?: string | null; middleNameEN?: string | null; lastName?: string; - lastNameEN: string; + lastNameEN?: string; addressEN?: string; address?: string; diff --git a/src/controllers/05-quotation-controller.ts b/src/controllers/05-quotation-controller.ts index b454db3..8d53973 100644 --- a/src/controllers/05-quotation-controller.ts +++ b/src/controllers/05-quotation-controller.ts @@ -61,7 +61,7 @@ type QuotationCreate = { middleName?: string; middleNameEN?: string; lastName: string; - lastNameEN: string; + lastNameEN?: string; } )[]; @@ -114,12 +114,12 @@ type QuotationUpdate = { nationality: string; namePrefix?: string; - firstName: string; + firstName?: string; firstNameEN: string; middleName?: string; middleNameEN?: string; - lastName: string; - lastNameEN: string; + lastName?: string; + lastNameEN?: string; } )[]; diff --git a/src/controllers/09-debit-note-controller.ts b/src/controllers/09-debit-note-controller.ts index 8ff5d94..5d6307d 100644 --- a/src/controllers/09-debit-note-controller.ts +++ b/src/controllers/09-debit-note-controller.ts @@ -112,12 +112,12 @@ type DebitNoteUpdate = { gender: string; nationality: string; namePrefix?: string; - firstName: string; + firstName?: string; firstNameEN: string; middleName?: string; middleNameEN?: string; - lastName: string; - lastNameEN: string; + lastName?: string; + lastNameEN?: string; } )[]; From 35ec6cc061a4075b4e383d1103627256d6d57dd7 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Tue, 22 Apr 2025 09:47:20 +0700 Subject: [PATCH 020/167] chore: migration --- .../migration.sql | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 prisma/migrations/20250422024534_remove_lastname_requirement_employee/migration.sql diff --git a/prisma/migrations/20250422024534_remove_lastname_requirement_employee/migration.sql b/prisma/migrations/20250422024534_remove_lastname_requirement_employee/migration.sql new file mode 100644 index 0000000..bd03e24 --- /dev/null +++ b/prisma/migrations/20250422024534_remove_lastname_requirement_employee/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Employee" ALTER COLUMN "lastNameEN" DROP NOT NULL; From 40e5f495e52e915d03a72d18d6e1b2b5da15c5cc Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Tue, 22 Apr 2025 11:20:11 +0700 Subject: [PATCH 021/167] fix: do not check global name conflict --- src/controllers/04-product-controller.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/controllers/04-product-controller.ts b/src/controllers/04-product-controller.ts index ed4d401..9e3ba30 100644 --- a/src/controllers/04-product-controller.ts +++ b/src/controllers/04-product-controller.ts @@ -500,6 +500,7 @@ export class ProductController extends Controller { prisma.product.findMany({ where: { productGroup: { + id: productGroupId, registeredBranch: { OR: permissionCondCompany(req.user), }, From 027326a9e437bdc5019f07ae9365cacaf27113bf Mon Sep 17 00:00:00 2001 From: Kanjana Date: Tue, 22 Apr 2025 11:23:12 +0700 Subject: [PATCH 022/167] add check point price --- src/controllers/04-product-controller.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/controllers/04-product-controller.ts b/src/controllers/04-product-controller.ts index ed4d401..69b1848 100644 --- a/src/controllers/04-product-controller.ts +++ b/src/controllers/04-product-controller.ts @@ -477,10 +477,13 @@ export class ProductController extends Controller { ? "serviceFee" : "processingFee", shared: item.shared === "ใช่" ? true : false, + price: +parseFloat(item.price.replace(",", "")).toFixed(6), calcVat: item.calcVat === "ใช่" ? true : false, vatIncluded: item.vatIncluded === "รวม" ? true : false, + agentPrice: +parseFloat(item.agentPrice.replace(",", "")).toFixed(6), agentPriceCalcVat: item.agentPriceCalcVat === "ใช่" ? true : false, agentPriceVatIncluded: item.agentPriceVatIncluded === "รวม" ? true : false, + serviceCharge: +parseFloat(item.serviceCharge.replace(",", "")).toFixed(6), serviceChargeCalcVat: item.serviceChargeCalcVat === "ใช่" ? true : false, serviceChargeVatIncluded: item.serviceChargeVatIncluded === "รวม" ? true : false, }; From 94c7de89eb7c5cbae8a20655673ea8dceefe4979 Mon Sep 17 00:00:00 2001 From: Kanjana Date: Tue, 22 Apr 2025 14:02:36 +0700 Subject: [PATCH 023/167] add group from keycloak --- src/controllers/00-keycloak-controller.ts | 10 +++- src/controllers/02-user-controller.ts | 15 +++++ src/services/keycloak.ts | 68 +++++++++++++++++++++++ 3 files changed, 92 insertions(+), 1 deletion(-) diff --git a/src/controllers/00-keycloak-controller.ts b/src/controllers/00-keycloak-controller.ts index 87ca8d0..e17331f 100644 --- a/src/controllers/00-keycloak-controller.ts +++ b/src/controllers/00-keycloak-controller.ts @@ -1,5 +1,5 @@ import { Body, Controller, Delete, Get, Path, Post, Route, Security, Tags } from "tsoa"; -import { addUserRoles, listRole, removeUserRoles } from "../services/keycloak"; +import { addUserRoles, getGroup, listRole, removeUserRoles } from "../services/keycloak"; @Route("api/v1/keycloak") @Tags("Single-Sign On") @@ -44,4 +44,12 @@ export class KeycloakController extends Controller { ); if (!result) throw new Error("Failed. Cannot remove user's role."); } + + @Get("group") + async getGroup() { + const group = await getGroup(); + if (!Array.isArray(group)) throw new Error("Failed. Cannot get group(s) data from the server."); + + return group; + } } diff --git a/src/controllers/02-user-controller.ts b/src/controllers/02-user-controller.ts index 20848be..f11255b 100644 --- a/src/controllers/02-user-controller.ts +++ b/src/controllers/02-user-controller.ts @@ -27,6 +27,7 @@ import { listRole, getUserRoles, removeUserRoles, + getGroupUser, } from "../services/keycloak"; import { isSystem } from "../utils/keycloak"; import { @@ -947,3 +948,17 @@ export class UserSignatureController extends Controller { await deleteFile(fileLocation.user.signature(userId)); } } + +@Route("api/v1/user/{userId}/group") +@Tags("User") +@Security("keycloak") +export class UserGroupController extends Controller { + @Get() + async getUserGroup(@Path() userId: string) { + const groupUser = await getGroupUser(userId); + if (!Array.isArray(groupUser)) + throw new Error("Failed. Cannot get user group(s) data from the server."); + + return groupUser; + } +} diff --git a/src/services/keycloak.ts b/src/services/keycloak.ts index db2d15d..3919b42 100644 --- a/src/services/keycloak.ts +++ b/src/services/keycloak.ts @@ -346,6 +346,74 @@ export async function removeUserRoles(userId: string, roles: { id: string; name: return true; } +export async function getGroup() { + const res = await fetch(`${KC_URL}/admin/realms/${KC_REALM}/groups`, { + headers: { + authorization: `Bearer ${await getToken()}`, + "content-type": `application/json`, + }, + method: "GET", + }); + + const dataMainGroup = await res.json(); + + const fetchSubGroups = async (group: any) => { + const resSub = await fetch(`${KC_URL}/admin/realms/${KC_REALM}/groups/${group.id}/children`, { + headers: { + authorization: `Bearer ${await getToken()}`, + "content-type": `application/json`, + }, + method: "GET", + }); + + const dataSubGroup = await resSub.json(); + let fullSubGroup = await Promise.all( + dataSubGroup.map(async (subGroupsData: any) => { + if (subGroupsData.subGroupCount > 0) { + return await fetchSubGroups(subGroupsData); + } else { + return { + id: subGroupsData.id, + name: subGroupsData.name, + path: subGroupsData.path, + subGroupCount: subGroupsData.subGroupCount, + subGroups: [], + }; + } + }), + ); + return { + id: group.id, + name: group.name, + path: group.path, + subGroupCount: group.subGroupCount, + subGroups: fullSubGroup, + }; + }; + + const fullMainGroup = await Promise.all(dataMainGroup.map(fetchSubGroups)); + return fullMainGroup; +} + +export async function getGroupUser(userId: string) { + const res = await fetch(`${KC_URL}/admin/realms/${KC_REALM}/users/${userId}/groups`, { + headers: { + authorization: `Bearer ${await getToken()}`, + "content-type": `application/json`, + }, + method: "GET", + }); + + const data = await res.json(); + return data.map((item: any) => { + return { + id: item.id, + name: item.name, + path: item.path, + }; + }); +} + export default { createUser, listRole, From 8b26f91dbae6941ab72a953cd9a6d41dcb6080a9 Mon Sep 17 00:00:00 2001 From: Kanjana Date: Tue, 22 Apr 2025 15:12:24 +0700 Subject: [PATCH 024/167] getGroup --- src/services/keycloak.ts | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/src/services/keycloak.ts b/src/services/keycloak.ts index 3919b42..3875b18 100644 --- a/src/services/keycloak.ts +++ b/src/services/keycloak.ts @@ -347,7 +347,7 @@ export async function removeUserRoles(userId: string, roles: { id: string; name: } export async function getGroup() { - const res = await fetch(`${KC_URL}/admin/realms/${KC_REALM}/groups`, { + const res = await fetch(`${KC_URL}/admin/realms/${KC_REALM}/groups?q`, { headers: { authorization: `Bearer ${await getToken()}`, "content-type": `application/json`, @@ -356,20 +356,10 @@ export async function getGroup() { }); const dataMainGroup = await res.json(); - const fetchSubGroups = async (group: any) => { - const resSub = await fetch(`${KC_URL}/admin/realms/${KC_REALM}/groups/${group.id}/children`, { - headers: { - authorization: `Bearer ${await getToken()}`, - "content-type": `application/json`, - }, - method: "GET", - }); - - const dataSubGroup = await resSub.json(); let fullSubGroup = await Promise.all( - dataSubGroup.map(async (subGroupsData: any) => { - if (subGroupsData.subGroupCount > 0) { + group.subGroups.map(async (subGroupsData: any) => { + if (group.subGroupCount > 0) { return await fetchSubGroups(subGroupsData); } else { return { From 3193403f9017936cee118ac431c304c49d32f493 Mon Sep 17 00:00:00 2001 From: Kanjana Date: Tue, 22 Apr 2025 16:27:13 +0700 Subject: [PATCH 025/167] check price where null --- src/controllers/04-product-controller.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/controllers/04-product-controller.ts b/src/controllers/04-product-controller.ts index ccb0b6a..dd17c5e 100644 --- a/src/controllers/04-product-controller.ts +++ b/src/controllers/04-product-controller.ts @@ -477,13 +477,15 @@ export class ProductController extends Controller { ? "serviceFee" : "processingFee", shared: item.shared === "ใช่" ? true : false, - price: +parseFloat(item.price.replace(",", "")).toFixed(6), + price: item?.price ? +parseFloat(item.price.replace(",", "")).toFixed(6) : 0, calcVat: item.calcVat === "ใช่" ? true : false, vatIncluded: item.vatIncluded === "รวม" ? true : false, - agentPrice: +parseFloat(item.agentPrice.replace(",", "")).toFixed(6), + agentPrice: item?.agentPrice ? +parseFloat(item.agentPrice.replace(",", "")).toFixed(6) : 0, agentPriceCalcVat: item.agentPriceCalcVat === "ใช่" ? true : false, agentPriceVatIncluded: item.agentPriceVatIncluded === "รวม" ? true : false, - serviceCharge: +parseFloat(item.serviceCharge.replace(",", "")).toFixed(6), + serviceCharge: item?.serviceCharge + ? +parseFloat(item.serviceCharge.replace(",", "")).toFixed(6) + : 0, serviceChargeCalcVat: item.serviceChargeCalcVat === "ใช่" ? true : false, serviceChargeVatIncluded: item.serviceChargeVatIncluded === "รวม" ? true : false, }; From 601deffce404878b607f620727c4d69741f0c3ba Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 23 Apr 2025 09:01:10 +0700 Subject: [PATCH 026/167] feat: check for type before try parse field --- src/controllers/04-product-controller.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/controllers/04-product-controller.ts b/src/controllers/04-product-controller.ts index dd17c5e..80b7c71 100644 --- a/src/controllers/04-product-controller.ts +++ b/src/controllers/04-product-controller.ts @@ -477,15 +477,22 @@ export class ProductController extends Controller { ? "serviceFee" : "processingFee", shared: item.shared === "ใช่" ? true : false, - price: item?.price ? +parseFloat(item.price.replace(",", "")).toFixed(6) : 0, + price: + typeof item.price === "number" + ? item.price + : +parseFloat(item.price.replace(",", "")).toFixed(6), calcVat: item.calcVat === "ใช่" ? true : false, vatIncluded: item.vatIncluded === "รวม" ? true : false, - agentPrice: item?.agentPrice ? +parseFloat(item.agentPrice.replace(",", "")).toFixed(6) : 0, + agentPrice: + typeof item.agentPrice === "number" + ? item.agentPrice + : +parseFloat(item.agentPrice.replace(",", "")).toFixed(6), agentPriceCalcVat: item.agentPriceCalcVat === "ใช่" ? true : false, agentPriceVatIncluded: item.agentPriceVatIncluded === "รวม" ? true : false, - serviceCharge: item?.serviceCharge - ? +parseFloat(item.serviceCharge.replace(",", "")).toFixed(6) - : 0, + serviceCharge: + typeof item.serviceCharge === "number" + ? item.serviceCharge + : +parseFloat(item.serviceCharge.replace(",", "")).toFixed(6), serviceChargeCalcVat: item.serviceChargeCalcVat === "ใช่" ? true : false, serviceChargeVatIncluded: item.serviceChargeVatIncluded === "รวม" ? true : false, }; From d15aa488c15d02af98f6aed3c6a1816993698e34 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 23 Apr 2025 09:03:03 +0700 Subject: [PATCH 027/167] feat: fallback to 0 if empty --- src/controllers/04-product-controller.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/controllers/04-product-controller.ts b/src/controllers/04-product-controller.ts index 80b7c71..3f2ff4e 100644 --- a/src/controllers/04-product-controller.ts +++ b/src/controllers/04-product-controller.ts @@ -480,19 +480,19 @@ export class ProductController extends Controller { price: typeof item.price === "number" ? item.price - : +parseFloat(item.price.replace(",", "")).toFixed(6), + : +parseFloat(item.price?.replace(",", "") || "0").toFixed(6), calcVat: item.calcVat === "ใช่" ? true : false, vatIncluded: item.vatIncluded === "รวม" ? true : false, agentPrice: typeof item.agentPrice === "number" ? item.agentPrice - : +parseFloat(item.agentPrice.replace(",", "")).toFixed(6), + : +parseFloat(item.agentPrice?.replace(",", "") || "0").toFixed(6), agentPriceCalcVat: item.agentPriceCalcVat === "ใช่" ? true : false, agentPriceVatIncluded: item.agentPriceVatIncluded === "รวม" ? true : false, serviceCharge: typeof item.serviceCharge === "number" ? item.serviceCharge - : +parseFloat(item.serviceCharge.replace(",", "")).toFixed(6), + : +parseFloat(item.serviceCharge?.replace(",", "") || "0").toFixed(6), serviceChargeCalcVat: item.serviceChargeCalcVat === "ใช่" ? true : false, serviceChargeVatIncluded: item.serviceChargeVatIncluded === "รวม" ? true : false, }; From 1d6224da73977f056390d05fea24f98869d27cfc Mon Sep 17 00:00:00 2001 From: Kanjana Date: Thu, 24 Apr 2025 10:41:22 +0700 Subject: [PATCH 028/167] add query in keycloak --- src/controllers/00-keycloak-controller.ts | 7 ++++--- src/services/keycloak.ts | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/controllers/00-keycloak-controller.ts b/src/controllers/00-keycloak-controller.ts index e17331f..1d5d9b9 100644 --- a/src/controllers/00-keycloak-controller.ts +++ b/src/controllers/00-keycloak-controller.ts @@ -1,4 +1,4 @@ -import { Body, Controller, Delete, Get, Path, Post, Route, Security, Tags } from "tsoa"; +import { Body, Controller, Delete, Get, Path, Post, Query, Route, Security, Tags } from "tsoa"; import { addUserRoles, getGroup, listRole, removeUserRoles } from "../services/keycloak"; @Route("api/v1/keycloak") @@ -46,8 +46,9 @@ export class KeycloakController extends Controller { } @Get("group") - async getGroup() { - const group = await getGroup(); + async getGroup(@Query() query: string = "") { + const querySearch = query === "" ? "q" : `search=${query}`; + const group = await getGroup(querySearch); if (!Array.isArray(group)) throw new Error("Failed. Cannot get group(s) data from the server."); return group; diff --git a/src/services/keycloak.ts b/src/services/keycloak.ts index 3875b18..5352b2c 100644 --- a/src/services/keycloak.ts +++ b/src/services/keycloak.ts @@ -346,8 +346,8 @@ export async function removeUserRoles(userId: string, roles: { id: string; name: return true; } -export async function getGroup() { - const res = await fetch(`${KC_URL}/admin/realms/${KC_REALM}/groups?q`, { +export async function getGroup(query: string) { + const res = await fetch(`${KC_URL}/admin/realms/${KC_REALM}/groups?${query}`, { headers: { authorization: `Bearer ${await getToken()}`, "content-type": `application/json`, From afadea2d64754ab81c1a85501eb996ef80647e35 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Thu, 24 Apr 2025 10:49:59 +0700 Subject: [PATCH 029/167] fix: search not work as expected --- src/controllers/06-request-list-controller.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/controllers/06-request-list-controller.ts b/src/controllers/06-request-list-controller.ts index 6b5e5f8..42b32ad 100644 --- a/src/controllers/06-request-list-controller.ts +++ b/src/controllers/06-request-list-controller.ts @@ -104,6 +104,8 @@ export class RequestDataController extends Controller { ], }, }, + }, + { employee: { OR: [ { From 109494c6d7619d62a415dd1c13645215b8aab507 Mon Sep 17 00:00:00 2001 From: Kanjana Date: Thu, 24 Apr 2025 11:40:02 +0700 Subject: [PATCH 030/167] add responsibleGroup in step --- prisma/migrations/20250424042834_add/migration.sql | 11 +++++++++++ prisma/schema.prisma | 10 ++++++++++ src/controllers/04-flow-template-controller.ts | 12 ++++++++++++ 3 files changed, 33 insertions(+) create mode 100644 prisma/migrations/20250424042834_add/migration.sql diff --git a/prisma/migrations/20250424042834_add/migration.sql b/prisma/migrations/20250424042834_add/migration.sql new file mode 100644 index 0000000..13999bc --- /dev/null +++ b/prisma/migrations/20250424042834_add/migration.sql @@ -0,0 +1,11 @@ +-- CreateTable +CREATE TABLE "WorkflowTemplateStepGroup" ( + "id" TEXT NOT NULL, + "group" TEXT NOT NULL, + "workflowTemplateStepId" TEXT NOT NULL, + + CONSTRAINT "WorkflowTemplateStepGroup_pkey" PRIMARY KEY ("id") +); + +-- AddForeignKey +ALTER TABLE "WorkflowTemplateStepGroup" ADD CONSTRAINT "WorkflowTemplateStepGroup_workflowTemplateStepId_fkey" FOREIGN KEY ("workflowTemplateStepId") REFERENCES "WorkflowTemplateStep"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index ab8414b..bc100b5 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -1089,6 +1089,15 @@ model WorkflowTemplateStepInstitution { workflowTemplateStepId String } +model WorkflowTemplateStepGroup { + id String @id @default(cuid()) + + group String + + workflowTemplateStep WorkflowTemplateStep @relation(fields: [workflowTemplateStepId], references: [id], onDelete: Cascade) + workflowTemplateStepId String +} + model WorkflowTemplateStep { id String @id @default(cuid()) @@ -1099,6 +1108,7 @@ model WorkflowTemplateStep { value WorkflowTemplateStepValue[] // NOTE: For enum or options type responsiblePerson WorkflowTemplateStepUser[] responsibleInstitution WorkflowTemplateStepInstitution[] + responsibleGroup WorkflowTemplateStepGroup[] messengerByArea Boolean @default(false) attributes Json? diff --git a/src/controllers/04-flow-template-controller.ts b/src/controllers/04-flow-template-controller.ts index 97bd1b9..940fd1d 100644 --- a/src/controllers/04-flow-template-controller.ts +++ b/src/controllers/04-flow-template-controller.ts @@ -37,6 +37,7 @@ type WorkflowPayload = { attributes?: { [key: string]: any }; responsiblePersonId?: string[]; responsibleInstitution?: string[]; + responsibleGroup?: string[]; messengerByArea?: boolean; }[]; registeredBranchId?: string; @@ -89,6 +90,7 @@ export class FlowTemplateController extends Controller { include: { user: true }, }, responsibleInstitution: true, + responsibleGroup: true, }, orderBy: { order: "asc" }, }, @@ -106,6 +108,7 @@ export class FlowTemplateController extends Controller { step: r.step.map((v) => ({ ...v, responsibleInstitution: v.responsibleInstitution.map((institution) => institution.group), + responsibleGroup: v.responsibleGroup.map((group) => group.group), })), })), page, @@ -126,6 +129,7 @@ export class FlowTemplateController extends Controller { include: { user: true }, }, responsibleInstitution: true, + responsibleGroup: true, }, }, }, @@ -140,6 +144,7 @@ export class FlowTemplateController extends Controller { step: record.step.map((v) => ({ ...v, responsibleInstitution: v.responsibleInstitution.map((institution) => institution.group), + responsibleGroup: v.responsibleGroup.map((group) => group.group), })), }; } @@ -215,6 +220,9 @@ export class FlowTemplateController extends Controller { responsibleInstitution: { create: v.responsibleInstitution?.map((group) => ({ group })), }, + responsibleGroup: { + create: v.responsibleGroup?.map((group) => ({ group })), + }, })), }, }, @@ -295,6 +303,10 @@ export class FlowTemplateController extends Controller { deleteMany: {}, create: v.responsibleInstitution?.map((group) => ({ group })), }, + responsibleGroup: { + deleteMany: {}, + create: v.responsibleGroup?.map((group) => ({ group })), + }, }, })), }, From d92e3bc57ded01dcef3029f6372eb5f5f6bf120b Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Thu, 24 Apr 2025 13:55:22 +0700 Subject: [PATCH 031/167] feat: add relation to response --- src/controllers/07-task-controller.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/controllers/07-task-controller.ts b/src/controllers/07-task-controller.ts index 92fc696..5e9977c 100644 --- a/src/controllers/07-task-controller.ts +++ b/src/controllers/07-task-controller.ts @@ -200,6 +200,7 @@ export class TaskController extends Controller { step: { include: { value: true, + responsibleGroup: true, responsiblePerson: { include: { user: true }, }, From 08b9ddd2e1c255d5ee5455d46ff3353dd962d950 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Thu, 24 Apr 2025 14:07:30 +0700 Subject: [PATCH 032/167] feat: reponse relation responsible gropu in request list --- src/controllers/06-request-list-controller.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/controllers/06-request-list-controller.ts b/src/controllers/06-request-list-controller.ts index 42b32ad..3289473 100644 --- a/src/controllers/06-request-list-controller.ts +++ b/src/controllers/06-request-list-controller.ts @@ -849,6 +849,7 @@ export class RequestListController extends Controller { include: { user: true }, }, responsibleInstitution: true, + responsibleGroup: true, }, }, }, From 4dbe89f2903ac69294a10b555989ad10b5c0ddb1 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Thu, 24 Apr 2025 14:09:57 +0700 Subject: [PATCH 033/167] feat: response more relation --- src/controllers/06-request-list-controller.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/controllers/06-request-list-controller.ts b/src/controllers/06-request-list-controller.ts index 3289473..387c1d5 100644 --- a/src/controllers/06-request-list-controller.ts +++ b/src/controllers/06-request-list-controller.ts @@ -174,6 +174,7 @@ export class RequestDataController extends Controller { include: { user: true }, }, responsibleInstitution: true, + responsibleGroup: true, }, }, }, @@ -789,6 +790,7 @@ export class RequestListController extends Controller { include: { user: true }, }, responsibleInstitution: true, + responsibleGroup: true, }, }, }, From 92104c05cb313c5018ecb02ffb44af2ef68a88b9 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Thu, 24 Apr 2025 14:53:04 +0700 Subject: [PATCH 034/167] feat: filter responsible only --- src/controllers/06-request-list-controller.ts | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/controllers/06-request-list-controller.ts b/src/controllers/06-request-list-controller.ts index 387c1d5..ca5398f 100644 --- a/src/controllers/06-request-list-controller.ts +++ b/src/controllers/06-request-list-controller.ts @@ -32,6 +32,7 @@ import { notFoundError } from "../utils/error"; import { deleteFile, fileLocation, getFile, getPresigned, listFile, setFile } from "../utils/minio"; import HttpError from "../interfaces/http-error"; import HttpStatus from "../interfaces/http-status"; +import { getGroupUser } from "../services/keycloak"; // User in company can edit. const permissionCheck = createPermCheck((_) => true); @@ -136,9 +137,24 @@ export class RequestDataController extends Controller { workflow: { step: { some: { - responsiblePerson: { - some: { userId: req.user.sub }, - }, + OR: [ + { + responsiblePerson: { + some: { userId: req.user.sub }, + }, + }, + { + responsibleGroup: { + some: { + group: { + in: await getGroupUser(req.user.sub).then((r) => + r.map(({ name }: { name: string }) => name), + ), + }, + }, + }, + }, + ], }, }, }, From a57e8d939f20c00d921e0f787728b007c84cfee1 Mon Sep 17 00:00:00 2001 From: Kanjana Date: Thu, 24 Apr 2025 15:10:42 +0700 Subject: [PATCH 035/167] async await in getGroup --- src/services/keycloak.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/services/keycloak.ts b/src/services/keycloak.ts index 5352b2c..494e19e 100644 --- a/src/services/keycloak.ts +++ b/src/services/keycloak.ts @@ -358,9 +358,9 @@ export async function getGroup(query: string) { const dataMainGroup = await res.json(); const fetchSubGroups = async (group: any) => { let fullSubGroup = await Promise.all( - group.subGroups.map(async (subGroupsData: any) => { + group.subGroups.map((subGroupsData: any) => { if (group.subGroupCount > 0) { - return await fetchSubGroups(subGroupsData); + return fetchSubGroups(subGroupsData); } else { return { id: subGroupsData.id, From ffb1ce2d40fc581c83473820af2a6729916032b7 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Thu, 24 Apr 2025 17:55:18 +0700 Subject: [PATCH 036/167] feat: allow multiple import nationality for user --- prisma/schema.prisma | 10 +++++++++- src/controllers/02-user-controller.ts | 21 ++++++++++++++++++--- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index bc100b5..ab19cb5 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -366,6 +366,14 @@ enum UserType { AGENCY } +model UserImportNationality { + id String @id @default(cuid()) + name String + + user User @relation(fields: [userId], references: [id]) + userId String +} + model User { id String @id @default(cuid()) @@ -424,7 +432,7 @@ model User { licenseExpireDate DateTime? @db.Date sourceNationality String? - importNationality String? + importNationality UserImportNationality[] trainingPlace String? responsibleArea UserResponsibleArea[] diff --git a/src/controllers/02-user-controller.ts b/src/controllers/02-user-controller.ts index f11255b..2c864e6 100644 --- a/src/controllers/02-user-controller.ts +++ b/src/controllers/02-user-controller.ts @@ -99,7 +99,7 @@ type UserCreate = { licenseIssueDate?: Date | null; licenseExpireDate?: Date | null; sourceNationality?: string | null; - importNationality?: string | null; + importNationality?: string[] | null; trainingPlace?: string | null; responsibleArea?: string[] | null; birthDate?: Date | null; @@ -161,7 +161,7 @@ type UserUpdate = { licenseIssueDate?: Date | null; licenseExpireDate?: Date | null; sourceNationality?: string | null; - importNationality?: string | null; + importNationality?: string[] | null; trainingPlace?: string | null; responsibleArea?: string[] | null; birthDate?: Date | null; @@ -383,6 +383,7 @@ export class UserController extends Controller { prisma.user.findMany({ orderBy: [{ statusOrder: "asc" }, { createdAt: "asc" }], include: { + importNationality: true, responsibleArea: true, province: true, district: true, @@ -401,6 +402,7 @@ export class UserController extends Controller { return { result: result.map((v) => ({ ...v, + importNationality: v.importNationality.map((v) => v.name), responsibleArea: v.responsibleArea.map((v) => v.area), branch: includeBranch ? v.branch.map((a) => a.branch) : undefined, })), @@ -415,6 +417,7 @@ export class UserController extends Controller { async getUserById(@Path() userId: string) { const record = await prisma.user.findFirst({ include: { + importNationality: true, province: true, district: true, subDistrict: true, @@ -426,7 +429,11 @@ export class UserController extends Controller { if (!record) throw notFoundError("User"); - return record; + const { importNationality, ...rest } = record; + + return Object.assign(rest, { + importNationality: importNationality.map((v) => v.name), + }); } @Post() @@ -528,6 +535,9 @@ export class UserController extends Controller { create: rest.responsibleArea.map((v) => ({ area: v })), } : undefined, + importNationality: { + createMany: { data: rest.importNationality?.map((v) => ({ name: v })) || [] }, + }, statusOrder: +(rest.status === "INACTIVE"), username, userRole: role.name, @@ -683,6 +693,7 @@ export class UserController extends Controller { const record = await prisma.user.update({ include: { + importNationality: true, province: true, district: true, subDistrict: true, @@ -697,6 +708,10 @@ export class UserController extends Controller { create: rest.responsibleArea.map((v) => ({ area: v })), } : undefined, + importNationality: { + deleteMany: {}, + createMany: { data: rest.importNationality?.map((v) => ({ name: v })) || [] }, + }, statusOrder: +(rest.status === "INACTIVE"), userRole, province: connectOrDisconnect(provinceId), From 7bc12f00b0dbcd1ab8d35400bd601748c10bfa6d Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Thu, 24 Apr 2025 17:55:21 +0700 Subject: [PATCH 037/167] chore: migration --- .../migration.sql | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 prisma/migrations/20250424094947_change_import_nationality_to_relation/migration.sql diff --git a/prisma/migrations/20250424094947_change_import_nationality_to_relation/migration.sql b/prisma/migrations/20250424094947_change_import_nationality_to_relation/migration.sql new file mode 100644 index 0000000..c961d96 --- /dev/null +++ b/prisma/migrations/20250424094947_change_import_nationality_to_relation/migration.sql @@ -0,0 +1,20 @@ +/* + Warnings: + + - You are about to drop the column `importNationality` on the `User` table. All the data in the column will be lost. + +*/ +-- AlterTable +ALTER TABLE "User" DROP COLUMN "importNationality"; + +-- CreateTable +CREATE TABLE "UserImportNationality" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + "userId" TEXT NOT NULL, + + CONSTRAINT "UserImportNationality_pkey" PRIMARY KEY ("id") +); + +-- AddForeignKey +ALTER TABLE "UserImportNationality" ADD CONSTRAINT "UserImportNationality_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; From 7fe0512a2f3ba5d5bb9ebd533d5b53e5e1b074ea Mon Sep 17 00:00:00 2001 From: Kanjana Date: Fri, 25 Apr 2025 11:59:46 +0700 Subject: [PATCH 038/167] add troubleshooting controller and field otherNationality --- .../20250425040315_add/migration.sql | 2 ++ .../20250425041426_add/migration.sql | 2 ++ prisma/schema.prisma | 32 ++++++++++--------- src/controllers/03-employee-controller.ts | 2 ++ .../03-employee-passport-controller.ts | 1 + src/controllers/05-quotation-controller.ts | 4 +++ src/controllers/09-debit-note-controller.ts | 2 ++ .../10-troubleshooting-controller.ts | 25 +++++++++++++++ 8 files changed, 55 insertions(+), 15 deletions(-) create mode 100644 prisma/migrations/20250425040315_add/migration.sql create mode 100644 prisma/migrations/20250425041426_add/migration.sql create mode 100644 src/controllers/10-troubleshooting-controller.ts diff --git a/prisma/migrations/20250425040315_add/migration.sql b/prisma/migrations/20250425040315_add/migration.sql new file mode 100644 index 0000000..9393a9a --- /dev/null +++ b/prisma/migrations/20250425040315_add/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Employee" ADD COLUMN "otherNationality" TEXT; diff --git a/prisma/migrations/20250425041426_add/migration.sql b/prisma/migrations/20250425041426_add/migration.sql new file mode 100644 index 0000000..a004873 --- /dev/null +++ b/prisma/migrations/20250425041426_add/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "EmployeePassport" ADD COLUMN "otherNationality" TEXT; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index ab19cb5..73be520 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -787,9 +787,10 @@ model Employee { lastName String? lastNameEN String? - dateOfBirth DateTime? @db.Date - gender String - nationality String + dateOfBirth DateTime? @db.Date + gender String + nationality String + otherNationality String? address String? addressEN String? @@ -864,18 +865,19 @@ model EmployeePassport { issuePlace String previousPassportRef String? - workerStatus String? - nationality String? - namePrefix String? - firstName String? - firstNameEN String? - middleName String? - middleNameEN String? - lastName String? - lastNameEN String? - gender String? - birthDate String? - birthCountry String? + workerStatus String? + nationality String? + otherNationality String? + namePrefix String? + firstName String? + firstNameEN String? + middleName String? + middleNameEN String? + lastName String? + lastNameEN String? + gender String? + birthDate String? + birthCountry String? employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade) employeeId String diff --git a/src/controllers/03-employee-controller.ts b/src/controllers/03-employee-controller.ts index fe4c57b..03ab1cc 100644 --- a/src/controllers/03-employee-controller.ts +++ b/src/controllers/03-employee-controller.ts @@ -74,6 +74,7 @@ type EmployeeCreate = { dateOfBirth?: Date | null; gender: string; nationality: string; + otherNationality?: string; namePrefix?: string | null; firstName?: string; @@ -110,6 +111,7 @@ type EmployeeUpdate = { dateOfBirth?: Date; gender?: string; nationality?: string; + otherNationality?: string; namePrefix?: string | null; firstName?: string; diff --git a/src/controllers/03-employee-passport-controller.ts b/src/controllers/03-employee-passport-controller.ts index 8f8c253..5509373 100644 --- a/src/controllers/03-employee-passport-controller.ts +++ b/src/controllers/03-employee-passport-controller.ts @@ -43,6 +43,7 @@ type EmployeePassportPayload = { workerStatus: string; nationality: string; + otherNationality: string; namePrefix?: string | null; firstName: string; firstNameEN: string; diff --git a/src/controllers/05-quotation-controller.ts b/src/controllers/05-quotation-controller.ts index 8d53973..9fddd04 100644 --- a/src/controllers/05-quotation-controller.ts +++ b/src/controllers/05-quotation-controller.ts @@ -55,6 +55,7 @@ type QuotationCreate = { dateOfBirth: Date; gender: string; nationality: string; + otherNationality?: string; namePrefix?: string; firstName: string; firstNameEN: string; @@ -112,6 +113,7 @@ type QuotationUpdate = { dateOfBirth: Date; gender: string; nationality: string; + otherNationality?: string; namePrefix?: string; firstName?: string; @@ -1008,6 +1010,7 @@ export class QuotationActionController extends Controller { dateOfBirth: Date; gender: string; nationality: string; + otherNationality?: string; namePrefix?: string; firstName: string; firstNameEN: string; @@ -1030,6 +1033,7 @@ export class QuotationActionController extends Controller { dateOfBirth: Date; gender: string; nationality: string; + otherNationality?: string; namePrefix?: string; firstName: string; firstNameEN: string; diff --git a/src/controllers/09-debit-note-controller.ts b/src/controllers/09-debit-note-controller.ts index 5d6307d..118575b 100644 --- a/src/controllers/09-debit-note-controller.ts +++ b/src/controllers/09-debit-note-controller.ts @@ -76,6 +76,7 @@ type DebitNoteCreate = { dateOfBirth: Date; gender: string; nationality: string; + otherNationality: string; namePrefix?: string; firstName: string; firstNameEN: string; @@ -111,6 +112,7 @@ type DebitNoteUpdate = { dateOfBirth: Date; gender: string; nationality: string; + otherNationality: string; namePrefix?: string; firstName?: string; firstNameEN: string; diff --git a/src/controllers/10-troubleshooting-controller.ts b/src/controllers/10-troubleshooting-controller.ts new file mode 100644 index 0000000..2c5418a --- /dev/null +++ b/src/controllers/10-troubleshooting-controller.ts @@ -0,0 +1,25 @@ +import express from "express"; +import { Controller, Get, Path, Request, Route } from "tsoa"; +import { getFile } from "../utils/minio"; + +@Route("api/v1/troubleshooting") +export class TroubleshootingController extends Controller { + @Get() + async get(@Request() req: express.Request) { + return req.res?.redirect(await getFile(".troubleshooting/toc.json")); + } + + @Get("{category}/assets/{name}") + async getAsset(@Request() req: express.Request, @Path() category: string, @Path() name: string) { + return req.res?.redirect(await getFile(`.troubleshooting/${category}/assets/${name}`)); + } + + @Get("{category}/page/{page}") + async getContent( + @Request() req: express.Request, + @Path() category: string, + @Path() page: string, + ) { + return req.res?.redirect(await getFile(`.troubleshooting/${category}/${page}.md`)); + } +} From ce42a6dca6a3c1d03df678330256e13008b31db7 Mon Sep 17 00:00:00 2001 From: Kanjana Date: Mon, 28 Apr 2025 13:43:20 +0700 Subject: [PATCH 039/167] add null otherNationality --- src/controllers/03-employee-controller.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/controllers/03-employee-controller.ts b/src/controllers/03-employee-controller.ts index 03ab1cc..95addd7 100644 --- a/src/controllers/03-employee-controller.ts +++ b/src/controllers/03-employee-controller.ts @@ -74,7 +74,7 @@ type EmployeeCreate = { dateOfBirth?: Date | null; gender: string; nationality: string; - otherNationality?: string; + otherNationality?: string | null; namePrefix?: string | null; firstName?: string; @@ -111,7 +111,7 @@ type EmployeeUpdate = { dateOfBirth?: Date; gender?: string; nationality?: string; - otherNationality?: string; + otherNationality?: string | null; namePrefix?: string | null; firstName?: string; From f1a774f3bc1f4675031e240ae9183a1244c35ba5 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Mon, 28 Apr 2025 16:07:13 +0700 Subject: [PATCH 040/167] fix: nullable --- src/controllers/03-employee-passport-controller.ts | 2 +- src/controllers/05-quotation-controller.ts | 8 ++++---- src/controllers/09-debit-note-controller.ts | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/controllers/03-employee-passport-controller.ts b/src/controllers/03-employee-passport-controller.ts index 5509373..12e0b01 100644 --- a/src/controllers/03-employee-passport-controller.ts +++ b/src/controllers/03-employee-passport-controller.ts @@ -43,7 +43,7 @@ type EmployeePassportPayload = { workerStatus: string; nationality: string; - otherNationality: string; + otherNationality?: string; namePrefix?: string | null; firstName: string; firstNameEN: string; diff --git a/src/controllers/05-quotation-controller.ts b/src/controllers/05-quotation-controller.ts index 9fddd04..7d27e2e 100644 --- a/src/controllers/05-quotation-controller.ts +++ b/src/controllers/05-quotation-controller.ts @@ -55,7 +55,7 @@ type QuotationCreate = { dateOfBirth: Date; gender: string; nationality: string; - otherNationality?: string; + otherNationality?: string | null; namePrefix?: string; firstName: string; firstNameEN: string; @@ -113,7 +113,7 @@ type QuotationUpdate = { dateOfBirth: Date; gender: string; nationality: string; - otherNationality?: string; + otherNationality?: string | null; namePrefix?: string; firstName?: string; @@ -1010,7 +1010,7 @@ export class QuotationActionController extends Controller { dateOfBirth: Date; gender: string; nationality: string; - otherNationality?: string; + otherNationality?: string | null; namePrefix?: string; firstName: string; firstNameEN: string; @@ -1033,7 +1033,7 @@ export class QuotationActionController extends Controller { dateOfBirth: Date; gender: string; nationality: string; - otherNationality?: string; + otherNationality?: string | null; namePrefix?: string; firstName: string; firstNameEN: string; diff --git a/src/controllers/09-debit-note-controller.ts b/src/controllers/09-debit-note-controller.ts index 118575b..9c247be 100644 --- a/src/controllers/09-debit-note-controller.ts +++ b/src/controllers/09-debit-note-controller.ts @@ -76,7 +76,7 @@ type DebitNoteCreate = { dateOfBirth: Date; gender: string; nationality: string; - otherNationality: string; + otherNationality?: string | null; namePrefix?: string; firstName: string; firstNameEN: string; @@ -112,7 +112,7 @@ type DebitNoteUpdate = { dateOfBirth: Date; gender: string; nationality: string; - otherNationality: string; + otherNationality?: string | null; namePrefix?: string; firstName?: string; firstNameEN: string; From 07e5f53be2eee4dbac07bda8387d4cceaa67a90a Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 30 Apr 2025 15:00:43 +0700 Subject: [PATCH 041/167] fix: request data notification when new --- src/controllers/05-payment-controller.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/controllers/05-payment-controller.ts b/src/controllers/05-payment-controller.ts index e5bf0a4..711e357 100644 --- a/src/controllers/05-payment-controller.ts +++ b/src/controllers/05-payment-controller.ts @@ -181,6 +181,7 @@ export class QuotationPayment extends Controller { await tx.quotation .update({ + include: { requestData: true }, where: { id: quotation.id }, data: { quotationStatus: @@ -238,6 +239,17 @@ export class QuotationPayment extends Controller { receiverId: res.createdByUserId, }, }); + + if (quotation.quotationStatus === "PaymentPending") { + await prisma.notification.create({ + data: { + title: "รายการคำขอใหม่ / New Request", + detail: "รหัส / code : " + res.requestData.map((v) => v.code).join(", "), + registeredBranchId: res.registeredBranchId, + groupReceiver: { create: { name: "document_checker" } }, + }, + }); + } }); return payment; From 2fa50bd7de34c04b88fa4de06e13f9c54b8954af Mon Sep 17 00:00:00 2001 From: Kanjana Date: Wed, 30 Apr 2025 15:36:13 +0700 Subject: [PATCH 042/167] add value : null in user --- src/controllers/02-user-controller.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/controllers/02-user-controller.ts b/src/controllers/02-user-controller.ts index 2c864e6..1a789ff 100644 --- a/src/controllers/02-user-controller.ts +++ b/src/controllers/02-user-controller.ts @@ -126,8 +126,8 @@ type UserCreate = { remark?: string; agencyStatus?: string; - contactName?: string; - contactTel?: string; + contactName?: string | null; + contactTel?: string | null; }; type UserUpdate = { @@ -188,8 +188,8 @@ type UserUpdate = { remark?: string; agencyStatus?: string; - contactName?: string; - contactTel?: string; + contactName?: string | null; + contactTel?: string | null; }; const permissionCondCompany = createPermCondition((_) => true); From 106343d33d13420ebb7a439648c8f8b2cb37f82a Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 7 May 2025 10:54:22 +0700 Subject: [PATCH 043/167] feat: generate barcode --- package.json | 3 +- pnpm-lock.yaml | 224 +++++++++++++----- src/controllers/00-doc-template-controller.ts | 27 ++- 3 files changed, 187 insertions(+), 67 deletions(-) diff --git a/package.json b/package.json index 6294f71..08efa02 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "@prisma/client": "^6.3.0", "@scalar/express-api-reference": "^0.4.182", "@tsoa/runtime": "^6.6.0", - "barcode": "^0.1.0", + "canvas": "^3.1.0", "cors": "^2.8.5", "cron": "^3.3.1", "dayjs": "^1.11.13", @@ -50,6 +50,7 @@ "exceljs": "^4.4.0", "express": "^4.21.2", "fast-jwt": "^5.0.5", + "jsbarcode": "^3.11.6", "json-2-csv": "^5.5.8", "kysely": "^0.27.5", "minio": "^8.0.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9ad8f82..4b7c34f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,9 +23,9 @@ importers: '@tsoa/runtime': specifier: ^6.6.0 version: 6.6.0 - barcode: - specifier: ^0.1.0 - version: 0.1.0 + canvas: + specifier: ^3.1.0 + version: 3.1.0 cors: specifier: ^2.8.5 version: 2.8.5 @@ -53,6 +53,9 @@ importers: fast-jwt: specifier: ^5.0.5 version: 5.0.5 + jsbarcode: + specifier: ^3.11.6 + version: 3.11.6 json-2-csv: specifier: ^5.5.8 version: 5.5.8 @@ -641,12 +644,6 @@ packages: array-flatten@1.1.1: resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} - array-parallel@0.1.3: - resolution: {integrity: sha512-TDPTwSWW5E4oiFiKmz6RGJ/a80Y91GuLgUYuLd49+XBS75tYo8PNgaT2K/OxuQYqkoI852MDGBorg9OcUSTQ8w==} - - array-series@0.1.5: - resolution: {integrity: sha512-L0XlBwfx9QetHOsbLDrE/vh2t018w9462HM3iaFfxRiK83aJjAt/Ja3NMkOW7FICwWTlQBa3ZbL5FKhuQWkDrg==} - array-union@2.1.0: resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} engines: {node: '>=8'} @@ -693,9 +690,6 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - barcode@0.1.0: - resolution: {integrity: sha512-GslbXakjG61fwHnIN/vrUkPsa61WVAJDnb7jAwmbjRW5bZdwINymo1+FgjYJrGf2MDHxAt3bUWgmEMF8ETZxHQ==} - base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} @@ -790,6 +784,10 @@ packages: resolution: {integrity: sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==} engines: {node: '>= 0.4'} + canvas@3.1.0: + resolution: {integrity: sha512-tTj3CqqukVJ9NgSahykNwtGda7V33VLObwrHfzT0vqJXu7J4d4C/7kQQW3fOEGDfZZoILPut5H00gOjyttPGyg==} + engines: {node: ^18.12.0 || >= 20.9.0} + chainsaw@0.1.0: resolution: {integrity: sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==} @@ -811,6 +809,9 @@ packages: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} + chownr@1.1.4: + resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + ci-info@3.8.0: resolution: {integrity: sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==} engines: {node: '>=8'} @@ -948,14 +949,6 @@ packages: dayjs@1.11.13: resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} - debug@0.7.0: - resolution: {integrity: sha512-UWZnvGiX9tQgtrsA+mhGLKnUFvr1moempl9IvqQKyFnEgN0T4kpCE+KJcqTLcVxQjRVRnLF3VSE1Hchki5N98g==} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} peerDependencies: @@ -986,10 +979,18 @@ packages: resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==} engines: {node: '>=0.10'} + decompress-response@6.0.0: + resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} + engines: {node: '>=10'} + deeks@3.1.0: resolution: {integrity: sha512-e7oWH1LzIdv/prMQ7pmlDlaVoL64glqzvNgkgQNgyec9ORPHrT2jaOqMtRyqJuwWjtfb6v+2rk9pmaHj+F137A==} engines: {node: '>= 16'} + deep-extend@0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + define-data-property@1.1.4: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} @@ -1010,6 +1011,10 @@ packages: resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + detect-libc@2.0.4: + resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==} + engines: {node: '>=8'} + diff@4.0.2: resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} engines: {node: '>=0.3.1'} @@ -1151,6 +1156,10 @@ packages: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} + expand-template@2.0.3: + resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} + engines: {node: '>=6'} + express@4.21.2: resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==} engines: {node: '>= 0.10.0'} @@ -1298,6 +1307,9 @@ packages: resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==} engines: {node: '>= 0.4'} + github-from-package@0.0.0: + resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -1327,11 +1339,6 @@ packages: resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} engines: {node: '>=10'} - gm@1.16.0: - resolution: {integrity: sha512-b5oVGr8wCI7VNfjzeXiFocCZrcpkRUxSoVYfksBMEp/jo2nYwRKhcfOURarxFwjXyW1GvEY2EmmupVLnh0vXjg==} - engines: {node: '>= 0.8.0'} - deprecated: The gm module has been sunset. Please migrate to an alternative. https://github.com/aheckmann/gm?tab=readme-ov-file#2025-02-24-this-project-is-not-maintained - gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} @@ -1449,6 +1456,9 @@ packages: inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + ini@2.0.0: resolution: {integrity: sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==} engines: {node: '>=10'} @@ -1646,6 +1656,9 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + jsbarcode@3.11.6: + resolution: {integrity: sha512-G5TKGyKY1zJo0ZQKFM1IIMfy0nF2rs92BLlCz+cU4/TazIc4ZH+X1GYeDRt7TKjrYqmPfTjwTBkU/QnQlsYiuA==} + json-2-csv@5.5.8: resolution: {integrity: sha512-eMQHOwV+av8Sgo+fkbEbQWOw/kwh89AZ5fNA8TYfcooG6TG1ZOL2WcPUrngIMIK8dBJitQ8QEU0zbncQ0CX4CQ==} engines: {node: '>= 16'} @@ -1846,6 +1859,10 @@ packages: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} + mimic-response@3.1.0: + resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} + engines: {node: '>=10'} + min-indent@1.0.1: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} @@ -1875,6 +1892,9 @@ packages: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} + mkdirp-classic@0.5.3: + resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + mkdirp@0.5.6: resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} hasBin: true @@ -1905,6 +1925,9 @@ packages: resolution: {integrity: sha512-VzGiVigcG9zUAoCNU+xShztrlr1auZOlurXynNvO9GiWD1/mTBbUljOKY+qMeazBqXgRnjzeEgJI/wyjJUHg9A==} engines: {node: '>= 6.0.0'} + napi-build-utils@2.0.0: + resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==} + negotiator@0.6.3: resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} engines: {node: '>= 0.6'} @@ -1919,6 +1942,13 @@ packages: next-line@1.1.0: resolution: {integrity: sha512-+I10J3wKNoKddNxn0CNpoZ3eTZuqxjNM3b1GImVx22+ePI+Y15P8g/j3WsbP0fhzzrFzrtjOAoq5NCCucswXOQ==} + node-abi@3.75.0: + resolution: {integrity: sha512-OhYaY5sDsIka7H7AtijtI9jwGYLyl29eQn/W623DiN/MIv5sUqc4g7BIDThX+gb7di9f6xK02nkp8sdfFWZLTg==} + engines: {node: '>=10'} + + node-addon-api@7.1.1: + resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} + node-fetch@2.6.12: resolution: {integrity: sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==} engines: {node: 4.x || >=6.0.0} @@ -2129,6 +2159,11 @@ packages: resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} engines: {node: '>= 0.4'} + prebuild-install@7.1.3: + resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==} + engines: {node: '>=10'} + hasBin: true + prettier@3.4.2: resolution: {integrity: sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==} engines: {node: '>=14'} @@ -2184,6 +2219,9 @@ packages: pstree.remy@1.1.8: resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==} + pump@3.0.2: + resolution: {integrity: sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -2210,6 +2248,10 @@ packages: resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} engines: {node: '>= 0.8'} + rc@1.2.8: + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} + hasBin: true + read-pkg-up@7.0.1: resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} engines: {node: '>=8'} @@ -2393,6 +2435,12 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} + simple-concat@1.0.1: + resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} + + simple-get@4.0.1: + resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} + simple-swizzle@0.2.2: resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} @@ -2464,10 +2512,6 @@ packages: stream-json@1.9.1: resolution: {integrity: sha512-uWkjJ+2Nt/LO9Z/JyKZbMusL8Dkh97uUBTv3AJQ74y07lVahLY4eEFsPsE97pxYBwr8nnjMAIch5eqI0gPShyw==} - stream-to-buffer@0.0.1: - resolution: {integrity: sha512-LsvisgE3iThboRqA+XLmtnY9ktPLVPOj3zZxXMhlezeCcAh0RhomquXJgB8H+lb/RR/pPcbNVGHVKFUwjpoRtw==} - engines: {node: '>= 0.8'} - streamsearch@1.1.0: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} engines: {node: '>=10.0.0'} @@ -2518,6 +2562,10 @@ packages: resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} engines: {node: '>=8'} + strip-json-comments@2.0.1: + resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} + engines: {node: '>=0.10.0'} + strnum@1.0.5: resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==} @@ -2541,6 +2589,9 @@ packages: resolution: {integrity: sha512-iK5/YhZxq5GO5z8wb0bY1317uDF3Zjpha0QFFLA8/trAoiLbQD0HUbMesEaxyzUgDxi2QlcbM8IvqOlEjgoXBA==} engines: {node: '>=12.17'} + tar-fs@2.1.2: + resolution: {integrity: sha512-EsaAXwxmx8UB7FRKqeozqEPop69DXcmYwTQwXvyAPF352HJsPdkVhvTaDPYqfNgruveJIJy3TA2l+2zj8LJIJA==} + tar-stream@2.2.0: resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} engines: {node: '>=6'} @@ -2566,9 +2617,6 @@ packages: through2@4.0.2: resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==} - through@2.3.8: - resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} - tmp@0.2.1: resolution: {integrity: sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==} engines: {node: '>=8.17.0'} @@ -2634,6 +2682,9 @@ packages: engines: {node: '>=18.0.0', yarn: '>=1.9.4'} hasBin: true + tunnel-agent@0.6.0: + resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + type-fest@0.16.0: resolution: {integrity: sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==} engines: {node: '>=10'} @@ -3634,10 +3685,6 @@ snapshots: array-flatten@1.1.1: {} - array-parallel@0.1.3: {} - - array-series@0.1.5: {} - array-union@2.1.0: {} array.prototype.map@1.0.7: @@ -3694,12 +3741,6 @@ snapshots: balanced-match@1.0.2: {} - barcode@0.1.0: - dependencies: - gm: 1.16.0 - transitivePeerDependencies: - - supports-color - base64-js@1.5.1: {} basic-auth@2.0.1: @@ -3807,6 +3848,11 @@ snapshots: call-bind-apply-helpers: 1.0.1 get-intrinsic: 1.2.6 + canvas@3.1.0: + dependencies: + node-addon-api: 7.1.1 + prebuild-install: 7.1.3 + chainsaw@0.1.0: dependencies: traverse: 0.3.9 @@ -3852,6 +3898,8 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + chownr@1.1.4: {} + ci-info@3.8.0: {} cjs-module-lexer@1.4.1: @@ -4002,8 +4050,6 @@ snapshots: dayjs@1.11.13: {} - debug@0.7.0: {} - debug@2.6.9: dependencies: ms: 2.0.0 @@ -4020,8 +4066,14 @@ snapshots: decode-uri-component@0.2.2: {} + decompress-response@6.0.0: + dependencies: + mimic-response: 3.1.0 + deeks@3.1.0: {} + deep-extend@0.6.0: {} + define-data-property@1.1.4: dependencies: es-define-property: 1.0.1 @@ -4049,6 +4101,8 @@ snapshots: destroy@1.2.0: {} + detect-libc@2.0.4: {} + diff@4.0.2: {} dir-glob@3.0.1: @@ -4289,6 +4343,8 @@ snapshots: signal-exit: 3.0.7 strip-final-newline: 2.0.0 + expand-template@2.0.3: {} + express@4.21.2: dependencies: accepts: 1.3.8 @@ -4494,6 +4550,8 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.2.6 + github-from-package@0.0.0: {} + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -4542,16 +4600,6 @@ snapshots: merge2: 1.4.1 slash: 3.0.0 - gm@1.16.0: - dependencies: - array-parallel: 0.1.3 - array-series: 0.1.5 - debug: 0.7.0 - stream-to-buffer: 0.0.1 - through: 2.3.8 - transitivePeerDependencies: - - supports-color - gopd@1.2.0: {} graceful-fs@4.2.11: {} @@ -4672,6 +4720,8 @@ snapshots: inherits@2.0.4: {} + ini@1.3.8: {} + ini@2.0.0: {} internal-slot@1.1.0: @@ -4852,6 +4902,8 @@ snapshots: js-tokens@4.0.0: {} + jsbarcode@3.11.6: {} + json-2-csv@5.5.8: dependencies: deeks: 3.1.0 @@ -5023,6 +5075,8 @@ snapshots: mimic-fn@2.1.0: {} + mimic-response@3.1.0: {} + min-indent@1.0.1: {} minimalistic-assert@1.0.1: {} @@ -5060,6 +5114,8 @@ snapshots: minipass@7.1.2: {} + mkdirp-classic@0.5.3: {} + mkdirp@0.5.6: dependencies: minimist: 1.2.8 @@ -5100,6 +5156,8 @@ snapshots: type-is: 1.6.18 xtend: 4.0.2 + napi-build-utils@2.0.0: {} + negotiator@0.6.3: {} neo-async@2.6.2: {} @@ -5109,6 +5167,12 @@ snapshots: next-line@1.1.0: optional: true + node-abi@3.75.0: + dependencies: + semver: 7.6.3 + + node-addon-api@7.1.1: {} + node-fetch@2.6.12(encoding@0.1.13): dependencies: whatwg-url: 5.0.0 @@ -5314,6 +5378,21 @@ snapshots: possible-typed-array-names@1.0.0: {} + prebuild-install@7.1.3: + dependencies: + detect-libc: 2.0.4 + expand-template: 2.0.3 + github-from-package: 0.0.0 + minimist: 1.2.8 + mkdirp-classic: 0.5.3 + napi-build-utils: 2.0.0 + node-abi: 3.75.0 + pump: 3.0.2 + rc: 1.2.8 + simple-get: 4.0.1 + tar-fs: 2.1.2 + tunnel-agent: 0.6.0 + prettier@3.4.2: {} prisma-extension-kysely@3.0.0(@prisma/client@6.3.0(prisma@6.3.0(typescript@5.7.2))(typescript@5.7.2)): @@ -5374,6 +5453,11 @@ snapshots: pstree.remy@1.1.8: {} + pump@3.0.2: + dependencies: + end-of-stream: 1.4.4 + once: 1.4.0 + punycode@2.3.1: optional: true @@ -5402,6 +5486,13 @@ snapshots: iconv-lite: 0.4.24 unpipe: 1.0.0 + rc@1.2.8: + dependencies: + deep-extend: 0.6.0 + ini: 1.3.8 + minimist: 1.2.8 + strip-json-comments: 2.0.1 + read-pkg-up@7.0.1: dependencies: find-up: 4.1.0 @@ -5632,6 +5723,14 @@ snapshots: signal-exit@4.1.0: {} + simple-concat@1.0.1: {} + + simple-get@4.0.1: + dependencies: + decompress-response: 6.0.0 + once: 1.4.0 + simple-concat: 1.0.1 + simple-swizzle@0.2.2: dependencies: is-arrayish: 0.3.2 @@ -5705,8 +5804,6 @@ snapshots: dependencies: stream-chain: 2.2.5 - stream-to-buffer@0.0.1: {} - streamsearch@1.1.0: {} strict-uri-encode@2.0.0: {} @@ -5768,6 +5865,8 @@ snapshots: dependencies: min-indent: 1.0.1 + strip-json-comments@2.0.1: {} + strnum@1.0.5: {} supports-color@5.5.0: @@ -5790,6 +5889,13 @@ snapshots: array-back: 6.2.2 wordwrapjs: 5.1.0 + tar-fs@2.1.2: + dependencies: + chownr: 1.1.4 + mkdirp-classic: 0.5.3 + pump: 3.0.2 + tar-stream: 2.2.0 + tar-stream@2.2.0: dependencies: bl: 4.1.0 @@ -5821,8 +5927,6 @@ snapshots: dependencies: readable-stream: 3.6.2 - through@2.3.8: {} - tmp@0.2.1: dependencies: rimraf: 3.0.2 @@ -5884,6 +5988,10 @@ snapshots: transitivePeerDependencies: - supports-color + tunnel-agent@0.6.0: + dependencies: + safe-buffer: 5.2.1 + type-fest@0.16.0: {} type-fest@0.21.3: {} diff --git a/src/controllers/00-doc-template-controller.ts b/src/controllers/00-doc-template-controller.ts index 1b9f70d..aa173e2 100644 --- a/src/controllers/00-doc-template-controller.ts +++ b/src/controllers/00-doc-template-controller.ts @@ -1,4 +1,5 @@ -import barcode from "barcode"; +import { createCanvas } from "canvas"; +import JsBarcode from "jsbarcode"; import createReport from "docx-templates"; import ThaiBahtText from "thai-baht-text"; import { District, Province, SubDistrict } from "@prisma/client"; @@ -253,13 +254,23 @@ export class DocTemplateController extends Controller { thaiBahtText: (input: string | number) => { ThaiBahtText(typeof input === "string" ? input.replaceAll(",", "") : input); }, - barcode: async (data: string) => - new Promise((resolve, reject) => - barcode("code39", { data, width: 400, height: 100 }).getBase64((err, data) => { - if (!err) return resolve(data); - return reject(err); - }), - ), + barcode: async (data: string, width?: number, height?: number) => + new Promise<{ + width: number; + height: number; + data: string; + extension: string; + }>((resolve) => { + const canvas = createCanvas(400, 100); + JsBarcode(canvas, data); + + resolve({ + width: width ?? 8, + height: height ?? 3, + data: canvas.toDataURL("image/jpeg").slice("data:image/jpeg;base64".length), + extension: ".jpeg", + }); + }), }, }).then(Buffer.from); From 2d0d977617924ccad5fb7f2ac4e1f5dd80f37bcb Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Tue, 13 May 2025 10:03:41 +0700 Subject: [PATCH 044/167] fix: wrong path --- src/controllers/00-doc-template-controller.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/controllers/00-doc-template-controller.ts b/src/controllers/00-doc-template-controller.ts index aa173e2..88b358f 100644 --- a/src/controllers/00-doc-template-controller.ts +++ b/src/controllers/00-doc-template-controller.ts @@ -112,12 +112,12 @@ export class DocTemplateController extends Controller { ) { const ret = await edmList( "file", - templateGroup ? [templateGroup, ...DOCUMENT_PATH] : DOCUMENT_PATH, + templateGroup ? [...DOCUMENT_PATH, templateGroup] : DOCUMENT_PATH, ); if (ret) return ret.map((v) => v.fileName); } return await listFile( - (templateGroup ? [templateGroup, ...DOCUMENT_PATH] : DOCUMENT_PATH).join("/") + "/", + (templateGroup ? [...DOCUMENT_PATH, templateGroup] : DOCUMENT_PATH).join("/") + "/", ); } From 0affb5337f1aa2c06be6480f9d2d49e5e462fcba Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Tue, 13 May 2025 14:31:37 +0700 Subject: [PATCH 045/167] fix: barcode font no display correctly --- Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 951bb52..7b7967d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,8 @@ ENV PNPM_HOME="/pnpm" ENV PATH="$PNPM_HOME:$PATH" RUN corepack enable -RUN apt-get update && apt-get install -y openssl +RUN apt-get update && apt-get install -y openssl fontconfig +RUN fc-cache -f -v RUN pnpm i -g prisma prisma-kysely WORKDIR /app From 897ef335b4f57577c0040018f756fcd476ff1cc2 Mon Sep 17 00:00:00 2001 From: Kanjana Date: Wed, 14 May 2025 09:28:57 +0700 Subject: [PATCH 046/167] add notification expireDate passport, visa --- .../20250513084929_add/migration.sql | 3 + prisma/schema.prisma | 3 + src/controllers/07-task-controller.ts | 30 ++++- src/services/schedule.ts | 114 ++++++++++++++++++ 4 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 prisma/migrations/20250513084929_add/migration.sql diff --git a/prisma/migrations/20250513084929_add/migration.sql b/prisma/migrations/20250513084929_add/migration.sql new file mode 100644 index 0000000..f982e5b --- /dev/null +++ b/prisma/migrations/20250513084929_add/migration.sql @@ -0,0 +1,3 @@ +-- AlterTable +ALTER TABLE "QuotationWorker" ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, +ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 73be520..47f82d6 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -1410,6 +1410,9 @@ model QuotationWorker { employeeId String quotation Quotation @relation(fields: [quotationId], references: [id], onDelete: Cascade) quotationId String + + createdAt DateTime @default(now()) + updatedAt DateTime @default(now()) @updatedAt } model QuotationProductServiceList { diff --git a/src/controllers/07-task-controller.ts b/src/controllers/07-task-controller.ts index 5e9977c..3f15e07 100644 --- a/src/controllers/07-task-controller.ts +++ b/src/controllers/07-task-controller.ts @@ -606,7 +606,21 @@ export class TaskActionController extends Controller { return await prisma.$transaction(async (tx) => { const promises = body.map(async (v) => { const record = await tx.task.findFirst({ - include: { requestWorkStep: true }, + include: { + requestWorkStep: { + include: { + requestWork: { + include: { + request: { + include: { + quotation: true, + }, + }, + }, + }, + }, + }, + }, where: { step: v.step, requestWorkId: v.requestWorkId, @@ -624,6 +638,20 @@ export class TaskActionController extends Controller { data: { userTaskStatus: UserTaskStatus.Restart }, }); } + + if (v.taskStatus === TaskStatus.Failed) { + const code = record.requestWorkStep.requestWork.request.quotation.code; + const name = record.requestWorkStep.requestWork.request.quotation.workName; + + await tx.notification.create({ + data: { + title: "ใบรายการคำขอที่จัดการเกิดปัญหา / Task Failed", + detail: `ใบรายการคำขอรหัส / code : ${code} - ${name} ใบรายการคำขอเกิดปัญหา`, + receiverId: record.requestWorkStep.requestWork.request.quotation.updatedByUserId, + }, + }); + } + return await tx.task.update({ where: { id: record.id }, data: { diff --git a/src/services/schedule.ts b/src/services/schedule.ts index bd12bf8..40e8763 100644 --- a/src/services/schedule.ts +++ b/src/services/schedule.ts @@ -38,6 +38,120 @@ const jobs = [ .catch((e) => console.error("[ERR]: Update expired quotation status, FAILED.", e)); }, }), + CronJob.from({ + cronTime: "0 0 0 * * *", + runOnInit: true, + onTick: async () => { + const employeeExpireData = await prisma.employee.findMany({ + include: { + employeePassport: { + orderBy: { + expireDate: "desc", + }, + take: 1, + }, + customerBranch: { + include: { + customer: true, + }, + }, + }, + where: { + employeePassport: { + some: { + expireDate: dayjs().add(90, "day").toDate(), + }, + }, + }, + }); + + await employeeExpireData.map(async (record) => { + const fullName = `${record.namePrefix}${record.firstNameEN} ${record.lastNameEN}`; + const expireDate = `${dayjs(record.employeePassport[0].expireDate).format("DD/MM")}/${dayjs(record.employeePassport[0].expireDate).year() + 543}`; + const textDetail = `ลูกจ้างรหัส / code : ${record.code} ชื่อ : ${fullName} หนังสือเดินทางจะหมดอายุในวันที่ ${expireDate}`; + const duplicateText = await prisma.notification.findFirst({ + where: { + detail: textDetail, + }, + }); + + if (!duplicateText) { + await prisma.notification + .create({ + data: { + title: "หนังสือเดินทางลูกจ้างหมดอายุ / Employee Passport Expire", + detail: textDetail, + groupReceiver: { + create: [{ name: "sale" }, { name: "head_of_sale" }], + }, + registeredBranchId: record.customerBranch.customer.registeredBranchId, + }, + }) + .then(() => console.log("[INFO]: Create notification employee passport expired, OK.")) + .catch((e) => + console.error("[ERR]: Create notification employee passport expired, FAILED.", e), + ); + } + }); + }, + }), + CronJob.from({ + cronTime: "0 0 0 * * *", + runOnInit: true, + onTick: async () => { + const employeeVisaData = await prisma.employee.findMany({ + include: { + employeeVisa: { + orderBy: { + expireDate: "desc", + }, + take: 1, + }, + customerBranch: { + include: { + customer: true, + }, + }, + }, + where: { + employeeVisa: { + some: { + expireDate: dayjs().add(90, "day").toDate(), + }, + }, + }, + }); + + await employeeVisaData.map(async (record) => { + const fullName = `${record.namePrefix}${record.firstNameEN} ${record.lastNameEN}`; + const expireDate = `${dayjs(record.employeeVisa[0].expireDate).format("DD/MM")}/${dayjs(record.employeeVisa[0].expireDate).year() + 543}`; + const textDetail = `ลูกจ้างรหัส / code : ${record.code} ชื่อ : ${fullName} ข้อมูลการตรวจลงตราจะหมดอายุในวันที่ ${expireDate}`; + const duplicateText = await prisma.notification.findFirst({ + where: { + detail: textDetail, + }, + }); + + if (!duplicateText) { + await prisma.notification + .create({ + data: { + title: "ข้อมูลการตรวจลงตราลูกจ้างหมดอายุ / Employee Visa Expire", + detail: textDetail, + groupReceiver: { + create: [{ name: "sale" }, { name: "head_of_sale" }], + }, + registeredBranchId: record.customerBranch.customer.registeredBranchId, + }, + }) + .then(() => console.log("[INFO]: Create notification employee visa expired, OK.")) + .catch((e) => + console.error("[ERR]: Create notification employee visa expired, FAILED.", e), + ); + } + }); + }, + }), ]; export function initSchedule() { From 0aa20d3728e25aa469b7adae2622c450ea30ad56 Mon Sep 17 00:00:00 2001 From: Kanjana Date: Wed, 14 May 2025 14:18:08 +0700 Subject: [PATCH 047/167] change detail notification task --- src/controllers/07-task-controller.ts | 19 +++- src/services/schedule.ts | 151 +++++++++++++++++--------- 2 files changed, 112 insertions(+), 58 deletions(-) diff --git a/src/controllers/07-task-controller.ts b/src/controllers/07-task-controller.ts index 3f15e07..e806797 100644 --- a/src/controllers/07-task-controller.ts +++ b/src/controllers/07-task-controller.ts @@ -614,12 +614,19 @@ export class TaskActionController extends Controller { request: { include: { quotation: true, + employee: true, + }, + }, + productService: { + include: { + product: true, }, }, }, }, }, }, + taskOrder: true, }, where: { step: v.step, @@ -640,14 +647,18 @@ export class TaskActionController extends Controller { } if (v.taskStatus === TaskStatus.Failed) { - const code = record.requestWorkStep.requestWork.request.quotation.code; - const name = record.requestWorkStep.requestWork.request.quotation.workName; + const taskCode = record.taskOrder.code; + const taskName = record.taskOrder.taskName; + const productCode = record.requestWorkStep.requestWork.productService.product.code; + const productName = record.requestWorkStep.requestWork.productService.product.name; + const employeeName = `${record.requestWorkStep.requestWork.request.employee.namePrefix}.${record.requestWorkStep.requestWork.request.employee.firstNameEN} ${record.requestWorkStep.requestWork.request.employee.lastNameEN}`; await tx.notification.create({ data: { title: "ใบรายการคำขอที่จัดการเกิดปัญหา / Task Failed", - detail: `ใบรายการคำขอรหัส / code : ${code} - ${name} ใบรายการคำขอเกิดปัญหา`, - receiverId: record.requestWorkStep.requestWork.request.quotation.updatedByUserId, + detail: `ใบรายการคำขอรหัส ${taskCode}: ${taskName} รหัสสินค้า ${productCode}: ${productName} ของลูกจ้าง ${employeeName} เกิดข้อผิดพลาด`, + groupReceiver: { create: { name: "document_checker" } }, + registeredBranchId: record.taskOrder.registeredBranchId, }, }); } diff --git a/src/services/schedule.ts b/src/services/schedule.ts index 40e8763..dd0591e 100644 --- a/src/services/schedule.ts +++ b/src/services/schedule.ts @@ -2,6 +2,7 @@ import dayjs from "dayjs"; import { CronJob } from "cron"; import prisma from "../db"; +import { Prisma } from "@prisma/client"; const jobs = [ CronJob.from({ @@ -55,6 +56,15 @@ const jobs = [ customer: true, }, }, + quotationWorker: { + include: { + quotation: true, + }, + orderBy: { + createdAt: "desc", + }, + take: 1, + }, }, where: { employeePassport: { @@ -65,34 +75,46 @@ const jobs = [ }, }); - await employeeExpireData.map(async (record) => { - const fullName = `${record.namePrefix}${record.firstNameEN} ${record.lastNameEN}`; - const expireDate = `${dayjs(record.employeePassport[0].expireDate).format("DD/MM")}/${dayjs(record.employeePassport[0].expireDate).year() + 543}`; - const textDetail = `ลูกจ้างรหัส / code : ${record.code} ชื่อ : ${fullName} หนังสือเดินทางจะหมดอายุในวันที่ ${expireDate}`; - const duplicateText = await prisma.notification.findFirst({ - where: { - detail: textDetail, - }, - }); + await Promise.all( + employeeExpireData.map(async (record) => { + const fullName = `${record.namePrefix}.${record.firstNameEN} ${record.lastNameEN}`; + const expireDate = `${dayjs(record.employeePassport[0].expireDate).format("DD/MM")}/${dayjs(record.employeePassport[0].expireDate).year() + 543}`; + const textDetail = `ลูกจ้างรหัส / code : ${record.code} ชื่อ : ${fullName} หนังสือเดินทางจะหมดอายุในวันที่ ${expireDate}`; + const duplicateText = await prisma.notification.findFirst({ + where: { + detail: textDetail, + }, + }); - if (!duplicateText) { - await prisma.notification - .create({ - data: { - title: "หนังสือเดินทางลูกจ้างหมดอายุ / Employee Passport Expire", - detail: textDetail, - groupReceiver: { - create: [{ name: "sale" }, { name: "head_of_sale" }], - }, - registeredBranchId: record.customerBranch.customer.registeredBranchId, - }, - }) - .then(() => console.log("[INFO]: Create notification employee passport expired, OK.")) - .catch((e) => - console.error("[ERR]: Create notification employee passport expired, FAILED.", e), - ); - } - }); + const dataNotification: Prisma.NotificationCreateArgs["data"] = { + title: "หนังสือเดินทางลูกจ้างหมดอายุ / Employee Passport Expire", + detail: textDetail, + }; + + if (record.quotationWorker && record.quotationWorker.length > 0) { + dataNotification.receiverId = record.quotationWorker[0].quotation.updatedByUserId; + dataNotification.registeredBranchId = + record.quotationWorker[0].quotation.registeredBranchId; + } else { + (dataNotification.groupReceiver = { + create: [{ name: "sale" }, { name: "head_of_sale" }], + }), + (dataNotification.registeredBranchId = + record.customerBranch.customer.registeredBranchId); + } + + if (!duplicateText) { + await prisma.notification + .create({ + data: dataNotification, + }) + .then(() => console.log("[INFO]: Create notification employee passport expired, OK.")) + .catch((e) => + console.error("[ERR]: Create notification employee passport expired, FAILED.", e), + ); + } + }), + ); }, }), CronJob.from({ @@ -112,6 +134,15 @@ const jobs = [ customer: true, }, }, + quotationWorker: { + include: { + quotation: true, + }, + orderBy: { + createdAt: "desc", + }, + take: 1, + }, }, where: { employeeVisa: { @@ -122,34 +153,46 @@ const jobs = [ }, }); - await employeeVisaData.map(async (record) => { - const fullName = `${record.namePrefix}${record.firstNameEN} ${record.lastNameEN}`; - const expireDate = `${dayjs(record.employeeVisa[0].expireDate).format("DD/MM")}/${dayjs(record.employeeVisa[0].expireDate).year() + 543}`; - const textDetail = `ลูกจ้างรหัส / code : ${record.code} ชื่อ : ${fullName} ข้อมูลการตรวจลงตราจะหมดอายุในวันที่ ${expireDate}`; - const duplicateText = await prisma.notification.findFirst({ - where: { - detail: textDetail, - }, - }); + await Promise.all( + employeeVisaData.map(async (record) => { + const fullName = `${record.namePrefix}.${record.firstNameEN} ${record.lastNameEN}`; + const expireDate = `${dayjs(record.employeeVisa[0].expireDate).format("DD/MM")}/${dayjs(record.employeeVisa[0].expireDate).year() + 543}`; + const textDetail = `ลูกจ้างรหัส / code : ${record.code} ชื่อ : ${fullName} ข้อมูลการตรวจลงตราจะหมดอายุในวันที่ ${expireDate}`; + const duplicateText = await prisma.notification.findFirst({ + where: { + detail: textDetail, + }, + }); - if (!duplicateText) { - await prisma.notification - .create({ - data: { - title: "ข้อมูลการตรวจลงตราลูกจ้างหมดอายุ / Employee Visa Expire", - detail: textDetail, - groupReceiver: { - create: [{ name: "sale" }, { name: "head_of_sale" }], - }, - registeredBranchId: record.customerBranch.customer.registeredBranchId, - }, - }) - .then(() => console.log("[INFO]: Create notification employee visa expired, OK.")) - .catch((e) => - console.error("[ERR]: Create notification employee visa expired, FAILED.", e), - ); - } - }); + const dataNotification: Prisma.NotificationCreateArgs["data"] = { + title: "ข้อมูลการตรวจลงตราลูกจ้างหมดอายุ / Employee Visa Expire", + detail: textDetail, + }; + + if (record.quotationWorker && record.quotationWorker.length > 0) { + dataNotification.receiverId = record.quotationWorker[0].quotation.updatedByUserId; + dataNotification.registeredBranchId = + record.quotationWorker[0].quotation.registeredBranchId; + } else { + (dataNotification.groupReceiver = { + create: [{ name: "sale" }, { name: "head_of_sale" }], + }), + (dataNotification.registeredBranchId = + record.customerBranch.customer.registeredBranchId); + } + + if (!duplicateText) { + await prisma.notification + .create({ + data: dataNotification, + }) + .then(() => console.log("[INFO]: Create notification employee visa expired, OK.")) + .catch((e) => + console.error("[ERR]: Create notification employee visa expired, FAILED.", e), + ); + } + }), + ); }, }), ]; From 791e8b49776c083cbbd68b40fcdd2a63c1098cbf Mon Sep 17 00:00:00 2001 From: Kanjana Date: Thu, 15 May 2025 13:29:12 +0700 Subject: [PATCH 048/167] feat : add receiverId in notification Task Failed --- src/controllers/07-task-controller.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/controllers/07-task-controller.ts b/src/controllers/07-task-controller.ts index e806797..da3c13d 100644 --- a/src/controllers/07-task-controller.ts +++ b/src/controllers/07-task-controller.ts @@ -658,6 +658,7 @@ export class TaskActionController extends Controller { title: "ใบรายการคำขอที่จัดการเกิดปัญหา / Task Failed", detail: `ใบรายการคำขอรหัส ${taskCode}: ${taskName} รหัสสินค้า ${productCode}: ${productName} ของลูกจ้าง ${employeeName} เกิดข้อผิดพลาด`, groupReceiver: { create: { name: "document_checker" } }, + receiverId: record.requestWorkStep.requestWork.request.quotation.createdByUserId, registeredBranchId: record.taskOrder.registeredBranchId, }, }); From 1896e2385d10634cf4aa61cc4db8b867ce40cac9 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Fri, 23 May 2025 17:12:06 +0700 Subject: [PATCH 049/167] feat(test): add api branch test --- package.json | 5 +- pnpm-lock.yaml | 909 ++++++++++++++++++++++++++++++++++++++++++++ test/branch.test.ts | 323 ++++++++++++++++ test/lib/index.ts | 10 + vite.config.ts | 5 + 5 files changed, 1251 insertions(+), 1 deletion(-) create mode 100644 test/branch.test.ts create mode 100644 test/lib/index.ts create mode 100644 vite.config.ts diff --git a/package.json b/package.json index 08efa02..2f64c4f 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "start": "node ./dist/app.js", "dev": "nodemon", "check": "tsc --noEmit", + "test": "vitest", "format": "prettier --write .", "debug": "nodemon", "build": "tsoa spec-and-routes && tsc", @@ -27,12 +28,14 @@ "@types/multer": "^1.4.12", "@types/node": "^20.17.10", "@types/nodemailer": "^6.4.17", + "@vitest/ui": "^3.1.4", "nodemon": "^3.1.9", "prettier": "^3.4.2", "prisma": "^6.3.0", "prisma-kysely": "^1.8.0", "ts-node": "^10.9.2", - "typescript": "^5.7.2" + "typescript": "^5.7.2", + "vitest": "^3.1.4" }, "dependencies": { "@elastic/elasticsearch": "^8.17.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4b7c34f..8471a8c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -117,6 +117,9 @@ importers: '@types/nodemailer': specifier: ^6.4.17 version: 6.4.17 + '@vitest/ui': + specifier: ^3.1.4 + version: 3.1.4(vitest@3.1.4) nodemon: specifier: ^3.1.9 version: 3.1.9 @@ -135,6 +138,9 @@ importers: typescript: specifier: ^5.7.2 version: 5.7.2 + vitest: + specifier: ^3.1.4 + version: 3.1.4(@types/node@20.17.10)(@vitest/ui@3.1.4)(yaml@2.6.1) packages: @@ -189,6 +195,156 @@ packages: resolution: {integrity: sha512-jasKNQeOb1vNf9aEYg+8zXmetaFjApDTSCC4QTl6aTixvyiRiSLcCiB8P6Q0lY9JIII/BhqNl8WbpFnsKitntw==} engines: {node: '>=18'} + '@esbuild/aix-ppc64@0.25.4': + resolution: {integrity: sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.4': + resolution: {integrity: sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.4': + resolution: {integrity: sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.4': + resolution: {integrity: sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.4': + resolution: {integrity: sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.4': + resolution: {integrity: sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.4': + resolution: {integrity: sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.4': + resolution: {integrity: sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.4': + resolution: {integrity: sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.4': + resolution: {integrity: sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.4': + resolution: {integrity: sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.4': + resolution: {integrity: sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.4': + resolution: {integrity: sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.4': + resolution: {integrity: sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.4': + resolution: {integrity: sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.4': + resolution: {integrity: sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.4': + resolution: {integrity: sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.4': + resolution: {integrity: sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.4': + resolution: {integrity: sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.4': + resolution: {integrity: sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.4': + resolution: {integrity: sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.25.4': + resolution: {integrity: sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.4': + resolution: {integrity: sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.4': + resolution: {integrity: sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.4': + resolution: {integrity: sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@fast-csv/format@4.3.5': resolution: {integrity: sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A==} @@ -357,6 +513,9 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} + '@polka/url@1.0.0-next.29': + resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} + '@prisma/client@6.3.0': resolution: {integrity: sha512-BY3Fi28PUSk447Bpv22LhZp4HgNPo7NsEN+EteM1CLDnLjig5863jpW+3c3HHLFmml+nB/eJv1CjSriFZ8z7Cg==} engines: {node: '>=18.18'} @@ -405,6 +564,106 @@ packages: '@prisma/prisma-schema-wasm@5.3.1-2.61e140623197a131c2a6189271ffee05a7aa9a59': resolution: {integrity: sha512-+zUI7NQDXfcNnU8HgrAj4jRMv8yRfITLzcfv0Urf0adKimM+hkkVG4rX38i9zWMlxekkEBw7NLFx3Gxxy8d3iQ==} + '@rollup/rollup-android-arm-eabi@4.41.0': + resolution: {integrity: sha512-KxN+zCjOYHGwCl4UCtSfZ6jrq/qi88JDUtiEFk8LELEHq2Egfc/FgW+jItZiOLRuQfb/3xJSgFuNPC9jzggX+A==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.41.0': + resolution: {integrity: sha512-yDvqx3lWlcugozax3DItKJI5j05B0d4Kvnjx+5mwiUpWramVvmAByYigMplaoAQ3pvdprGCTCE03eduqE/8mPQ==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.41.0': + resolution: {integrity: sha512-2KOU574vD3gzcPSjxO0eyR5iWlnxxtmW1F5CkNOHmMlueKNCQkxR6+ekgWyVnz6zaZihpUNkGxjsYrkTJKhkaw==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.41.0': + resolution: {integrity: sha512-gE5ACNSxHcEZyP2BA9TuTakfZvULEW4YAOtxl/A/YDbIir/wPKukde0BNPlnBiP88ecaN4BJI2TtAd+HKuZPQQ==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.41.0': + resolution: {integrity: sha512-GSxU6r5HnWij7FoSo7cZg3l5GPg4HFLkzsFFh0N/b16q5buW1NAWuCJ+HMtIdUEi6XF0qH+hN0TEd78laRp7Dg==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.41.0': + resolution: {integrity: sha512-KGiGKGDg8qLRyOWmk6IeiHJzsN/OYxO6nSbT0Vj4MwjS2XQy/5emsmtoqLAabqrohbgLWJ5GV3s/ljdrIr8Qjg==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.41.0': + resolution: {integrity: sha512-46OzWeqEVQyX3N2/QdiU/CMXYDH/lSHpgfBkuhl3igpZiaB3ZIfSjKuOnybFVBQzjsLwkus2mjaESy8H41SzvA==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.41.0': + resolution: {integrity: sha512-lfgW3KtQP4YauqdPpcUZHPcqQXmTmH4nYU0cplNeW583CMkAGjtImw4PKli09NFi2iQgChk4e9erkwlfYem6Lg==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.41.0': + resolution: {integrity: sha512-nn8mEyzMbdEJzT7cwxgObuwviMx6kPRxzYiOl6o/o+ChQq23gfdlZcUNnt89lPhhz3BYsZ72rp0rxNqBSfqlqw==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.41.0': + resolution: {integrity: sha512-l+QK99je2zUKGd31Gh+45c4pGDAqZSuWQiuRFCdHYC2CSiO47qUWsCcenrI6p22hvHZrDje9QjwSMAFL3iwXwQ==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loongarch64-gnu@4.41.0': + resolution: {integrity: sha512-WbnJaxPv1gPIm6S8O/Wg+wfE/OzGSXlBMbOe4ie+zMyykMOeqmgD1BhPxZQuDqwUN+0T/xOFtL2RUWBspnZj3w==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-powerpc64le-gnu@4.41.0': + resolution: {integrity: sha512-eRDWR5t67/b2g8Q/S8XPi0YdbKcCs4WQ8vklNnUYLaSWF+Cbv2axZsp4jni6/j7eKvMLYCYdcsv8dcU+a6QNFg==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.41.0': + resolution: {integrity: sha512-TWrZb6GF5jsEKG7T1IHwlLMDRy2f3DPqYldmIhnA2DVqvvhY2Ai184vZGgahRrg8k9UBWoSlHv+suRfTN7Ua4A==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.41.0': + resolution: {integrity: sha512-ieQljaZKuJpmWvd8gW87ZmSFwid6AxMDk5bhONJ57U8zT77zpZ/TPKkU9HpnnFrM4zsgr4kiGuzbIbZTGi7u9A==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.41.0': + resolution: {integrity: sha512-/L3pW48SxrWAlVsKCN0dGLB2bi8Nv8pr5S5ocSM+S0XCn5RCVCXqi8GVtHFsOBBCSeR+u9brV2zno5+mg3S4Aw==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.41.0': + resolution: {integrity: sha512-XMLeKjyH8NsEDCRptf6LO8lJk23o9wvB+dJwcXMaH6ZQbbkHu2dbGIUindbMtRN6ux1xKi16iXWu6q9mu7gDhQ==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.41.0': + resolution: {integrity: sha512-m/P7LycHZTvSQeXhFmgmdqEiTqSV80zn6xHaQ1JSqwCtD1YGtwEK515Qmy9DcB2HK4dOUVypQxvhVSy06cJPEg==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-win32-arm64-msvc@4.41.0': + resolution: {integrity: sha512-4yodtcOrFHpbomJGVEqZ8fzD4kfBeCbpsUy5Pqk4RluXOdsWdjLnjhiKy2w3qzcASWd04fp52Xz7JKarVJ5BTg==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.41.0': + resolution: {integrity: sha512-tmazCrAsKzdkXssEc65zIE1oC6xPHwfy9d5Ta25SRCDOZS+I6RypVVShWALNuU9bxIfGA0aqrmzlzoM5wO5SPQ==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.41.0': + resolution: {integrity: sha512-h1J+Yzjo/X+0EAvR2kIXJDuTuyT7drc+t2ALY0nIcGPbTatNOf0VWdhEA2Z4AAjv6X1NJV7SYo5oCTYRJhSlVA==} + cpu: [x64] + os: [win32] + '@scalar/express-api-reference@0.4.182': resolution: {integrity: sha512-T8y+/FM24H1C13GMDkjmM62obEhfGclIPwR2tn2JK+TY6LhKuHAF0xPzaBkfiRjlSEnrJigJSJxb8lwGxHWr9A==} engines: {node: '>=18'} @@ -474,6 +733,9 @@ packages: '@types/debug@4.1.8': resolution: {integrity: sha512-/vPO1EPOs306Cvhwv7KfVfYvOJqA/S/AXjaHQiJboCZzcNDb+TIJFN9/2C9DZ//ijSKWioNyUxD792QmDJ+HKQ==} + '@types/estree@1.0.7': + resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==} + '@types/express-serve-static-core@4.19.6': resolution: {integrity: sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==} @@ -543,6 +805,40 @@ packages: '@unhead/schema@1.11.14': resolution: {integrity: sha512-V9W9u5tF1/+TiLqxu+Qvh1ShoMDkPEwHoEo4DKdDG6ko7YlbzFfDxV6el9JwCren45U/4Vy/4Xi7j8OH02wsiA==} + '@vitest/expect@3.1.4': + resolution: {integrity: sha512-xkD/ljeliyaClDYqHPNCiJ0plY5YIcM0OlRiZizLhlPmpXWpxnGMyTZXOHFhFeG7w9P5PBeL4IdtJ/HeQwTbQA==} + + '@vitest/mocker@3.1.4': + resolution: {integrity: sha512-8IJ3CvwtSw/EFXqWFL8aCMu+YyYXG2WUSrQbViOZkWTKTVicVwZ/YiEZDSqD00kX+v/+W+OnxhNWoeVKorHygA==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 || ^6.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@3.1.4': + resolution: {integrity: sha512-cqv9H9GvAEoTaoq+cYqUTCGscUjKqlJZC7PRwY5FMySVj5J+xOm1KQcCiYHJOEzOKRUhLH4R2pTwvFlWCEScsg==} + + '@vitest/runner@3.1.4': + resolution: {integrity: sha512-djTeF1/vt985I/wpKVFBMWUlk/I7mb5hmD5oP8K9ACRmVXgKTae3TUOtXAEBfslNKPzUQvnKhNd34nnRSYgLNQ==} + + '@vitest/snapshot@3.1.4': + resolution: {integrity: sha512-JPHf68DvuO7vilmvwdPr9TS0SuuIzHvxeaCkxYcCD4jTk67XwL45ZhEHFKIuCm8CYstgI6LZ4XbwD6ANrwMpFg==} + + '@vitest/spy@3.1.4': + resolution: {integrity: sha512-Xg1bXhu+vtPXIodYN369M86K8shGLouNjoVI78g8iAq2rFoHFdajNvJJ5A/9bPMFcfQqdaCpOgWKEoMQg/s0Yg==} + + '@vitest/ui@3.1.4': + resolution: {integrity: sha512-CFc2Bpb3sz4Sdt53kdNGq+qZKLftBwX4qZLC03CBUc0N1LJrOoL0ZeK0oq/708mtnpwccL0BZCY9d1WuiBSr7Q==} + peerDependencies: + vitest: 3.1.4 + + '@vitest/utils@3.1.4': + resolution: {integrity: sha512-yriMuO1cfFhmiGc8ataN51+9ooHRuURdfAZfwFd3usWynjzpLslZdYnRegTv32qdgtJTsj15FoeZe2g15fY1gg==} + '@zxing/text-encoding@0.9.0': resolution: {integrity: sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==} @@ -662,6 +958,10 @@ packages: asn1.js@5.4.1: resolution: {integrity: sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==} + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + astral-regex@2.0.0: resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} engines: {node: '>=8'} @@ -772,6 +1072,10 @@ packages: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + call-bind-apply-helpers@1.0.1: resolution: {integrity: sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==} engines: {node: '>= 0.4'} @@ -788,6 +1092,10 @@ packages: resolution: {integrity: sha512-tTj3CqqukVJ9NgSahykNwtGda7V33VLObwrHfzT0vqJXu7J4d4C/7kQQW3fOEGDfZZoILPut5H00gOjyttPGyg==} engines: {node: ^18.12.0 || >= 20.9.0} + chai@5.2.0: + resolution: {integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==} + engines: {node: '>=12'} + chainsaw@0.1.0: resolution: {integrity: sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==} @@ -799,6 +1107,10 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} + check-error@2.1.1: + resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} + engines: {node: '>= 16'} + checkpoint-client@1.1.27: resolution: {integrity: sha512-xstymfUalJOv6ZvTtmkwP4ORJN36ikT4PvrIoLe3wstbYf87XIXCcZrSmbFQOjyB0v1qbBnCsAscDpfdZlCkFA==} @@ -987,6 +1299,10 @@ packages: resolution: {integrity: sha512-e7oWH1LzIdv/prMQ7pmlDlaVoL64glqzvNgkgQNgyec9ORPHrT2jaOqMtRyqJuwWjtfb6v+2rk9pmaHj+F137A==} engines: {node: '>= 16'} + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + deep-extend@0.6.0: resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} engines: {node: '>=4.0.0'} @@ -1118,6 +1434,9 @@ packages: es-get-iterator@1.1.3: resolution: {integrity: sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==} + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + es-object-atoms@1.0.0: resolution: {integrity: sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==} engines: {node: '>= 0.4'} @@ -1130,6 +1449,11 @@ packages: resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} engines: {node: '>= 0.4'} + esbuild@0.25.4: + resolution: {integrity: sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==} + engines: {node: '>=18'} + hasBin: true + escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -1141,6 +1465,9 @@ packages: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + etag@1.8.1: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} @@ -1160,6 +1487,10 @@ packages: resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} engines: {node: '>=6'} + expect-type@1.2.1: + resolution: {integrity: sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==} + engines: {node: '>=12.0.0'} + express@4.21.2: resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==} engines: {node: '>= 0.10.0'} @@ -1193,9 +1524,20 @@ packages: fastq@1.17.1: resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} + fdir@6.4.4: + resolution: {integrity: sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + fecha@4.2.3: resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==} + fflate@0.8.2: + resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} + fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} @@ -1230,6 +1572,9 @@ packages: flatstr@1.0.12: resolution: {integrity: sha512-4zPxDyhCyiN2wIAtSLI6gc82/EjqZc1onI4Mz/l0pWrAlsSfYH/2ZIcU+e3oA2wDwbzIWNKwa23F8rh6+DRWkw==} + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + fn.name@1.1.0: resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==} @@ -1772,6 +2117,9 @@ packages: resolution: {integrity: sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==} engines: {node: '>= 12.0.0'} + loupe@3.1.3: + resolution: {integrity: sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==} + lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} @@ -1786,6 +2134,9 @@ packages: resolution: {integrity: sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==} engines: {node: '>=12'} + magic-string@0.30.17: + resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + make-dir@3.1.0: resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} engines: {node: '>=8'} @@ -1912,6 +2263,10 @@ packages: resolution: {integrity: sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==} engines: {node: '>= 0.8.0'} + mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} + engines: {node: '>=10'} + ms@2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} @@ -1925,6 +2280,11 @@ packages: resolution: {integrity: sha512-VzGiVigcG9zUAoCNU+xShztrlr1auZOlurXynNvO9GiWD1/mTBbUljOKY+qMeazBqXgRnjzeEgJI/wyjJUHg9A==} engines: {node: '>= 6.0.0'} + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + napi-build-utils@2.0.0: resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==} @@ -2137,6 +2497,13 @@ packages: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + pathval@2.0.0: + resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} + engines: {node: '>= 14.16'} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -2144,6 +2511,10 @@ packages: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} + picomatch@4.0.2: + resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} + engines: {node: '>=12'} + pino-std-serializers@3.2.0: resolution: {integrity: sha512-EqX4pwDPrt3MuOAAUBMU0Tk5kR/YcCM5fNPEzgCO2zJ5HfX0vbiH9HbJglnyeQsN96Kznae6MWD47pZB5avTrg==} @@ -2159,6 +2530,10 @@ packages: resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} engines: {node: '>= 0.4'} + postcss@8.5.3: + resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==} + engines: {node: ^10 || ^12 || >=14} + prebuild-install@7.1.3: resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==} engines: {node: '>=10'} @@ -2329,6 +2704,11 @@ packages: deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true + rollup@4.41.0: + resolution: {integrity: sha512-HqMFpUbWlf/tvcxBFNKnJyzc7Lk+XO3FGc3pbNBLqEbOz0gPLRgcrlS3UF4MfUrVlstOaP/q0kM6GVvi+LrLRg==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -2428,6 +2808,9 @@ packages: resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} engines: {node: '>= 0.4'} + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} @@ -2448,6 +2831,10 @@ packages: resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==} engines: {node: '>=10'} + sirv@3.0.1: + resolution: {integrity: sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==} + engines: {node: '>=18'} + sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} @@ -2462,6 +2849,10 @@ packages: sonic-boom@1.4.1: resolution: {integrity: sha512-LRHh/A8tpW7ru89lrlkU4AszXt1dbwSjVWguGrmlxE7tawVmDBlI1PILMkXAxJTwqhgsEeTHzj36D5CmHgQmNg==} + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + source-map@0.6.1: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} @@ -2492,6 +2883,9 @@ packages: stack-trace@0.0.10: resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==} + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + stackframe@1.3.4: resolution: {integrity: sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==} @@ -2499,6 +2893,9 @@ packages: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} + std-env@3.9.0: + resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==} + stop-iteration-iterator@1.1.0: resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} engines: {node: '>= 0.4'} @@ -2617,6 +3014,28 @@ packages: through2@4.0.2: resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==} + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinyglobby@0.2.13: + resolution: {integrity: sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==} + engines: {node: '>=12.0.0'} + + tinypool@1.0.2: + resolution: {integrity: sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@2.0.0: + resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} + engines: {node: '>=14.0.0'} + + tinyspy@3.0.2: + resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} + engines: {node: '>=14.0.0'} + tmp@0.2.1: resolution: {integrity: sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==} engines: {node: '>=8.17.0'} @@ -2636,6 +3055,10 @@ packages: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} + totalist@3.0.1: + resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} + engines: {node: '>=6'} + touch@3.1.1: resolution: {integrity: sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==} hasBin: true @@ -2808,6 +3231,79 @@ packages: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} + vite-node@3.1.4: + resolution: {integrity: sha512-6enNwYnpyDo4hEgytbmc6mYWHXDHYEn0D1/rw4Q+tnHUGtKTJsn8T1YkX6Q18wI5LCrS8CTYlBaiCqxOy2kvUA==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + + vite@6.3.5: + resolution: {integrity: sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@3.1.4: + resolution: {integrity: sha512-Ta56rT7uWxCSJXlBtKgIlApJnT6e6IGmTYxYcmxjJ4ujuZDI59GUQgVDObXXJujOmPDBYXHK1qmaGtneu6TNIQ==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/debug': ^4.1.12 + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + '@vitest/browser': 3.1.4 + '@vitest/ui': 3.1.4 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/debug': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + web-encoding@1.1.5: resolution: {integrity: sha512-HYLeVCdJ0+lBYV2FvNZmv3HJ2Nt0QYXqZojk3d9FJOLkwnuhzM9tmamh8d7HPM8QqjKH8DeHkFTx+CFlWpZZDA==} @@ -2844,6 +3340,11 @@ packages: engines: {node: '>= 8'} hasBin: true + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + winston-elasticsearch@0.19.0: resolution: {integrity: sha512-yD+Wi/NmMsKCSkWvzdmk2RZ2KSHJ+ox5PM/480nsahWFtiLESI90ESXnS8Yfvc0N4NFnCXNaIj2FERIgjImjoQ==} engines: {node: '>= 8.0.0'} @@ -2997,6 +3498,81 @@ snapshots: transitivePeerDependencies: - supports-color + '@esbuild/aix-ppc64@0.25.4': + optional: true + + '@esbuild/android-arm64@0.25.4': + optional: true + + '@esbuild/android-arm@0.25.4': + optional: true + + '@esbuild/android-x64@0.25.4': + optional: true + + '@esbuild/darwin-arm64@0.25.4': + optional: true + + '@esbuild/darwin-x64@0.25.4': + optional: true + + '@esbuild/freebsd-arm64@0.25.4': + optional: true + + '@esbuild/freebsd-x64@0.25.4': + optional: true + + '@esbuild/linux-arm64@0.25.4': + optional: true + + '@esbuild/linux-arm@0.25.4': + optional: true + + '@esbuild/linux-ia32@0.25.4': + optional: true + + '@esbuild/linux-loong64@0.25.4': + optional: true + + '@esbuild/linux-mips64el@0.25.4': + optional: true + + '@esbuild/linux-ppc64@0.25.4': + optional: true + + '@esbuild/linux-riscv64@0.25.4': + optional: true + + '@esbuild/linux-s390x@0.25.4': + optional: true + + '@esbuild/linux-x64@0.25.4': + optional: true + + '@esbuild/netbsd-arm64@0.25.4': + optional: true + + '@esbuild/netbsd-x64@0.25.4': + optional: true + + '@esbuild/openbsd-arm64@0.25.4': + optional: true + + '@esbuild/openbsd-x64@0.25.4': + optional: true + + '@esbuild/sunos-x64@0.25.4': + optional: true + + '@esbuild/win32-arm64@0.25.4': + optional: true + + '@esbuild/win32-ia32@0.25.4': + optional: true + + '@esbuild/win32-x64@0.25.4': + optional: true + '@fast-csv/format@4.3.5': dependencies: '@types/node': 14.18.63 @@ -3255,6 +3831,8 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true + '@polka/url@1.0.0-next.29': {} + '@prisma/client@6.3.0(prisma@6.3.0(typescript@5.7.2))(typescript@5.7.2)': optionalDependencies: prisma: 6.3.0(typescript@5.7.2) @@ -3388,6 +3966,66 @@ snapshots: '@prisma/prisma-schema-wasm@5.3.1-2.61e140623197a131c2a6189271ffee05a7aa9a59': {} + '@rollup/rollup-android-arm-eabi@4.41.0': + optional: true + + '@rollup/rollup-android-arm64@4.41.0': + optional: true + + '@rollup/rollup-darwin-arm64@4.41.0': + optional: true + + '@rollup/rollup-darwin-x64@4.41.0': + optional: true + + '@rollup/rollup-freebsd-arm64@4.41.0': + optional: true + + '@rollup/rollup-freebsd-x64@4.41.0': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.41.0': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.41.0': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.41.0': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.41.0': + optional: true + + '@rollup/rollup-linux-loongarch64-gnu@4.41.0': + optional: true + + '@rollup/rollup-linux-powerpc64le-gnu@4.41.0': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.41.0': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.41.0': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.41.0': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.41.0': + optional: true + + '@rollup/rollup-linux-x64-musl@4.41.0': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.41.0': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.41.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.41.0': + optional: true + '@scalar/express-api-reference@0.4.182': dependencies: '@scalar/types': 0.0.33 @@ -3482,6 +4120,8 @@ snapshots: dependencies: '@types/ms': 0.7.34 + '@types/estree@1.0.7': {} + '@types/express-serve-static-core@4.19.6': dependencies: '@types/node': 20.17.10 @@ -3567,6 +4207,57 @@ snapshots: hookable: 5.5.3 zhead: 2.2.4 + '@vitest/expect@3.1.4': + dependencies: + '@vitest/spy': 3.1.4 + '@vitest/utils': 3.1.4 + chai: 5.2.0 + tinyrainbow: 2.0.0 + + '@vitest/mocker@3.1.4(vite@6.3.5(@types/node@20.17.10)(yaml@2.6.1))': + dependencies: + '@vitest/spy': 3.1.4 + estree-walker: 3.0.3 + magic-string: 0.30.17 + optionalDependencies: + vite: 6.3.5(@types/node@20.17.10)(yaml@2.6.1) + + '@vitest/pretty-format@3.1.4': + dependencies: + tinyrainbow: 2.0.0 + + '@vitest/runner@3.1.4': + dependencies: + '@vitest/utils': 3.1.4 + pathe: 2.0.3 + + '@vitest/snapshot@3.1.4': + dependencies: + '@vitest/pretty-format': 3.1.4 + magic-string: 0.30.17 + pathe: 2.0.3 + + '@vitest/spy@3.1.4': + dependencies: + tinyspy: 3.0.2 + + '@vitest/ui@3.1.4(vitest@3.1.4)': + dependencies: + '@vitest/utils': 3.1.4 + fflate: 0.8.2 + flatted: 3.3.3 + pathe: 2.0.3 + sirv: 3.0.1 + tinyglobby: 0.2.13 + tinyrainbow: 2.0.0 + vitest: 3.1.4(@types/node@20.17.10)(@vitest/ui@3.1.4)(yaml@2.6.1) + + '@vitest/utils@3.1.4': + dependencies: + '@vitest/pretty-format': 3.1.4 + loupe: 3.1.3 + tinyrainbow: 2.0.0 + '@zxing/text-encoding@0.9.0': optional: true @@ -3715,6 +4406,8 @@ snapshots: minimalistic-assert: 1.0.1 safer-buffer: 2.1.2 + assertion-error@2.0.1: {} + astral-regex@2.0.0: {} async-cache@1.1.0: @@ -3831,6 +4524,8 @@ snapshots: bytes@3.1.2: {} + cac@6.7.14: {} + call-bind-apply-helpers@1.0.1: dependencies: es-errors: 1.3.0 @@ -3853,6 +4548,14 @@ snapshots: node-addon-api: 7.1.1 prebuild-install: 7.1.3 + chai@5.2.0: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.1 + deep-eql: 5.0.2 + loupe: 3.1.3 + pathval: 2.0.0 + chainsaw@0.1.0: dependencies: traverse: 0.3.9 @@ -3866,6 +4569,8 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 + check-error@2.1.1: {} + checkpoint-client@1.1.27(encoding@0.1.13): dependencies: ci-info: 3.8.0 @@ -4072,6 +4777,8 @@ snapshots: deeks@3.1.0: {} + deep-eql@5.0.2: {} + deep-extend@0.6.0: {} define-data-property@1.1.4: @@ -4293,6 +5000,8 @@ snapshots: isarray: 2.0.5 stop-iteration-iterator: 1.1.0 + es-module-lexer@1.7.0: {} + es-object-atoms@1.0.0: dependencies: es-errors: 1.3.0 @@ -4309,12 +5018,44 @@ snapshots: is-date-object: 1.1.0 is-symbol: 1.1.1 + esbuild@0.25.4: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.4 + '@esbuild/android-arm': 0.25.4 + '@esbuild/android-arm64': 0.25.4 + '@esbuild/android-x64': 0.25.4 + '@esbuild/darwin-arm64': 0.25.4 + '@esbuild/darwin-x64': 0.25.4 + '@esbuild/freebsd-arm64': 0.25.4 + '@esbuild/freebsd-x64': 0.25.4 + '@esbuild/linux-arm': 0.25.4 + '@esbuild/linux-arm64': 0.25.4 + '@esbuild/linux-ia32': 0.25.4 + '@esbuild/linux-loong64': 0.25.4 + '@esbuild/linux-mips64el': 0.25.4 + '@esbuild/linux-ppc64': 0.25.4 + '@esbuild/linux-riscv64': 0.25.4 + '@esbuild/linux-s390x': 0.25.4 + '@esbuild/linux-x64': 0.25.4 + '@esbuild/netbsd-arm64': 0.25.4 + '@esbuild/netbsd-x64': 0.25.4 + '@esbuild/openbsd-arm64': 0.25.4 + '@esbuild/openbsd-x64': 0.25.4 + '@esbuild/sunos-x64': 0.25.4 + '@esbuild/win32-arm64': 0.25.4 + '@esbuild/win32-ia32': 0.25.4 + '@esbuild/win32-x64': 0.25.4 + escalade@3.2.0: {} escape-html@1.0.3: {} escape-string-regexp@4.0.0: {} + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.7 + etag@1.8.1: {} eventemitter3@5.0.1: {} @@ -4345,6 +5086,8 @@ snapshots: expand-template@2.0.3: {} + expect-type@1.2.1: {} + express@4.21.2: dependencies: accepts: 1.3.8 @@ -4420,8 +5163,14 @@ snapshots: dependencies: reusify: 1.0.4 + fdir@6.4.4(picomatch@4.0.2): + optionalDependencies: + picomatch: 4.0.2 + fecha@4.2.3: {} + fflate@0.8.2: {} + fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 @@ -4465,6 +5214,8 @@ snapshots: flatstr@1.0.12: optional: true + flatted@3.3.3: {} + fn.name@1.1.0: {} for-each@0.3.3: @@ -5000,6 +5751,8 @@ snapshots: safe-stable-stringify: 2.5.0 triple-beam: 1.4.1 + loupe@3.1.3: {} + lru-cache@10.4.3: {} lru-cache@4.1.5: @@ -5015,6 +5768,10 @@ snapshots: luxon@3.5.0: {} + magic-string@0.30.17: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + make-dir@3.1.0: dependencies: semver: 6.3.1 @@ -5140,6 +5897,8 @@ snapshots: transitivePeerDependencies: - supports-color + mrmime@2.0.1: {} + ms@2.0.0: {} ms@2.1.2: {} @@ -5156,6 +5915,8 @@ snapshots: type-is: 1.6.18 xtend: 4.0.2 + nanoid@3.3.11: {} + napi-build-utils@2.0.0: {} negotiator@0.6.3: {} @@ -5354,10 +6115,16 @@ snapshots: path-type@4.0.0: {} + pathe@2.0.3: {} + + pathval@2.0.0: {} + picocolors@1.1.1: {} picomatch@2.3.1: {} + picomatch@4.0.2: {} + pino-std-serializers@3.2.0: optional: true @@ -5378,6 +6145,12 @@ snapshots: possible-typed-array-names@1.0.0: {} + postcss@8.5.3: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + prebuild-install@7.1.3: dependencies: detect-libc: 2.0.4 @@ -5593,6 +6366,32 @@ snapshots: dependencies: glob: 7.2.3 + rollup@4.41.0: + dependencies: + '@types/estree': 1.0.7 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.41.0 + '@rollup/rollup-android-arm64': 4.41.0 + '@rollup/rollup-darwin-arm64': 4.41.0 + '@rollup/rollup-darwin-x64': 4.41.0 + '@rollup/rollup-freebsd-arm64': 4.41.0 + '@rollup/rollup-freebsd-x64': 4.41.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.41.0 + '@rollup/rollup-linux-arm-musleabihf': 4.41.0 + '@rollup/rollup-linux-arm64-gnu': 4.41.0 + '@rollup/rollup-linux-arm64-musl': 4.41.0 + '@rollup/rollup-linux-loongarch64-gnu': 4.41.0 + '@rollup/rollup-linux-powerpc64le-gnu': 4.41.0 + '@rollup/rollup-linux-riscv64-gnu': 4.41.0 + '@rollup/rollup-linux-riscv64-musl': 4.41.0 + '@rollup/rollup-linux-s390x-gnu': 4.41.0 + '@rollup/rollup-linux-x64-gnu': 4.41.0 + '@rollup/rollup-linux-x64-musl': 4.41.0 + '@rollup/rollup-win32-arm64-msvc': 4.41.0 + '@rollup/rollup-win32-ia32-msvc': 4.41.0 + '@rollup/rollup-win32-x64-msvc': 4.41.0 + fsevents: 2.3.3 + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 @@ -5719,6 +6518,8 @@ snapshots: side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 + siginfo@2.0.0: {} + signal-exit@3.0.7: {} signal-exit@4.1.0: {} @@ -5739,6 +6540,12 @@ snapshots: dependencies: semver: 7.6.3 + sirv@3.0.1: + dependencies: + '@polka/url': 1.0.0-next.29 + mrmime: 2.0.1 + totalist: 3.0.1 + sisteransi@1.0.5: {} slash@3.0.0: {} @@ -5755,6 +6562,8 @@ snapshots: flatstr: 1.0.12 optional: true + source-map-js@1.2.1: {} + source-map@0.6.1: {} source-map@0.8.0-beta.0: @@ -5783,11 +6592,15 @@ snapshots: stack-trace@0.0.10: {} + stackback@0.0.2: {} + stackframe@1.3.4: optional: true statuses@2.0.1: {} + std-env@3.9.0: {} + stop-iteration-iterator@1.1.0: dependencies: es-errors: 1.3.0 @@ -5927,6 +6740,21 @@ snapshots: dependencies: readable-stream: 3.6.2 + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} + + tinyglobby@0.2.13: + dependencies: + fdir: 6.4.4(picomatch@4.0.2) + picomatch: 4.0.2 + + tinypool@1.0.2: {} + + tinyrainbow@2.0.0: {} + + tinyspy@3.0.2: {} + tmp@0.2.1: dependencies: rimraf: 3.0.2 @@ -5944,6 +6772,8 @@ snapshots: toidentifier@1.0.1: {} + totalist@3.0.1: {} + touch@3.1.1: {} tr46@0.0.3: {} @@ -6119,6 +6949,80 @@ snapshots: vary@1.1.2: {} + vite-node@3.1.4(@types/node@20.17.10)(yaml@2.6.1): + dependencies: + cac: 6.7.14 + debug: 4.4.0(supports-color@5.5.0) + es-module-lexer: 1.7.0 + pathe: 2.0.3 + vite: 6.3.5(@types/node@20.17.10)(yaml@2.6.1) + transitivePeerDependencies: + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + vite@6.3.5(@types/node@20.17.10)(yaml@2.6.1): + dependencies: + esbuild: 0.25.4 + fdir: 6.4.4(picomatch@4.0.2) + picomatch: 4.0.2 + postcss: 8.5.3 + rollup: 4.41.0 + tinyglobby: 0.2.13 + optionalDependencies: + '@types/node': 20.17.10 + fsevents: 2.3.3 + yaml: 2.6.1 + + vitest@3.1.4(@types/node@20.17.10)(@vitest/ui@3.1.4)(yaml@2.6.1): + dependencies: + '@vitest/expect': 3.1.4 + '@vitest/mocker': 3.1.4(vite@6.3.5(@types/node@20.17.10)(yaml@2.6.1)) + '@vitest/pretty-format': 3.1.4 + '@vitest/runner': 3.1.4 + '@vitest/snapshot': 3.1.4 + '@vitest/spy': 3.1.4 + '@vitest/utils': 3.1.4 + chai: 5.2.0 + debug: 4.4.0(supports-color@5.5.0) + expect-type: 1.2.1 + magic-string: 0.30.17 + pathe: 2.0.3 + std-env: 3.9.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.13 + tinypool: 1.0.2 + tinyrainbow: 2.0.0 + vite: 6.3.5(@types/node@20.17.10)(yaml@2.6.1) + vite-node: 3.1.4(@types/node@20.17.10)(yaml@2.6.1) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 20.17.10 + '@vitest/ui': 3.1.4(vitest@3.1.4) + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + web-encoding@1.1.5: dependencies: util: 0.12.5 @@ -6185,6 +7089,11 @@ snapshots: dependencies: isexe: 2.0.0 + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + winston-elasticsearch@0.19.0: dependencies: '@elastic/elasticsearch': 8.17.0 diff --git a/test/branch.test.ts b/test/branch.test.ts new file mode 100644 index 0000000..25226d5 --- /dev/null +++ b/test/branch.test.ts @@ -0,0 +1,323 @@ +import { afterAll, beforeAll, describe, expect, it, onTestFailed } from "vitest"; +import { PrismaClient } from "@prisma/client"; + +import { isDateString } from "./lib"; + +const prisma = new PrismaClient({ + datasourceUrl: process.env.TEST_DATABASE_URL || process.env.DATABASE_URL, +}); +const baseUrl = process.env.TEST_BASE_URL || "http://localhost"; +const record: Record = { + code: "CMT", + taxNo: "1052299402851", + name: "Chamomind", + nameEN: "Chamomind", + email: "contact@chamomind.com", + lineId: "@chamomind", + telephoneNo: "0988929248", + contactName: "John", + webUrl: "https://chamomind.com", + latitude: "", + longitude: "", + virtual: false, + permitNo: "1135182804792", + permitIssueDate: "2025-01-01T00:00:00.000Z", + permitExpireDate: "2030-01-01T00:00:00.000Z", + address: "11/3", + addressEN: "11/3", + soi: "1", + soiEN: "1", + moo: "2", + mooEN: "2", + street: "Straight", + streetEN: "Straight", + provinceId: "50", + districtId: "5001", + subDistrictId: "500107", +}; +const recordList: Record[] = []; + +let token: string; + +beforeAll(async () => { + const body = new URLSearchParams(); + + body.append("grant_type", "password"); + body.append("client_id", "app"); + body.append("username", process.env.TEST_USERNAME || ""); + body.append("password", process.env.TEST_PASSWORD || ""); + body.append("scope", "openid"); + + const res = await fetch( + process.env.KC_URL + "/realms/" + process.env.KC_REALM + "/protocol/openid-connect/token", + { + method: "POST", + body: body, + }, + ); + + expect(res.ok).toBe(true); + + await res.json().then((data) => { + token = data["access_token"]; + }); +}); + +afterAll(async () => { + if (!record["id"]) return; + + await prisma.branch.deleteMany({ + where: { id: { in: [record, ...recordList].map((v) => v["id"]) } }, + }); + await prisma.runningNo.deleteMany({ + where: { + key: { in: [record, ...recordList].map((v) => `MAIN_BRANCH_${v["code"].slice(0, -5)}`) }, + }, + }); +}); + +describe("branch management", () => { + it("create branch without required fields", async () => { + const requiredFields = [ + "taxNo", + "name", + "nameEN", + "permitNo", + "telephoneNo", + "address", + "addressEN", + "email", + ]; + onTestFailed(() => console.log("Field:", requiredFields, "is required.")); + + for await (const field of requiredFields) { + const res = await fetch(baseUrl + "/api/v1/branch", { + method: "POST", + headers: { + ["Content-Type"]: "application/json", + ["Authorization"]: "Bearer " + token, + }, + body: JSON.stringify({ ...record, [field]: undefined }), + }); + + if (res.ok) recordList.push(await res.json()); + + expect(res.ok).toBe(false); + expect(res.status).toBe(400); + } + }); + + it("create branch", async () => { + const res = await fetch(baseUrl + "/api/v1/branch", { + method: "POST", + headers: { + ["Content-Type"]: "application/json", + ["Authorization"]: "Bearer " + token, + }, + body: JSON.stringify(record), + }); + if (!res.ok) { + const text = await res.text(); + try { + console.log(JSON.parse(text)); + } catch (e) { + console.log(text); + } + } + + expect(res.ok).toBe(true); + + const data = await res.json(); + + record["id"] = data["id"]; // This field is auto generated + record["code"] = data["code"]; // This field is auto generated + + recordList.push(data); + + expect(data).toMatchObject(record); + }); + + it("get branch list", async () => { + const res = await fetch(baseUrl + "/api/v1/branch", { + method: "GET", + headers: { + ["Authorization"]: "Bearer " + token, + }, + }); + if (!res.ok) { + const text = await res.text(); + try { + console.log(JSON.parse(text)); + } catch (e) { + console.log(text); + } + } + + expect(res.ok).toBe(true); + + const data = await res.json(); + + expect(data).toHaveProperty("result"); + expect(data).toHaveProperty("total"); + expect(data).toHaveProperty("page"); + expect(data).toHaveProperty("pageSize"); + expect(data.result).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: expect.any(String), + code: expect.any(String), + virtual: expect.any(Boolean), + name: expect.any(String), + nameEN: expect.any(String), + email: expect.any(String), + taxNo: expect.any(String), + telephoneNo: expect.any(String), + latitude: expect.any(String), + longitude: expect.any(String), + contactName: expect.toBeOneOf([expect.any(String), null]), + lineId: expect.toBeOneOf([expect.any(String), null]), + webUrl: expect.toBeOneOf([expect.any(String), null]), + remark: expect.toBeOneOf([expect.any(String), null]), + selectedImage: expect.toBeOneOf([expect.any(String), null]), + + isHeadOffice: expect.any(Boolean), + + permitNo: expect.any(String), + permitIssueDate: expect.toSatisfy(isDateString(true)), + permitExpireDate: expect.toSatisfy(isDateString(true)), + + address: expect.any(String), + addressEN: expect.any(String), + moo: expect.toBeOneOf([expect.any(String), null]), + mooEN: expect.toBeOneOf([expect.any(String), null]), + street: expect.toBeOneOf([expect.any(String), null]), + streetEN: expect.toBeOneOf([expect.any(String), null]), + provinceId: expect.any(String), + province: expect.objectContaining({ + id: expect.any(String), + name: expect.any(String), + nameEN: expect.any(String), + }), + districtId: expect.any(String), + district: expect.objectContaining({ + id: expect.any(String), + name: expect.any(String), + nameEN: expect.any(String), + }), + subDistrictId: expect.any(String), + subDistrict: expect.objectContaining({ + id: expect.any(String), + name: expect.any(String), + nameEN: expect.any(String), + zipCode: expect.any(String), + }), + + status: expect.toBeOneOf(["CREATED", "ACTIVE", "INACTIVE"]), + statusOrder: expect.toBeOneOf([1, 0]), + + createdAt: expect.toSatisfy(isDateString()), + createdByUserId: expect.toBeOneOf([expect.any(String), null]), + createdBy: expect.objectContaining({ + id: expect.any(String), + username: expect.any(String), + firstName: expect.any(String), + lastName: expect.any(String), + firstNameEN: expect.any(String), + lastNameEN: expect.any(String), + }), + updatedAt: expect.toSatisfy(isDateString()), + updatedByUserId: expect.toBeOneOf([expect.any(String), null]), + updatedBy: expect.objectContaining({ + id: expect.any(String), + username: expect.any(String), + firstName: expect.any(String), + lastName: expect.any(String), + firstNameEN: expect.any(String), + lastNameEN: expect.any(String), + }), + + _count: expect.objectContaining({ + branch: expect.any(Number), + }), + }), + ]), + ); + }); + + it("get branch by id", async () => { + const res = await fetch(baseUrl + "/api/v1/branch/" + record["id"], { + method: "GET", + headers: { + ["Authorization"]: "Bearer " + token, + }, + }); + if (!res.ok) { + const text = await res.text(); + try { + console.log(JSON.parse(text)); + } catch (e) { + console.log(text); + } + } + + expect(res.ok).toBe(true); + + const data = await res.json(); + + expect(data).toMatchObject(record); + }); + + it("update branch by id", async () => { + const res = await fetch(baseUrl + "/api/v1/branch/" + record["id"], { + method: "PUT", + headers: { + ["Content-Type"]: "application/json", + ["Authorization"]: "Bearer " + token, + }, + body: JSON.stringify({ name: "Chamomind Intl.", nameEN: "Chamomind Intl." }), + }); + + record["name"] = "Chamomind Intl."; + record["nameEN"] = "Chamomind Intl."; + + expect(res.ok).toBe(true); + + const data = await res.json(); + + expect(data).toMatchObject(record); + }); + + it("delete branch by id", async () => { + const res = await fetch(baseUrl + "/api/v1/branch/" + record["id"], { + method: "DELETE", + headers: { + ["Content-Type"]: "application/json", + ["Authorization"]: "Bearer " + token, + }, + }); + if (!res.ok) { + const text = await res.text(); + try { + console.log(JSON.parse(text)); + } catch (e) { + console.log(text); + } + } + expect(res.ok).toBe(true); + + const data = await res.json(); + + expect(data).toMatchObject(record); + }); + + it("get deleted branch by id", async () => { + const res = await fetch(baseUrl + "/api/v1/branch/" + record["id"], { + method: "GET", + headers: { + ["Authorization"]: "Bearer " + token, + }, + }); + + expect(res.ok).toBe(false); + }); +}); diff --git a/test/lib/index.ts b/test/lib/index.ts new file mode 100644 index 0000000..ed9d771 --- /dev/null +++ b/test/lib/index.ts @@ -0,0 +1,10 @@ +export function isDateString(nullable: boolean = false): (val: any) => boolean { + return (value: any) => { + try { + if (value) return !!new Date(value); + return nullable; + } catch (_) { + return false; + } + }; +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..77a73cf --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,5 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: {}, +}); From e02a29f05319fb7ca34940c54c39f8d47ec398f2 Mon Sep 17 00:00:00 2001 From: Kanjana Date: Fri, 30 May 2025 13:05:33 +0700 Subject: [PATCH 050/167] fix : add type null in otherNationality --- src/controllers/03-employee-passport-controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/03-employee-passport-controller.ts b/src/controllers/03-employee-passport-controller.ts index 12e0b01..6af121c 100644 --- a/src/controllers/03-employee-passport-controller.ts +++ b/src/controllers/03-employee-passport-controller.ts @@ -43,7 +43,7 @@ type EmployeePassportPayload = { workerStatus: string; nationality: string; - otherNationality?: string; + otherNationality?: string | null; namePrefix?: string | null; firstName: string; firstNameEN: string; From c430fc3c7a7c09db961add6e7909d234e5d07fc3 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 11 Jun 2025 10:25:19 +0700 Subject: [PATCH 051/167] fix: missing zip code when issue doc --- src/controllers/00-doc-template-controller.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/controllers/00-doc-template-controller.ts b/src/controllers/00-doc-template-controller.ts index 88b358f..7f05110 100644 --- a/src/controllers/00-doc-template-controller.ts +++ b/src/controllers/00-doc-template-controller.ts @@ -353,6 +353,8 @@ function addressFull(addr: FullAddress, lang: "th" | "en" = "en") { break; } + if (addr.subDistrict) fragments.push(addr.subDistrict.zipCode); + return fragments.join(" "); } From 5262ad2a6367f853cf6f25d7f1abf74ca16a507c Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Mon, 30 Jun 2025 13:53:07 +0700 Subject: [PATCH 052/167] fix: branch order --- src/controllers/01-branch-controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/01-branch-controller.ts b/src/controllers/01-branch-controller.ts index da4c958..7a19c45 100644 --- a/src/controllers/01-branch-controller.ts +++ b/src/controllers/01-branch-controller.ts @@ -326,7 +326,7 @@ export class BranchController extends Controller { district: true, subDistrict: true, }, - orderBy: { code: "asc" }, + orderBy: [{ statusOrder: "asc" }, { code: "asc" }], } : false, bank: true, From 7bd685ea969749a4125b3b3db7109f78b192a08e Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Mon, 30 Jun 2025 14:05:41 +0700 Subject: [PATCH 053/167] fix: relation order get by id --- src/controllers/01-branch-controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/01-branch-controller.ts b/src/controllers/01-branch-controller.ts index 7a19c45..a10352d 100644 --- a/src/controllers/01-branch-controller.ts +++ b/src/controllers/01-branch-controller.ts @@ -370,7 +370,7 @@ export class BranchController extends Controller { bank: true, contact: includeContact, }, - orderBy: { code: "asc" }, + orderBy: [{ statusOrder: "asc" }, { code: "asc" }], }, bank: true, contact: includeContact, From a69962db48fa2c2217ecd5c7e06e9707bc109ceb Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Mon, 30 Jun 2025 15:38:16 +0700 Subject: [PATCH 054/167] fix: error validation --- src/controllers/02-user-controller.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/controllers/02-user-controller.ts b/src/controllers/02-user-controller.ts index 1a789ff..b8b70ac 100644 --- a/src/controllers/02-user-controller.ts +++ b/src/controllers/02-user-controller.ts @@ -144,9 +144,9 @@ type UserUpdate = { namePrefix?: string | null; firstName?: string; - firstNameEN: string; + firstNameEN?: string; middleName?: string | null; - middleNameEN: string | null; + middleNameEN?: string | null; lastName?: string; lastNameEN?: string; gender?: string; From 1a33080ac861b9bb9d080ef420f01c9d535aa3fa Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Mon, 30 Jun 2025 16:13:57 +0700 Subject: [PATCH 055/167] fix: relation constraint --- prisma/schema.prisma | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 47f82d6..7639cf2 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -370,7 +370,7 @@ model UserImportNationality { id String @id @default(cuid()) name String - user User @relation(fields: [userId], references: [id]) + user User @relation(fields: [userId], references: [id], onDelete: Cascade) userId String } From acd6bb35e90438263f8dd8ac1b351fe0eb70b5e7 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Mon, 30 Jun 2025 16:14:36 +0700 Subject: [PATCH 056/167] chore: migration --- .../20250630091430_update_constraint/migration.sql | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 prisma/migrations/20250630091430_update_constraint/migration.sql diff --git a/prisma/migrations/20250630091430_update_constraint/migration.sql b/prisma/migrations/20250630091430_update_constraint/migration.sql new file mode 100644 index 0000000..5bd662c --- /dev/null +++ b/prisma/migrations/20250630091430_update_constraint/migration.sql @@ -0,0 +1,5 @@ +-- DropForeignKey +ALTER TABLE "UserImportNationality" DROP CONSTRAINT "UserImportNationality_userId_fkey"; + +-- AddForeignKey +ALTER TABLE "UserImportNationality" ADD CONSTRAINT "UserImportNationality_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; From 15381c089cc1baac7f0d0c9701a9295f2295153c Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Thu, 3 Jul 2025 10:34:51 +0700 Subject: [PATCH 057/167] fix: employee stats not working as expected --- src/controllers/03-employee-controller.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/controllers/03-employee-controller.ts b/src/controllers/03-employee-controller.ts index 95addd7..a6d67c1 100644 --- a/src/controllers/03-employee-controller.ts +++ b/src/controllers/03-employee-controller.ts @@ -144,9 +144,18 @@ type EmployeeUpdate = { export class EmployeeController extends Controller { @Get("stats") @Security("keycloak") - async getEmployeeStats(@Query() customerBranchId?: string) { + async getEmployeeStats(@Request() req: RequestWithUser, @Query() customerBranchId?: string) { return await prisma.employee.count({ - where: { customerBranchId }, + where: { + customerBranchId, + customerBranch: { + customer: isSystem(req.user) + ? undefined + : { + registeredBranch: { OR: permissionCond(req.user) }, + }, + }, + }, }); } From 425e99bfdecc1b57f7a553427e9a9ede96e144ec Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 2 Jul 2025 10:26:57 +0700 Subject: [PATCH 058/167] feat(perm): update api branch perm --- src/controllers/01-branch-controller.ts | 22 ++++++++++++-------- src/controllers/01-branch-user-controller.ts | 13 ++++++++++-- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/src/controllers/01-branch-controller.ts b/src/controllers/01-branch-controller.ts index a10352d..a09e24b 100644 --- a/src/controllers/01-branch-controller.ts +++ b/src/controllers/01-branch-controller.ts @@ -47,16 +47,20 @@ if (!process.env.MINIO_BUCKET) { throw Error("Require MinIO bucket."); } -const MANAGE_ROLES = ["system", "head_of_admin"]; +const MANAGE_ROLES = [ + "system", + "head_of_admin", + "admin", + "executive", + "accountant", + "branch_admin", + "branch_manager", + "branch_accountant", +]; function globalAllow(user: RequestWithUser["user"]) { - return MANAGE_ROLES.some((v) => user.roles?.includes(v)); -} - -function globalAllowView(user: RequestWithUser["user"]) { - return MANAGE_ROLES.concat("head_of_accountant", "head_of_sale").some((v) => - user.roles?.includes(v), - ); + const listAllowed = ["system", "head_of_admin", "admin", "executive", "accountant"]; + return user.roles?.some((v) => listAllowed.includes(v)) || false; } type BranchCreate = { @@ -147,7 +151,7 @@ type BranchUpdate = { }[]; }; -const permissionCond = createPermCondition(globalAllowView); +const permissionCond = createPermCondition(globalAllow); const permissionCheck = createPermCheck(globalAllow); @Route("api/v1/branch") diff --git a/src/controllers/01-branch-user-controller.ts b/src/controllers/01-branch-user-controller.ts index 05177d2..d74742f 100644 --- a/src/controllers/01-branch-user-controller.ts +++ b/src/controllers/01-branch-user-controller.ts @@ -20,10 +20,19 @@ import { RequestWithUser } from "../interfaces/user"; import { branchRelationPermInclude, createPermCheck } from "../services/permission"; import { queryOrNot, whereDateQuery } from "../utils/relation"; -const MANAGE_ROLES = ["system", "head_of_admin", "admin", "branch_manager"]; +const MANAGE_ROLES = [ + "system", + "head_of_admin", + "admin", + "executive", + "accountant", + "branch_admin", + "branch_manager", + "branch_accountant", +]; function globalAllow(user: RequestWithUser["user"]) { - const listAllowed = ["system", "head_of_admin", "admin"]; + const listAllowed = ["system", "head_of_admin", "admin", "executive", "accountant"]; return user.roles?.some((v) => listAllowed.includes(v)) || false; } From 41f5de7fd02ccabc250ddf0ffd43964484e6ce12 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 2 Jul 2025 10:27:06 +0700 Subject: [PATCH 059/167] feat(perm): update api user perm --- src/controllers/02-user-controller.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/controllers/02-user-controller.ts b/src/controllers/02-user-controller.ts index b8b70ac..143eb71 100644 --- a/src/controllers/02-user-controller.ts +++ b/src/controllers/02-user-controller.ts @@ -61,10 +61,17 @@ if (!process.env.MINIO_BUCKET) { throw Error("Require MinIO bucket."); } -const MANAGE_ROLES = ["system", "head_of_admin", "admin", "branch_manager"]; +const MANAGE_ROLES = [ + "system", + "head_of_admin", + "admin", + "executive", + "branch_admin", + "branch_manager", +]; function globalAllow(user: RequestWithUser["user"]) { - const listAllowed = ["system", "head_of_admin"]; + const listAllowed = ["system", "head_of_admin", "admin", "executive"]; return user.roles?.some((v) => listAllowed.includes(v)) || false; } From fa95fe46a5c898f4107f676bce51f5ca272ff143 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 2 Jul 2025 10:27:24 +0700 Subject: [PATCH 060/167] feat(perm): update api customer/employee perm --- .../03-customer-branch-citizen-controller.ts | 11 ++++++----- src/controllers/03-customer-branch-controller.ts | 11 ++++++----- src/controllers/03-customer-controller.ts | 11 ++++++----- src/controllers/03-employee-checkup-controller.ts | 10 ++++++---- src/controllers/03-employee-controller.ts | 10 ++++++---- src/controllers/03-employee-other-info-controller.ts | 10 ++++++---- src/controllers/03-employee-passport-controller.ts | 10 ++++++---- src/controllers/03-employee-visa-controller.ts | 10 ++++++---- src/controllers/03-employee-work-controller.ts | 10 ++++++---- 9 files changed, 54 insertions(+), 39 deletions(-) diff --git a/src/controllers/03-customer-branch-citizen-controller.ts b/src/controllers/03-customer-branch-citizen-controller.ts index 75110c3..f17b746 100644 --- a/src/controllers/03-customer-branch-citizen-controller.ts +++ b/src/controllers/03-customer-branch-citizen-controller.ts @@ -23,15 +23,16 @@ const MANAGE_ROLES = [ "system", "head_of_admin", "admin", - "head_of_accountant", + "executive", "accountant", - "head_of_sale", - "sale", + "branch_admin", + "branch_manager", + "branch_accountant", ]; function globalAllow(user: RequestWithUser["user"]) { - const allowList = ["system", "head_of_admin", "admin", "head_of_accountant", "head_of_sale"]; - return allowList.some((v) => user.roles?.includes(v)); + const listAllowed = ["system", "head_of_admin", "admin", "executive", "accountant"]; + return user.roles?.some((v) => listAllowed.includes(v)) || false; } type CustomerBranchCitizenPayload = { diff --git a/src/controllers/03-customer-branch-controller.ts b/src/controllers/03-customer-branch-controller.ts index 5c94a2f..529700b 100644 --- a/src/controllers/03-customer-branch-controller.ts +++ b/src/controllers/03-customer-branch-controller.ts @@ -47,15 +47,16 @@ const MANAGE_ROLES = [ "system", "head_of_admin", "admin", - "head_of_accountant", + "executive", "accountant", - "head_of_sale", - "sale", + "branch_admin", + "branch_manager", + "branch_accountant", ]; function globalAllow(user: RequestWithUser["user"]) { - const allowList = ["system", "head_of_admin", "head_of_accountant", "head_of_sale"]; - return allowList.some((v) => user.roles?.includes(v)); + const listAllowed = ["system", "head_of_admin", "admin", "executive", "accountant"]; + return user.roles?.some((v) => listAllowed.includes(v)) || false; } const permissionCondCompany = createPermCondition((_) => true); diff --git a/src/controllers/03-customer-controller.ts b/src/controllers/03-customer-controller.ts index 122726f..bfc327d 100644 --- a/src/controllers/03-customer-controller.ts +++ b/src/controllers/03-customer-controller.ts @@ -42,15 +42,16 @@ const MANAGE_ROLES = [ "system", "head_of_admin", "admin", - "head_of_accountant", + "executive", "accountant", - "head_of_sale", - "sale", + "branch_admin", + "branch_manager", + "branch_accountant", ]; function globalAllow(user: RequestWithUser["user"]) { - const allowList = ["system", "head_of_admin", "head_of_accountant", "head_of_sale"]; - return allowList.some((v) => user.roles?.includes(v)); + const listAllowed = ["system", "head_of_admin", "admin", "executive", "accountant"]; + return user.roles?.some((v) => listAllowed.includes(v)) || false; } const permissionCondCompany = createPermCondition((_) => true); diff --git a/src/controllers/03-employee-checkup-controller.ts b/src/controllers/03-employee-checkup-controller.ts index a72ad2d..71ceafd 100644 --- a/src/controllers/03-employee-checkup-controller.ts +++ b/src/controllers/03-employee-checkup-controller.ts @@ -23,14 +23,16 @@ const MANAGE_ROLES = [ "system", "head_of_admin", "admin", - "head_of_accountant", + "executive", "accountant", - "head_of_sale", + "branch_admin", + "branch_manager", + "branch_accountant", ]; function globalAllow(user: RequestWithUser["user"]) { - const allowList = ["system", "head_of_admin", "head_of_accountant", "head_of_sale"]; - return allowList.some((v) => user.roles?.includes(v)); + const listAllowed = ["system", "head_of_admin", "admin", "executive", "accountant"]; + return user.roles?.some((v) => listAllowed.includes(v)) || false; } type EmployeeCheckupPayload = { diff --git a/src/controllers/03-employee-controller.ts b/src/controllers/03-employee-controller.ts index a6d67c1..797a994 100644 --- a/src/controllers/03-employee-controller.ts +++ b/src/controllers/03-employee-controller.ts @@ -51,14 +51,16 @@ const MANAGE_ROLES = [ "system", "head_of_admin", "admin", - "head_of_accountant", + "executive", "accountant", - "head_of_sale", + "branch_admin", + "branch_manager", + "branch_accountant", ]; function globalAllow(user: RequestWithUser["user"]) { - const allowList = ["system", "head_of_admin", "head_of_accountant", "head_of_sale"]; - return allowList.some((v) => user.roles?.includes(v)); + const listAllowed = ["system", "head_of_admin", "admin", "executive", "accountant"]; + return user.roles?.some((v) => listAllowed.includes(v)) || false; } const permissionCond = createPermCondition(globalAllow); diff --git a/src/controllers/03-employee-other-info-controller.ts b/src/controllers/03-employee-other-info-controller.ts index d6b73e7..bc85efc 100644 --- a/src/controllers/03-employee-other-info-controller.ts +++ b/src/controllers/03-employee-other-info-controller.ts @@ -23,14 +23,16 @@ const MANAGE_ROLES = [ "system", "head_of_admin", "admin", - "head_of_accountant", + "executive", "accountant", - "head_of_sale", + "branch_admin", + "branch_manager", + "branch_accountant", ]; function globalAllow(user: RequestWithUser["user"]) { - const allowList = ["system", "head_of_admin", "head_of_accountant", "head_of_sale"]; - return allowList.some((v) => user.roles?.includes(v)); + const listAllowed = ["system", "head_of_admin", "admin", "executive", "accountant"]; + return user.roles?.some((v) => listAllowed.includes(v)) || false; } type EmployeeOtherInfoPayload = { diff --git a/src/controllers/03-employee-passport-controller.ts b/src/controllers/03-employee-passport-controller.ts index 6af121c..1d17e07 100644 --- a/src/controllers/03-employee-passport-controller.ts +++ b/src/controllers/03-employee-passport-controller.ts @@ -22,14 +22,16 @@ const MANAGE_ROLES = [ "system", "head_of_admin", "admin", - "head_of_accountant", + "executive", "accountant", - "head_of_sale", + "branch_admin", + "branch_manager", + "branch_accountant", ]; function globalAllow(user: RequestWithUser["user"]) { - const allowList = ["system", "head_of_admin", "head_of_accountant", "head_of_sale"]; - return allowList.some((v) => user.roles?.includes(v)); + const listAllowed = ["system", "head_of_admin", "admin", "executive", "accountant"]; + return user.roles?.some((v) => listAllowed.includes(v)) || false; } type EmployeePassportPayload = { diff --git a/src/controllers/03-employee-visa-controller.ts b/src/controllers/03-employee-visa-controller.ts index b1fe09d..a5af041 100644 --- a/src/controllers/03-employee-visa-controller.ts +++ b/src/controllers/03-employee-visa-controller.ts @@ -22,14 +22,16 @@ const MANAGE_ROLES = [ "system", "head_of_admin", "admin", - "head_of_accountant", + "executive", "accountant", - "head_of_sale", + "branch_admin", + "branch_manager", + "branch_accountant", ]; function globalAllow(user: RequestWithUser["user"]) { - const allowList = ["system", "head_of_admin", "head_of_accountant", "head_of_sale"]; - return allowList.some((v) => user.roles?.includes(v)); + const listAllowed = ["system", "head_of_admin", "admin", "executive", "accountant"]; + return user.roles?.some((v) => listAllowed.includes(v)) || false; } type EmployeeVisaPayload = { diff --git a/src/controllers/03-employee-work-controller.ts b/src/controllers/03-employee-work-controller.ts index 3704546..7671c2a 100644 --- a/src/controllers/03-employee-work-controller.ts +++ b/src/controllers/03-employee-work-controller.ts @@ -22,14 +22,16 @@ const MANAGE_ROLES = [ "system", "head_of_admin", "admin", - "head_of_accountant", + "executive", "accountant", - "head_of_sale", + "branch_admin", + "branch_manager", + "branch_accountant", ]; function globalAllow(user: RequestWithUser["user"]) { - const allowList = ["system", "head_of_admin", "head_of_accountant", "head_of_sale"]; - return allowList.some((v) => user.roles?.includes(v)); + const listAllowed = ["system", "head_of_admin", "admin", "executive", "accountant"]; + return user.roles?.some((v) => listAllowed.includes(v)) || false; } type EmployeeWorkPayload = { From 6d44d2979b7f9ed5182a6182c275f36c6ce46745 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 2 Jul 2025 10:48:24 +0700 Subject: [PATCH 061/167] feat(perm): update api flow template permission --- .../04-flow-template-controller.ts | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/src/controllers/04-flow-template-controller.ts b/src/controllers/04-flow-template-controller.ts index 940fd1d..85ab0b3 100644 --- a/src/controllers/04-flow-template-controller.ts +++ b/src/controllers/04-flow-template-controller.ts @@ -44,14 +44,30 @@ type WorkflowPayload = { status?: Status; }; -const permissionCondCompany = createPermCondition((_) => true); -const permissionCheckCompany = createPermCheck((_) => true); +const MANAGE_ROLES = [ + "system", + "head_of_admin", + "admin", + "executive", + "accountant", + "branch_admin", + "branch_manager", + "branch_accountant", +]; + +function globalAllow(user: RequestWithUser["user"]) { + const listAllowed = ["system", "head_of_admin", "admin", "executive", "accountant"]; + return user.roles?.some((v) => listAllowed.includes(v)) || false; +} + +const permissionCondCompany = createPermCondition(globalAllow); +const permissionCheckCompany = createPermCheck(globalAllow); @Route("api/v1/workflow-template") @Tags("Workflow") -@Security("keycloak") export class FlowTemplateController extends Controller { @Get() + @Security("keycloak") async getFlowTemplate( @Request() req: RequestWithUser, @Query() page: number = 1, @@ -118,6 +134,7 @@ export class FlowTemplateController extends Controller { } @Get("{templateId}") + @Security("keycloak") async getFlowTemplateById(@Request() _req: RequestWithUser, @Path() templateId: string) { const record = await prisma.workflowTemplate.findFirst({ include: { @@ -150,6 +167,7 @@ export class FlowTemplateController extends Controller { } @Post() + @Security("keycloak", MANAGE_ROLES) async createFlowTemplate(@Request() req: RequestWithUser, @Body() body: WorkflowPayload) { const where = { OR: [ @@ -230,6 +248,7 @@ export class FlowTemplateController extends Controller { } @Put("{templateId}") + @Security("keycloak", MANAGE_ROLES) async updateFlowTemplate( @Request() req: RequestWithUser, @Path() templateId: string, @@ -315,6 +334,7 @@ export class FlowTemplateController extends Controller { } @Delete("{templateId}") + @Security("keycloak", MANAGE_ROLES) async deleteFlowTemplateById(@Request() req: RequestWithUser, @Path() templateId: string) { const record = await prisma.workflowTemplate.findUnique({ where: { id: templateId }, From b0e941085eeb2b9cbb39b53219513fb4afde67b1 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 2 Jul 2025 11:11:55 +0700 Subject: [PATCH 062/167] feat(perm): update api institue permission --- src/controllers/04-institution-controller.ts | 29 ++++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/src/controllers/04-institution-controller.ts b/src/controllers/04-institution-controller.ts index 21611a7..7121f3c 100644 --- a/src/controllers/04-institution-controller.ts +++ b/src/controllers/04-institution-controller.ts @@ -95,6 +95,17 @@ type InstitutionUpdatePayload = { }[]; }; +const MANAGE_ROLES = [ + "system", + "head_of_admin", + "admin", + "executive", + "accountant", + "branch_admin", + "branch_manager", + "branch_accountant", +]; + @Route("api/v1/institution") @Tags("Institution") export class InstitutionController extends Controller { @@ -185,7 +196,7 @@ export class InstitutionController extends Controller { } @Post() - @Security("keycloak") + @Security("keycloak", MANAGE_ROLES) @OperationId("createInstitution") async createInstitution( @Body() @@ -229,7 +240,7 @@ export class InstitutionController extends Controller { } @Put("{institutionId}") - @Security("keycloak") + @Security("keycloak", MANAGE_ROLES) @OperationId("updateInstitution") async updateInstitution( @Path() institutionId: string, @@ -278,7 +289,7 @@ export class InstitutionController extends Controller { } @Delete("{institutionId}") - @Security("keycloak") + @Security("keycloak", MANAGE_ROLES) @OperationId("deleteInstitution") async deleteInstitution(@Path() institutionId: string) { return await prisma.$transaction(async (tx) => { @@ -350,7 +361,7 @@ export class InstitutionFileController extends Controller { } @Put("image/{name}") - @Security("keycloak") + @Security("keycloak", MANAGE_ROLES) async putImage( @Request() req: RequestWithUser, @Path() institutionId: string, @@ -364,7 +375,7 @@ export class InstitutionFileController extends Controller { } @Delete("image/{name}") - @Security("keycloak") + @Security("keycloak", MANAGE_ROLES) async delImage( @Request() req: RequestWithUser, @Path() institutionId: string, @@ -394,7 +405,7 @@ export class InstitutionFileController extends Controller { } @Put("attachment/{name}") - @Security("keycloak") + @Security("keycloak", MANAGE_ROLES) async putAttachment( @Request() req: RequestWithUser, @Path() institutionId: string, @@ -405,7 +416,7 @@ export class InstitutionFileController extends Controller { } @Delete("attachment/{name}") - @Security("keycloak") + @Security("keycloak", MANAGE_ROLES) async delAttachment( @Request() req: RequestWithUser, @Path() institutionId: string, @@ -436,7 +447,7 @@ export class InstitutionFileController extends Controller { } @Put("bank-qr/{bankId}") - @Security("keycloak") + @Security("keycloak", MANAGE_ROLES) async putBankImage( @Request() req: RequestWithUser, @Path() institutionId: string, @@ -450,7 +461,7 @@ export class InstitutionFileController extends Controller { } @Delete("bank-qr/{bankId}") - @Security("keycloak") + @Security("keycloak", MANAGE_ROLES) async delBankImage( @Request() req: RequestWithUser, @Path() institutionId: string, From afb725fceb011289c425ccec37ae6643d5cb34cf Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 2 Jul 2025 13:21:10 +0700 Subject: [PATCH 063/167] feat(perm): update api product/service permission --- src/controllers/04-product-controller.ts | 10 +++--- .../04-product-group-controller.ts | 31 +++++++++++++++---- src/controllers/04-service-controller.ts | 10 +++--- 3 files changed, 37 insertions(+), 14 deletions(-) diff --git a/src/controllers/04-product-controller.ts b/src/controllers/04-product-controller.ts index 3f2ff4e..0a3bbdd 100644 --- a/src/controllers/04-product-controller.ts +++ b/src/controllers/04-product-controller.ts @@ -35,14 +35,16 @@ const MANAGE_ROLES = [ "system", "head_of_admin", "admin", - "head_of_accountant", + "executive", "accountant", - "head_of_sale", + "branch_admin", + "branch_manager", + "branch_accountant", ]; function globalAllow(user: RequestWithUser["user"]) { - const allowList = ["system", "head_of_admin", "head_of_accountant", "head_of_sale"]; - return allowList.some((v) => user.roles?.includes(v)); + const listAllowed = ["system", "head_of_admin", "admin", "executive", "accountant"]; + return user.roles?.some((v) => listAllowed.includes(v)) || false; } const permissionCondCompany = createPermCondition((_) => true); diff --git a/src/controllers/04-product-group-controller.ts b/src/controllers/04-product-group-controller.ts index ecd4ac6..2cb47b3 100644 --- a/src/controllers/04-product-group-controller.ts +++ b/src/controllers/04-product-group-controller.ts @@ -35,7 +35,7 @@ type ProductGroupCreate = { remark: string; status?: Status; shared?: boolean; - registeredBranchId: string; + registeredBranchId?: string; }; type ProductGroupUpdate = { @@ -51,14 +51,16 @@ const MANAGE_ROLES = [ "system", "head_of_admin", "admin", - "head_of_accountant", + "executive", "accountant", - "head_of_sale", + "branch_admin", + "branch_manager", + "branch_accountant", ]; function globalAllow(user: RequestWithUser["user"]) { - const allowList = ["system", "head_of_admin", "head_of_accountant", "head_of_sale"]; - return allowList.some((v) => user.roles?.includes(v)); + const listAllowed = ["system", "head_of_admin", "admin", "executive", "accountant"]; + return user.roles?.some((v) => listAllowed.includes(v)) || false; } const permissionCond = createPermCondition((_) => true); @@ -157,7 +159,23 @@ export class ProductGroup extends Controller { @Post() @Security("keycloak", MANAGE_ROLES) async createProductGroup(@Request() req: RequestWithUser, @Body() body: ProductGroupCreate) { - let company = await permissionCheck(req.user, body.registeredBranchId).then( + const userAffiliatedBranch = await prisma.branch.findFirst({ + include: branchRelationPermInclude(req.user), + where: body.registeredBranchId + ? { id: body.registeredBranchId } + : { + user: { some: { userId: req.user.sub } }, + }, + }); + if (!userAffiliatedBranch) { + throw new HttpError( + HttpStatus.BAD_REQUEST, + "You must be affilated with at least one branch or specify branch to be registered (System permission required).", + "reqMinAffilatedBranch", + ); + } + + let company = await permissionCheck(req.user, userAffiliatedBranch).then( (v) => (v.headOffice || v).code, ); @@ -181,6 +199,7 @@ export class ProductGroup extends Controller { }, data: { ...body, + registeredBranchId: userAffiliatedBranch.id, statusOrder: +(body.status === "INACTIVE"), code: `G${last.value.toString().padStart(2, "0")}`, createdByUserId: req.user.sub, diff --git a/src/controllers/04-service-controller.ts b/src/controllers/04-service-controller.ts index ed46c18..9b9253e 100644 --- a/src/controllers/04-service-controller.ts +++ b/src/controllers/04-service-controller.ts @@ -42,14 +42,16 @@ const MANAGE_ROLES = [ "system", "head_of_admin", "admin", - "head_of_accountant", + "executive", "accountant", - "head_of_sale", + "branch_admin", + "branch_manager", + "branch_accountant", ]; function globalAllow(user: RequestWithUser["user"]) { - const allowList = ["system", "head_of_admin", "head_of_accountant", "head_of_sale"]; - return allowList.some((v) => user.roles?.includes(v)); + const listAllowed = ["system", "head_of_admin", "admin", "executive", "accountant"]; + return user.roles?.some((v) => listAllowed.includes(v)) || false; } const permissionCondCompany = createPermCondition((_) => true); From d08327afb6175e89a188bd7ed5a590754fde4d3a Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 2 Jul 2025 13:52:07 +0700 Subject: [PATCH 064/167] feat(perm): update api task permission --- src/controllers/07-task-controller.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/controllers/07-task-controller.ts b/src/controllers/07-task-controller.ts index da3c13d..bac70dd 100644 --- a/src/controllers/07-task-controller.ts +++ b/src/controllers/07-task-controller.ts @@ -44,11 +44,21 @@ import { } from "../utils/minio"; import { queryOrNot, whereDateQuery } from "../utils/relation"; -const MANAGE_ROLES = ["system", "head_of_admin", "admin", "document_checker"]; +const MANAGE_ROLES = [ + "system", + "head_of_admin", + "admin", + "executive", + "accountant", + "branch_admin", + "branch_manager", + "branch_accountant", + "data_entry", +]; function globalAllow(user: RequestWithUser["user"]) { - const allowList = ["system", "head_of_admin"]; - return allowList.some((v) => user.roles?.includes(v)); + const listAllowed = ["system", "head_of_admin", "admin", "executive", "accountant"]; + return user.roles?.some((v) => listAllowed.includes(v)) || false; } const permissionCondCompany = createPermCondition((_) => true); From 68025aad08ff9310b4ecb3d1d6b168d6af918bf7 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Thu, 3 Jul 2025 14:33:00 +0700 Subject: [PATCH 065/167] feat(perm): update api account related permission --- src/controllers/04-invoice-controller.ts | 19 ++++++++++++++----- src/controllers/05-payment-controller.ts | 15 ++++++++++++--- src/controllers/05-quotation-controller.ts | 4 ++-- src/controllers/08-credit-note-controller.ts | 20 +++++++++----------- src/controllers/09-debit-note-controller.ts | 16 +++++++--------- 5 files changed, 44 insertions(+), 30 deletions(-) diff --git a/src/controllers/04-invoice-controller.ts b/src/controllers/04-invoice-controller.ts index fb3fdf0..ffac9d1 100644 --- a/src/controllers/04-invoice-controller.ts +++ b/src/controllers/04-invoice-controller.ts @@ -29,14 +29,23 @@ type InvoicePayload = { installmentNo: number[]; }; -const MANAGE_ROLES = ["system", "head_of_admin", "admin", "head_of_accountant", "accountant"]; +const MANAGE_ROLES = [ + "system", + "head_of_admin", + "admin", + "executive", + "accountant", + "branch_admin", + "branch_manager", + "branch_accountant", +]; function globalAllow(user: RequestWithUser["user"]) { - const allowList = ["system", "head_of_admin", "head_of_accountant"]; - return allowList.some((v) => user.roles?.includes(v)); + const listAllowed = ["system", "head_of_admin", "admin", "executive", "accountant"]; + return user.roles?.some((v) => listAllowed.includes(v)) || false; } -const permissionCondCompany = createPermCondition((_) => true); +const permissionCondCompany = createPermCondition(globalAllow); const permissionCheck = createPermCheck(globalAllow); @Route("/api/v1/invoice") @@ -229,7 +238,7 @@ export class InvoiceController extends Controller { title: "ใบแจ้งหนี้ใหม่ / New Invoice", detail: "รหัส / code : " + record.code, registeredBranchId: record.registeredBranchId, - groupReceiver: { create: { name: "accountant" } }, + groupReceiver: { create: { name: "branch_accountant" } }, }, }); diff --git a/src/controllers/05-payment-controller.ts b/src/controllers/05-payment-controller.ts index 711e357..2c40446 100644 --- a/src/controllers/05-payment-controller.ts +++ b/src/controllers/05-payment-controller.ts @@ -26,11 +26,20 @@ 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"]; +const MANAGE_ROLES = [ + "system", + "head_of_admin", + "admin", + "executive", + "accountant", + "branch_admin", + "branch_manager", + "branch_accountant", +]; function globalAllow(user: RequestWithUser["user"]) { - const allowList = ["system", "head_of_admin", "head_of_accountant"]; - return allowList.some((v) => user.roles?.includes(v)); + const listAllowed = ["system", "head_of_admin", "admin", "executive", "accountant"]; + return user.roles?.some((v) => listAllowed.includes(v)) || false; } const permissionCondCompany = createPermCondition((_) => true); diff --git a/src/controllers/05-quotation-controller.ts b/src/controllers/05-quotation-controller.ts index 7d27e2e..80a0b95 100644 --- a/src/controllers/05-quotation-controller.ts +++ b/src/controllers/05-quotation-controller.ts @@ -150,14 +150,14 @@ const MANAGE_ROLES = [ "system", "head_of_admin", "admin", - "head_of_accountant", "accountant", + "branch_accountant", "head_of_sale", "sale", ]; function globalAllow(user: RequestWithUser["user"]) { - const allowList = ["system", "head_of_admin", "head_of_accountant", "head_of_sale"]; + const allowList = ["system", "head_of_admin", "accountant", "head_of_sale"]; return allowList.some((v) => user.roles?.includes(v)); } diff --git a/src/controllers/08-credit-note-controller.ts b/src/controllers/08-credit-note-controller.ts index 4a6f622..6da9954 100644 --- a/src/controllers/08-credit-note-controller.ts +++ b/src/controllers/08-credit-note-controller.ts @@ -42,22 +42,20 @@ const MANAGE_ROLES = [ "system", "head_of_admin", "admin", - "head_of_accountant", + "executive", "accountant", - "head_of_sale", - "sale", + "branch_admin", + "branch_manager", + "branch_accountant", ]; function globalAllow(user: RequestWithUser["user"]) { - const allowList = ["system", "head_of_admin", "head_of_accountant", "head_of_sale"]; - return allowList.some((v) => user.roles?.includes(v)); + const listAllowed = ["system", "head_of_admin", "admin", "executive", "accountant"]; + return user.roles?.some((v) => listAllowed.includes(v)) || false; } -// NOTE: permission condition/check in requestWork -> requestData -> quotation -> registeredBranch const permissionCond = createPermCondition(globalAllow); -const permissionCondCompany = createPermCondition((_) => true); const permissionCheck = createPermCheck(globalAllow); -const permissionCheckCompany = createPermCheck((_) => true); type CreditNoteCreate = { requestWorkId: string[]; @@ -94,7 +92,7 @@ export class CreditNoteController extends Controller { request: { quotationId, quotation: { - registeredBranch: { OR: permissionCondCompany(req.user) }, + registeredBranch: { OR: permissionCond(req.user) }, }, }, }, @@ -200,7 +198,7 @@ export class CreditNoteController extends Controller { request: { quotationId, quotation: { - registeredBranch: { OR: permissionCondCompany(req.user) }, + registeredBranch: { OR: permissionCond(req.user) }, }, }, }, @@ -243,7 +241,7 @@ export class CreditNoteController extends Controller { some: { request: { quotation: { - registeredBranch: { OR: permissionCondCompany(req.user) }, + registeredBranch: { OR: permissionCond(req.user) }, }, }, }, diff --git a/src/controllers/09-debit-note-controller.ts b/src/controllers/09-debit-note-controller.ts index 9c247be..91457d2 100644 --- a/src/controllers/09-debit-note-controller.ts +++ b/src/controllers/09-debit-note-controller.ts @@ -44,22 +44,20 @@ const MANAGE_ROLES = [ "system", "head_of_admin", "admin", - "head_of_accountant", + "executive", "accountant", - "head_of_sale", - "sale", + "branch_admin", + "branch_manager", + "branch_accountant", ]; function globalAllow(user: RequestWithUser["user"]) { - const allowList = ["system", "head_of_admin", "head_of_accountant", "head_of_sale"]; - return allowList.some((v) => user.roles?.includes(v)); + const listAllowed = ["system", "head_of_admin", "admin", "executive", "accountant"]; + return user.roles?.some((v) => listAllowed.includes(v)) || false; } -// NOTE: permission condition/check in registeredBranch const permissionCond = createPermCondition(globalAllow); -const permissionCondCompany = createPermCondition((_) => true); const permissionCheck = createPermCheck(globalAllow); -const permissionCheckCompany = createPermCheck((_) => true); type DebitNoteCreate = { quotationId: string; @@ -605,7 +603,7 @@ export class DebitNoteController extends Controller { if (!record) throw notFoundError("Debit Note"); - await permissionCheckCompany(req.user, record.registeredBranch); + await permissionCheck(req.user, record.registeredBranch); const { productServiceList: _productServiceList, ...rest } = body; const ids = { From 5c7db2afc68604097d8eb1200823c2fca60fba52 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Thu, 3 Jul 2025 14:41:33 +0700 Subject: [PATCH 066/167] feat(perm): update api quotation perm --- src/controllers/05-quotation-controller.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/controllers/05-quotation-controller.ts b/src/controllers/05-quotation-controller.ts index 80a0b95..92e096c 100644 --- a/src/controllers/05-quotation-controller.ts +++ b/src/controllers/05-quotation-controller.ts @@ -150,15 +150,16 @@ const MANAGE_ROLES = [ "system", "head_of_admin", "admin", + "executive", "accountant", + "branch_admin", + "branch_manager", "branch_accountant", - "head_of_sale", - "sale", ]; function globalAllow(user: RequestWithUser["user"]) { - const allowList = ["system", "head_of_admin", "accountant", "head_of_sale"]; - return allowList.some((v) => user.roles?.includes(v)); + const listAllowed = ["system", "head_of_admin", "admin", "executive", "accountant"]; + return user.roles?.some((v) => listAllowed.includes(v)) || false; } const permissionCheckCompany = createPermCheck((_) => true); From 1e0f97cdef1700b45a4641a05b45012669021ada Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Thu, 3 Jul 2025 16:20:23 +0700 Subject: [PATCH 067/167] feat(perm): allow anyone to edit owned data --- src/controllers/05-payment-controller.ts | 2 +- src/controllers/05-quotation-controller.ts | 2 +- src/controllers/08-credit-note-controller.ts | 2 +- src/controllers/09-debit-note-controller.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/controllers/05-payment-controller.ts b/src/controllers/05-payment-controller.ts index 2c40446..63b0689 100644 --- a/src/controllers/05-payment-controller.ts +++ b/src/controllers/05-payment-controller.ts @@ -110,7 +110,7 @@ export class QuotationPayment extends Controller { } @Put("{paymentId}") - @Security("keycloak", MANAGE_ROLES) + @Security("keycloak") async updatePayment( @Path() paymentId: string, @Body() body: { amount?: number; date?: Date; paymentStatus?: PaymentStatus }, diff --git a/src/controllers/05-quotation-controller.ts b/src/controllers/05-quotation-controller.ts index 92e096c..140994b 100644 --- a/src/controllers/05-quotation-controller.ts +++ b/src/controllers/05-quotation-controller.ts @@ -667,7 +667,7 @@ export class QuotationController extends Controller { } @Put("{quotationId}") - @Security("keycloak", MANAGE_ROLES) + @Security("keycloak") async editQuotation( @Request() req: RequestWithUser, @Path() quotationId: string, diff --git a/src/controllers/08-credit-note-controller.ts b/src/controllers/08-credit-note-controller.ts index 6da9954..f1bef3c 100644 --- a/src/controllers/08-credit-note-controller.ts +++ b/src/controllers/08-credit-note-controller.ts @@ -400,7 +400,7 @@ export class CreditNoteController extends Controller { } @Put("{creditNoteId}") - @Security("keycloak", MANAGE_ROLES) + @Security("keycloak") async updateCreditNote( @Request() req: RequestWithUser, @Path() creditNoteId: string, diff --git a/src/controllers/09-debit-note-controller.ts b/src/controllers/09-debit-note-controller.ts index 91457d2..badb52e 100644 --- a/src/controllers/09-debit-note-controller.ts +++ b/src/controllers/09-debit-note-controller.ts @@ -579,7 +579,7 @@ export class DebitNoteController extends Controller { } @Put("{debitNoteId}") - @Security("keycloak", MANAGE_ROLES) + @Security("keycloak") async updateDebitNote( @Request() req: RequestWithUser, @Path() debitNoteId: string, From ced55b95182bc9db02a6090e015a805f41d235c4 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Fri, 4 Jul 2025 13:26:26 +0700 Subject: [PATCH 068/167] feat: allow sale to manage --- src/controllers/03-customer-branch-controller.ts | 2 ++ src/controllers/03-customer-controller.ts | 2 ++ src/controllers/03-employee-checkup-controller.ts | 2 ++ src/controllers/03-employee-controller.ts | 2 ++ src/controllers/03-employee-other-info-controller.ts | 2 ++ src/controllers/03-employee-passport-controller.ts | 2 ++ src/controllers/03-employee-visa-controller.ts | 2 ++ src/controllers/03-employee-work-controller.ts | 2 ++ src/controllers/04-invoice-controller.ts | 2 ++ src/controllers/05-payment-controller.ts | 2 ++ src/controllers/05-quotation-controller.ts | 2 ++ 11 files changed, 22 insertions(+) diff --git a/src/controllers/03-customer-branch-controller.ts b/src/controllers/03-customer-branch-controller.ts index 529700b..61852ad 100644 --- a/src/controllers/03-customer-branch-controller.ts +++ b/src/controllers/03-customer-branch-controller.ts @@ -52,6 +52,8 @@ const MANAGE_ROLES = [ "branch_admin", "branch_manager", "branch_accountant", + "head_of_sale", + "sale", ]; function globalAllow(user: RequestWithUser["user"]) { diff --git a/src/controllers/03-customer-controller.ts b/src/controllers/03-customer-controller.ts index bfc327d..a52ea71 100644 --- a/src/controllers/03-customer-controller.ts +++ b/src/controllers/03-customer-controller.ts @@ -47,6 +47,8 @@ const MANAGE_ROLES = [ "branch_admin", "branch_manager", "branch_accountant", + "head_of_sale", + "sale", ]; function globalAllow(user: RequestWithUser["user"]) { diff --git a/src/controllers/03-employee-checkup-controller.ts b/src/controllers/03-employee-checkup-controller.ts index 71ceafd..cb3c8ee 100644 --- a/src/controllers/03-employee-checkup-controller.ts +++ b/src/controllers/03-employee-checkup-controller.ts @@ -28,6 +28,8 @@ const MANAGE_ROLES = [ "branch_admin", "branch_manager", "branch_accountant", + "head_of_sale", + "sale", ]; function globalAllow(user: RequestWithUser["user"]) { diff --git a/src/controllers/03-employee-controller.ts b/src/controllers/03-employee-controller.ts index 797a994..add8a8f 100644 --- a/src/controllers/03-employee-controller.ts +++ b/src/controllers/03-employee-controller.ts @@ -56,6 +56,8 @@ const MANAGE_ROLES = [ "branch_admin", "branch_manager", "branch_accountant", + "head_of_sale", + "sale", ]; function globalAllow(user: RequestWithUser["user"]) { diff --git a/src/controllers/03-employee-other-info-controller.ts b/src/controllers/03-employee-other-info-controller.ts index bc85efc..3624f95 100644 --- a/src/controllers/03-employee-other-info-controller.ts +++ b/src/controllers/03-employee-other-info-controller.ts @@ -28,6 +28,8 @@ const MANAGE_ROLES = [ "branch_admin", "branch_manager", "branch_accountant", + "head_of_sale", + "sale", ]; function globalAllow(user: RequestWithUser["user"]) { diff --git a/src/controllers/03-employee-passport-controller.ts b/src/controllers/03-employee-passport-controller.ts index 1d17e07..e0b1769 100644 --- a/src/controllers/03-employee-passport-controller.ts +++ b/src/controllers/03-employee-passport-controller.ts @@ -27,6 +27,8 @@ const MANAGE_ROLES = [ "branch_admin", "branch_manager", "branch_accountant", + "head_of_sale", + "sale", ]; function globalAllow(user: RequestWithUser["user"]) { diff --git a/src/controllers/03-employee-visa-controller.ts b/src/controllers/03-employee-visa-controller.ts index a5af041..358a293 100644 --- a/src/controllers/03-employee-visa-controller.ts +++ b/src/controllers/03-employee-visa-controller.ts @@ -27,6 +27,8 @@ const MANAGE_ROLES = [ "branch_admin", "branch_manager", "branch_accountant", + "head_of_sale", + "sale", ]; function globalAllow(user: RequestWithUser["user"]) { diff --git a/src/controllers/03-employee-work-controller.ts b/src/controllers/03-employee-work-controller.ts index 7671c2a..8bbfdca 100644 --- a/src/controllers/03-employee-work-controller.ts +++ b/src/controllers/03-employee-work-controller.ts @@ -27,6 +27,8 @@ const MANAGE_ROLES = [ "branch_admin", "branch_manager", "branch_accountant", + "head_of_sale", + "sale", ]; function globalAllow(user: RequestWithUser["user"]) { diff --git a/src/controllers/04-invoice-controller.ts b/src/controllers/04-invoice-controller.ts index ffac9d1..2033898 100644 --- a/src/controllers/04-invoice-controller.ts +++ b/src/controllers/04-invoice-controller.ts @@ -38,6 +38,8 @@ const MANAGE_ROLES = [ "branch_admin", "branch_manager", "branch_accountant", + "head_of_sale", + "sale", ]; function globalAllow(user: RequestWithUser["user"]) { diff --git a/src/controllers/05-payment-controller.ts b/src/controllers/05-payment-controller.ts index 63b0689..01b4c31 100644 --- a/src/controllers/05-payment-controller.ts +++ b/src/controllers/05-payment-controller.ts @@ -35,6 +35,8 @@ const MANAGE_ROLES = [ "branch_admin", "branch_manager", "branch_accountant", + "head_of_sale", + "sale", ]; function globalAllow(user: RequestWithUser["user"]) { diff --git a/src/controllers/05-quotation-controller.ts b/src/controllers/05-quotation-controller.ts index 140994b..ccce491 100644 --- a/src/controllers/05-quotation-controller.ts +++ b/src/controllers/05-quotation-controller.ts @@ -155,6 +155,8 @@ const MANAGE_ROLES = [ "branch_admin", "branch_manager", "branch_accountant", + "head_of_sale", + "sale", ]; function globalAllow(user: RequestWithUser["user"]) { From 2b255ff35573a3c1725e66dc82849f97f663aa0f Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Fri, 4 Jul 2025 13:31:01 +0700 Subject: [PATCH 069/167] feat: do not allow sale to delete data --- src/controllers/04-invoice-controller.ts | 4 +--- src/controllers/05-payment-controller.ts | 4 +--- src/controllers/05-quotation-controller.ts | 6 ++---- 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/controllers/04-invoice-controller.ts b/src/controllers/04-invoice-controller.ts index 2033898..5902521 100644 --- a/src/controllers/04-invoice-controller.ts +++ b/src/controllers/04-invoice-controller.ts @@ -38,8 +38,6 @@ const MANAGE_ROLES = [ "branch_admin", "branch_manager", "branch_accountant", - "head_of_sale", - "sale", ]; function globalAllow(user: RequestWithUser["user"]) { @@ -195,7 +193,7 @@ export class InvoiceController extends Controller { @Post() @OperationId("createInvoice") - @Security("keycloak", MANAGE_ROLES) + @Security("keycloak", MANAGE_ROLES.concat(["head_of_sale", "sale"])) async createInvoice(@Request() req: RequestWithUser, @Body() body: InvoicePayload) { const [quotation] = await prisma.$transaction([ prisma.quotation.findUnique({ diff --git a/src/controllers/05-payment-controller.ts b/src/controllers/05-payment-controller.ts index 01b4c31..196054f 100644 --- a/src/controllers/05-payment-controller.ts +++ b/src/controllers/05-payment-controller.ts @@ -35,8 +35,6 @@ const MANAGE_ROLES = [ "branch_admin", "branch_manager", "branch_accountant", - "head_of_sale", - "sale", ]; function globalAllow(user: RequestWithUser["user"]) { @@ -112,7 +110,7 @@ export class QuotationPayment extends Controller { } @Put("{paymentId}") - @Security("keycloak") + @Security("keycloak", MANAGE_ROLES.concat(["head_of_sale", "sale"])) async updatePayment( @Path() paymentId: string, @Body() body: { amount?: number; date?: Date; paymentStatus?: PaymentStatus }, diff --git a/src/controllers/05-quotation-controller.ts b/src/controllers/05-quotation-controller.ts index ccce491..63a0b17 100644 --- a/src/controllers/05-quotation-controller.ts +++ b/src/controllers/05-quotation-controller.ts @@ -155,8 +155,6 @@ const MANAGE_ROLES = [ "branch_admin", "branch_manager", "branch_accountant", - "head_of_sale", - "sale", ]; function globalAllow(user: RequestWithUser["user"]) { @@ -418,7 +416,7 @@ export class QuotationController extends Controller { } @Post() - @Security("keycloak", MANAGE_ROLES) + @Security("keycloak", MANAGE_ROLES.concat(["head_of_sale", "sale"])) async createQuotation(@Request() req: RequestWithUser, @Body() body: QuotationCreate) { const ids = { employee: body.worker.filter((v) => typeof v === "string"), @@ -669,7 +667,7 @@ export class QuotationController extends Controller { } @Put("{quotationId}") - @Security("keycloak") + @Security("keycloak", MANAGE_ROLES.concat(["head_of_sale", "sale"])) async editQuotation( @Request() req: RequestWithUser, @Path() quotationId: string, From 859a1e3deffcb5ce3eb0d82986ca689ef913790a Mon Sep 17 00:00:00 2001 From: Kanjana Date: Fri, 4 Jul 2025 14:08:24 +0700 Subject: [PATCH 070/167] chore: migration --- .../migration.sql | 5 +++++ prisma/schema.prisma | 4 ++++ 2 files changed, 9 insertions(+) create mode 100644 prisma/migrations/20250704070342_add_relation_seller_in_quotation/migration.sql diff --git a/prisma/migrations/20250704070342_add_relation_seller_in_quotation/migration.sql b/prisma/migrations/20250704070342_add_relation_seller_in_quotation/migration.sql new file mode 100644 index 0000000..ef63931 --- /dev/null +++ b/prisma/migrations/20250704070342_add_relation_seller_in_quotation/migration.sql @@ -0,0 +1,5 @@ +-- AlterTable +ALTER TABLE "Quotation" ADD COLUMN "sellerId" TEXT; + +-- AddForeignKey +ALTER TABLE "Quotation" ADD CONSTRAINT "Quotation_sellerId_fkey" FOREIGN KEY ("sellerId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 7639cf2..72b19a3 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -511,6 +511,7 @@ model User { contactName String? contactTel String? + quotation Quotation[] } model UserResponsibleArea { @@ -1386,6 +1387,9 @@ model Quotation { invoice Invoice[] creditNote CreditNote[] + + seller User? @relation(fields: [sellerId], references: [id], onDelete: Cascade) + sellerId String? } model QuotationPaySplit { From e0be1f6ab593645273dbfaa8d065a46fcc74cef1 Mon Sep 17 00:00:00 2001 From: Kanjana Date: Fri, 4 Jul 2025 14:40:46 +0700 Subject: [PATCH 071/167] feat: add sellerId in quotation --- src/controllers/05-quotation-controller.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/controllers/05-quotation-controller.ts b/src/controllers/05-quotation-controller.ts index 63a0b17..4e6dc05 100644 --- a/src/controllers/05-quotation-controller.ts +++ b/src/controllers/05-quotation-controller.ts @@ -84,6 +84,8 @@ type QuotationCreate = { installmentNo?: number; workerIndex?: number[]; }[]; + + sellerId?: string; }; type QuotationUpdate = { @@ -142,6 +144,8 @@ type QuotationUpdate = { installmentNo?: number; workerIndex?: number[]; }[]; + + sellerId?: string; }; const VAT_DEFAULT = config.vat; @@ -211,6 +215,7 @@ export class QuotationController extends Controller { @Query() query = "", @Query() startDate?: Date, @Query() endDate?: Date, + @Query() sellerId?: string, ) { const where = { OR: queryOrNot(query, [ @@ -259,6 +264,7 @@ export class QuotationController extends Controller { } : undefined, ...whereDateQuery(startDate, endDate), + sellerId: sellerId ? sellerId : null, } satisfies Prisma.QuotationWhereInput; const [result, total] = await prisma.$transaction([ From 138031f662e2c649b5f7445d3dc8f679989a03c1 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Fri, 4 Jul 2025 15:02:42 +0700 Subject: [PATCH 072/167] fix: error not found --- src/controllers/06-request-list-controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/06-request-list-controller.ts b/src/controllers/06-request-list-controller.ts index ca5398f..a20020f 100644 --- a/src/controllers/06-request-list-controller.ts +++ b/src/controllers/06-request-list-controller.ts @@ -977,7 +977,7 @@ export class RequestListController extends Controller { }); if (record.responsibleUserId === null) { - await prisma.requestWorkStepStatus.update({ + await tx.requestWorkStepStatus.update({ where: { step_requestWorkId: { step: step, From 8d25dda326b26d4347e53fa17bedcfd5265a4b9c Mon Sep 17 00:00:00 2001 From: Kanjana Date: Mon, 7 Jul 2025 13:50:28 +0700 Subject: [PATCH 073/167] feat: change where sellerId --- src/controllers/05-quotation-controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/05-quotation-controller.ts b/src/controllers/05-quotation-controller.ts index 4e6dc05..5a375e5 100644 --- a/src/controllers/05-quotation-controller.ts +++ b/src/controllers/05-quotation-controller.ts @@ -264,7 +264,7 @@ export class QuotationController extends Controller { } : undefined, ...whereDateQuery(startDate, endDate), - sellerId: sellerId ? sellerId : null, + sellerId: sellerId, } satisfies Prisma.QuotationWhereInput; const [result, total] = await prisma.$transaction([ From 3d98f9d0ad78760d16f32afe3a97aae347f59709 Mon Sep 17 00:00:00 2001 From: Kanjana Date: Mon, 7 Jul 2025 17:55:32 +0700 Subject: [PATCH 074/167] feat: add null in update dateOfBirth --- src/controllers/03-employee-controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/03-employee-controller.ts b/src/controllers/03-employee-controller.ts index add8a8f..bb66f58 100644 --- a/src/controllers/03-employee-controller.ts +++ b/src/controllers/03-employee-controller.ts @@ -112,7 +112,7 @@ type EmployeeUpdate = { nrcNo?: string | null; - dateOfBirth?: Date; + dateOfBirth?: Date | null; gender?: string; nationality?: string; otherNationality?: string | null; From 842d81026e33611ff1d663432d2c17e8f04114c3 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Tue, 8 Jul 2025 17:04:23 +0700 Subject: [PATCH 075/167] feat: allow anybody to edit customer data if can manage --- src/controllers/03-customer-branch-citizen-controller.ts | 2 +- src/controllers/03-customer-controller.ts | 2 +- src/controllers/03-employee-checkup-controller.ts | 2 +- src/controllers/03-employee-controller.ts | 2 +- src/controllers/03-employee-other-info-controller.ts | 2 +- src/controllers/03-employee-passport-controller.ts | 2 +- src/controllers/03-employee-visa-controller.ts | 2 +- src/controllers/03-employee-work-controller.ts | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/controllers/03-customer-branch-citizen-controller.ts b/src/controllers/03-customer-branch-citizen-controller.ts index f17b746..ad7127d 100644 --- a/src/controllers/03-customer-branch-citizen-controller.ts +++ b/src/controllers/03-customer-branch-citizen-controller.ts @@ -31,7 +31,7 @@ const MANAGE_ROLES = [ ]; function globalAllow(user: RequestWithUser["user"]) { - const listAllowed = ["system", "head_of_admin", "admin", "executive", "accountant"]; + const listAllowed = MANAGE_ROLES; return user.roles?.some((v) => listAllowed.includes(v)) || false; } diff --git a/src/controllers/03-customer-controller.ts b/src/controllers/03-customer-controller.ts index a52ea71..082b067 100644 --- a/src/controllers/03-customer-controller.ts +++ b/src/controllers/03-customer-controller.ts @@ -52,7 +52,7 @@ const MANAGE_ROLES = [ ]; function globalAllow(user: RequestWithUser["user"]) { - const listAllowed = ["system", "head_of_admin", "admin", "executive", "accountant"]; + const listAllowed = MANAGE_ROLES; return user.roles?.some((v) => listAllowed.includes(v)) || false; } diff --git a/src/controllers/03-employee-checkup-controller.ts b/src/controllers/03-employee-checkup-controller.ts index cb3c8ee..3fd334d 100644 --- a/src/controllers/03-employee-checkup-controller.ts +++ b/src/controllers/03-employee-checkup-controller.ts @@ -33,7 +33,7 @@ const MANAGE_ROLES = [ ]; function globalAllow(user: RequestWithUser["user"]) { - const listAllowed = ["system", "head_of_admin", "admin", "executive", "accountant"]; + const listAllowed = MANAGE_ROLES; return user.roles?.some((v) => listAllowed.includes(v)) || false; } diff --git a/src/controllers/03-employee-controller.ts b/src/controllers/03-employee-controller.ts index bb66f58..99d69ef 100644 --- a/src/controllers/03-employee-controller.ts +++ b/src/controllers/03-employee-controller.ts @@ -61,7 +61,7 @@ const MANAGE_ROLES = [ ]; function globalAllow(user: RequestWithUser["user"]) { - const listAllowed = ["system", "head_of_admin", "admin", "executive", "accountant"]; + const listAllowed = MANAGE_ROLES; return user.roles?.some((v) => listAllowed.includes(v)) || false; } diff --git a/src/controllers/03-employee-other-info-controller.ts b/src/controllers/03-employee-other-info-controller.ts index 3624f95..83a0a9c 100644 --- a/src/controllers/03-employee-other-info-controller.ts +++ b/src/controllers/03-employee-other-info-controller.ts @@ -33,7 +33,7 @@ const MANAGE_ROLES = [ ]; function globalAllow(user: RequestWithUser["user"]) { - const listAllowed = ["system", "head_of_admin", "admin", "executive", "accountant"]; + const listAllowed = MANAGE_ROLES; return user.roles?.some((v) => listAllowed.includes(v)) || false; } diff --git a/src/controllers/03-employee-passport-controller.ts b/src/controllers/03-employee-passport-controller.ts index e0b1769..c8f5483 100644 --- a/src/controllers/03-employee-passport-controller.ts +++ b/src/controllers/03-employee-passport-controller.ts @@ -32,7 +32,7 @@ const MANAGE_ROLES = [ ]; function globalAllow(user: RequestWithUser["user"]) { - const listAllowed = ["system", "head_of_admin", "admin", "executive", "accountant"]; + const listAllowed = MANAGE_ROLES; return user.roles?.some((v) => listAllowed.includes(v)) || false; } diff --git a/src/controllers/03-employee-visa-controller.ts b/src/controllers/03-employee-visa-controller.ts index 358a293..eafb22a 100644 --- a/src/controllers/03-employee-visa-controller.ts +++ b/src/controllers/03-employee-visa-controller.ts @@ -32,7 +32,7 @@ const MANAGE_ROLES = [ ]; function globalAllow(user: RequestWithUser["user"]) { - const listAllowed = ["system", "head_of_admin", "admin", "executive", "accountant"]; + const listAllowed = MANAGE_ROLES; return user.roles?.some((v) => listAllowed.includes(v)) || false; } diff --git a/src/controllers/03-employee-work-controller.ts b/src/controllers/03-employee-work-controller.ts index 8bbfdca..89f8ccf 100644 --- a/src/controllers/03-employee-work-controller.ts +++ b/src/controllers/03-employee-work-controller.ts @@ -32,7 +32,7 @@ const MANAGE_ROLES = [ ]; function globalAllow(user: RequestWithUser["user"]) { - const listAllowed = ["system", "head_of_admin", "admin", "executive", "accountant"]; + const listAllowed = MANAGE_ROLES; return user.roles?.some((v) => listAllowed.includes(v)) || false; } From f9c4d579c4d33573fb82dca7a7749eff35957e3c Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Tue, 8 Jul 2025 17:17:07 +0700 Subject: [PATCH 076/167] feat: allow manage role to update any product under same head --- src/controllers/04-product-controller.ts | 2 +- src/controllers/04-product-group-controller.ts | 2 +- src/controllers/04-service-controller.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/controllers/04-product-controller.ts b/src/controllers/04-product-controller.ts index 0a3bbdd..b50462c 100644 --- a/src/controllers/04-product-controller.ts +++ b/src/controllers/04-product-controller.ts @@ -43,7 +43,7 @@ const MANAGE_ROLES = [ ]; function globalAllow(user: RequestWithUser["user"]) { - const listAllowed = ["system", "head_of_admin", "admin", "executive", "accountant"]; + const listAllowed = MANAGE_ROLES; return user.roles?.some((v) => listAllowed.includes(v)) || false; } diff --git a/src/controllers/04-product-group-controller.ts b/src/controllers/04-product-group-controller.ts index 2cb47b3..e71bd7a 100644 --- a/src/controllers/04-product-group-controller.ts +++ b/src/controllers/04-product-group-controller.ts @@ -59,7 +59,7 @@ const MANAGE_ROLES = [ ]; function globalAllow(user: RequestWithUser["user"]) { - const listAllowed = ["system", "head_of_admin", "admin", "executive", "accountant"]; + const listAllowed = MANAGE_ROLES; return user.roles?.some((v) => listAllowed.includes(v)) || false; } diff --git a/src/controllers/04-service-controller.ts b/src/controllers/04-service-controller.ts index 9b9253e..82ab2ea 100644 --- a/src/controllers/04-service-controller.ts +++ b/src/controllers/04-service-controller.ts @@ -50,7 +50,7 @@ const MANAGE_ROLES = [ ]; function globalAllow(user: RequestWithUser["user"]) { - const listAllowed = ["system", "head_of_admin", "admin", "executive", "accountant"]; + const listAllowed = MANAGE_ROLES; return user.roles?.some((v) => listAllowed.includes(v)) || false; } From e74516ce3bca4c37aac97f16ef58e78b99cbe676 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 9 Jul 2025 09:59:13 +0700 Subject: [PATCH 077/167] fix: allow anyone in company to see template --- src/controllers/04-flow-template-controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/04-flow-template-controller.ts b/src/controllers/04-flow-template-controller.ts index 85ab0b3..cdb654f 100644 --- a/src/controllers/04-flow-template-controller.ts +++ b/src/controllers/04-flow-template-controller.ts @@ -56,7 +56,7 @@ const MANAGE_ROLES = [ ]; function globalAllow(user: RequestWithUser["user"]) { - const listAllowed = ["system", "head_of_admin", "admin", "executive", "accountant"]; + const listAllowed = MANAGE_ROLES; return user.roles?.some((v) => listAllowed.includes(v)) || false; } From 8fb28ec3ab1a05b7122f6e33d21ec487b15e88e9 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 9 Jul 2025 11:40:39 +0700 Subject: [PATCH 078/167] fix: perm --- src/controllers/03-customer-branch-controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/03-customer-branch-controller.ts b/src/controllers/03-customer-branch-controller.ts index 61852ad..7f13048 100644 --- a/src/controllers/03-customer-branch-controller.ts +++ b/src/controllers/03-customer-branch-controller.ts @@ -57,7 +57,7 @@ const MANAGE_ROLES = [ ]; function globalAllow(user: RequestWithUser["user"]) { - const listAllowed = ["system", "head_of_admin", "admin", "executive", "accountant"]; + const listAllowed = MANAGE_ROLES; return user.roles?.some((v) => listAllowed.includes(v)) || false; } From a61bd8c83ed9a4f92efeb92110a7f6423231a807 Mon Sep 17 00:00:00 2001 From: Kanjana Date: Wed, 9 Jul 2025 15:21:00 +0700 Subject: [PATCH 079/167] chore: migration --- .../migration.sql | 36 +++++++++++++++++++ prisma/schema.prisma | 32 +++++++++++++---- 2 files changed, 61 insertions(+), 7 deletions(-) create mode 100644 prisma/migrations/20250709082027_add_business_type_add_delect_customer_name/migration.sql diff --git a/prisma/migrations/20250709082027_add_business_type_add_delect_customer_name/migration.sql b/prisma/migrations/20250709082027_add_business_type_add_delect_customer_name/migration.sql new file mode 100644 index 0000000..50f8cd1 --- /dev/null +++ b/prisma/migrations/20250709082027_add_business_type_add_delect_customer_name/migration.sql @@ -0,0 +1,36 @@ +/* + Warnings: + + - You are about to drop the column `businessType` on the `CustomerBranch` table. All the data in the column will be lost. + - You are about to drop the column `customerName` on the `CustomerBranch` table. All the data in the column will be lost. + +*/ +-- AlterTable +ALTER TABLE "CustomerBranch" DROP COLUMN "businessType", +DROP COLUMN "customerName", +ADD COLUMN "businessTypeId" TEXT; + +-- AlterTable +ALTER TABLE "EmployeeVisa" ADD COLUMN "reportDate" DATE; + +-- CreateTable +CREATE TABLE "BusinessType" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + "nameEN" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "createdByUserId" TEXT, + "updatedAt" TIMESTAMP(3) NOT NULL, + "updatedByUserId" TEXT, + + CONSTRAINT "BusinessType_pkey" PRIMARY KEY ("id") +); + +-- AddForeignKey +ALTER TABLE "CustomerBranch" ADD CONSTRAINT "CustomerBranch_businessTypeId_fkey" FOREIGN KEY ("businessTypeId") REFERENCES "BusinessType"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "BusinessType" ADD CONSTRAINT "BusinessType_createdByUserId_fkey" FOREIGN KEY ("createdByUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "BusinessType" ADD CONSTRAINT "BusinessType_updatedByUserId_fkey" FOREIGN KEY ("updatedByUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 72b19a3..e444ae1 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -501,6 +501,8 @@ model User { creditNoteCreated CreditNote[] @relation("CreditNoteCreatedByUser") institutionCreated Institution[] @relation("InstitutionCreatedByUser") institutionUpdated Institution[] @relation("InstitutionUpdatedByUser") + businessTypeCreated BusinessType[] @relation("BusinessTypeCreatedByUser") + businessTypeUpdated BusinessType[] @relation("BusinessTypeUpdatedByUser") requestWorkStepStatus RequestWorkStepStatus[] userTask UserTask[] @@ -550,10 +552,9 @@ model Customer { } model CustomerBranch { - id String @id @default(cuid()) - customer Customer @relation(fields: [customerId], references: [id], onDelete: Cascade) - customerId String - customerName String? + id String @id @default(cuid()) + customer Customer @relation(fields: [customerId], references: [id], onDelete: Cascade) + customerId String code String codeCustomer String @@ -615,7 +616,8 @@ model CustomerBranch { agentUser User? @relation(fields: [agentUserId], references: [id], onDelete: SetNull) // NOTE: Business - businessType String + businessTypeId String? + businessType BusinessType? @relation(fields: [businessTypeId], references: [id], onDelete: SetNull) jobPosition String jobDescription String payDate String @@ -774,6 +776,21 @@ model CustomerBranchVatRegis { customerBranch CustomerBranch @relation(fields: [customerBranchId], references: [id], onDelete: Cascade) } +model BusinessType { + id String @id @default(cuid()) + name String + nameEN String + + createdAt DateTime @default(now()) + createdBy User? @relation(name: "BusinessTypeCreatedByUser", fields: [createdByUserId], references: [id], onDelete: SetNull) + createdByUserId String? + updatedAt DateTime @updatedAt + updatedBy User? @relation(name: "BusinessTypeUpdatedByUser", fields: [updatedByUserId], references: [id], onDelete: SetNull) + updatedByUserId String? + + customerBranch CustomerBranch[] +} + model Employee { id String @id @default(cuid()) @@ -895,8 +912,9 @@ model EmployeeVisa { entryCount Int issueCountry String issuePlace String - issueDate DateTime @db.Date - expireDate DateTime @db.Date + issueDate DateTime @db.Date + expireDate DateTime @db.Date + reportDate DateTime? @db.Date mrz String? remark String? From 50fca4d540fd1b56b0b38a65b96b7eb16562d919 Mon Sep 17 00:00:00 2001 From: Kanjana Date: Wed, 9 Jul 2025 16:02:45 +0700 Subject: [PATCH 080/167] feat: remove customerName add reportDate --- .../03-customer-branch-controller.ts | 31 ++++++++++++++----- src/controllers/03-customer-controller.ts | 5 ++- .../03-employee-visa-controller.ts | 1 + src/controllers/04-invoice-controller.ts | 1 - src/controllers/05-quotation-controller.ts | 1 - src/controllers/06-request-list-controller.ts | 1 - src/controllers/08-credit-note-controller.ts | 1 - src/controllers/09-debit-note-controller.ts | 1 - src/controllers/09-line-controller.ts | 3 +- src/controllers/09-web-hook-controller.ts | 8 ++--- 10 files changed, 31 insertions(+), 22 deletions(-) diff --git a/src/controllers/03-customer-branch-controller.ts b/src/controllers/03-customer-branch-controller.ts index 7f13048..f8c8a6b 100644 --- a/src/controllers/03-customer-branch-controller.ts +++ b/src/controllers/03-customer-branch-controller.ts @@ -57,7 +57,7 @@ const MANAGE_ROLES = [ ]; function globalAllow(user: RequestWithUser["user"]) { - const listAllowed = MANAGE_ROLES; + const listAllowed = ["system", "head_of_admin", "admin", "executive", "accountant"]; return user.roles?.some((v) => listAllowed.includes(v)) || false; } @@ -87,7 +87,6 @@ export type CustomerBranchCreate = { authorizedCapital?: string; authorizedName?: string; authorizedNameEN?: string; - customerName?: string; telephoneNo: string; @@ -111,7 +110,7 @@ export type CustomerBranchCreate = { contactName: string; agentUserId?: string; - businessType: string; + businessTypeId?: string; jobPosition: string; jobDescription: string; payDate: string; @@ -145,7 +144,6 @@ export type CustomerBranchUpdate = { authorizedCapital?: string; authorizedName?: string; authorizedNameEN?: string; - customerName?: string; telephoneNo: string; @@ -169,7 +167,7 @@ export type CustomerBranchUpdate = { contactName?: string; agentUserId?: string; - businessType?: string; + businessTypeId?: string; jobPosition?: string; jobDescription?: string; payDate?: string; @@ -204,7 +202,6 @@ export class CustomerBranchController extends Controller { ) { const where = { OR: queryOrNot(query, [ - { customerName: { contains: query, mode: "insensitive" } }, { registerName: { contains: query, mode: "insensitive" } }, { registerNameEN: { contains: query, mode: "insensitive" } }, { email: { contains: query, mode: "insensitive" } }, @@ -381,7 +378,15 @@ export class CustomerBranchController extends Controller { (v) => (v.headOffice || v).code, ); - const { provinceId, districtId, subDistrictId, customerId, agentUserId, ...rest } = body; + const { + provinceId, + districtId, + subDistrictId, + customerId, + agentUserId, + businessTypeId, + ...rest + } = body; const record = await prisma.$transaction( async (tx) => { @@ -435,6 +440,7 @@ export class CustomerBranchController extends Controller { province: connectOrNot(provinceId), district: connectOrNot(districtId), subDistrict: connectOrNot(subDistrictId), + businessType: connectOrNot(businessTypeId), createdBy: { connect: { id: req.user.sub } }, updatedBy: { connect: { id: req.user.sub } }, }, @@ -509,7 +515,15 @@ export class CustomerBranchController extends Controller { await permissionCheck(req.user, customer.registeredBranch); } - const { provinceId, districtId, subDistrictId, customerId, agentUserId, ...rest } = body; + const { + provinceId, + districtId, + subDistrictId, + customerId, + agentUserId, + businessTypeId, + ...rest + } = body; return await prisma.customerBranch.update({ where: { id: branchId }, @@ -528,6 +542,7 @@ export class CustomerBranchController extends Controller { province: connectOrDisconnect(provinceId), district: connectOrDisconnect(districtId), subDistrict: connectOrDisconnect(subDistrictId), + businessType: connectOrNot(businessTypeId), updatedBy: { connect: { id: req.user.sub } }, }, }); diff --git a/src/controllers/03-customer-controller.ts b/src/controllers/03-customer-controller.ts index 082b067..580e60f 100644 --- a/src/controllers/03-customer-controller.ts +++ b/src/controllers/03-customer-controller.ts @@ -85,7 +85,6 @@ export type CustomerCreate = { authorizedCapital?: string; authorizedName?: string; authorizedNameEN?: string; - customerName?: string; telephoneNo: string; @@ -109,7 +108,7 @@ export type CustomerCreate = { contactName: string; agentUserId?: string; - businessType: string; + businessTypeId?: string | null; jobPosition: string; jobDescription: string; payDate: string; @@ -174,7 +173,6 @@ export class CustomerController extends Controller { const where = { OR: queryOrNot(query, [ { branch: { some: { namePrefix: { contains: query, mode: "insensitive" } } } }, - { branch: { some: { customerName: { contains: query, mode: "insensitive" } } } }, { branch: { some: { registerName: { contains: query, mode: "insensitive" } } } }, { branch: { some: { registerNameEN: { contains: query, mode: "insensitive" } } } }, { branch: { some: { firstName: { contains: query, mode: "insensitive" } } } }, @@ -220,6 +218,7 @@ export class CustomerController extends Controller { }, createdBy: true, updatedBy: true, + // businessType:true }, orderBy: [{ statusOrder: "asc" }, { createdAt: "asc" }], where, diff --git a/src/controllers/03-employee-visa-controller.ts b/src/controllers/03-employee-visa-controller.ts index eafb22a..7c51bf2 100644 --- a/src/controllers/03-employee-visa-controller.ts +++ b/src/controllers/03-employee-visa-controller.ts @@ -44,6 +44,7 @@ type EmployeeVisaPayload = { issuePlace: string; issueDate: Date; expireDate: Date; + reportDate?: Date | null; mrz?: string | null; remark?: string | null; diff --git a/src/controllers/04-invoice-controller.ts b/src/controllers/04-invoice-controller.ts index 5902521..cff9d20 100644 --- a/src/controllers/04-invoice-controller.ts +++ b/src/controllers/04-invoice-controller.ts @@ -117,7 +117,6 @@ export class InvoiceController extends Controller { customerBranch: { OR: [ { code: { contains: query, mode: "insensitive" } }, - { customerName: { contains: query, mode: "insensitive" } }, { registerName: { contains: query, mode: "insensitive" } }, { registerNameEN: { contains: query, mode: "insensitive" } }, { firstName: { contains: query, mode: "insensitive" } }, diff --git a/src/controllers/05-quotation-controller.ts b/src/controllers/05-quotation-controller.ts index 5a375e5..04353bb 100644 --- a/src/controllers/05-quotation-controller.ts +++ b/src/controllers/05-quotation-controller.ts @@ -225,7 +225,6 @@ export class QuotationController extends Controller { customerBranch: { OR: [ { code: { contains: query, mode: "insensitive" } }, - { customerName: { contains: query, mode: "insensitive" } }, { firstName: { contains: query, mode: "insensitive" } }, { firstNameEN: { contains: query, mode: "insensitive" } }, { lastName: { contains: query, mode: "insensitive" } }, diff --git a/src/controllers/06-request-list-controller.ts b/src/controllers/06-request-list-controller.ts index a20020f..37fd8e2 100644 --- a/src/controllers/06-request-list-controller.ts +++ b/src/controllers/06-request-list-controller.ts @@ -95,7 +95,6 @@ export class RequestDataController extends Controller { customerBranch: { OR: [ { code: { contains: query, mode: "insensitive" } }, - { customerName: { contains: query, mode: "insensitive" } }, { registerName: { contains: query, mode: "insensitive" } }, { registerNameEN: { contains: query, mode: "insensitive" } }, { firstName: { contains: query, mode: "insensitive" } }, diff --git a/src/controllers/08-credit-note-controller.ts b/src/controllers/08-credit-note-controller.ts index f1bef3c..fd7a2a3 100644 --- a/src/controllers/08-credit-note-controller.ts +++ b/src/controllers/08-credit-note-controller.ts @@ -163,7 +163,6 @@ export class CreditNoteController extends Controller { customerBranch: { OR: [ { code: { contains: query, mode: "insensitive" } }, - { customerName: { contains: query, mode: "insensitive" } }, { firstName: { contains: query, mode: "insensitive" } }, { firstNameEN: { contains: query, mode: "insensitive" } }, { lastName: { contains: query, mode: "insensitive" } }, diff --git a/src/controllers/09-debit-note-controller.ts b/src/controllers/09-debit-note-controller.ts index badb52e..5fcdee9 100644 --- a/src/controllers/09-debit-note-controller.ts +++ b/src/controllers/09-debit-note-controller.ts @@ -211,7 +211,6 @@ export class DebitNoteController extends Controller { customerBranch: { OR: [ { code: { contains: query, mode: "insensitive" } }, - { customerName: { contains: query, mode: "insensitive" } }, { firstName: { contains: query, mode: "insensitive" } }, { firstNameEN: { contains: query, mode: "insensitive" } }, { lastName: { contains: query, mode: "insensitive" } }, diff --git a/src/controllers/09-line-controller.ts b/src/controllers/09-line-controller.ts index 91c06bb..8e708be 100644 --- a/src/controllers/09-line-controller.ts +++ b/src/controllers/09-line-controller.ts @@ -189,7 +189,6 @@ export class LineController extends Controller { customerBranch: { OR: [ { code: { contains: query, mode: "insensitive" } }, - { customerName: { contains: query, mode: "insensitive" } }, { registerName: { contains: query, mode: "insensitive" } }, { registerNameEN: { contains: query, mode: "insensitive" } }, { firstName: { contains: query, mode: "insensitive" } }, @@ -624,7 +623,7 @@ export class LineController extends Controller { customerBranch: { OR: [ { code: { contains: query, mode: "insensitive" } }, - { customerName: { contains: query, mode: "insensitive" } }, + { registerName: { contains: query, mode: "insensitive" } }, { firstName: { contains: query, mode: "insensitive" } }, { firstNameEN: { contains: query, mode: "insensitive" } }, { lastName: { contains: query, mode: "insensitive" } }, diff --git a/src/controllers/09-web-hook-controller.ts b/src/controllers/09-web-hook-controller.ts index 2914e20..028bf75 100644 --- a/src/controllers/09-web-hook-controller.ts +++ b/src/controllers/09-web-hook-controller.ts @@ -90,7 +90,7 @@ export class WebHookController extends Controller { firstNameEN: true, lastName: true, lastNameEN: true, - customerName: true, + registerName: true, customer: { select: { customerType: true, @@ -133,13 +133,13 @@ export class WebHookController extends Controller { let textData = ""; if (dataEmployee.length > 0) { - const customerName = - dataEmployee[0]?.employee?.customerBranch?.customerName ?? "ไม่ระบุ"; + const registerName = + dataEmployee[0]?.employee?.customerBranch?.registerName ?? "ไม่ระบุ"; const telephoneNo = dataEmployee[0]?.employee?.customerBranch?.customer.registeredBranch.telephoneNo ?? "ไม่ระบุ"; - const textEmployer = `เรียน คุณ${customerName}`; + const textEmployer = `เรียน คุณ${registerName}`; const textAlert = "ขอแจ้งให้ทราบว่าหนังสือเดินทางของลูกจ้าง"; const textAlert2 = "และจำเป็นต้องดำเนินการต่ออายุในเร็ว ๆ นี้"; const textExpDate = From 73fea9d9ed2a56901fe799cb2fc8368d90a0ab17 Mon Sep 17 00:00:00 2001 From: Kanjana Date: Wed, 9 Jul 2025 16:12:13 +0700 Subject: [PATCH 081/167] feat: add businessType --- src/controllers/03-customer-branch-controller.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/controllers/03-customer-branch-controller.ts b/src/controllers/03-customer-branch-controller.ts index f8c8a6b..d07f0ce 100644 --- a/src/controllers/03-customer-branch-controller.ts +++ b/src/controllers/03-customer-branch-controller.ts @@ -246,6 +246,7 @@ export class CustomerBranchController extends Controller { createdBy: true, updatedBy: true, _count: true, + businessType: true, }, where, take: pageSize, @@ -268,6 +269,7 @@ export class CustomerBranchController extends Controller { subDistrict: true, createdBy: true, updatedBy: true, + businessType: true, }, where: { id: branchId }, }); @@ -429,6 +431,7 @@ export class CustomerBranchController extends Controller { subDistrict: true, createdBy: true, updatedBy: true, + businessType: true, }, data: { ...rest, @@ -471,6 +474,7 @@ export class CustomerBranchController extends Controller { }, }, }, + businessType: true, }, }); @@ -533,6 +537,7 @@ export class CustomerBranchController extends Controller { subDistrict: true, createdBy: true, updatedBy: true, + businessType: true, }, data: { ...rest, @@ -561,6 +566,7 @@ export class CustomerBranchController extends Controller { }, }, }, + businessType: true, }, }); @@ -613,6 +619,7 @@ export class CustomerBranchFileController extends Controller { }, }, }, + businessType: true, }, }); if (!data) throw notFoundError("Customer Branch"); From 126e00baf15a9c4ae51bd24afe5de2ea2cccb11a Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 9 Jul 2025 16:34:00 +0700 Subject: [PATCH 082/167] fix: stats does not account for permission --- src/controllers/07-task-controller.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/controllers/07-task-controller.ts b/src/controllers/07-task-controller.ts index bac70dd..6707781 100644 --- a/src/controllers/07-task-controller.ts +++ b/src/controllers/07-task-controller.ts @@ -70,8 +70,9 @@ const permissionCheckCompany = createPermCheck((_) => true); @Tags("Task Order") export class TaskController extends Controller { @Get("stats") - async getTaskOrderStats() { + async getTaskOrderStats(@Request() req: RequestWithUser) { const task = await prisma.taskOrder.groupBy({ + where: { registeredBranch: { OR: permissionCondCompany(req.user) } }, by: ["taskOrderStatus"], _count: true, }); From 15e0e34a47ebf9be6d582188dad490a52dcd64ab Mon Sep 17 00:00:00 2001 From: Kanjana Date: Wed, 9 Jul 2025 17:26:49 +0700 Subject: [PATCH 083/167] feat: change listAllowed --- src/controllers/03-customer-branch-controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/03-customer-branch-controller.ts b/src/controllers/03-customer-branch-controller.ts index d07f0ce..aada50c 100644 --- a/src/controllers/03-customer-branch-controller.ts +++ b/src/controllers/03-customer-branch-controller.ts @@ -57,7 +57,7 @@ const MANAGE_ROLES = [ ]; function globalAllow(user: RequestWithUser["user"]) { - const listAllowed = ["system", "head_of_admin", "admin", "executive", "accountant"]; + const listAllowed = MANAGE_ROLES; return user.roles?.some((v) => listAllowed.includes(v)) || false; } From 7112a545b181d73c8b7017c54406c114699e3084 Mon Sep 17 00:00:00 2001 From: Kanjana Date: Wed, 9 Jul 2025 17:31:46 +0700 Subject: [PATCH 084/167] feat: add crud businessType --- .../10-business-type-controller.ts | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 src/controllers/10-business-type-controller.ts diff --git a/src/controllers/10-business-type-controller.ts b/src/controllers/10-business-type-controller.ts new file mode 100644 index 0000000..2cd80a6 --- /dev/null +++ b/src/controllers/10-business-type-controller.ts @@ -0,0 +1,111 @@ +import { + Body, + Controller, + Delete, + Get, + Path, + Post, + Put, + Query, + Request, + Route, + Security, + Tags, +} from "tsoa"; +import { RequestWithUser } from "../interfaces/user"; +import prisma from "../db"; +import { Prisma } from "@prisma/client"; +import { queryOrNot } from "../utils/relation"; +import { notFoundError } from "../utils/error"; + +type BusinessTypePayload = { + name: string; + nameEN: string; +}; + +@Route("api/v1/business-type") +@Tags("Business Type") +export class businessTypeController extends Controller { + @Get() + @Security("keycloak") + async getList( + @Request() req: RequestWithUser, + @Query() query: string = "", + @Query() page: number = 1, + @Query() pageSize: number = 30, + ) { + const where = { + OR: queryOrNot(query, [ + { name: { contains: query, mode: "insensitive" } }, + { nameEN: { contains: query, mode: "insensitive" } }, + ]), + } satisfies Prisma.BusinessTypeWhereInput; + + const [result, total] = await prisma.$transaction([ + prisma.businessType.findMany({ + where, + }), + prisma.businessType.count({ where }), + ]); + + return { result, page, pageSize, total }; + } + + @Post() + @Security("keycloak") + async createBusinessType(@Request() req: RequestWithUser, @Body() body: BusinessTypePayload) { + return await prisma.businessType.create({ + data: { + ...body, + createdByUserId: req.user.sub, + updatedByUserId: req.user.sub, + }, + }); + } + + @Get(":businessTypeId") + @Security("keycloak") + async getBusinessTypeById(@Path() businessTypeId: string) { + return await prisma.businessType.findUnique({ + where: { id: businessTypeId }, + }); + } + + @Put(":businessTypeId") + @Security("keycloak") + async updateBusinessType( + @Request() req: RequestWithUser, + @Path() businessTypeId: string, + @Body() body: BusinessTypePayload, + ) { + return await prisma.$transaction(async (tx) => { + const record = await tx.businessType.findUnique({ + where: { id: businessTypeId }, + }); + + if (!record) throw notFoundError("BusinessType"); + return await prisma.businessType.update({ + where: { id: businessTypeId }, + data: { + ...body, + updatedByUserId: req.user.sub, + }, + }); + }); + } + + @Delete(":businessTypeId") + @Security("keycloak") + async deleteBusinessType(@Path() businessTypeId: string) { + return await prisma.$transaction(async (tx) => { + const record = await tx.businessType.findUnique({ + where: { id: businessTypeId }, + }); + + if (!record) throw notFoundError("BusinessType"); + return await prisma.businessType.delete({ + where: { id: businessTypeId }, + }); + }); + } +} From 1f63089363b675e42c6cdb40f7abf01d38edf27d Mon Sep 17 00:00:00 2001 From: Kanjana Date: Wed, 9 Jul 2025 17:40:24 +0700 Subject: [PATCH 085/167] fest: add page --- src/controllers/10-business-type-controller.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/controllers/10-business-type-controller.ts b/src/controllers/10-business-type-controller.ts index 2cd80a6..6d7373c 100644 --- a/src/controllers/10-business-type-controller.ts +++ b/src/controllers/10-business-type-controller.ts @@ -44,6 +44,8 @@ export class businessTypeController extends Controller { const [result, total] = await prisma.$transaction([ prisma.businessType.findMany({ where, + take: pageSize, + skip: (page - 1) * pageSize, }), prisma.businessType.count({ where }), ]); @@ -84,7 +86,7 @@ export class businessTypeController extends Controller { }); if (!record) throw notFoundError("BusinessType"); - return await prisma.businessType.update({ + return await tx.businessType.update({ where: { id: businessTypeId }, data: { ...body, @@ -103,7 +105,7 @@ export class businessTypeController extends Controller { }); if (!record) throw notFoundError("BusinessType"); - return await prisma.businessType.delete({ + return await tx.businessType.delete({ where: { id: businessTypeId }, }); }); From 1cf53c91aa451a0fc82bfc7c7a96d0884c50e16c Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 9 Jul 2025 17:45:14 +0700 Subject: [PATCH 086/167] feat: doc template now include business type relation --- src/controllers/00-doc-template-controller.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/controllers/00-doc-template-controller.ts b/src/controllers/00-doc-template-controller.ts index 7f05110..f457372 100644 --- a/src/controllers/00-doc-template-controller.ts +++ b/src/controllers/00-doc-template-controller.ts @@ -36,6 +36,7 @@ const quotationData = (id: string) => customerBranch: { include: { customer: true, + businessType: true, province: true, district: true, subDistrict: true, From ccee3092681ae0d151ec7cada66cb79b654cd262 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 9 Jul 2025 17:46:27 +0700 Subject: [PATCH 087/167] feat: deprecated business type helper --- src/controllers/00-doc-template-controller.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/controllers/00-doc-template-controller.ts b/src/controllers/00-doc-template-controller.ts index f457372..bda7956 100644 --- a/src/controllers/00-doc-template-controller.ts +++ b/src/controllers/00-doc-template-controller.ts @@ -368,6 +368,9 @@ function gender(text: string, lang: "th" | "en" = "en") { } } +/** + * @deprecated + */ function businessType(text: string, lang: "th" | "en" = "en") { switch (lang) { case "th": From 1f34ea7ecbaa47c2401398e5dd54b9aaf72e794e Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Fri, 11 Jul 2025 11:13:18 +0700 Subject: [PATCH 088/167] feat: include business relation --- src/controllers/03-customer-controller.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/controllers/03-customer-controller.ts b/src/controllers/03-customer-controller.ts index 580e60f..0ff2b19 100644 --- a/src/controllers/03-customer-controller.ts +++ b/src/controllers/03-customer-controller.ts @@ -201,6 +201,7 @@ export class CustomerController extends Controller { branch: includeBranch ? { include: { + businessType: true, province: true, district: true, subDistrict: true, From 0c53ac69b0ad649cca06279b2685d5c084225366 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Mon, 14 Jul 2025 11:13:33 +0700 Subject: [PATCH 089/167] fix: auth error --- src/controllers/07-task-controller.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/controllers/07-task-controller.ts b/src/controllers/07-task-controller.ts index 6707781..bc82406 100644 --- a/src/controllers/07-task-controller.ts +++ b/src/controllers/07-task-controller.ts @@ -70,6 +70,7 @@ const permissionCheckCompany = createPermCheck((_) => true); @Tags("Task Order") export class TaskController extends Controller { @Get("stats") + @Security("keycloak") async getTaskOrderStats(@Request() req: RequestWithUser) { const task = await prisma.taskOrder.groupBy({ where: { registeredBranch: { OR: permissionCondCompany(req.user) } }, From 6c350b12ceca077a6307c543e71dee2fd10d4a6d Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Tue, 15 Jul 2025 15:17:53 +0700 Subject: [PATCH 090/167] fix: prisma client error --- src/controllers/03-customer-controller.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/controllers/03-customer-controller.ts b/src/controllers/03-customer-controller.ts index 0ff2b19..2f31d79 100644 --- a/src/controllers/03-customer-controller.ts +++ b/src/controllers/03-customer-controller.ts @@ -326,6 +326,8 @@ export class CustomerController extends Controller { ...v, code: `${runningKey.replace(`CUSTOMER_BRANCH_${company}_`, "")}-${`${last.value - branch.length + i}`.padStart(2, "0")}`, codeCustomer: runningKey.replace(`CUSTOMER_BRANCH_${company}_`, ""), + businessType: connectOrNot(v.businessTypeId), + businessTypeId: undefined, agentUser: connectOrNot(v.agentUserId), agentUserId: undefined, province: connectOrNot(v.provinceId), From 236ee48eab8e79d3dae2861be14706cd7e2dc2c8 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 16 Jul 2025 09:38:30 +0700 Subject: [PATCH 091/167] feat: foreign address --- prisma/schema.prisma | 19 +++++++++++++------ src/controllers/02-user-controller.ts | 8 ++++++++ 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index e444ae1..db3ea95 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -398,14 +398,21 @@ model User { street String? streetEN String? - province Province? @relation(fields: [provinceId], references: [id], onDelete: SetNull) - provinceId String? + addressForeign Boolean @default(false) - district District? @relation(fields: [districtId], references: [id], onDelete: SetNull) - districtId String? + provinceText String? + province Province? @relation(fields: [provinceId], references: [id], onDelete: SetNull) + provinceId String? - subDistrict SubDistrict? @relation(fields: [subDistrictId], references: [id], onDelete: SetNull) - subDistrictId String? + districtText String? + district District? @relation(fields: [districtId], references: [id], onDelete: SetNull) + districtId String? + + subDistrictText String? + subDistrict SubDistrict? @relation(fields: [subDistrictId], references: [id], onDelete: SetNull) + subDistrictId String? + + zipCodeText String? email String telephoneNo String diff --git a/src/controllers/02-user-controller.ts b/src/controllers/02-user-controller.ts index 143eb71..b00f650 100644 --- a/src/controllers/02-user-controller.ts +++ b/src/controllers/02-user-controller.ts @@ -111,6 +111,7 @@ type UserCreate = { responsibleArea?: string[] | null; birthDate?: Date | null; + addressForeign?: boolean; address: string; addressEN: string; soi?: string | null; @@ -122,8 +123,11 @@ type UserCreate = { email: string; telephoneNo: string; + subDistrictText?: string | null; subDistrictId?: string | null; + districtText?: string | null; districtId?: string | null; + provinceText?: string | null; provinceId?: string | null; selectedImage?: string; @@ -173,6 +177,7 @@ type UserUpdate = { responsibleArea?: string[] | null; birthDate?: Date | null; + addressForeign?: boolean; address?: string; addressEN?: string; soi?: string | null; @@ -186,8 +191,11 @@ type UserUpdate = { selectedImage?: string; + subDistrictText?: string | null; subDistrictId?: string | null; + districtText?: string | null; districtId?: string | null; + provinceText?: string | null; provinceId?: string | null; branchId?: string | string[]; From 2ee0e97953c9ac747ed7fb758bcfe261b96bfb24 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 16 Jul 2025 09:38:34 +0700 Subject: [PATCH 092/167] chore: migration --- .../20250716023822_allow_foreign_address/migration.sql | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 prisma/migrations/20250716023822_allow_foreign_address/migration.sql diff --git a/prisma/migrations/20250716023822_allow_foreign_address/migration.sql b/prisma/migrations/20250716023822_allow_foreign_address/migration.sql new file mode 100644 index 0000000..a255d89 --- /dev/null +++ b/prisma/migrations/20250716023822_allow_foreign_address/migration.sql @@ -0,0 +1,6 @@ +-- AlterTable +ALTER TABLE "User" ADD COLUMN "addressForeign" BOOLEAN NOT NULL DEFAULT false, +ADD COLUMN "districtText" TEXT, +ADD COLUMN "provinceText" TEXT, +ADD COLUMN "subDistrictText" TEXT, +ADD COLUMN "zipCodeText" TEXT; From f90ee41a56f17a07d2edb6c1a9a5af2d9b0e1fa9 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 16 Jul 2025 09:44:46 +0700 Subject: [PATCH 093/167] feat: add address text en --- prisma/schema.prisma | 21 ++++++++++++--------- src/controllers/02-user-controller.ts | 6 ++++++ 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index db3ea95..9cf97fd 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -400,17 +400,20 @@ model User { addressForeign Boolean @default(false) - provinceText String? - province Province? @relation(fields: [provinceId], references: [id], onDelete: SetNull) - provinceId String? + provinceText String? + provinceTextEN String? + province Province? @relation(fields: [provinceId], references: [id], onDelete: SetNull) + provinceId String? - districtText String? - district District? @relation(fields: [districtId], references: [id], onDelete: SetNull) - districtId String? + districtText String? + districtTextEN String? + district District? @relation(fields: [districtId], references: [id], onDelete: SetNull) + districtId String? - subDistrictText String? - subDistrict SubDistrict? @relation(fields: [subDistrictId], references: [id], onDelete: SetNull) - subDistrictId String? + subDistrictText String? + subDistrictTextEN String? + subDistrict SubDistrict? @relation(fields: [subDistrictId], references: [id], onDelete: SetNull) + subDistrictId String? zipCodeText String? diff --git a/src/controllers/02-user-controller.ts b/src/controllers/02-user-controller.ts index b00f650..c663b74 100644 --- a/src/controllers/02-user-controller.ts +++ b/src/controllers/02-user-controller.ts @@ -124,10 +124,13 @@ type UserCreate = { telephoneNo: string; subDistrictText?: string | null; + subDistrictTextEN?: string | null; subDistrictId?: string | null; districtText?: string | null; + districtTextEN?: string | null; districtId?: string | null; provinceText?: string | null; + provinceTextEN?: string | null; provinceId?: string | null; selectedImage?: string; @@ -192,10 +195,13 @@ type UserUpdate = { selectedImage?: string; subDistrictText?: string | null; + subDistrictTextEN?: string | null; subDistrictId?: string | null; districtText?: string | null; + districtTextEN?: string | null; districtId?: string | null; provinceText?: string | null; + provinceTextEN?: string | null; provinceId?: string | null; branchId?: string | string[]; From 5aa8b06cf2d1a848a2eb036d6e514b52eca24b88 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 16 Jul 2025 09:44:50 +0700 Subject: [PATCH 094/167] chore: migration --- .../20250716024423_add_address_text_en/migration.sql | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 prisma/migrations/20250716024423_add_address_text_en/migration.sql diff --git a/prisma/migrations/20250716024423_add_address_text_en/migration.sql b/prisma/migrations/20250716024423_add_address_text_en/migration.sql new file mode 100644 index 0000000..e55a4d0 --- /dev/null +++ b/prisma/migrations/20250716024423_add_address_text_en/migration.sql @@ -0,0 +1,4 @@ +-- AlterTable +ALTER TABLE "User" ADD COLUMN "districtTextEN" TEXT, +ADD COLUMN "provinceTextEN" TEXT, +ADD COLUMN "subDistrictTextEN" TEXT; From b7a13b2d7a44ad4af780463dcfe67f182314c2d3 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 16 Jul 2025 09:51:27 +0700 Subject: [PATCH 095/167] feat: handle foreign address --- src/controllers/00-doc-template-controller.ts | 47 +++++++++++++++---- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/src/controllers/00-doc-template-controller.ts b/src/controllers/00-doc-template-controller.ts index bda7956..da5dc9d 100644 --- a/src/controllers/00-doc-template-controller.ts +++ b/src/controllers/00-doc-template-controller.ts @@ -288,6 +288,7 @@ function replaceEmptyField(data: T): T { } type FullAddress = { + addressForeign?: boolean; address: string; addressEN: string; moo?: string; @@ -296,8 +297,14 @@ type FullAddress = { soiEN?: string; street?: string; streetEN?: string; + provinceText?: string | null; + provinceTextEN?: string | null; province?: Province | null; + districtText?: string | null; + districtTextEN?: string | null; district?: District | null; + subDistrictText?: string | null; + subDistrictTextEN?: string | null; subDistrict?: SubDistrict | null; en?: boolean; }; @@ -331,13 +338,22 @@ function addressFull(addr: FullAddress, lang: "th" | "en" = "en") { if (addr.soi) fragments.push(`ซอย ${addr.soi},`); if (addr.street) fragments.push(`ถนน${addr.street},`); - if (addr.subDistrict) { - fragments.push(`${addr.province?.id === "10" ? "แขวง" : "ตำบล"}${addr.subDistrict.name},`); + if (!addr.addressForeign && addr.subDistrict) { + fragments.push(`${addr.province?.id === "10" ? "แขวง" : "ตำบล"}${addr.subDistrict.name}`); } - if (addr.district) { - fragments.push(`${addr.province?.id === "10" ? "เขต" : "อำเภอ"}${addr.district.name},`); + if (addr.addressForeign && addr.subDistrictText) { + fragments.push(`ตำบล${addr.subDistrictText}`); } - if (addr.province) fragments.push(`จังหวัด${addr.province.name},`); + + if (!addr.addressForeign && addr.district) { + fragments.push(`${addr.province?.id === "10" ? "เขต" : "อำเภอ"}${addr.district.name}`); + } + if (addr.addressForeign && addr.districtText) { + fragments.push(`อำเภอ${addr.districtText}`); + } + + if (!addr.addressForeign && addr.province) fragments.push(`จังหวัด${addr.province.name}`); + if (addr.addressForeign && addr.provinceText) fragments.push(`จังหวัด${addr.provinceText}`); break; default: @@ -346,11 +362,26 @@ function addressFull(addr: FullAddress, lang: "th" | "en" = "en") { if (addr.soiEN) fragments.push(`Soi ${addr.soiEN},`); if (addr.streetEN) fragments.push(`${addr.streetEN} Rd.`); - if (addr.subDistrict) { + if (!addr.addressForeign && addr.subDistrict) { fragments.push(`${addr.subDistrict.nameEN} sub-district,`); } - if (addr.district) fragments.push(`${addr.district.nameEN} district,`); - if (addr.province) fragments.push(`${addr.province.nameEN},`); + if (addr.addressForeign && addr.subDistrictTextEN) { + fragments.push(`${addr.subDistrictTextEN} sub-district,`); + } + + if (!addr.addressForeign && addr.district) { + fragments.push(`${addr.district.nameEN} district,`); + } + if (addr.addressForeign && addr.districtTextEN) { + fragments.push(`${addr.districtTextEN} district,`); + } + + if (!addr.addressForeign && addr.province) { + fragments.push(`${addr.province.nameEN},`); + } + if (addr.addressForeign && addr.provinceTextEN) { + fragments.push(`${addr.provinceTextEN} district,`); + } break; } From 46ba857e2892fb2107a353566bedd8831c69f5ed Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 16 Jul 2025 11:38:11 +0700 Subject: [PATCH 096/167] fix: missing type --- src/controllers/02-user-controller.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/controllers/02-user-controller.ts b/src/controllers/02-user-controller.ts index c663b74..e05fcd1 100644 --- a/src/controllers/02-user-controller.ts +++ b/src/controllers/02-user-controller.ts @@ -132,6 +132,7 @@ type UserCreate = { provinceText?: string | null; provinceTextEN?: string | null; provinceId?: string | null; + zipCodeText?: string | null; selectedImage?: string; @@ -203,6 +204,7 @@ type UserUpdate = { provinceText?: string | null; provinceTextEN?: string | null; provinceId?: string | null; + zipCodeText?: string | null; branchId?: string | string[]; From e4caeaa7809af10c1b08581f4e556a62b066d3de Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 16 Jul 2025 12:54:52 +0700 Subject: [PATCH 097/167] fix: new request does not trigger noti --- src/controllers/05-payment-controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/05-payment-controller.ts b/src/controllers/05-payment-controller.ts index 196054f..5f530d0 100644 --- a/src/controllers/05-payment-controller.ts +++ b/src/controllers/05-payment-controller.ts @@ -249,7 +249,7 @@ export class QuotationPayment extends Controller { }, }); - if (quotation.quotationStatus === "PaymentPending") { + if (quotation.quotationStatus === "PaymentInProcess") { await prisma.notification.create({ data: { title: "รายการคำขอใหม่ / New Request", From 5c824a738aac25d81aa0406f98bf797d2d55fdf6 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 16 Jul 2025 12:59:15 +0700 Subject: [PATCH 098/167] feat: notify more group when create new quotation --- src/controllers/05-quotation-controller.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/controllers/05-quotation-controller.ts b/src/controllers/05-quotation-controller.ts index 04353bb..29face5 100644 --- a/src/controllers/05-quotation-controller.ts +++ b/src/controllers/05-quotation-controller.ts @@ -664,7 +664,14 @@ export class QuotationController extends Controller { title: "ใบเสนอราคาใหม่ / New Quotation", detail: "รหัส / code : " + ret.code, registeredBranchId: ret.registeredBranchId, - groupReceiver: { create: [{ name: "sale" }, { name: "head_of_sale" }] }, + groupReceiver: { + create: [ + { name: "sale" }, + { name: "head_of_sale" }, + { name: "accountant" }, + { name: "branch_accountant" }, + ], + }, }, }); From afb89ef94970d16d62974711d34fea50286dfca6 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Mon, 21 Jul 2025 10:38:31 +0700 Subject: [PATCH 099/167] feat: notify document check when status changed --- src/controllers/07-task-controller.ts | 144 ++++++++++++++------------ 1 file changed, 80 insertions(+), 64 deletions(-) diff --git a/src/controllers/07-task-controller.ts b/src/controllers/07-task-controller.ts index bc82406..253b9a5 100644 --- a/src/controllers/07-task-controller.ts +++ b/src/controllers/07-task-controller.ts @@ -337,49 +337,51 @@ export class TaskController extends Controller { where: { OR: taskList }, }); - return await tx.taskOrder.create({ - include: { - taskList: { - include: { - requestWorkStep: { - include: { - requestWork: { - include: { - request: { - include: { - employee: true, - quotation: { - include: { - customerBranch: { - include: { - customer: true, - }, - }, - }, - }, - }, - }, - productService: { - include: { - service: { - include: { - workflow: { - include: { - step: { - include: { - value: true, - responsiblePerson: { - include: { user: true }, - }, - responsibleInstitution: true, - }, + return await tx.taskOrder + .create({ + include: { + taskList: { + include: { + requestWorkStep: { + include: { + requestWork: { + include: { + request: { + include: { + employee: true, + quotation: { + include: { + customerBranch: { + include: { + customer: true, }, }, }, }, }, - work: true, - product: true, + }, + productService: { + include: { + service: { + include: { + workflow: { + include: { + step: { + include: { + value: true, + responsiblePerson: { + include: { user: true }, + }, + responsibleInstitution: true, + }, + }, + }, + }, + }, + }, + work: true, + product: true, + }, }, }, }, @@ -387,20 +389,29 @@ export class TaskController extends Controller { }, }, }, + institution: true, + createdBy: true, }, - institution: true, - createdBy: true, - }, - data: { - ...rest, - code, - urgent: work.some((v) => v.requestWork.request.quotation.urgent), - registeredBranchId: userAffiliatedBranch.id, - createdByUserId: req.user.sub, - taskList: { create: taskList }, - taskProduct: { create: taskProduct }, - }, - }); + data: { + ...rest, + code, + urgent: work.some((v) => v.requestWork.request.quotation.urgent), + registeredBranchId: userAffiliatedBranch.id, + createdByUserId: req.user.sub, + taskList: { create: taskList }, + taskProduct: { create: taskProduct }, + }, + }) + .then(async (v) => { + await prisma.notification.create({ + data: { + title: "ใบสั่งงานใหม่ / New Task Order", + detail: "รหัส / code : " + v.code, + groupReceiver: { create: { name: "document_checker" } }, + }, + }); + return v; + }); }); } @@ -543,6 +554,7 @@ export class TaskController extends Controller { title: "มีการส่งงาน / Task Submitted", detail: "รหัสใบสั่งงาน / Order : " + record.code, receiverId: record.createdByUserId, + groupReceiver: { create: { name: "document_checker" } }, }, }); } @@ -737,6 +749,7 @@ export class TaskActionController extends Controller { title: "มีการส่งงาน / Task Submitted", detail: "รหัสใบสั่งงาน / Order : " + record.code, receiverId: record.createdByUserId, + groupReceiver: { create: { name: "document_checker" } }, }, }), ]); @@ -936,6 +949,7 @@ export class TaskActionController extends Controller { title: "สถานะใบเสนอราคาเปลี่ยนแปลง / Quotation Status Updated", detail: "รหัส / code : " + v.code + " Completed", receiverId: v.createdByUserId, + groupReceiver: { create: { name: "document_checker" } }, })), }); @@ -1176,19 +1190,21 @@ export class UserTaskController extends Controller { }, }) .then(async (v) => { - await tx.notification.createMany({ - data: [ - { - title: "สถานะใบส่งงานมีการเปลี่ยนแปลง / Order Status Changed", - detail: "รหัสใบสั่งงาน / Order : " + v.code + " InProgress", - receiverId: v.createdByUserId, - }, - { - title: "มีการรับงาน / Task Accepted", - detail: "รหัสใบสั่งงาน / Order : " + v.code, - receiverId: v.createdByUserId, - }, - ], + await tx.notification.create({ + data: { + title: "สถานะใบส่งงานมีการเปลี่ยนแปลง / Order Status Changed", + detail: "รหัสใบสั่งงาน / Order : " + v.code + " InProgress", + receiverId: v.createdByUserId, + groupReceiver: { create: { name: "document_checker" } }, + }, + }); + await tx.notification.create({ + data: { + title: "มีการรับงาน / Task Accepted", + detail: "รหัสใบสั่งงาน / Order : " + v.code, + receiverId: v.createdByUserId, + groupReceiver: { create: { name: "document_checker" } }, + }, }); }), tx.task.updateMany({ From 3455ae604a87da045e4d511a8af130d65da66fa7 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Mon, 21 Jul 2025 11:17:23 +0700 Subject: [PATCH 100/167] feat: more notification related to task and request --- src/controllers/07-task-controller.ts | 63 +++++++++++++++++++-------- 1 file changed, 46 insertions(+), 17 deletions(-) diff --git a/src/controllers/07-task-controller.ts b/src/controllers/07-task-controller.ts index 253b9a5..c9b6b9d 100644 --- a/src/controllers/07-task-controller.ts +++ b/src/controllers/07-task-controller.ts @@ -782,22 +782,33 @@ export class TaskActionController extends Controller { const code = `RI${year}${month}${last.value.toString().padStart(6, "0")}`; await Promise.all([ - tx.taskOrder.update({ - where: { id: taskOrderId }, - data: { - urgent: false, - taskOrderStatus: TaskOrderStatus.Complete, - codeProductReceived: code, - userTask: { - updateMany: { - where: { taskOrderId }, - data: { - userTaskStatus: UserTaskStatus.Submit, + tx.taskOrder + .update({ + where: { id: taskOrderId }, + data: { + urgent: false, + taskOrderStatus: TaskOrderStatus.Complete, + codeProductReceived: code, + userTask: { + updateMany: { + where: { taskOrderId }, + data: { + userTaskStatus: UserTaskStatus.Submit, + }, }, }, }, - }, - }), + }) + .then(async (record) => { + await tx.notification.create({ + data: { + title: "ใบงานเสร็จสิ้น / Task Complete", + detail: "รหัสใบสั่งงาน / Order : " + record.code, + receiverId: record.createdByUserId, + groupReceiver: { create: { name: "document_checker" } }, + }, + }); + }), tx.requestWorkStepStatus.updateMany({ where: { task: { @@ -901,10 +912,28 @@ export class TaskActionController extends Controller { if (completeCheck) completed.push(item.id); }); - await tx.requestData.updateMany({ - where: { id: { in: completed } }, - data: { requestDataStatus: RequestDataStatus.Completed }, - }); + await tx.requestData + .updateManyAndReturn({ + where: { id: { in: completed } }, + include: { + quotation: { + select: { + createdByUserId: true, + }, + }, + }, + data: { requestDataStatus: RequestDataStatus.Completed }, + }) + .then(async (res) => { + await tx.notification.createMany({ + data: res.map((v) => ({ + title: "รายการคำขอเสร็จสิ้น / Request Complete", + detail: "รหัส / code : " + v.code + " Completed", + receiverId: v.quotation.createdByUserId, + groupReceiver: { create: { name: "document_checker" } }, + })), + }); + }); await tx.quotation .updateManyAndReturn({ where: { From 8a87e37097743904306be2b73d8cd152c93c14bf Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Mon, 21 Jul 2025 13:25:34 +0700 Subject: [PATCH 101/167] fix: search and pendingOnly get mixed --- src/controllers/09-line-controller.ts | 65 +++++++++++++-------------- 1 file changed, 32 insertions(+), 33 deletions(-) diff --git a/src/controllers/09-line-controller.ts b/src/controllers/09-line-controller.ts index 8e708be..87480b7 100644 --- a/src/controllers/09-line-controller.ts +++ b/src/controllers/09-line-controller.ts @@ -613,39 +613,22 @@ export class LineController extends Controller { @Query() endDate?: Date, ) { const where = { - OR: - query || pendingOnly - ? [ - ...(queryOrNot(query, [ - { code: { contains: query, mode: "insensitive" } }, - { workName: { contains: query, mode: "insensitive" } }, - { - customerBranch: { - OR: [ - { code: { contains: query, mode: "insensitive" } }, - { registerName: { contains: query, mode: "insensitive" } }, - { firstName: { contains: query, mode: "insensitive" } }, - { firstNameEN: { contains: query, mode: "insensitive" } }, - { lastName: { contains: query, mode: "insensitive" } }, - { lastNameEN: { contains: query, mode: "insensitive" } }, - ], - }, - }, - ]) || []), - ...(queryOrNot(!!pendingOnly, [ - { - requestData: { - some: { - requestDataStatus: "Pending", - }, - }, - }, - { - requestData: { none: {} }, - }, - ]) || []), - ] - : undefined, + OR: queryOrNot(query, [ + { code: { contains: query, mode: "insensitive" } }, + { workName: { contains: query, mode: "insensitive" } }, + { + customerBranch: { + OR: [ + { code: { contains: query, mode: "insensitive" } }, + { registerName: { contains: query, mode: "insensitive" } }, + { firstName: { contains: query, mode: "insensitive" } }, + { firstNameEN: { contains: query, mode: "insensitive" } }, + { lastName: { contains: query, mode: "insensitive" } }, + { lastNameEN: { contains: query, mode: "insensitive" } }, + ], + }, + }, + ]), isDebitNote: false, code, payCondition, @@ -667,6 +650,22 @@ export class LineController extends Controller { }, } : undefined, + AND: pendingOnly + ? { + OR: [ + { + requestData: { + some: { + requestDataStatus: "Pending", + }, + }, + }, + { + requestData: { none: {} }, + }, + ], + } + : undefined, ...whereDateQuery(startDate, endDate), } satisfies Prisma.QuotationWhereInput; From 5de1f27fca9760b30011dcc1ce53d71da9ae98f2 Mon Sep 17 00:00:00 2001 From: Kanjana Date: Mon, 21 Jul 2025 17:57:00 +0700 Subject: [PATCH 102/167] feat: add alert RequestData and CreditNote --- src/controllers/06-request-list-controller.ts | 125 +++++++++++ src/controllers/08-credit-note-controller.ts | 209 ++++++++++++++---- 2 files changed, 295 insertions(+), 39 deletions(-) diff --git a/src/controllers/06-request-list-controller.ts b/src/controllers/06-request-list-controller.ts index 37fd8e2..eb7ec75 100644 --- a/src/controllers/06-request-list-controller.ts +++ b/src/controllers/06-request-list-controller.ts @@ -321,6 +321,14 @@ export class RequestDataController extends Controller { @Route("/api/v1/request-data/{requestDataId}") @Tags("Request List") export class RequestDataActionController extends Controller { + async #getLineToken() { + if (!process.env.LINE_MESSAGING_API_TOKEN) { + console.warn("Line Webhook Activated but LINE_MESSAGING_API_TOKEN not set."); + } + + return process.env.LINE_MESSAGING_API_TOKEN; + } + @Post("reject-request-cancel") @Security("keycloak") async rejectRequestCancel( @@ -395,6 +403,17 @@ export class RequestDataActionController extends Controller { }, }, }, + include: { + quotation: { + include: { + customerBranch: { + include: { + customer: { include: { branch: { where: { userId: { not: null } } } } }, + }, + }, + }, + }, + }, }); if (!result) throw notFoundError("Request Data"); @@ -454,6 +473,49 @@ export class RequestDataActionController extends Controller { data: { taskOrderStatus: TaskStatus.Canceled }, }), ]); + + const token = await this.#getLineToken(); + if (!token) return; + + const textHead = "JWS ALERT:"; + + const textAlert = "ขอแจ้งให้ทราบว่าใบเสนอราคา"; + const textAlert2 = "ได้ดำเนินการยกเลิกเรียบร้อยแล้ว"; + const textAlert3 = "หากต้องการข้อมูลเพิ่มเติม กรุณาแจ้งให้ฝ่ายที่เกี่ยวข้องทราบ 🙏"; + let finalTextWork = ""; + let textData = ""; + + let dataCustomerId: string[] = []; + let dataUserId: string[] = []; + + result.quotation.customerBranch.customer.branch.forEach((item) => { + if (!dataCustomerId?.includes(item.id) && item.userId) { + dataCustomerId.push(item.id); + dataUserId.push(item.userId); + } + }); + finalTextWork = `เลขที่ใบเสนอราคา: ${result.code} ${result.quotation.workName}`; + + textData = `${textHead}\n\n${textAlert}\n${finalTextWork}\n${textAlert2}\n\n${textAlert3}`; + + const data = { + to: dataUserId, + messages: [ + { + type: "text", + text: textData, + }, + ], + }; + + await fetch("https://api.line.me/v2/bot/message/multicast", { + method: "POST", + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + body: JSON.stringify(data), + }); }); } @@ -674,6 +736,19 @@ export class RequestDataActionController extends Controller { }, }, data: { quotationStatus: QuotationStatus.ProcessComplete, urgent: false }, + include: { + customerBranch: { + include: { + customer: { + include: { + branch: { + where: { userId: { not: null } }, + }, + }, + }, + }, + }, + }, }) .then(async (res) => { await tx.notification.createMany({ @@ -683,6 +758,56 @@ export class RequestDataActionController extends Controller { receiverId: v.createdByUserId, })), }); + + const token = await this.#getLineToken(); + if (!token) return; + + const textHead = "JWS ALERT:"; + + const textAlert = "ขอแจ้งให้ทราบว่าใบเสนอราคา"; + const textAlert2 = "ได้ดำเนินการเสร็จสิ้นทุกกระบวนการเรียบร้อยแล้ว"; + const textAlert3 = "หากต้องการข้อมูลเพิ่มเติม กรุณาแจ้งให้ฝ่ายที่เกี่ยวข้องทราบ 🙏"; + let finalTextWork = ""; + let textData = ""; + + let dataCustomerId: string[] = []; + let textWorkList: string[] = []; + let dataUserId: string[] = []; + + if (res) { + res.forEach((data, index) => { + data.customerBranch.customer.branch.forEach((item) => { + if (!dataCustomerId?.includes(item.id) && item.userId) { + dataCustomerId.push(item.id); + dataUserId.push(item.userId); + } + }); + textWorkList.push(`${index + 1}. เลขที่ใบเสนอราคา ${data.code} ${data.workName}`); + }); + + finalTextWork = textWorkList.join("\n"); + } + + textData = `${textHead}\n\n${textAlert}\n${finalTextWork}\n${textAlert2}\n\n${textAlert3}`; + + const data = { + to: dataUserId, + messages: [ + { + type: "text", + text: textData, + }, + ], + }; + + await fetch("https://api.line.me/v2/bot/message/multicast", { + method: "POST", + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + body: JSON.stringify(data), + }); }); // dataRecord.push(record); return data; diff --git a/src/controllers/08-credit-note-controller.ts b/src/controllers/08-credit-note-controller.ts index fd7a2a3..8af230b 100644 --- a/src/controllers/08-credit-note-controller.ts +++ b/src/controllers/08-credit-note-controller.ts @@ -83,6 +83,14 @@ type CreditNoteUpdate = { @Route("api/v1/credit-note") @Tags("Credit Note") export class CreditNoteController extends Controller { + async #getLineToken() { + if (!process.env.LINE_MESSAGING_API_TOKEN) { + console.warn("Line Webhook Activated but LINE_MESSAGING_API_TOKEN not set."); + } + + return process.env.LINE_MESSAGING_API_TOKEN; + } + @Get("stats") @Security("keycloak") async getCreditNoteStats(@Request() req: RequestWithUser, @Query() quotationId?: string) { @@ -366,33 +374,90 @@ export class CreditNoteController extends Controller { update: { value: { increment: 1 } }, }); - return await prisma.creditNote.create({ - include: { - requestWork: { - include: { - request: true, + return await prisma.creditNote + .create({ + include: { + requestWork: { + include: { + request: true, + }, + }, + quotation: { + include: { + customerBranch: { + include: { + customer: { include: { branch: { where: { userId: { not: null } } } } }, + }, + }, + }, }, }, - quotation: true, - }, - data: { - reason: body.reason, - detail: body.detail, - remark: body.remark, - paybackType: body.paybackType, - paybackBank: body.paybackBank, - paybackAccount: body.paybackAccount, - paybackAccountName: body.paybackAccountName, - code: `CN${currentYear.toString().padStart(2, "0")}${currentMonth.toString().padStart(2, "0")}${last.value.toString().padStart(6, "0")}`, - value, - requestWork: { - connect: body.requestWorkId.map((v) => ({ - id: v, - })), + data: { + reason: body.reason, + detail: body.detail, + remark: body.remark, + paybackType: body.paybackType, + paybackBank: body.paybackBank, + paybackAccount: body.paybackAccount, + paybackAccountName: body.paybackAccountName, + code: `CN${currentYear.toString().padStart(2, "0")}${currentMonth.toString().padStart(2, "0")}${last.value.toString().padStart(6, "0")}`, + value, + requestWork: { + connect: body.requestWorkId.map((v) => ({ + id: v, + })), + }, + quotationId: body.quotationId, }, - quotationId: body.quotationId, - }, - }); + }) + .then(async (res) => { + const token = await this.#getLineToken(); + if (!token) return; + + const textHead = "JWS ALERT:"; + + const textAlert = "ขอแจ้งให้ทราบว่าใบลดหนี้"; + const textAlert2 = "ได้ถูกสร้างขึ้นเรียบร้อยแล้ว"; + const textAlert3 = + "หากท่านต้องการข้อมูลเพิ่มเติมหรือมีข้อสงสัยประการใด โปรดแจ้งให้ฝ่ายที่เกี่ยวข้องทราบ ทางเรายินดีให้ความช่วยเหลืออย่างเต็มที่ 🙏"; + let finalTextWork = ""; + let textData = ""; + + let dataCustomerId: string[] = []; + let textWorkList: string[] = []; + let dataUserId: string[] = []; + + if (res) { + res.quotation.customerBranch.customer.branch.forEach((item) => { + if (!dataCustomerId?.includes(item.id) && item.userId) { + dataCustomerId.push(item.id); + dataUserId.push(item.userId); + } + }); + finalTextWork = `จำนวนเงิน ${res.value.toFixed(2)} บาท `; + } + + textData = `${textHead}\n\n${textAlert}\n${finalTextWork}${textAlert2}\n\n${textAlert3}`; + + const data = { + to: dataUserId, + messages: [ + { + type: "text", + text: textData, + }, + ], + }; + + await fetch("https://api.line.me/v2/bot/message/multicast", { + method: "POST", + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + body: JSON.stringify(data), + }); + }); }, { isolationLevel: Prisma.TransactionIsolationLevel.Serializable }, ); @@ -573,6 +638,14 @@ export class CreditNoteActionController extends Controller { return creditNoteData; } + async #getLineToken() { + if (!process.env.LINE_MESSAGING_API_TOKEN) { + console.warn("Line Webhook Activated but LINE_MESSAGING_API_TOKEN not set."); + } + + return process.env.LINE_MESSAGING_API_TOKEN; + } + @Post("accept") @Security("keycloak", MANAGE_ROLES) async acceptCreditNote(@Request() req: RequestWithUser, @Path() creditNoteId: string) { @@ -591,23 +664,81 @@ export class CreditNoteActionController extends Controller { @Body() body: { paybackStatus: PaybackStatus }, ) { await this.#checkPermission(req.user, creditNoteId); - return await prisma.creditNote.update({ - where: { id: creditNoteId }, - include: { - requestWork: { - include: { - request: true, + return await prisma.creditNote + .update({ + where: { id: creditNoteId }, + include: { + requestWork: { + include: { + request: true, + }, + }, + quotation: { + include: { + customerBranch: { + include: { + customer: { include: { branch: { where: { userId: { not: null } } } } }, + }, + }, + }, }, }, - quotation: true, - }, - data: { - creditNoteStatus: - body.paybackStatus === PaybackStatus.Done ? CreditNoteStatus.Success : undefined, - paybackStatus: body.paybackStatus, - paybackDate: body.paybackStatus === PaybackStatus.Done ? new Date() : undefined, - }, - }); + data: { + creditNoteStatus: + body.paybackStatus === PaybackStatus.Done ? CreditNoteStatus.Success : undefined, + paybackStatus: body.paybackStatus, + paybackDate: body.paybackStatus === PaybackStatus.Done ? new Date() : undefined, + }, + }) + .then(async (res) => { + const token = await this.#getLineToken(); + if (!token) return; + + const textHead = "JWS ALERT:"; + + const textAlert = "ทางเราขอแจ้งให้ทราบว่าการดำเนินการคืนเงินสำหรับใบลดหนี้"; + const textAlert2 = "ได้รับการอนุมัติและเสร็จสมบูรณ์เรียบร้อยแล้ว"; + const textAlert3 = + "หากท่านต้องการข้อมูลเพิ่มเติมหรือมีข้อสงสัยประการใด โปรดแจ้งให้ฝ่ายที่เกี่ยวข้องทราบ ทางเรายินดีให้ความช่วยเหลืออย่างเต็มที่ 🙏"; + let finalTextWork = ""; + let textData = ""; + + let dataCustomerId: string[] = []; + let textWorkList: string[] = []; + let dataUserId: string[] = []; + + if (res) { + res.quotation.customerBranch.customer.branch.forEach((item) => { + if (!dataCustomerId?.includes(item.id) && item.userId) { + dataCustomerId.push(item.id); + dataUserId.push(item.userId); + } + }); + finalTextWork = `จำนวนเงิน ${res.value.toFixed(2)} บาท `; + } + + textData = `${textHead}\n\n${textAlert}\n${finalTextWork}${textAlert2}\n\n${textAlert3}`; + + const data = { + to: dataUserId, + messages: [ + { + type: "text", + text: textData, + }, + ], + }; + body.paybackStatus === PaybackStatus.Done + ? await fetch("https://api.line.me/v2/bot/message/multicast", { + method: "POST", + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + body: JSON.stringify(data), + }) + : undefined; + }); } } From c7183887c9372c0881ab18930ef1395a7fd805d8 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Tue, 22 Jul 2025 10:35:48 +0700 Subject: [PATCH 103/167] fix: error create many with relation --- src/controllers/07-task-controller.ts | 40 ++++++++++++++++----------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/src/controllers/07-task-controller.ts b/src/controllers/07-task-controller.ts index c9b6b9d..ba2f656 100644 --- a/src/controllers/07-task-controller.ts +++ b/src/controllers/07-task-controller.ts @@ -925,14 +925,18 @@ export class TaskActionController extends Controller { data: { requestDataStatus: RequestDataStatus.Completed }, }) .then(async (res) => { - await tx.notification.createMany({ - data: res.map((v) => ({ - title: "รายการคำขอเสร็จสิ้น / Request Complete", - detail: "รหัส / code : " + v.code + " Completed", - receiverId: v.quotation.createdByUserId, - groupReceiver: { create: { name: "document_checker" } }, - })), - }); + await Promise.all( + res.map((v) => + tx.notification.create({ + data: { + title: "สถานะใบเสนอราคาเปลี่ยนแปลง / Quotation Status Updated", + detail: "รหัส / code : " + v.code + " Completed", + receiverId: v.quotation.createdByUserId, + groupReceiver: { create: { name: "document_checker" } }, + }, + }), + ), + ); }); await tx.quotation .updateManyAndReturn({ @@ -973,14 +977,18 @@ export class TaskActionController extends Controller { }, }) .then(async (res) => { - await tx.notification.createMany({ - data: res.map((v) => ({ - title: "สถานะใบเสนอราคาเปลี่ยนแปลง / Quotation Status Updated", - detail: "รหัส / code : " + v.code + " Completed", - receiverId: v.createdByUserId, - groupReceiver: { create: { name: "document_checker" } }, - })), - }); + await Promise.all( + res.map((v) => + tx.notification.create({ + data: { + title: "สถานะใบเสนอราคาเปลี่ยนแปลง / Quotation Status Updated", + detail: "รหัส / code : " + v.code + " Completed", + receiverId: v.createdByUserId, + groupReceiver: { create: { name: "document_checker" } }, + }, + }), + ), + ); const token = await this.#getLineToken(); From a30bc33b8160e20367860eeecec9edb6c961383a Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Tue, 22 Jul 2025 14:38:29 +0700 Subject: [PATCH 104/167] fix: create response --- src/controllers/08-credit-note-controller.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/controllers/08-credit-note-controller.ts b/src/controllers/08-credit-note-controller.ts index 8af230b..48ee2ab 100644 --- a/src/controllers/08-credit-note-controller.ts +++ b/src/controllers/08-credit-note-controller.ts @@ -457,6 +457,8 @@ export class CreditNoteController extends Controller { }, body: JSON.stringify(data), }); + + return res; }); }, { isolationLevel: Prisma.TransactionIsolationLevel.Serializable }, From 9e208dee895339df8b9886e1129b0c738eed3843 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Fri, 25 Jul 2025 09:42:22 +0700 Subject: [PATCH 105/167] feat: notify document_checker when task is canceled --- src/controllers/06-request-list-controller.ts | 29 ++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/src/controllers/06-request-list-controller.ts b/src/controllers/06-request-list-controller.ts index eb7ec75..a01f465 100644 --- a/src/controllers/06-request-list-controller.ts +++ b/src/controllers/06-request-list-controller.ts @@ -464,14 +464,29 @@ export class RequestDataActionController extends Controller { })), }); }), - tx.taskOrder.updateMany({ - where: { - taskList: { - every: { taskStatus: TaskStatus.Canceled }, + tx.taskOrder + .updateManyAndReturn({ + where: { + taskList: { + every: { taskStatus: TaskStatus.Canceled }, + }, }, - }, - data: { taskOrderStatus: TaskStatus.Canceled }, - }), + data: { taskOrderStatus: TaskStatus.Canceled }, + }) + .then(async (res) => { + await Promise.all( + res.map((v) => + tx.notification.create({ + data: { + title: "สถานะใบเสนอราคาเปลี่ยนแปลง / Quotation Status Updated", + detail: "รหัส / code : " + v.code + " Canceled", + receiverId: v.createdByUserId, + groupReceiver: { create: { name: "document_checker" } }, + }, + }), + ), + ); + }), ]); const token = await this.#getLineToken(); From bfc2608af4d4e26717a6485fc2c968989d9f6976 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Tue, 29 Jul 2025 09:44:25 +0700 Subject: [PATCH 106/167] fix: notification not show because of missing registered branch --- src/controllers/06-request-list-controller.ts | 11 +++++++++++ src/controllers/07-task-controller.ts | 9 +++++++++ 2 files changed, 20 insertions(+) diff --git a/src/controllers/06-request-list-controller.ts b/src/controllers/06-request-list-controller.ts index a01f465..45c21f7 100644 --- a/src/controllers/06-request-list-controller.ts +++ b/src/controllers/06-request-list-controller.ts @@ -461,6 +461,8 @@ export class RequestDataActionController extends Controller { title: "สถานะใบเสนอราคาเปลี่ยนแปลง / Quotation Status Updated", detail: "รหัส / code : " + v.code + " Canceled", receiverId: v.createdByUserId, + registeredBranchId: v.registeredBranchId, + groupReceiver: { create: { name: "document_checker" } }, })), }); }), @@ -481,6 +483,7 @@ export class RequestDataActionController extends Controller { title: "สถานะใบเสนอราคาเปลี่ยนแปลง / Quotation Status Updated", detail: "รหัส / code : " + v.code + " Canceled", receiverId: v.createdByUserId, + registeredBranchId: v.registeredBranchId, groupReceiver: { create: { name: "document_checker" } }, }, }), @@ -667,6 +670,8 @@ export class RequestDataActionController extends Controller { title: "สถานะใบเสนอราคาเปลี่ยนแปลง / Quotation Status Updated", detail: "รหัส / code : " + v.code + " Canceled", receiverId: v.createdByUserId, + registeredBranchId: v.registeredBranchId, + groupReceiver: { create: { name: "document_checker" } }, })), }); }), @@ -771,6 +776,8 @@ export class RequestDataActionController extends Controller { title: "สถานะใบเสนอราคาเปลี่ยนแปลง / Quotation Status Updated", detail: "รหัส / code : " + v.code + " Completed", receiverId: v.createdByUserId, + registeredBranchId: v.registeredBranchId, + groupReceiver: { create: { name: "document_checker" } }, })), }); @@ -1183,6 +1190,8 @@ export class RequestListController extends Controller { title: "สถานะใบเสนอราคาเปลี่ยนแปลง / Quotation Status Updated", detail: "รหัส / code : " + v.code + " Canceled", receiverId: v.createdByUserId, + registeredBranchId: v.registeredBranchId, + groupReceiver: { create: { name: "document_checker" } }, })), }); }), @@ -1297,6 +1306,8 @@ export class RequestListController extends Controller { title: "สถานะใบเสนอราคาเปลี่ยนแปลง / Quotation Status Updated", detail: "รหัส / code : " + v.code + " Completed", receiverId: v.createdByUserId, + registeredBranchId: v.registeredBranchId, + groupReceiver: { create: { name: "document_checker" } }, })), }); const token = await this.#getLineToken(); diff --git a/src/controllers/07-task-controller.ts b/src/controllers/07-task-controller.ts index ba2f656..ad97be0 100644 --- a/src/controllers/07-task-controller.ts +++ b/src/controllers/07-task-controller.ts @@ -407,6 +407,7 @@ export class TaskController extends Controller { data: { title: "ใบสั่งงานใหม่ / New Task Order", detail: "รหัส / code : " + v.code, + registeredBranchId: v.registeredBranchId, groupReceiver: { create: { name: "document_checker" } }, }, }); @@ -554,6 +555,7 @@ export class TaskController extends Controller { title: "มีการส่งงาน / Task Submitted", detail: "รหัสใบสั่งงาน / Order : " + record.code, receiverId: record.createdByUserId, + registeredBranchId: record.registeredBranchId, groupReceiver: { create: { name: "document_checker" } }, }, }); @@ -749,6 +751,7 @@ export class TaskActionController extends Controller { title: "มีการส่งงาน / Task Submitted", detail: "รหัสใบสั่งงาน / Order : " + record.code, receiverId: record.createdByUserId, + registeredBranchId: record.registeredBranchId, groupReceiver: { create: { name: "document_checker" } }, }, }), @@ -805,6 +808,7 @@ export class TaskActionController extends Controller { title: "ใบงานเสร็จสิ้น / Task Complete", detail: "รหัสใบสั่งงาน / Order : " + record.code, receiverId: record.createdByUserId, + registeredBranchId: record.registeredBranchId, groupReceiver: { create: { name: "document_checker" } }, }, }); @@ -918,6 +922,7 @@ export class TaskActionController extends Controller { include: { quotation: { select: { + registeredBranchId: true, createdByUserId: true, }, }, @@ -932,6 +937,7 @@ export class TaskActionController extends Controller { title: "สถานะใบเสนอราคาเปลี่ยนแปลง / Quotation Status Updated", detail: "รหัส / code : " + v.code + " Completed", receiverId: v.quotation.createdByUserId, + registeredBranchId: v.quotation.registeredBranchId, groupReceiver: { create: { name: "document_checker" } }, }, }), @@ -984,6 +990,7 @@ export class TaskActionController extends Controller { title: "สถานะใบเสนอราคาเปลี่ยนแปลง / Quotation Status Updated", detail: "รหัส / code : " + v.code + " Completed", receiverId: v.createdByUserId, + registeredBranchId: v.registeredBranchId, groupReceiver: { create: { name: "document_checker" } }, }, }), @@ -1232,6 +1239,7 @@ export class UserTaskController extends Controller { title: "สถานะใบส่งงานมีการเปลี่ยนแปลง / Order Status Changed", detail: "รหัสใบสั่งงาน / Order : " + v.code + " InProgress", receiverId: v.createdByUserId, + registeredBranchId: v.registeredBranchId, groupReceiver: { create: { name: "document_checker" } }, }, }); @@ -1240,6 +1248,7 @@ export class UserTaskController extends Controller { title: "มีการรับงาน / Task Accepted", detail: "รหัสใบสั่งงาน / Order : " + v.code, receiverId: v.createdByUserId, + registeredBranchId: v.registeredBranchId, groupReceiver: { create: { name: "document_checker" } }, }, }); From dfe7bd16d819c5975169d6f5d2128edbf01ad7cf Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Tue, 29 Jul 2025 10:18:33 +0700 Subject: [PATCH 107/167] fix: error trying to create relation in create many --- src/controllers/06-request-list-controller.ts | 110 +++++++++++------- 1 file changed, 65 insertions(+), 45 deletions(-) diff --git a/src/controllers/06-request-list-controller.ts b/src/controllers/06-request-list-controller.ts index 45c21f7..764fbc7 100644 --- a/src/controllers/06-request-list-controller.ts +++ b/src/controllers/06-request-list-controller.ts @@ -456,15 +456,19 @@ export class RequestDataActionController extends Controller { data: { quotationStatus: QuotationStatus.Canceled, urgent: false }, }) .then(async (res) => { - await tx.notification.createMany({ - data: res.map((v) => ({ - title: "สถานะใบเสนอราคาเปลี่ยนแปลง / Quotation Status Updated", - detail: "รหัส / code : " + v.code + " Canceled", - receiverId: v.createdByUserId, - registeredBranchId: v.registeredBranchId, - groupReceiver: { create: { name: "document_checker" } }, - })), - }); + await Promise.all( + res.map((v) => + tx.notification.create({ + data: { + title: "สถานะใบเสนอราคาเปลี่ยนแปลง / Quotation Status Updated", + detail: "รหัส / code : " + v.code + " Canceled", + receiverId: v.createdByUserId, + registeredBranchId: v.registeredBranchId, + groupReceiver: { create: { name: "document_checker" } }, + }, + }), + ), + ); }), tx.taskOrder .updateManyAndReturn({ @@ -665,15 +669,19 @@ export class RequestDataActionController extends Controller { data: { quotationStatus: QuotationStatus.Canceled, urgent: false }, }) .then(async (res) => { - await tx.notification.createMany({ - data: res.map((v) => ({ - title: "สถานะใบเสนอราคาเปลี่ยนแปลง / Quotation Status Updated", - detail: "รหัส / code : " + v.code + " Canceled", - receiverId: v.createdByUserId, - registeredBranchId: v.registeredBranchId, - groupReceiver: { create: { name: "document_checker" } }, - })), - }); + await Promise.all( + res.map((v) => + tx.notification.create({ + data: { + title: "สถานะใบเสนอราคาเปลี่ยนแปลง / Quotation Status Updated", + detail: "รหัส / code : " + v.code + " Canceled", + receiverId: v.createdByUserId, + registeredBranchId: v.registeredBranchId, + groupReceiver: { create: { name: "document_checker" } }, + }, + }), + ), + ); }), tx.taskOrder.updateMany({ where: { @@ -771,15 +779,19 @@ export class RequestDataActionController extends Controller { }, }) .then(async (res) => { - await tx.notification.createMany({ - data: res.map((v) => ({ - title: "สถานะใบเสนอราคาเปลี่ยนแปลง / Quotation Status Updated", - detail: "รหัส / code : " + v.code + " Completed", - receiverId: v.createdByUserId, - registeredBranchId: v.registeredBranchId, - groupReceiver: { create: { name: "document_checker" } }, - })), - }); + await Promise.all( + res.map((v) => + tx.notification.create({ + data: { + title: "สถานะใบเสนอราคาเปลี่ยนแปลง / Quotation Status Updated", + detail: "รหัส / code : " + v.code + " Completed", + receiverId: v.createdByUserId, + registeredBranchId: v.registeredBranchId, + groupReceiver: { create: { name: "document_checker" } }, + }, + }), + ), + ); const token = await this.#getLineToken(); if (!token) return; @@ -1185,15 +1197,19 @@ export class RequestListController extends Controller { data: { quotationStatus: QuotationStatus.Canceled, urgent: false }, }) .then(async (res) => { - await tx.notification.createMany({ - data: res.map((v) => ({ - title: "สถานะใบเสนอราคาเปลี่ยนแปลง / Quotation Status Updated", - detail: "รหัส / code : " + v.code + " Canceled", - receiverId: v.createdByUserId, - registeredBranchId: v.registeredBranchId, - groupReceiver: { create: { name: "document_checker" } }, - })), - }); + await Promise.all( + res.map((v) => + tx.notification.create({ + data: { + title: "สถานะใบเสนอราคาเปลี่ยนแปลง / Quotation Status Updated", + detail: "รหัส / code : " + v.code + " Canceled", + receiverId: v.createdByUserId, + registeredBranchId: v.registeredBranchId, + groupReceiver: { create: { name: "document_checker" } }, + }, + }), + ), + ); }), tx.taskOrder.updateMany({ where: { @@ -1301,15 +1317,19 @@ export class RequestListController extends Controller { }, }) .then(async (res) => { - await tx.notification.createMany({ - data: res.map((v) => ({ - title: "สถานะใบเสนอราคาเปลี่ยนแปลง / Quotation Status Updated", - detail: "รหัส / code : " + v.code + " Completed", - receiverId: v.createdByUserId, - registeredBranchId: v.registeredBranchId, - groupReceiver: { create: { name: "document_checker" } }, - })), - }); + await Promise.all( + res.map((v) => + tx.notification.create({ + data: { + title: "สถานะใบเสนอราคาเปลี่ยนแปลง / Quotation Status Updated", + detail: "รหัส / code : " + v.code + " Completed", + receiverId: v.createdByUserId, + registeredBranchId: v.registeredBranchId, + groupReceiver: { create: { name: "document_checker" } }, + }, + }), + ), + ); const token = await this.#getLineToken(); if (!token) return; From f1620813708e36923836918452fffed98345fe2e Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Fri, 1 Aug 2025 10:58:04 +0700 Subject: [PATCH 108/167] fix: dashboard payment now taken cancled into account --- src/controllers/00-stats-controller.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/controllers/00-stats-controller.ts b/src/controllers/00-stats-controller.ts index b4aef31..e186178 100644 --- a/src/controllers/00-stats-controller.ts +++ b/src/controllers/00-stats-controller.ts @@ -631,6 +631,7 @@ export class StatsController extends Controller { createdAt: { gte: v, lte: date.endOf("month").toDate() }, invoice: { quotation: { + NOT: { quotationStatus: QuotationStatus.Canceled }, registeredBranch: { OR: permissionCondCompany(req.user) }, }, }, From c9939bf8bb76d29856cd0d8a54e23d6b9b8b77b4 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Mon, 4 Aug 2025 09:01:56 +0700 Subject: [PATCH 109/167] fix: stats will only take non canceled status --- src/controllers/00-stats-controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/00-stats-controller.ts b/src/controllers/00-stats-controller.ts index e186178..95484a4 100644 --- a/src/controllers/00-stats-controller.ts +++ b/src/controllers/00-stats-controller.ts @@ -631,7 +631,7 @@ export class StatsController extends Controller { createdAt: { gte: v, lte: date.endOf("month").toDate() }, invoice: { quotation: { - NOT: { quotationStatus: QuotationStatus.Canceled }, + quotationStatus: { notIn: [QuotationStatus.Canceled] }, registeredBranch: { OR: permissionCondCompany(req.user) }, }, }, From 1789fe1de0160a6524f6b98ffcf0165b63b74017 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Thu, 14 Aug 2025 13:10:44 +0700 Subject: [PATCH 110/167] feat: add updated at to step status --- prisma/schema.prisma | 1 + 1 file changed, 1 insertion(+) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 9cf97fd..00bd192 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -1612,6 +1612,7 @@ model RequestWork { model RequestWorkStepStatus { step Int workStatus RequestWorkStatus @default(Pending) + updatedAt DateTime @default(now()) @updatedAt requestWork RequestWork @relation(fields: [requestWorkId], references: [id], onDelete: Cascade) requestWorkId String From c2eaa5fba817f9107cce3bee349fa69589ae9052 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Thu, 14 Aug 2025 13:10:49 +0700 Subject: [PATCH 111/167] chore: migration --- .../20250814060937_add_updated_at_to_work_step/migration.sql | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 prisma/migrations/20250814060937_add_updated_at_to_work_step/migration.sql diff --git a/prisma/migrations/20250814060937_add_updated_at_to_work_step/migration.sql b/prisma/migrations/20250814060937_add_updated_at_to_work_step/migration.sql new file mode 100644 index 0000000..d1bf6c5 --- /dev/null +++ b/prisma/migrations/20250814060937_add_updated_at_to_work_step/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "RequestWorkStepStatus" ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP; From df38eebbcccb1839efad45150ec66eb41abdb9a9 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Fri, 22 Aug 2025 09:33:49 +0700 Subject: [PATCH 112/167] fix: permission --- src/controllers/03-customer-branch-controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/03-customer-branch-controller.ts b/src/controllers/03-customer-branch-controller.ts index aada50c..eda1546 100644 --- a/src/controllers/03-customer-branch-controller.ts +++ b/src/controllers/03-customer-branch-controller.ts @@ -623,7 +623,7 @@ export class CustomerBranchFileController extends Controller { }, }); if (!data) throw notFoundError("Customer Branch"); - await permissionCheck(user, data.customer.registeredBranch); + await permissionCheckCompany(user, data.customer.registeredBranch); } @Get("attachment") From 2c9fae400caab11738532aa823dbc3bee342df25 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Fri, 22 Aug 2025 09:41:43 +0700 Subject: [PATCH 113/167] fix: permission employee --- src/controllers/03-employee-controller.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/controllers/03-employee-controller.ts b/src/controllers/03-employee-controller.ts index 99d69ef..411b817 100644 --- a/src/controllers/03-employee-controller.ts +++ b/src/controllers/03-employee-controller.ts @@ -65,7 +65,9 @@ function globalAllow(user: RequestWithUser["user"]) { return user.roles?.some((v) => listAllowed.includes(v)) || false; } +const permissionCondCompany = createPermCondition((_) => true); const permissionCond = createPermCondition(globalAllow); +const permissionCheckCompany = createPermCheck((_) => true); const permissionCheck = createPermCheck(globalAllow); type EmployeeCreate = { @@ -669,7 +671,7 @@ export class EmployeeFileController extends Controller { }, }); if (!data) throw notFoundError("Employee"); - await permissionCheck(user, data.customerBranch.customer.registeredBranch); + await permissionCheckCompany(user, data.customerBranch.customer.registeredBranch); } @Get("image") From d95eb349ec62f6c5dc05e94dac478213af790206 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Fri, 22 Aug 2025 14:23:40 +0700 Subject: [PATCH 114/167] fix: update messenger also update work step messenger --- src/controllers/06-request-list-controller.ts | 50 +++++++++++++------ 1 file changed, 35 insertions(+), 15 deletions(-) diff --git a/src/controllers/06-request-list-controller.ts b/src/controllers/06-request-list-controller.ts index 764fbc7..a453716 100644 --- a/src/controllers/06-request-list-controller.ts +++ b/src/controllers/06-request-list-controller.ts @@ -293,28 +293,48 @@ export class RequestDataController extends Controller { async updateRequestData( @Request() req: RequestWithUser, @Body() - boby: { + body: { defaultMessengerId: string; requestDataId: string[]; }, ) { - const record = await prisma.requestData.updateManyAndReturn({ - where: { - id: { in: boby.requestDataId }, - quotation: { - registeredBranch: { - OR: permissionCond(req.user), + if (body.requestDataId.length === 0) return; + + return await prisma.$transaction(async (tx) => { + const record = await tx.requestData.updateManyAndReturn({ + where: { + id: { in: body.requestDataId }, + quotation: { + registeredBranch: { + OR: permissionCond(req.user), + }, }, }, - }, - data: { - defaultMessengerId: boby.defaultMessengerId, - }, + data: { + defaultMessengerId: body.defaultMessengerId, + }, + }); + + if (record.length <= 0) throw notFoundError("Request Data"); + + await tx.requestWorkStepStatus.updateMany({ + where: { + workStatus: { + in: [ + RequestWorkStatus.Pending, + RequestWorkStatus.Waiting, + RequestWorkStatus.InProgress, + ], + }, + requestWork: { + requestDataId: { in: body.requestDataId }, + }, + }, + data: { responsibleUserId: body.defaultMessengerId }, + }); + + return record[0]; }); - - if (record.length <= 0) throw notFoundError("Request Data"); - - return record[0]; } } From ab8fd2ca4337cbbeaf0736b4eb8cdd8da4806c47 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Thu, 28 Aug 2025 14:38:49 +0700 Subject: [PATCH 115/167] feat: export customer and employee as csv --- src/controllers/03-customer-controller.ts | 42 ++++++++++++++++++ src/controllers/03-employee-controller.ts | 54 ++++++++++++++++++++++- 2 files changed, 95 insertions(+), 1 deletion(-) diff --git a/src/controllers/03-customer-controller.ts b/src/controllers/03-customer-controller.ts index 2f31d79..7e16f2d 100644 --- a/src/controllers/03-customer-controller.ts +++ b/src/controllers/03-customer-controller.ts @@ -37,6 +37,7 @@ import { } from "../utils/minio"; import { isUsedError, notFoundError, relationError } from "../utils/error"; import { connectOrNot, queryOrNot, whereDateQuery } from "../utils/relation"; +import { json2csv } from "json-2-csv"; const MANAGE_ROLES = [ "system", @@ -547,3 +548,44 @@ export class CustomerImageController extends Controller { await deleteFile(fileLocation.customer.img(customerId, name)); } } + +@Route("api/v1/customer-export") +@Tags("Customer") +export class CustomerExportController extends CustomerController { + @Get() + @Security("keycloak") + async exportCustomer( + @Request() req: RequestWithUser, + @Query() customerType?: CustomerType, + @Query() query: string = "", + @Query() status?: Status, + @Query() page: number = 1, + @Query() pageSize: number = 30, + @Query() includeBranch: boolean = false, + @Query() company: boolean = false, + @Query() activeBranchOnly?: boolean, + @Query() startDate?: Date, + @Query() endDate?: Date, + ) { + const ret = await this.list( + req, + customerType, + query, + status, + page, + pageSize, + includeBranch, + company, + activeBranchOnly, + startDate, + endDate, + ); + + this.setHeader("Content-Type", "text/csv"); + + return json2csv( + ret.result.map((v) => Object.assign(v, { branch: v.branch.at(0) ?? null })), + { useDateIso8601Format: true, expandNestedObjects: true }, + ); + } +} diff --git a/src/controllers/03-employee-controller.ts b/src/controllers/03-employee-controller.ts index 411b817..303ba15 100644 --- a/src/controllers/03-employee-controller.ts +++ b/src/controllers/03-employee-controller.ts @@ -42,6 +42,7 @@ import { listFile, setFile, } from "../utils/minio"; +import { json2csv } from "json-2-csv"; if (!process.env.MINIO_BUCKET) { throw Error("Require MinIO bucket."); @@ -249,7 +250,6 @@ export class EmployeeController extends Controller { endDate, ); } - @Post("list") @Security("keycloak") async listByCriteria( @@ -927,3 +927,55 @@ export class EmployeeFileController extends Controller { return await deleteFile(fileLocation.employee.inCountryNotice(employeeId, noticeId)); } } + +@Route("api/v1/employee-export") +@Tags("Employee") +export class EmployeeExportController extends EmployeeController { + @Get() + @Security("keycloak") + async exportEmployee( + @Request() req: RequestWithUser, + @Query() zipCode?: string, + @Query() gender?: string, + @Query() status?: Status, + @Query() visa?: boolean, + @Query() passport?: boolean, + @Query() customerId?: string, + @Query() customerBranchId?: string, + @Query() query: string = "", + @Query() page: number = 1, + @Query() pageSize: number = 30, + @Query() activeOnly?: boolean, + @Query() startDate?: Date, + @Query() endDate?: Date, + ) { + const ret = await this.listByCriteria( + req, + zipCode, + gender, + status, + visa, + passport, + customerId, + customerBranchId, + query, + page, + pageSize, + activeOnly, + startDate, + endDate, + ); + + this.setHeader("Content-Type", "text/csv"); + + return json2csv( + ret.result.map((v) => + Object.assign(v, { + employeePassport: v.employeePassport?.at(0) ?? null, + employeeVisa: v.employeeVisa?.at(0) ?? null, + }), + ), + { useDateIso8601Format: true }, + ); + } +} From f487a9169ca147695bdbbea312e633d90a17a9f4 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Thu, 28 Aug 2025 15:01:04 +0700 Subject: [PATCH 116/167] fix: prevent line user id and otp exposes --- src/controllers/00-doc-template-controller.ts | 5 +++ .../03-customer-branch-controller.ts | 15 +++++++++ src/controllers/03-customer-controller.ts | 33 ++++++++++++++++++- 3 files changed, 52 insertions(+), 1 deletion(-) diff --git a/src/controllers/00-doc-template-controller.ts b/src/controllers/00-doc-template-controller.ts index da5dc9d..09dab18 100644 --- a/src/controllers/00-doc-template-controller.ts +++ b/src/controllers/00-doc-template-controller.ts @@ -34,6 +34,11 @@ const quotationData = (id: string) => }, }, customerBranch: { + omit: { + otpCode: true, + otpExpires: true, + userId: true, + }, include: { customer: true, businessType: true, diff --git a/src/controllers/03-customer-branch-controller.ts b/src/controllers/03-customer-branch-controller.ts index eda1546..a4d87e4 100644 --- a/src/controllers/03-customer-branch-controller.ts +++ b/src/controllers/03-customer-branch-controller.ts @@ -238,6 +238,11 @@ export class CustomerBranchController extends Controller { const [result, total] = await prisma.$transaction([ prisma.customerBranch.findMany({ orderBy: [{ code: "asc" }, { statusOrder: "asc" }, { createdAt: "asc" }], + omit: { + otpCode: true, + otpExpires: true, + userId: true, + }, include: { customer: includeCustomer, province: true, @@ -262,6 +267,11 @@ export class CustomerBranchController extends Controller { @Security("keycloak") async getById(@Path() branchId: string) { const record = await prisma.customerBranch.findFirst({ + omit: { + otpCode: true, + otpExpires: true, + userId: true, + }, include: { customer: true, province: true, @@ -352,6 +362,11 @@ export class CustomerBranchController extends Controller { include: branchRelationPermInclude(req.user), }, branch: { + omit: { + otpCode: true, + otpExpires: true, + userId: true, + }, take: 1, orderBy: { createdAt: "asc" }, }, diff --git a/src/controllers/03-customer-controller.ts b/src/controllers/03-customer-controller.ts index 7e16f2d..1854842 100644 --- a/src/controllers/03-customer-controller.ts +++ b/src/controllers/03-customer-controller.ts @@ -207,6 +207,11 @@ export class CustomerController extends Controller { district: true, subDistrict: true, }, + omit: { + otpCode: true, + otpExpires: true, + userId: true, + }, orderBy: [{ statusOrder: "asc" }, { createdAt: "asc" }], } : { @@ -215,6 +220,11 @@ export class CustomerController extends Controller { district: true, subDistrict: true, }, + omit: { + otpCode: true, + otpExpires: true, + userId: true, + }, take: 1, orderBy: { createdAt: "asc" }, }, @@ -245,6 +255,11 @@ export class CustomerController extends Controller { district: true, subDistrict: true, }, + omit: { + otpCode: true, + otpExpires: true, + userId: true, + }, orderBy: { createdAt: "asc" }, }, createdBy: true, @@ -316,6 +331,11 @@ export class CustomerController extends Controller { district: true, subDistrict: true, }, + omit: { + otpCode: true, + otpExpires: true, + userId: true, + }, }, createdBy: true, updatedBy: true, @@ -415,6 +435,11 @@ export class CustomerController extends Controller { district: true, subDistrict: true, }, + omit: { + otpCode: true, + otpExpires: true, + userId: true, + }, }, createdBy: true, updatedBy: true, @@ -453,7 +478,13 @@ export class CustomerController extends Controller { await deleteFolder(`customer/${customerId}`); const data = await tx.customer.delete({ include: { - branch: true, + branch: { + omit: { + otpCode: true, + otpExpires: true, + userId: true, + }, + }, registeredBranch: { include: { headOffice: true, From 4042cbcea4b5b92a5256a9af48640b3197f2d467 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Mon, 1 Sep 2025 09:27:53 +0700 Subject: [PATCH 117/167] chore: add html to text dep --- package.json | 2 + pnpm-lock.yaml | 109 +++++++++++++++++++++++++++++++++++ src/utils/string-template.ts | 67 +++++++++++++++++++++ 3 files changed, 178 insertions(+) create mode 100644 src/utils/string-template.ts diff --git a/package.json b/package.json index 2f64c4f..13cd0e0 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "@prisma/client": "^6.3.0", "@scalar/express-api-reference": "^0.4.182", "@tsoa/runtime": "^6.6.0", + "@types/html-to-text": "^9.0.4", "canvas": "^3.1.0", "cors": "^2.8.5", "cron": "^3.3.1", @@ -53,6 +54,7 @@ "exceljs": "^4.4.0", "express": "^4.21.2", "fast-jwt": "^5.0.5", + "html-to-text": "^9.0.5", "jsbarcode": "^3.11.6", "json-2-csv": "^5.5.8", "kysely": "^0.27.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8471a8c..8d3df75 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,6 +23,9 @@ importers: '@tsoa/runtime': specifier: ^6.6.0 version: 6.6.0 + '@types/html-to-text': + specifier: ^9.0.4 + version: 9.0.4 canvas: specifier: ^3.1.0 version: 3.1.0 @@ -53,6 +56,9 @@ importers: fast-jwt: specifier: ^5.0.5 version: 5.0.5 + html-to-text: + specifier: ^9.0.5 + version: 9.0.5 jsbarcode: specifier: ^3.11.6 version: 3.11.6 @@ -676,6 +682,9 @@ packages: resolution: {integrity: sha512-4mQYkQJO0HHaoFd8Z+vSdQAvYcCJ2bRLN9ewE+GneB8kvoLG/oM3ynroqzGQdoytH8BmhnJwD3aEUagfbK2x5g==} engines: {node: '>=18'} + '@selderee/plugin-htmlparser2@0.11.0': + resolution: {integrity: sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==} + '@swc/helpers@0.5.15': resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} @@ -742,6 +751,9 @@ packages: '@types/express@4.17.21': resolution: {integrity: sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==} + '@types/html-to-text@9.0.4': + resolution: {integrity: sha512-pUY3cKH/Nm2yYrEmDlPR1mR7yszjGx4DrwPjQ702C4/D5CwHuZTgZdIdwPkRbcuhs7BAh2L5rg3CL5cbRiGTCQ==} + '@types/http-assert@1.5.6': resolution: {integrity: sha512-TTEwmtjgVbYAzZYWyeHPrrtWnfVkm8tQkP8P21uQifPgMRgjrow3XDEYqucuC8SKZJT7pUnhU/JymvjggxO9vw==} @@ -1307,6 +1319,10 @@ packages: resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} engines: {node: '>=4.0.0'} + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + define-data-property@1.1.4: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} @@ -1347,6 +1363,19 @@ packages: resolution: {integrity: sha512-tTmR3WhROYctuyVReQ+PfCU3zprmC45/VuSVzn8EjovzpRkXYUdXiDatB9M8pasj0V+wuuOyY8bcSHvlQ2GNag==} engines: {node: '>=6'} + dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + + domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + + domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + + domutils@3.2.2: + resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} + dotenv@16.0.3: resolution: {integrity: sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==} engines: {node: '>=12'} @@ -1398,6 +1427,10 @@ packages: end-of-stream@1.4.4: resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + env-paths@2.2.1: resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} engines: {node: '>=6'} @@ -1740,6 +1773,13 @@ packages: resolution: {integrity: sha512-A91dYTeIB6NoXG+PxTQpCCDDnfHsW9kc06Lvpu1TEe9gnd6ZFeiBoRO9JvzEv6xK7EX97/dUE8g/vBMTqTS3CA==} engines: {node: '>=14'} + html-to-text@9.0.5: + resolution: {integrity: sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg==} + engines: {node: '>=14'} + + htmlparser2@8.0.2: + resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==} + http-errors@2.0.0: resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} engines: {node: '>= 0.8'} @@ -2040,6 +2080,9 @@ packages: resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==} engines: {node: '>= 0.6.3'} + leac@0.6.0: + resolution: {integrity: sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==} + lie@3.3.0: resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} @@ -2467,6 +2510,9 @@ packages: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} + parseley@0.12.1: + resolution: {integrity: sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==} + parseurl@1.3.3: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} @@ -2504,6 +2550,9 @@ packages: resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} engines: {node: '>= 14.16'} + peberminta@0.9.0: + resolution: {integrity: sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -2746,6 +2795,9 @@ packages: secure-json-parse@2.7.0: resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==} + selderee@0.11.0: + resolution: {integrity: sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==} + semver@5.7.2: resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} hasBin: true @@ -4037,6 +4089,11 @@ snapshots: '@scalar/openapi-types': 0.1.7 '@unhead/schema': 1.11.14 + '@selderee/plugin-htmlparser2@0.11.0': + dependencies: + domhandler: 5.0.3 + selderee: 0.11.0 + '@swc/helpers@0.5.15': dependencies: tslib: 2.8.1 @@ -4136,6 +4193,8 @@ snapshots: '@types/qs': 6.9.17 '@types/serve-static': 1.15.7 + '@types/html-to-text@9.0.4': {} + '@types/http-assert@1.5.6': {} '@types/http-errors@2.0.4': {} @@ -4781,6 +4840,8 @@ snapshots: deep-extend@0.6.0: {} + deepmerge@4.3.1: {} + define-data-property@1.1.4: dependencies: es-define-property: 1.0.1 @@ -4823,6 +4884,24 @@ snapshots: jszip: 3.10.1 sax: 1.3.0 + dom-serializer@2.0.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.5.0 + + domelementtype@2.3.0: {} + + domhandler@5.0.3: + dependencies: + domelementtype: 2.3.0 + + domutils@3.2.2: + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + dotenv@16.0.3: {} dotenv@16.4.7: {} @@ -4908,6 +4987,8 @@ snapshots: dependencies: once: 1.4.0 + entities@4.5.0: {} + env-paths@2.2.1: {} error-callsites@2.0.4: @@ -5399,6 +5480,21 @@ snapshots: hpagent@1.2.0: {} + html-to-text@9.0.5: + dependencies: + '@selderee/plugin-htmlparser2': 0.11.0 + deepmerge: 4.3.1 + dom-serializer: 2.0.0 + htmlparser2: 8.0.2 + selderee: 0.11.0 + + htmlparser2@8.0.2: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.2.2 + entities: 4.5.0 + http-errors@2.0.0: dependencies: depd: 2.0.0 @@ -5689,6 +5785,8 @@ snapshots: dependencies: readable-stream: 2.3.8 + leac@0.6.0: {} + lie@3.3.0: dependencies: immediate: 3.0.6 @@ -6096,6 +6194,11 @@ snapshots: json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 + parseley@0.12.1: + dependencies: + leac: 0.6.0 + peberminta: 0.9.0 + parseurl@1.3.3: {} path-exists@4.0.0: {} @@ -6119,6 +6222,8 @@ snapshots: pathval@2.0.0: {} + peberminta@0.9.0: {} + picocolors@1.1.1: {} picomatch@2.3.1: {} @@ -6428,6 +6533,10 @@ snapshots: secure-json-parse@2.7.0: {} + selderee@0.11.0: + dependencies: + parseley: 0.12.1 + semver@5.7.2: {} semver@6.3.1: {} diff --git a/src/utils/string-template.ts b/src/utils/string-template.ts new file mode 100644 index 0000000..aaa477e --- /dev/null +++ b/src/utils/string-template.ts @@ -0,0 +1,67 @@ +export function formatNumberDecimal(num: number, point: number = 2): string { + return (num || 0).toLocaleString("eng", { + minimumFractionDigits: point, + maximumFractionDigits: point, + }); +} + +const templates = { + "quotation-labor": { + converter: (context?: { name: string[] }) => { + return context?.name.join("
") || ""; + }, + }, + "quotation-payment": { + converter: (context?: { + paymentType: "Full" | "Split" | "SplitCustom" | "BillFull" | "BillSplit" | "BillSplitCustom"; + + amount?: number; + installments?: { + no: number; + amount: number; + }[]; + }) => { + if (context?.paymentType === "Full") { + return [ + "**** เงื่อนไขเพิ่มเติม", + "- เงื่อนไขการชำระเงิน แบบเต็มจำนวน", + `  จำนวน ${formatNumberDecimal(context?.amount || 0, 2)}`, + ].join("
"); + } else { + return [ + "**** เงื่อนไขเพิ่มเติม", + `- เงื่อนไขการชำระเงิน แบบแบ่งจ่าย${context?.paymentType === "SplitCustom" ? " กำหนดเอง " : " "}${context?.installments?.length} งวด`, + ...(context?.installments?.map( + (v) => `  งวดที่ ${v.no} จำนวน ${formatNumberDecimal(v.amount, 2)}`, + ) || []), + ].join("
"); + } + }, + }, +} as const; + +type Template = typeof templates; +type TemplateName = keyof Template; +type TemplateContext = { + [key in TemplateName]?: Parameters[0]; +}; + +export function convertTemplate( + text: string, + context?: TemplateContext, + templateUse?: TemplateName[], +) { + let ret = text; + + for (const [name, template] of Object.entries(templates)) { + if (templateUse && !templateUse.includes(name as TemplateName)) continue; + ret = ret.replace( + new RegExp("\\#\\[" + name.replaceAll("-", "\\-") + "\\]", "g"), + typeof template.converter === "function" + ? template.converter(context?.[name as TemplateName] as any) + : template.converter, + ); + } + + return ret; +} From 710382d5441eb6c36f543d9d1174a6109b73e8c4 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Thu, 11 Sep 2025 09:17:23 +0700 Subject: [PATCH 118/167] feat: add more metadata for payment --- prisma/schema.prisma | 7 +++++-- src/controllers/05-payment-controller.ts | 10 +++++++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 00bd192..78609d6 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -1524,8 +1524,11 @@ model Payment { paymentStatus PaymentStatus - amount Float - date DateTime? + amount Float + date DateTime? + channel String? + account String? + reference String? createdAt DateTime @default(now()) createdBy User? @relation(name: "PaymentCreatedByUser", fields: [createdByUserId], references: [id], onDelete: SetNull) diff --git a/src/controllers/05-payment-controller.ts b/src/controllers/05-payment-controller.ts index 5f530d0..c61d4bc 100644 --- a/src/controllers/05-payment-controller.ts +++ b/src/controllers/05-payment-controller.ts @@ -113,7 +113,15 @@ export class QuotationPayment extends Controller { @Security("keycloak", MANAGE_ROLES.concat(["head_of_sale", "sale"])) async updatePayment( @Path() paymentId: string, - @Body() body: { amount?: number; date?: Date; paymentStatus?: PaymentStatus }, + @Body() + body: { + amount?: number; + date?: Date; + paymentStatus?: PaymentStatus; + channel?: string | null; + account?: string | null; + reference?: string | null; + }, @Request() req: RequestWithUser, ) { const record = await prisma.payment.findUnique({ From c47ffb5435a9c6f0a5d8672b2211adb426eed7b2 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Thu, 11 Sep 2025 09:17:52 +0700 Subject: [PATCH 119/167] chore: migration --- .../20250911021745_add_more_payment_metadata/migration.sql | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 prisma/migrations/20250911021745_add_more_payment_metadata/migration.sql diff --git a/prisma/migrations/20250911021745_add_more_payment_metadata/migration.sql b/prisma/migrations/20250911021745_add_more_payment_metadata/migration.sql new file mode 100644 index 0000000..27036c0 --- /dev/null +++ b/prisma/migrations/20250911021745_add_more_payment_metadata/migration.sql @@ -0,0 +1,4 @@ +-- AlterTable +ALTER TABLE "Payment" ADD COLUMN "account" TEXT, +ADD COLUMN "channel" TEXT, +ADD COLUMN "reference" TEXT; From d3c5b49649004f4a6a8da3464f6cb4f1a12e0064 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Thu, 11 Sep 2025 09:32:38 +0700 Subject: [PATCH 120/167] chore: update prisma --- package.json | 4 +- pnpm-lock.yaml | 298 ++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 250 insertions(+), 52 deletions(-) diff --git a/package.json b/package.json index 13cd0e0..7fc3a6c 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "@vitest/ui": "^3.1.4", "nodemon": "^3.1.9", "prettier": "^3.4.2", - "prisma": "^6.3.0", + "prisma": "^6.16.0", "prisma-kysely": "^1.8.0", "ts-node": "^10.9.2", "typescript": "^5.7.2", @@ -40,7 +40,7 @@ "dependencies": { "@elastic/elasticsearch": "^8.17.0", "@fast-csv/parse": "^5.0.2", - "@prisma/client": "^6.3.0", + "@prisma/client": "^6.16.0", "@scalar/express-api-reference": "^0.4.182", "@tsoa/runtime": "^6.6.0", "@types/html-to-text": "^9.0.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8d3df75..8bbe67b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,8 +15,8 @@ importers: specifier: ^5.0.2 version: 5.0.2 '@prisma/client': - specifier: ^6.3.0 - version: 6.3.0(prisma@6.3.0(typescript@5.7.2))(typescript@5.7.2) + specifier: ^6.16.0 + version: 6.16.0(prisma@6.16.0(typescript@5.7.2))(typescript@5.7.2) '@scalar/express-api-reference': specifier: ^0.4.182 version: 0.4.182 @@ -82,7 +82,7 @@ importers: version: 6.10.0 prisma-extension-kysely: specifier: ^3.0.0 - version: 3.0.0(@prisma/client@6.3.0(prisma@6.3.0(typescript@5.7.2))(typescript@5.7.2)) + version: 3.0.0(@prisma/client@6.16.0(prisma@6.16.0(typescript@5.7.2))(typescript@5.7.2)) promise.any: specifier: ^2.0.6 version: 2.0.6 @@ -133,8 +133,8 @@ importers: specifier: ^3.4.2 version: 3.4.2 prisma: - specifier: ^6.3.0 - version: 6.3.0(typescript@5.7.2) + specifier: ^6.16.0 + version: 6.16.0(typescript@5.7.2) prisma-kysely: specifier: ^1.8.0 version: 1.8.0(encoding@0.1.13) @@ -146,7 +146,7 @@ importers: version: 5.7.2 vitest: specifier: ^3.1.4 - version: 3.1.4(@types/node@20.17.10)(@vitest/ui@3.1.4)(yaml@2.6.1) + version: 3.1.4(@types/node@20.17.10)(@vitest/ui@3.1.4)(jiti@2.5.1)(yaml@2.6.1) packages: @@ -522,8 +522,8 @@ packages: '@polka/url@1.0.0-next.29': resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} - '@prisma/client@6.3.0': - resolution: {integrity: sha512-BY3Fi28PUSk447Bpv22LhZp4HgNPo7NsEN+EteM1CLDnLjig5863jpW+3c3HHLFmml+nB/eJv1CjSriFZ8z7Cg==} + '@prisma/client@6.16.0': + resolution: {integrity: sha512-FYkFJtgwpwJRMxtmrB26y7gtpR372kyChw6lWng5TMmvn5V+uisy0OyllO5EJD1s8lX78V8X3XjhiXOoMLnu3w==} engines: {node: '>=18.18'} peerDependencies: prisma: '*' @@ -534,26 +534,29 @@ packages: typescript: optional: true + '@prisma/config@6.16.0': + resolution: {integrity: sha512-Q9TgfnllVehvQziY9lJwRJLGmziX0OimZUEQ/MhCUBoJMSScj2VivCjw/Of2vlO1FfyaHXxrvjZAr7ASl7DVcw==} + '@prisma/debug@5.3.1': resolution: {integrity: sha512-eYrxqslEKf+wpMFIIHgbcNYuZBXUdiJLA85Or3TwOhgPIN1ZoXT9CwJph3ynW8H1Xg0LkdYLwVmuULCwiMoU5A==} - '@prisma/debug@6.3.0': - resolution: {integrity: sha512-m1lQv//0Rc5RG8TBpNUuLCxC35Ghi5XfpPmL83Gh04/GICHD2J5H2ndMlaljrUNaQDF9dOxIuFAYP1rE9wkXkg==} + '@prisma/debug@6.16.0': + resolution: {integrity: sha512-bxzro5vbVqAPkWyDs2A6GpQtRZunD8tyrLmSAchx9u0b+gWCDY6eV+oh5A0YtYT9245dIxQBswckayHuJG4u3w==} - '@prisma/engines-version@6.3.0-17.acc0b9dd43eb689cbd20c9470515d719db10d0b0': - resolution: {integrity: sha512-R/ZcMuaWZT2UBmgX3Ko6PAV3f8//ZzsjRIG1eKqp3f2rqEqVtCv+mtzuH2rBPUC9ujJ5kCb9wwpxeyCkLcHVyA==} + '@prisma/engines-version@6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43': + resolution: {integrity: sha512-ThvlDaKIVrnrv97ujNFDYiQbeMQpLa0O86HFA2mNoip4mtFqM7U5GSz2ie1i2xByZtvPztJlNRgPsXGeM/kqAA==} '@prisma/engines@5.3.1': resolution: {integrity: sha512-6QkILNyfeeN67BNEPEtkgh3Xo2tm6D7V+UhrkBbRHqKw9CTaz/vvTP/ROwYSP/3JT2MtIutZm/EnhxUiuOPVDA==} - '@prisma/engines@6.3.0': - resolution: {integrity: sha512-RXqYhlZb9sx/xkUfYIZuEPn7sT0WgTxNOuEYQ7AGw3IMpP9QGVEDVsluc/GcNkM8NTJszeqk8AplJzI9lm7Jxw==} + '@prisma/engines@6.16.0': + resolution: {integrity: sha512-RHJGCH/zi017W4CWYWqg0Sv1pquGGFVo8T3auJ9sodDNaiRzbeNldydjaQzszVS8nscdtcvLuJzy7e65C3puqQ==} '@prisma/fetch-engine@5.3.1': resolution: {integrity: sha512-w1yk1YiK8N82Pobdq58b85l6e8akyrkxuzwV9DoiUTRf3gpsuhJJesHc4Yi0WzUC9/3znizl1UfCsI6dhkj3Vw==} - '@prisma/fetch-engine@6.3.0': - resolution: {integrity: sha512-GBy0iT4f1mH31ePzfcpVSUa7JLRTeq4914FG2vR3LqDwRweSm4ja1o5flGDz+eVIa/BNYfkBvRRxv4D6ve6Eew==} + '@prisma/fetch-engine@6.16.0': + resolution: {integrity: sha512-Mx5rml0XRIDizhB9eZxSP8c0nMoXYVITTiJJwxlWn9rNCel8mG8NAqIw+vJlN3gPR+kt3IBkP1SQVsplPPpYrA==} '@prisma/generator-helper@5.3.1': resolution: {integrity: sha512-zrYS0iHLgPlOJjYnd5KvVMMvSS+ktOL39EwooS5EnyvfzwfzxlKCeOUgxTfiKYs0WUWqzEvyNAYtramYgSknsQ==} @@ -561,8 +564,8 @@ packages: '@prisma/get-platform@5.3.1': resolution: {integrity: sha512-3IiZY2BUjKnAuZ0569zppZE6/rZbVAM09//c2nvPbbkGG9MqrirA8fbhhF7tfVmhyVfdmVCHnf/ujWPHJ8B46Q==} - '@prisma/get-platform@6.3.0': - resolution: {integrity: sha512-V8zZ1d0xfyi6FjpNP4AcYuwSpGcdmu35OXWnTPm8IW594PYALzKXHwIa9+o0f+Lo9AecFWrwrwaoYe56UNfTtQ==} + '@prisma/get-platform@6.16.0': + resolution: {integrity: sha512-eaJOOvAoGslSUTjiQrtE9E0hoBdfL43j8SymOGD6LbdrKRNtIoiy6qiBaEr2fNYD+R/Qns7QOwPhl7SVHJayKA==} '@prisma/internals@5.3.1': resolution: {integrity: sha512-zkW73hPHHNrMD21PeYgCTBfMu71vzJf+WtfydtJbS0JVJKyLfOel0iWSQg7wjNeQfccKp+NdHJ/5rTJ4NEUzgA==} @@ -685,6 +688,9 @@ packages: '@selderee/plugin-htmlparser2@0.11.0': resolution: {integrity: sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==} + '@standard-schema/spec@1.0.0': + resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} + '@swc/helpers@0.5.15': resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} @@ -1084,6 +1090,14 @@ packages: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} + c12@3.1.0: + resolution: {integrity: sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==} + peerDependencies: + magicast: ^0.3.5 + peerDependenciesMeta: + magicast: + optional: true + cac@6.7.14: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} @@ -1133,6 +1147,10 @@ packages: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + chownr@1.1.4: resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} @@ -1140,6 +1158,9 @@ packages: resolution: {integrity: sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==} engines: {node: '>=8'} + citty@0.1.6: + resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} + cjs-module-lexer@1.4.1: resolution: {integrity: sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA==} @@ -1199,6 +1220,13 @@ packages: resolution: {integrity: sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==} engines: {'0': node >= 0.8} + confbox@0.2.2: + resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==} + + consola@3.4.2: + resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} + engines: {node: ^14.18.0 || >=16.10.0} + console-log-level@1.4.1: resolution: {integrity: sha512-VZzbIORbP+PPcN/gg3DXClTLPLg5Slwd5fL2MIc+o1qZ4BXBvWyc6QxPk6T/Mkr6IVjRpoAGf32XxP3ZWMVRcQ==} @@ -1319,6 +1347,10 @@ packages: resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} engines: {node: '>=4.0.0'} + deepmerge-ts@7.1.5: + resolution: {integrity: sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==} + engines: {node: '>=16.0.0'} + deepmerge@4.3.1: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} @@ -1331,6 +1363,9 @@ packages: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} + defu@6.1.4: + resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + del@6.1.1: resolution: {integrity: sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg==} engines: {node: '>=10'} @@ -1339,6 +1374,9 @@ packages: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} + destr@2.0.5: + resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} + destroy@1.2.0: resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} @@ -1384,6 +1422,10 @@ packages: resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==} engines: {node: '>=12'} + dotenv@16.6.1: + resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} + engines: {node: '>=12'} + dunder-proto@1.0.0: resolution: {integrity: sha512-9+Sj30DIu+4KvHqMfLUGLFYL2PkURSYMVXJyXe92nFRvlYq5hBjLEhblKB+vkd/WVlUYMWigiY07T91Fkk0+4A==} engines: {node: '>= 0.4'} @@ -1400,6 +1442,9 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + effect@3.16.12: + resolution: {integrity: sha512-N39iBk0K71F9nb442TLbTkjl24FLUzuvx2i1I2RsEAQsdAdUTuUoW0vlfUXgkMTUOnYqKnWcFfqw4hK4Pw27hg==} + elastic-apm-node@3.52.2: resolution: {integrity: sha512-NVFthDcoBOpTwtppF7b+BIeIu4Xon3RBNpddIaJv+DtjL6Q61x4j7ClYdiXjv3XKgyp7yUlOnLjU6PY/EYXwLQ==} engines: {node: '>=8.6.0'} @@ -1410,6 +1455,10 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + empathic@2.0.0: + resolution: {integrity: sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==} + engines: {node: '>=14'} + enabled@2.0.0: resolution: {integrity: sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==} @@ -1528,6 +1577,13 @@ packages: resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==} engines: {node: '>= 0.10.0'} + exsolve@1.0.7: + resolution: {integrity: sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==} + + fast-check@3.23.2: + resolution: {integrity: sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==} + engines: {node: '>=8.0.0'} + fast-csv@4.3.6: resolution: {integrity: sha512-2RNSpuwwsJGP0frGsOmTb9oUF+VkFSM4SyLTDgwf2ciHWTarN0lQTC+F2f/t5J9QjW+c65VFIAAu85GsvMIusw==} engines: {node: '>=10.0.0'} @@ -1685,6 +1741,10 @@ packages: resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==} engines: {node: '>= 0.4'} + giget@2.0.0: + resolution: {integrity: sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==} + hasBin: true + github-from-package@0.0.0: resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} @@ -2038,6 +2098,10 @@ packages: jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + jiti@2.5.1: + resolution: {integrity: sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==} + hasBin: true + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -2352,6 +2416,9 @@ packages: node-addon-api@7.1.1: resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} + node-fetch-native@1.6.7: + resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==} + node-fetch@2.6.12: resolution: {integrity: sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==} engines: {node: 4.x || >=6.0.0} @@ -2403,6 +2470,11 @@ packages: resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} engines: {node: '>=8'} + nypm@0.6.1: + resolution: {integrity: sha512-hlacBiRiv1k9hZFiphPUkfSQ/ZfQzZDzC+8z0wL3lvDAOUu/2NnChkKuMoMjNur/9OpKuz2QsIeiPVN0xM5Q0w==} + engines: {node: ^14.16.0 || >=16.10.0} + hasBin: true + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -2432,6 +2504,9 @@ packages: obliterator@2.0.4: resolution: {integrity: sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ==} + ohash@2.0.11: + resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} + on-finished@2.3.0: resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==} engines: {node: '>= 0.8'} @@ -2553,6 +2628,9 @@ packages: peberminta@0.9.0: resolution: {integrity: sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==} + perfect-debounce@1.0.0: + resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -2575,6 +2653,9 @@ packages: resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} engines: {node: '>=8'} + pkg-types@2.3.0: + resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} + possible-typed-array-names@1.0.0: resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} engines: {node: '>= 0.4'} @@ -2602,8 +2683,8 @@ packages: resolution: {integrity: sha512-VpNpolZ8RXRgfU+j4R+fPZmX8EE95w3vJ2tt7+FwuiQc0leNTfLK5QLf3KbbPDes2rfjh3g20AjDxefQIo5GIA==} hasBin: true - prisma@6.3.0: - resolution: {integrity: sha512-y+Zh3Qg+xGCWyyrNUUNaFW/OltaV/yXYuTa0WRgYkz5LGyifmAsgpv94I47+qGRocZrMGcbF2A/78/oO2zgifA==} + prisma@6.16.0: + resolution: {integrity: sha512-TTh+H1Kw8N68KN9cDzdAyMroqMOvdCO/Z+kS2wKEVYR1nuR21qH5Q/Db/bZHsAgw7l/TPHtM/veG5VABcdwPDw==} engines: {node: '>=18.18'} hasBin: true peerDependencies: @@ -2650,6 +2731,9 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + pure-rand@6.1.0: + resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} + qs@6.13.0: resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} engines: {node: '>=0.6'} @@ -2672,6 +2756,9 @@ packages: resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} engines: {node: '>= 0.8'} + rc9@2.1.2: + resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==} + rc@1.2.8: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true @@ -2698,6 +2785,10 @@ packages: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + reflect-metadata@0.2.2: resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} @@ -3072,6 +3163,9 @@ packages: tinyexec@0.3.2: resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + tinyexec@1.0.1: + resolution: {integrity: sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==} + tinyglobby@0.2.13: resolution: {integrity: sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==} engines: {node: '>=12.0.0'} @@ -3885,11 +3979,20 @@ snapshots: '@polka/url@1.0.0-next.29': {} - '@prisma/client@6.3.0(prisma@6.3.0(typescript@5.7.2))(typescript@5.7.2)': + '@prisma/client@6.16.0(prisma@6.16.0(typescript@5.7.2))(typescript@5.7.2)': optionalDependencies: - prisma: 6.3.0(typescript@5.7.2) + prisma: 6.16.0(typescript@5.7.2) typescript: 5.7.2 + '@prisma/config@6.16.0': + dependencies: + c12: 3.1.0 + deepmerge-ts: 7.1.5 + effect: 3.16.12 + empathic: 2.0.0 + transitivePeerDependencies: + - magicast + '@prisma/debug@5.3.1': dependencies: '@types/debug': 4.1.8 @@ -3898,18 +4001,18 @@ snapshots: transitivePeerDependencies: - supports-color - '@prisma/debug@6.3.0': {} + '@prisma/debug@6.16.0': {} - '@prisma/engines-version@6.3.0-17.acc0b9dd43eb689cbd20c9470515d719db10d0b0': {} + '@prisma/engines-version@6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43': {} '@prisma/engines@5.3.1': {} - '@prisma/engines@6.3.0': + '@prisma/engines@6.16.0': dependencies: - '@prisma/debug': 6.3.0 - '@prisma/engines-version': 6.3.0-17.acc0b9dd43eb689cbd20c9470515d719db10d0b0 - '@prisma/fetch-engine': 6.3.0 - '@prisma/get-platform': 6.3.0 + '@prisma/debug': 6.16.0 + '@prisma/engines-version': 6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43 + '@prisma/fetch-engine': 6.16.0 + '@prisma/get-platform': 6.16.0 '@prisma/fetch-engine@5.3.1(encoding@0.1.13)': dependencies: @@ -3934,11 +4037,11 @@ snapshots: - encoding - supports-color - '@prisma/fetch-engine@6.3.0': + '@prisma/fetch-engine@6.16.0': dependencies: - '@prisma/debug': 6.3.0 - '@prisma/engines-version': 6.3.0-17.acc0b9dd43eb689cbd20c9470515d719db10d0b0 - '@prisma/get-platform': 6.3.0 + '@prisma/debug': 6.16.0 + '@prisma/engines-version': 6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43 + '@prisma/get-platform': 6.16.0 '@prisma/generator-helper@5.3.1': dependencies: @@ -3964,9 +4067,9 @@ snapshots: transitivePeerDependencies: - supports-color - '@prisma/get-platform@6.3.0': + '@prisma/get-platform@6.16.0': dependencies: - '@prisma/debug': 6.3.0 + '@prisma/debug': 6.16.0 '@prisma/internals@5.3.1(encoding@0.1.13)': dependencies: @@ -4094,6 +4197,8 @@ snapshots: domhandler: 5.0.3 selderee: 0.11.0 + '@standard-schema/spec@1.0.0': {} + '@swc/helpers@0.5.15': dependencies: tslib: 2.8.1 @@ -4273,13 +4378,13 @@ snapshots: chai: 5.2.0 tinyrainbow: 2.0.0 - '@vitest/mocker@3.1.4(vite@6.3.5(@types/node@20.17.10)(yaml@2.6.1))': + '@vitest/mocker@3.1.4(vite@6.3.5(@types/node@20.17.10)(jiti@2.5.1)(yaml@2.6.1))': dependencies: '@vitest/spy': 3.1.4 estree-walker: 3.0.3 magic-string: 0.30.17 optionalDependencies: - vite: 6.3.5(@types/node@20.17.10)(yaml@2.6.1) + vite: 6.3.5(@types/node@20.17.10)(jiti@2.5.1)(yaml@2.6.1) '@vitest/pretty-format@3.1.4': dependencies: @@ -4309,7 +4414,7 @@ snapshots: sirv: 3.0.1 tinyglobby: 0.2.13 tinyrainbow: 2.0.0 - vitest: 3.1.4(@types/node@20.17.10)(@vitest/ui@3.1.4)(yaml@2.6.1) + vitest: 3.1.4(@types/node@20.17.10)(@vitest/ui@3.1.4)(jiti@2.5.1)(yaml@2.6.1) '@vitest/utils@3.1.4': dependencies: @@ -4583,6 +4688,21 @@ snapshots: bytes@3.1.2: {} + c12@3.1.0: + dependencies: + chokidar: 4.0.3 + confbox: 0.2.2 + defu: 6.1.4 + dotenv: 16.6.1 + exsolve: 1.0.7 + giget: 2.0.0 + jiti: 2.5.1 + ohash: 2.0.11 + pathe: 2.0.3 + perfect-debounce: 1.0.0 + pkg-types: 2.3.0 + rc9: 2.1.2 + cac@6.7.14: {} call-bind-apply-helpers@1.0.1: @@ -4662,10 +4782,18 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + chownr@1.1.4: {} ci-info@3.8.0: {} + citty@0.1.6: + dependencies: + consola: 3.4.2 + cjs-module-lexer@1.4.1: optional: true @@ -4741,6 +4869,10 @@ snapshots: readable-stream: 2.3.8 typedarray: 0.0.6 + confbox@0.2.2: {} + + consola@3.4.2: {} + console-log-level@1.4.1: optional: true @@ -4840,6 +4972,8 @@ snapshots: deep-extend@0.6.0: {} + deepmerge-ts@7.1.5: {} + deepmerge@4.3.1: {} define-data-property@1.1.4: @@ -4854,6 +4988,8 @@ snapshots: has-property-descriptors: 1.0.2 object-keys: 1.1.1 + defu@6.1.4: {} + del@6.1.1: dependencies: globby: 11.1.0 @@ -4867,6 +5003,8 @@ snapshots: depd@2.0.0: {} + destr@2.0.5: {} + destroy@1.2.0: {} detect-libc@2.0.4: {} @@ -4906,6 +5044,8 @@ snapshots: dotenv@16.4.7: {} + dotenv@16.6.1: {} + dunder-proto@1.0.0: dependencies: call-bind-apply-helpers: 1.0.1 @@ -4924,6 +5064,11 @@ snapshots: ee-first@1.1.1: {} + effect@3.16.12: + dependencies: + '@standard-schema/spec': 1.0.0 + fast-check: 3.23.2 + elastic-apm-node@3.52.2: dependencies: '@elastic/ecs-pino-format': 1.5.0 @@ -4972,6 +5117,8 @@ snapshots: emoji-regex@9.2.2: {} + empathic@2.0.0: {} + enabled@2.0.0: {} encodeurl@1.0.2: {} @@ -5205,6 +5352,12 @@ snapshots: transitivePeerDependencies: - supports-color + exsolve@1.0.7: {} + + fast-check@3.23.2: + dependencies: + pure-rand: 6.1.0 + fast-csv@4.3.6: dependencies: '@fast-csv/format': 4.3.5 @@ -5382,6 +5535,15 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.2.6 + giget@2.0.0: + dependencies: + citty: 0.1.6 + consola: 3.4.2 + defu: 6.1.4 + node-fetch-native: 1.6.7 + nypm: 0.6.1 + pathe: 2.0.3 + github-from-package@0.0.0: {} glob-parent@5.1.2: @@ -5747,6 +5909,8 @@ snapshots: optionalDependencies: '@pkgjs/parseargs': 0.11.0 + jiti@2.5.1: {} + js-tokens@4.0.0: {} jsbarcode@3.11.6: {} @@ -6032,6 +6196,8 @@ snapshots: node-addon-api@7.1.1: {} + node-fetch-native@1.6.7: {} + node-fetch@2.6.12(encoding@0.1.13): dependencies: whatwg-url: 5.0.0 @@ -6085,6 +6251,14 @@ snapshots: dependencies: path-key: 3.1.1 + nypm@0.6.1: + dependencies: + citty: 0.1.6 + consola: 3.4.2 + pathe: 2.0.3 + pkg-types: 2.3.0 + tinyexec: 1.0.1 + object-assign@4.1.1: {} object-filter-sequence@1.0.0: @@ -6115,6 +6289,8 @@ snapshots: obliterator@2.0.4: {} + ohash@2.0.11: {} + on-finished@2.3.0: dependencies: ee-first: 1.1.1 @@ -6224,6 +6400,8 @@ snapshots: peberminta@0.9.0: {} + perfect-debounce@1.0.0: {} + picocolors@1.1.1: {} picomatch@2.3.1: {} @@ -6248,6 +6426,12 @@ snapshots: dependencies: find-up: 4.1.0 + pkg-types@2.3.0: + dependencies: + confbox: 0.2.2 + exsolve: 1.0.7 + pathe: 2.0.3 + possible-typed-array-names@1.0.0: {} postcss@8.5.3: @@ -6273,9 +6457,9 @@ snapshots: prettier@3.4.2: {} - prisma-extension-kysely@3.0.0(@prisma/client@6.3.0(prisma@6.3.0(typescript@5.7.2))(typescript@5.7.2)): + prisma-extension-kysely@3.0.0(@prisma/client@6.16.0(prisma@6.16.0(typescript@5.7.2))(typescript@5.7.2)): dependencies: - '@prisma/client': 6.3.0(prisma@6.3.0(typescript@5.7.2))(typescript@5.7.2) + '@prisma/client': 6.16.0(prisma@6.16.0(typescript@5.7.2))(typescript@5.7.2) prisma-kysely@1.8.0(encoding@0.1.13): dependencies: @@ -6288,12 +6472,14 @@ snapshots: - encoding - supports-color - prisma@6.3.0(typescript@5.7.2): + prisma@6.16.0(typescript@5.7.2): dependencies: - '@prisma/engines': 6.3.0 + '@prisma/config': 6.16.0 + '@prisma/engines': 6.16.0 optionalDependencies: - fsevents: 2.3.3 typescript: 5.7.2 + transitivePeerDependencies: + - magicast process-nextick-args@2.0.1: {} @@ -6339,6 +6525,8 @@ snapshots: punycode@2.3.1: optional: true + pure-rand@6.1.0: {} + qs@6.13.0: dependencies: side-channel: 1.1.0 @@ -6364,6 +6552,11 @@ snapshots: iconv-lite: 0.4.24 unpipe: 1.0.0 + rc9@2.1.2: + dependencies: + defu: 6.1.4 + destr: 2.0.5 + rc@1.2.8: dependencies: deep-extend: 0.6.0 @@ -6408,6 +6601,8 @@ snapshots: dependencies: picomatch: 2.3.1 + readdirp@4.1.2: {} + reflect-metadata@0.2.2: {} reflect.getprototypeof@1.0.8: @@ -6853,6 +7048,8 @@ snapshots: tinyexec@0.3.2: {} + tinyexec@1.0.1: {} + tinyglobby@0.2.13: dependencies: fdir: 6.4.4(picomatch@4.0.2) @@ -7058,13 +7255,13 @@ snapshots: vary@1.1.2: {} - vite-node@3.1.4(@types/node@20.17.10)(yaml@2.6.1): + vite-node@3.1.4(@types/node@20.17.10)(jiti@2.5.1)(yaml@2.6.1): dependencies: cac: 6.7.14 debug: 4.4.0(supports-color@5.5.0) es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 6.3.5(@types/node@20.17.10)(yaml@2.6.1) + vite: 6.3.5(@types/node@20.17.10)(jiti@2.5.1)(yaml@2.6.1) transitivePeerDependencies: - '@types/node' - jiti @@ -7079,7 +7276,7 @@ snapshots: - tsx - yaml - vite@6.3.5(@types/node@20.17.10)(yaml@2.6.1): + vite@6.3.5(@types/node@20.17.10)(jiti@2.5.1)(yaml@2.6.1): dependencies: esbuild: 0.25.4 fdir: 6.4.4(picomatch@4.0.2) @@ -7090,12 +7287,13 @@ snapshots: optionalDependencies: '@types/node': 20.17.10 fsevents: 2.3.3 + jiti: 2.5.1 yaml: 2.6.1 - vitest@3.1.4(@types/node@20.17.10)(@vitest/ui@3.1.4)(yaml@2.6.1): + vitest@3.1.4(@types/node@20.17.10)(@vitest/ui@3.1.4)(jiti@2.5.1)(yaml@2.6.1): dependencies: '@vitest/expect': 3.1.4 - '@vitest/mocker': 3.1.4(vite@6.3.5(@types/node@20.17.10)(yaml@2.6.1)) + '@vitest/mocker': 3.1.4(vite@6.3.5(@types/node@20.17.10)(jiti@2.5.1)(yaml@2.6.1)) '@vitest/pretty-format': 3.1.4 '@vitest/runner': 3.1.4 '@vitest/snapshot': 3.1.4 @@ -7112,8 +7310,8 @@ snapshots: tinyglobby: 0.2.13 tinypool: 1.0.2 tinyrainbow: 2.0.0 - vite: 6.3.5(@types/node@20.17.10)(yaml@2.6.1) - vite-node: 3.1.4(@types/node@20.17.10)(yaml@2.6.1) + vite: 6.3.5(@types/node@20.17.10)(jiti@2.5.1)(yaml@2.6.1) + vite-node: 3.1.4(@types/node@20.17.10)(jiti@2.5.1)(yaml@2.6.1) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 20.17.10 From 5674a18cc3e46edec63a322cf3282384fc6177f0 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Thu, 11 Sep 2025 11:48:17 +0700 Subject: [PATCH 121/167] fix: price calc match with flow account --- src/controllers/05-quotation-controller.ts | 43 +++++++++++----------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/src/controllers/05-quotation-controller.ts b/src/controllers/05-quotation-controller.ts index 29face5..d283dbb 100644 --- a/src/controllers/05-quotation-controller.ts +++ b/src/controllers/05-quotation-controller.ts @@ -527,14 +527,13 @@ export class QuotationController extends Controller { const vatIncluded = body.agentPrice ? p.agentPriceVatIncluded : p.vatIncluded; const originalPrice = body.agentPrice ? p.agentPrice : p.price; - const finalPriceWithVat = precisionRound( + const finalPrice = precisionRound( originalPrice + (vatIncluded ? 0 : originalPrice * VAT_DEFAULT), ); - - const price = finalPriceWithVat; - const pricePerUnit = price / (1 + VAT_DEFAULT); + const pricePerUnit = finalPrice / (1 + VAT_DEFAULT); const vat = (body.agentPrice ? p.agentPriceCalcVat : p.calcVat) - ? (pricePerUnit * v.amount - (v.discount || 0)) * VAT_DEFAULT + ? pricePerUnit * (1 + VAT_DEFAULT) * v.amount - + ((v.discount || 0) / (1 + VAT_DEFAULT)) * VAT_DEFAULT : 0; return { @@ -557,13 +556,13 @@ export class QuotationController extends Controller { const price = list.reduce( (a, c) => { - a.totalPrice = precisionRound(a.totalPrice + c.pricePerUnit * c.amount); + const vat = c.vat ? VAT_DEFAULT : 0; + const price = c.pricePerUnit * c.amount * (1 + vat) - c.discount; + + a.totalPrice = precisionRound(a.totalPrice + price / (1 + vat) + c.discount); a.totalDiscount = precisionRound(a.totalDiscount + c.discount); a.vat = precisionRound(a.vat + c.vat); - a.vatExcluded = - c.vat === 0 - ? precisionRound(a.vatExcluded + (c.pricePerUnit * c.amount - (c.discount || 0))) - : a.vatExcluded; + a.vatExcluded = c.vat === 0 ? precisionRound(a.vatExcluded + price) : a.vatExcluded; a.finalPrice = precisionRound( Math.max(a.totalPrice - a.totalDiscount + a.vat - (body.discount || 0), 0), ); @@ -815,14 +814,13 @@ export class QuotationController extends Controller { const vatIncluded = record.agentPrice ? p.agentPriceVatIncluded : p.vatIncluded; const originalPrice = record.agentPrice ? p.agentPrice : p.price; - const finalPriceWithVat = precisionRound( + const finalPrice = precisionRound( originalPrice + (vatIncluded ? 0 : originalPrice * VAT_DEFAULT), ); - - const price = finalPriceWithVat; - const pricePerUnit = price / (1 + VAT_DEFAULT); + const pricePerUnit = finalPrice / (1 + VAT_DEFAULT); const vat = (record.agentPrice ? p.agentPriceCalcVat : p.calcVat) - ? (pricePerUnit * v.amount - (v.discount || 0)) * VAT_DEFAULT + ? pricePerUnit * (1 + VAT_DEFAULT) * v.amount - + ((v.discount || 0) / (1 + VAT_DEFAULT)) * VAT_DEFAULT : 0; return { @@ -845,15 +843,13 @@ export class QuotationController extends Controller { const price = list?.reduce( (a, c) => { - a.totalPrice = precisionRound(a.totalPrice + c.pricePerUnit * c.amount); + const vat = c.vat ? VAT_DEFAULT : 0; + const price = c.pricePerUnit * c.amount * (1 + vat) - c.discount; + + a.totalPrice = precisionRound(a.totalPrice + price / (1 + vat) + c.discount); a.totalDiscount = precisionRound(a.totalDiscount + c.discount); a.vat = precisionRound(a.vat + c.vat); - a.vatExcluded = - c.vat === 0 - ? precisionRound( - a.vatExcluded + (c.pricePerUnit * c.amount - (c.discount || 0)) * VAT_DEFAULT, - ) - : a.vatExcluded; + a.vatExcluded = c.vat === 0 ? precisionRound(a.vatExcluded + price) : a.vatExcluded; a.finalPrice = precisionRound( Math.max(a.totalPrice - a.totalDiscount + a.vat - (body.discount || 0), 0), ); @@ -869,6 +865,9 @@ export class QuotationController extends Controller { finalPrice: 0, }, ); + + console.log(price); + const changed = list?.some((lhs) => { const found = record.productServiceList.find((rhs) => { return ( From 86db927efe0fca149c18a0bec1173ae2e739d410 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Thu, 11 Sep 2025 13:57:23 +0700 Subject: [PATCH 122/167] fix: price calc --- src/controllers/05-quotation-controller.ts | 13 +++-- src/controllers/09-debit-note-controller.ts | 60 ++++++++++++--------- 2 files changed, 40 insertions(+), 33 deletions(-) diff --git a/src/controllers/05-quotation-controller.ts b/src/controllers/05-quotation-controller.ts index d283dbb..942e4d1 100644 --- a/src/controllers/05-quotation-controller.ts +++ b/src/controllers/05-quotation-controller.ts @@ -532,10 +532,10 @@ export class QuotationController extends Controller { ); const pricePerUnit = finalPrice / (1 + VAT_DEFAULT); const vat = (body.agentPrice ? p.agentPriceCalcVat : p.calcVat) - ? pricePerUnit * (1 + VAT_DEFAULT) * v.amount - - ((v.discount || 0) / (1 + VAT_DEFAULT)) * VAT_DEFAULT + ? ((pricePerUnit * (1 + VAT_DEFAULT) * v.amount - (v.discount || 0)) / + (1 + VAT_DEFAULT)) * + VAT_DEFAULT : 0; - return { order: i + 1, productId: v.productId, @@ -819,8 +819,9 @@ export class QuotationController extends Controller { ); const pricePerUnit = finalPrice / (1 + VAT_DEFAULT); const vat = (record.agentPrice ? p.agentPriceCalcVat : p.calcVat) - ? pricePerUnit * (1 + VAT_DEFAULT) * v.amount - - ((v.discount || 0) / (1 + VAT_DEFAULT)) * VAT_DEFAULT + ? ((pricePerUnit * (1 + VAT_DEFAULT) * v.amount - (v.discount || 0)) / + (1 + VAT_DEFAULT)) * + VAT_DEFAULT : 0; return { @@ -866,8 +867,6 @@ export class QuotationController extends Controller { }, ); - console.log(price); - const changed = list?.some((lhs) => { const found = record.productServiceList.find((rhs) => { return ( diff --git a/src/controllers/09-debit-note-controller.ts b/src/controllers/09-debit-note-controller.ts index 5fcdee9..f87bbe9 100644 --- a/src/controllers/09-debit-note-controller.ts +++ b/src/controllers/09-debit-note-controller.ts @@ -430,12 +430,18 @@ export class DebitNoteController extends Controller { const list = body.productServiceList.map((v, i) => { const p = product.find((p) => p.id === v.productId)!; - const price = body.agentPrice ? p.agentPrice : p.price; - const pricePerUnit = p.vatIncluded ? price / (1 + VAT_DEFAULT) : price; - const vat = p.calcVat - ? (pricePerUnit * (v.discount ? v.amount : 1) - (v.discount || 0)) * - VAT_DEFAULT * - (!v.discount ? v.amount : 1) + + const vatIncluded = body.agentPrice ? p.agentPriceVatIncluded : p.vatIncluded; + + const originalPrice = body.agentPrice ? p.agentPrice : p.price; + const finalPrice = precisionRound( + originalPrice + (vatIncluded ? 0 : originalPrice * VAT_DEFAULT), + ); + const pricePerUnit = finalPrice / (1 + VAT_DEFAULT); + const vat = (body.agentPrice ? p.agentPriceCalcVat : p.calcVat) + ? ((pricePerUnit * (1 + VAT_DEFAULT) * v.amount - (v.discount || 0)) / + (1 + VAT_DEFAULT)) * + VAT_DEFAULT : 0; return { @@ -458,15 +464,13 @@ export class DebitNoteController extends Controller { const price = list.reduce( (a, c) => { - a.totalPrice = precisionRound(a.totalPrice + c.pricePerUnit * c.amount); + const vat = c.vat ? VAT_DEFAULT : 0; + const price = c.pricePerUnit * c.amount * (1 + vat) - c.discount; + + a.totalPrice = precisionRound(a.totalPrice + price / (1 + vat) + c.discount); a.totalDiscount = precisionRound(a.totalDiscount + c.discount); a.vat = precisionRound(a.vat + c.vat); - a.vatExcluded = - c.vat === 0 - ? precisionRound( - a.vatExcluded + (c.pricePerUnit * c.amount - (c.discount || 0)) * VAT_DEFAULT, - ) - : a.vatExcluded; + a.vatExcluded = c.vat === 0 ? precisionRound(a.vatExcluded + price) : a.vatExcluded; a.finalPrice = precisionRound( Math.max(a.totalPrice - a.totalDiscount + a.vat - (body.discount || 0), 0), ); @@ -673,12 +677,18 @@ export class DebitNoteController extends Controller { } const list = body.productServiceList.map((v, i) => { const p = product.find((p) => p.id === v.productId)!; - const price = body.agentPrice ? p.agentPrice : p.price; - const pricePerUnit = p.vatIncluded ? price / (1 + VAT_DEFAULT) : price; - const vat = p.calcVat - ? (pricePerUnit * (v.discount ? v.amount : 1) - (v.discount || 0)) * - VAT_DEFAULT * - (!v.discount ? v.amount : 1) + + const vatIncluded = record.agentPrice ? p.agentPriceVatIncluded : p.vatIncluded; + + const originalPrice = record.agentPrice ? p.agentPrice : p.price; + const finalPrice = precisionRound( + originalPrice + (vatIncluded ? 0 : originalPrice * VAT_DEFAULT), + ); + const pricePerUnit = finalPrice / (1 + VAT_DEFAULT); + const vat = (record.agentPrice ? p.agentPriceCalcVat : p.calcVat) + ? ((pricePerUnit * (1 + VAT_DEFAULT) * v.amount - (v.discount || 0)) / + (1 + VAT_DEFAULT)) * + VAT_DEFAULT : 0; return { @@ -701,15 +711,13 @@ export class DebitNoteController extends Controller { const price = list.reduce( (a, c) => { - a.totalPrice = precisionRound(a.totalPrice + c.pricePerUnit * c.amount); + const vat = c.vat ? VAT_DEFAULT : 0; + const price = c.pricePerUnit * c.amount * (1 + vat) - c.discount; + + a.totalPrice = precisionRound(a.totalPrice + price / (1 + vat) + c.discount); a.totalDiscount = precisionRound(a.totalDiscount + c.discount); a.vat = precisionRound(a.vat + c.vat); - a.vatExcluded = - c.vat === 0 - ? precisionRound( - a.vatExcluded + (c.pricePerUnit * c.amount - (c.discount || 0)) * VAT_DEFAULT, - ) - : a.vatExcluded; + a.vatExcluded = c.vat === 0 ? precisionRound(a.vatExcluded + price) : a.vatExcluded; a.finalPrice = precisionRound( Math.max(a.totalPrice - a.totalDiscount + a.vat - (body.discount || 0), 0), ); From eda0edbd292116f81246b632ffd93e59ee126c0d Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Thu, 11 Sep 2025 17:23:21 +0700 Subject: [PATCH 123/167] feat: send remark to flowaccount --- src/services/flowaccount.ts | 41 +++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/services/flowaccount.ts b/src/services/flowaccount.ts index 730482d..80c0ca0 100644 --- a/src/services/flowaccount.ts +++ b/src/services/flowaccount.ts @@ -1,6 +1,8 @@ import prisma from "../db"; import config from "../config.json"; import { CustomerType, PayCondition } from "@prisma/client"; +import { convertTemplate } from "../utils/string-template"; +import { htmlToText } from "html-to-text"; if (!process.env.FLOW_ACCOUNT_URL) throw new Error("Require FLOW_ACCOUNT_URL"); if (!process.env.FLOW_ACCOUNT_CLIENT_ID) throw new Error("Require FLOW_ACCOUNT_CLIENT_ID"); @@ -232,6 +234,29 @@ const flowAccount = { installments: true, quotation: { include: { + paySplit: true, + worker: { + select: { + employee: { + select: { + employeePassport: { + select: { + number: true, + }, + orderBy: { + expireDate: "desc", + }, + take: 1, + }, + namePrefix: true, + firstName: true, + lastName: true, + firstNameEN: true, + lastNameEN: true, + }, + }, + }, + }, registeredBranch: { include: { province: true, @@ -326,6 +351,22 @@ const flowAccount = { ? data.installments.reduce((a, c) => a + c.amount, 0) : quotation.finalPrice, + remarks: htmlToText( + convertTemplate(quotation.remark ?? "", { + "quotation-payment": { + paymentType: quotation?.payCondition || "Full", + amount: quotation.finalPrice, + installments: quotation?.paySplit, + }, + "quotation-labor": { + name: quotation.worker.map( + (v, i) => + `${i + 1}. ` + + `${v.employee.employeePassport.length !== 0 ? v.employee.employeePassport[0].number + "_" : ""}${v.employee.namePrefix}. ${v.employee.firstNameEN ? `${v.employee.firstNameEN} ${v.employee.lastNameEN}` : `${v.employee.firstName} ${v.employee.lastName}`} `.toUpperCase(), + ), + }, + }), + ), items: product.map((v) => ({ type: ProductAndServiceType.ProductNonInv, name: v.product.name, From 8f2810ea294641b01f1dcf85d683cc7bb5adaa77 Mon Sep 17 00:00:00 2001 From: HAM Date: Fri, 12 Sep 2025 11:56:40 +0700 Subject: [PATCH 124/167] feat: add flowAccountProductId field --- prisma/schema.prisma | 3 +++ 1 file changed, 3 insertions(+) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 78609d6..2f25bb2 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -1243,6 +1243,9 @@ model Product { productGroup ProductGroup @relation(fields: [productGroupId], references: [id], onDelete: Cascade) productGroupId String + flowAccountProductIdSellPrice String? + flowAccountProductIdAgentPrice String? + workProduct WorkProduct[] quotationProductServiceList QuotationProductServiceList[] taskProduct TaskProduct[] From c774e9f44c5b0a243d7188059e9e5d3a369cf0f0 Mon Sep 17 00:00:00 2001 From: HAM Date: Fri, 12 Sep 2025 11:56:58 +0700 Subject: [PATCH 125/167] chore: migration --- .../migration.sql | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 prisma/migrations/20250911092303_add_flow_account_product_id_sell_price_and_flow_account_product_id_agent_price_field_in_product_table/migration.sql diff --git a/prisma/migrations/20250911092303_add_flow_account_product_id_sell_price_and_flow_account_product_id_agent_price_field_in_product_table/migration.sql b/prisma/migrations/20250911092303_add_flow_account_product_id_sell_price_and_flow_account_product_id_agent_price_field_in_product_table/migration.sql new file mode 100644 index 0000000..0ef2494 --- /dev/null +++ b/prisma/migrations/20250911092303_add_flow_account_product_id_sell_price_and_flow_account_product_id_agent_price_field_in_product_table/migration.sql @@ -0,0 +1,3 @@ +-- AlterTable +ALTER TABLE "public"."Product" ADD COLUMN "flowAccountProductIdAgentPrice" TEXT, +ADD COLUMN "flowAccountProductIdSellPrice" TEXT; From 250bbca226f69b269808e425d92a02cf88fad619 Mon Sep 17 00:00:00 2001 From: HAM Date: Fri, 12 Sep 2025 15:19:51 +0700 Subject: [PATCH 126/167] feat: create product for flowAccount service --- src/services/flowaccount.ts | 184 ++++++++++++++++++++++++++++++++++++ 1 file changed, 184 insertions(+) diff --git a/src/services/flowaccount.ts b/src/services/flowaccount.ts index 80c0ca0..df20be0 100644 --- a/src/services/flowaccount.ts +++ b/src/services/flowaccount.ts @@ -3,6 +3,7 @@ import config from "../config.json"; import { CustomerType, PayCondition } from "@prisma/client"; import { convertTemplate } from "../utils/string-template"; import { htmlToText } from "html-to-text"; +import { JsonObject } from "@prisma/client/runtime/library"; if (!process.env.FLOW_ACCOUNT_URL) throw new Error("Require FLOW_ACCOUNT_URL"); if (!process.env.FLOW_ACCOUNT_CLIENT_ID) throw new Error("Require FLOW_ACCOUNT_CLIENT_ID"); @@ -388,6 +389,189 @@ const flowAccount = { } return null; }, + + // flowAccount GET Product list + async getProducts() { + const { token } = await flowAccountAPI.auth(); + + const res = await fetch(api + "/products", { + method: "GET", + headers: { + ["Content-Type"]: `application/json`, + ["Authorization"]: `Bearer ${token}`, + }, + }); + + return { + ok: res.ok, + status: res.status, + body: await res.json(), + }; + }, + + // flowAccount GET Product by id + async getProductsById(recordId: string) { + const { token } = await flowAccountAPI.auth(); + + const res = await fetch(api + `/products/${recordId}`, { + method: "GET", + headers: { + ["Content-Type"]: `application/json`, + ["Authorization"]: `Bearer ${token}`, + }, + }); + + const data = await res.json(); + + return { + ok: res.ok, + status: res.status, + list: data.data.list, + total: data.data.total, + }; + }, + + // flowAccount POST create Product + async createProducts(body: JsonObject) { + const { token } = await flowAccountAPI.auth(); + + const commonBody = { + productStructureType: null, + type: "3", + name: body.name, + sellDescription: body.detail, + sellVatType: 3, + unitName: "Unit", + categoryName: "Car", + }; // helper function สำหรับสร้าง product + + const createProduct = async (price: any, vatIncluded: boolean) => { + try { + const res = await fetch(api + "/products", { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify({ + ...commonBody, + sellPrice: price, + sellVatType: vatIncluded ? 1 : 3, + }), + }); + + if (!res.ok) { + throw new Error(`Request failed with status ${res.status}`); + } // ป้องกัน response ที่ไม่ใช่ JSON หรือว่าง + + let json: any = null; + try { + json = await res.json(); + } catch { + throw new Error("Response is not valid JSON"); + } + + return json?.data?.list?.[0]?.id ?? null; + } catch (err) { + console.error("createProduct error:", err); + return null; + } + }; + + const [sellId, agentId] = await Promise.all([ + createProduct(body.price, /true/.test(`${body.vatIncluded}`)), + createProduct(body.agentPrice, /true/.test(`${body.agentPriceVatIncluded}`)), + ]); + + return { + ok: !!(agentId && sellId), + status: agentId && sellId ? 200 : 500, + data: { + productIdAgentPrice: agentId, + productIdSellPrice: sellId, + }, + }; + }, + + // flowAccount PUT edit Product + async editProducts(sellPriceId: String, agentPriceId: String, body: JsonObject) { + console.log("body: ", body); + const { token } = await flowAccountAPI.auth(); + + const commonBody = { + productStructureType: null, + type: "3", + name: body.name, + sellDescription: body.detail, + sellVatType: 3, + unitName: "Unit", + categoryName: "Car", + }; // helper function สำหรับสร้าง product + + const editProduct = async (id: String, price: any, vatIncluded: boolean) => { + try { + const res = await fetch(api + `/products/${id}`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify({ + ...commonBody, + sellPrice: price, + sellVatType: vatIncluded ? 1 : 3, + }), + }); + + if (!res.ok) { + throw new Error(`Request failed with status ${res.status}`); + } // ป้องกัน response ที่ไม่ใช่ JSON หรือว่าง + + let json: any = null; + try { + json = await res.json(); + } catch { + throw new Error("Response is not valid JSON"); + } + + return json?.data?.list?.[0]?.id ?? null; + } catch (err) { + console.error("createProduct error:", err); + return null; + } + }; + + const [agentId, sellId] = await Promise.all([ + editProduct(sellPriceId, body.price, /true/.test(`${body.vatIncluded}`)), + editProduct(agentPriceId, body.agentPrice, /true/.test(`${body.agentPriceVatIncluded}`)), + ]); + + return { + ok: !!(agentId && sellId), + status: agentId && sellId ? 200 : 500, + data: { + productIdAgentPrice: agentId, + productIdSellPrice: sellId, + }, + }; + }, + + // flowAccount DELETE Product + async deleteProduct(recordId: string) { + const { token } = await flowAccountAPI.auth(); + + const res = await fetch(api + `/products/${recordId}`, { + method: "DELETE", + headers: { + ["Authorization"]: `Bearer ${token}`, + }, + }); + + return { + ok: res.ok, + status: res.status, + }; + }, }; export default flowAccount; From 61825309d1224a42a9cf14c08b57278d64bdd3b1 Mon Sep 17 00:00:00 2001 From: HAM Date: Fri, 12 Sep 2025 15:20:20 +0700 Subject: [PATCH 127/167] feat: sync data from data to flowAccount --- src/controllers/04-product-controller.ts | 31 ++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/controllers/04-product-controller.ts b/src/controllers/04-product-controller.ts index b50462c..c1c40d8 100644 --- a/src/controllers/04-product-controller.ts +++ b/src/controllers/04-product-controller.ts @@ -30,6 +30,7 @@ import { deleteFile, deleteFolder, fileLocation, getFile, listFile, setFile } fr import { isUsedError, notFoundError, relationError } from "../utils/error"; import { queryOrNot, whereDateQuery } from "../utils/relation"; import spreadsheet from "../utils/spreadsheet"; +import flowAccount from "../services/flowaccount"; const MANAGE_ROLES = [ "system", @@ -299,6 +300,9 @@ export class ProductController extends Controller { }, update: { value: { increment: 1 } }, }); + + const listId = await flowAccount.createProducts(body); + return await prisma.product.create({ include: { createdBy: true, @@ -306,6 +310,8 @@ export class ProductController extends Controller { }, data: { ...body, + flowAccountProductIdAgentPrice: `${listId.data.productIdAgentPrice}`, + flowAccountProductIdSellPrice: `${listId.data.productIdSellPrice}`, document: body.document ? { createMany: { data: body.document.map((v) => ({ name: v })) }, @@ -379,6 +385,19 @@ export class ProductController extends Controller { await permissionCheck(req.user, productGroup.registeredBranch); } + if ( + product.flowAccountProductIdSellPrice !== null && + product.flowAccountProductIdAgentPrice !== null + ) { + await flowAccount.editProducts( + product.flowAccountProductIdSellPrice, + product.flowAccountProductIdAgentPrice, + body, + ); + } else { + throw notFoundError("FlowAccountProductId"); + } + const record = await prisma.product.update({ include: { productGroup: true, @@ -441,6 +460,18 @@ export class ProductController extends Controller { if (record.status !== Status.CREATED) throw isUsedError("Product"); + if ( + record.flowAccountProductIdSellPrice !== null && + record.flowAccountProductIdAgentPrice !== null + ) { + await Promise.all([ + flowAccount.deleteProduct(record.flowAccountProductIdSellPrice), + flowAccount.deleteProduct(record.flowAccountProductIdAgentPrice), + ]); + } else { + throw notFoundError("FlowAccountProductId"); + } + await deleteFolder(fileLocation.product.img(productId)); return await prisma.product.delete({ From d51531cd4db8e34c97898c06b54847d1c2730cb2 Mon Sep 17 00:00:00 2001 From: HAM Date: Fri, 12 Sep 2025 15:29:52 +0700 Subject: [PATCH 128/167] chore: clean --- src/services/flowaccount.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/services/flowaccount.ts b/src/services/flowaccount.ts index df20be0..49060e0 100644 --- a/src/services/flowaccount.ts +++ b/src/services/flowaccount.ts @@ -487,15 +487,14 @@ const flowAccount = { ok: !!(agentId && sellId), status: agentId && sellId ? 200 : 500, data: { - productIdAgentPrice: agentId, productIdSellPrice: sellId, + productIdAgentPrice: agentId, }, }; }, // flowAccount PUT edit Product async editProducts(sellPriceId: String, agentPriceId: String, body: JsonObject) { - console.log("body: ", body); const { token } = await flowAccountAPI.auth(); const commonBody = { @@ -541,7 +540,7 @@ const flowAccount = { } }; - const [agentId, sellId] = await Promise.all([ + const [sellId, agentId] = await Promise.all([ editProduct(sellPriceId, body.price, /true/.test(`${body.vatIncluded}`)), editProduct(agentPriceId, body.agentPrice, /true/.test(`${body.agentPriceVatIncluded}`)), ]); @@ -550,8 +549,8 @@ const flowAccount = { ok: !!(agentId && sellId), status: agentId && sellId ? 200 : 500, data: { - productIdAgentPrice: agentId, productIdSellPrice: sellId, + productIdAgentPrice: agentId, }, }; }, From 1486ce79abf710101ea8a8783856c50da42e5381 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Mon, 15 Sep 2025 10:50:40 +0700 Subject: [PATCH 129/167] fix: calculate credit note price --- src/controllers/08-credit-note-controller.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/controllers/08-credit-note-controller.ts b/src/controllers/08-credit-note-controller.ts index 48ee2ab..1b3c75d 100644 --- a/src/controllers/08-credit-note-controller.ts +++ b/src/controllers/08-credit-note-controller.ts @@ -13,6 +13,7 @@ import { Security, Tags, } from "tsoa"; +import config from "../config.json"; import prisma from "../db"; @@ -53,6 +54,7 @@ function globalAllow(user: RequestWithUser["user"]) { const listAllowed = ["system", "head_of_admin", "admin", "executive", "accountant"]; return user.roles?.some((v) => listAllowed.includes(v)) || false; } +const VAT_DEFAULT = config.vat; const permissionCond = createPermCondition(globalAllow); const permissionCheck = createPermCheck(globalAllow); @@ -346,9 +348,9 @@ export class CreditNoteController extends Controller { ).length; const price = - c.productService.pricePerUnit - - c.productService.discount / c.productService.amount + - c.productService.vat / c.productService.amount; + c.productService.pricePerUnit * + (1 + (c.productService.product.serviceChargeCalcVat ? VAT_DEFAULT : 0)) - + c.productService.discount; if (serviceChargeStepCount && successCount) { return a + price - c.productService.product.serviceCharge * successCount; @@ -424,7 +426,6 @@ export class CreditNoteController extends Controller { let textData = ""; let dataCustomerId: string[] = []; - let textWorkList: string[] = []; let dataUserId: string[] = []; if (res) { @@ -541,9 +542,9 @@ export class CreditNoteController extends Controller { ).length; const price = - c.productService.pricePerUnit - - c.productService.discount / c.productService.amount + - c.productService.vat / c.productService.amount; + c.productService.pricePerUnit * + (1 + (c.productService.product.serviceChargeCalcVat ? VAT_DEFAULT : 0)) - + c.productService.discount; if (serviceChargeStepCount && successCount) { return a + price - c.productService.product.serviceCharge * successCount; From f2def1b9623cbb7987f449aa814883895b0b746b Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Mon, 15 Sep 2025 16:43:01 +0700 Subject: [PATCH 130/167] fix: wrong calc vat condition --- src/controllers/08-credit-note-controller.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/controllers/08-credit-note-controller.ts b/src/controllers/08-credit-note-controller.ts index 1b3c75d..7cd029e 100644 --- a/src/controllers/08-credit-note-controller.ts +++ b/src/controllers/08-credit-note-controller.ts @@ -348,8 +348,7 @@ export class CreditNoteController extends Controller { ).length; const price = - c.productService.pricePerUnit * - (1 + (c.productService.product.serviceChargeCalcVat ? VAT_DEFAULT : 0)) - + c.productService.pricePerUnit * (1 + (c.productService.vat > 0 ? VAT_DEFAULT : 0)) - c.productService.discount; if (serviceChargeStepCount && successCount) { @@ -542,8 +541,7 @@ export class CreditNoteController extends Controller { ).length; const price = - c.productService.pricePerUnit * - (1 + (c.productService.product.serviceChargeCalcVat ? VAT_DEFAULT : 0)) - + c.productService.pricePerUnit * (1 + (c.productService.vat > 0 ? VAT_DEFAULT : 0)) - c.productService.discount; if (serviceChargeStepCount && successCount) { From 892d76583fc894d0547445b116276dc57b78f32c Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Tue, 16 Sep 2025 09:48:59 +0700 Subject: [PATCH 131/167] chore: flowaccount enable inline vat --- src/services/flowaccount.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/services/flowaccount.ts b/src/services/flowaccount.ts index 49060e0..0bb5512 100644 --- a/src/services/flowaccount.ts +++ b/src/services/flowaccount.ts @@ -327,6 +327,7 @@ const flowAccount = { dueDate: quotation.dueDate, salesName: [quotation.createdBy?.firstName, quotation.createdBy?.lastName].join(" "), + useInlineVat: true, isVatInclusive: true, isVat: true, From 6776188f7beb4b9e7872e233b52ed351eaba3a41 Mon Sep 17 00:00:00 2001 From: HAM Date: Tue, 16 Sep 2025 09:50:23 +0700 Subject: [PATCH 132/167] fix: decimal number --- src/services/flowaccount.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/services/flowaccount.ts b/src/services/flowaccount.ts index 0bb5512..fde345d 100644 --- a/src/services/flowaccount.ts +++ b/src/services/flowaccount.ts @@ -372,7 +372,7 @@ const flowAccount = { items: product.map((v) => ({ type: ProductAndServiceType.ProductNonInv, name: v.product.name, - pricePerUnit: v.pricePerUnit, + pricePerUnit: Math.round(v.pricePerUnit * 100) / 100, quantity: v.amount, discountAmount: v.discount, total: (v.pricePerUnit - (v.discount || 0)) * v.amount + v.vat, @@ -443,8 +443,7 @@ const flowAccount = { sellDescription: body.detail, sellVatType: 3, unitName: "Unit", - categoryName: "Car", - }; // helper function สำหรับสร้าง product + }; const createProduct = async (price: any, vatIncluded: boolean) => { try { @@ -463,7 +462,7 @@ const flowAccount = { if (!res.ok) { throw new Error(`Request failed with status ${res.status}`); - } // ป้องกัน response ที่ไม่ใช่ JSON หรือว่าง + } let json: any = null; try { @@ -506,7 +505,7 @@ const flowAccount = { sellVatType: 3, unitName: "Unit", categoryName: "Car", - }; // helper function สำหรับสร้าง product + }; const editProduct = async (id: String, price: any, vatIncluded: boolean) => { try { @@ -525,7 +524,7 @@ const flowAccount = { if (!res.ok) { throw new Error(`Request failed with status ${res.status}`); - } // ป้องกัน response ที่ไม่ใช่ JSON หรือว่าง + } let json: any = null; try { From 4e71343af7cae29af0f4d9496ec004364e046cbd Mon Sep 17 00:00:00 2001 From: HAM Date: Tue, 16 Sep 2025 09:53:03 +0700 Subject: [PATCH 133/167] refactor: use function for correct decimal number --- src/services/flowaccount.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/services/flowaccount.ts b/src/services/flowaccount.ts index fde345d..ec934ca 100644 --- a/src/services/flowaccount.ts +++ b/src/services/flowaccount.ts @@ -4,6 +4,7 @@ import { CustomerType, PayCondition } from "@prisma/client"; import { convertTemplate } from "../utils/string-template"; import { htmlToText } from "html-to-text"; import { JsonObject } from "@prisma/client/runtime/library"; +import { precisionRound } from "../utils/arithmetic"; if (!process.env.FLOW_ACCOUNT_URL) throw new Error("Require FLOW_ACCOUNT_URL"); if (!process.env.FLOW_ACCOUNT_CLIENT_ID) throw new Error("Require FLOW_ACCOUNT_CLIENT_ID"); @@ -185,6 +186,8 @@ const flowAccountAPI = { data.paymentDate = `${year}-${String(month).padStart(2, "0")}-${String(date).padStart(2, "0")}`; } */ + console.log(JSON.stringify(data, null, 2)); + const res = await fetch( api + "/upgrade/receipts/inline" + (withPayment ? "/with-payment" : ""), { @@ -327,7 +330,6 @@ const flowAccount = { dueDate: quotation.dueDate, salesName: [quotation.createdBy?.firstName, quotation.createdBy?.lastName].join(" "), - useInlineVat: true, isVatInclusive: true, isVat: true, @@ -372,7 +374,7 @@ const flowAccount = { items: product.map((v) => ({ type: ProductAndServiceType.ProductNonInv, name: v.product.name, - pricePerUnit: Math.round(v.pricePerUnit * 100) / 100, + pricePerUnit: precisionRound(v.pricePerUnit), quantity: v.amount, discountAmount: v.discount, total: (v.pricePerUnit - (v.discount || 0)) * v.amount + v.vat, From de33d036314a4e260fa8cd029060a5be8811a725 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Tue, 16 Sep 2025 10:08:01 +0700 Subject: [PATCH 134/167] fix: calculate price --- src/services/flowaccount.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/services/flowaccount.ts b/src/services/flowaccount.ts index ec934ca..31c2fd6 100644 --- a/src/services/flowaccount.ts +++ b/src/services/flowaccount.ts @@ -334,6 +334,7 @@ const flowAccount = { isVat: true, useReceiptDeduction: false, + useInlineVat: true, discounPercentage: 0, discountAmount: quotation.totalDiscount, @@ -377,7 +378,9 @@ const flowAccount = { pricePerUnit: precisionRound(v.pricePerUnit), quantity: v.amount, discountAmount: v.discount, - total: (v.pricePerUnit - (v.discount || 0)) * v.amount + v.vat, + total: + precisionRound(v.pricePerUnit * (1 + (v.vat === 0 ? VAT_DEFAULT : 0))) * v.amount - + (v.discount ?? 0), vatRate: v.vat === 0 ? 0 : Math.round(VAT_DEFAULT * 100), })), }; From 25a4b50f8eae8f5b2d24e7f6bbcdd28b00ad386a Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Tue, 16 Sep 2025 10:11:02 +0700 Subject: [PATCH 135/167] fix: wrong condition --- src/services/flowaccount.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/flowaccount.ts b/src/services/flowaccount.ts index 31c2fd6..90eb5c0 100644 --- a/src/services/flowaccount.ts +++ b/src/services/flowaccount.ts @@ -379,7 +379,7 @@ const flowAccount = { quantity: v.amount, discountAmount: v.discount, total: - precisionRound(v.pricePerUnit * (1 + (v.vat === 0 ? VAT_DEFAULT : 0))) * v.amount - + precisionRound(v.pricePerUnit * (1 + (v.vat > 0 ? VAT_DEFAULT : 0))) * v.amount - (v.discount ?? 0), vatRate: v.vat === 0 ? 0 : Math.round(VAT_DEFAULT * 100), })), From 0772e4710a85bd7da732a4dd7d3f193ecc52f22d Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Tue, 16 Sep 2025 11:44:13 +0700 Subject: [PATCH 136/167] fix: cannot update payment data after set payment completed --- src/controllers/05-payment-controller.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/controllers/05-payment-controller.ts b/src/controllers/05-payment-controller.ts index c61d4bc..7ad9584 100644 --- a/src/controllers/05-payment-controller.ts +++ b/src/controllers/05-payment-controller.ts @@ -152,7 +152,18 @@ export class QuotationPayment extends Controller { if (!record) throw notFoundError("Payment"); - if (record.paymentStatus === "PaymentSuccess") return record; + if (record.paymentStatus === "PaymentSuccess") { + const { channel, account, reference } = body; + return await prisma.payment.update({ + where: { id: paymentId, invoice: { quotationId: record.invoice.quotationId } }, + data: { + channel, + account, + reference, + updatedByUserId: req.user.sub, + }, + }); + } return await prisma.$transaction(async (tx) => { const current = new Date(); From 158a6ff1631f333e17914cf999c9a549c163e6d7 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Tue, 16 Sep 2025 11:52:13 +0700 Subject: [PATCH 137/167] fix: timeout in some case --- src/utils/thailand-area.ts | 274 +++++++++++++++++++------------------ 1 file changed, 142 insertions(+), 132 deletions(-) diff --git a/src/utils/thailand-area.ts b/src/utils/thailand-area.ts index 1319e0b..7bcf360 100644 --- a/src/utils/thailand-area.ts +++ b/src/utils/thailand-area.ts @@ -62,85 +62,90 @@ export async function initThailandAreaDatabase() { return result; } - await prisma.$transaction(async (tx) => { - const meta = { - createdBy: null, - createdAt: new Date(), - updatedBy: null, - updatedAt: new Date(), - }; + await prisma.$transaction( + async (tx) => { + const meta = { + createdBy: null, + createdAt: new Date(), + updatedBy: null, + updatedAt: new Date(), + }; - await Promise.all( - splitChunk(province, 1000, async (r) => { - return await tx.$kysely - .insertInto("Province") - .columns(["id", "name", "nameEN", "createdBy", "createdAt", "updatedBy", "updatedAt"]) - .values(r.map((v) => ({ ...v, ...meta }))) - .onConflict((oc) => - oc.column("id").doUpdateSet({ - name: (eb) => eb.ref("excluded.name"), - nameEN: (eb) => eb.ref("excluded.nameEN"), - updatedAt: (eb) => eb.ref("excluded.updatedAt"), - }), - ) - .execute(); - }), - ); + await Promise.all( + splitChunk(province, 1000, async (r) => { + return await tx.$kysely + .insertInto("Province") + .columns(["id", "name", "nameEN", "createdBy", "createdAt", "updatedBy", "updatedAt"]) + .values(r.map((v) => ({ ...v, ...meta }))) + .onConflict((oc) => + oc.column("id").doUpdateSet({ + name: (eb) => eb.ref("excluded.name"), + nameEN: (eb) => eb.ref("excluded.nameEN"), + updatedAt: (eb) => eb.ref("excluded.updatedAt"), + }), + ) + .execute(); + }), + ); - await Promise.all( - splitChunk(district, 2000, async (r) => { - return await tx.$kysely - .insertInto("District") - .columns([ - "id", - "name", - "nameEN", - "provinceId", - "createdBy", - "createdAt", - "updatedBy", - "updatedAt", - ]) - .values(r.map((v) => ({ ...v, ...meta }))) - .onConflict((oc) => - oc.column("id").doUpdateSet({ - name: (eb) => eb.ref("excluded.name"), - nameEN: (eb) => eb.ref("excluded.nameEN"), - provinceId: (eb) => eb.ref("excluded.provinceId"), - updatedAt: (eb) => eb.ref("excluded.updatedAt"), - }), - ) - .execute(); - }), - ); + await Promise.all( + splitChunk(district, 2000, async (r) => { + return await tx.$kysely + .insertInto("District") + .columns([ + "id", + "name", + "nameEN", + "provinceId", + "createdBy", + "createdAt", + "updatedBy", + "updatedAt", + ]) + .values(r.map((v) => ({ ...v, ...meta }))) + .onConflict((oc) => + oc.column("id").doUpdateSet({ + name: (eb) => eb.ref("excluded.name"), + nameEN: (eb) => eb.ref("excluded.nameEN"), + provinceId: (eb) => eb.ref("excluded.provinceId"), + updatedAt: (eb) => eb.ref("excluded.updatedAt"), + }), + ) + .execute(); + }), + ); - await Promise.all( - splitChunk(subDistrict, 1000, async (r) => { - return await tx.$kysely - .insertInto("SubDistrict") - .columns([ - "id", - "name", - "nameEN", - "districtId", - "createdBy", - "createdAt", - "updatedBy", - "updatedAt", - ]) - .values(r.map((v) => ({ ...v, ...meta }))) - .onConflict((oc) => - oc.column("id").doUpdateSet({ - name: (eb) => eb.ref("excluded.name"), - nameEN: (eb) => eb.ref("excluded.nameEN"), - districtId: (eb) => eb.ref("excluded.districtId"), - updatedAt: (eb) => eb.ref("excluded.updatedAt"), - }), - ) - .execute(); - }), - ); - }); + await Promise.all( + splitChunk(subDistrict, 1000, async (r) => { + return await tx.$kysely + .insertInto("SubDistrict") + .columns([ + "id", + "name", + "nameEN", + "districtId", + "createdBy", + "createdAt", + "updatedBy", + "updatedAt", + ]) + .values(r.map((v) => ({ ...v, ...meta }))) + .onConflict((oc) => + oc.column("id").doUpdateSet({ + name: (eb) => eb.ref("excluded.name"), + nameEN: (eb) => eb.ref("excluded.nameEN"), + districtId: (eb) => eb.ref("excluded.districtId"), + updatedAt: (eb) => eb.ref("excluded.updatedAt"), + }), + ) + .execute(); + }), + ); + }, + { + timeout: 15_000, + }, + ); console.log("[INFO]: Sync thailand province, district and subdistrict, OK."); } @@ -170,67 +175,72 @@ export async function initEmploymentOffice() { const list = await prisma.province.findMany(); - await prisma.$transaction(async (tx) => { - await Promise.all( - list - .map(async (province) => { - if (special[province.id]) { - await tx.employmentOffice.deleteMany({ - where: { provinceId: province.id, district: { none: {} } }, - }); - return await Promise.all( - Object.entries(special[province.id]).map(async ([key, val]) => { - const id = province.id + "-" + key.padStart(2, "0"); - return tx.employmentOffice.upsert({ - where: { id }, - create: { - id, - name: nameSpecial(province.name, +key), - nameEN: nameSpecialEN(province.nameEN, +key), - provinceId: province.id, - district: { - createMany: { - data: val.map((districtId) => ({ districtId })), - skipDuplicates: true, + await prisma.$transaction( + async (tx) => { + await Promise.all( + list + .map(async (province) => { + if (special[province.id]) { + await tx.employmentOffice.deleteMany({ + where: { provinceId: province.id, district: { none: {} } }, + }); + return await Promise.all( + Object.entries(special[province.id]).map(async ([key, val]) => { + const id = province.id + "-" + key.padStart(2, "0"); + return tx.employmentOffice.upsert({ + where: { id }, + create: { + id, + name: nameSpecial(province.name, +key), + nameEN: nameSpecialEN(province.nameEN, +key), + provinceId: province.id, + district: { + createMany: { + data: val.map((districtId) => ({ districtId })), + skipDuplicates: true, + }, }, }, - }, - update: { - id, - name: nameSpecial(province.name, +key), - nameEN: nameSpecialEN(province.nameEN, +key), - provinceId: province.id, - district: { - deleteMany: { districtId: { notIn: val } }, - createMany: { - data: val.map((districtId) => ({ districtId })), - skipDuplicates: true, + update: { + id, + name: nameSpecial(province.name, +key), + nameEN: nameSpecialEN(province.nameEN, +key), + provinceId: province.id, + district: { + deleteMany: { districtId: { notIn: val } }, + createMany: { + data: val.map((districtId) => ({ districtId })), + skipDuplicates: true, + }, }, }, - }, - }); - }), - ); - } + }); + }), + ); + } - return tx.employmentOffice.upsert({ - where: { id: province.id }, - create: { - id: province.id, - name: name(province.name), - nameEN: nameEN(province.nameEN), - provinceId: province.id, - }, - update: { - name: name(province.name), - nameEN: nameEN(province.nameEN), - provinceId: province.id, - }, - }); - }) - .flat(), - ); - }); + return tx.employmentOffice.upsert({ + where: { id: province.id }, + create: { + id: province.id, + name: name(province.name), + nameEN: nameEN(province.nameEN), + provinceId: province.id, + }, + update: { + name: name(province.name), + nameEN: nameEN(province.nameEN), + provinceId: province.id, + }, + }); + }) + .flat(), + ); + }, + { + timeout: 15_000, + }, + ); console.log("[INFO]: Sync employment office, OK."); } From 2c790de6066bd0bd15a1d7d81ce956313ec8f78f Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 17 Sep 2025 09:51:35 +0700 Subject: [PATCH 138/167] fix: customer status not change after used --- src/controllers/05-quotation-controller.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/controllers/05-quotation-controller.ts b/src/controllers/05-quotation-controller.ts index 942e4d1..f85b125 100644 --- a/src/controllers/05-quotation-controller.ts +++ b/src/controllers/05-quotation-controller.ts @@ -900,6 +900,20 @@ export class QuotationController extends Controller { }), ]); + if (customerBranch) { + await tx.customerBranch.update({ + where: { id: customerBranch.id }, + data: { + customer: { + update: { + status: Status.ACTIVE, + }, + }, + status: Status.ACTIVE, + }, + }); + } + return await tx.quotation.update({ include: { productServiceList: { From 6598cd3bdfc4e67419ccd3f75103d5783c024ede Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 17 Sep 2025 10:42:35 +0700 Subject: [PATCH 139/167] fix: error field ambigous --- package.json | 4 +- pnpm-lock.yaml | 74 +++++++++++++------------- src/controllers/00-stats-controller.ts | 20 ++++--- 3 files changed, 53 insertions(+), 45 deletions(-) diff --git a/package.json b/package.json index 7fc3a6c..92e1d51 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "@vitest/ui": "^3.1.4", "nodemon": "^3.1.9", "prettier": "^3.4.2", - "prisma": "^6.16.0", + "prisma": "^6.16.2", "prisma-kysely": "^1.8.0", "ts-node": "^10.9.2", "typescript": "^5.7.2", @@ -40,7 +40,7 @@ "dependencies": { "@elastic/elasticsearch": "^8.17.0", "@fast-csv/parse": "^5.0.2", - "@prisma/client": "^6.16.0", + "@prisma/client": "^6.16.2", "@scalar/express-api-reference": "^0.4.182", "@tsoa/runtime": "^6.6.0", "@types/html-to-text": "^9.0.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8bbe67b..5c7f3d7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,8 +15,8 @@ importers: specifier: ^5.0.2 version: 5.0.2 '@prisma/client': - specifier: ^6.16.0 - version: 6.16.0(prisma@6.16.0(typescript@5.7.2))(typescript@5.7.2) + specifier: ^6.16.2 + version: 6.16.2(prisma@6.16.2(typescript@5.7.2))(typescript@5.7.2) '@scalar/express-api-reference': specifier: ^0.4.182 version: 0.4.182 @@ -82,7 +82,7 @@ importers: version: 6.10.0 prisma-extension-kysely: specifier: ^3.0.0 - version: 3.0.0(@prisma/client@6.16.0(prisma@6.16.0(typescript@5.7.2))(typescript@5.7.2)) + version: 3.0.0(@prisma/client@6.16.2(prisma@6.16.2(typescript@5.7.2))(typescript@5.7.2)) promise.any: specifier: ^2.0.6 version: 2.0.6 @@ -133,8 +133,8 @@ importers: specifier: ^3.4.2 version: 3.4.2 prisma: - specifier: ^6.16.0 - version: 6.16.0(typescript@5.7.2) + specifier: ^6.16.2 + version: 6.16.2(typescript@5.7.2) prisma-kysely: specifier: ^1.8.0 version: 1.8.0(encoding@0.1.13) @@ -522,8 +522,8 @@ packages: '@polka/url@1.0.0-next.29': resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} - '@prisma/client@6.16.0': - resolution: {integrity: sha512-FYkFJtgwpwJRMxtmrB26y7gtpR372kyChw6lWng5TMmvn5V+uisy0OyllO5EJD1s8lX78V8X3XjhiXOoMLnu3w==} + '@prisma/client@6.16.2': + resolution: {integrity: sha512-E00PxBcalMfYO/TWnXobBVUai6eW/g5OsifWQsQDzJYm7yaY+IRLo7ZLsaefi0QkTpxfuhFcQ/w180i6kX3iJw==} engines: {node: '>=18.18'} peerDependencies: prisma: '*' @@ -534,14 +534,14 @@ packages: typescript: optional: true - '@prisma/config@6.16.0': - resolution: {integrity: sha512-Q9TgfnllVehvQziY9lJwRJLGmziX0OimZUEQ/MhCUBoJMSScj2VivCjw/Of2vlO1FfyaHXxrvjZAr7ASl7DVcw==} + '@prisma/config@6.16.2': + resolution: {integrity: sha512-mKXSUrcqXj0LXWPmJsK2s3p9PN+aoAbyMx7m5E1v1FufofR1ZpPoIArjjzOIm+bJRLLvYftoNYLx1tbHgF9/yg==} '@prisma/debug@5.3.1': resolution: {integrity: sha512-eYrxqslEKf+wpMFIIHgbcNYuZBXUdiJLA85Or3TwOhgPIN1ZoXT9CwJph3ynW8H1Xg0LkdYLwVmuULCwiMoU5A==} - '@prisma/debug@6.16.0': - resolution: {integrity: sha512-bxzro5vbVqAPkWyDs2A6GpQtRZunD8tyrLmSAchx9u0b+gWCDY6eV+oh5A0YtYT9245dIxQBswckayHuJG4u3w==} + '@prisma/debug@6.16.2': + resolution: {integrity: sha512-bo4/gA/HVV6u8YK2uY6glhNsJ7r+k/i5iQ9ny/3q5bt9ijCj7WMPUwfTKPvtEgLP+/r26Z686ly11hhcLiQ8zA==} '@prisma/engines-version@6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43': resolution: {integrity: sha512-ThvlDaKIVrnrv97ujNFDYiQbeMQpLa0O86HFA2mNoip4mtFqM7U5GSz2ie1i2xByZtvPztJlNRgPsXGeM/kqAA==} @@ -549,14 +549,14 @@ packages: '@prisma/engines@5.3.1': resolution: {integrity: sha512-6QkILNyfeeN67BNEPEtkgh3Xo2tm6D7V+UhrkBbRHqKw9CTaz/vvTP/ROwYSP/3JT2MtIutZm/EnhxUiuOPVDA==} - '@prisma/engines@6.16.0': - resolution: {integrity: sha512-RHJGCH/zi017W4CWYWqg0Sv1pquGGFVo8T3auJ9sodDNaiRzbeNldydjaQzszVS8nscdtcvLuJzy7e65C3puqQ==} + '@prisma/engines@6.16.2': + resolution: {integrity: sha512-7yf3AjfPUgsg/l7JSu1iEhsmZZ/YE00yURPjTikqm2z4btM0bCl2coFtTGfeSOWbQMmq45Jab+53yGUIAT1sjA==} '@prisma/fetch-engine@5.3.1': resolution: {integrity: sha512-w1yk1YiK8N82Pobdq58b85l6e8akyrkxuzwV9DoiUTRf3gpsuhJJesHc4Yi0WzUC9/3znizl1UfCsI6dhkj3Vw==} - '@prisma/fetch-engine@6.16.0': - resolution: {integrity: sha512-Mx5rml0XRIDizhB9eZxSP8c0nMoXYVITTiJJwxlWn9rNCel8mG8NAqIw+vJlN3gPR+kt3IBkP1SQVsplPPpYrA==} + '@prisma/fetch-engine@6.16.2': + resolution: {integrity: sha512-wPnZ8DMRqpgzye758ZvfAMiNJRuYpz+rhgEBZi60ZqDIgOU2694oJxiuu3GKFeYeR/hXxso4/2oBC243t/whxQ==} '@prisma/generator-helper@5.3.1': resolution: {integrity: sha512-zrYS0iHLgPlOJjYnd5KvVMMvSS+ktOL39EwooS5EnyvfzwfzxlKCeOUgxTfiKYs0WUWqzEvyNAYtramYgSknsQ==} @@ -564,8 +564,8 @@ packages: '@prisma/get-platform@5.3.1': resolution: {integrity: sha512-3IiZY2BUjKnAuZ0569zppZE6/rZbVAM09//c2nvPbbkGG9MqrirA8fbhhF7tfVmhyVfdmVCHnf/ujWPHJ8B46Q==} - '@prisma/get-platform@6.16.0': - resolution: {integrity: sha512-eaJOOvAoGslSUTjiQrtE9E0hoBdfL43j8SymOGD6LbdrKRNtIoiy6qiBaEr2fNYD+R/Qns7QOwPhl7SVHJayKA==} + '@prisma/get-platform@6.16.2': + resolution: {integrity: sha512-U/P36Uke5wS7r1+omtAgJpEB94tlT4SdlgaeTc6HVTTT93pXj7zZ+B/cZnmnvjcNPfWddgoDx8RLjmQwqGDYyA==} '@prisma/internals@5.3.1': resolution: {integrity: sha512-zkW73hPHHNrMD21PeYgCTBfMu71vzJf+WtfydtJbS0JVJKyLfOel0iWSQg7wjNeQfccKp+NdHJ/5rTJ4NEUzgA==} @@ -2683,8 +2683,8 @@ packages: resolution: {integrity: sha512-VpNpolZ8RXRgfU+j4R+fPZmX8EE95w3vJ2tt7+FwuiQc0leNTfLK5QLf3KbbPDes2rfjh3g20AjDxefQIo5GIA==} hasBin: true - prisma@6.16.0: - resolution: {integrity: sha512-TTh+H1Kw8N68KN9cDzdAyMroqMOvdCO/Z+kS2wKEVYR1nuR21qH5Q/Db/bZHsAgw7l/TPHtM/veG5VABcdwPDw==} + prisma@6.16.2: + resolution: {integrity: sha512-aRvldGE5UUJTtVmFiH3WfNFNiqFlAtePUxcI0UEGlnXCX7DqhiMT5TRYwncHFeA/Reca5W6ToXXyCMTeFPdSXA==} engines: {node: '>=18.18'} hasBin: true peerDependencies: @@ -3979,12 +3979,12 @@ snapshots: '@polka/url@1.0.0-next.29': {} - '@prisma/client@6.16.0(prisma@6.16.0(typescript@5.7.2))(typescript@5.7.2)': + '@prisma/client@6.16.2(prisma@6.16.2(typescript@5.7.2))(typescript@5.7.2)': optionalDependencies: - prisma: 6.16.0(typescript@5.7.2) + prisma: 6.16.2(typescript@5.7.2) typescript: 5.7.2 - '@prisma/config@6.16.0': + '@prisma/config@6.16.2': dependencies: c12: 3.1.0 deepmerge-ts: 7.1.5 @@ -4001,18 +4001,18 @@ snapshots: transitivePeerDependencies: - supports-color - '@prisma/debug@6.16.0': {} + '@prisma/debug@6.16.2': {} '@prisma/engines-version@6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43': {} '@prisma/engines@5.3.1': {} - '@prisma/engines@6.16.0': + '@prisma/engines@6.16.2': dependencies: - '@prisma/debug': 6.16.0 + '@prisma/debug': 6.16.2 '@prisma/engines-version': 6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43 - '@prisma/fetch-engine': 6.16.0 - '@prisma/get-platform': 6.16.0 + '@prisma/fetch-engine': 6.16.2 + '@prisma/get-platform': 6.16.2 '@prisma/fetch-engine@5.3.1(encoding@0.1.13)': dependencies: @@ -4037,11 +4037,11 @@ snapshots: - encoding - supports-color - '@prisma/fetch-engine@6.16.0': + '@prisma/fetch-engine@6.16.2': dependencies: - '@prisma/debug': 6.16.0 + '@prisma/debug': 6.16.2 '@prisma/engines-version': 6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43 - '@prisma/get-platform': 6.16.0 + '@prisma/get-platform': 6.16.2 '@prisma/generator-helper@5.3.1': dependencies: @@ -4067,9 +4067,9 @@ snapshots: transitivePeerDependencies: - supports-color - '@prisma/get-platform@6.16.0': + '@prisma/get-platform@6.16.2': dependencies: - '@prisma/debug': 6.16.0 + '@prisma/debug': 6.16.2 '@prisma/internals@5.3.1(encoding@0.1.13)': dependencies: @@ -6457,9 +6457,9 @@ snapshots: prettier@3.4.2: {} - prisma-extension-kysely@3.0.0(@prisma/client@6.16.0(prisma@6.16.0(typescript@5.7.2))(typescript@5.7.2)): + prisma-extension-kysely@3.0.0(@prisma/client@6.16.2(prisma@6.16.2(typescript@5.7.2))(typescript@5.7.2)): dependencies: - '@prisma/client': 6.16.0(prisma@6.16.0(typescript@5.7.2))(typescript@5.7.2) + '@prisma/client': 6.16.2(prisma@6.16.2(typescript@5.7.2))(typescript@5.7.2) prisma-kysely@1.8.0(encoding@0.1.13): dependencies: @@ -6472,10 +6472,10 @@ snapshots: - encoding - supports-color - prisma@6.16.0(typescript@5.7.2): + prisma@6.16.2(typescript@5.7.2): dependencies: - '@prisma/config': 6.16.0 - '@prisma/engines': 6.16.0 + '@prisma/config': 6.16.2 + '@prisma/engines': 6.16.2 optionalDependencies: typescript: 5.7.2 transitivePeerDependencies: diff --git a/src/controllers/00-stats-controller.ts b/src/controllers/00-stats-controller.ts index 95484a4..47e803e 100644 --- a/src/controllers/00-stats-controller.ts +++ b/src/controllers/00-stats-controller.ts @@ -618,9 +618,22 @@ export class StatsController extends Controller { startDate = dayjs(startDate).startOf("month").add(1, "month").toDate(); } + const invoices = await tx.invoice.findMany({ + select: { id: true }, + where: { + quotation: { + quotationStatus: { notIn: [QuotationStatus.Canceled] }, + registeredBranch: { OR: permissionCondCompany(req.user) }, + }, + }, + }); + + if (invoices.length === 0) return []; + return await Promise.all( months.map(async (v) => { const date = dayjs(v); + return { month: date.format("MM"), year: date.format("YYYY"), @@ -629,12 +642,7 @@ export class StatsController extends Controller { _sum: { amount: true }, where: { createdAt: { gte: v, lte: date.endOf("month").toDate() }, - invoice: { - quotation: { - quotationStatus: { notIn: [QuotationStatus.Canceled] }, - registeredBranch: { OR: permissionCondCompany(req.user) }, - }, - }, + invoiceId: { in: invoices.map((v) => v.id) }, }, by: "paymentStatus", }) From beb7f4bcfe669120caa7396742f41f466098d16f Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 17 Sep 2025 11:34:54 +0700 Subject: [PATCH 140/167] fix: flowaccount contact name --- src/services/flowaccount.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/services/flowaccount.ts b/src/services/flowaccount.ts index 90eb5c0..33e09cf 100644 --- a/src/services/flowaccount.ts +++ b/src/services/flowaccount.ts @@ -300,10 +300,7 @@ const flowAccount = { ); const payload = { contactCode: customer.code, - contactName: - (customer.customer.customerType === CustomerType.PERS - ? [customer.firstName, customer.lastName].join(" ").trim() - : customer.registerName) || "-", + contactName: customer.contactName || "-", contactAddress: [ customer.address, !!customer.moo ? "หมู่ " + customer.moo : null, @@ -312,7 +309,6 @@ const flowAccount = { (customer.province?.id === "10" ? "แขวง" : "อำเภอ") + customer.subDistrict?.name, (customer.province?.id === "10" ? "เขต" : "ตำบล") + customer.district?.name, "จังหวัด" + customer.province?.name, - customer.subDistrict?.zipCode, ] .filter(Boolean) .join(" "), From 068ba2d293b5248eb2fb8af576e860140dad77a5 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 17 Sep 2025 11:53:15 +0700 Subject: [PATCH 141/167] fix: wrong tax --- src/services/flowaccount.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/flowaccount.ts b/src/services/flowaccount.ts index 33e09cf..c7d0f4c 100644 --- a/src/services/flowaccount.ts +++ b/src/services/flowaccount.ts @@ -312,7 +312,7 @@ const flowAccount = { ] .filter(Boolean) .join(" "), - contactTaxId: customer.citizenId || customer.code, + contactTaxId: customer.citizenId || customer.legalPersonNo, contactBranch: (customer.customer.customerType === CustomerType.PERS ? [customer.firstName, customer.lastName].join(" ").trim() From ab4ea4ba4b7fce3a354f422dd26b153233cbff04 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 17 Sep 2025 11:55:06 +0700 Subject: [PATCH 142/167] fix: type error --- src/services/flowaccount.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/flowaccount.ts b/src/services/flowaccount.ts index c7d0f4c..9450b02 100644 --- a/src/services/flowaccount.ts +++ b/src/services/flowaccount.ts @@ -312,7 +312,7 @@ const flowAccount = { ] .filter(Boolean) .join(" "), - contactTaxId: customer.citizenId || customer.legalPersonNo, + contactTaxId: customer.citizenId || customer.legalPersonNo || "-", contactBranch: (customer.customer.customerType === CustomerType.PERS ? [customer.firstName, customer.lastName].join(" ").trim() From 0d78ce4db3d7341f9828c125a8d6576ee8c7a6d8 Mon Sep 17 00:00:00 2001 From: HAM Date: Wed, 17 Sep 2025 12:37:41 +0700 Subject: [PATCH 143/167] feat: filter businessType, province, district, subDistrict for customer and customer-export endpoint --- src/controllers/03-customer-controller.ts | 45 +++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/src/controllers/03-customer-controller.ts b/src/controllers/03-customer-controller.ts index 1854842..5a6ef36 100644 --- a/src/controllers/03-customer-controller.ts +++ b/src/controllers/03-customer-controller.ts @@ -170,6 +170,10 @@ export class CustomerController extends Controller { @Query() activeBranchOnly?: boolean, @Query() startDate?: Date, @Query() endDate?: Date, + @Query() businessType?: string, + @Query() province?: string, + @Query() district?: string, + @Query() subDistrict?: string, ) { const where = { OR: queryOrNot(query, [ @@ -192,6 +196,47 @@ export class CustomerController extends Controller { : permissionCond(req.user, { activeOnly: activeBranchOnly }), }, }, + branch: { + some: { + AND: [ + businessType + ? { + OR: [ + { businessType: { name: { contains: businessType, mode: "insensitive" } } }, + { businessType: { nameEN: { contains: businessType, mode: "insensitive" } } }, + ], + } + : {}, + + province + ? { + OR: [ + { province: { name: { contains: province, mode: "insensitive" } } }, + { province: { nameEN: { contains: province, mode: "insensitive" } } }, + ], + } + : {}, + + district + ? { + OR: [ + { district: { name: { contains: district, mode: "insensitive" } } }, + { district: { nameEN: { contains: district, mode: "insensitive" } } }, + ], + } + : {}, + + subDistrict + ? { + OR: [ + { subDistrict: { name: { contains: subDistrict, mode: "insensitive" } } }, + { subDistrict: { nameEN: { contains: subDistrict, mode: "insensitive" } } }, + ], + } + : {}, + ], + }, + }, ...whereDateQuery(startDate, endDate), } satisfies Prisma.CustomerWhereInput; From e5a3d948a5f3acf7037727505eb6b3a5b3aad181 Mon Sep 17 00:00:00 2001 From: HAM Date: Wed, 17 Sep 2025 12:39:10 +0700 Subject: [PATCH 144/167] fix: missing query --- src/controllers/03-customer-controller.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/controllers/03-customer-controller.ts b/src/controllers/03-customer-controller.ts index 5a6ef36..43dd376 100644 --- a/src/controllers/03-customer-controller.ts +++ b/src/controllers/03-customer-controller.ts @@ -642,6 +642,10 @@ export class CustomerExportController extends CustomerController { @Query() activeBranchOnly?: boolean, @Query() startDate?: Date, @Query() endDate?: Date, + @Query() businessType?: string, + @Query() province?: string, + @Query() district?: string, + @Query() subDistrict?: string, ) { const ret = await this.list( req, @@ -655,6 +659,10 @@ export class CustomerExportController extends CustomerController { activeBranchOnly, startDate, endDate, + businessType, + province, + district, + subDistrict, ); this.setHeader("Content-Type", "text/csv"); From 7e7b8025c9884090e75e3fecd0109fdac7be1751 Mon Sep 17 00:00:00 2001 From: HAM Date: Wed, 17 Sep 2025 12:58:14 +0700 Subject: [PATCH 145/167] refactor: change to id --- src/controllers/03-customer-controller.ts | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/src/controllers/03-customer-controller.ts b/src/controllers/03-customer-controller.ts index 43dd376..95e2df6 100644 --- a/src/controllers/03-customer-controller.ts +++ b/src/controllers/03-customer-controller.ts @@ -201,37 +201,25 @@ export class CustomerController extends Controller { AND: [ businessType ? { - OR: [ - { businessType: { name: { contains: businessType, mode: "insensitive" } } }, - { businessType: { nameEN: { contains: businessType, mode: "insensitive" } } }, - ], + OR: [{ businessType: { id: businessType } }], } : {}, province ? { - OR: [ - { province: { name: { contains: province, mode: "insensitive" } } }, - { province: { nameEN: { contains: province, mode: "insensitive" } } }, - ], + OR: [{ province: { id: province } }], } : {}, district ? { - OR: [ - { district: { name: { contains: district, mode: "insensitive" } } }, - { district: { nameEN: { contains: district, mode: "insensitive" } } }, - ], + OR: [{ district: { id: district } }], } : {}, subDistrict ? { - OR: [ - { subDistrict: { name: { contains: subDistrict, mode: "insensitive" } } }, - { subDistrict: { nameEN: { contains: subDistrict, mode: "insensitive" } } }, - ], + OR: [{ subDistrict: { id: subDistrict } }], } : {}, ], From f50285161b02142019469e089f186b20cec6e511 Mon Sep 17 00:00:00 2001 From: HAM Date: Thu, 18 Sep 2025 10:13:39 +0700 Subject: [PATCH 146/167] fix: variable name for filter --- src/controllers/03-customer-controller.ts | 40 +++++++++++------------ 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/controllers/03-customer-controller.ts b/src/controllers/03-customer-controller.ts index 95e2df6..bd3bc4d 100644 --- a/src/controllers/03-customer-controller.ts +++ b/src/controllers/03-customer-controller.ts @@ -170,10 +170,10 @@ export class CustomerController extends Controller { @Query() activeBranchOnly?: boolean, @Query() startDate?: Date, @Query() endDate?: Date, - @Query() businessType?: string, - @Query() province?: string, - @Query() district?: string, - @Query() subDistrict?: string, + @Query() businessTypeId?: string, + @Query() provinceId?: string, + @Query() districtId?: string, + @Query() subDistrictId?: string, ) { const where = { OR: queryOrNot(query, [ @@ -199,27 +199,27 @@ export class CustomerController extends Controller { branch: { some: { AND: [ - businessType + businessTypeId ? { - OR: [{ businessType: { id: businessType } }], + OR: [{ businessType: { id: businessTypeId } }], } : {}, - province + provinceId ? { - OR: [{ province: { id: province } }], + OR: [{ province: { id: provinceId } }], } : {}, - district + districtId ? { - OR: [{ district: { id: district } }], + OR: [{ district: { id: districtId } }], } : {}, - subDistrict + subDistrictId ? { - OR: [{ subDistrict: { id: subDistrict } }], + OR: [{ subDistrict: { id: subDistrictId } }], } : {}, ], @@ -630,10 +630,10 @@ export class CustomerExportController extends CustomerController { @Query() activeBranchOnly?: boolean, @Query() startDate?: Date, @Query() endDate?: Date, - @Query() businessType?: string, - @Query() province?: string, - @Query() district?: string, - @Query() subDistrict?: string, + @Query() businessTypeId?: string, + @Query() provinceId?: string, + @Query() districtId?: string, + @Query() subDistrictId?: string, ) { const ret = await this.list( req, @@ -647,10 +647,10 @@ export class CustomerExportController extends CustomerController { activeBranchOnly, startDate, endDate, - businessType, - province, - district, - subDistrict, + businessTypeId, + provinceId, + districtId, + subDistrictId, ); this.setHeader("Content-Type", "text/csv"); From be3c6405c685d8f727af21afb1ef45b0b968074b Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Thu, 18 Sep 2025 10:15:32 +0700 Subject: [PATCH 147/167] feat: export product --- src/controllers/04-product-controller.ts | 41 ++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/controllers/04-product-controller.ts b/src/controllers/04-product-controller.ts index c1c40d8..612275e 100644 --- a/src/controllers/04-product-controller.ts +++ b/src/controllers/04-product-controller.ts @@ -31,6 +31,7 @@ import { isUsedError, notFoundError, relationError } from "../utils/error"; import { queryOrNot, whereDateQuery } from "../utils/relation"; import spreadsheet from "../utils/spreadsheet"; import flowAccount from "../services/flowaccount"; +import { json2csv } from "json-2-csv"; const MANAGE_ROLES = [ "system", @@ -673,3 +674,43 @@ export class ProductFileController extends Controller { return await deleteFile(fileLocation.product.img(productId, name)); } } + +@Route("api/v1/product-export") +@Tags("Product") +export class ProductExportController extends ProductController { + @Get() + @Security("keycloak") + async exportCustomer( + @Request() req: RequestWithUser, + @Query() status?: Status, + @Query() shared?: boolean, + @Query() productGroupId?: string, + @Query() query: string = "", + @Query() page: number = 1, + @Query() pageSize: number = 30, + @Query() orderField?: keyof Product, + @Query() orderBy?: "asc" | "desc", + @Query() activeOnly?: boolean, + @Query() startDate?: Date, + @Query() endDate?: Date, + ) { + const ret = await this.getProduct( + req, + status, + shared, + productGroupId, + query, + page, + pageSize, + orderField, + orderBy, + activeOnly, + startDate, + endDate, + ); + + this.setHeader("Content-Type", "text/csv"); + + return json2csv(ret.result, { useDateIso8601Format: true, expandNestedObjects: true }); + } +} From a426e18025b280acbcbdb8f3a8004d199eb22bc6 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Thu, 18 Sep 2025 16:55:34 +0700 Subject: [PATCH 148/167] fix: wrong number --- src/services/flowaccount.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/services/flowaccount.ts b/src/services/flowaccount.ts index 9450b02..32d5c88 100644 --- a/src/services/flowaccount.ts +++ b/src/services/flowaccount.ts @@ -186,8 +186,6 @@ const flowAccountAPI = { data.paymentDate = `${year}-${String(month).padStart(2, "0")}-${String(date).padStart(2, "0")}`; } */ - console.log(JSON.stringify(data, null, 2)); - const res = await fetch( api + "/upgrade/receipts/inline" + (withPayment ? "/with-payment" : ""), { @@ -338,7 +336,7 @@ const flowAccount = { subTotal: quotation.payCondition === "BillSplitCustom" || quotation.payCondition === "SplitCustom" ? 0 - : quotation.totalPrice, + : quotation.totalPrice - quotation.totalDiscount - quotation.vatExcluded, totalAfterDiscount: quotation.payCondition === "BillSplitCustom" || quotation.payCondition === "SplitCustom" ? 0 From 7858291ae50e1888ff59ca912c3e79dfcc8625b3 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Thu, 18 Sep 2025 17:04:40 +0700 Subject: [PATCH 149/167] fix: receipt flow account --- src/services/flowaccount.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/services/flowaccount.ts b/src/services/flowaccount.ts index 32d5c88..d004c66 100644 --- a/src/services/flowaccount.ts +++ b/src/services/flowaccount.ts @@ -334,9 +334,17 @@ const flowAccount = { discountAmount: quotation.totalDiscount, subTotal: + quotation.payCondition === "BillSplitCustom" || quotation.payCondition === "SplitCustom" + ? 0 + : quotation.totalPrice, + vatableAmount: quotation.payCondition === "BillSplitCustom" || quotation.payCondition === "SplitCustom" ? 0 : quotation.totalPrice - quotation.totalDiscount - quotation.vatExcluded, + exemptAmount: + quotation.payCondition === "BillSplitCustom" || quotation.payCondition === "SplitCustom" + ? 0 + : quotation.vatExcluded, totalAfterDiscount: quotation.payCondition === "BillSplitCustom" || quotation.payCondition === "SplitCustom" ? 0 From 9ef006c860eedef5d1bb536e03ebe4ee7b9387de Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Thu, 18 Sep 2025 17:12:02 +0700 Subject: [PATCH 150/167] refactor: handle flow account installments --- src/services/flowaccount.ts | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/services/flowaccount.ts b/src/services/flowaccount.ts index d004c66..2b4b706 100644 --- a/src/services/flowaccount.ts +++ b/src/services/flowaccount.ts @@ -334,23 +334,38 @@ const flowAccount = { discountAmount: quotation.totalDiscount, subTotal: - quotation.payCondition === "BillSplitCustom" || quotation.payCondition === "SplitCustom" + quotation.payCondition === "BillSplitCustom" || + quotation.payCondition === "BillSplit" || + quotation.payCondition === "SplitCustom" || + quotation.payCondition === "Split" ? 0 : quotation.totalPrice, vatableAmount: - quotation.payCondition === "BillSplitCustom" || quotation.payCondition === "SplitCustom" + quotation.payCondition === "BillSplitCustom" || + quotation.payCondition === "BillSplit" || + quotation.payCondition === "SplitCustom" || + quotation.payCondition === "Split" ? 0 : quotation.totalPrice - quotation.totalDiscount - quotation.vatExcluded, exemptAmount: - quotation.payCondition === "BillSplitCustom" || quotation.payCondition === "SplitCustom" + quotation.payCondition === "BillSplitCustom" || + quotation.payCondition === "BillSplit" || + quotation.payCondition === "SplitCustom" || + quotation.payCondition === "Split" ? 0 : quotation.vatExcluded, totalAfterDiscount: - quotation.payCondition === "BillSplitCustom" || quotation.payCondition === "SplitCustom" + quotation.payCondition === "BillSplitCustom" || + quotation.payCondition === "BillSplit" || + quotation.payCondition === "SplitCustom" || + quotation.payCondition === "Split" ? 0 : quotation.finalPrice, vatAmount: - quotation.payCondition === "BillSplitCustom" || quotation.payCondition === "SplitCustom" + quotation.payCondition === "BillSplitCustom" || + quotation.payCondition === "BillSplit" || + quotation.payCondition === "SplitCustom" || + quotation.payCondition === "Split" ? 0 : quotation.vat, grandTotal: From a33983c530ffcd6c6952b4ba0061d49203a17d9d Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Fri, 19 Sep 2025 09:11:21 +0700 Subject: [PATCH 151/167] fix: transaction ended before create --- src/controllers/04-product-controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/04-product-controller.ts b/src/controllers/04-product-controller.ts index 612275e..d58aca3 100644 --- a/src/controllers/04-product-controller.ts +++ b/src/controllers/04-product-controller.ts @@ -304,7 +304,7 @@ export class ProductController extends Controller { const listId = await flowAccount.createProducts(body); - return await prisma.product.create({ + return await tx.product.create({ include: { createdBy: true, updatedBy: true, From 95ee32fc571c96d81a21c66938e666e50e582645 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Fri, 19 Sep 2025 11:12:03 +0700 Subject: [PATCH 152/167] feat: response branch api --- src/controllers/01-branch-controller.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/controllers/01-branch-controller.ts b/src/controllers/01-branch-controller.ts index a09e24b..943413b 100644 --- a/src/controllers/01-branch-controller.ts +++ b/src/controllers/01-branch-controller.ts @@ -387,6 +387,14 @@ export class BranchController extends Controller { return record; } + @Get("{branchId}/bank") + @Security("keycloak") + async getBranchBankById(@Path() branchId: string) { + return await prisma.branchBank.findMany({ + where: { branchId }, + }); + } + @Post() @Security("keycloak", MANAGE_ROLES) async createBranch(@Request() req: RequestWithUser, @Body() body: BranchCreate) { From a9201f715adecac609f09aec195b0f4506b450d4 Mon Sep 17 00:00:00 2001 From: HAM Date: Fri, 19 Sep 2025 15:35:14 +0700 Subject: [PATCH 153/167] =?UTF-8?q?fix:=20missing=20product=20code,=20buyP?= =?UTF-8?q?rice=20when=20create=20and=20edit=20and=20add=20(=E0=B8=A3?= =?UTF-8?q?=E0=B8=B2=E0=B8=84=E0=B8=B2=E0=B8=95=E0=B8=B1=E0=B8=A7=E0=B9=81?= =?UTF-8?q?=E0=B8=97=E0=B8=99)=20for=20agentPrice=20product=20name?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/controllers/04-product-controller.ts | 19 ++++++++- src/services/flowaccount.ts | 54 +++++++++++++----------- 2 files changed, 47 insertions(+), 26 deletions(-) diff --git a/src/controllers/04-product-controller.ts b/src/controllers/04-product-controller.ts index d58aca3..512fff8 100644 --- a/src/controllers/04-product-controller.ts +++ b/src/controllers/04-product-controller.ts @@ -78,6 +78,7 @@ type ProductCreate = { type ProductUpdate = { status?: "ACTIVE" | "INACTIVE"; + code?: string; name?: string; detail?: string; process?: number; @@ -302,7 +303,10 @@ export class ProductController extends Controller { update: { value: { increment: 1 } }, }); - const listId = await flowAccount.createProducts(body); + const listId = await flowAccount.createProducts( + `${body.code.toLocaleUpperCase()}${last.value.toString().padStart(3, "0")}`, + body, + ); return await tx.product.create({ include: { @@ -390,10 +394,21 @@ export class ProductController extends Controller { product.flowAccountProductIdSellPrice !== null && product.flowAccountProductIdAgentPrice !== null ) { + const mergedBody = { + ...body, + code: body.code ?? product.code, + price: body.price ?? product.price, + agentPrice: body.agentPrice ?? product.agentPrice, + serviceCharge: body.serviceCharge ?? product.serviceCharge, + vatIncluded: body.vatIncluded ?? product.vatIncluded, + agentPriceVatIncluded: body.agentPriceVatIncluded ?? product.agentPriceVatIncluded, + serviceChargeVatIncluded: body.serviceChargeVatIncluded ?? product.serviceChargeVatIncluded, + }; + await flowAccount.editProducts( product.flowAccountProductIdSellPrice, product.flowAccountProductIdAgentPrice, - body, + mergedBody, ); } else { throw notFoundError("FlowAccountProductId"); diff --git a/src/services/flowaccount.ts b/src/services/flowaccount.ts index 2b4b706..6f131fe 100644 --- a/src/services/flowaccount.ts +++ b/src/services/flowaccount.ts @@ -455,19 +455,22 @@ const flowAccount = { }, // flowAccount POST create Product - async createProducts(body: JsonObject) { + async createProducts(code: String, body: JsonObject) { const { token } = await flowAccountAPI.auth(); const commonBody = { productStructureType: null, - type: "3", + type: 3, + code: `${code}`, name: body.name, sellDescription: body.detail, sellVatType: 3, - unitName: "Unit", + buyPrice: body.serviceCharge, + buyVatType: body.serviceChargeVatIncluded ? 1 : 3, + buyDescription: body.detail, }; - const createProduct = async (price: any, vatIncluded: boolean) => { + const createProduct = async (name: string, price: any, vatIncluded: boolean) => { try { const res = await fetch(api + "/products", { method: "POST", @@ -477,13 +480,14 @@ const flowAccount = { }, body: JSON.stringify({ ...commonBody, + name: name, sellPrice: price, sellVatType: vatIncluded ? 1 : 3, }), }); if (!res.ok) { - throw new Error(`Request failed with status ${res.status}`); + throw new Error(`Request failed with status ${res.status} ${res}`); } let json: any = null; @@ -501,8 +505,12 @@ const flowAccount = { }; const [sellId, agentId] = await Promise.all([ - createProduct(body.price, /true/.test(`${body.vatIncluded}`)), - createProduct(body.agentPrice, /true/.test(`${body.agentPriceVatIncluded}`)), + createProduct(`${body.name}`, body.price, /true/.test(`${body.vatIncluded}`)), + createProduct( + `${body.name} (ราคาตัวแทน)`, + body.agentPrice, + /true/.test(`${body.agentPriceVatIncluded}`), + ), ]); return { @@ -521,15 +529,16 @@ const flowAccount = { const commonBody = { productStructureType: null, - type: "3", + type: 3, name: body.name, sellDescription: body.detail, sellVatType: 3, - unitName: "Unit", - categoryName: "Car", + buyPrice: body.serviceCharge, + buyVatType: body.serviceChargeVatIncluded ? 1 : 3, + buyDescription: body.detail, }; - const editProduct = async (id: String, price: any, vatIncluded: boolean) => { + const editProduct = async (id: String, name: String, price: any, vatIncluded: boolean) => { try { const res = await fetch(api + `/products/${id}`, { method: "PUT", @@ -539,13 +548,14 @@ const flowAccount = { }, body: JSON.stringify({ ...commonBody, + name: name, sellPrice: price, sellVatType: vatIncluded ? 1 : 3, }), }); if (!res.ok) { - throw new Error(`Request failed with status ${res.status}`); + throw new Error(`Request failed with status ${res.status} ${res}`); } let json: any = null; @@ -562,19 +572,15 @@ const flowAccount = { } }; - const [sellId, agentId] = await Promise.all([ - editProduct(sellPriceId, body.price, /true/.test(`${body.vatIncluded}`)), - editProduct(agentPriceId, body.agentPrice, /true/.test(`${body.agentPriceVatIncluded}`)), + await Promise.all([ + editProduct(sellPriceId, `${body.name}`, body.price, /true/.test(`${body.vatIncluded}`)), + editProduct( + agentPriceId, + `${body.name} (ราคาตัวแทน)`, + body.agentPrice, + /true/.test(`${body.agentPriceVatIncluded}`), + ), ]); - - return { - ok: !!(agentId && sellId), - status: agentId && sellId ? 200 : 500, - data: { - productIdSellPrice: sellId, - productIdAgentPrice: agentId, - }, - }; }, // flowAccount DELETE Product From 334fb57b46b344aca8e14cf70dc803be6f7ad936 Mon Sep 17 00:00:00 2001 From: HAM Date: Fri, 19 Sep 2025 16:09:21 +0700 Subject: [PATCH 154/167] refactor: flowaccount move product code to front for product name --- src/services/flowaccount.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/services/flowaccount.ts b/src/services/flowaccount.ts index 6f131fe..df40d27 100644 --- a/src/services/flowaccount.ts +++ b/src/services/flowaccount.ts @@ -461,7 +461,6 @@ const flowAccount = { const commonBody = { productStructureType: null, type: 3, - code: `${code}`, name: body.name, sellDescription: body.detail, sellVatType: 3, @@ -505,9 +504,9 @@ const flowAccount = { }; const [sellId, agentId] = await Promise.all([ - createProduct(`${body.name}`, body.price, /true/.test(`${body.vatIncluded}`)), + createProduct(`${code} ${body.name}`, body.price, /true/.test(`${body.vatIncluded}`)), createProduct( - `${body.name} (ราคาตัวแทน)`, + `${code} ${body.name} (ราคาตัวแทน)`, body.agentPrice, /true/.test(`${body.agentPriceVatIncluded}`), ), @@ -573,10 +572,15 @@ const flowAccount = { }; await Promise.all([ - editProduct(sellPriceId, `${body.name}`, body.price, /true/.test(`${body.vatIncluded}`)), + editProduct( + sellPriceId, + `${body.code} ${body.name}`, + body.price, + /true/.test(`${body.vatIncluded}`), + ), editProduct( agentPriceId, - `${body.name} (ราคาตัวแทน)`, + `${body.code} ${body.name} (ราคาตัวแทน)`, body.agentPrice, /true/.test(`${body.agentPriceVatIncluded}`), ), From 3454e462120e3fa87dd40292e736cf872a2783ef Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Fri, 19 Sep 2025 16:17:56 +0700 Subject: [PATCH 155/167] feat: flowaccount handle installments --- src/services/flowaccount.ts | 103 ++++++++++++++++++------------------ 1 file changed, 51 insertions(+), 52 deletions(-) diff --git a/src/services/flowaccount.ts b/src/services/flowaccount.ts index df40d27..0c46f50 100644 --- a/src/services/flowaccount.ts +++ b/src/services/flowaccount.ts @@ -289,13 +289,55 @@ const flowAccount = { const quotation = data.quotation; const customer = quotation.customerBranch; - const product = + + const summary = { + subTotal: 0, + discountAmount: 0, + vatableAmount: 0, + exemptAmount: 0, + vatAmount: 0, + grandTotal: 0, + }; + + const products = ( quotation.payCondition === PayCondition.BillFull || quotation.payCondition === PayCondition.Full ? quotation.productServiceList : quotation.productServiceList.filter((lhs) => data.installments.some((rhs) => rhs.no === lhs.installmentNo), - ); + ) + ).map((v) => { + // TODO: Use product's VAT field (not implemented) instead. + const VAT_RATE = VAT_DEFAULT; + + summary.subTotal += + precisionRound(v.pricePerUnit * (1 + (v.vat > 0 ? VAT_RATE : 0))) * v.amount; + summary.discountAmount += v.discount; + + const total = + precisionRound(v.pricePerUnit * (1 + (v.vat > 0 ? VAT_RATE : 0))) * v.amount - + (v.discount ?? 0); + + if (v.vat > 0) { + summary.vatableAmount += precisionRound(total / (1 + VAT_RATE)); + summary.vatAmount += v.vat; + } else { + summary.exemptAmount += total; + } + + summary.grandTotal += total; + + return { + type: ProductAndServiceType.ProductNonInv, + name: v.product.name, + pricePerUnit: precisionRound(v.pricePerUnit), + quantity: v.amount, + discountAmount: v.discount, + vatRate: v.vat === 0 ? 0 : Math.round(VAT_RATE * 100), + total, + }; + }); + const payload = { contactCode: customer.code, contactName: customer.contactName || "-", @@ -333,45 +375,12 @@ const flowAccount = { discounPercentage: 0, discountAmount: quotation.totalDiscount, - subTotal: - quotation.payCondition === "BillSplitCustom" || - quotation.payCondition === "BillSplit" || - quotation.payCondition === "SplitCustom" || - quotation.payCondition === "Split" - ? 0 - : quotation.totalPrice, - vatableAmount: - quotation.payCondition === "BillSplitCustom" || - quotation.payCondition === "BillSplit" || - quotation.payCondition === "SplitCustom" || - quotation.payCondition === "Split" - ? 0 - : quotation.totalPrice - quotation.totalDiscount - quotation.vatExcluded, - exemptAmount: - quotation.payCondition === "BillSplitCustom" || - quotation.payCondition === "BillSplit" || - quotation.payCondition === "SplitCustom" || - quotation.payCondition === "Split" - ? 0 - : quotation.vatExcluded, - totalAfterDiscount: - quotation.payCondition === "BillSplitCustom" || - quotation.payCondition === "BillSplit" || - quotation.payCondition === "SplitCustom" || - quotation.payCondition === "Split" - ? 0 - : quotation.finalPrice, - vatAmount: - quotation.payCondition === "BillSplitCustom" || - quotation.payCondition === "BillSplit" || - quotation.payCondition === "SplitCustom" || - quotation.payCondition === "Split" - ? 0 - : quotation.vat, - grandTotal: - quotation.payCondition === "BillSplitCustom" || quotation.payCondition === "SplitCustom" - ? data.installments.reduce((a, c) => a + c.amount, 0) - : quotation.finalPrice, + subTotal: summary.subTotal, + totalAfterDiscount: summary.subTotal - summary.discountAmount, + vatableAmount: summary.vatableAmount, + exemptAmount: summary.exemptAmount, + vatAmount: summary.vatAmount, + grandTotal: summary.grandTotal, remarks: htmlToText( convertTemplate(quotation.remark ?? "", { @@ -389,17 +398,7 @@ const flowAccount = { }, }), ), - items: product.map((v) => ({ - type: ProductAndServiceType.ProductNonInv, - name: v.product.name, - pricePerUnit: precisionRound(v.pricePerUnit), - quantity: v.amount, - discountAmount: v.discount, - total: - precisionRound(v.pricePerUnit * (1 + (v.vat > 0 ? VAT_DEFAULT : 0))) * v.amount - - (v.discount ?? 0), - vatRate: v.vat === 0 ? 0 : Math.round(VAT_DEFAULT * 100), - })), + items: products, }; return await flowAccountAPI.createReceipt(payload, false); From 5dc88c22dcf37f5e48df14be1e6e5751eade858b Mon Sep 17 00:00:00 2001 From: HAM Date: Mon, 29 Sep 2025 09:44:41 +0700 Subject: [PATCH 156/167] feat: delete created data when flow account create fail one --- src/services/flowaccount.ts | 52 ++++++++++++++++++++++++++----------- 1 file changed, 37 insertions(+), 15 deletions(-) diff --git a/src/services/flowaccount.ts b/src/services/flowaccount.ts index 0c46f50..7faf492 100644 --- a/src/services/flowaccount.ts +++ b/src/services/flowaccount.ts @@ -454,7 +454,7 @@ const flowAccount = { }, // flowAccount POST create Product - async createProducts(code: String, body: JsonObject) { + async createProducts(code: string, body: JsonObject) { const { token } = await flowAccountAPI.auth(); const commonBody = { @@ -470,7 +470,7 @@ const flowAccount = { const createProduct = async (name: string, price: any, vatIncluded: boolean) => { try { - const res = await fetch(api + "/products", { + const res = await fetch(`${api}/products`, { method: "POST", headers: { "Content-Type": "application/json", @@ -478,22 +478,17 @@ const flowAccount = { }, body: JSON.stringify({ ...commonBody, - name: name, + name, sellPrice: price, sellVatType: vatIncluded ? 1 : 3, }), }); - if (!res.ok) { - throw new Error(`Request failed with status ${res.status} ${res}`); - } + if (!res.ok) throw new Error(`HTTP ${res.status}: Failed to create product`); - let json: any = null; - try { - json = await res.json(); - } catch { - throw new Error("Response is not valid JSON"); - } + const json = await res.json().catch(() => { + throw new Error("Invalid JSON response from FlowAccount API"); + }); return json?.data?.list?.[0]?.id ?? null; } catch (err) { @@ -502,7 +497,18 @@ const flowAccount = { } }; - const [sellId, agentId] = await Promise.all([ + const deleteProduct = async (id: string) => { + try { + await fetch(`${api}/products/${id}`, { + method: "DELETE", + headers: { Authorization: `Bearer ${token}` }, + }); + } catch (err) { + console.error("Rollback delete failed:", err); + } + }; + + const [sellResult, agentResult] = await Promise.allSettled([ createProduct(`${code} ${body.name}`, body.price, /true/.test(`${body.vatIncluded}`)), createProduct( `${code} ${body.name} (ราคาตัวแทน)`, @@ -511,9 +517,25 @@ const flowAccount = { ), ]); + const sellId = sellResult.status === "fulfilled" ? sellResult.value : null; + const agentId = agentResult.status === "fulfilled" ? agentResult.value : null; + + // --- validation --- + if (!sellId && !agentId) { + throw new Error("FlowAccountProductError.BOTH_CREATION_FAILED"); + } + if (!sellId && agentId) { + await deleteProduct(agentId); + throw new Error("FlowAccountProductError.SELL_PRICE_CREATION_FAILED"); + } + if (sellId && !agentId) { + await deleteProduct(sellId); + throw new Error("FlowAccountProductError.AGENT_PRICE_CREATION_FAILED"); + } + return { - ok: !!(agentId && sellId), - status: agentId && sellId ? 200 : 500, + ok: true, + status: 200, data: { productIdSellPrice: sellId, productIdAgentPrice: agentId, From 78669ed7aee2d0b2a03cab425f62397cd692e3f0 Mon Sep 17 00:00:00 2001 From: HAM Date: Tue, 14 Oct 2025 16:19:37 +0700 Subject: [PATCH 157/167] feat: check invalid data for create taskOrder --- src/controllers/07-task-controller.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/controllers/07-task-controller.ts b/src/controllers/07-task-controller.ts index ad97be0..cf7b402 100644 --- a/src/controllers/07-task-controller.ts +++ b/src/controllers/07-task-controller.ts @@ -77,6 +77,7 @@ export class TaskController extends Controller { by: ["taskOrderStatus"], _count: true, }); + return task.reduce>( (a, c) => Object.assign(a, { [c.taskOrderStatus]: c._count }), { @@ -264,6 +265,12 @@ export class TaskController extends Controller { taskProduct?: { productId: string; discount?: number }[]; }, ) { + if (!body.taskList || !body.registeredBranchId) + throw new HttpError( + HttpStatus.BAD_REQUEST, + "Your created invalid task order", + "taskOrderInvalid", + ); return await prisma.$transaction(async (tx) => { const last = await tx.runningNo.upsert({ where: { @@ -311,10 +318,10 @@ export class TaskController extends Controller { }); if (updated.count !== taskList.length) { - throw new HttpError( - HttpStatus.PRECONDITION_FAILED, - "All request work to issue task order must be in ready state.", - "requestWorkMustReady", + throw new httperror( + httpstatus.precondition_failed, + "all request work to issue task order must be in ready state.", + "requestworkmustready", ); } await tx.institution.updateMany({ From 16c4c64c899b3666cb15f5d20b05606c31075732 Mon Sep 17 00:00:00 2001 From: net Date: Tue, 14 Oct 2025 17:12:00 +0700 Subject: [PATCH 158/167] refactor: handle code error --- src/controllers/07-task-controller.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/controllers/07-task-controller.ts b/src/controllers/07-task-controller.ts index cf7b402..02def1e 100644 --- a/src/controllers/07-task-controller.ts +++ b/src/controllers/07-task-controller.ts @@ -265,7 +265,7 @@ export class TaskController extends Controller { taskProduct?: { productId: string; discount?: number }[]; }, ) { - if (!body.taskList || !body.registeredBranchId) + if (body.taskList.length < 1 || !body.registeredBranchId) throw new HttpError( HttpStatus.BAD_REQUEST, "Your created invalid task order", @@ -318,8 +318,8 @@ export class TaskController extends Controller { }); if (updated.count !== taskList.length) { - throw new httperror( - httpstatus.precondition_failed, + throw new HttpError( + HttpStatus.PRECONDITION_FAILED, "all request work to issue task order must be in ready state.", "requestworkmustready", ); From cef26278ba7f58cc7f200778f05dced5c467fac7 Mon Sep 17 00:00:00 2001 From: HAM Date: Mon, 12 Jan 2026 13:15:31 +0700 Subject: [PATCH 159/167] fix: missing prisma query take and skip --- src/controllers/08-credit-note-controller.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/controllers/08-credit-note-controller.ts b/src/controllers/08-credit-note-controller.ts index 7cd029e..a8e8767 100644 --- a/src/controllers/08-credit-note-controller.ts +++ b/src/controllers/08-credit-note-controller.ts @@ -156,7 +156,6 @@ export class CreditNoteController extends Controller { @Query() creditNoteStatus?: CreditNoteStatus, @Query() startDate?: Date, @Query() endDate?: Date, - @Body() body?: {}, ) { const where = { OR: queryOrNot(query, [ @@ -218,6 +217,8 @@ export class CreditNoteController extends Controller { const [result, total] = await prisma.$transaction([ prisma.creditNote.findMany({ where, + take: pageSize, + skip: (page - 1) * pageSize, include: { quotation: { include: { From e54f62a5b3334bca74223a2168261c59f545752e Mon Sep 17 00:00:00 2001 From: HAM Date: Mon, 12 Jan 2026 13:39:04 +0700 Subject: [PATCH 160/167] fix: prisma v7 config --- prisma.config.ts | 7 +++++++ src/db.ts | 1 + 2 files changed, 8 insertions(+) create mode 100644 prisma.config.ts diff --git a/prisma.config.ts b/prisma.config.ts new file mode 100644 index 0000000..69604f1 --- /dev/null +++ b/prisma.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from "prisma/config"; + +export default defineConfig({ + migrations: { + path: "prisma/migrations", + }, +}); diff --git a/src/db.ts b/src/db.ts index 1e2206b..0402c07 100644 --- a/src/db.ts +++ b/src/db.ts @@ -4,6 +4,7 @@ import kyselyExtension from "prisma-extension-kysely"; import type { DB } from "./generated/kysely/types"; const prisma = new PrismaClient({ + datasourceUrl: process.env.DATABASE_URL, errorFormat: process.env.NODE_ENV === "production" ? "minimal" : "pretty", }).$extends( kyselyExtension({ From 84b9ddcd2b0fb133a17b310822b6fcdf42958618 Mon Sep 17 00:00:00 2001 From: HAM Date: Mon, 12 Jan 2026 13:41:00 +0700 Subject: [PATCH 161/167] fix: prisma v7 config in schema.prisma --- prisma/schema.prisma | 1 - 1 file changed, 1 deletion(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 2f25bb2..b8f518b 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -10,7 +10,6 @@ generator kysely { datasource db { provider = "postgresql" - url = env("DATABASE_URL") } model Notification { From f0a106e5fee18c2b7f3c3d7df979ab9ed6e571b0 Mon Sep 17 00:00:00 2001 From: HAM Date: Mon, 12 Jan 2026 13:48:10 +0700 Subject: [PATCH 162/167] fix: use prisma v6 config --- package.json | 6 ++++-- prisma/schema.prisma | 1 + src/db.ts | 1 - 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 92e1d51..69799c0 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "@vitest/ui": "^3.1.4", "nodemon": "^3.1.9", "prettier": "^3.4.2", - "prisma": "^6.16.2", + "prisma": "6.16.2", "prisma-kysely": "^1.8.0", "ts-node": "^10.9.2", "typescript": "^5.7.2", @@ -40,13 +40,14 @@ "dependencies": { "@elastic/elasticsearch": "^8.17.0", "@fast-csv/parse": "^5.0.2", - "@prisma/client": "^6.16.2", + "@prisma/client": "6.16.2", "@scalar/express-api-reference": "^0.4.182", "@tsoa/runtime": "^6.6.0", "@types/html-to-text": "^9.0.4", "canvas": "^3.1.0", "cors": "^2.8.5", "cron": "^3.3.1", + "csv-parse": "^6.1.0", "dayjs": "^1.11.13", "dayjs-plugin-utc": "^0.1.2", "docx-templates": "^4.13.0", @@ -62,6 +63,7 @@ "morgan": "^1.10.0", "multer": "^1.4.5-lts.2", "nodemailer": "^6.10.0", + "pnpm": "^10.18.3", "prisma-extension-kysely": "^3.0.0", "promise.any": "^2.0.6", "thai-baht-text": "^2.0.5", diff --git a/prisma/schema.prisma b/prisma/schema.prisma index b8f518b..2f25bb2 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -10,6 +10,7 @@ generator kysely { datasource db { provider = "postgresql" + url = env("DATABASE_URL") } model Notification { diff --git a/src/db.ts b/src/db.ts index 0402c07..1e2206b 100644 --- a/src/db.ts +++ b/src/db.ts @@ -4,7 +4,6 @@ import kyselyExtension from "prisma-extension-kysely"; import type { DB } from "./generated/kysely/types"; const prisma = new PrismaClient({ - datasourceUrl: process.env.DATABASE_URL, errorFormat: process.env.NODE_ENV === "production" ? "minimal" : "pretty", }).$extends( kyselyExtension({ From 8909a763c9fe10c8da0f80a3c46a99bc566f3c88 Mon Sep 17 00:00:00 2001 From: HAM Date: Mon, 12 Jan 2026 13:51:54 +0700 Subject: [PATCH 163/167] chore: remove unuse file --- prisma.config.ts | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 prisma.config.ts diff --git a/prisma.config.ts b/prisma.config.ts deleted file mode 100644 index 69604f1..0000000 --- a/prisma.config.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { defineConfig } from "prisma/config"; - -export default defineConfig({ - migrations: { - path: "prisma/migrations", - }, -}); From c11fed98325507faefde5af1bbff724943ab9cad Mon Sep 17 00:00:00 2001 From: HAM Date: Mon, 12 Jan 2026 14:10:11 +0700 Subject: [PATCH 164/167] fix: lock prisma to v6 and fix lockfile --- pnpm-lock.yaml | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5c7f3d7..d724159 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,7 +15,7 @@ importers: specifier: ^5.0.2 version: 5.0.2 '@prisma/client': - specifier: ^6.16.2 + specifier: 6.16.2 version: 6.16.2(prisma@6.16.2(typescript@5.7.2))(typescript@5.7.2) '@scalar/express-api-reference': specifier: ^0.4.182 @@ -35,6 +35,9 @@ importers: cron: specifier: ^3.3.1 version: 3.3.1 + csv-parse: + specifier: ^6.1.0 + version: 6.1.0 dayjs: specifier: ^1.11.13 version: 1.11.13 @@ -80,6 +83,9 @@ importers: nodemailer: specifier: ^6.10.0 version: 6.10.0 + pnpm: + specifier: ^10.18.3 + version: 10.28.0 prisma-extension-kysely: specifier: ^3.0.0 version: 3.0.0(@prisma/client@6.16.2(prisma@6.16.2(typescript@5.7.2))(typescript@5.7.2)) @@ -133,7 +139,7 @@ importers: specifier: ^3.4.2 version: 3.4.2 prisma: - specifier: ^6.16.2 + specifier: 6.16.2 version: 6.16.2(typescript@5.7.2) prisma-kysely: specifier: ^1.8.0 @@ -1283,6 +1289,9 @@ packages: resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==} engines: {node: '>=8'} + csv-parse@6.1.0: + resolution: {integrity: sha512-CEE+jwpgLn+MmtCpVcPtiCZpVtB6Z2OKPTr34pycYYoL7sxdOkXDdQ4lRiw6ioC0q6BLqhc6cKweCVvral8yhw==} + data-view-buffer@1.0.1: resolution: {integrity: sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==} engines: {node: '>= 0.4'} @@ -2656,6 +2665,11 @@ packages: pkg-types@2.3.0: resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} + pnpm@10.28.0: + resolution: {integrity: sha512-Bd9x0UIfITmeBT/eVnzqNNRG+gLHZXFEG/wceVbpjjYwiJgtlARl/TRIDU2QoGaLwSNi+KqIAApk6D0LDke+SA==} + engines: {node: '>=18.12'} + hasBin: true + possible-typed-array-names@1.0.0: resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} engines: {node: '>= 0.4'} @@ -4924,6 +4938,8 @@ snapshots: crypto-random-string@2.0.0: {} + csv-parse@6.1.0: {} + data-view-buffer@1.0.1: dependencies: call-bind: 1.0.8 @@ -6432,6 +6448,8 @@ snapshots: exsolve: 1.0.7 pathe: 2.0.3 + pnpm@10.28.0: {} + possible-typed-array-names@1.0.0: {} postcss@8.5.3: From a10626e75627d29548b38f0e8d455b4ba564f414 Mon Sep 17 00:00:00 2001 From: HAM Date: Mon, 12 Jan 2026 14:32:29 +0700 Subject: [PATCH 165/167] update: entrypoint.sh --- entrypoint.sh | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/entrypoint.sh b/entrypoint.sh index 4d9a5e6..df5f60f 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -1,4 +1,8 @@ #!/bin/sh +set -e -pnpm prisma migrate deploy -pnpm run start +echo "Running prisma migrations..." +npx prisma migrate deploy + +echo "Starting server..." +node ./dist/app.js From 5d3997343fb007fd4e22223d947b0d4aced5e992 Mon Sep 17 00:00:00 2001 From: HAM Date: Mon, 12 Jan 2026 14:52:16 +0700 Subject: [PATCH 166/167] fix: Dockerfile --- Dockerfile | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/Dockerfile b/Dockerfile index 7b7967d..3ff374d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,32 +3,36 @@ FROM node:23-slim AS base ENV PNPM_HOME="/pnpm" ENV PATH="$PNPM_HOME:$PATH" -RUN corepack enable -RUN apt-get update && apt-get install -y openssl fontconfig -RUN fc-cache -f -v -RUN pnpm i -g prisma prisma-kysely +RUN corepack enable \ + && corepack prepare pnpm@9.15.0 --activate + +RUN apt-get update \ + && apt-get install -y openssl fontconfig \ + && fc-cache -f -v \ + && rm -rf /var/lib/apt/lists/* WORKDIR /app - COPY . . -FROM base AS deps -RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod --frozen-lockfile -RUN pnpm prisma generate - FROM base AS build -RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile -RUN pnpm prisma generate +RUN --mount=type=cache,id=pnpm,target=/pnpm/store \ + pnpm install --frozen-lockfile +RUN pnpm exec prisma generate RUN pnpm run build -FROM base AS prod +FROM base AS deps +RUN --mount=type=cache,id=pnpm,target=/pnpm/store \ + pnpm install --prod --frozen-lockfile -ENV NODE_ENV="production" +FROM node:23-slim AS prod +ENV NODE_ENV=production +WORKDIR /app COPY --from=deps /app/node_modules /app/node_modules COPY --from=build /app/dist /app/dist -COPY --from=base /app/static /app/static - -RUN chmod u+x ./entrypoint.sh +COPY --from=build /app/prisma /app/prisma +COPY --from=build /app/static /app/static +COPY entrypoint.sh . +RUN chmod +x ./entrypoint.sh ENTRYPOINT ["./entrypoint.sh"] From 426ffb27a7a9055c6515bc6dae7a95b60cf674c6 Mon Sep 17 00:00:00 2001 From: HAM Date: Mon, 12 Jan 2026 15:05:21 +0700 Subject: [PATCH 167/167] fix: update Dockerfile --- Dockerfile | 44 ++++++++++++++------------------------------ entrypoint.sh | 8 ++------ 2 files changed, 16 insertions(+), 36 deletions(-) diff --git a/Dockerfile b/Dockerfile index 3ff374d..f29dc9a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,38 +1,22 @@ -FROM node:23-slim AS base +FROM node:20-slim -ENV PNPM_HOME="/pnpm" -ENV PATH="$PNPM_HOME:$PATH" - -RUN corepack enable \ - && corepack prepare pnpm@9.15.0 --activate - -RUN apt-get update \ - && apt-get install -y openssl fontconfig \ - && fc-cache -f -v \ - && rm -rf /var/lib/apt/lists/* +RUN apt-get update -y \ + && apt-get install -y openssl \ + && npm install -g pnpm \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* WORKDIR /app + +COPY package.json pnpm-lock.yaml ./ +RUN pnpm install --frozen-lockfile + COPY . . -FROM base AS build -RUN --mount=type=cache,id=pnpm,target=/pnpm/store \ - pnpm install --frozen-lockfile -RUN pnpm exec prisma generate +RUN pnpm prisma generate RUN pnpm run build -FROM base AS deps -RUN --mount=type=cache,id=pnpm,target=/pnpm/store \ - pnpm install --prod --frozen-lockfile +COPY entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh -FROM node:23-slim AS prod -ENV NODE_ENV=production -WORKDIR /app - -COPY --from=deps /app/node_modules /app/node_modules -COPY --from=build /app/dist /app/dist -COPY --from=build /app/prisma /app/prisma -COPY --from=build /app/static /app/static -COPY entrypoint.sh . - -RUN chmod +x ./entrypoint.sh -ENTRYPOINT ["./entrypoint.sh"] +ENTRYPOINT ["/entrypoint.sh"] diff --git a/entrypoint.sh b/entrypoint.sh index df5f60f..4d9a5e6 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -1,8 +1,4 @@ #!/bin/sh -set -e -echo "Running prisma migrations..." -npx prisma migrate deploy - -echo "Starting server..." -node ./dist/app.js +pnpm prisma migrate deploy +pnpm run start