From e6ec5739972c09e98955d83f7b6485cd7541b9ba Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 22 Jan 2025 13:31:59 +0700 Subject: [PATCH 01/10] feat: add ready status to request data --- prisma/schema.prisma | 1 + src/controllers/06-request-list-controller.ts | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 51f30b8..684f96a 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -1406,6 +1406,7 @@ model Payment { enum RequestDataStatus { Pending + Ready InProgress Completed Canceled diff --git a/src/controllers/06-request-list-controller.ts b/src/controllers/06-request-list-controller.ts index f669f0a..4f428fa 100644 --- a/src/controllers/06-request-list-controller.ts +++ b/src/controllers/06-request-list-controller.ts @@ -520,7 +520,11 @@ export class RequestListController extends Controller { return await prisma.$transaction(async (tx) => { const record = await tx.requestWorkStepStatus.upsert({ include: { - requestWork: true, + requestWork: { + include: { + request: true, + }, + }, }, where: { step_requestWorkId: { @@ -537,6 +541,17 @@ export class RequestListController extends Controller { }); switch (payload.workStatus) { + case "Ready": + if (record.requestWork.request.requestDataStatus === "Pending") { + await tx.requestData.updateMany({ + where: { + id: record.requestWork.requestDataId, + requestDataStatus: "Pending", + }, + data: { requestDataStatus: "Ready" }, + }); + } + break; case "InProgress": case "Waiting": case "Validate": From 847f586707df04e025b9d7f77a92f70c6593733a Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 22 Jan 2025 13:32:19 +0700 Subject: [PATCH 02/10] chore: migration --- prisma/migrations/20250122062003_add_ready_status/migration.sql | 2 ++ prisma/migrations/migration_lock.toml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 prisma/migrations/20250122062003_add_ready_status/migration.sql diff --git a/prisma/migrations/20250122062003_add_ready_status/migration.sql b/prisma/migrations/20250122062003_add_ready_status/migration.sql new file mode 100644 index 0000000..aca939d --- /dev/null +++ b/prisma/migrations/20250122062003_add_ready_status/migration.sql @@ -0,0 +1,2 @@ +-- AlterEnum +ALTER TYPE "RequestDataStatus" ADD VALUE 'Ready'; diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml index fbffa92..648c57f 100644 --- a/prisma/migrations/migration_lock.toml +++ b/prisma/migrations/migration_lock.toml @@ -1,3 +1,3 @@ # Please do not edit this file manually -# It should be added in your version-control system (i.e. Git) +# It should be added in your version-control system (e.g., Git) provider = "postgresql" \ No newline at end of file From 71401da000e8d3def57ed93a5e5a4d7c4e52cea7 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 22 Jan 2025 13:34:07 +0700 Subject: [PATCH 03/10] fix: missing stats --- 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 4f428fa..f90918a 100644 --- a/src/controllers/06-request-list-controller.ts +++ b/src/controllers/06-request-list-controller.ts @@ -63,6 +63,7 @@ export class RequestDataController extends Controller { (a, c) => Object.assign(a, { [c.requestDataStatus]: c._count }), { [RequestDataStatus.Pending]: 0, + [RequestDataStatus.Ready]: 0, [RequestDataStatus.InProgress]: 0, [RequestDataStatus.Completed]: 0, [RequestDataStatus.Canceled]: 0, From 48661a92595c0849442b3909e1166bcdb0277dd0 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 22 Jan 2025 14:45:46 +0700 Subject: [PATCH 04/10] feat: add date time format function --- src/controllers/00-doc-template-controller.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/controllers/00-doc-template-controller.ts b/src/controllers/00-doc-template-controller.ts index b7d1a03..32822f4 100644 --- a/src/controllers/00-doc-template-controller.ts +++ b/src/controllers/00-doc-template-controller.ts @@ -8,6 +8,7 @@ import { notFoundError } from "../utils/error"; import HttpError from "../interfaces/http-error"; import HttpStatus from "../interfaces/http-status"; import { getFileBuffer, listFile } from "../utils/minio"; +import { dateFormat } from "../utils/datetime"; const quotationData = (id: string) => prisma.quotation.findFirst({ @@ -128,6 +129,23 @@ export class DocTemplateController extends Controller { template, data: record, additionalJsContext: { + date: (date: string, locale?: string) => dateFormat({ date, locale }), + dateTime: (date: string, locale?: string) => dateFormat({ date, withTime: true, locale }), + dateLong: (date: string, locale?: string) => + dateFormat({ date, locale, monthStyle: "long" }), + dateLongTime: (date: string, locale?: string) => + dateFormat({ date, withTime: true, locale, monthStyle: "long" }), + dateTH: (date: string) => dateFormat({ date, locale: "th-TH" }), + dateTimeTH: (date: string) => dateFormat({ date, withTime: true, locale: "th-TH" }), + dateLongTH: (date: string) => dateFormat({ date, locale: "th-TH", monthStyle: "long" }), + dateTimeLongTH: (date: string) => + dateFormat({ date, withTime: true, locale: "th-TH", monthStyle: "long" }), + dateEN: (date: string) => dateFormat({ date, locale: "en-US" }), + dateTimeEN: (date: string) => + dateFormat({ date, withTime: true, locale: "en-US", monthStyle: "long" }), + dateLongEN: (date: string) => dateFormat({ date, locale: "en-US" }), + dateTimeLongEN: (date: string) => + dateFormat({ date, withTime: true, locale: "en-US", monthStyle: "long" }), addressFull, addressFullTH: (addr: FullAddress) => addressFull(addr, "th"), addressFullEN: (addr: FullAddress) => addressFull(addr, "en"), From bd75a76ffd49bbf03afeb561242da1723ba21e68 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 22 Jan 2025 14:48:39 +0700 Subject: [PATCH 05/10] feat: add datetime utils --- src/utils/datetime.ts | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 src/utils/datetime.ts diff --git a/src/utils/datetime.ts b/src/utils/datetime.ts new file mode 100644 index 0000000..81ed0bd --- /dev/null +++ b/src/utils/datetime.ts @@ -0,0 +1,41 @@ +export function dateFormat(opts: { + date: string | Date | null; + locale?: string; + dayStyle?: "numeric" | "2-digit"; + monthStyle?: "numeric" | "2-digit" | "long" | "short"; + timeStyle?: "full" | "long" | "medium" | "short"; + timeOnly?: boolean; + noDay?: boolean; + noMonth?: boolean; + noYear?: boolean; + withTime?: boolean; +}): string { + const dt = opts.date ? new Date(opts.date) : new Date(); + + if (opts.timeOnly) { + opts.noDay = true; + opts.noMonth = true; + opts.noYear = true; + opts.timeStyle = opts.timeStyle || "short"; + } + + let timeText = opts.withTime + ? " " + + dateFormat({ + date: opts.date, + timeOnly: true, + timeStyle: opts.timeStyle, + }) + : ""; + + if (timeText) opts.timeStyle = undefined; + + let formatted = new Intl.DateTimeFormat(opts.locale, { + day: opts.noDay ? undefined : opts.dayStyle || "numeric", + month: opts.noMonth ? undefined : opts.monthStyle || "short", + timeStyle: opts.timeStyle, + year: opts.noYear ? undefined : "numeric", + }).format(dt); + + return formatted + timeText; +} From b1c56b7c4e50c338da96eecbcba4ce1ac42c9ba8 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 22 Jan 2025 16:21:00 +0700 Subject: [PATCH 06/10] refactor: reorder request data --- 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 f90918a..4f7b652 100644 --- a/src/controllers/06-request-list-controller.ts +++ b/src/controllers/06-request-list-controller.ts @@ -189,6 +189,7 @@ export class RequestDataController extends Controller { }, take: pageSize, skip: (page - 1) * pageSize, + orderBy: { createdAt: "desc" }, }), prisma.requestData.count({ where }), ]); From 87a1c3fa802505c8b985adf7ce53820da174a057 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 22 Jan 2025 16:47:20 +0700 Subject: [PATCH 07/10] feat: update task order revert status if removed --- src/controllers/07-task-controller.ts | 89 +++++++++++++++++++-------- 1 file changed, 63 insertions(+), 26 deletions(-) diff --git a/src/controllers/07-task-controller.ts b/src/controllers/07-task-controller.ts index 77a593b..111b828 100644 --- a/src/controllers/07-task-controller.ts +++ b/src/controllers/07-task-controller.ts @@ -381,43 +381,71 @@ export class TaskController extends Controller { await permissionCheckCompany(req.user, record.registeredBranch); - await prisma.taskOrder.update({ - where: { id: taskOrderId }, - include: { - taskList: { - include: { - requestWorkStep: { - include: { - requestWork: true, - }, - }, - }, - }, - institution: true, - registeredBranch: true, - createdBy: true, - }, - data: { - ...body, - taskList: { - deleteMany: record?.taskList.filter( + if (record.taskList.some((v) => v.taskStatus !== "Pending")) { + throw new HttpError( + HttpStatus.BAD_REQUEST, + "One or more task is not pending", + "taskListNotPending", + ); + } + + return await prisma.$transaction(async (tx) => { + await Promise.all( + record.taskList + .filter( (lhs) => !body.taskList.find( (rhs) => lhs.requestWorkId === rhs.requestWorkId && lhs.step === rhs.step, ), + ) + .map((v) => + tx.task.update({ + where: { id: v.id }, + data: { + requestWorkStep: { update: { workStatus: "Pending" } }, + }, + }), ), - createMany: { - data: body.taskList.filter( + ); + + return await tx.taskOrder.update({ + where: { id: taskOrderId }, + include: { + taskList: { + include: { + requestWorkStep: { + include: { + requestWork: true, + }, + }, + }, + }, + institution: true, + registeredBranch: true, + createdBy: true, + }, + data: { + ...body, + taskList: { + deleteMany: record?.taskList.filter( (lhs) => - !record?.taskList.find( + !body.taskList.find( (rhs) => lhs.requestWorkId === rhs.requestWorkId && lhs.step === rhs.step, ), ), - skipDuplicates: true, + createMany: { + data: body.taskList.filter( + (lhs) => + !record?.taskList.find( + (rhs) => lhs.requestWorkId === rhs.requestWorkId && lhs.step === rhs.step, + ), + ), + skipDuplicates: true, + }, }, + taskProduct: { deleteMany: {}, create: body.taskProduct }, }, - taskProduct: { deleteMany: {}, create: body.taskProduct }, - }, + }); }); } @@ -431,6 +459,7 @@ export class TaskController extends Controller { registeredBranch: { include: branchRelationPermInclude(req.user), }, + taskList: true, }, }); @@ -438,6 +467,14 @@ export class TaskController extends Controller { await permissionCheck(req.user, record.registeredBranch); + if (record.taskList.some((v) => v.taskStatus !== "Pending")) { + throw new HttpError( + HttpStatus.BAD_REQUEST, + "One or more task is not pending", + "taskListNotPending", + ); + } + await Promise.all([deleteFolder(fileLocation.task.attachment(taskOrderId))]); await tx.taskOrder.delete({ where: { id: taskOrderId } }); }); From 4031a8121c8379b176d0cbea5ae22315c92b71d1 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 22 Jan 2025 16:50:47 +0700 Subject: [PATCH 08/10] fix: wrong status revert --- src/controllers/07-task-controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/07-task-controller.ts b/src/controllers/07-task-controller.ts index 111b828..6a91faa 100644 --- a/src/controllers/07-task-controller.ts +++ b/src/controllers/07-task-controller.ts @@ -402,7 +402,7 @@ export class TaskController extends Controller { tx.task.update({ where: { id: v.id }, data: { - requestWorkStep: { update: { workStatus: "Pending" } }, + requestWorkStep: { update: { workStatus: "Ready" } }, }, }), ), From ee53b8cbc463af9d97b81e21a91a3e0974c20eaf Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Thu, 23 Jan 2025 10:09:45 +0700 Subject: [PATCH 09/10] feat: add doc template function address without area --- src/controllers/00-doc-template-controller.ts | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/controllers/00-doc-template-controller.ts b/src/controllers/00-doc-template-controller.ts index 32822f4..1726bf0 100644 --- a/src/controllers/00-doc-template-controller.ts +++ b/src/controllers/00-doc-template-controller.ts @@ -146,6 +146,9 @@ export class DocTemplateController extends Controller { dateLongEN: (date: string) => dateFormat({ date, locale: "en-US" }), dateTimeLongEN: (date: string) => dateFormat({ date, withTime: true, locale: "en-US", monthStyle: "long" }), + address, + addressTH: (addr: FullAddress) => address(addr, "th"), + addressEN: (addr: FullAddress) => address(addr, "en"), addressFull, addressFullTH: (addr: FullAddress) => addressFull(addr, "th"), addressFullEN: (addr: FullAddress) => addressFull(addr, "en"), @@ -193,6 +196,26 @@ type FullAddress = { en?: boolean; }; +function address(addr: FullAddress, lang: "th" | "en" = "en") { + let fragments: string[]; + switch (lang) { + case "th": + fragments = [`${addr.address},`]; + if (addr.moo) fragments.push(`หมู่ ${addr.moo},`); + if (addr.soi) fragments.push(`ซอย ${addr.soi},`); + if (addr.street) fragments.push(`ถนน${addr.street},`); + break; + default: + fragments = [`${addr.addressEN},`]; + if (addr.mooEN) fragments.push(`Moo ${addr.mooEN},`); + if (addr.soiEN) fragments.push(`Soi ${addr.soiEN},`); + if (addr.streetEN) fragments.push(`${addr.streetEN} Rd.`); + break; + } + + return fragments.join(" "); +} + function addressFull(addr: FullAddress, lang: "th" | "en" = "en") { let fragments: string[]; switch (lang) { From bb0d14ce92459bb921acb6743624ff27140f941e Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Thu, 23 Jan 2025 10:21:10 +0700 Subject: [PATCH 10/10] fix: .01 wrong price --- src/controllers/05-quotation-controller.ts | 29 +++++++++++++--------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/controllers/05-quotation-controller.ts b/src/controllers/05-quotation-controller.ts index be18cc8..866b93a 100644 --- a/src/controllers/05-quotation-controller.ts +++ b/src/controllers/05-quotation-controller.ts @@ -477,13 +477,15 @@ export class QuotationController 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 originalPrice = body.agentPrice ? p.agentPrice : p.price; + const finalPriceWithVat = precisionRound( + originalPrice + (p.vatIncluded ? 0 : originalPrice * 1.07), + ); + + const price = finalPriceWithVat; 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) - : 0; + const vat = p.calcVat ? (pricePerUnit * v.amount - (v.discount || 0)) * VAT_DEFAULT : 0; return { order: i + 1, @@ -742,13 +744,16 @@ export class QuotationController extends Controller { const list = body.productServiceList?.map((v, i) => { const p = product.find((p) => p.id === v.productId)!; - const price = record.agentPrice ? p.agentPrice : p.price; + + const originalPrice = record.agentPrice ? p.agentPrice : p.price; + const finalPriceWithVat = precisionRound( + originalPrice + (p.vatIncluded ? 0 : originalPrice * 1.07), + ); + + const price = finalPriceWithVat; 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) - : 0; + const vat = p.calcVat ? (pricePerUnit * v.amount - (v.discount || 0)) * VAT_DEFAULT : 0; + return { order: i + 1, productId: v.productId,