From 4e3c51d84b228f9a740da30749b1d14d8d8c5c5e Mon Sep 17 00:00:00 2001 From: Methapon Metanipat <162551568+Methapon-Frappet@users.noreply.github.com> Date: Wed, 22 Jan 2025 11:27:47 +0700 Subject: [PATCH] feat: doc template (#10) * feat: doc-template * fix: empty not converted to dash * feat: also return province, district and sub district * refactor: move some relation to outer * feat: add more function * chore: deps * feat: add more relation * feat: count employee by gender * feat: count all employee * feat: add more function * feat: get employment office * fix: error --------- Co-authored-by: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> --- package.json | 3 + pnpm-lock.yaml | 113 +++++- src/controllers/00-doc-template-controller.ts | 348 ++++++++++++++++++ src/utils/minio.ts | 5 + 4 files changed, 456 insertions(+), 13 deletions(-) create mode 100644 src/controllers/00-doc-template-controller.ts diff --git a/package.json b/package.json index 8d647b9..3f524a2 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "@types/morgan": "^1.9.9", "cors": "^2.8.5", "cron": "^3.3.1", + "docx-templates": "^4.13.0", "dotenv": "^16.4.7", "express": "^4.21.2", "fast-jwt": "^4.0.6", @@ -46,6 +47,8 @@ "morgan": "^1.10.0", "prisma-extension-kysely": "^3.0.0", "promise.any": "^2.0.6", + "thai-baht-text": "^2.0.5", + "to-words": "^4.2.0", "tsoa": "^6.6.0", "winston": "^3.17.0", "winston-elasticsearch": "^0.19.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 95f2fa6..b13e4c8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32,6 +32,9 @@ importers: cron: specifier: ^3.3.1 version: 3.3.1 + docx-templates: + specifier: ^4.13.0 + version: 4.13.0 dotenv: specifier: ^16.4.7 version: 16.4.7 @@ -56,6 +59,12 @@ importers: promise.any: specifier: ^2.0.6 version: 2.0.6 + thai-baht-text: + specifier: ^2.0.5 + version: 2.0.5 + to-words: + specifier: ^4.2.0 + version: 4.2.0 tsoa: specifier: ^6.6.0 version: 6.6.0 @@ -86,7 +95,7 @@ importers: version: 6.2.1 prisma-kysely: specifier: ^1.8.0 - version: 1.8.0 + version: 1.8.0(encoding@0.1.13) ts-node: specifier: ^10.9.2 version: 10.9.2(@types/node@20.17.10)(typescript@5.7.2) @@ -902,6 +911,10 @@ packages: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} + docx-templates@4.13.0: + resolution: {integrity: sha512-tTmR3WhROYctuyVReQ+PfCU3zprmC45/VuSVzn8EjovzpRkXYUdXiDatB9M8pasj0V+wuuOyY8bcSHvlQ2GNag==} + engines: {node: '>=6'} + dotenv@16.0.3: resolution: {integrity: sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==} engines: {node: '>=12'} @@ -944,6 +957,9 @@ packages: resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} engines: {node: '>= 0.8'} + encoding@0.1.13: + resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==} + end-of-stream@1.4.4: resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} @@ -1267,6 +1283,10 @@ packages: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} @@ -1281,6 +1301,9 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} + immediate@3.0.6: + resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} + import-in-the-middle@1.4.2: resolution: {integrity: sha512-9WOz1Yh/cvO/p69sxRmhyQwrIGGSp7EIdcb+fFNVi7CzQGQB8U1/1XrKVSbEd/GNOAeM0peJtmi7+qphe7NvAw==} @@ -1502,6 +1525,9 @@ packages: jsonfile@6.1.0: resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} + jszip@3.10.1: + resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==} + kleur@3.0.3: resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} engines: {node: '>=6'} @@ -1521,6 +1547,9 @@ packages: resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==} engines: {node: '>= 0.6.3'} + lie@3.3.0: + resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} + lilconfig@2.1.0: resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} engines: {node: '>=10'} @@ -1884,6 +1913,9 @@ packages: package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + pako@1.0.11: + resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} + parse-json@5.2.0: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} @@ -2112,6 +2144,9 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + sax@1.3.0: + resolution: {integrity: sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==} + sax@1.4.1: resolution: {integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==} @@ -2147,6 +2182,9 @@ packages: resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} engines: {node: '>= 0.4'} + setimmediate@1.0.5: + resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} + setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} @@ -2343,6 +2381,9 @@ packages: text-hex@1.0.0: resolution: {integrity: sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==} + thai-baht-text@2.0.5: + resolution: {integrity: sha512-xTb1marcZhO7PFm2mBPKDJgtMg3crT8uCNHZIbPaV+B/R9LNWS1wBPg4DULtSgZSllsdmEObPzefxOeKAb6X+Q==} + through2@4.0.2: resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==} @@ -2357,6 +2398,10 @@ packages: to-source-code@1.0.2: resolution: {integrity: sha512-YzWtjmNIf3E75eZYa7m1SCyl0vgOGoTzdpH3svfa8SUm5rqTgl9hnDolrAGOghCF9P2gsITXQoMrlujOoz+RPw==} + to-words@4.2.0: + resolution: {integrity: sha512-KY2WEyu1ZVQ9h44Ac3w/E3i59ne873opFQZ8PXU3L7PWzsl8IjgQvSyogCqhCz+FFRCYEFJ1ERAeSB1Mu5sNjw==} + engines: {node: '>=12.0.0'} + toidentifier@1.0.1: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} @@ -2963,7 +3008,7 @@ snapshots: '@prisma/fetch-engine': 6.2.1 '@prisma/get-platform': 6.2.1 - '@prisma/fetch-engine@5.3.1': + '@prisma/fetch-engine@5.3.1(encoding@0.1.13)': dependencies: '@prisma/debug': 5.3.1 '@prisma/get-platform': 5.3.1 @@ -2974,7 +3019,7 @@ snapshots: http-proxy-agent: 7.0.0 https-proxy-agent: 7.0.2 kleur: 4.1.5 - node-fetch: 2.7.0 + node-fetch: 2.7.0(encoding@0.1.13) p-filter: 2.1.0 p-map: 4.0.0 p-retry: 4.6.2 @@ -3020,19 +3065,19 @@ snapshots: dependencies: '@prisma/debug': 6.2.1 - '@prisma/internals@5.3.1': + '@prisma/internals@5.3.1(encoding@0.1.13)': dependencies: '@antfu/ni': 0.21.8 '@opentelemetry/api': 1.4.1 '@prisma/debug': 5.3.1 '@prisma/engines': 5.3.1 - '@prisma/fetch-engine': 5.3.1 + '@prisma/fetch-engine': 5.3.1(encoding@0.1.13) '@prisma/generator-helper': 5.3.1 '@prisma/get-platform': 5.3.1 '@prisma/prisma-schema-wasm': 5.3.1-2.61e140623197a131c2a6189271ffee05a7aa9a59 archiver: 5.3.2 arg: 5.0.2 - checkpoint-client: 1.1.27 + checkpoint-client: 1.1.27(encoding@0.1.13) cli-truncate: 2.1.0 dotenv: 16.0.3 escape-string-regexp: 4.0.0 @@ -3048,7 +3093,7 @@ snapshots: is-wsl: 2.2.0 kleur: 4.1.5 new-github-issue-url: 0.2.1 - node-fetch: 2.7.0 + node-fetch: 2.7.0(encoding@0.1.13) npm-packlist: 5.1.3 open: 7.4.2 p-map: 4.0.0 @@ -3508,13 +3553,13 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 - checkpoint-client@1.1.27: + checkpoint-client@1.1.27(encoding@0.1.13): dependencies: ci-info: 3.8.0 env-paths: 2.2.1 make-dir: 4.0.0 ms: 2.1.3 - node-fetch: 2.6.12 + node-fetch: 2.6.12(encoding@0.1.13) uuid: 9.0.0 transitivePeerDependencies: - encoding @@ -3730,6 +3775,11 @@ snapshots: dependencies: path-type: 4.0.0 + docx-templates@4.13.0: + dependencies: + jszip: 3.10.1 + sax: 1.3.0 + dotenv@16.0.3: {} dotenv@16.4.7: {} @@ -3802,6 +3852,11 @@ snapshots: encodeurl@2.0.0: {} + encoding@0.1.13: + dependencies: + iconv-lite: 0.6.3 + optional: true + end-of-stream@1.4.4: dependencies: once: 1.4.0 @@ -4263,6 +4318,11 @@ snapshots: dependencies: safer-buffer: 2.1.2 + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + optional: true + ieee754@1.2.1: {} ignore-by-default@1.0.1: {} @@ -4273,6 +4333,8 @@ snapshots: ignore@5.3.2: {} + immediate@3.0.6: {} + import-in-the-middle@1.4.2: dependencies: acorn: 8.14.0 @@ -4480,6 +4542,13 @@ snapshots: optionalDependencies: graceful-fs: 4.2.11 + jszip@3.10.1: + dependencies: + lie: 3.3.0 + pako: 1.0.11 + readable-stream: 2.3.8 + setimmediate: 1.0.5 + kleur@3.0.3: {} kleur@4.1.5: {} @@ -4492,6 +4561,10 @@ snapshots: dependencies: readable-stream: 2.3.8 + lie@3.3.0: + dependencies: + immediate: 3.0.6 + lilconfig@2.1.0: {} lines-and-columns@1.2.4: {} @@ -4691,13 +4764,17 @@ snapshots: next-line@1.1.0: optional: true - node-fetch@2.6.12: + node-fetch@2.6.12(encoding@0.1.13): dependencies: whatwg-url: 5.0.0 + optionalDependencies: + encoding: 0.1.13 - node-fetch@2.7.0: + node-fetch@2.7.0(encoding@0.1.13): dependencies: whatwg-url: 5.0.0 + optionalDependencies: + encoding: 0.1.13 nodemon@3.1.9: dependencies: @@ -4838,6 +4915,8 @@ snapshots: package-json-from-dist@1.0.1: {} + pako@1.0.11: {} + parse-json@5.2.0: dependencies: '@babel/code-frame': 7.26.2 @@ -4894,11 +4973,11 @@ snapshots: dependencies: '@prisma/client': 6.2.1(prisma@6.2.1) - prisma-kysely@1.8.0: + prisma-kysely@1.8.0(encoding@0.1.13): dependencies: '@mrleebo/prisma-ast': 0.7.0 '@prisma/generator-helper': 5.3.1 - '@prisma/internals': 5.3.1 + '@prisma/internals': 5.3.1(encoding@0.1.13) typescript: 5.7.2 zod: 3.24.1 transitivePeerDependencies: @@ -5097,6 +5176,8 @@ snapshots: safer-buffer@2.1.2: {} + sax@1.3.0: {} + sax@1.4.1: {} secure-json-parse@2.7.0: {} @@ -5150,6 +5231,8 @@ snapshots: functions-have-names: 1.2.3 has-property-descriptors: 1.0.2 + setimmediate@1.0.5: {} + setprototypeof@1.2.0: {} shallow-clone-shim@2.0.0: @@ -5372,6 +5455,8 @@ snapshots: text-hex@1.0.0: {} + thai-baht-text@2.0.5: {} + through2@4.0.2: dependencies: readable-stream: 3.6.2 @@ -5389,6 +5474,8 @@ snapshots: is-nil: 1.0.1 optional: true + to-words@4.2.0: {} + toidentifier@1.0.1: {} touch@3.1.1: {} diff --git a/src/controllers/00-doc-template-controller.ts b/src/controllers/00-doc-template-controller.ts new file mode 100644 index 0000000..b7d1a03 --- /dev/null +++ b/src/controllers/00-doc-template-controller.ts @@ -0,0 +1,348 @@ +import createReport from "docx-templates"; +import ThaiBahtText from "thai-baht-text"; +import { District, Province, SubDistrict } from "@prisma/client"; +import { Readable } from "node:stream"; +import { Controller, Get, Path, Query, Route } from "tsoa"; +import prisma from "../db"; +import { notFoundError } from "../utils/error"; +import HttpError from "../interfaces/http-error"; +import HttpStatus from "../interfaces/http-status"; +import { getFileBuffer, listFile } from "../utils/minio"; + +const quotationData = (id: string) => + prisma.quotation.findFirst({ + where: { id, isDebitNote: false }, + include: { + registeredBranch: { + include: { + province: true, + district: true, + subDistrict: true, + headOffice: { + include: { + province: true, + district: true, + subDistrict: true, + }, + }, + }, + }, + customerBranch: { + include: { + customer: true, + province: true, + district: true, + subDistrict: true, + }, + }, + worker: { + include: { + employee: { + include: { + province: true, + district: true, + subDistrict: true, + employeePassport: { + orderBy: { expireDate: "desc" }, + }, + employeeWork: true, + }, + }, + }, + }, + productServiceList: { + include: { + work: true, + product: true, + service: true, + }, + }, + }, + }); + +@Route("api/v1/doc-template") +export class DocTemplateController extends Controller { + @Get() + async getTemplate() { + return await listFile(`doc-template/`); + } + + @Get("{documentTemplate}") + async getDocument( + @Path() documentTemplate: string, + @Query() data: string, + @Query() dataId: string, + @Query() dataOnly?: boolean, + ): Promise> { + let record: Record; + + switch (data) { + case "quotation": + record = await quotationData(dataId).then(async (quotation) => + replaceEmptyField({ + quotation, + customerBranch: quotation?.customerBranch, + registeredBranch: quotation?.registeredBranch, + employee: quotation?.worker.map((item) => item.employee), + employeeCount: { + all: quotation?.worker.length, + male: quotation?.worker.filter((item) => item.employee.gender === "male").length, + female: quotation?.worker.filter((item) => item.employee.gender === "female").length, + }, + employmentOffice: + quotation && quotation.customerBranch.districtId + ? await prisma.employmentOffice.findFirst({ + where: { + OR: [ + { + province: { + district: { some: { id: quotation.customerBranch.districtId } }, + }, + district: { none: {} }, + }, + { + district: { + some: { districtId: quotation.customerBranch.districtId }, + }, + }, + ], + }, + orderBy: [{ provinceId: "asc" }, { id: "asc" }], + }) + : undefined, + }), + ); + break; + default: + throw new HttpError(HttpStatus.BAD_REQUEST, "No data for template", "noDataTemplate"); + } + + if (!data) throw notFoundError("Data"); + if (dataOnly) return record; + + const template = await getFileBuffer(`doc-template/${documentTemplate}`); + + if (!data) Readable.from(template); + + const report = await createReport({ + template, + data: record, + additionalJsContext: { + addressFull, + addressFullTH: (addr: FullAddress) => addressFull(addr, "th"), + addressFullEN: (addr: FullAddress) => addressFull(addr, "en"), + gender, + genderTH: (text: string) => gender(text, "th"), + genderEN: (text: string) => gender(text, "en"), + businessType, + businessTypeEN: (text: string) => businessType(text, "en"), + businessTypeTH: (text: string) => businessType(text, "th"), + namePrefix, + namePrefixEN: (text: string) => namePrefix(text, "en"), + namePrefixTH: (text: string) => namePrefix(text, "th"), + jobPosition, + jobPositionEN: (text: string) => jobPosition(text, "en"), + jobPositionTH: (text: string) => jobPosition(text, "th"), + nationality, + nationalityEN: (text: string) => nationality(text, "en"), + nationalityTH: (text: string) => nationality(text, "th"), + thaiBahtText: (input: string | number) => { + ThaiBahtText(typeof input === "string" ? input.replaceAll(",", "") : input); + }, + }, + }).then(Buffer.from); + + return Readable.from(report); + } +} + +function replaceEmptyField(data: T): T { + return JSON.parse(JSON.stringify(data).replace(/null|\"\"/g, '"\-"')); +} + +type FullAddress = { + address: string; + addressEN: string; + moo?: string; + mooEN?: string; + soi?: string; + soiEN?: string; + street?: string; + streetEN?: string; + province?: Province | null; + district?: District | null; + subDistrict?: SubDistrict | null; + en?: boolean; +}; + +function addressFull(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},`); + + if (addr.subDistrict) { + fragments.push(`${addr.province?.id === "10" ? "แขวง" : "ตำบล"}${addr.subDistrict.name},`); + } + if (addr.district) { + fragments.push(`${addr.province?.id === "10" ? "เขต" : "อำเภอ"}${addr.district.name},`); + } + if (addr.province) fragments.push(`จังหวัด${addr.province.name},`); + + 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.`); + + if (addr.subDistrict) { + fragments.push(`${addr.subDistrict.nameEN} sub-district,`); + } + if (addr.district) fragments.push(`${addr.district.nameEN} district,`); + if (addr.province) fragments.push(`${addr.province.nameEN},`); + break; + } + + return fragments.join(" "); +} + +function gender(text: string, lang: "th" | "en" = "en") { + switch (lang) { + case "th": + return { male: "ชาย", female: "หญิง" }[text] || text; + default: + text.charAt(0).toUpperCase() + text.slice(1); + } +} + +function businessType(text: string, lang: "th" | "en" = "en") { + switch (lang) { + case "th": + return ( + { + ["fisheries"]: "ประมง", + ["continuous-fisheries"]: "ต่อเนื่องประมงทะเล", + ["agriculture"]: "เกษตรและปศุสัตว์", + ["construction"]: "กิจการก่อสร้าง", + ["domesticHelper"]: "ผู้รับใช้ในบ้าน", + ["continuousAgriculture"]: "กิจการต่อเนื่องการเกษตร", + ["continuousButchery"]: "ต่อเนื่องปศุสัตว์โรงฆ่าสัตว์ ชำแหละ", + ["recycling"]: "กิจการรีไซเคิล", + ["mining"]: "เหมืองแร่/เหมืองหิน", + ["metal"]: "จำหน่ายผลิตภัณฑ์โลหะ", + ["food"]: "จำหน่ายอาหารและเครื่องดื่ม", + ["soilBasedProducts"]: "ผลิตหรือจำหน่ายผลิตภัณฑ์จากดิน", + ["constructionMaterials"]: "ผลิตหรือจำหน่ายวัสดุก่อสร้าง", + ["stone"]: "แปรรูปหิน", + ["cloth"]: "ผลิตหรือจำหน่ายเสื้อผ้าสำเร็จรูป", + ["plastic"]: "ผลิตหรือจำหน่ายผลิตภัณฑ์พลาสติก", + ["paper"]: "ผลิตหรือจำหน่ายผลิตภัณฑ์กระดาษ", + ["electronics"]: "ผลิตหรือจำหน่ายผลิตภัณฑ์อิเล็กทรอนิกส์", + ["transport"]: "ขนถ่ายสินค้าทางบก น้ำ คลังสินค้า", + ["market"]: "ค้าส่ง ค้าปลีก แผงลอย", + ["car"]: "อู่ซ่อมรถ ล้าง อัดฉีด", + ["fuel"]: "สถานีบริการน้ำมัน แก้ส เชื้อเพลิง", + ["institution"]: "สถานศึกษา มูลนิธิ สมาคม สถานพยาบาล", + ["service"]: "การให้บริการต่างๆ", + ["coordinator"]: "งานผู้ประสานงานด้านภาษากัมพูชา ลาว หรือเมียนมา", + ["seafood"]: "แปรรูปสัตว์น้ำ", + }[text] || text + ); + default: + return ( + { + ["fisheries"]: "Fisheries", + ["continuous-fisheries"]: "Continuous fisheries", + ["agriculture"]: "Agriculture and livestock", + ["construction"]: "Construction business", + ["domesticHelper"]: "Domestic helper", + ["continuousAgriculture"]: "Continuous agricultural operation", + ["continuousButchery"]: "Continuous livestock slaughter and processing ", + ["recycling"]: "Recycling business", + ["mining"]: "Mining/quarry", + ["metal"]: "Metal products distribution", + ["food"]: "Food and beverage distribution", + ["soilBasedProducts"]: "Manufacture or sell products made from soil", + ["constructionMaterials"]: "Manufacture or sell construction materials", + ["stone"]: "Stone processing", + ["cloth"]: "Manufacture or sell ready-to-wear clothing", + ["plastic"]: "Manufacture or sell plastic products", + ["paper"]: "Manufacture or sell paper products", + ["electronics"]: "Manufacture or sell electronic products", + ["transport"]: "Transport goods by land, water, and operate warehouses", + ["market"]: "Wholesale, retail, floating panels", + ["car"]: "Auto repair shop, car wash, and detailing", + ["fuel"]: "Gas station, fuel station, and service station", + ["institution"]: "Educational institution, foundation, association, hospital", + ["service"]: "Various services", + ["coordinator"]: "Coordinator for Khmer, Laos, or Myanmar language services", + ["seafood"]: "Processing seafood", + }[text] || text + ); + } +} + +function namePrefix(text: string, lang: "th" | "en" = "en") { + switch (lang) { + case "th": + return { mr: "นาย", mrs: "นาง", miss: "นางสาว" }[text] || text; + default: + text.charAt(0).toUpperCase() + text.slice(1); + } +} + +function nationality(text: string, lang: "th" | "en" = "en") { + switch (lang) { + case "th": + return ( + { + ["THA"]: "ไทย", + ["MMR"]: "เมียนมา", + ["LAO"]: "ลาว", + ["KHM"]: "กัมพูชา", + ["VNM"]: "เวียดนาม", + ["PHL"]: "ฟิลิปปินส์", + ["CHN"]: "จีน", + }[text] || text + ); + default: + return ( + { + ["THA"]: "Thai", + ["MMR"]: "Myanmar", + ["LAO"]: "Laos", + ["KHM"]: "Khmer", + ["VNM"]: "Vietnam", + ["PHL"]: "Philippines", + ["CHN"]: "China", + }[text] || text + ); + } +} + +function jobPosition(text: string, lang: "th" | "en" = "en") { + switch (lang) { + case "th": + return ( + { + ["labourer"]: "กรรมกร", + ["boatsMechanic"]: "ช่างเครื่องยนต์ในเรือประมงทะเล", + ["domesticHelper"]: "ผู้รับใช้ในบ้าน", + ["coordinator"]: "งานผู้ประสานงานด้านภาษากัมพูชา ลาว หรือเมียนมา", + }[text] || text + ); + default: + return ( + { + labourer: "Labourer", + boatsMechanic: "Marine engine mechanic on fishing boats", + domesticHelper: "Domestic helper", + coordinator: "Coordinator for Khmer, Laos, or Myanmar language services", + }[text] || text + ); + } +} diff --git a/src/utils/minio.ts b/src/utils/minio.ts index f5f37e6..ba02cac 100644 --- a/src/utils/minio.ts +++ b/src/utils/minio.ts @@ -1,3 +1,4 @@ +import { buffer } from "node:stream/consumers"; import minio from "../services/minio"; if (!process.env.MINIO_BUCKET) { @@ -26,6 +27,10 @@ export async function getFile(path: string, exp = 60 * 60) { return await minio.presignedGetObject(MINIO_BUCKET, path, exp); } +export async function getFileBuffer(path: string) { + return await minio.getObject(MINIO_BUCKET, path).then(buffer); +} + export async function setFile(path: string, exp = 6 * 60 * 60) { return await minio.presignedPutObject(MINIO_BUCKET, path, exp); }