feat: add request work support
All checks were successful
Spell Check / Spell Check with Typos (push) Successful in 5s

This commit is contained in:
Methapon2001 2025-03-25 13:48:31 +07:00
parent c5d250ab0c
commit c6c187b8d3
3 changed files with 127 additions and 4 deletions

View file

@ -20,6 +20,7 @@
"author": "Frappe'T", "author": "Frappe'T",
"license": "ISC", "license": "ISC",
"devDependencies": { "devDependencies": {
"@types/barcode": "^0.0.33",
"@types/cors": "^2.8.17", "@types/cors": "^2.8.17",
"@types/express": "^4.17.21", "@types/express": "^4.17.21",
"@types/morgan": "^1.9.9", "@types/morgan": "^1.9.9",
@ -38,6 +39,7 @@
"@prisma/client": "^6.3.0", "@prisma/client": "^6.3.0",
"@scalar/express-api-reference": "^0.4.182", "@scalar/express-api-reference": "^0.4.182",
"@tsoa/runtime": "^6.6.0", "@tsoa/runtime": "^6.6.0",
"barcode": "^0.1.0",
"cors": "^2.8.5", "cors": "^2.8.5",
"cron": "^3.3.1", "cron": "^3.3.1",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",

68
pnpm-lock.yaml generated
View file

@ -23,6 +23,9 @@ importers:
'@tsoa/runtime': '@tsoa/runtime':
specifier: ^6.6.0 specifier: ^6.6.0
version: 6.6.0 version: 6.6.0
barcode:
specifier: ^0.1.0
version: 0.1.0
cors: cors:
specifier: ^2.8.5 specifier: ^2.8.5
version: 2.8.5 version: 2.8.5
@ -84,6 +87,9 @@ importers:
specifier: ^0.19.0 specifier: ^0.19.0
version: 0.19.0 version: 0.19.0
devDependencies: devDependencies:
'@types/barcode':
specifier: ^0.0.33
version: 0.0.33
'@types/cors': '@types/cors':
specifier: ^2.8.17 specifier: ^2.8.17
version: 2.8.17 version: 2.8.17
@ -420,6 +426,9 @@ packages:
'@types/accepts@1.3.7': '@types/accepts@1.3.7':
resolution: {integrity: sha512-Pay9fq2lM2wXPWbteBsRAGiWH2hig4ZE2asK+mm7kUzlxRTfL961rj89I6zV/E3PcIkDqyuBEcMxFT7rccugeQ==} resolution: {integrity: sha512-Pay9fq2lM2wXPWbteBsRAGiWH2hig4ZE2asK+mm7kUzlxRTfL961rj89I6zV/E3PcIkDqyuBEcMxFT7rccugeQ==}
'@types/barcode@0.0.33':
resolution: {integrity: sha512-PotMNcya1L26CgWRfLNxNw0VIyhb104YhXB12e6xjoo/+hZaitrpRnToU9Ru0yTfty6tGc1Bk/1JNcGs9YVKXQ==}
'@types/body-parser@1.19.5': '@types/body-parser@1.19.5':
resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==} resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==}
@ -611,6 +620,12 @@ packages:
array-flatten@1.1.1: array-flatten@1.1.1:
resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==}
array-parallel@0.1.3:
resolution: {integrity: sha512-TDPTwSWW5E4oiFiKmz6RGJ/a80Y91GuLgUYuLd49+XBS75tYo8PNgaT2K/OxuQYqkoI852MDGBorg9OcUSTQ8w==}
array-series@0.1.5:
resolution: {integrity: sha512-L0XlBwfx9QetHOsbLDrE/vh2t018w9462HM3iaFfxRiK83aJjAt/Ja3NMkOW7FICwWTlQBa3ZbL5FKhuQWkDrg==}
array-union@2.1.0: array-union@2.1.0:
resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==}
engines: {node: '>=8'} engines: {node: '>=8'}
@ -657,6 +672,9 @@ packages:
balanced-match@1.0.2: balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
barcode@0.1.0:
resolution: {integrity: sha512-GslbXakjG61fwHnIN/vrUkPsa61WVAJDnb7jAwmbjRW5bZdwINymo1+FgjYJrGf2MDHxAt3bUWgmEMF8ETZxHQ==}
base64-js@1.5.1: base64-js@1.5.1:
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
@ -877,6 +895,14 @@ packages:
dayjs@1.11.13: dayjs@1.11.13:
resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==}
debug@0.7.0:
resolution: {integrity: sha512-UWZnvGiX9tQgtrsA+mhGLKnUFvr1moempl9IvqQKyFnEgN0T4kpCE+KJcqTLcVxQjRVRnLF3VSE1Hchki5N98g==}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
debug@2.6.9: debug@2.6.9:
resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
peerDependencies: peerDependencies:
@ -1232,6 +1258,11 @@ packages:
resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==}
engines: {node: '>=10'} engines: {node: '>=10'}
gm@1.16.0:
resolution: {integrity: sha512-b5oVGr8wCI7VNfjzeXiFocCZrcpkRUxSoVYfksBMEp/jo2nYwRKhcfOURarxFwjXyW1GvEY2EmmupVLnh0vXjg==}
engines: {node: '>= 0.8.0'}
deprecated: The gm module has been sunset. Please migrate to an alternative. https://github.com/aheckmann/gm?tab=readme-ov-file#2025-02-24-this-project-is-not-maintained
gopd@1.2.0: gopd@1.2.0:
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@ -2337,6 +2368,10 @@ packages:
stream-json@1.9.1: stream-json@1.9.1:
resolution: {integrity: sha512-uWkjJ+2Nt/LO9Z/JyKZbMusL8Dkh97uUBTv3AJQ74y07lVahLY4eEFsPsE97pxYBwr8nnjMAIch5eqI0gPShyw==} resolution: {integrity: sha512-uWkjJ+2Nt/LO9Z/JyKZbMusL8Dkh97uUBTv3AJQ74y07lVahLY4eEFsPsE97pxYBwr8nnjMAIch5eqI0gPShyw==}
stream-to-buffer@0.0.1:
resolution: {integrity: sha512-LsvisgE3iThboRqA+XLmtnY9ktPLVPOj3zZxXMhlezeCcAh0RhomquXJgB8H+lb/RR/pPcbNVGHVKFUwjpoRtw==}
engines: {node: '>= 0.8'}
strict-uri-encode@2.0.0: strict-uri-encode@2.0.0:
resolution: {integrity: sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==} resolution: {integrity: sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==}
engines: {node: '>=4'} engines: {node: '>=4'}
@ -2431,6 +2466,9 @@ packages:
through2@4.0.2: through2@4.0.2:
resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==} resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==}
through@2.3.8:
resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
tmp@0.2.1: tmp@0.2.1:
resolution: {integrity: sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==} resolution: {integrity: sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==}
engines: {node: '>=8.17.0'} engines: {node: '>=8.17.0'}
@ -3216,6 +3254,10 @@ snapshots:
dependencies: dependencies:
'@types/node': 20.17.10 '@types/node': 20.17.10
'@types/barcode@0.0.33':
dependencies:
'@types/node': 20.17.10
'@types/body-parser@1.19.5': '@types/body-parser@1.19.5':
dependencies: dependencies:
'@types/connect': 3.4.38 '@types/connect': 3.4.38
@ -3449,6 +3491,10 @@ snapshots:
array-flatten@1.1.1: {} array-flatten@1.1.1: {}
array-parallel@0.1.3: {}
array-series@0.1.5: {}
array-union@2.1.0: {} array-union@2.1.0: {}
array.prototype.map@1.0.7: array.prototype.map@1.0.7:
@ -3505,6 +3551,12 @@ snapshots:
balanced-match@1.0.2: {} balanced-match@1.0.2: {}
barcode@0.1.0:
dependencies:
gm: 1.16.0
transitivePeerDependencies:
- supports-color
base64-js@1.5.1: {} base64-js@1.5.1: {}
basic-auth@2.0.1: basic-auth@2.0.1:
@ -3777,6 +3829,8 @@ snapshots:
dayjs@1.11.13: {} dayjs@1.11.13: {}
debug@0.7.0: {}
debug@2.6.9: debug@2.6.9:
dependencies: dependencies:
ms: 2.0.0 ms: 2.0.0
@ -4287,6 +4341,16 @@ snapshots:
merge2: 1.4.1 merge2: 1.4.1
slash: 3.0.0 slash: 3.0.0
gm@1.16.0:
dependencies:
array-parallel: 0.1.3
array-series: 0.1.5
debug: 0.7.0
stream-to-buffer: 0.0.1
through: 2.3.8
transitivePeerDependencies:
- supports-color
gopd@1.2.0: {} gopd@1.2.0: {}
graceful-fs@4.2.11: {} graceful-fs@4.2.11: {}
@ -5412,6 +5476,8 @@ snapshots:
dependencies: dependencies:
stream-chain: 2.2.5 stream-chain: 2.2.5
stream-to-buffer@0.0.1: {}
strict-uri-encode@2.0.0: {} strict-uri-encode@2.0.0: {}
string-width@4.2.3: string-width@4.2.3:
@ -5524,6 +5590,8 @@ snapshots:
dependencies: dependencies:
readable-stream: 3.6.2 readable-stream: 3.6.2
through@2.3.8: {}
tmp@0.2.1: tmp@0.2.1:
dependencies: dependencies:
rimraf: 3.0.2 rimraf: 3.0.2

