Merge branch 'develop'

This commit is contained in:
Methapon2001 2025-04-25 17:30:16 +07:00
commit 8a3a9e7eb3
42 changed files with 1295 additions and 226 deletions

View file

@ -24,6 +24,7 @@
"@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",
"@types/multer": "^1.4.12",
"@types/node": "^20.17.10", "@types/node": "^20.17.10",
"@types/nodemailer": "^6.4.17", "@types/nodemailer": "^6.4.17",
"nodemon": "^3.1.9", "nodemon": "^3.1.9",
@ -46,12 +47,14 @@
"dayjs-plugin-utc": "^0.1.2", "dayjs-plugin-utc": "^0.1.2",
"docx-templates": "^4.13.0", "docx-templates": "^4.13.0",
"dotenv": "^16.4.7", "dotenv": "^16.4.7",
"exceljs": "^4.4.0",
"express": "^4.21.2", "express": "^4.21.2",
"fast-jwt": "^5.0.5", "fast-jwt": "^5.0.5",
"json-2-csv": "^5.5.8", "json-2-csv": "^5.5.8",
"kysely": "^0.27.5", "kysely": "^0.27.5",
"minio": "^8.0.2", "minio": "^8.0.2",
"morgan": "^1.10.0", "morgan": "^1.10.0",
"multer": "^1.4.5-lts.2",
"nodemailer": "^6.10.0", "nodemailer": "^6.10.0",
"prisma-extension-kysely": "^3.0.0", "prisma-extension-kysely": "^3.0.0",
"promise.any": "^2.0.6", "promise.any": "^2.0.6",

254
pnpm-lock.yaml generated
View file

@ -44,6 +44,9 @@ importers:
dotenv: dotenv:
specifier: ^16.4.7 specifier: ^16.4.7
version: 16.4.7 version: 16.4.7
exceljs:
specifier: ^4.4.0
version: 4.4.0
express: express:
specifier: ^4.21.2 specifier: ^4.21.2
version: 4.21.2 version: 4.21.2
@ -62,6 +65,9 @@ importers:
morgan: morgan:
specifier: ^1.10.0 specifier: ^1.10.0
version: 1.10.0 version: 1.10.0
multer:
specifier: ^1.4.5-lts.2
version: 1.4.5-lts.2
nodemailer: nodemailer:
specifier: ^6.10.0 specifier: ^6.10.0
version: 6.10.0 version: 6.10.0
@ -99,6 +105,9 @@ importers:
'@types/morgan': '@types/morgan':
specifier: ^1.9.9 specifier: ^1.9.9
version: 1.9.9 version: 1.9.9
'@types/multer':
specifier: ^1.4.12
version: 1.4.12
'@types/node': '@types/node':
specifier: ^20.17.10 specifier: ^20.17.10
version: 20.17.10 version: 20.17.10
@ -177,6 +186,12 @@ packages:
resolution: {integrity: sha512-jasKNQeOb1vNf9aEYg+8zXmetaFjApDTSCC4QTl6aTixvyiRiSLcCiB8P6Q0lY9JIII/BhqNl8WbpFnsKitntw==} resolution: {integrity: sha512-jasKNQeOb1vNf9aEYg+8zXmetaFjApDTSCC4QTl6aTixvyiRiSLcCiB8P6Q0lY9JIII/BhqNl8WbpFnsKitntw==}
engines: {node: '>=18'} engines: {node: '>=18'}
'@fast-csv/format@4.3.5':
resolution: {integrity: sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A==}
'@fast-csv/parse@4.3.6':
resolution: {integrity: sha512-uRsLYksqpbDmWaSmzvJcuApSEe38+6NQZBUsuAyMZKqHxH0g1wcJgsKUvN3WC8tewaqFjBMMGrkHmC+T7k8LvA==}
'@fast-csv/parse@5.0.2': '@fast-csv/parse@5.0.2':
resolution: {integrity: sha512-gMu1Btmm99TP+wc0tZnlH30E/F1Gw1Tah3oMDBHNPe9W8S68ixVHjt89Wg5lh7d9RuQMtwN+sGl5kxR891+fzw==} resolution: {integrity: sha512-gMu1Btmm99TP+wc0tZnlH30E/F1Gw1Tah3oMDBHNPe9W8S68ixVHjt89Wg5lh7d9RuQMtwN+sGl5kxR891+fzw==}
@ -492,6 +507,9 @@ packages:
'@types/multer@1.4.12': '@types/multer@1.4.12':
resolution: {integrity: sha512-pQ2hoqvXiJt2FP9WQVLPRO+AmiIm/ZYkavPlIQnx282u4ZrVdztx0pkh3jjpQt0Kz+YI0YhSG264y08UJKoUQg==} resolution: {integrity: sha512-pQ2hoqvXiJt2FP9WQVLPRO+AmiIm/ZYkavPlIQnx282u4ZrVdztx0pkh3jjpQt0Kz+YI0YhSG264y08UJKoUQg==}
'@types/node@14.18.63':
resolution: {integrity: sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==}
'@types/node@20.17.10': '@types/node@20.17.10':
resolution: {integrity: sha512-/jrvh5h6NXhEauFFexRin69nA0uHJ5gwk4iDivp/DeoEua3uwCUto6PC86IpRITBOs4+6i2I56K5x5b6WYGXHA==} resolution: {integrity: sha512-/jrvh5h6NXhEauFFexRin69nA0uHJ5gwk4iDivp/DeoEua3uwCUto6PC86IpRITBOs4+6i2I56K5x5b6WYGXHA==}
@ -587,6 +605,9 @@ packages:
resolution: {integrity: sha512-v/ShMp57iBnBp4lDgV8Jx3d3Q5/Hac25FWmQ98eMahUiHPXcvwIMKJD0hBIgclm/FCG+LwPkAKtkRO1O/W0YGg==} resolution: {integrity: sha512-v/ShMp57iBnBp4lDgV8Jx3d3Q5/Hac25FWmQ98eMahUiHPXcvwIMKJD0hBIgclm/FCG+LwPkAKtkRO1O/W0YGg==}
hasBin: true hasBin: true
append-field@1.0.0:
resolution: {integrity: sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==}
archiver-utils@2.1.0: archiver-utils@2.1.0:
resolution: {integrity: sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==} resolution: {integrity: sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==}
engines: {node: '>= 6'} engines: {node: '>= 6'}
@ -682,6 +703,10 @@ packages:
resolution: {integrity: sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==} resolution: {integrity: sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==}
engines: {node: '>= 0.8'} engines: {node: '>= 0.8'}
big-integer@1.6.52:
resolution: {integrity: sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==}
engines: {node: '>=0.6'}
binary-extensions@2.3.0: binary-extensions@2.3.0:
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
engines: {node: '>=8'} engines: {node: '>=8'}
@ -689,12 +714,18 @@ packages:
binary-search@1.3.6: binary-search@1.3.6:
resolution: {integrity: sha512-nbE1WxOTTrUWIfsfZ4aHGYu5DOuNkbxGokjV6Z2kxfJK3uaAb8zNK1muzOeipoLHZjInT4Br88BHpzevc681xA==} resolution: {integrity: sha512-nbE1WxOTTrUWIfsfZ4aHGYu5DOuNkbxGokjV6Z2kxfJK3uaAb8zNK1muzOeipoLHZjInT4Br88BHpzevc681xA==}
binary@0.3.0:
resolution: {integrity: sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==}
bl@4.1.0: bl@4.1.0:
resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
block-stream2@2.1.0: block-stream2@2.1.0:
resolution: {integrity: sha512-suhjmLI57Ewpmq00qaygS8UgEq2ly2PCItenIyhMqVjo4t4pGzqMvfgJuX8iWTeSDdfSSqS6j38fL4ToNL7Pfg==} resolution: {integrity: sha512-suhjmLI57Ewpmq00qaygS8UgEq2ly2PCItenIyhMqVjo4t4pGzqMvfgJuX8iWTeSDdfSSqS6j38fL4ToNL7Pfg==}
bluebird@3.4.7:
resolution: {integrity: sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==}
bn.js@4.12.1: bn.js@4.12.1:
resolution: {integrity: sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==} resolution: {integrity: sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==}
@ -725,9 +756,24 @@ packages:
resolution: {integrity: sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==} resolution: {integrity: sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==}
engines: {node: '>=8.0.0'} engines: {node: '>=8.0.0'}
buffer-from@1.1.2:
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
buffer-indexof-polyfill@1.0.2:
resolution: {integrity: sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==}
engines: {node: '>=0.10'}
buffer@5.7.1: buffer@5.7.1:
resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}
buffers@0.1.1:
resolution: {integrity: sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==}
engines: {node: '>=0.2.0'}
busboy@1.6.0:
resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==}
engines: {node: '>=10.16.0'}
bytes@3.1.2: bytes@3.1.2:
resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
engines: {node: '>= 0.8'} engines: {node: '>= 0.8'}
@ -744,6 +790,9 @@ packages:
resolution: {integrity: sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==} resolution: {integrity: sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
chainsaw@0.1.0:
resolution: {integrity: sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==}
chalk-template@0.4.0: chalk-template@0.4.0:
resolution: {integrity: sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==} resolution: {integrity: sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -821,6 +870,10 @@ packages:
concat-map@0.0.1: concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
concat-stream@1.6.2:
resolution: {integrity: sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==}
engines: {'0': node >= 0.8}
console-log-level@1.4.1: console-log-level@1.4.1:
resolution: {integrity: sha512-VZzbIORbP+PPcN/gg3DXClTLPLg5Slwd5fL2MIc+o1qZ4BXBvWyc6QxPk6T/Mkr6IVjRpoAGf32XxP3ZWMVRcQ==} resolution: {integrity: sha512-VZzbIORbP+PPcN/gg3DXClTLPLg5Slwd5fL2MIc+o1qZ4BXBvWyc6QxPk6T/Mkr6IVjRpoAGf32XxP3ZWMVRcQ==}
@ -985,6 +1038,9 @@ packages:
resolution: {integrity: sha512-9+Sj30DIu+4KvHqMfLUGLFYL2PkURSYMVXJyXe92nFRvlYq5hBjLEhblKB+vkd/WVlUYMWigiY07T91Fkk0+4A==} resolution: {integrity: sha512-9+Sj30DIu+4KvHqMfLUGLFYL2PkURSYMVXJyXe92nFRvlYq5hBjLEhblKB+vkd/WVlUYMWigiY07T91Fkk0+4A==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
duplexer2@0.1.4:
resolution: {integrity: sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==}
eastasianwidth@0.2.0: eastasianwidth@0.2.0:
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
@ -1087,6 +1143,10 @@ packages:
eventemitter3@5.0.1: eventemitter3@5.0.1:
resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==}
exceljs@4.4.0:
resolution: {integrity: sha512-XctvKaEMaj1Ii9oDOqbW/6e1gXknSY4g/aLCDicOXqBE4M0nRWkUu0PTp++UPNzoFY12BNHMfs/VadKIS6llvg==}
engines: {node: '>=8.3.0'}
execa@5.1.1: execa@5.1.1:
resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==}
engines: {node: '>=10'} engines: {node: '>=10'}
@ -1095,6 +1155,10 @@ packages:
resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==} resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==}
engines: {node: '>= 0.10.0'} engines: {node: '>= 0.10.0'}
fast-csv@4.3.6:
resolution: {integrity: sha512-2RNSpuwwsJGP0frGsOmTb9oUF+VkFSM4SyLTDgwf2ciHWTarN0lQTC+F2f/t5J9QjW+c65VFIAAu85GsvMIusw==}
engines: {node: '>=10.0.0'}
fast-glob@3.3.2: fast-glob@3.3.2:
resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==}
engines: {node: '>=8.6.0'} engines: {node: '>=8.6.0'}
@ -1203,6 +1267,11 @@ packages:
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin] os: [darwin]
fstream@1.0.12:
resolution: {integrity: sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==}
engines: {node: '>=0.6'}
deprecated: This package is no longer supported.
function-bind@1.1.2: function-bind@1.1.2:
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
@ -1623,6 +1692,9 @@ packages:
lines-and-columns@1.2.4: lines-and-columns@1.2.4:
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
listenercount@1.0.1:
resolution: {integrity: sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==}
locate-path@5.0.0: locate-path@5.0.0:
resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}
engines: {node: '>=8'} engines: {node: '>=8'}
@ -1649,6 +1721,13 @@ packages:
lodash.groupby@4.6.0: lodash.groupby@4.6.0:
resolution: {integrity: sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==} resolution: {integrity: sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==}
lodash.isboolean@3.0.3:
resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==}
lodash.isequal@4.5.0:
resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==}
deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead.
lodash.isfunction@3.0.9: lodash.isfunction@3.0.9:
resolution: {integrity: sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==} resolution: {integrity: sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==}
@ -1796,6 +1875,10 @@ packages:
resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
engines: {node: '>=16 || 14 >=14.17'} engines: {node: '>=16 || 14 >=14.17'}
mkdirp@0.5.6:
resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==}
hasBin: true
mnemonist@0.39.8: mnemonist@0.39.8:
resolution: {integrity: sha512-vyWo2K3fjrUw8YeeZ1zF0fy6Mu59RHokURlld8ymdUPjMlD9EC9ov1/YPqTgqRvUN9nTr3Gqfz29LYAmu0PHPQ==} resolution: {integrity: sha512-vyWo2K3fjrUw8YeeZ1zF0fy6Mu59RHokURlld8ymdUPjMlD9EC9ov1/YPqTgqRvUN9nTr3Gqfz29LYAmu0PHPQ==}
@ -1818,6 +1901,10 @@ packages:
ms@2.1.3: ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
multer@1.4.5-lts.2:
resolution: {integrity: sha512-VzGiVigcG9zUAoCNU+xShztrlr1auZOlurXynNvO9GiWD1/mTBbUljOKY+qMeazBqXgRnjzeEgJI/wyjJUHg9A==}
engines: {node: '>= 6.0.0'}
negotiator@0.6.3: negotiator@0.6.3:
resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
@ -2190,6 +2277,11 @@ packages:
resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
engines: {iojs: '>=1.0.0', node: '>=0.10.0'} engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
rimraf@2.7.1:
resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==}
deprecated: Rimraf versions prior to v4 are no longer supported
hasBin: true
rimraf@3.0.2: rimraf@3.0.2:
resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
deprecated: Rimraf versions prior to v4 are no longer supported deprecated: Rimraf versions prior to v4 are no longer supported
@ -2225,6 +2317,10 @@ packages:
sax@1.4.1: sax@1.4.1:
resolution: {integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==} resolution: {integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==}
saxes@5.0.1:
resolution: {integrity: sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==}
engines: {node: '>=10'}
secure-json-parse@2.7.0: secure-json-parse@2.7.0:
resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==} resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==}
@ -2372,6 +2468,10 @@ packages:
resolution: {integrity: sha512-LsvisgE3iThboRqA+XLmtnY9ktPLVPOj3zZxXMhlezeCcAh0RhomquXJgB8H+lb/RR/pPcbNVGHVKFUwjpoRtw==} resolution: {integrity: sha512-LsvisgE3iThboRqA+XLmtnY9ktPLVPOj3zZxXMhlezeCcAh0RhomquXJgB8H+lb/RR/pPcbNVGHVKFUwjpoRtw==}
engines: {node: '>= 0.8'} engines: {node: '>= 0.8'}
streamsearch@1.1.0:
resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==}
engines: {node: '>=10.0.0'}
strict-uri-encode@2.0.0: 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'}
@ -2498,6 +2598,9 @@ packages:
tr46@1.0.1: tr46@1.0.1:
resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==}
traverse@0.3.9:
resolution: {integrity: sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==}
triple-beam@1.4.1: triple-beam@1.4.1:
resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==} resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==}
engines: {node: '>= 14.0.0'} engines: {node: '>= 14.0.0'}
@ -2567,6 +2670,9 @@ packages:
resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
typedarray@0.0.6:
resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==}
typescript@5.7.2: typescript@5.7.2:
resolution: {integrity: sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==} resolution: {integrity: sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==}
engines: {node: '>=14.17'} engines: {node: '>=14.17'}
@ -2616,6 +2722,9 @@ packages:
resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
engines: {node: '>= 0.8'} engines: {node: '>= 0.8'}
unzipper@0.10.14:
resolution: {integrity: sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g==}
util-deprecate@1.0.2: util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
@ -2626,6 +2735,10 @@ packages:
resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==}
engines: {node: '>= 0.4.0'} engines: {node: '>= 0.4.0'}
uuid@8.3.2:
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
hasBin: true
uuid@9.0.0: uuid@9.0.0:
resolution: {integrity: sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==} resolution: {integrity: sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==}
hasBin: true hasBin: true
@ -2718,6 +2831,13 @@ packages:
resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==} resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==}
engines: {node: '>=4.0'} engines: {node: '>=4.0'}
xmlchars@2.2.0:
resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
xtend@4.0.2:
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
engines: {node: '>=0.4'}
y18n@5.0.8: y18n@5.0.8:
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
engines: {node: '>=10'} engines: {node: '>=10'}
@ -2826,6 +2946,25 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@fast-csv/format@4.3.5':
dependencies:
'@types/node': 14.18.63
lodash.escaperegexp: 4.1.2
lodash.isboolean: 3.0.3
lodash.isequal: 4.5.0
lodash.isfunction: 3.0.9
lodash.isnil: 4.0.0
'@fast-csv/parse@4.3.6':
dependencies:
'@types/node': 14.18.63
lodash.escaperegexp: 4.1.2
lodash.groupby: 4.6.0
lodash.isfunction: 3.0.9
lodash.isnil: 4.0.0
lodash.isundefined: 3.0.1
lodash.uniq: 4.5.0
'@fast-csv/parse@5.0.2': '@fast-csv/parse@5.0.2':
dependencies: dependencies:
lodash.escaperegexp: 4.1.2 lodash.escaperegexp: 4.1.2
@ -3341,6 +3480,8 @@ snapshots:
dependencies: dependencies:
'@types/express': 4.17.21 '@types/express': 4.17.21
'@types/node@14.18.63': {}
'@types/node@20.17.10': '@types/node@20.17.10':
dependencies: dependencies:
undici-types: 6.19.8 undici-types: 6.19.8
@ -3440,6 +3581,8 @@ snapshots:
json-bignum: 0.0.3 json-bignum: 0.0.3
tslib: 2.8.1 tslib: 2.8.1
append-field@1.0.0: {}
archiver-utils@2.1.0: archiver-utils@2.1.0:
dependencies: dependencies:
glob: 7.2.3 glob: 7.2.3
@ -3563,11 +3706,18 @@ snapshots:
dependencies: dependencies:
safe-buffer: 5.1.2 safe-buffer: 5.1.2
big-integer@1.6.52: {}
binary-extensions@2.3.0: {} binary-extensions@2.3.0: {}
binary-search@1.3.6: binary-search@1.3.6:
optional: true optional: true
binary@0.3.0:
dependencies:
buffers: 0.1.1
chainsaw: 0.1.0
bl@4.1.0: bl@4.1.0:
dependencies: dependencies:
buffer: 5.7.1 buffer: 5.7.1
@ -3578,6 +3728,8 @@ snapshots:
dependencies: dependencies:
readable-stream: 3.6.2 readable-stream: 3.6.2
bluebird@3.4.7: {}
bn.js@4.12.1: {} bn.js@4.12.1: {}
body-parser@1.20.3: body-parser@1.20.3:
@ -3621,11 +3773,21 @@ snapshots:
buffer-crc32@1.0.0: {} buffer-crc32@1.0.0: {}
buffer-from@1.1.2: {}
buffer-indexof-polyfill@1.0.2: {}
buffer@5.7.1: buffer@5.7.1:
dependencies: dependencies:
base64-js: 1.5.1 base64-js: 1.5.1
ieee754: 1.2.1 ieee754: 1.2.1
buffers@0.1.1: {}
busboy@1.6.0:
dependencies:
streamsearch: 1.1.0
bytes@3.1.2: {} bytes@3.1.2: {}
call-bind-apply-helpers@1.0.1: call-bind-apply-helpers@1.0.1:
@ -3645,6 +3807,10 @@ snapshots:
call-bind-apply-helpers: 1.0.1 call-bind-apply-helpers: 1.0.1
get-intrinsic: 1.2.6 get-intrinsic: 1.2.6
chainsaw@0.1.0:
dependencies:
traverse: 0.3.9
chalk-template@0.4.0: chalk-template@0.4.0:
dependencies: dependencies:
chalk: 4.1.2 chalk: 4.1.2
@ -3756,6 +3922,13 @@ snapshots:
concat-map@0.0.1: {} concat-map@0.0.1: {}
concat-stream@1.6.2:
dependencies:
buffer-from: 1.1.2
inherits: 2.0.4
readable-stream: 2.3.8
typedarray: 0.0.6
console-log-level@1.4.1: console-log-level@1.4.1:
optional: true optional: true
@ -3899,6 +4072,10 @@ snapshots:
es-errors: 1.3.0 es-errors: 1.3.0
gopd: 1.2.0 gopd: 1.2.0
duplexer2@0.1.4:
dependencies:
readable-stream: 2.3.8
eastasianwidth@0.2.0: {} eastasianwidth@0.2.0: {}
ecdsa-sig-formatter@1.0.11: ecdsa-sig-formatter@1.0.11:
@ -4088,6 +4265,18 @@ snapshots:
eventemitter3@5.0.1: {} eventemitter3@5.0.1: {}
exceljs@4.4.0:
dependencies:
archiver: 5.3.2
dayjs: 1.11.13
fast-csv: 4.3.6
jszip: 3.10.1
readable-stream: 3.6.2
saxes: 5.0.1
tmp: 0.2.1
unzipper: 0.10.14
uuid: 8.3.2
execa@5.1.1: execa@5.1.1:
dependencies: dependencies:
cross-spawn: 7.0.6 cross-spawn: 7.0.6
@ -4136,6 +4325,11 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
fast-csv@4.3.6:
dependencies:
'@fast-csv/format': 4.3.5
'@fast-csv/parse': 4.3.6
fast-glob@3.3.2: fast-glob@3.3.2:
dependencies: dependencies:
'@nodelib/fs.stat': 2.0.5 '@nodelib/fs.stat': 2.0.5
@ -4258,6 +4452,13 @@ snapshots:
fsevents@2.3.3: fsevents@2.3.3:
optional: true optional: true
fstream@1.0.12:
dependencies:
graceful-fs: 4.2.11
inherits: 2.0.4
mkdirp: 0.5.6
rimraf: 2.7.1
function-bind@1.1.2: {} function-bind@1.1.2: {}
function.prototype.name@1.1.7: function.prototype.name@1.1.7:
@ -4693,6 +4894,8 @@ snapshots:
lines-and-columns@1.2.4: {} lines-and-columns@1.2.4: {}
listenercount@1.0.1: {}
locate-path@5.0.0: locate-path@5.0.0:
dependencies: dependencies:
p-locate: 4.1.0 p-locate: 4.1.0
@ -4713,6 +4916,10 @@ snapshots:
lodash.groupby@4.6.0: {} lodash.groupby@4.6.0: {}
lodash.isboolean@3.0.3: {}
lodash.isequal@4.5.0: {}
lodash.isfunction@3.0.9: {} lodash.isfunction@3.0.9: {}
lodash.isnil@4.0.0: {} lodash.isnil@4.0.0: {}
@ -4853,6 +5060,10 @@ snapshots:
minipass@7.1.2: {} minipass@7.1.2: {}
mkdirp@0.5.6:
dependencies:
minimist: 1.2.8
mnemonist@0.39.8: mnemonist@0.39.8:
dependencies: dependencies:
obliterator: 2.0.4 obliterator: 2.0.4
@ -4879,6 +5090,16 @@ snapshots:
ms@2.1.3: {} ms@2.1.3: {}
multer@1.4.5-lts.2:
dependencies:
append-field: 1.0.0
busboy: 1.6.0
concat-stream: 1.6.2
mkdirp: 0.5.6
object-assign: 4.1.1
type-is: 1.6.18
xtend: 4.0.2
negotiator@0.6.3: {} negotiator@0.6.3: {}
neo-async@2.6.2: {} neo-async@2.6.2: {}
@ -5273,6 +5494,10 @@ snapshots:
reusify@1.0.4: {} reusify@1.0.4: {}
rimraf@2.7.1:
dependencies:
glob: 7.2.3
rimraf@3.0.2: rimraf@3.0.2:
dependencies: dependencies:
glob: 7.2.3 glob: 7.2.3
@ -5307,6 +5532,10 @@ snapshots:
sax@1.4.1: {} sax@1.4.1: {}
saxes@5.0.1:
dependencies:
xmlchars: 2.2.0
secure-json-parse@2.7.0: {} secure-json-parse@2.7.0: {}
semver@5.7.2: {} semver@5.7.2: {}
@ -5478,6 +5707,8 @@ snapshots:
stream-to-buffer@0.0.1: {} stream-to-buffer@0.0.1: {}
streamsearch@1.1.0: {}
strict-uri-encode@2.0.0: {} strict-uri-encode@2.0.0: {}
string-width@4.2.3: string-width@4.2.3:
@ -5618,6 +5849,8 @@ snapshots:
punycode: 2.3.1 punycode: 2.3.1
optional: true optional: true
traverse@0.3.9: {}
triple-beam@1.4.1: {} triple-beam@1.4.1: {}
ts-deepmerge@7.0.2: {} ts-deepmerge@7.0.2: {}
@ -5697,6 +5930,8 @@ snapshots:
possible-typed-array-names: 1.0.0 possible-typed-array-names: 1.0.0
reflect.getprototypeof: 1.0.8 reflect.getprototypeof: 1.0.8
typedarray@0.0.6: {}
typescript@5.7.2: {} typescript@5.7.2: {}
typical@4.0.0: {} typical@4.0.0: {}
@ -5736,6 +5971,19 @@ snapshots:
unpipe@1.0.0: {} unpipe@1.0.0: {}
unzipper@0.10.14:
dependencies:
big-integer: 1.6.52
binary: 0.3.0
bluebird: 3.4.7
buffer-indexof-polyfill: 1.0.2
duplexer2: 0.1.4
fstream: 1.0.12
graceful-fs: 4.2.11
listenercount: 1.0.1
readable-stream: 2.3.8
setimmediate: 1.0.5
util-deprecate@1.0.2: {} util-deprecate@1.0.2: {}
util@0.12.5: util@0.12.5:
@ -5748,6 +5996,8 @@ snapshots:
utils-merge@1.0.1: {} utils-merge@1.0.1: {}
uuid@8.3.2: {}
uuid@9.0.0: {} uuid@9.0.0: {}
v8-compile-cache-lib@3.0.1: {} v8-compile-cache-lib@3.0.1: {}
@ -5888,6 +6138,10 @@ snapshots:
xmlbuilder@11.0.1: {} xmlbuilder@11.0.1: {}
xmlchars@2.2.0: {}
xtend@4.0.2: {}
y18n@5.0.8: {} y18n@5.0.8: {}
yallist@2.1.2: yallist@2.1.2:

View file

@ -0,0 +1,3 @@
-- AlterTable
ALTER TABLE "User" ADD COLUMN "contactName" TEXT,
ADD COLUMN "contactTel" TEXT;

View file

@ -0,0 +1,3 @@
-- AlterTable
ALTER TABLE "User" ALTER COLUMN "firstName" DROP NOT NULL,
ALTER COLUMN "lastName" DROP NOT NULL;

View file

@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "TaskOrder" ADD COLUMN "codeProductReceived" TEXT;

View file

@ -0,0 +1,18 @@
-- AlterTable
ALTER TABLE "Institution" ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
ADD COLUMN "createdByUserId" TEXT,
ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
ADD COLUMN "updatedByUserId" TEXT;
-- AlterTable
ALTER TABLE "Payment" ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
ADD COLUMN "updatedByUserId" TEXT;
-- AddForeignKey
ALTER TABLE "Institution" ADD CONSTRAINT "Institution_createdByUserId_fkey" FOREIGN KEY ("createdByUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Institution" ADD CONSTRAINT "Institution_updatedByUserId_fkey" FOREIGN KEY ("updatedByUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Payment" ADD CONSTRAINT "Payment_updatedByUserId_fkey" FOREIGN KEY ("updatedByUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;

View file

@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Employee" ALTER COLUMN "lastNameEN" DROP NOT NULL;

View file

@ -0,0 +1,11 @@
-- CreateTable
CREATE TABLE "WorkflowTemplateStepGroup" (
"id" TEXT NOT NULL,
"group" TEXT NOT NULL,
"workflowTemplateStepId" TEXT NOT NULL,
CONSTRAINT "WorkflowTemplateStepGroup_pkey" PRIMARY KEY ("id")
);
-- AddForeignKey
ALTER TABLE "WorkflowTemplateStepGroup" ADD CONSTRAINT "WorkflowTemplateStepGroup_workflowTemplateStepId_fkey" FOREIGN KEY ("workflowTemplateStepId") REFERENCES "WorkflowTemplateStep"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View file

@ -0,0 +1,20 @@
/*
Warnings:
- You are about to drop the column `importNationality` on the `User` table. All the data in the column will be lost.
*/
-- AlterTable
ALTER TABLE "User" DROP COLUMN "importNationality";
-- CreateTable
CREATE TABLE "UserImportNationality" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"userId" TEXT NOT NULL,
CONSTRAINT "UserImportNationality_pkey" PRIMARY KEY ("id")
);
-- AddForeignKey
ALTER TABLE "UserImportNationality" ADD CONSTRAINT "UserImportNationality_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View file

@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Employee" ADD COLUMN "otherNationality" TEXT;

View file

@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "EmployeePassport" ADD COLUMN "otherNationality" TEXT;

View file

@ -366,16 +366,24 @@ enum UserType {
AGENCY AGENCY
} }
model UserImportNationality {
id String @id @default(cuid())
name String
user User @relation(fields: [userId], references: [id])
userId String
}
model User { model User {
id String @id @default(cuid()) id String @id @default(cuid())
code String? code String?
namePrefix String? namePrefix String?
firstName String firstName String?
firstNameEN String firstNameEN String
middleName String? middleName String?
middleNameEN String? middleNameEN String?
lastName String lastName String?
lastNameEN String lastNameEN String
username String username String
gender String gender String
@ -424,7 +432,7 @@ model User {
licenseExpireDate DateTime? @db.Date licenseExpireDate DateTime? @db.Date
sourceNationality String? sourceNationality String?
importNationality String? importNationality UserImportNationality[]
trainingPlace String? trainingPlace String?
responsibleArea UserResponsibleArea[] responsibleArea UserResponsibleArea[]
@ -484,12 +492,15 @@ model User {
flowCreated WorkflowTemplate[] @relation("FlowCreatedByUser") flowCreated WorkflowTemplate[] @relation("FlowCreatedByUser")
flowUpdated WorkflowTemplate[] @relation("FlowUpdatedByUser") flowUpdated WorkflowTemplate[] @relation("FlowUpdatedByUser")
invoiceCreated Invoice[] invoiceCreated Invoice[]
paymentCreated Payment[] paymentCreated Payment[] @relation("PaymentCreatedByUser")
paymentUpdated Payment[] @relation("PaymentUpdatedByUser")
notificationReceive Notification[] @relation("NotificationReceiver") notificationReceive Notification[] @relation("NotificationReceiver")
notificationRead Notification[] @relation("NotificationRead") notificationRead Notification[] @relation("NotificationRead")
notificationDelete Notification[] @relation("NotificationDelete") notificationDelete Notification[] @relation("NotificationDelete")
taskOrderCreated TaskOrder[] @relation("TaskOrderCreatedByUser") taskOrderCreated TaskOrder[] @relation("TaskOrderCreatedByUser")
creditNoteCreated CreditNote[] @relation("CreditNoteCreatedByUser") creditNoteCreated CreditNote[] @relation("CreditNoteCreatedByUser")
institutionCreated Institution[] @relation("InstitutionCreatedByUser")
institutionUpdated Institution[] @relation("InstitutionUpdatedByUser")
requestWorkStepStatus RequestWorkStepStatus[] requestWorkStepStatus RequestWorkStepStatus[]
userTask UserTask[] userTask UserTask[]
@ -497,6 +508,9 @@ model User {
remark String? remark String?
agencyStatus String? agencyStatus String?
contactName String?
contactTel String?
} }
model UserResponsibleArea { model UserResponsibleArea {
@ -771,11 +785,12 @@ model Employee {
middleName String? middleName String?
middleNameEN String? middleNameEN String?
lastName String? lastName String?
lastNameEN String lastNameEN String?
dateOfBirth DateTime? @db.Date dateOfBirth DateTime? @db.Date
gender String gender String
nationality String nationality String
otherNationality String?
address String? address String?
addressEN String? addressEN String?
@ -850,18 +865,19 @@ model EmployeePassport {
issuePlace String issuePlace String
previousPassportRef String? previousPassportRef String?
workerStatus String? workerStatus String?
nationality String? nationality String?
namePrefix String? otherNationality String?
firstName String? namePrefix String?
firstNameEN String? firstName String?
middleName String? firstNameEN String?
middleNameEN String? middleName String?
lastName String? middleNameEN String?
lastNameEN String? lastName String?
gender String? lastNameEN String?
birthDate String? gender String?
birthCountry String? birthDate String?
birthCountry String?
employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade) employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade)
employeeId String employeeId String
@ -1012,6 +1028,13 @@ model Institution {
contactEmail String? contactEmail String?
contactTel String? contactTel String?
createdAt DateTime @default(now())
createdBy User? @relation(name: "InstitutionCreatedByUser", fields: [createdByUserId], references: [id], onDelete: SetNull)
createdByUserId String?
updatedAt DateTime @default(now()) @updatedAt
updatedBy User? @relation(name: "InstitutionUpdatedByUser", fields: [updatedByUserId], references: [id], onDelete: SetNull)
updatedByUserId String?
bank InstitutionBank[] bank InstitutionBank[]
} }
@ -1076,6 +1099,15 @@ model WorkflowTemplateStepInstitution {
workflowTemplateStepId String workflowTemplateStepId String
} }
model WorkflowTemplateStepGroup {
id String @id @default(cuid())
group String
workflowTemplateStep WorkflowTemplateStep @relation(fields: [workflowTemplateStepId], references: [id], onDelete: Cascade)
workflowTemplateStepId String
}
model WorkflowTemplateStep { model WorkflowTemplateStep {
id String @id @default(cuid()) id String @id @default(cuid())
@ -1086,6 +1118,7 @@ model WorkflowTemplateStep {
value WorkflowTemplateStepValue[] // NOTE: For enum or options type value WorkflowTemplateStepValue[] // NOTE: For enum or options type
responsiblePerson WorkflowTemplateStepUser[] responsiblePerson WorkflowTemplateStepUser[]
responsibleInstitution WorkflowTemplateStepInstitution[] responsibleInstitution WorkflowTemplateStepInstitution[]
responsibleGroup WorkflowTemplateStepGroup[]
messengerByArea Boolean @default(false) messengerByArea Boolean @default(false)
attributes Json? attributes Json?
@ -1460,8 +1493,12 @@ model Payment {
date DateTime? date DateTime?
createdAt DateTime @default(now()) createdAt DateTime @default(now())
createdBy User? @relation(fields: [createdByUserId], references: [id], onDelete: SetNull) createdBy User? @relation(name: "PaymentCreatedByUser", fields: [createdByUserId], references: [id], onDelete: SetNull)
createdByUserId String? createdByUserId String?
updatedAt DateTime @default(now()) @updatedAt
updatedBy User? @relation(name: "PaymentUpdatedByUser", fields: [updatedByUserId], references: [id], onDelete: SetNull)
updatedByUserId String?
} }
enum RequestDataStatus { enum RequestDataStatus {
@ -1614,7 +1651,8 @@ model TaskProduct {
model TaskOrder { model TaskOrder {
id String @id @default(cuid()) id String @id @default(cuid())
code String code String
codeProductReceived String?
taskName String taskName String
taskOrderStatus TaskOrderStatus @default(Pending) taskOrderStatus TaskOrderStatus @default(Pending)

View file

@ -2,6 +2,7 @@ import { Body, Controller, Get, Path, Post, Query, Route, Tags } from "tsoa";
import prisma from "../db"; import prisma from "../db";
import { queryOrNot } from "../utils/relation"; import { queryOrNot } from "../utils/relation";
import { notFoundError } from "../utils/error"; import { notFoundError } from "../utils/error";
import { Prisma } from "@prisma/client";
@Route("/api/v1/employment-office") @Route("/api/v1/employment-office")
@Tags("Employment Office") @Tags("Employment Office")
@ -11,6 +12,39 @@ export class EmploymentOfficeController extends Controller {
return this.getEmploymentOfficeListByCriteria(districtId, query); return this.getEmploymentOfficeListByCriteria(districtId, query);
} }
@Post("list-same-office-area")
async getSameOfficeArea(@Body() body: { districtId: string }) {
const office = await prisma.employmentOffice.findFirst({
include: {
province: {
include: {
district: true,
},
},
district: true,
},
where: {
OR: [
{
province: { district: { some: { id: body.districtId } } },
district: { none: {} },
},
{
district: {
some: { districtId: body.districtId },
},
},
],
},
});
if (!office) return [];
return [
...office.district.map((v) => v.districtId),
...office.province.district.map((v) => v.id),
];
}
@Post("list") @Post("list")
async getEmploymentOfficeListByCriteria( async getEmploymentOfficeListByCriteria(
@Query() districtId?: string, @Query() districtId?: string,
@ -40,11 +74,14 @@ export class EmploymentOfficeController extends Controller {
], ],
[], [],
), ),
...queryOrNot( ...(queryOrNot(
query, query,
[{ name: { contains: query } }, { nameEN: { contains: query } }], [
{ name: { contains: query, mode: "insensitive" } },
{ nameEN: { contains: query, mode: "insensitive" } },
],
[], [],
), ) satisfies Prisma.EmploymentOfficeWhereInput["OR"]),
...queryOrNot(!!body?.id, [{ id: { in: body?.id } }], []), ...queryOrNot(!!body?.id, [{ id: { in: body?.id } }], []),
] ]
: undefined, : undefined,

View file

@ -1,5 +1,5 @@
import { Body, Controller, Delete, Get, Path, Post, Route, Security, Tags } from "tsoa"; import { Body, Controller, Delete, Get, Path, Post, Query, Route, Security, Tags } from "tsoa";
import { addUserRoles, listRole, removeUserRoles } from "../services/keycloak"; import { addUserRoles, getGroup, listRole, removeUserRoles } from "../services/keycloak";
@Route("api/v1/keycloak") @Route("api/v1/keycloak")
@Tags("Single-Sign On") @Tags("Single-Sign On")
@ -44,4 +44,13 @@ export class KeycloakController extends Controller {
); );
if (!result) throw new Error("Failed. Cannot remove user's role."); if (!result) throw new Error("Failed. Cannot remove user's role.");
} }
@Get("group")
async getGroup(@Query() query: string = "") {
const querySearch = query === "" ? "q" : `search=${query}`;
const group = await getGroup(querySearch);
if (!Array.isArray(group)) throw new Error("Failed. Cannot get group(s) data from the server.");
return group;
}
} }

View file

@ -36,8 +36,8 @@ export class NotificationController extends Controller {
AND: [ AND: [
{ {
OR: queryOrNot<(typeof where)[]>(query, [ OR: queryOrNot<(typeof where)[]>(query, [
{ title: { contains: query } }, { title: { contains: query, mode: "insensitive" } },
{ detail: { contains: query } }, { detail: { contains: query, mode: "insensitive" } },
]), ]),
}, },
{ {

View file

@ -39,6 +39,7 @@ import {
connectOrNot, connectOrNot,
queryOrNot, queryOrNot,
whereAddressQuery, whereAddressQuery,
whereDateQuery,
} from "../utils/relation"; } from "../utils/relation";
import { isUsedError, notFoundError, relationError } from "../utils/error"; import { isUsedError, notFoundError, relationError } from "../utils/error";
@ -250,6 +251,8 @@ export class BranchController extends Controller {
@Query() query: string = "", @Query() query: string = "",
@Query() page: number = 1, @Query() page: number = 1,
@Query() pageSize: number = 30, @Query() pageSize: number = 30,
@Query() startDate?: Date,
@Query() endDate?: Date,
) { ) {
const where = { const where = {
AND: { AND: {
@ -265,26 +268,27 @@ export class BranchController extends Controller {
}, },
OR: queryOrNot<Prisma.BranchWhereInput[]>(query, [ OR: queryOrNot<Prisma.BranchWhereInput[]>(query, [
{ code: { contains: query, mode: "insensitive" } }, { code: { contains: query, mode: "insensitive" } },
{ nameEN: { contains: query } }, { nameEN: { contains: query, mode: "insensitive" } },
{ name: { contains: query } }, { name: { contains: query, mode: "insensitive" } },
{ email: { contains: query } }, { email: { contains: query, mode: "insensitive" } },
{ telephoneNo: { contains: query } }, { telephoneNo: { contains: query, mode: "insensitive" } },
...whereAddressQuery(query), ...whereAddressQuery(query),
{ {
branch: { branch: {
some: { some: {
OR: [ OR: [
{ code: { contains: query, mode: "insensitive" } }, { code: { contains: query, mode: "insensitive" } },
{ nameEN: { contains: query } }, { nameEN: { contains: query, mode: "insensitive" } },
{ name: { contains: query } }, { name: { contains: query, mode: "insensitive" } },
{ email: { contains: query } }, { email: { contains: query, mode: "insensitive" } },
{ telephoneNo: { contains: query } }, { telephoneNo: { contains: query, mode: "insensitive" } },
...whereAddressQuery(query), ...whereAddressQuery(query),
], ],
}, },
}, },
}, },
]), ]),
...whereDateQuery(startDate, endDate),
} satisfies Prisma.BranchWhereInput; } satisfies Prisma.BranchWhereInput;
const [result, total] = await prisma.$transaction([ const [result, total] = await prisma.$transaction([
@ -309,12 +313,13 @@ export class BranchController extends Controller {
where: { where: {
AND: { OR: permissionCond(req.user) }, AND: { OR: permissionCond(req.user) },
OR: [ OR: [
{ nameEN: { contains: query } }, { nameEN: { contains: query, mode: "insensitive" } },
{ name: { contains: query } }, { name: { contains: query, mode: "insensitive" } },
{ email: { contains: query } }, { email: { contains: query, mode: "insensitive" } },
{ telephoneNo: { contains: query } }, { telephoneNo: { contains: query, mode: "insensitive" } },
...whereAddressQuery(query), ...whereAddressQuery(query),
], ],
...whereDateQuery(startDate, endDate),
}, },
include: { include: {
province: true, province: true,

View file

@ -18,7 +18,7 @@ import HttpError from "../interfaces/http-error";
import HttpStatus from "../interfaces/http-status"; import HttpStatus from "../interfaces/http-status";
import { RequestWithUser } from "../interfaces/user"; import { RequestWithUser } from "../interfaces/user";
import { branchRelationPermInclude, createPermCheck } from "../services/permission"; import { branchRelationPermInclude, createPermCheck } from "../services/permission";
import { queryOrNot } from "../utils/relation"; import { queryOrNot, whereDateQuery } from "../utils/relation";
const MANAGE_ROLES = ["system", "head_of_admin", "admin", "branch_manager"]; const MANAGE_ROLES = ["system", "head_of_admin", "admin", "branch_manager"];
@ -97,6 +97,8 @@ export class UserBranchController extends Controller {
@Query() query: string = "", @Query() query: string = "",
@Query() page: number = 1, @Query() page: number = 1,
@Query() pageSize: number = 30, @Query() pageSize: number = 30,
@Query() startDate?: Date,
@Query() endDate?: Date,
) { ) {
const where = { const where = {
AND: { AND: {
@ -104,9 +106,10 @@ export class UserBranchController extends Controller {
userId, userId,
}, },
OR: queryOrNot<Prisma.BranchUserWhereInput[]>(query, [ OR: queryOrNot<Prisma.BranchUserWhereInput[]>(query, [
{ branch: { name: { contains: query } } }, { branch: { name: { contains: query, mode: "insensitive" } } },
{ branch: { nameEN: { contains: query } } }, { branch: { nameEN: { contains: query, mode: "insensitive" } } },
]), ]),
...whereDateQuery(startDate, endDate),
} satisfies Prisma.BranchUserWhereInput; } satisfies Prisma.BranchUserWhereInput;
const [result, total] = await prisma.$transaction([ const [result, total] = await prisma.$transaction([
@ -150,6 +153,8 @@ export class BranchUserController extends Controller {
@Query() query: string = "", @Query() query: string = "",
@Query() page: number = 1, @Query() page: number = 1,
@Query() pageSize: number = 30, @Query() pageSize: number = 30,
@Query() startDate?: Date,
@Query() endDate?: Date,
) { ) {
const where = { const where = {
AND: { AND: {
@ -157,13 +162,14 @@ export class BranchUserController extends Controller {
branchId, branchId,
}, },
OR: [ OR: [
{ user: { firstName: { contains: query } } }, { user: { firstName: { contains: query, mode: "insensitive" } } },
{ user: { firstNameEN: { contains: query } } }, { user: { firstNameEN: { contains: query, mode: "insensitive" } } },
{ user: { lastName: { contains: query } } }, { user: { lastName: { contains: query, mode: "insensitive" } } },
{ user: { lastNameEN: { contains: query } } }, { user: { lastNameEN: { contains: query, mode: "insensitive" } } },
{ user: { email: { contains: query } } }, { user: { email: { contains: query, mode: "insensitive" } } },
{ user: { telephoneNo: { contains: query } } }, { user: { telephoneNo: { contains: query, mode: "insensitive" } } },
], ],
...whereDateQuery(startDate, endDate),
} satisfies Prisma.BranchUserWhereInput; } satisfies Prisma.BranchUserWhereInput;
const [result, total] = await prisma.$transaction([ const [result, total] = await prisma.$transaction([

View file

@ -27,6 +27,7 @@ import {
listRole, listRole,
getUserRoles, getUserRoles,
removeUserRoles, removeUserRoles,
getGroupUser,
} from "../services/keycloak"; } from "../services/keycloak";
import { isSystem } from "../utils/keycloak"; import { isSystem } from "../utils/keycloak";
import { import {
@ -51,6 +52,7 @@ import {
connectOrNot, connectOrNot,
queryOrNot, queryOrNot,
whereAddressQuery, whereAddressQuery,
whereDateQuery,
} from "../utils/relation"; } from "../utils/relation";
import { isUsedError, notFoundError, relationError } from "../utils/error"; import { isUsedError, notFoundError, relationError } from "../utils/error";
import { retry } from "../utils/func"; import { retry } from "../utils/func";
@ -79,11 +81,11 @@ type UserCreate = {
citizenExpire?: Date | null; citizenExpire?: Date | null;
namePrefix?: string | null; namePrefix?: string | null;
firstName: string; firstName?: string;
firstNameEN: string; firstNameEN: string;
middleName?: string | null; middleName?: string | null;
middleNameEN?: string | null; middleNameEN?: string | null;
lastName: string; lastName?: string;
lastNameEN: string; lastNameEN: string;
gender: string; gender: string;
@ -97,7 +99,7 @@ type UserCreate = {
licenseIssueDate?: Date | null; licenseIssueDate?: Date | null;
licenseExpireDate?: Date | null; licenseExpireDate?: Date | null;
sourceNationality?: string | null; sourceNationality?: string | null;
importNationality?: string | null; importNationality?: string[] | null;
trainingPlace?: string | null; trainingPlace?: string | null;
responsibleArea?: string[] | null; responsibleArea?: string[] | null;
birthDate?: Date | null; birthDate?: Date | null;
@ -123,6 +125,9 @@ type UserCreate = {
remark?: string; remark?: string;
agencyStatus?: string; agencyStatus?: string;
contactName?: string;
contactTel?: string;
}; };
type UserUpdate = { type UserUpdate = {
@ -139,9 +144,9 @@ type UserUpdate = {
namePrefix?: string | null; namePrefix?: string | null;
firstName?: string; firstName?: string;
firstNameEN?: string; firstNameEN: string;
middleName?: string | null; middleName?: string | null;
middleNameEN?: string | null; middleNameEN: string | null;
lastName?: string; lastName?: string;
lastNameEN?: string; lastNameEN?: string;
gender?: string; gender?: string;
@ -156,7 +161,7 @@ type UserUpdate = {
licenseIssueDate?: Date | null; licenseIssueDate?: Date | null;
licenseExpireDate?: Date | null; licenseExpireDate?: Date | null;
sourceNationality?: string | null; sourceNationality?: string | null;
importNationality?: string | null; importNationality?: string[] | null;
trainingPlace?: string | null; trainingPlace?: string | null;
responsibleArea?: string[] | null; responsibleArea?: string[] | null;
birthDate?: Date | null; birthDate?: Date | null;
@ -182,6 +187,9 @@ type UserUpdate = {
remark?: string; remark?: string;
agencyStatus?: string; agencyStatus?: string;
contactName?: string;
contactTel?: string;
}; };
const permissionCondCompany = createPermCondition((_) => true); const permissionCondCompany = createPermCondition((_) => true);
@ -273,6 +281,8 @@ export class UserController extends Controller {
@Query() status?: Status, @Query() status?: Status,
@Query() responsibleDistrictId?: string, @Query() responsibleDistrictId?: string,
@Query() activeBranchOnly?: boolean, @Query() activeBranchOnly?: boolean,
@Query() startDate?: Date,
@Query() endDate?: Date,
) { ) {
return this.getUserByCriteria( return this.getUserByCriteria(
req, req,
@ -284,6 +294,8 @@ export class UserController extends Controller {
status, status,
responsibleDistrictId, responsibleDistrictId,
activeBranchOnly, activeBranchOnly,
startDate,
endDate,
); );
} }
@ -299,6 +311,8 @@ export class UserController extends Controller {
@Query() status?: Status, @Query() status?: Status,
@Query() responsibleDistrictId?: string, @Query() responsibleDistrictId?: string,
@Query() activeBranchOnly?: boolean, @Query() activeBranchOnly?: boolean,
@Query() startDate?: Date,
@Query() endDate?: Date,
@Body() @Body()
body?: { body?: {
userId?: string[]; userId?: string[];
@ -324,12 +338,12 @@ export class UserController extends Controller {
const where = { const where = {
OR: queryOrNot<Prisma.UserWhereInput[]>(query, [ OR: queryOrNot<Prisma.UserWhereInput[]>(query, [
{ code: { contains: query, mode: "insensitive" } }, { code: { contains: query, mode: "insensitive" } },
{ firstName: { contains: query } }, { firstName: { contains: query, mode: "insensitive" } },
{ firstNameEN: { contains: query } }, { firstNameEN: { contains: query, mode: "insensitive" } },
{ lastName: { contains: query } }, { lastName: { contains: query, mode: "insensitive" } },
{ lastNameEN: { contains: query } }, { lastNameEN: { contains: query, mode: "insensitive" } },
{ email: { contains: query } }, { email: { contains: query, mode: "insensitive" } },
{ telephoneNo: { contains: query } }, { telephoneNo: { contains: query, mode: "insensitive" } },
...whereAddressQuery(query), ...whereAddressQuery(query),
]), ]),
AND: { AND: {
@ -362,12 +376,14 @@ export class UserController extends Controller {
}, },
}, },
}, },
...whereDateQuery(startDate, endDate),
} satisfies Prisma.UserWhereInput; } satisfies Prisma.UserWhereInput;
const [result, total] = await prisma.$transaction([ const [result, total] = await prisma.$transaction([
prisma.user.findMany({ prisma.user.findMany({
orderBy: [{ statusOrder: "asc" }, { createdAt: "asc" }], orderBy: [{ statusOrder: "asc" }, { createdAt: "asc" }],
include: { include: {
importNationality: true,
responsibleArea: true, responsibleArea: true,
province: true, province: true,
district: true, district: true,
@ -386,6 +402,7 @@ export class UserController extends Controller {
return { return {
result: result.map((v) => ({ result: result.map((v) => ({
...v, ...v,
importNationality: v.importNationality.map((v) => v.name),
responsibleArea: v.responsibleArea.map((v) => v.area), responsibleArea: v.responsibleArea.map((v) => v.area),
branch: includeBranch ? v.branch.map((a) => a.branch) : undefined, branch: includeBranch ? v.branch.map((a) => a.branch) : undefined,
})), })),
@ -400,6 +417,7 @@ export class UserController extends Controller {
async getUserById(@Path() userId: string) { async getUserById(@Path() userId: string) {
const record = await prisma.user.findFirst({ const record = await prisma.user.findFirst({
include: { include: {
importNationality: true,
province: true, province: true,
district: true, district: true,
subDistrict: true, subDistrict: true,
@ -411,7 +429,11 @@ export class UserController extends Controller {
if (!record) throw notFoundError("User"); if (!record) throw notFoundError("User");
return record; const { importNationality, ...rest } = record;
return Object.assign(rest, {
importNationality: importNationality.map((v) => v.name),
});
} }
@Post() @Post()
@ -477,8 +499,8 @@ export class UserController extends Controller {
} }
const userId = await createUser(username, username, { const userId = await createUser(username, username, {
firstName: body.firstName, firstName: body.firstNameEN,
lastName: body.lastName, lastName: body.lastNameEN,
email: body.email, email: body.email,
requiredActions: ["UPDATE_PASSWORD"], requiredActions: ["UPDATE_PASSWORD"],
enabled: rest.status !== "INACTIVE", enabled: rest.status !== "INACTIVE",
@ -513,6 +535,9 @@ export class UserController extends Controller {
create: rest.responsibleArea.map((v) => ({ area: v })), create: rest.responsibleArea.map((v) => ({ area: v })),
} }
: undefined, : undefined,
importNationality: {
createMany: { data: rest.importNationality?.map((v) => ({ name: v })) || [] },
},
statusOrder: +(rest.status === "INACTIVE"), statusOrder: +(rest.status === "INACTIVE"),
username, username,
userRole: role.name, userRole: role.name,
@ -668,6 +693,7 @@ export class UserController extends Controller {
const record = await prisma.user.update({ const record = await prisma.user.update({
include: { include: {
importNationality: true,
province: true, province: true,
district: true, district: true,
subDistrict: true, subDistrict: true,
@ -682,6 +708,10 @@ export class UserController extends Controller {
create: rest.responsibleArea.map((v) => ({ area: v })), create: rest.responsibleArea.map((v) => ({ area: v })),
} }
: undefined, : undefined,
importNationality: {
deleteMany: {},
createMany: { data: rest.importNationality?.map((v) => ({ name: v })) || [] },
},
statusOrder: +(rest.status === "INACTIVE"), statusOrder: +(rest.status === "INACTIVE"),
userRole, userRole,
province: connectOrDisconnect(provinceId), province: connectOrDisconnect(provinceId),
@ -933,3 +963,17 @@ export class UserSignatureController extends Controller {
await deleteFile(fileLocation.user.signature(userId)); await deleteFile(fileLocation.user.signature(userId));
} }
} }
@Route("api/v1/user/{userId}/group")
@Tags("User")
@Security("keycloak")
export class UserGroupController extends Controller {
@Get()
async getUserGroup(@Path() userId: string) {
const groupUser = await getGroupUser(userId);
if (!Array.isArray(groupUser))
throw new Error("Failed. Cannot get user group(s) data from the server.");
return groupUser;
}
}

View file

@ -30,6 +30,7 @@ import {
connectOrNot, connectOrNot,
queryOrNot, queryOrNot,
whereAddressQuery, whereAddressQuery,
whereDateQuery,
} from "../utils/relation"; } from "../utils/relation";
import { isUsedError, notFoundError, relationError } from "../utils/error"; import { isUsedError, notFoundError, relationError } from "../utils/error";
import { import {
@ -195,18 +196,20 @@ export class CustomerBranchController extends Controller {
@Query() page: number = 1, @Query() page: number = 1,
@Query() pageSize: number = 30, @Query() pageSize: number = 30,
@Query() activeRegisBranchOnly?: boolean, @Query() activeRegisBranchOnly?: boolean,
@Query() startDate?: Date,
@Query() endDate?: Date,
) { ) {
const where = { const where = {
OR: queryOrNot<Prisma.CustomerBranchWhereInput[]>(query, [ OR: queryOrNot<Prisma.CustomerBranchWhereInput[]>(query, [
{ customerName: { contains: query } }, { customerName: { contains: query, mode: "insensitive" } },
{ registerName: { contains: query } }, { registerName: { contains: query, mode: "insensitive" } },
{ registerNameEN: { contains: query } }, { registerNameEN: { contains: query, mode: "insensitive" } },
{ email: { contains: query } }, { email: { contains: query, mode: "insensitive" } },
{ code: { contains: query } }, { code: { contains: query, mode: "insensitive" } },
{ firstName: { contains: query } }, { firstName: { contains: query, mode: "insensitive" } },
{ firstNameEN: { contains: query } }, { firstNameEN: { contains: query, mode: "insensitive" } },
{ lastName: { contains: query } }, { lastName: { contains: query, mode: "insensitive" } },
{ lastNameEN: { contains: query } }, { lastNameEN: { contains: query, mode: "insensitive" } },
...whereAddressQuery(query), ...whereAddressQuery(query),
]), ]),
AND: { AND: {
@ -229,6 +232,7 @@ export class CustomerBranchController extends Controller {
subDistrict: zipCode ? { zipCode } : undefined, subDistrict: zipCode ? { zipCode } : undefined,
...filterStatus(activeRegisBranchOnly ? Status.ACTIVE : status), ...filterStatus(activeRegisBranchOnly ? Status.ACTIVE : status),
}, },
...whereDateQuery(startDate, endDate),
} satisfies Prisma.CustomerBranchWhereInput; } satisfies Prisma.CustomerBranchWhereInput;
const [result, total] = await prisma.$transaction([ const [result, total] = await prisma.$transaction([
@ -285,13 +289,15 @@ export class CustomerBranchController extends Controller {
@Query() visa?: boolean, @Query() visa?: boolean,
@Query() page: number = 1, @Query() page: number = 1,
@Query() pageSize: number = 30, @Query() pageSize: number = 30,
@Query() startDate?: Date,
@Query() endDate?: Date,
) { ) {
const where = { const where = {
OR: queryOrNot<Prisma.EmployeeWhereInput[]>(query, [ OR: queryOrNot<Prisma.EmployeeWhereInput[]>(query, [
{ firstName: { contains: query } }, { firstName: { contains: query, mode: "insensitive" } },
{ firstNameEN: { contains: query } }, { firstNameEN: { contains: query, mode: "insensitive" } },
{ lastName: { contains: query } }, { lastName: { contains: query, mode: "insensitive" } },
{ lastNameEN: { contains: query } }, { lastNameEN: { contains: query, mode: "insensitive" } },
...whereAddressQuery(query), ...whereAddressQuery(query),
]), ]),
AND: { AND: {
@ -300,6 +306,7 @@ export class CustomerBranchController extends Controller {
subDistrict: zipCode ? { zipCode } : undefined, subDistrict: zipCode ? { zipCode } : undefined,
gender, gender,
}, },
...whereDateQuery(startDate, endDate),
} satisfies Prisma.EmployeeWhereInput; } satisfies Prisma.EmployeeWhereInput;
const [result, total] = await prisma.$transaction([ const [result, total] = await prisma.$transaction([

View file

@ -36,7 +36,7 @@ import {
setFile, setFile,
} from "../utils/minio"; } from "../utils/minio";
import { isUsedError, notFoundError, relationError } from "../utils/error"; import { isUsedError, notFoundError, relationError } from "../utils/error";
import { connectOrNot, queryOrNot } from "../utils/relation"; import { connectOrNot, queryOrNot, whereDateQuery } from "../utils/relation";
const MANAGE_ROLES = [ const MANAGE_ROLES = [
"system", "system",
@ -165,17 +165,19 @@ export class CustomerController extends Controller {
@Query() includeBranch: boolean = false, @Query() includeBranch: boolean = false,
@Query() company: boolean = false, @Query() company: boolean = false,
@Query() activeBranchOnly?: boolean, @Query() activeBranchOnly?: boolean,
@Query() startDate?: Date,
@Query() endDate?: Date,
) { ) {
const where = { const where = {
OR: queryOrNot<Prisma.CustomerWhereInput[]>(query, [ OR: queryOrNot<Prisma.CustomerWhereInput[]>(query, [
{ branch: { some: { namePrefix: { contains: query } } } }, { branch: { some: { namePrefix: { contains: query, mode: "insensitive" } } } },
{ branch: { some: { customerName: { contains: query } } } }, { branch: { some: { customerName: { contains: query, mode: "insensitive" } } } },
{ branch: { some: { registerName: { contains: query } } } }, { branch: { some: { registerName: { contains: query, mode: "insensitive" } } } },
{ branch: { some: { registerNameEN: { contains: query } } } }, { branch: { some: { registerNameEN: { contains: query, mode: "insensitive" } } } },
{ branch: { some: { firstName: { contains: query } } } }, { branch: { some: { firstName: { contains: query, mode: "insensitive" } } } },
{ branch: { some: { firstNameEN: { contains: query } } } }, { branch: { some: { firstNameEN: { contains: query, mode: "insensitive" } } } },
{ branch: { some: { lastName: { contains: query } } } }, { branch: { some: { lastName: { contains: query, mode: "insensitive" } } } },
{ branch: { some: { lastNameEN: { contains: query } } } }, { branch: { some: { lastNameEN: { contains: query, mode: "insensitive" } } } },
]), ]),
AND: { AND: {
customerType, customerType,
@ -188,6 +190,7 @@ export class CustomerController extends Controller {
: permissionCond(req.user, { activeOnly: activeBranchOnly }), : permissionCond(req.user, { activeOnly: activeBranchOnly }),
}, },
}, },
...whereDateQuery(startDate, endDate),
} satisfies Prisma.CustomerWhereInput; } satisfies Prisma.CustomerWhereInput;
const [result, total] = await prisma.$transaction([ const [result, total] = await prisma.$transaction([

View file

@ -30,6 +30,7 @@ import {
connectOrNot, connectOrNot,
queryOrNot, queryOrNot,
whereAddressQuery, whereAddressQuery,
whereDateQuery,
} from "../utils/relation"; } from "../utils/relation";
import { isUsedError, notFoundError, relationError } from "../utils/error"; import { isUsedError, notFoundError, relationError } from "../utils/error";
import { import {
@ -73,6 +74,7 @@ type EmployeeCreate = {
dateOfBirth?: Date | null; dateOfBirth?: Date | null;
gender: string; gender: string;
nationality: string; nationality: string;
otherNationality?: string;
namePrefix?: string | null; namePrefix?: string | null;
firstName?: string; firstName?: string;
@ -109,6 +111,7 @@ type EmployeeUpdate = {
dateOfBirth?: Date; dateOfBirth?: Date;
gender?: string; gender?: string;
nationality?: string; nationality?: string;
otherNationality?: string;
namePrefix?: string | null; namePrefix?: string | null;
firstName?: string; firstName?: string;
@ -116,7 +119,7 @@ type EmployeeUpdate = {
middleName?: string | null; middleName?: string | null;
middleNameEN?: string | null; middleNameEN?: string | null;
lastName?: string; lastName?: string;
lastNameEN: string; lastNameEN?: string;
addressEN?: string; addressEN?: string;
address?: string; address?: string;
@ -154,6 +157,8 @@ export class EmployeeController extends Controller {
@Query() customerBranchId?: string, @Query() customerBranchId?: string,
@Query() status?: Status, @Query() status?: Status,
@Query() query: string = "", @Query() query: string = "",
@Query() startDate?: Date,
@Query() endDate?: Date,
) { ) {
return await prisma.employee return await prisma.employee
.groupBy({ .groupBy({
@ -163,13 +168,13 @@ export class EmployeeController extends Controller {
OR: queryOrNot<Prisma.EmployeeWhereInput[]>(query, [ OR: queryOrNot<Prisma.EmployeeWhereInput[]>(query, [
{ {
employeePassport: { employeePassport: {
some: { number: { contains: query } }, some: { number: { contains: query, mode: "insensitive" } },
}, },
}, },
{ firstName: { contains: query } }, { firstName: { contains: query, mode: "insensitive" } },
{ firstNameEN: { contains: query } }, { firstNameEN: { contains: query, mode: "insensitive" } },
{ lastName: { contains: query } }, { lastName: { contains: query, mode: "insensitive" } },
{ lastNameEN: { contains: query } }, { lastNameEN: { contains: query, mode: "insensitive" } },
...whereAddressQuery(query), ...whereAddressQuery(query),
]), ]),
AND: { AND: {
@ -183,6 +188,7 @@ export class EmployeeController extends Controller {
}, },
}, },
}, },
...whereDateQuery(startDate, endDate),
}, },
}) })
.then((res) => .then((res) =>
@ -208,6 +214,8 @@ export class EmployeeController extends Controller {
@Query() page: number = 1, @Query() page: number = 1,
@Query() pageSize: number = 30, @Query() pageSize: number = 30,
@Query() activeOnly?: boolean, @Query() activeOnly?: boolean,
@Query() startDate?: Date,
@Query() endDate?: Date,
) { ) {
return this.listByCriteria( return this.listByCriteria(
req, req,
@ -222,6 +230,8 @@ export class EmployeeController extends Controller {
page, page,
pageSize, pageSize,
activeOnly, activeOnly,
startDate,
endDate,
); );
} }
@ -240,6 +250,8 @@ export class EmployeeController extends Controller {
@Query() page: number = 1, @Query() page: number = 1,
@Query() pageSize: number = 30, @Query() pageSize: number = 30,
@Query() activeOnly?: boolean, @Query() activeOnly?: boolean,
@Query() startDate?: Date,
@Query() endDate?: Date,
@Body() @Body()
body?: { body?: {
passport?: string[]; passport?: string[];
@ -252,13 +264,13 @@ export class EmployeeController extends Controller {
...(queryOrNot<Prisma.EmployeeWhereInput[]>(query, [ ...(queryOrNot<Prisma.EmployeeWhereInput[]>(query, [
{ {
employeePassport: { employeePassport: {
some: { number: { contains: query } }, some: { number: { contains: query, mode: "insensitive" } },
}, },
}, },
{ firstName: { contains: query } }, { firstName: { contains: query, mode: "insensitive" } },
{ firstNameEN: { contains: query } }, { firstNameEN: { contains: query, mode: "insensitive" } },
{ lastName: { contains: query } }, { lastName: { contains: query, mode: "insensitive" } },
{ lastNameEN: { contains: query } }, { lastNameEN: { contains: query, mode: "insensitive" } },
...whereAddressQuery(query), ...whereAddressQuery(query),
]) ?? []), ]) ?? []),
...(queryOrNot<Prisma.EmployeeWhereInput[]>(!!body, [ ...(queryOrNot<Prisma.EmployeeWhereInput[]>(!!body, [
@ -288,6 +300,7 @@ export class EmployeeController extends Controller {
subDistrict: zipCode ? { zipCode } : undefined, subDistrict: zipCode ? { zipCode } : undefined,
gender, gender,
}, },
...whereDateQuery(startDate, endDate),
} satisfies Prisma.EmployeeWhereInput; } satisfies Prisma.EmployeeWhereInput;
const [result, total] = await prisma.$transaction([ const [result, total] = await prisma.$transaction([

View file

@ -43,6 +43,7 @@ type EmployeePassportPayload = {
workerStatus: string; workerStatus: string;
nationality: string; nationality: string;
otherNationality: string;
namePrefix?: string | null; namePrefix?: string | null;
firstName: string; firstName: string;
firstNameEN: string; firstNameEN: string;

View file

@ -24,7 +24,7 @@ import HttpError from "../interfaces/http-error";
import HttpStatus from "../interfaces/http-status"; import HttpStatus from "../interfaces/http-status";
import { notFoundError } from "../utils/error"; import { notFoundError } from "../utils/error";
import { filterStatus } from "../services/prisma"; import { filterStatus } from "../services/prisma";
import { queryOrNot } from "../utils/relation"; import { queryOrNot, whereDateQuery } from "../utils/relation";
type WorkflowPayload = { type WorkflowPayload = {
name: string; name: string;
@ -37,6 +37,7 @@ type WorkflowPayload = {
attributes?: { [key: string]: any }; attributes?: { [key: string]: any };
responsiblePersonId?: string[]; responsiblePersonId?: string[];
responsibleInstitution?: string[]; responsibleInstitution?: string[];
responsibleGroup?: string[];
messengerByArea?: boolean; messengerByArea?: boolean;
}[]; }[];
registeredBranchId?: string; registeredBranchId?: string;
@ -58,13 +59,15 @@ export class FlowTemplateController extends Controller {
@Query() status?: Status, @Query() status?: Status,
@Query() query = "", @Query() query = "",
@Query() activeOnly?: boolean, @Query() activeOnly?: boolean,
@Query() startDate?: Date,
@Query() endDate?: Date,
) { ) {
const where = { const where = {
OR: queryOrNot(query, [ OR: queryOrNot(query, [
{ name: { contains: query } }, { name: { contains: query, mode: "insensitive" } },
{ {
step: { step: {
some: { name: { contains: query } }, some: { name: { contains: query, mode: "insensitive" } },
}, },
}, },
]), ]),
@ -74,6 +77,7 @@ export class FlowTemplateController extends Controller {
OR: permissionCondCompany(req.user, { activeOnly: true }), OR: permissionCondCompany(req.user, { activeOnly: true }),
}, },
}, },
...whereDateQuery(startDate, endDate),
} satisfies Prisma.WorkflowTemplateWhereInput; } satisfies Prisma.WorkflowTemplateWhereInput;
const [result, total] = await prisma.$transaction([ const [result, total] = await prisma.$transaction([
prisma.workflowTemplate.findMany({ prisma.workflowTemplate.findMany({
@ -86,6 +90,7 @@ export class FlowTemplateController extends Controller {
include: { user: true }, include: { user: true },
}, },
responsibleInstitution: true, responsibleInstitution: true,
responsibleGroup: true,
}, },
orderBy: { order: "asc" }, orderBy: { order: "asc" },
}, },
@ -103,6 +108,7 @@ export class FlowTemplateController extends Controller {
step: r.step.map((v) => ({ step: r.step.map((v) => ({
...v, ...v,
responsibleInstitution: v.responsibleInstitution.map((institution) => institution.group), responsibleInstitution: v.responsibleInstitution.map((institution) => institution.group),
responsibleGroup: v.responsibleGroup.map((group) => group.group),
})), })),
})), })),
page, page,
@ -123,6 +129,7 @@ export class FlowTemplateController extends Controller {
include: { user: true }, include: { user: true },
}, },
responsibleInstitution: true, responsibleInstitution: true,
responsibleGroup: true,
}, },
}, },
}, },
@ -137,6 +144,7 @@ export class FlowTemplateController extends Controller {
step: record.step.map((v) => ({ step: record.step.map((v) => ({
...v, ...v,
responsibleInstitution: v.responsibleInstitution.map((institution) => institution.group), responsibleInstitution: v.responsibleInstitution.map((institution) => institution.group),
responsibleGroup: v.responsibleGroup.map((group) => group.group),
})), })),
}; };
} }
@ -212,6 +220,9 @@ export class FlowTemplateController extends Controller {
responsibleInstitution: { responsibleInstitution: {
create: v.responsibleInstitution?.map((group) => ({ group })), create: v.responsibleInstitution?.map((group) => ({ group })),
}, },
responsibleGroup: {
create: v.responsibleGroup?.map((group) => ({ group })),
},
})), })),
}, },
}, },
@ -292,6 +303,10 @@ export class FlowTemplateController extends Controller {
deleteMany: {}, deleteMany: {},
create: v.responsibleInstitution?.map((group) => ({ group })), create: v.responsibleInstitution?.map((group) => ({ group })),
}, },
responsibleGroup: {
deleteMany: {},
create: v.responsibleGroup?.map((group) => ({ group })),
},
}, },
})), })),
}, },

View file

@ -17,7 +17,7 @@ import {
} from "tsoa"; } from "tsoa";
import prisma from "../db"; import prisma from "../db";
import { isUsedError, notFoundError } from "../utils/error"; import { isUsedError, notFoundError } from "../utils/error";
import { queryOrNot } from "../utils/relation"; import { queryOrNot, whereDateQuery } from "../utils/relation";
import { RequestWithUser } from "../interfaces/user"; import { RequestWithUser } from "../interfaces/user";
import { deleteFile, fileLocation, getFile, getPresigned, listFile, setFile } from "../utils/minio"; import { deleteFile, fileLocation, getFile, getPresigned, listFile, setFile } from "../utils/minio";
import HttpError from "../interfaces/http-error"; import HttpError from "../interfaces/http-error";
@ -108,8 +108,19 @@ export class InstitutionController extends Controller {
@Query() status?: Status, @Query() status?: Status,
@Query() activeOnly?: boolean, @Query() activeOnly?: boolean,
@Query() group?: string, @Query() group?: string,
@Query() startDate?: Date,
@Query() endDate?: Date,
) { ) {
return this.getInstitutionListByCriteria(query, page, pageSize, status, activeOnly, group); return this.getInstitutionListByCriteria(
query,
page,
pageSize,
status,
activeOnly,
group,
startDate,
endDate,
);
} }
@Post("list") @Post("list")
@ -122,6 +133,8 @@ export class InstitutionController extends Controller {
@Query() status?: Status, @Query() status?: Status,
@Query() activeOnly?: boolean, @Query() activeOnly?: boolean,
@Query() group?: string, @Query() group?: string,
@Query() startDate?: Date,
@Query() endDate?: Date,
@Body() @Body()
body?: { body?: {
group?: string[]; group?: string[];
@ -131,9 +144,10 @@ export class InstitutionController extends Controller {
...filterStatus(activeOnly ? Status.ACTIVE : status), ...filterStatus(activeOnly ? Status.ACTIVE : status),
group: body?.group ? { in: body.group } : group, group: body?.group ? { in: body.group } : group,
OR: queryOrNot<Prisma.InstitutionWhereInput[]>(query, [ OR: queryOrNot<Prisma.InstitutionWhereInput[]>(query, [
{ name: { contains: query } }, { name: { contains: query, mode: "insensitive" } },
{ code: { contains: query, mode: "insensitive" } }, { code: { contains: query, mode: "insensitive" } },
]), ]),
...whereDateQuery(startDate, endDate),
} satisfies Prisma.InstitutionWhereInput; } satisfies Prisma.InstitutionWhereInput;
const [result, total] = await prisma.$transaction([ const [result, total] = await prisma.$transaction([
@ -178,6 +192,7 @@ export class InstitutionController extends Controller {
body: InstitutionPayload & { body: InstitutionPayload & {
status?: Status; status?: Status;
}, },
@Request() req: RequestWithUser,
) { ) {
return await prisma.$transaction(async (tx) => { return await prisma.$transaction(async (tx) => {
const last = await tx.runningNo.upsert({ const last = await tx.runningNo.upsert({
@ -194,6 +209,8 @@ export class InstitutionController extends Controller {
return await tx.institution.create({ return await tx.institution.create({
include: { include: {
bank: true, bank: true,
createdBy: true,
updatedBy: true,
}, },
data: { data: {
...body, ...body,
@ -204,6 +221,8 @@ export class InstitutionController extends Controller {
data: body.bank ?? [], data: body.bank ?? [],
}, },
}, },
createdByUserId: req.user.sub,
updatedByUserId: req.user.sub,
}, },
}); });
}); });

View file

@ -21,6 +21,7 @@ import {
createPermCondition, createPermCondition,
} from "../services/permission"; } from "../services/permission";
import { PaymentStatus } from "../generated/kysely/types"; import { PaymentStatus } from "../generated/kysely/types";
import { whereDateQuery } from "../utils/relation";
type InvoicePayload = { type InvoicePayload = {
quotationId: string; quotationId: string;
@ -95,23 +96,25 @@ export class InvoiceController extends Controller {
@Query() quotationId?: string, @Query() quotationId?: string,
@Query() debitNoteId?: string, @Query() debitNoteId?: string,
@Query() pay?: boolean, @Query() pay?: boolean,
@Query() startDate?: Date,
@Query() endDate?: Date,
) { ) {
const where: Prisma.InvoiceWhereInput = { const where: Prisma.InvoiceWhereInput = {
OR: [ OR: [
{ code: { contains: query, mode: "insensitive" } }, { code: { contains: query, mode: "insensitive" } },
{ quotation: { workName: { contains: query } } }, { quotation: { workName: { contains: query, mode: "insensitive" } } },
{ {
quotation: { quotation: {
customerBranch: { customerBranch: {
OR: [ OR: [
{ code: { contains: query, mode: "insensitive" } }, { code: { contains: query, mode: "insensitive" } },
{ customerName: { contains: query } }, { customerName: { contains: query, mode: "insensitive" } },
{ registerName: { contains: query } }, { registerName: { contains: query, mode: "insensitive" } },
{ registerNameEN: { contains: query } }, { registerNameEN: { contains: query, mode: "insensitive" } },
{ firstName: { contains: query } }, { firstName: { contains: query, mode: "insensitive" } },
{ firstNameEN: { contains: query } }, { firstNameEN: { contains: query, mode: "insensitive" } },
{ lastName: { contains: query } }, { lastName: { contains: query, mode: "insensitive" } },
{ lastNameEN: { contains: query } }, { lastNameEN: { contains: query, mode: "insensitive" } },
], ],
}, },
}, },
@ -132,6 +135,7 @@ export class InvoiceController extends Controller {
OR: permissionCondCompany(req.user), OR: permissionCondCompany(req.user),
}, },
}, },
...whereDateQuery(startDate, endDate),
}; };
const [result, total] = await prisma.$transaction([ const [result, total] = await prisma.$transaction([

View file

@ -11,6 +11,7 @@ import {
Security, Security,
Tags, Tags,
Query, Query,
UploadedFile,
} from "tsoa"; } from "tsoa";
import { Prisma, Product, Status } from "@prisma/client"; import { Prisma, Product, Status } from "@prisma/client";
@ -27,7 +28,8 @@ import { isSystem } from "../utils/keycloak";
import { filterStatus } from "../services/prisma"; import { filterStatus } from "../services/prisma";
import { deleteFile, deleteFolder, fileLocation, getFile, listFile, setFile } from "../utils/minio"; import { deleteFile, deleteFolder, fileLocation, getFile, listFile, setFile } from "../utils/minio";
import { isUsedError, notFoundError, relationError } from "../utils/error"; import { isUsedError, notFoundError, relationError } from "../utils/error";
import { queryOrNot } from "../utils/relation"; import { queryOrNot, whereDateQuery } from "../utils/relation";
import spreadsheet from "../utils/spreadsheet";
const MANAGE_ROLES = [ const MANAGE_ROLES = [
"system", "system",
@ -139,6 +141,8 @@ export class ProductController extends Controller {
@Query() orderField?: keyof Product, @Query() orderField?: keyof Product,
@Query() orderBy?: "asc" | "desc", @Query() orderBy?: "asc" | "desc",
@Query() activeOnly?: boolean, @Query() activeOnly?: boolean,
@Query() startDate?: Date,
@Query() endDate?: Date,
) { ) {
// NOTE: will be used to scope product within product group that is shared between branch but not company when select shared product if user is system // NOTE: will be used to scope product within product group that is shared between branch but not company when select shared product if user is system
const targetGroup = const targetGroup =
@ -154,8 +158,8 @@ export class ProductController extends Controller {
const where = { const where = {
OR: queryOrNot<Prisma.ProductWhereInput[]>(query, [ OR: queryOrNot<Prisma.ProductWhereInput[]>(query, [
{ name: { contains: query } }, { name: { contains: query, mode: "insensitive" } },
{ detail: { contains: query } }, { detail: { contains: query, mode: "insensitive" } },
{ code: { contains: query, mode: "insensitive" } }, { code: { contains: query, mode: "insensitive" } },
]), ]),
AND: { AND: {
@ -194,6 +198,7 @@ export class ProductController extends Controller {
: []), : []),
], ],
}, },
...whereDateQuery(startDate, endDate),
} satisfies Prisma.ProductWhereInput; } satisfies Prisma.ProductWhereInput;
const [result, total] = await prisma.$transaction([ const [result, total] = await prisma.$transaction([
@ -444,6 +449,146 @@ export class ProductController extends Controller {
where: { id: productId }, where: { id: productId },
}); });
} }
@Post("import-product")
@Security("keycloak", MANAGE_ROLES)
async importProduct(
@Request() req: RequestWithUser,
@UploadedFile() file: Express.Multer.File,
@Query() productGroupId: string,
) {
if (!file?.buffer) throw notFoundError("File");
const buffer = new Uint8Array(file.buffer).buffer;
const dataFile = await spreadsheet.readExcel(buffer, {
header: true,
worksheet: "Sheet1",
});
let dataName: string[] = [];
const data = dataFile.map((item: any) => {
dataName.push(item.name);
return {
...item,
expenseType:
item.expenseType === "ค่าธรรมเนียม"
? "fee"
: item.expenseType === "ค่าบริการ"
? "serviceFee"
: "processingFee",
shared: item.shared === "ใช่" ? true : false,
price:
typeof item.price === "number"
? item.price
: +parseFloat(item.price?.replace(",", "") || "0").toFixed(6),
calcVat: item.calcVat === "ใช่" ? true : false,
vatIncluded: item.vatIncluded === "รวม" ? true : false,
agentPrice:
typeof item.agentPrice === "number"
? item.agentPrice
: +parseFloat(item.agentPrice?.replace(",", "") || "0").toFixed(6),
agentPriceCalcVat: item.agentPriceCalcVat === "ใช่" ? true : false,
agentPriceVatIncluded: item.agentPriceVatIncluded === "รวม" ? true : false,
serviceCharge:
typeof item.serviceCharge === "number"
? item.serviceCharge
: +parseFloat(item.serviceCharge?.replace(",", "") || "0").toFixed(6),
serviceChargeCalcVat: item.serviceChargeCalcVat === "ใช่" ? true : false,
serviceChargeVatIncluded: item.serviceChargeVatIncluded === "รวม" ? true : false,
};
});
const [productGroup, productSameName] = await prisma.$transaction([
prisma.productGroup.findFirst({
include: {
registeredBranch: {
include: branchRelationPermInclude(req.user),
},
createdBy: true,
updatedBy: true,
},
where: { id: productGroupId },
}),
prisma.product.findMany({
where: {
productGroup: {
id: productGroupId,
registeredBranch: {
OR: permissionCondCompany(req.user),
},
},
name: { in: dataName },
},
}),
]);
if (!productGroup) throw relationError("Product Group");
await permissionCheck(req.user, productGroup.registeredBranch);
let dataProduct: ProductCreate[] = [];
const record = await prisma.$transaction(
async (tx) => {
const branch = productGroup.registeredBranch;
const company = (branch.headOffice || branch).code;
await Promise.all(
data.map(async (item) => {
const dataDuplicate = productSameName.some(
(v) => v.code.slice(0, -3) === item.code.toUpperCase() && v.name === item.name,
);
if (!dataDuplicate) {
const last = await tx.runningNo.upsert({
where: {
key: `PRODUCT_${company}_${item.code.toLocaleUpperCase()}`,
},
create: {
key: `PRODUCT_${company}_${item.code.toLocaleUpperCase()}`,
value: 1,
},
update: { value: { increment: 1 } },
});
dataProduct.push({
...item,
code: `${item.code.toLocaleUpperCase()}${last.value.toString().padStart(3, "0")}`,
createdByUserId: req.user.sub,
updatedByUserId: req.user.sub,
productGroupId: productGroupId,
});
}
}),
);
return await prisma.product.createManyAndReturn({
data: dataProduct,
include: {
createdBy: true,
updatedBy: true,
},
});
},
{
isolationLevel: Prisma.TransactionIsolationLevel.Serializable,
},
);
if (productGroup.status === "CREATED") {
await prisma.productGroup.update({
include: {
createdBy: true,
updatedBy: true,
},
where: { id: productGroupId },
data: { status: Status.ACTIVE },
});
}
this.setStatus(HttpStatus.CREATED);
return record;
}
} }
@Route("api/v1/product/{productId}") @Route("api/v1/product/{productId}")

View file

@ -27,7 +27,7 @@ import {
} from "../services/permission"; } from "../services/permission";
import { filterStatus } from "../services/prisma"; import { filterStatus } from "../services/prisma";
import { isUsedError, notFoundError, relationError } from "../utils/error"; import { isUsedError, notFoundError, relationError } from "../utils/error";
import { queryOrNot } from "../utils/relation"; import { queryOrNot, whereDateQuery } from "../utils/relation";
type ProductGroupCreate = { type ProductGroupCreate = {
name: string; name: string;
@ -90,11 +90,13 @@ export class ProductGroup extends Controller {
@Query() page: number = 1, @Query() page: number = 1,
@Query() pageSize: number = 30, @Query() pageSize: number = 30,
@Query() activeOnly?: boolean, @Query() activeOnly?: boolean,
@Query() startDate?: Date,
@Query() endDate?: Date,
) { ) {
const where = { const where = {
OR: queryOrNot<Prisma.ProductGroupWhereInput[]>(query, [ OR: queryOrNot<Prisma.ProductGroupWhereInput[]>(query, [
{ name: { contains: query } }, { name: { contains: query, mode: "insensitive" } },
{ detail: { contains: query } }, { detail: { contains: query, mode: "insensitive" } },
{ code: { contains: query, mode: "insensitive" } }, { code: { contains: query, mode: "insensitive" } },
]), ]),
AND: [ AND: [
@ -105,6 +107,7 @@ export class ProductGroup extends Controller {
: { OR: permissionCond(req.user, { activeOnly }) }, : { OR: permissionCond(req.user, { activeOnly }) },
}, },
], ],
...whereDateQuery(startDate, endDate),
} satisfies Prisma.ProductGroupWhereInput; } satisfies Prisma.ProductGroupWhereInput;
const [result, total] = await prisma.$transaction([ const [result, total] = await prisma.$transaction([

View file

@ -24,7 +24,7 @@ import HttpError from "../interfaces/http-error";
import HttpStatus from "../interfaces/http-status"; import HttpStatus from "../interfaces/http-status";
import { notFoundError } from "../utils/error"; import { notFoundError } from "../utils/error";
import { filterStatus } from "../services/prisma"; import { filterStatus } from "../services/prisma";
import { queryOrNot } from "../utils/relation"; import { queryOrNot, whereDateQuery } from "../utils/relation";
type PropertyPayload = { type PropertyPayload = {
name: string; name: string;
@ -49,15 +49,21 @@ export class PropertiesController extends Controller {
@Query() status?: Status, @Query() status?: Status,
@Query() query = "", @Query() query = "",
@Query() activeOnly?: boolean, @Query() activeOnly?: boolean,
@Query() startDate?: Date,
@Query() endDate?: Date,
) { ) {
const where = { const where = {
OR: queryOrNot(query, [{ name: { contains: query } }, { nameEN: { contains: query } }]), OR: queryOrNot(query, [
{ name: { contains: query, mode: "insensitive" } },
{ nameEN: { contains: query, mode: "insensitive" } },
]),
AND: { AND: {
...filterStatus(activeOnly ? Status.ACTIVE : status), ...filterStatus(activeOnly ? Status.ACTIVE : status),
registeredBranch: { registeredBranch: {
OR: permissionCondCompany(req.user, { activeOnly: true }), OR: permissionCondCompany(req.user, { activeOnly: true }),
}, },
}, },
...whereDateQuery(startDate, endDate),
} satisfies Prisma.PropertyWhereInput; } satisfies Prisma.PropertyWhereInput;
const [result, total] = await prisma.$transaction([ const [result, total] = await prisma.$transaction([
prisma.property.findMany({ prisma.property.findMany({

View file

@ -4,6 +4,7 @@ import { Prisma } from "@prisma/client";
import { notFoundError } from "../utils/error"; import { notFoundError } from "../utils/error";
import { RequestWithUser } from "../interfaces/user"; import { RequestWithUser } from "../interfaces/user";
import { createPermCondition } from "../services/permission"; import { createPermCondition } from "../services/permission";
import { whereDateQuery } from "../utils/relation";
const permissionCondCompany = createPermCondition((_) => true); const permissionCondCompany = createPermCondition((_) => true);
@ -21,6 +22,8 @@ export class ReceiptController extends Controller {
@Query() quotationId?: string, @Query() quotationId?: string,
@Query() debitNoteId?: string, @Query() debitNoteId?: string,
@Query() debitNoteOnly?: boolean, @Query() debitNoteOnly?: boolean,
@Query() startDate?: Date,
@Query() endDate?: Date,
) { ) {
const where: Prisma.PaymentWhereInput = { const where: Prisma.PaymentWhereInput = {
paymentStatus: "PaymentSuccess", paymentStatus: "PaymentSuccess",
@ -33,6 +36,7 @@ export class ReceiptController extends Controller {
}, },
}, },
}, },
...whereDateQuery(startDate, endDate),
}; };
const [result, total] = await prisma.$transaction([ const [result, total] = await prisma.$transaction([

View file

@ -36,7 +36,7 @@ import {
listFile, listFile,
setFile, setFile,
} from "../utils/minio"; } from "../utils/minio";
import { queryOrNot } from "../utils/relation"; import { queryOrNot, whereDateQuery } from "../utils/relation";
const MANAGE_ROLES = [ const MANAGE_ROLES = [
"system", "system",
@ -164,6 +164,8 @@ export class ServiceController extends Controller {
@Query() fullDetail?: boolean, @Query() fullDetail?: boolean,
@Query() activeOnly?: boolean, @Query() activeOnly?: boolean,
@Query() shared?: boolean, @Query() shared?: boolean,
@Query() startDate?: Date,
@Query() endDate?: Date,
) { ) {
// NOTE: will be used to scope product within product group that is shared between branch but not company when select shared product if user is system // NOTE: will be used to scope product within product group that is shared between branch but not company when select shared product if user is system
const targetGroup = const targetGroup =
@ -179,8 +181,8 @@ export class ServiceController extends Controller {
const where = { const where = {
OR: queryOrNot<Prisma.ServiceWhereInput[]>(query, [ OR: queryOrNot<Prisma.ServiceWhereInput[]>(query, [
{ name: { contains: query } }, { name: { contains: query, mode: "insensitive" } },
{ detail: { contains: query } }, { detail: { contains: query, mode: "insensitive" } },
{ code: { contains: query, mode: "insensitive" } }, { code: { contains: query, mode: "insensitive" } },
]), ]),
AND: { AND: {
@ -219,6 +221,7 @@ export class ServiceController extends Controller {
: []), : []),
], ],
}, },
...whereDateQuery(startDate, endDate),
} satisfies Prisma.ServiceWhereInput; } satisfies Prisma.ServiceWhereInput;
const [result, total] = await prisma.$transaction([ const [result, total] = await prisma.$transaction([

View file

@ -18,6 +18,7 @@ import prisma from "../db";
import { RequestWithUser } from "../interfaces/user"; import { RequestWithUser } from "../interfaces/user";
import HttpStatus from "../interfaces/http-status"; import HttpStatus from "../interfaces/http-status";
import { isUsedError, notFoundError } from "../utils/error"; import { isUsedError, notFoundError } from "../utils/error";
import { whereDateQuery } from "../utils/relation";
type WorkCreate = { type WorkCreate = {
order: number; order: number;
@ -45,9 +46,12 @@ export class WorkController extends Controller {
@Query() query: string = "", @Query() query: string = "",
@Query() page: number = 1, @Query() page: number = 1,
@Query() pageSize: number = 30, @Query() pageSize: number = 30,
@Query() startDate?: Date,
@Query() endDate?: Date,
) { ) {
const where = { const where = {
OR: [{ name: { contains: query }, serviceId: baseOnly ? null : undefined }], OR: [{ name: { contains: query }, serviceId: baseOnly ? null : undefined }],
...whereDateQuery(startDate, endDate),
} satisfies Prisma.WorkWhereInput; } satisfies Prisma.WorkWhereInput;
const [result, total] = await prisma.$transaction([ const [result, total] = await prisma.$transaction([

View file

@ -105,6 +105,7 @@ export class QuotationPayment extends Controller {
async updatePayment( async updatePayment(
@Path() paymentId: string, @Path() paymentId: string,
@Body() body: { amount?: number; date?: Date; paymentStatus?: PaymentStatus }, @Body() body: { amount?: number; date?: Date; paymentStatus?: PaymentStatus },
@Request() req: RequestWithUser,
) { ) {
const record = await prisma.payment.findUnique({ const record = await prisma.payment.findUnique({
where: { id: paymentId }, where: { id: paymentId },
@ -164,6 +165,7 @@ export class QuotationPayment extends Controller {
code: lastReceipt code: lastReceipt
? `RE${year}${month}${lastReceipt.value.toString().padStart(6, "0")}` ? `RE${year}${month}${lastReceipt.value.toString().padStart(6, "0")}`
: undefined, : undefined,
updatedByUserId: req.user.sub,
}, },
}); });

View file

@ -25,7 +25,7 @@ import {
import { isSystem } from "../utils/keycloak"; import { isSystem } from "../utils/keycloak";
import { isUsedError, notFoundError, relationError } from "../utils/error"; import { isUsedError, notFoundError, relationError } from "../utils/error";
import { precisionRound } from "../utils/arithmetic"; import { precisionRound } from "../utils/arithmetic";
import { queryOrNot } from "../utils/relation"; import { queryOrNot, whereDateQuery } from "../utils/relation";
import { deleteFile, fileLocation, getFile, getPresigned, listFile, setFile } from "../utils/minio"; import { deleteFile, fileLocation, getFile, getPresigned, listFile, setFile } from "../utils/minio";
import HttpError from "../interfaces/http-error"; import HttpError from "../interfaces/http-error";
import HttpStatus from "../interfaces/http-status"; import HttpStatus from "../interfaces/http-status";
@ -55,13 +55,14 @@ type QuotationCreate = {
dateOfBirth: Date; dateOfBirth: Date;
gender: string; gender: string;
nationality: string; nationality: string;
otherNationality?: string;
namePrefix?: string; namePrefix?: string;
firstName: string; firstName: string;
firstNameEN: string; firstNameEN: string;
middleName?: string; middleName?: string;
middleNameEN?: string; middleNameEN?: string;
lastName: string; lastName: string;
lastNameEN: string; lastNameEN?: string;
} }
)[]; )[];
@ -112,14 +113,15 @@ type QuotationUpdate = {
dateOfBirth: Date; dateOfBirth: Date;
gender: string; gender: string;
nationality: string; nationality: string;
otherNationality?: string;
namePrefix?: string; namePrefix?: string;
firstName: string; firstName?: string;
firstNameEN: string; firstNameEN: string;
middleName?: string; middleName?: string;
middleNameEN?: string; middleNameEN?: string;
lastName: string; lastName?: string;
lastNameEN: string; lastNameEN?: string;
} }
)[]; )[];
@ -206,20 +208,22 @@ export class QuotationController extends Controller {
@Query() forDebitNote?: boolean, @Query() forDebitNote?: boolean,
@Query() code?: string, @Query() code?: string,
@Query() query = "", @Query() query = "",
@Query() startDate?: Date,
@Query() endDate?: Date,
) { ) {
const where = { const where = {
OR: queryOrNot<Prisma.QuotationWhereInput[]>(query, [ OR: queryOrNot<Prisma.QuotationWhereInput[]>(query, [
{ code: { contains: query, mode: "insensitive" } }, { code: { contains: query, mode: "insensitive" } },
{ workName: { contains: query } }, { workName: { contains: query, mode: "insensitive" } },
{ {
customerBranch: { customerBranch: {
OR: [ OR: [
{ code: { contains: query, mode: "insensitive" } }, { code: { contains: query, mode: "insensitive" } },
{ customerName: { contains: query } }, { customerName: { contains: query, mode: "insensitive" } },
{ firstName: { contains: query } }, { firstName: { contains: query, mode: "insensitive" } },
{ firstNameEN: { contains: query } }, { firstNameEN: { contains: query, mode: "insensitive" } },
{ lastName: { contains: query } }, { lastName: { contains: query, mode: "insensitive" } },
{ lastNameEN: { contains: query } }, { lastNameEN: { contains: query, mode: "insensitive" } },
], ],
}, },
}, },
@ -253,6 +257,7 @@ export class QuotationController extends Controller {
}, },
} }
: undefined, : undefined,
...whereDateQuery(startDate, endDate),
} satisfies Prisma.QuotationWhereInput; } satisfies Prisma.QuotationWhereInput;
const [result, total] = await prisma.$transaction([ const [result, total] = await prisma.$transaction([
@ -1005,6 +1010,7 @@ export class QuotationActionController extends Controller {
dateOfBirth: Date; dateOfBirth: Date;
gender: string; gender: string;
nationality: string; nationality: string;
otherNationality?: string;
namePrefix?: string; namePrefix?: string;
firstName: string; firstName: string;
firstNameEN: string; firstNameEN: string;
@ -1027,6 +1033,7 @@ export class QuotationActionController extends Controller {
dateOfBirth: Date; dateOfBirth: Date;
gender: string; gender: string;
nationality: string; nationality: string;
otherNationality?: string;
namePrefix?: string; namePrefix?: string;
firstName: string; firstName: string;
firstNameEN: string; firstNameEN: string;

View file

@ -27,11 +27,12 @@ import {
createPermCheck, createPermCheck,
createPermCondition, createPermCondition,
} from "../services/permission"; } from "../services/permission";
import { queryOrNot } from "../utils/relation"; import { queryOrNot, whereDateQuery } from "../utils/relation";
import { notFoundError } from "../utils/error"; import { notFoundError } from "../utils/error";
import { deleteFile, fileLocation, getFile, getPresigned, listFile, setFile } from "../utils/minio"; import { deleteFile, fileLocation, getFile, getPresigned, listFile, setFile } from "../utils/minio";
import HttpError from "../interfaces/http-error"; import HttpError from "../interfaces/http-error";
import HttpStatus from "../interfaces/http-status"; import HttpStatus from "../interfaces/http-status";
import { getGroupUser } from "../services/keycloak";
// User in company can edit. // User in company can edit.
const permissionCheck = createPermCheck((_) => true); const permissionCheck = createPermCheck((_) => true);
@ -80,6 +81,9 @@ export class RequestDataController extends Controller {
@Query() requestDataStatus?: RequestDataStatus, @Query() requestDataStatus?: RequestDataStatus,
@Query() quotationId?: string, @Query() quotationId?: string,
@Query() code?: string, @Query() code?: string,
@Query() incomplete?: boolean,
@Query() startDate?: Date,
@Query() endDate?: Date,
) { ) {
const where = { const where = {
OR: queryOrNot<Prisma.RequestDataWhereInput[]>(query, [ OR: queryOrNot<Prisma.RequestDataWhereInput[]>(query, [
@ -91,34 +95,40 @@ export class RequestDataController extends Controller {
customerBranch: { customerBranch: {
OR: [ OR: [
{ code: { contains: query, mode: "insensitive" } }, { code: { contains: query, mode: "insensitive" } },
{ customerName: { contains: query } }, { customerName: { contains: query, mode: "insensitive" } },
{ registerName: { contains: query } }, { registerName: { contains: query, mode: "insensitive" } },
{ registerNameEN: { contains: query } }, { registerNameEN: { contains: query, mode: "insensitive" } },
{ firstName: { contains: query } }, { firstName: { contains: query, mode: "insensitive" } },
{ firstNameEN: { contains: query } }, { firstNameEN: { contains: query, mode: "insensitive" } },
{ lastName: { contains: query } }, { lastName: { contains: query, mode: "insensitive" } },
{ lastNameEN: { contains: query } }, { lastNameEN: { contains: query, mode: "insensitive" } },
], ],
}, },
}, },
},
{
employee: { employee: {
OR: [ OR: [
{ {
employeePassport: { employeePassport: {
some: { number: { contains: query } }, some: { number: { contains: query, mode: "insensitive" } },
}, },
}, },
{ code: { contains: query, mode: "insensitive" } }, { code: { contains: query, mode: "insensitive" } },
{ firstName: { contains: query } }, { firstName: { contains: query, mode: "insensitive" } },
{ firstNameEN: { contains: query } }, { firstNameEN: { contains: query, mode: "insensitive" } },
{ lastName: { contains: query } }, { lastName: { contains: query, mode: "insensitive" } },
{ lastNameEN: { contains: query } }, { lastNameEN: { contains: query, mode: "insensitive" } },
], ],
}, },
}, },
]), ]),
code, code,
requestDataStatus, requestDataStatus: incomplete
? {
notIn: [RequestDataStatus.Completed, RequestDataStatus.Canceled],
}
: requestDataStatus,
requestWork: responsibleOnly requestWork: responsibleOnly
? { ? {
some: { some: {
@ -127,9 +137,24 @@ export class RequestDataController extends Controller {
workflow: { workflow: {
step: { step: {
some: { some: {
responsiblePerson: { OR: [
some: { userId: req.user.sub }, {
}, responsiblePerson: {
some: { userId: req.user.sub },
},
},
{
responsibleGroup: {
some: {
group: {
in: await getGroupUser(req.user.sub).then((r) =>
r.map(({ name }: { name: string }) => name),
),
},
},
},
},
],
}, },
}, },
}, },
@ -142,6 +167,7 @@ export class RequestDataController extends Controller {
id: quotationId, id: quotationId,
registeredBranch: { OR: permissionCond(req.user) }, registeredBranch: { OR: permissionCond(req.user) },
}, },
...whereDateQuery(startDate, endDate),
} satisfies Prisma.RequestDataWhereInput; } satisfies Prisma.RequestDataWhereInput;
const [result, total] = await prisma.$transaction([ const [result, total] = await prisma.$transaction([
@ -164,6 +190,7 @@ export class RequestDataController extends Controller {
include: { user: true }, include: { user: true },
}, },
responsibleInstitution: true, responsibleInstitution: true,
responsibleGroup: true,
}, },
}, },
}, },
@ -182,6 +209,20 @@ export class RequestDataController extends Controller {
employeePassport: { employeePassport: {
orderBy: { expireDate: "desc" }, orderBy: { expireDate: "desc" },
}, },
customerBranch: {
include: {
province: {
include: {
employmentOffice: true,
},
},
district: {
include: {
employmentOffice: true,
},
},
},
},
}, },
}, },
}, },
@ -192,7 +233,24 @@ export class RequestDataController extends Controller {
prisma.requestData.count({ where }), prisma.requestData.count({ where }),
]); ]);
return { result, page, pageSize, total }; const dataRequestData = result.map((item) => {
const employee = item.employee;
const dataOffice =
employee.customerBranch.district?.employmentOffice.at(0) ??
employee.customerBranch.province?.employmentOffice.at(0);
return {
...item,
dataOffice,
};
});
return {
result: dataRequestData,
page,
pageSize,
total,
};
} }
@Get("{requestDataId}") @Get("{requestDataId}")
@ -231,7 +289,7 @@ export class RequestDataController extends Controller {
return record; return record;
} }
@Post("updata-messenger") @Post("update-messenger")
@Security("keycloak") @Security("keycloak")
async updateRequestData( async updateRequestData(
@Request() req: RequestWithUser, @Request() req: RequestWithUser,
@ -748,6 +806,7 @@ export class RequestListController extends Controller {
include: { user: true }, include: { user: true },
}, },
responsibleInstitution: true, responsibleInstitution: true,
responsibleGroup: true,
}, },
}, },
}, },
@ -808,6 +867,7 @@ export class RequestListController extends Controller {
include: { user: true }, include: { user: true },
}, },
responsibleInstitution: true, responsibleInstitution: true,
responsibleGroup: true,
}, },
}, },
}, },

View file

@ -42,7 +42,7 @@ import {
listFile, listFile,
setFile, setFile,
} from "../utils/minio"; } from "../utils/minio";
import { queryOrNot } from "../utils/relation"; import { queryOrNot, whereDateQuery } from "../utils/relation";
const MANAGE_ROLES = ["system", "head_of_admin", "admin", "document_checker"]; const MANAGE_ROLES = ["system", "head_of_admin", "admin", "document_checker"];
@ -86,6 +86,8 @@ export class TaskController extends Controller {
@Query() pageSize = 30, @Query() pageSize = 30,
@Query() assignedByUserId?: string, @Query() assignedByUserId?: string,
@Query() taskOrderStatus?: TaskOrderStatus, @Query() taskOrderStatus?: TaskOrderStatus,
@Query() startDate?: Date,
@Query() endDate?: Date,
) { ) {
return this.getTaskOrderListByCriteria( return this.getTaskOrderListByCriteria(
req, req,
@ -94,6 +96,8 @@ export class TaskController extends Controller {
pageSize, pageSize,
assignedByUserId, assignedByUserId,
taskOrderStatus, taskOrderStatus,
startDate,
endDate,
); );
} }
@ -106,6 +110,8 @@ export class TaskController extends Controller {
@Query() pageSize = 30, @Query() pageSize = 30,
@Query() assignedUserId?: string, @Query() assignedUserId?: string,
@Query() taskOrderStatus?: TaskOrderStatus, @Query() taskOrderStatus?: TaskOrderStatus,
@Query() startDate?: Date,
@Query() endDate?: Date,
@Body() body?: { code?: string[] }, @Body() body?: { code?: string[] },
) { ) {
const where = { const where = {
@ -121,10 +127,11 @@ export class TaskController extends Controller {
code: body?.code ? { in: body.code } : undefined, code: body?.code ? { in: body.code } : undefined,
OR: queryOrNot(query, [ OR: queryOrNot(query, [
{ code: { contains: query, mode: "insensitive" } }, { code: { contains: query, mode: "insensitive" } },
{ taskName: { contains: query } }, { taskName: { contains: query, mode: "insensitive" } },
{ contactName: { contains: query } }, { contactName: { contains: query, mode: "insensitive" } },
{ contactTel: { contains: query } }, { contactTel: { contains: query, mode: "insensitive" } },
]), ]),
...whereDateQuery(startDate, endDate),
} satisfies Prisma.TaskOrderWhereInput; } satisfies Prisma.TaskOrderWhereInput;
const [result, total] = await prisma.$transaction([ const [result, total] = await prisma.$transaction([
@ -193,6 +200,7 @@ export class TaskController extends Controller {
step: { step: {
include: { include: {
value: true, value: true,
responsibleGroup: true,
responsiblePerson: { responsiblePerson: {
include: { user: true }, include: { user: true },
}, },
@ -690,12 +698,31 @@ export class TaskActionController extends Controller {
if (!record) throw notFoundError("Task Order"); if (!record) throw notFoundError("Task Order");
await prisma.$transaction(async (tx) => { await prisma.$transaction(async (tx) => {
const last = await tx.runningNo.upsert({
where: {
key: "TASK_RI",
},
create: {
key: "TASK_RI",
value: 1,
},
update: {
value: { increment: 1 },
},
});
const current = new Date();
const year = `${current.getFullYear()}`.padStart(2, "0");
const month = `${current.getMonth() + 1}`.padStart(2, "0");
const code = `RI${year}${month}${last.value.toString().padStart(6, "0")}`;
await Promise.all([ await Promise.all([
tx.taskOrder.update({ tx.taskOrder.update({
where: { id: taskOrderId }, where: { id: taskOrderId },
data: { data: {
urgent: false, urgent: false,
taskOrderStatus: TaskOrderStatus.Complete, taskOrderStatus: TaskOrderStatus.Complete,
codeProductReceived: code,
userTask: { userTask: {
updateMany: { updateMany: {
where: { taskOrderId }, where: { taskOrderId },
@ -979,6 +1006,8 @@ export class UserTaskController extends Controller {
@Query() page = 1, @Query() page = 1,
@Query() pageSize = 30, @Query() pageSize = 30,
@Query() userTaskStatus?: UserTaskStatus, @Query() userTaskStatus?: UserTaskStatus,
@Query() startDate?: Date,
@Query() endDate?: Date,
) { ) {
const where = { const where = {
taskList: { taskList: {
@ -1021,10 +1050,11 @@ export class UserTaskController extends Controller {
: undefined, : undefined,
OR: queryOrNot(query, [ OR: queryOrNot(query, [
{ code: { contains: query, mode: "insensitive" } }, { code: { contains: query, mode: "insensitive" } },
{ taskName: { contains: query } }, { taskName: { contains: query, mode: "insensitive" } },
{ contactName: { contains: query } }, { contactName: { contains: query, mode: "insensitive" } },
{ contactTel: { contains: query } }, { contactTel: { contains: query, mode: "insensitive" } },
]), ]),
...whereDateQuery(startDate, endDate),
} satisfies Prisma.TaskOrderWhereInput; } satisfies Prisma.TaskOrderWhereInput;
const [result, total] = await prisma.$transaction([ const [result, total] = await prisma.$transaction([

View file

@ -35,7 +35,7 @@ import {
} from "../utils/minio"; } from "../utils/minio";
import { notFoundError } from "../utils/error"; import { notFoundError } from "../utils/error";
import { CreditNotePaybackType, CreditNoteStatus, Prisma, RequestDataStatus } from "@prisma/client"; import { CreditNotePaybackType, CreditNoteStatus, Prisma, RequestDataStatus } from "@prisma/client";
import { queryOrNot } from "../utils/relation"; import { queryOrNot, whereDateQuery } from "../utils/relation";
import { PaybackStatus, RequestWorkStatus } from "../generated/kysely/types"; import { PaybackStatus, RequestWorkStatus } from "../generated/kysely/types";
const MANAGE_ROLES = [ const MANAGE_ROLES = [
@ -121,6 +121,8 @@ export class CreditNoteController extends Controller {
@Query() query: string = "", @Query() query: string = "",
@Query() quotationId?: string, @Query() quotationId?: string,
@Query() creditNoteStatus?: CreditNoteStatus, @Query() creditNoteStatus?: CreditNoteStatus,
@Query() startDate?: Date,
@Query() endDate?: Date,
) { ) {
return await this.getCreditNoteListByCriteria( return await this.getCreditNoteListByCriteria(
req, req,
@ -129,6 +131,8 @@ export class CreditNoteController extends Controller {
query, query,
quotationId, quotationId,
creditNoteStatus, creditNoteStatus,
startDate,
endDate,
); );
} }
@ -142,6 +146,8 @@ export class CreditNoteController extends Controller {
@Query() query: string = "", @Query() query: string = "",
@Query() quotationId?: string, @Query() quotationId?: string,
@Query() creditNoteStatus?: CreditNoteStatus, @Query() creditNoteStatus?: CreditNoteStatus,
@Query() startDate?: Date,
@Query() endDate?: Date,
@Body() body?: {}, @Body() body?: {},
) { ) {
const where = { const where = {
@ -153,17 +159,17 @@ export class CreditNoteController extends Controller {
request: { request: {
OR: queryOrNot<Prisma.RequestDataWhereInput[]>(query, [ OR: queryOrNot<Prisma.RequestDataWhereInput[]>(query, [
{ quotation: { code: { contains: query, mode: "insensitive" } } }, { quotation: { code: { contains: query, mode: "insensitive" } } },
{ quotation: { workName: { contains: query } } }, { quotation: { workName: { contains: query, mode: "insensitive" } } },
{ {
quotation: { quotation: {
customerBranch: { customerBranch: {
OR: [ OR: [
{ code: { contains: query, mode: "insensitive" } }, { code: { contains: query, mode: "insensitive" } },
{ customerName: { contains: query } }, { customerName: { contains: query, mode: "insensitive" } },
{ firstName: { contains: query } }, { firstName: { contains: query, mode: "insensitive" } },
{ firstNameEN: { contains: query } }, { firstNameEN: { contains: query, mode: "insensitive" } },
{ lastName: { contains: query } }, { lastName: { contains: query, mode: "insensitive" } },
{ lastNameEN: { contains: query } }, { lastNameEN: { contains: query, mode: "insensitive" } },
], ],
}, },
}, },
@ -171,14 +177,14 @@ export class CreditNoteController extends Controller {
OR: [ OR: [
{ {
employeePassport: { employeePassport: {
some: { number: { contains: query } }, some: { number: { contains: query, mode: "insensitive" } },
}, },
}, },
{ code: { contains: query, mode: "insensitive" } }, { code: { contains: query, mode: "insensitive" } },
{ firstName: { contains: query } }, { firstName: { contains: query, mode: "insensitive" } },
{ firstNameEN: { contains: query } }, { firstNameEN: { contains: query, mode: "insensitive" } },
{ lastName: { contains: query } }, { lastName: { contains: query, mode: "insensitive" } },
{ lastNameEN: { contains: query } }, { lastNameEN: { contains: query, mode: "insensitive" } },
], ],
}, },
}, },
@ -199,6 +205,7 @@ export class CreditNoteController extends Controller {
}, },
}, },
}, },
...whereDateQuery(startDate, endDate),
} satisfies Prisma.CreditNoteWhereInput; } satisfies Prisma.CreditNoteWhereInput;
const [result, total] = await prisma.$transaction([ const [result, total] = await prisma.$transaction([

View file

@ -36,7 +36,7 @@ import {
setFile, setFile,
} from "../utils/minio"; } from "../utils/minio";
import { isUsedError, notFoundError, relationError } from "../utils/error"; import { isUsedError, notFoundError, relationError } from "../utils/error";
import { queryOrNot } from "../utils/relation"; import { queryOrNot, whereDateQuery } from "../utils/relation";
import { isSystem } from "../utils/keycloak"; import { isSystem } from "../utils/keycloak";
import { precisionRound } from "../utils/arithmetic"; import { precisionRound } from "../utils/arithmetic";
@ -76,6 +76,7 @@ type DebitNoteCreate = {
dateOfBirth: Date; dateOfBirth: Date;
gender: string; gender: string;
nationality: string; nationality: string;
otherNationality: string;
namePrefix?: string; namePrefix?: string;
firstName: string; firstName: string;
firstNameEN: string; firstNameEN: string;
@ -111,13 +112,14 @@ type DebitNoteUpdate = {
dateOfBirth: Date; dateOfBirth: Date;
gender: string; gender: string;
nationality: string; nationality: string;
otherNationality: string;
namePrefix?: string; namePrefix?: string;
firstName: string; firstName?: string;
firstNameEN: string; firstNameEN: string;
middleName?: string; middleName?: string;
middleNameEN?: string; middleNameEN?: string;
lastName: string; lastName?: string;
lastNameEN: string; lastNameEN?: string;
} }
)[]; )[];
@ -168,6 +170,8 @@ export class DebitNoteController extends Controller {
@Query() payCondition?: PayCondition, @Query() payCondition?: PayCondition,
@Query() includeRegisteredBranch?: boolean, @Query() includeRegisteredBranch?: boolean,
@Query() code?: string, @Query() code?: string,
@Query() startDate?: Date,
@Query() endDate?: Date,
) { ) {
return await this.getDebitNoteListByCriteria( return await this.getDebitNoteListByCriteria(
req, req,
@ -179,6 +183,8 @@ export class DebitNoteController extends Controller {
payCondition, payCondition,
includeRegisteredBranch, includeRegisteredBranch,
code, code,
startDate,
endDate,
); );
} }
@ -195,21 +201,23 @@ export class DebitNoteController extends Controller {
@Query() payCondition?: PayCondition, @Query() payCondition?: PayCondition,
@Query() includeRegisteredBranch?: boolean, @Query() includeRegisteredBranch?: boolean,
@Query() code?: string, @Query() code?: string,
@Query() startDate?: Date,
@Query() endDate?: Date,
@Body() body?: {}, @Body() body?: {},
) { ) {
const where = { const where = {
OR: queryOrNot<Prisma.QuotationWhereInput[]>(query, [ OR: queryOrNot<Prisma.QuotationWhereInput[]>(query, [
{ code: { contains: query, mode: "insensitive" } }, { code: { contains: query, mode: "insensitive" } },
{ workName: { contains: query } }, { workName: { contains: query, mode: "insensitive" } },
{ {
customerBranch: { customerBranch: {
OR: [ OR: [
{ code: { contains: query, mode: "insensitive" } }, { code: { contains: query, mode: "insensitive" } },
{ customerName: { contains: query } }, { customerName: { contains: query, mode: "insensitive" } },
{ firstName: { contains: query } }, { firstName: { contains: query, mode: "insensitive" } },
{ firstNameEN: { contains: query } }, { firstNameEN: { contains: query, mode: "insensitive" } },
{ lastName: { contains: query } }, { lastName: { contains: query, mode: "insensitive" } },
{ lastNameEN: { contains: query } }, { lastNameEN: { contains: query, mode: "insensitive" } },
], ],
}, },
}, },
@ -220,6 +228,7 @@ export class DebitNoteController extends Controller {
debitNoteQuotationId: quotationId, debitNoteQuotationId: quotationId,
registeredBranch: isSystem(req.user) ? undefined : { OR: permissionCond(req.user) }, registeredBranch: isSystem(req.user) ? undefined : { OR: permissionCond(req.user) },
quotationStatus: status, quotationStatus: status,
...whereDateQuery(startDate, endDate),
} satisfies Prisma.QuotationWhereInput; } satisfies Prisma.QuotationWhereInput;
const [result, total] = await prisma.$transaction([ const [result, total] = await prisma.$transaction([

View file

@ -25,7 +25,7 @@ import {
TaskStatus, TaskStatus,
RequestWorkStatus, RequestWorkStatus,
} from "@prisma/client"; } from "@prisma/client";
import { queryOrNot, whereAddressQuery } from "../utils/relation"; import { queryOrNot, whereAddressQuery, whereDateQuery } from "../utils/relation";
import { filterStatus } from "../services/prisma"; import { filterStatus } from "../services/prisma";
// import { RequestWorkStatus } from "../generated/kysely/types"; // import { RequestWorkStatus } from "../generated/kysely/types";
import { deleteFile, fileLocation, getFile, getPresigned, listFile, setFile } from "../utils/minio"; import { deleteFile, fileLocation, getFile, getPresigned, listFile, setFile } from "../utils/minio";
@ -51,6 +51,8 @@ export class LineController extends Controller {
@Query() page: number = 1, @Query() page: number = 1,
@Query() pageSize: number = 30, @Query() pageSize: number = 30,
@Query() activeOnly?: boolean, @Query() activeOnly?: boolean,
@Query() startDate?: Date,
@Query() endDate?: Date,
) { ) {
const where = { const where = {
OR: !!query OR: !!query
@ -58,13 +60,13 @@ export class LineController extends Controller {
...(queryOrNot<Prisma.EmployeeWhereInput[]>(query, [ ...(queryOrNot<Prisma.EmployeeWhereInput[]>(query, [
{ {
employeePassport: { employeePassport: {
some: { number: { contains: query } }, some: { number: { contains: query, mode: "insensitive" } },
}, },
}, },
{ firstName: { contains: query } }, { firstName: { contains: query, mode: "insensitive" } },
{ firstNameEN: { contains: query } }, { firstNameEN: { contains: query, mode: "insensitive" } },
{ lastName: { contains: query } }, { lastName: { contains: query, mode: "insensitive" } },
{ lastNameEN: { contains: query } }, { lastNameEN: { contains: query, mode: "insensitive" } },
...whereAddressQuery(query), ...whereAddressQuery(query),
]) ?? []), ]) ?? []),
] ]
@ -87,6 +89,7 @@ export class LineController extends Controller {
subDistrict: zipCode ? { zipCode } : undefined, subDistrict: zipCode ? { zipCode } : undefined,
gender, gender,
}, },
...whereDateQuery(startDate, endDate),
} satisfies Prisma.EmployeeWhereInput; } satisfies Prisma.EmployeeWhereInput;
const [result, total] = await prisma.$transaction([ const [result, total] = await prisma.$transaction([
@ -173,24 +176,26 @@ export class LineController extends Controller {
@Query() requestDataStatus?: RequestDataStatus, @Query() requestDataStatus?: RequestDataStatus,
@Query() quotationId?: string, @Query() quotationId?: string,
@Query() code?: string, @Query() code?: string,
@Query() startDate?: Date,
@Query() endDate?: Date,
) { ) {
const where = { const where = {
OR: queryOrNot<Prisma.RequestDataWhereInput[]>(query, [ OR: queryOrNot<Prisma.RequestDataWhereInput[]>(query, [
{ code: { contains: query, mode: "insensitive" } }, { code: { contains: query, mode: "insensitive" } },
{ quotation: { code: { contains: query, mode: "insensitive" } } }, { quotation: { code: { contains: query, mode: "insensitive" } } },
{ quotation: { workName: { contains: query } } }, { quotation: { workName: { contains: query, mode: "insensitive" } } },
{ {
quotation: { quotation: {
customerBranch: { customerBranch: {
OR: [ OR: [
{ code: { contains: query, mode: "insensitive" } }, { code: { contains: query, mode: "insensitive" } },
{ customerName: { contains: query } }, { customerName: { contains: query, mode: "insensitive" } },
{ registerName: { contains: query } }, { registerName: { contains: query, mode: "insensitive" } },
{ registerNameEN: { contains: query } }, { registerNameEN: { contains: query, mode: "insensitive" } },
{ firstName: { contains: query } }, { firstName: { contains: query, mode: "insensitive" } },
{ firstNameEN: { contains: query } }, { firstNameEN: { contains: query, mode: "insensitive" } },
{ lastName: { contains: query } }, { lastName: { contains: query, mode: "insensitive" } },
{ lastNameEN: { contains: query } }, { lastNameEN: { contains: query, mode: "insensitive" } },
], ],
}, },
}, },
@ -198,14 +203,14 @@ export class LineController extends Controller {
OR: [ OR: [
{ {
employeePassport: { employeePassport: {
some: { number: { contains: query } }, some: { number: { contains: query, mode: "insensitive" } },
}, },
}, },
{ code: { contains: query, mode: "insensitive" } }, { code: { contains: query, mode: "insensitive" } },
{ firstName: { contains: query } }, { firstName: { contains: query, mode: "insensitive" } },
{ firstNameEN: { contains: query } }, { firstNameEN: { contains: query, mode: "insensitive" } },
{ lastName: { contains: query } }, { lastName: { contains: query, mode: "insensitive" } },
{ lastNameEN: { contains: query } }, { lastNameEN: { contains: query, mode: "insensitive" } },
], ],
}, },
}, },
@ -247,6 +252,7 @@ export class LineController extends Controller {
], ],
}, },
}, },
...whereDateQuery(startDate, endDate),
} satisfies Prisma.RequestDataWhereInput; } satisfies Prisma.RequestDataWhereInput;
const [result, total] = await prisma.$transaction([ const [result, total] = await prisma.$transaction([
@ -604,6 +610,8 @@ export class LineController extends Controller {
@Query() includeRegisteredBranch?: boolean, @Query() includeRegisteredBranch?: boolean,
@Query() code?: string, @Query() code?: string,
@Query() query = "", @Query() query = "",
@Query() startDate?: Date,
@Query() endDate?: Date,
) { ) {
const where = { const where = {
OR: OR:
@ -611,16 +619,16 @@ export class LineController extends Controller {
? [ ? [
...(queryOrNot<Prisma.QuotationWhereInput[]>(query, [ ...(queryOrNot<Prisma.QuotationWhereInput[]>(query, [
{ code: { contains: query, mode: "insensitive" } }, { code: { contains: query, mode: "insensitive" } },
{ workName: { contains: query } }, { workName: { contains: query, mode: "insensitive" } },
{ {
customerBranch: { customerBranch: {
OR: [ OR: [
{ code: { contains: query, mode: "insensitive" } }, { code: { contains: query, mode: "insensitive" } },
{ customerName: { contains: query } }, { customerName: { contains: query, mode: "insensitive" } },
{ firstName: { contains: query } }, { firstName: { contains: query, mode: "insensitive" } },
{ firstNameEN: { contains: query } }, { firstNameEN: { contains: query, mode: "insensitive" } },
{ lastName: { contains: query } }, { lastName: { contains: query, mode: "insensitive" } },
{ lastNameEN: { contains: query } }, { lastNameEN: { contains: query, mode: "insensitive" } },
], ],
}, },
}, },
@ -660,6 +668,7 @@ export class LineController extends Controller {
}, },
} }
: undefined, : undefined,
...whereDateQuery(startDate, endDate),
} satisfies Prisma.QuotationWhereInput; } satisfies Prisma.QuotationWhereInput;
const [result, total] = await prisma.$transaction([ const [result, total] = await prisma.$transaction([
@ -1368,3 +1377,65 @@ export class LineQuotationFileController extends Controller {
return await deleteFile(fileLocation.quotation.attachment(quotationId, name)); return await deleteFile(fileLocation.quotation.attachment(quotationId, name));
} }
} }
@Route("api/v1/line/payment/{paymentId}/attachment")
@Tags("Line")
export class PaymentFileLineController extends Controller {
private async checkPermission(_user: RequestWithUser["user"], id: string) {
const data = await prisma.payment.findUnique({
include: {
invoice: {
include: {
quotation: true,
},
},
},
where: { id },
});
if (!data) throw notFoundError("Payment");
return { paymentId: id, quotationId: data.invoice.quotationId };
}
@Get()
@Security("line")
async listAttachment(@Request() req: RequestWithUser, @Path() paymentId: string) {
const { quotationId } = await this.checkPermission(req.user, paymentId);
return await listFile(fileLocation.quotation.payment(quotationId, paymentId));
}
@Head("{name}")
async headAttachment(
@Request() req: RequestWithUser,
@Path() paymentId: string,
@Path() name: string,
) {
const data = await prisma.payment.findUnique({
where: { id: paymentId },
include: { invoice: true },
});
if (!data) throw notFoundError("Payment");
return req.res?.redirect(
await getPresigned(
"head",
fileLocation.quotation.payment(data.invoice.quotationId, paymentId, name),
),
);
}
@Get("{name}")
async getAttachment(
@Request() req: RequestWithUser,
@Path() paymentId: string,
@Path() name: string,
) {
const data = await prisma.payment.findUnique({
where: { id: paymentId },
include: { invoice: true },
});
if (!data) throw notFoundError("Payment");
return req.res?.redirect(
await getFile(fileLocation.quotation.payment(data.invoice.quotationId, paymentId, name)),
);
}
}

View file

@ -0,0 +1,25 @@
import express from "express";
import { Controller, Get, Path, Request, Route } from "tsoa";
import { getFile } from "../utils/minio";
@Route("api/v1/troubleshooting")
export class TroubleshootingController extends Controller {
@Get()
async get(@Request() req: express.Request) {
return req.res?.redirect(await getFile(".troubleshooting/toc.json"));
}
@Get("{category}/assets/{name}")
async getAsset(@Request() req: express.Request, @Path() category: string, @Path() name: string) {
return req.res?.redirect(await getFile(`.troubleshooting/${category}/assets/${name}`));
}
@Get("{category}/page/{page}")
async getContent(
@Request() req: express.Request,
@Path() category: string,
@Path() page: string,
) {
return req.res?.redirect(await getFile(`.troubleshooting/${category}/${page}.md`));
}
}

View file

@ -346,6 +346,64 @@ export async function removeUserRoles(userId: string, roles: { id: string; name:
return true; return true;
} }
export async function getGroup(query: string) {
const res = await fetch(`${KC_URL}/admin/realms/${KC_REALM}/groups?${query}`, {
headers: {
authorization: `Bearer ${await getToken()}`,
"content-type": `application/json`,
},
method: "GET",
});
const dataMainGroup = await res.json();
const fetchSubGroups = async (group: any) => {
let fullSubGroup = await Promise.all(
group.subGroups.map((subGroupsData: any) => {
if (group.subGroupCount > 0) {
return fetchSubGroups(subGroupsData);
} else {
return {
id: subGroupsData.id,
name: subGroupsData.name,
path: subGroupsData.path,
subGroupCount: subGroupsData.subGroupCount,
subGroups: [],
};
}
}),
);
return {
id: group.id,
name: group.name,
path: group.path,
subGroupCount: group.subGroupCount,
subGroups: fullSubGroup,
};
};
const fullMainGroup = await Promise.all(dataMainGroup.map(fetchSubGroups));
return fullMainGroup;
}
export async function getGroupUser(userId: string) {
const res = await fetch(`${KC_URL}/admin/realms/${KC_REALM}/users/${userId}/groups`, {
headers: {
authorization: `Bearer ${await getToken()}`,
"content-type": `application/json`,
},
method: "GET",
});
const data = await res.json();
return data.map((item: any) => {
return {
id: item.id,
name: item.name,
path: item.path,
};
});
}
export default { export default {
createUser, createUser,
listRole, listRole,

View file

@ -10,26 +10,35 @@ export function connectOrDisconnect(id?: string | null) {
export function whereAddressQuery(query: string) { export function whereAddressQuery(query: string) {
return [ return [
{ address: { contains: query } }, { address: { contains: query, mode: "insensitive" } },
{ addressEN: { contains: query } }, { addressEN: { contains: query, mode: "insensitive" } },
{ soi: { contains: query } }, { soi: { contains: query, mode: "insensitive" } },
{ soiEN: { contains: query } }, { soiEN: { contains: query, mode: "insensitive" } },
{ moo: { contains: query } }, { moo: { contains: query, mode: "insensitive" } },
{ mooEN: { contains: query } }, { mooEN: { contains: query, mode: "insensitive" } },
{ street: { contains: query } }, { street: { contains: query, mode: "insensitive" } },
{ streetEN: { contains: query } }, { streetEN: { contains: query, mode: "insensitive" } },
{ province: { name: { contains: query } } }, { province: { name: { contains: query, mode: "insensitive" } } },
{ province: { nameEN: { contains: query } } }, { province: { nameEN: { contains: query, mode: "insensitive" } } },
{ district: { name: { contains: query } } }, { district: { name: { contains: query, mode: "insensitive" } } },
{ district: { nameEN: { contains: query } } }, { district: { nameEN: { contains: query, mode: "insensitive" } } },
{ subDistrict: { name: { contains: query } } }, { subDistrict: { name: { contains: query, mode: "insensitive" } } },
{ subDistrict: { nameEN: { contains: query } } }, { subDistrict: { nameEN: { contains: query, mode: "insensitive" } } },
{ subDistrict: { zipCode: { contains: query } } }, { subDistrict: { zipCode: { contains: query, mode: "insensitive" } } },
]; ] as const;
} }
export function queryOrNot<T>(query: string | boolean, where: T): T | undefined; export function queryOrNot<T>(query: any, where: T): T | undefined;
export function queryOrNot<T, U>(query: string | boolean, where: T, fallback: U): T | U; export function queryOrNot<T, U>(query: any, where: T, fallback: U): T | U;
export function queryOrNot<T, U>(query: string | boolean, where: T, fallback?: U) { export function queryOrNot<T, U>(query: any, where: T, fallback?: U) {
return !!query ? where : fallback; return !!query ? where : fallback;
} }
export function whereDateQuery(startDate: Date | undefined, endDate: Date | undefined) {
return {
createdAt: {
gte: startDate,
lte: endDate,
},
};
}

105
src/utils/spreadsheet.ts Normal file
View file

@ -0,0 +1,105 @@
import Excel from "exceljs";
export default class spreadsheet {
static async readCsv() {
// TODO: read csv
}
/**
* This function read data from excel file.
*
* @param buffer - Excel file.
* @param opts.header - Interprets the first row as the names of the fields.
* @param opts.worksheet - Specifies the worksheet to read. Can be the worksheet's name or its 1-based index.
*
* @returns
*/
static async readExcel<T extends unknown>(
buffer: Excel.Buffer,
opts?: { header?: boolean; worksheet?: number | string },
): Promise<T[]> {
const workbook = new Excel.Workbook();
await workbook.xlsx.load(buffer);
const worksheet = workbook.getWorksheet(opts?.worksheet ?? 1);
if (!worksheet) return [];
const header: Record<number, string | number> = {};
const values: any[] = [];
worksheet.eachRow((row, rowId) => {
if (rowId === 1 && opts?.header !== false) {
row.eachCell((cell, cellId) => {
if (typeof cell.value === "string") {
header[cellId] = nameValue(cell.value);
} else {
header[cellId] = cellId.toString();
}
});
} else {
const data: Record<string | number, Excel.CellValue> = {};
row.eachCell((cell, cellId) => {
data[opts?.header !== false ? header[cellId] : cellId - 1] = cell.value;
});
values.push(opts?.header !== false ? data : Object.values(data));
}
});
return values;
}
}
function nameValue(value: string) {
let code: string;
switch (value) {
case "ชื่อสินค้าและบริการ":
code = "name";
break;
case "ระยะเวลาดำเนินการ":
code = "process";
break;
case "ประเภทค่าใช้จ่าย":
code = "expenseType";
break;
case "รายละเอียด":
code = "detail";
break;
case "หมายเหตุ":
code = "remark";
break;
case "ใช้งานร่วมกัน":
code = "shared";
break;
case "คำนวณภาษีราคาขาย":
code = "calcVat";
break;
case "รวม VAT ราคาขาย":
code = "vatIncluded";
break;
case "ราคาต่อหน่วย (บาท) ราคาขาย":
code = "price";
break;
case "คำนวณภาษีราคาตัวแทน":
code = "agentPriceCalcVat";
break;
case "รวม VAT ราคาตัวแทน":
code = "agentPriceVatIncluded";
break;
case "ราคาต่อหน่วย (บาท) ราคาตัวแทน":
code = "agentPrice";
break;
case "คำนวณภาษีราคาดำเนินการ":
code = "serviceChargeCalcVat";
break;
case "รวม VAT ราคาดำเนินการ":
code = "serviceChargeVatIncluded";
break;
case "ราคาต่อหน่วย (บาท) ราคาดำเนินการ":
code = "serviceCharge";
break;
default:
code = "code";
break;
}
return code;
}