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 01/24] 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 02/24] 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 03/24] 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 04/24] 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 05/24] 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 06/24] 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 07/24] 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 08/24] 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 09/24] 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 10/24] 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 11/24] 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 12/24] 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 13/24] 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 14/24] 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 15/24] 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 16/24] 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 17/24] 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 18/24] 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 19/24] 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 20/24] 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 21/24] 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 22/24] 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 23/24] 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 24/24] 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.");
}