View file

@ -1,3 +1,4 @@
import barcode from "barcode";
import createReport from "docx-templates"; import createReport from "docx-templates";
import ThaiBahtText from "thai-baht-text"; import ThaiBahtText from "thai-baht-text";
import { District, Province, SubDistrict } from "@prisma/client"; import { District, Province, SubDistrict } from "@prisma/client";
@ -71,19 +72,52 @@ const quotationData = (id: string) =>
}, },
}); });
const requestWorkData = (id: string, step?: number) =>
prisma.requestWork.findFirst({
where: { id },
include: {
processByUser: true,
productService: {
include: {
product: true,
},
},
request: {
include: {
employee: {
include: {
subDistrict: true,
district: true,
province: true,
},
},
quotation: true,
},
},
stepStatus: {
where: { step },
},
},
});
@Route("api/v1/doc-template") @Route("api/v1/doc-template")
@Tags("Document Template") @Tags("Document Template")
export class DocTemplateController extends Controller { export class DocTemplateController extends Controller {
@Get() @Get()
async getTemplate() { async getTemplate(@Query() templateGroup?: string) {
if ( if (
process.env.DOCUMENT_TEMPLATE_PROVIDER && process.env.DOCUMENT_TEMPLATE_PROVIDER &&
process.env.DOCUMENT_TEMPLATE_PROVIDER === "edm-api" process.env.DOCUMENT_TEMPLATE_PROVIDER === "edm-api"
) { ) {
const ret = await edmList("file", DOCUMENT_PATH); const ret = await edmList(
"file",
templateGroup ? [templateGroup, ...DOCUMENT_PATH] : DOCUMENT_PATH,
);
if (ret) return ret.map((v) => v.fileName); if (ret) return ret.map((v) => v.fileName);
} }
return await listFile(DOCUMENT_PATH.join("/") + "/"); return await listFile(
(templateGroup ? [templateGroup, ...DOCUMENT_PATH] : DOCUMENT_PATH).join("/") + "/",
);
} }
@Get("{documentTemplate}") @Get("{documentTemplate}")
@ -92,6 +126,7 @@ export class DocTemplateController extends Controller {
@Query() data: string, @Query() data: string,
@Query() dataId: string, @Query() dataId: string,
@Query() dataOnly?: boolean, @Query() dataOnly?: boolean,
@Query() templateGroup?: string,
): Promise<Readable | Record<string, any>> { ): Promise<Readable | Record<string, any>> {
let record: Record<string, any>; let record: Record<string, any>;
@ -132,12 +167,19 @@ export class DocTemplateController extends Controller {
}), }),
); );
break; break;
case "request-work":
record = await requestWorkData(dataId).then((requestWork) => ({
request: replaceEmptyField(requestWork?.request),
requestWork: replaceEmptyField(requestWork),
employee: requestWork?.request.employee,
}));
default: default:
throw new HttpError(HttpStatus.BAD_REQUEST, "No data for template", "noDataTemplate"); throw new HttpError(HttpStatus.BAD_REQUEST, "No data for template", "noDataTemplate");
} }
if (!data) throw notFoundError("Data"); if (!data) throw notFoundError("Data");
if (dataOnly) return record; if (dataOnly) return record;
if (templateGroup) documentTemplate = templateGroup + "/" + documentTemplate;
let template: Buffer<ArrayBufferLike> | null = null; let template: Buffer<ArrayBufferLike> | null = null;
@ -210,6 +252,13 @@ export class DocTemplateController extends Controller {
thaiBahtText: (input: string | number) => { thaiBahtText: (input: string | number) => {
ThaiBahtText(typeof input === "string" ? input.replaceAll(",", "") : input); ThaiBahtText(typeof input === "string" ? input.replaceAll(",", "") : input);
}, },
barcode: async (data: string) =>
new Promise<string>((resolve, reject) =>
barcode("code39", { data, width: 400, height: 100 }).getBase64((err, data) => {
if (!err) return resolve(data);
return reject(err);
}),
),
}, },
}).then(Buffer.from); }).then(Buffer.from);
@ -218,7 +267,11 @@ export class DocTemplateController extends Controller {
} }
function replaceEmptyField<T>(data: T): T { function replaceEmptyField<T>(data: T): T {
return JSON.parse(JSON.stringify(data).replace(/null|\"\"/g, '"\-"')); try {
return JSON.parse(JSON.stringify(data).replace(/null|\"\"/g, '"\-"'));
} catch (e) {
return data;
}
} }
type FullAddress = { type FullAddress = {