diff --git a/package.json b/package.json index 2f64c4f..7fc3a6c 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "@vitest/ui": "^3.1.4", "nodemon": "^3.1.9", "prettier": "^3.4.2", - "prisma": "^6.3.0", + "prisma": "^6.16.0", "prisma-kysely": "^1.8.0", "ts-node": "^10.9.2", "typescript": "^5.7.2", @@ -40,9 +40,10 @@ "dependencies": { "@elastic/elasticsearch": "^8.17.0", "@fast-csv/parse": "^5.0.2", - "@prisma/client": "^6.3.0", + "@prisma/client": "^6.16.0", "@scalar/express-api-reference": "^0.4.182", "@tsoa/runtime": "^6.6.0", + "@types/html-to-text": "^9.0.4", "canvas": "^3.1.0", "cors": "^2.8.5", "cron": "^3.3.1", @@ -53,6 +54,7 @@ "exceljs": "^4.4.0", "express": "^4.21.2", "fast-jwt": "^5.0.5", + "html-to-text": "^9.0.5", "jsbarcode": "^3.11.6", "json-2-csv": "^5.5.8", "kysely": "^0.27.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8471a8c..8bbe67b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,14 +15,17 @@ importers: specifier: ^5.0.2 version: 5.0.2 '@prisma/client': - specifier: ^6.3.0 - version: 6.3.0(prisma@6.3.0(typescript@5.7.2))(typescript@5.7.2) + specifier: ^6.16.0 + version: 6.16.0(prisma@6.16.0(typescript@5.7.2))(typescript@5.7.2) '@scalar/express-api-reference': specifier: ^0.4.182 version: 0.4.182 '@tsoa/runtime': specifier: ^6.6.0 version: 6.6.0 + '@types/html-to-text': + specifier: ^9.0.4 + version: 9.0.4 canvas: specifier: ^3.1.0 version: 3.1.0 @@ -53,6 +56,9 @@ importers: fast-jwt: specifier: ^5.0.5 version: 5.0.5 + html-to-text: + specifier: ^9.0.5 + version: 9.0.5 jsbarcode: specifier: ^3.11.6 version: 3.11.6 @@ -76,7 +82,7 @@ importers: version: 6.10.0 prisma-extension-kysely: specifier: ^3.0.0 - version: 3.0.0(@prisma/client@6.3.0(prisma@6.3.0(typescript@5.7.2))(typescript@5.7.2)) + version: 3.0.0(@prisma/client@6.16.0(prisma@6.16.0(typescript@5.7.2))(typescript@5.7.2)) promise.any: specifier: ^2.0.6 version: 2.0.6 @@ -127,8 +133,8 @@ importers: specifier: ^3.4.2 version: 3.4.2 prisma: - specifier: ^6.3.0 - version: 6.3.0(typescript@5.7.2) + specifier: ^6.16.0 + version: 6.16.0(typescript@5.7.2) prisma-kysely: specifier: ^1.8.0 version: 1.8.0(encoding@0.1.13) @@ -140,7 +146,7 @@ importers: version: 5.7.2 vitest: specifier: ^3.1.4 - version: 3.1.4(@types/node@20.17.10)(@vitest/ui@3.1.4)(yaml@2.6.1) + version: 3.1.4(@types/node@20.17.10)(@vitest/ui@3.1.4)(jiti@2.5.1)(yaml@2.6.1) packages: @@ -516,8 +522,8 @@ packages: '@polka/url@1.0.0-next.29': resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} - '@prisma/client@6.3.0': - resolution: {integrity: sha512-BY3Fi28PUSk447Bpv22LhZp4HgNPo7NsEN+EteM1CLDnLjig5863jpW+3c3HHLFmml+nB/eJv1CjSriFZ8z7Cg==} + '@prisma/client@6.16.0': + resolution: {integrity: sha512-FYkFJtgwpwJRMxtmrB26y7gtpR372kyChw6lWng5TMmvn5V+uisy0OyllO5EJD1s8lX78V8X3XjhiXOoMLnu3w==} engines: {node: '>=18.18'} peerDependencies: prisma: '*' @@ -528,26 +534,29 @@ packages: typescript: optional: true + '@prisma/config@6.16.0': + resolution: {integrity: sha512-Q9TgfnllVehvQziY9lJwRJLGmziX0OimZUEQ/MhCUBoJMSScj2VivCjw/Of2vlO1FfyaHXxrvjZAr7ASl7DVcw==} + '@prisma/debug@5.3.1': resolution: {integrity: sha512-eYrxqslEKf+wpMFIIHgbcNYuZBXUdiJLA85Or3TwOhgPIN1ZoXT9CwJph3ynW8H1Xg0LkdYLwVmuULCwiMoU5A==} - '@prisma/debug@6.3.0': - resolution: {integrity: sha512-m1lQv//0Rc5RG8TBpNUuLCxC35Ghi5XfpPmL83Gh04/GICHD2J5H2ndMlaljrUNaQDF9dOxIuFAYP1rE9wkXkg==} + '@prisma/debug@6.16.0': + resolution: {integrity: sha512-bxzro5vbVqAPkWyDs2A6GpQtRZunD8tyrLmSAchx9u0b+gWCDY6eV+oh5A0YtYT9245dIxQBswckayHuJG4u3w==} - '@prisma/engines-version@6.3.0-17.acc0b9dd43eb689cbd20c9470515d719db10d0b0': - resolution: {integrity: sha512-R/ZcMuaWZT2UBmgX3Ko6PAV3f8//ZzsjRIG1eKqp3f2rqEqVtCv+mtzuH2rBPUC9ujJ5kCb9wwpxeyCkLcHVyA==} + '@prisma/engines-version@6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43': + resolution: {integrity: sha512-ThvlDaKIVrnrv97ujNFDYiQbeMQpLa0O86HFA2mNoip4mtFqM7U5GSz2ie1i2xByZtvPztJlNRgPsXGeM/kqAA==} '@prisma/engines@5.3.1': resolution: {integrity: sha512-6QkILNyfeeN67BNEPEtkgh3Xo2tm6D7V+UhrkBbRHqKw9CTaz/vvTP/ROwYSP/3JT2MtIutZm/EnhxUiuOPVDA==} - '@prisma/engines@6.3.0': - resolution: {integrity: sha512-RXqYhlZb9sx/xkUfYIZuEPn7sT0WgTxNOuEYQ7AGw3IMpP9QGVEDVsluc/GcNkM8NTJszeqk8AplJzI9lm7Jxw==} + '@prisma/engines@6.16.0': + resolution: {integrity: sha512-RHJGCH/zi017W4CWYWqg0Sv1pquGGFVo8T3auJ9sodDNaiRzbeNldydjaQzszVS8nscdtcvLuJzy7e65C3puqQ==} '@prisma/fetch-engine@5.3.1': resolution: {integrity: sha512-w1yk1YiK8N82Pobdq58b85l6e8akyrkxuzwV9DoiUTRf3gpsuhJJesHc4Yi0WzUC9/3znizl1UfCsI6dhkj3Vw==} - '@prisma/fetch-engine@6.3.0': - resolution: {integrity: sha512-GBy0iT4f1mH31ePzfcpVSUa7JLRTeq4914FG2vR3LqDwRweSm4ja1o5flGDz+eVIa/BNYfkBvRRxv4D6ve6Eew==} + '@prisma/fetch-engine@6.16.0': + resolution: {integrity: sha512-Mx5rml0XRIDizhB9eZxSP8c0nMoXYVITTiJJwxlWn9rNCel8mG8NAqIw+vJlN3gPR+kt3IBkP1SQVsplPPpYrA==} '@prisma/generator-helper@5.3.1': resolution: {integrity: sha512-zrYS0iHLgPlOJjYnd5KvVMMvSS+ktOL39EwooS5EnyvfzwfzxlKCeOUgxTfiKYs0WUWqzEvyNAYtramYgSknsQ==} @@ -555,8 +564,8 @@ packages: '@prisma/get-platform@5.3.1': resolution: {integrity: sha512-3IiZY2BUjKnAuZ0569zppZE6/rZbVAM09//c2nvPbbkGG9MqrirA8fbhhF7tfVmhyVfdmVCHnf/ujWPHJ8B46Q==} - '@prisma/get-platform@6.3.0': - resolution: {integrity: sha512-V8zZ1d0xfyi6FjpNP4AcYuwSpGcdmu35OXWnTPm8IW594PYALzKXHwIa9+o0f+Lo9AecFWrwrwaoYe56UNfTtQ==} + '@prisma/get-platform@6.16.0': + resolution: {integrity: sha512-eaJOOvAoGslSUTjiQrtE9E0hoBdfL43j8SymOGD6LbdrKRNtIoiy6qiBaEr2fNYD+R/Qns7QOwPhl7SVHJayKA==} '@prisma/internals@5.3.1': resolution: {integrity: sha512-zkW73hPHHNrMD21PeYgCTBfMu71vzJf+WtfydtJbS0JVJKyLfOel0iWSQg7wjNeQfccKp+NdHJ/5rTJ4NEUzgA==} @@ -676,6 +685,12 @@ packages: resolution: {integrity: sha512-4mQYkQJO0HHaoFd8Z+vSdQAvYcCJ2bRLN9ewE+GneB8kvoLG/oM3ynroqzGQdoytH8BmhnJwD3aEUagfbK2x5g==} engines: {node: '>=18'} + '@selderee/plugin-htmlparser2@0.11.0': + resolution: {integrity: sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==} + + '@standard-schema/spec@1.0.0': + resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} + '@swc/helpers@0.5.15': resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} @@ -742,6 +757,9 @@ packages: '@types/express@4.17.21': resolution: {integrity: sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==} + '@types/html-to-text@9.0.4': + resolution: {integrity: sha512-pUY3cKH/Nm2yYrEmDlPR1mR7yszjGx4DrwPjQ702C4/D5CwHuZTgZdIdwPkRbcuhs7BAh2L5rg3CL5cbRiGTCQ==} + '@types/http-assert@1.5.6': resolution: {integrity: sha512-TTEwmtjgVbYAzZYWyeHPrrtWnfVkm8tQkP8P21uQifPgMRgjrow3XDEYqucuC8SKZJT7pUnhU/JymvjggxO9vw==} @@ -1072,6 +1090,14 @@ packages: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} + c12@3.1.0: + resolution: {integrity: sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==} + peerDependencies: + magicast: ^0.3.5 + peerDependenciesMeta: + magicast: + optional: true + cac@6.7.14: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} @@ -1121,6 +1147,10 @@ packages: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + chownr@1.1.4: resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} @@ -1128,6 +1158,9 @@ packages: resolution: {integrity: sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==} engines: {node: '>=8'} + citty@0.1.6: + resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} + cjs-module-lexer@1.4.1: resolution: {integrity: sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA==} @@ -1187,6 +1220,13 @@ packages: resolution: {integrity: sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==} engines: {'0': node >= 0.8} + confbox@0.2.2: + resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==} + + consola@3.4.2: + resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} + engines: {node: ^14.18.0 || >=16.10.0} + console-log-level@1.4.1: resolution: {integrity: sha512-VZzbIORbP+PPcN/gg3DXClTLPLg5Slwd5fL2MIc+o1qZ4BXBvWyc6QxPk6T/Mkr6IVjRpoAGf32XxP3ZWMVRcQ==} @@ -1307,6 +1347,14 @@ packages: resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} engines: {node: '>=4.0.0'} + deepmerge-ts@7.1.5: + resolution: {integrity: sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==} + engines: {node: '>=16.0.0'} + + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + define-data-property@1.1.4: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} @@ -1315,6 +1363,9 @@ packages: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} + defu@6.1.4: + resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + del@6.1.1: resolution: {integrity: sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg==} engines: {node: '>=10'} @@ -1323,6 +1374,9 @@ packages: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} + destr@2.0.5: + resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} + destroy@1.2.0: resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} @@ -1347,6 +1401,19 @@ packages: resolution: {integrity: sha512-tTmR3WhROYctuyVReQ+PfCU3zprmC45/VuSVzn8EjovzpRkXYUdXiDatB9M8pasj0V+wuuOyY8bcSHvlQ2GNag==} engines: {node: '>=6'} + dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + + domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + + domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + + domutils@3.2.2: + resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} + dotenv@16.0.3: resolution: {integrity: sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==} engines: {node: '>=12'} @@ -1355,6 +1422,10 @@ packages: resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==} engines: {node: '>=12'} + dotenv@16.6.1: + resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} + engines: {node: '>=12'} + dunder-proto@1.0.0: resolution: {integrity: sha512-9+Sj30DIu+4KvHqMfLUGLFYL2PkURSYMVXJyXe92nFRvlYq5hBjLEhblKB+vkd/WVlUYMWigiY07T91Fkk0+4A==} engines: {node: '>= 0.4'} @@ -1371,6 +1442,9 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + effect@3.16.12: + resolution: {integrity: sha512-N39iBk0K71F9nb442TLbTkjl24FLUzuvx2i1I2RsEAQsdAdUTuUoW0vlfUXgkMTUOnYqKnWcFfqw4hK4Pw27hg==} + elastic-apm-node@3.52.2: resolution: {integrity: sha512-NVFthDcoBOpTwtppF7b+BIeIu4Xon3RBNpddIaJv+DtjL6Q61x4j7ClYdiXjv3XKgyp7yUlOnLjU6PY/EYXwLQ==} engines: {node: '>=8.6.0'} @@ -1381,6 +1455,10 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + empathic@2.0.0: + resolution: {integrity: sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==} + engines: {node: '>=14'} + enabled@2.0.0: resolution: {integrity: sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==} @@ -1398,6 +1476,10 @@ packages: end-of-stream@1.4.4: resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + env-paths@2.2.1: resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} engines: {node: '>=6'} @@ -1495,6 +1577,13 @@ packages: resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==} engines: {node: '>= 0.10.0'} + exsolve@1.0.7: + resolution: {integrity: sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==} + + fast-check@3.23.2: + resolution: {integrity: sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==} + engines: {node: '>=8.0.0'} + fast-csv@4.3.6: resolution: {integrity: sha512-2RNSpuwwsJGP0frGsOmTb9oUF+VkFSM4SyLTDgwf2ciHWTarN0lQTC+F2f/t5J9QjW+c65VFIAAu85GsvMIusw==} engines: {node: '>=10.0.0'} @@ -1652,6 +1741,10 @@ packages: resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==} engines: {node: '>= 0.4'} + giget@2.0.0: + resolution: {integrity: sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==} + hasBin: true + github-from-package@0.0.0: resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} @@ -1740,6 +1833,13 @@ packages: resolution: {integrity: sha512-A91dYTeIB6NoXG+PxTQpCCDDnfHsW9kc06Lvpu1TEe9gnd6ZFeiBoRO9JvzEv6xK7EX97/dUE8g/vBMTqTS3CA==} engines: {node: '>=14'} + html-to-text@9.0.5: + resolution: {integrity: sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg==} + engines: {node: '>=14'} + + htmlparser2@8.0.2: + resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==} + http-errors@2.0.0: resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} engines: {node: '>= 0.8'} @@ -1998,6 +2098,10 @@ packages: jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + jiti@2.5.1: + resolution: {integrity: sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==} + hasBin: true + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -2040,6 +2144,9 @@ packages: resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==} engines: {node: '>= 0.6.3'} + leac@0.6.0: + resolution: {integrity: sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==} + lie@3.3.0: resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} @@ -2309,6 +2416,9 @@ packages: node-addon-api@7.1.1: resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} + node-fetch-native@1.6.7: + resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==} + node-fetch@2.6.12: resolution: {integrity: sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==} engines: {node: 4.x || >=6.0.0} @@ -2360,6 +2470,11 @@ packages: resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} engines: {node: '>=8'} + nypm@0.6.1: + resolution: {integrity: sha512-hlacBiRiv1k9hZFiphPUkfSQ/ZfQzZDzC+8z0wL3lvDAOUu/2NnChkKuMoMjNur/9OpKuz2QsIeiPVN0xM5Q0w==} + engines: {node: ^14.16.0 || >=16.10.0} + hasBin: true + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -2389,6 +2504,9 @@ packages: obliterator@2.0.4: resolution: {integrity: sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ==} + ohash@2.0.11: + resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} + on-finished@2.3.0: resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==} engines: {node: '>= 0.8'} @@ -2467,6 +2585,9 @@ packages: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} + parseley@0.12.1: + resolution: {integrity: sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==} + parseurl@1.3.3: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} @@ -2504,6 +2625,12 @@ packages: resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} engines: {node: '>= 14.16'} + peberminta@0.9.0: + resolution: {integrity: sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==} + + perfect-debounce@1.0.0: + resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -2526,6 +2653,9 @@ packages: resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} engines: {node: '>=8'} + pkg-types@2.3.0: + resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} + possible-typed-array-names@1.0.0: resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} engines: {node: '>= 0.4'} @@ -2553,8 +2683,8 @@ packages: resolution: {integrity: sha512-VpNpolZ8RXRgfU+j4R+fPZmX8EE95w3vJ2tt7+FwuiQc0leNTfLK5QLf3KbbPDes2rfjh3g20AjDxefQIo5GIA==} hasBin: true - prisma@6.3.0: - resolution: {integrity: sha512-y+Zh3Qg+xGCWyyrNUUNaFW/OltaV/yXYuTa0WRgYkz5LGyifmAsgpv94I47+qGRocZrMGcbF2A/78/oO2zgifA==} + prisma@6.16.0: + resolution: {integrity: sha512-TTh+H1Kw8N68KN9cDzdAyMroqMOvdCO/Z+kS2wKEVYR1nuR21qH5Q/Db/bZHsAgw7l/TPHtM/veG5VABcdwPDw==} engines: {node: '>=18.18'} hasBin: true peerDependencies: @@ -2601,6 +2731,9 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + pure-rand@6.1.0: + resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} + qs@6.13.0: resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} engines: {node: '>=0.6'} @@ -2623,6 +2756,9 @@ packages: resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} engines: {node: '>= 0.8'} + rc9@2.1.2: + resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==} + rc@1.2.8: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true @@ -2649,6 +2785,10 @@ packages: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + reflect-metadata@0.2.2: resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} @@ -2746,6 +2886,9 @@ packages: secure-json-parse@2.7.0: resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==} + selderee@0.11.0: + resolution: {integrity: sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==} + semver@5.7.2: resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} hasBin: true @@ -3020,6 +3163,9 @@ packages: tinyexec@0.3.2: resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + tinyexec@1.0.1: + resolution: {integrity: sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==} + tinyglobby@0.2.13: resolution: {integrity: sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==} engines: {node: '>=12.0.0'} @@ -3833,11 +3979,20 @@ snapshots: '@polka/url@1.0.0-next.29': {} - '@prisma/client@6.3.0(prisma@6.3.0(typescript@5.7.2))(typescript@5.7.2)': + '@prisma/client@6.16.0(prisma@6.16.0(typescript@5.7.2))(typescript@5.7.2)': optionalDependencies: - prisma: 6.3.0(typescript@5.7.2) + prisma: 6.16.0(typescript@5.7.2) typescript: 5.7.2 + '@prisma/config@6.16.0': + dependencies: + c12: 3.1.0 + deepmerge-ts: 7.1.5 + effect: 3.16.12 + empathic: 2.0.0 + transitivePeerDependencies: + - magicast + '@prisma/debug@5.3.1': dependencies: '@types/debug': 4.1.8 @@ -3846,18 +4001,18 @@ snapshots: transitivePeerDependencies: - supports-color - '@prisma/debug@6.3.0': {} + '@prisma/debug@6.16.0': {} - '@prisma/engines-version@6.3.0-17.acc0b9dd43eb689cbd20c9470515d719db10d0b0': {} + '@prisma/engines-version@6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43': {} '@prisma/engines@5.3.1': {} - '@prisma/engines@6.3.0': + '@prisma/engines@6.16.0': dependencies: - '@prisma/debug': 6.3.0 - '@prisma/engines-version': 6.3.0-17.acc0b9dd43eb689cbd20c9470515d719db10d0b0 - '@prisma/fetch-engine': 6.3.0 - '@prisma/get-platform': 6.3.0 + '@prisma/debug': 6.16.0 + '@prisma/engines-version': 6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43 + '@prisma/fetch-engine': 6.16.0 + '@prisma/get-platform': 6.16.0 '@prisma/fetch-engine@5.3.1(encoding@0.1.13)': dependencies: @@ -3882,11 +4037,11 @@ snapshots: - encoding - supports-color - '@prisma/fetch-engine@6.3.0': + '@prisma/fetch-engine@6.16.0': dependencies: - '@prisma/debug': 6.3.0 - '@prisma/engines-version': 6.3.0-17.acc0b9dd43eb689cbd20c9470515d719db10d0b0 - '@prisma/get-platform': 6.3.0 + '@prisma/debug': 6.16.0 + '@prisma/engines-version': 6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43 + '@prisma/get-platform': 6.16.0 '@prisma/generator-helper@5.3.1': dependencies: @@ -3912,9 +4067,9 @@ snapshots: transitivePeerDependencies: - supports-color - '@prisma/get-platform@6.3.0': + '@prisma/get-platform@6.16.0': dependencies: - '@prisma/debug': 6.3.0 + '@prisma/debug': 6.16.0 '@prisma/internals@5.3.1(encoding@0.1.13)': dependencies: @@ -4037,6 +4192,13 @@ snapshots: '@scalar/openapi-types': 0.1.7 '@unhead/schema': 1.11.14 + '@selderee/plugin-htmlparser2@0.11.0': + dependencies: + domhandler: 5.0.3 + selderee: 0.11.0 + + '@standard-schema/spec@1.0.0': {} + '@swc/helpers@0.5.15': dependencies: tslib: 2.8.1 @@ -4136,6 +4298,8 @@ snapshots: '@types/qs': 6.9.17 '@types/serve-static': 1.15.7 + '@types/html-to-text@9.0.4': {} + '@types/http-assert@1.5.6': {} '@types/http-errors@2.0.4': {} @@ -4214,13 +4378,13 @@ snapshots: chai: 5.2.0 tinyrainbow: 2.0.0 - '@vitest/mocker@3.1.4(vite@6.3.5(@types/node@20.17.10)(yaml@2.6.1))': + '@vitest/mocker@3.1.4(vite@6.3.5(@types/node@20.17.10)(jiti@2.5.1)(yaml@2.6.1))': dependencies: '@vitest/spy': 3.1.4 estree-walker: 3.0.3 magic-string: 0.30.17 optionalDependencies: - vite: 6.3.5(@types/node@20.17.10)(yaml@2.6.1) + vite: 6.3.5(@types/node@20.17.10)(jiti@2.5.1)(yaml@2.6.1) '@vitest/pretty-format@3.1.4': dependencies: @@ -4250,7 +4414,7 @@ snapshots: sirv: 3.0.1 tinyglobby: 0.2.13 tinyrainbow: 2.0.0 - vitest: 3.1.4(@types/node@20.17.10)(@vitest/ui@3.1.4)(yaml@2.6.1) + vitest: 3.1.4(@types/node@20.17.10)(@vitest/ui@3.1.4)(jiti@2.5.1)(yaml@2.6.1) '@vitest/utils@3.1.4': dependencies: @@ -4524,6 +4688,21 @@ snapshots: bytes@3.1.2: {} + c12@3.1.0: + dependencies: + chokidar: 4.0.3 + confbox: 0.2.2 + defu: 6.1.4 + dotenv: 16.6.1 + exsolve: 1.0.7 + giget: 2.0.0 + jiti: 2.5.1 + ohash: 2.0.11 + pathe: 2.0.3 + perfect-debounce: 1.0.0 + pkg-types: 2.3.0 + rc9: 2.1.2 + cac@6.7.14: {} call-bind-apply-helpers@1.0.1: @@ -4603,10 +4782,18 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + chownr@1.1.4: {} ci-info@3.8.0: {} + citty@0.1.6: + dependencies: + consola: 3.4.2 + cjs-module-lexer@1.4.1: optional: true @@ -4682,6 +4869,10 @@ snapshots: readable-stream: 2.3.8 typedarray: 0.0.6 + confbox@0.2.2: {} + + consola@3.4.2: {} + console-log-level@1.4.1: optional: true @@ -4781,6 +4972,10 @@ snapshots: deep-extend@0.6.0: {} + deepmerge-ts@7.1.5: {} + + deepmerge@4.3.1: {} + define-data-property@1.1.4: dependencies: es-define-property: 1.0.1 @@ -4793,6 +4988,8 @@ snapshots: has-property-descriptors: 1.0.2 object-keys: 1.1.1 + defu@6.1.4: {} + del@6.1.1: dependencies: globby: 11.1.0 @@ -4806,6 +5003,8 @@ snapshots: depd@2.0.0: {} + destr@2.0.5: {} + destroy@1.2.0: {} detect-libc@2.0.4: {} @@ -4823,10 +5022,30 @@ snapshots: jszip: 3.10.1 sax: 1.3.0 + dom-serializer@2.0.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.5.0 + + domelementtype@2.3.0: {} + + domhandler@5.0.3: + dependencies: + domelementtype: 2.3.0 + + domutils@3.2.2: + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + dotenv@16.0.3: {} dotenv@16.4.7: {} + dotenv@16.6.1: {} + dunder-proto@1.0.0: dependencies: call-bind-apply-helpers: 1.0.1 @@ -4845,6 +5064,11 @@ snapshots: ee-first@1.1.1: {} + effect@3.16.12: + dependencies: + '@standard-schema/spec': 1.0.0 + fast-check: 3.23.2 + elastic-apm-node@3.52.2: dependencies: '@elastic/ecs-pino-format': 1.5.0 @@ -4893,6 +5117,8 @@ snapshots: emoji-regex@9.2.2: {} + empathic@2.0.0: {} + enabled@2.0.0: {} encodeurl@1.0.2: {} @@ -4908,6 +5134,8 @@ snapshots: dependencies: once: 1.4.0 + entities@4.5.0: {} + env-paths@2.2.1: {} error-callsites@2.0.4: @@ -5124,6 +5352,12 @@ snapshots: transitivePeerDependencies: - supports-color + exsolve@1.0.7: {} + + fast-check@3.23.2: + dependencies: + pure-rand: 6.1.0 + fast-csv@4.3.6: dependencies: '@fast-csv/format': 4.3.5 @@ -5301,6 +5535,15 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.2.6 + giget@2.0.0: + dependencies: + citty: 0.1.6 + consola: 3.4.2 + defu: 6.1.4 + node-fetch-native: 1.6.7 + nypm: 0.6.1 + pathe: 2.0.3 + github-from-package@0.0.0: {} glob-parent@5.1.2: @@ -5399,6 +5642,21 @@ snapshots: hpagent@1.2.0: {} + html-to-text@9.0.5: + dependencies: + '@selderee/plugin-htmlparser2': 0.11.0 + deepmerge: 4.3.1 + dom-serializer: 2.0.0 + htmlparser2: 8.0.2 + selderee: 0.11.0 + + htmlparser2@8.0.2: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.2.2 + entities: 4.5.0 + http-errors@2.0.0: dependencies: depd: 2.0.0 @@ -5651,6 +5909,8 @@ snapshots: optionalDependencies: '@pkgjs/parseargs': 0.11.0 + jiti@2.5.1: {} + js-tokens@4.0.0: {} jsbarcode@3.11.6: {} @@ -5689,6 +5949,8 @@ snapshots: dependencies: readable-stream: 2.3.8 + leac@0.6.0: {} + lie@3.3.0: dependencies: immediate: 3.0.6 @@ -5934,6 +6196,8 @@ snapshots: node-addon-api@7.1.1: {} + node-fetch-native@1.6.7: {} + node-fetch@2.6.12(encoding@0.1.13): dependencies: whatwg-url: 5.0.0 @@ -5987,6 +6251,14 @@ snapshots: dependencies: path-key: 3.1.1 + nypm@0.6.1: + dependencies: + citty: 0.1.6 + consola: 3.4.2 + pathe: 2.0.3 + pkg-types: 2.3.0 + tinyexec: 1.0.1 + object-assign@4.1.1: {} object-filter-sequence@1.0.0: @@ -6017,6 +6289,8 @@ snapshots: obliterator@2.0.4: {} + ohash@2.0.11: {} + on-finished@2.3.0: dependencies: ee-first: 1.1.1 @@ -6096,6 +6370,11 @@ snapshots: json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 + parseley@0.12.1: + dependencies: + leac: 0.6.0 + peberminta: 0.9.0 + parseurl@1.3.3: {} path-exists@4.0.0: {} @@ -6119,6 +6398,10 @@ snapshots: pathval@2.0.0: {} + peberminta@0.9.0: {} + + perfect-debounce@1.0.0: {} + picocolors@1.1.1: {} picomatch@2.3.1: {} @@ -6143,6 +6426,12 @@ snapshots: dependencies: find-up: 4.1.0 + pkg-types@2.3.0: + dependencies: + confbox: 0.2.2 + exsolve: 1.0.7 + pathe: 2.0.3 + possible-typed-array-names@1.0.0: {} postcss@8.5.3: @@ -6168,9 +6457,9 @@ snapshots: prettier@3.4.2: {} - prisma-extension-kysely@3.0.0(@prisma/client@6.3.0(prisma@6.3.0(typescript@5.7.2))(typescript@5.7.2)): + prisma-extension-kysely@3.0.0(@prisma/client@6.16.0(prisma@6.16.0(typescript@5.7.2))(typescript@5.7.2)): dependencies: - '@prisma/client': 6.3.0(prisma@6.3.0(typescript@5.7.2))(typescript@5.7.2) + '@prisma/client': 6.16.0(prisma@6.16.0(typescript@5.7.2))(typescript@5.7.2) prisma-kysely@1.8.0(encoding@0.1.13): dependencies: @@ -6183,12 +6472,14 @@ snapshots: - encoding - supports-color - prisma@6.3.0(typescript@5.7.2): + prisma@6.16.0(typescript@5.7.2): dependencies: - '@prisma/engines': 6.3.0 + '@prisma/config': 6.16.0 + '@prisma/engines': 6.16.0 optionalDependencies: - fsevents: 2.3.3 typescript: 5.7.2 + transitivePeerDependencies: + - magicast process-nextick-args@2.0.1: {} @@ -6234,6 +6525,8 @@ snapshots: punycode@2.3.1: optional: true + pure-rand@6.1.0: {} + qs@6.13.0: dependencies: side-channel: 1.1.0 @@ -6259,6 +6552,11 @@ snapshots: iconv-lite: 0.4.24 unpipe: 1.0.0 + rc9@2.1.2: + dependencies: + defu: 6.1.4 + destr: 2.0.5 + rc@1.2.8: dependencies: deep-extend: 0.6.0 @@ -6303,6 +6601,8 @@ snapshots: dependencies: picomatch: 2.3.1 + readdirp@4.1.2: {} + reflect-metadata@0.2.2: {} reflect.getprototypeof@1.0.8: @@ -6428,6 +6728,10 @@ snapshots: secure-json-parse@2.7.0: {} + selderee@0.11.0: + dependencies: + parseley: 0.12.1 + semver@5.7.2: {} semver@6.3.1: {} @@ -6744,6 +7048,8 @@ snapshots: tinyexec@0.3.2: {} + tinyexec@1.0.1: {} + tinyglobby@0.2.13: dependencies: fdir: 6.4.4(picomatch@4.0.2) @@ -6949,13 +7255,13 @@ snapshots: vary@1.1.2: {} - vite-node@3.1.4(@types/node@20.17.10)(yaml@2.6.1): + vite-node@3.1.4(@types/node@20.17.10)(jiti@2.5.1)(yaml@2.6.1): dependencies: cac: 6.7.14 debug: 4.4.0(supports-color@5.5.0) es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 6.3.5(@types/node@20.17.10)(yaml@2.6.1) + vite: 6.3.5(@types/node@20.17.10)(jiti@2.5.1)(yaml@2.6.1) transitivePeerDependencies: - '@types/node' - jiti @@ -6970,7 +7276,7 @@ snapshots: - tsx - yaml - vite@6.3.5(@types/node@20.17.10)(yaml@2.6.1): + vite@6.3.5(@types/node@20.17.10)(jiti@2.5.1)(yaml@2.6.1): dependencies: esbuild: 0.25.4 fdir: 6.4.4(picomatch@4.0.2) @@ -6981,12 +7287,13 @@ snapshots: optionalDependencies: '@types/node': 20.17.10 fsevents: 2.3.3 + jiti: 2.5.1 yaml: 2.6.1 - vitest@3.1.4(@types/node@20.17.10)(@vitest/ui@3.1.4)(yaml@2.6.1): + vitest@3.1.4(@types/node@20.17.10)(@vitest/ui@3.1.4)(jiti@2.5.1)(yaml@2.6.1): dependencies: '@vitest/expect': 3.1.4 - '@vitest/mocker': 3.1.4(vite@6.3.5(@types/node@20.17.10)(yaml@2.6.1)) + '@vitest/mocker': 3.1.4(vite@6.3.5(@types/node@20.17.10)(jiti@2.5.1)(yaml@2.6.1)) '@vitest/pretty-format': 3.1.4 '@vitest/runner': 3.1.4 '@vitest/snapshot': 3.1.4 @@ -7003,8 +7310,8 @@ snapshots: tinyglobby: 0.2.13 tinypool: 1.0.2 tinyrainbow: 2.0.0 - vite: 6.3.5(@types/node@20.17.10)(yaml@2.6.1) - vite-node: 3.1.4(@types/node@20.17.10)(yaml@2.6.1) + vite: 6.3.5(@types/node@20.17.10)(jiti@2.5.1)(yaml@2.6.1) + vite-node: 3.1.4(@types/node@20.17.10)(jiti@2.5.1)(yaml@2.6.1) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 20.17.10 diff --git a/prisma/migrations/20250911021745_add_more_payment_metadata/migration.sql b/prisma/migrations/20250911021745_add_more_payment_metadata/migration.sql new file mode 100644 index 0000000..27036c0 --- /dev/null +++ b/prisma/migrations/20250911021745_add_more_payment_metadata/migration.sql @@ -0,0 +1,4 @@ +-- AlterTable +ALTER TABLE "Payment" ADD COLUMN "account" TEXT, +ADD COLUMN "channel" TEXT, +ADD COLUMN "reference" TEXT; diff --git a/prisma/migrations/20250911092303_add_flow_account_product_id_sell_price_and_flow_account_product_id_agent_price_field_in_product_table/migration.sql b/prisma/migrations/20250911092303_add_flow_account_product_id_sell_price_and_flow_account_product_id_agent_price_field_in_product_table/migration.sql new file mode 100644 index 0000000..0ef2494 --- /dev/null +++ b/prisma/migrations/20250911092303_add_flow_account_product_id_sell_price_and_flow_account_product_id_agent_price_field_in_product_table/migration.sql @@ -0,0 +1,3 @@ +-- AlterTable +ALTER TABLE "public"."Product" ADD COLUMN "flowAccountProductIdAgentPrice" TEXT, +ADD COLUMN "flowAccountProductIdSellPrice" TEXT; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 00bd192..2f25bb2 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -1243,6 +1243,9 @@ model Product { productGroup ProductGroup @relation(fields: [productGroupId], references: [id], onDelete: Cascade) productGroupId String + flowAccountProductIdSellPrice String? + flowAccountProductIdAgentPrice String? + workProduct WorkProduct[] quotationProductServiceList QuotationProductServiceList[] taskProduct TaskProduct[] @@ -1524,8 +1527,11 @@ model Payment { paymentStatus PaymentStatus - amount Float - date DateTime? + amount Float + date DateTime? + channel String? + account String? + reference String? createdAt DateTime @default(now()) createdBy User? @relation(name: "PaymentCreatedByUser", fields: [createdByUserId], references: [id], onDelete: SetNull) diff --git a/src/controllers/00-doc-template-controller.ts b/src/controllers/00-doc-template-controller.ts index da5dc9d..09dab18 100644 --- a/src/controllers/00-doc-template-controller.ts +++ b/src/controllers/00-doc-template-controller.ts @@ -34,6 +34,11 @@ const quotationData = (id: string) => }, }, customerBranch: { + omit: { + otpCode: true, + otpExpires: true, + userId: true, + }, include: { customer: true, businessType: true, diff --git a/src/controllers/03-customer-branch-controller.ts b/src/controllers/03-customer-branch-controller.ts index eda1546..a4d87e4 100644 --- a/src/controllers/03-customer-branch-controller.ts +++ b/src/controllers/03-customer-branch-controller.ts @@ -238,6 +238,11 @@ export class CustomerBranchController extends Controller { const [result, total] = await prisma.$transaction([ prisma.customerBranch.findMany({ orderBy: [{ code: "asc" }, { statusOrder: "asc" }, { createdAt: "asc" }], + omit: { + otpCode: true, + otpExpires: true, + userId: true, + }, include: { customer: includeCustomer, province: true, @@ -262,6 +267,11 @@ export class CustomerBranchController extends Controller { @Security("keycloak") async getById(@Path() branchId: string) { const record = await prisma.customerBranch.findFirst({ + omit: { + otpCode: true, + otpExpires: true, + userId: true, + }, include: { customer: true, province: true, @@ -352,6 +362,11 @@ export class CustomerBranchController extends Controller { include: branchRelationPermInclude(req.user), }, branch: { + omit: { + otpCode: true, + otpExpires: true, + userId: true, + }, take: 1, orderBy: { createdAt: "asc" }, }, diff --git a/src/controllers/03-customer-controller.ts b/src/controllers/03-customer-controller.ts index 2f31d79..1854842 100644 --- a/src/controllers/03-customer-controller.ts +++ b/src/controllers/03-customer-controller.ts @@ -37,6 +37,7 @@ import { } from "../utils/minio"; import { isUsedError, notFoundError, relationError } from "../utils/error"; import { connectOrNot, queryOrNot, whereDateQuery } from "../utils/relation"; +import { json2csv } from "json-2-csv"; const MANAGE_ROLES = [ "system", @@ -206,6 +207,11 @@ export class CustomerController extends Controller { district: true, subDistrict: true, }, + omit: { + otpCode: true, + otpExpires: true, + userId: true, + }, orderBy: [{ statusOrder: "asc" }, { createdAt: "asc" }], } : { @@ -214,6 +220,11 @@ export class CustomerController extends Controller { district: true, subDistrict: true, }, + omit: { + otpCode: true, + otpExpires: true, + userId: true, + }, take: 1, orderBy: { createdAt: "asc" }, }, @@ -244,6 +255,11 @@ export class CustomerController extends Controller { district: true, subDistrict: true, }, + omit: { + otpCode: true, + otpExpires: true, + userId: true, + }, orderBy: { createdAt: "asc" }, }, createdBy: true, @@ -315,6 +331,11 @@ export class CustomerController extends Controller { district: true, subDistrict: true, }, + omit: { + otpCode: true, + otpExpires: true, + userId: true, + }, }, createdBy: true, updatedBy: true, @@ -414,6 +435,11 @@ export class CustomerController extends Controller { district: true, subDistrict: true, }, + omit: { + otpCode: true, + otpExpires: true, + userId: true, + }, }, createdBy: true, updatedBy: true, @@ -452,7 +478,13 @@ export class CustomerController extends Controller { await deleteFolder(`customer/${customerId}`); const data = await tx.customer.delete({ include: { - branch: true, + branch: { + omit: { + otpCode: true, + otpExpires: true, + userId: true, + }, + }, registeredBranch: { include: { headOffice: true, @@ -547,3 +579,44 @@ export class CustomerImageController extends Controller { await deleteFile(fileLocation.customer.img(customerId, name)); } } + +@Route("api/v1/customer-export") +@Tags("Customer") +export class CustomerExportController extends CustomerController { + @Get() + @Security("keycloak") + async exportCustomer( + @Request() req: RequestWithUser, + @Query() customerType?: CustomerType, + @Query() query: string = "", + @Query() status?: Status, + @Query() page: number = 1, + @Query() pageSize: number = 30, + @Query() includeBranch: boolean = false, + @Query() company: boolean = false, + @Query() activeBranchOnly?: boolean, + @Query() startDate?: Date, + @Query() endDate?: Date, + ) { + const ret = await this.list( + req, + customerType, + query, + status, + page, + pageSize, + includeBranch, + company, + activeBranchOnly, + startDate, + endDate, + ); + + this.setHeader("Content-Type", "text/csv"); + + return json2csv( + ret.result.map((v) => Object.assign(v, { branch: v.branch.at(0) ?? null })), + { useDateIso8601Format: true, expandNestedObjects: true }, + ); + } +} diff --git a/src/controllers/03-employee-controller.ts b/src/controllers/03-employee-controller.ts index 411b817..303ba15 100644 --- a/src/controllers/03-employee-controller.ts +++ b/src/controllers/03-employee-controller.ts @@ -42,6 +42,7 @@ import { listFile, setFile, } from "../utils/minio"; +import { json2csv } from "json-2-csv"; if (!process.env.MINIO_BUCKET) { throw Error("Require MinIO bucket."); @@ -249,7 +250,6 @@ export class EmployeeController extends Controller { endDate, ); } - @Post("list") @Security("keycloak") async listByCriteria( @@ -927,3 +927,55 @@ export class EmployeeFileController extends Controller { return await deleteFile(fileLocation.employee.inCountryNotice(employeeId, noticeId)); } } + +@Route("api/v1/employee-export") +@Tags("Employee") +export class EmployeeExportController extends EmployeeController { + @Get() + @Security("keycloak") + async exportEmployee( + @Request() req: RequestWithUser, + @Query() zipCode?: string, + @Query() gender?: string, + @Query() status?: Status, + @Query() visa?: boolean, + @Query() passport?: boolean, + @Query() customerId?: string, + @Query() customerBranchId?: string, + @Query() query: string = "", + @Query() page: number = 1, + @Query() pageSize: number = 30, + @Query() activeOnly?: boolean, + @Query() startDate?: Date, + @Query() endDate?: Date, + ) { + const ret = await this.listByCriteria( + req, + zipCode, + gender, + status, + visa, + passport, + customerId, + customerBranchId, + query, + page, + pageSize, + activeOnly, + startDate, + endDate, + ); + + this.setHeader("Content-Type", "text/csv"); + + return json2csv( + ret.result.map((v) => + Object.assign(v, { + employeePassport: v.employeePassport?.at(0) ?? null, + employeeVisa: v.employeeVisa?.at(0) ?? null, + }), + ), + { useDateIso8601Format: true }, + ); + } +} diff --git a/src/controllers/04-product-controller.ts b/src/controllers/04-product-controller.ts index b50462c..c1c40d8 100644 --- a/src/controllers/04-product-controller.ts +++ b/src/controllers/04-product-controller.ts @@ -30,6 +30,7 @@ import { deleteFile, deleteFolder, fileLocation, getFile, listFile, setFile } fr import { isUsedError, notFoundError, relationError } from "../utils/error"; import { queryOrNot, whereDateQuery } from "../utils/relation"; import spreadsheet from "../utils/spreadsheet"; +import flowAccount from "../services/flowaccount"; const MANAGE_ROLES = [ "system", @@ -299,6 +300,9 @@ export class ProductController extends Controller { }, update: { value: { increment: 1 } }, }); + + const listId = await flowAccount.createProducts(body); + return await prisma.product.create({ include: { createdBy: true, @@ -306,6 +310,8 @@ export class ProductController extends Controller { }, data: { ...body, + flowAccountProductIdAgentPrice: `${listId.data.productIdAgentPrice}`, + flowAccountProductIdSellPrice: `${listId.data.productIdSellPrice}`, document: body.document ? { createMany: { data: body.document.map((v) => ({ name: v })) }, @@ -379,6 +385,19 @@ export class ProductController extends Controller { await permissionCheck(req.user, productGroup.registeredBranch); } + if ( + product.flowAccountProductIdSellPrice !== null && + product.flowAccountProductIdAgentPrice !== null + ) { + await flowAccount.editProducts( + product.flowAccountProductIdSellPrice, + product.flowAccountProductIdAgentPrice, + body, + ); + } else { + throw notFoundError("FlowAccountProductId"); + } + const record = await prisma.product.update({ include: { productGroup: true, @@ -441,6 +460,18 @@ export class ProductController extends Controller { if (record.status !== Status.CREATED) throw isUsedError("Product"); + if ( + record.flowAccountProductIdSellPrice !== null && + record.flowAccountProductIdAgentPrice !== null + ) { + await Promise.all([ + flowAccount.deleteProduct(record.flowAccountProductIdSellPrice), + flowAccount.deleteProduct(record.flowAccountProductIdAgentPrice), + ]); + } else { + throw notFoundError("FlowAccountProductId"); + } + await deleteFolder(fileLocation.product.img(productId)); return await prisma.product.delete({ diff --git a/src/controllers/05-payment-controller.ts b/src/controllers/05-payment-controller.ts index 5f530d0..7ad9584 100644 --- a/src/controllers/05-payment-controller.ts +++ b/src/controllers/05-payment-controller.ts @@ -113,7 +113,15 @@ export class QuotationPayment extends Controller { @Security("keycloak", MANAGE_ROLES.concat(["head_of_sale", "sale"])) async updatePayment( @Path() paymentId: string, - @Body() body: { amount?: number; date?: Date; paymentStatus?: PaymentStatus }, + @Body() + body: { + amount?: number; + date?: Date; + paymentStatus?: PaymentStatus; + channel?: string | null; + account?: string | null; + reference?: string | null; + }, @Request() req: RequestWithUser, ) { const record = await prisma.payment.findUnique({ @@ -144,7 +152,18 @@ export class QuotationPayment extends Controller { if (!record) throw notFoundError("Payment"); - if (record.paymentStatus === "PaymentSuccess") return record; + if (record.paymentStatus === "PaymentSuccess") { + const { channel, account, reference } = body; + return await prisma.payment.update({ + where: { id: paymentId, invoice: { quotationId: record.invoice.quotationId } }, + data: { + channel, + account, + reference, + updatedByUserId: req.user.sub, + }, + }); + } return await prisma.$transaction(async (tx) => { const current = new Date(); diff --git a/src/controllers/05-quotation-controller.ts b/src/controllers/05-quotation-controller.ts index 29face5..942e4d1 100644 --- a/src/controllers/05-quotation-controller.ts +++ b/src/controllers/05-quotation-controller.ts @@ -527,16 +527,15 @@ export class QuotationController extends Controller { const vatIncluded = body.agentPrice ? p.agentPriceVatIncluded : p.vatIncluded; const originalPrice = body.agentPrice ? p.agentPrice : p.price; - const finalPriceWithVat = precisionRound( + const finalPrice = precisionRound( originalPrice + (vatIncluded ? 0 : originalPrice * VAT_DEFAULT), ); - - const price = finalPriceWithVat; - const pricePerUnit = price / (1 + VAT_DEFAULT); + const pricePerUnit = finalPrice / (1 + VAT_DEFAULT); const vat = (body.agentPrice ? p.agentPriceCalcVat : p.calcVat) - ? (pricePerUnit * v.amount - (v.discount || 0)) * VAT_DEFAULT + ? ((pricePerUnit * (1 + VAT_DEFAULT) * v.amount - (v.discount || 0)) / + (1 + VAT_DEFAULT)) * + VAT_DEFAULT : 0; - return { order: i + 1, productId: v.productId, @@ -557,13 +556,13 @@ export class QuotationController extends Controller { const price = list.reduce( (a, c) => { - a.totalPrice = precisionRound(a.totalPrice + c.pricePerUnit * c.amount); + const vat = c.vat ? VAT_DEFAULT : 0; + const price = c.pricePerUnit * c.amount * (1 + vat) - c.discount; + + a.totalPrice = precisionRound(a.totalPrice + price / (1 + vat) + c.discount); a.totalDiscount = precisionRound(a.totalDiscount + c.discount); a.vat = precisionRound(a.vat + c.vat); - a.vatExcluded = - c.vat === 0 - ? precisionRound(a.vatExcluded + (c.pricePerUnit * c.amount - (c.discount || 0))) - : a.vatExcluded; + a.vatExcluded = c.vat === 0 ? precisionRound(a.vatExcluded + price) : a.vatExcluded; a.finalPrice = precisionRound( Math.max(a.totalPrice - a.totalDiscount + a.vat - (body.discount || 0), 0), ); @@ -815,14 +814,14 @@ export class QuotationController extends Controller { const vatIncluded = record.agentPrice ? p.agentPriceVatIncluded : p.vatIncluded; const originalPrice = record.agentPrice ? p.agentPrice : p.price; - const finalPriceWithVat = precisionRound( + const finalPrice = precisionRound( originalPrice + (vatIncluded ? 0 : originalPrice * VAT_DEFAULT), ); - - const price = finalPriceWithVat; - const pricePerUnit = price / (1 + VAT_DEFAULT); + const pricePerUnit = finalPrice / (1 + VAT_DEFAULT); const vat = (record.agentPrice ? p.agentPriceCalcVat : p.calcVat) - ? (pricePerUnit * v.amount - (v.discount || 0)) * VAT_DEFAULT + ? ((pricePerUnit * (1 + VAT_DEFAULT) * v.amount - (v.discount || 0)) / + (1 + VAT_DEFAULT)) * + VAT_DEFAULT : 0; return { @@ -845,15 +844,13 @@ export class QuotationController extends Controller { const price = list?.reduce( (a, c) => { - a.totalPrice = precisionRound(a.totalPrice + c.pricePerUnit * c.amount); + const vat = c.vat ? VAT_DEFAULT : 0; + const price = c.pricePerUnit * c.amount * (1 + vat) - c.discount; + + a.totalPrice = precisionRound(a.totalPrice + price / (1 + vat) + c.discount); a.totalDiscount = precisionRound(a.totalDiscount + c.discount); a.vat = precisionRound(a.vat + c.vat); - a.vatExcluded = - c.vat === 0 - ? precisionRound( - a.vatExcluded + (c.pricePerUnit * c.amount - (c.discount || 0)) * VAT_DEFAULT, - ) - : a.vatExcluded; + a.vatExcluded = c.vat === 0 ? precisionRound(a.vatExcluded + price) : a.vatExcluded; a.finalPrice = precisionRound( Math.max(a.totalPrice - a.totalDiscount + a.vat - (body.discount || 0), 0), ); @@ -869,6 +866,7 @@ export class QuotationController extends Controller { finalPrice: 0, }, ); + const changed = list?.some((lhs) => { const found = record.productServiceList.find((rhs) => { return ( diff --git a/src/controllers/06-request-list-controller.ts b/src/controllers/06-request-list-controller.ts index 764fbc7..a453716 100644 --- a/src/controllers/06-request-list-controller.ts +++ b/src/controllers/06-request-list-controller.ts @@ -293,28 +293,48 @@ export class RequestDataController extends Controller { async updateRequestData( @Request() req: RequestWithUser, @Body() - boby: { + body: { defaultMessengerId: string; requestDataId: string[]; }, ) { - const record = await prisma.requestData.updateManyAndReturn({ - where: { - id: { in: boby.requestDataId }, - quotation: { - registeredBranch: { - OR: permissionCond(req.user), + if (body.requestDataId.length === 0) return; + + return await prisma.$transaction(async (tx) => { + const record = await tx.requestData.updateManyAndReturn({ + where: { + id: { in: body.requestDataId }, + quotation: { + registeredBranch: { + OR: permissionCond(req.user), + }, }, }, - }, - data: { - defaultMessengerId: boby.defaultMessengerId, - }, + data: { + defaultMessengerId: body.defaultMessengerId, + }, + }); + + if (record.length <= 0) throw notFoundError("Request Data"); + + await tx.requestWorkStepStatus.updateMany({ + where: { + workStatus: { + in: [ + RequestWorkStatus.Pending, + RequestWorkStatus.Waiting, + RequestWorkStatus.InProgress, + ], + }, + requestWork: { + requestDataId: { in: body.requestDataId }, + }, + }, + data: { responsibleUserId: body.defaultMessengerId }, + }); + + return record[0]; }); - - if (record.length <= 0) throw notFoundError("Request Data"); - - return record[0]; } } diff --git a/src/controllers/08-credit-note-controller.ts b/src/controllers/08-credit-note-controller.ts index 48ee2ab..7cd029e 100644 --- a/src/controllers/08-credit-note-controller.ts +++ b/src/controllers/08-credit-note-controller.ts @@ -13,6 +13,7 @@ import { Security, Tags, } from "tsoa"; +import config from "../config.json"; import prisma from "../db"; @@ -53,6 +54,7 @@ function globalAllow(user: RequestWithUser["user"]) { const listAllowed = ["system", "head_of_admin", "admin", "executive", "accountant"]; return user.roles?.some((v) => listAllowed.includes(v)) || false; } +const VAT_DEFAULT = config.vat; const permissionCond = createPermCondition(globalAllow); const permissionCheck = createPermCheck(globalAllow); @@ -346,9 +348,8 @@ export class CreditNoteController extends Controller { ).length; const price = - c.productService.pricePerUnit - - c.productService.discount / c.productService.amount + - c.productService.vat / c.productService.amount; + c.productService.pricePerUnit * (1 + (c.productService.vat > 0 ? VAT_DEFAULT : 0)) - + c.productService.discount; if (serviceChargeStepCount && successCount) { return a + price - c.productService.product.serviceCharge * successCount; @@ -424,7 +425,6 @@ export class CreditNoteController extends Controller { let textData = ""; let dataCustomerId: string[] = []; - let textWorkList: string[] = []; let dataUserId: string[] = []; if (res) { @@ -541,9 +541,8 @@ export class CreditNoteController extends Controller { ).length; const price = - c.productService.pricePerUnit - - c.productService.discount / c.productService.amount + - c.productService.vat / c.productService.amount; + c.productService.pricePerUnit * (1 + (c.productService.vat > 0 ? VAT_DEFAULT : 0)) - + c.productService.discount; if (serviceChargeStepCount && successCount) { return a + price - c.productService.product.serviceCharge * successCount; diff --git a/src/controllers/09-debit-note-controller.ts b/src/controllers/09-debit-note-controller.ts index 5fcdee9..f87bbe9 100644 --- a/src/controllers/09-debit-note-controller.ts +++ b/src/controllers/09-debit-note-controller.ts @@ -430,12 +430,18 @@ export class DebitNoteController extends Controller { const list = body.productServiceList.map((v, i) => { const p = product.find((p) => p.id === v.productId)!; - const price = body.agentPrice ? p.agentPrice : p.price; - const pricePerUnit = p.vatIncluded ? price / (1 + VAT_DEFAULT) : price; - const vat = p.calcVat - ? (pricePerUnit * (v.discount ? v.amount : 1) - (v.discount || 0)) * - VAT_DEFAULT * - (!v.discount ? v.amount : 1) + + const vatIncluded = body.agentPrice ? p.agentPriceVatIncluded : p.vatIncluded; + + const originalPrice = body.agentPrice ? p.agentPrice : p.price; + const finalPrice = precisionRound( + originalPrice + (vatIncluded ? 0 : originalPrice * VAT_DEFAULT), + ); + const pricePerUnit = finalPrice / (1 + VAT_DEFAULT); + const vat = (body.agentPrice ? p.agentPriceCalcVat : p.calcVat) + ? ((pricePerUnit * (1 + VAT_DEFAULT) * v.amount - (v.discount || 0)) / + (1 + VAT_DEFAULT)) * + VAT_DEFAULT : 0; return { @@ -458,15 +464,13 @@ export class DebitNoteController extends Controller { const price = list.reduce( (a, c) => { - a.totalPrice = precisionRound(a.totalPrice + c.pricePerUnit * c.amount); + const vat = c.vat ? VAT_DEFAULT : 0; + const price = c.pricePerUnit * c.amount * (1 + vat) - c.discount; + + a.totalPrice = precisionRound(a.totalPrice + price / (1 + vat) + c.discount); a.totalDiscount = precisionRound(a.totalDiscount + c.discount); a.vat = precisionRound(a.vat + c.vat); - a.vatExcluded = - c.vat === 0 - ? precisionRound( - a.vatExcluded + (c.pricePerUnit * c.amount - (c.discount || 0)) * VAT_DEFAULT, - ) - : a.vatExcluded; + a.vatExcluded = c.vat === 0 ? precisionRound(a.vatExcluded + price) : a.vatExcluded; a.finalPrice = precisionRound( Math.max(a.totalPrice - a.totalDiscount + a.vat - (body.discount || 0), 0), ); @@ -673,12 +677,18 @@ export class DebitNoteController extends Controller { } const list = body.productServiceList.map((v, i) => { const p = product.find((p) => p.id === v.productId)!; - const price = body.agentPrice ? p.agentPrice : p.price; - const pricePerUnit = p.vatIncluded ? price / (1 + VAT_DEFAULT) : price; - const vat = p.calcVat - ? (pricePerUnit * (v.discount ? v.amount : 1) - (v.discount || 0)) * - VAT_DEFAULT * - (!v.discount ? v.amount : 1) + + const vatIncluded = record.agentPrice ? p.agentPriceVatIncluded : p.vatIncluded; + + const originalPrice = record.agentPrice ? p.agentPrice : p.price; + const finalPrice = precisionRound( + originalPrice + (vatIncluded ? 0 : originalPrice * VAT_DEFAULT), + ); + const pricePerUnit = finalPrice / (1 + VAT_DEFAULT); + const vat = (record.agentPrice ? p.agentPriceCalcVat : p.calcVat) + ? ((pricePerUnit * (1 + VAT_DEFAULT) * v.amount - (v.discount || 0)) / + (1 + VAT_DEFAULT)) * + VAT_DEFAULT : 0; return { @@ -701,15 +711,13 @@ export class DebitNoteController extends Controller { const price = list.reduce( (a, c) => { - a.totalPrice = precisionRound(a.totalPrice + c.pricePerUnit * c.amount); + const vat = c.vat ? VAT_DEFAULT : 0; + const price = c.pricePerUnit * c.amount * (1 + vat) - c.discount; + + a.totalPrice = precisionRound(a.totalPrice + price / (1 + vat) + c.discount); a.totalDiscount = precisionRound(a.totalDiscount + c.discount); a.vat = precisionRound(a.vat + c.vat); - a.vatExcluded = - c.vat === 0 - ? precisionRound( - a.vatExcluded + (c.pricePerUnit * c.amount - (c.discount || 0)) * VAT_DEFAULT, - ) - : a.vatExcluded; + a.vatExcluded = c.vat === 0 ? precisionRound(a.vatExcluded + price) : a.vatExcluded; a.finalPrice = precisionRound( Math.max(a.totalPrice - a.totalDiscount + a.vat - (body.discount || 0), 0), ); diff --git a/src/services/flowaccount.ts b/src/services/flowaccount.ts index 730482d..90eb5c0 100644 --- a/src/services/flowaccount.ts +++ b/src/services/flowaccount.ts @@ -1,6 +1,10 @@ import prisma from "../db"; import config from "../config.json"; import { CustomerType, PayCondition } from "@prisma/client"; +import { convertTemplate } from "../utils/string-template"; +import { htmlToText } from "html-to-text"; +import { JsonObject } from "@prisma/client/runtime/library"; +import { precisionRound } from "../utils/arithmetic"; if (!process.env.FLOW_ACCOUNT_URL) throw new Error("Require FLOW_ACCOUNT_URL"); if (!process.env.FLOW_ACCOUNT_CLIENT_ID) throw new Error("Require FLOW_ACCOUNT_CLIENT_ID"); @@ -182,6 +186,8 @@ const flowAccountAPI = { data.paymentDate = `${year}-${String(month).padStart(2, "0")}-${String(date).padStart(2, "0")}`; } */ + console.log(JSON.stringify(data, null, 2)); + const res = await fetch( api + "/upgrade/receipts/inline" + (withPayment ? "/with-payment" : ""), { @@ -232,6 +238,29 @@ const flowAccount = { installments: true, quotation: { include: { + paySplit: true, + worker: { + select: { + employee: { + select: { + employeePassport: { + select: { + number: true, + }, + orderBy: { + expireDate: "desc", + }, + take: 1, + }, + namePrefix: true, + firstName: true, + lastName: true, + firstNameEN: true, + lastNameEN: true, + }, + }, + }, + }, registeredBranch: { include: { province: true, @@ -305,6 +334,7 @@ const flowAccount = { isVat: true, useReceiptDeduction: false, + useInlineVat: true, discounPercentage: 0, discountAmount: quotation.totalDiscount, @@ -326,13 +356,31 @@ const flowAccount = { ? data.installments.reduce((a, c) => a + c.amount, 0) : quotation.finalPrice, + remarks: htmlToText( + convertTemplate(quotation.remark ?? "", { + "quotation-payment": { + paymentType: quotation?.payCondition || "Full", + amount: quotation.finalPrice, + installments: quotation?.paySplit, + }, + "quotation-labor": { + name: quotation.worker.map( + (v, i) => + `${i + 1}. ` + + `${v.employee.employeePassport.length !== 0 ? v.employee.employeePassport[0].number + "_" : ""}${v.employee.namePrefix}. ${v.employee.firstNameEN ? `${v.employee.firstNameEN} ${v.employee.lastNameEN}` : `${v.employee.firstName} ${v.employee.lastName}`} `.toUpperCase(), + ), + }, + }), + ), items: product.map((v) => ({ type: ProductAndServiceType.ProductNonInv, name: v.product.name, - pricePerUnit: v.pricePerUnit, + pricePerUnit: precisionRound(v.pricePerUnit), quantity: v.amount, discountAmount: v.discount, - total: (v.pricePerUnit - (v.discount || 0)) * v.amount + v.vat, + total: + precisionRound(v.pricePerUnit * (1 + (v.vat > 0 ? VAT_DEFAULT : 0))) * v.amount - + (v.discount ?? 0), vatRate: v.vat === 0 ? 0 : Math.round(VAT_DEFAULT * 100), })), }; @@ -347,6 +395,187 @@ const flowAccount = { } return null; }, + + // flowAccount GET Product list + async getProducts() { + const { token } = await flowAccountAPI.auth(); + + const res = await fetch(api + "/products", { + method: "GET", + headers: { + ["Content-Type"]: `application/json`, + ["Authorization"]: `Bearer ${token}`, + }, + }); + + return { + ok: res.ok, + status: res.status, + body: await res.json(), + }; + }, + + // flowAccount GET Product by id + async getProductsById(recordId: string) { + const { token } = await flowAccountAPI.auth(); + + const res = await fetch(api + `/products/${recordId}`, { + method: "GET", + headers: { + ["Content-Type"]: `application/json`, + ["Authorization"]: `Bearer ${token}`, + }, + }); + + const data = await res.json(); + + return { + ok: res.ok, + status: res.status, + list: data.data.list, + total: data.data.total, + }; + }, + + // flowAccount POST create Product + async createProducts(body: JsonObject) { + const { token } = await flowAccountAPI.auth(); + + const commonBody = { + productStructureType: null, + type: "3", + name: body.name, + sellDescription: body.detail, + sellVatType: 3, + unitName: "Unit", + }; + + const createProduct = async (price: any, vatIncluded: boolean) => { + try { + const res = await fetch(api + "/products", { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify({ + ...commonBody, + sellPrice: price, + sellVatType: vatIncluded ? 1 : 3, + }), + }); + + if (!res.ok) { + throw new Error(`Request failed with status ${res.status}`); + } + + let json: any = null; + try { + json = await res.json(); + } catch { + throw new Error("Response is not valid JSON"); + } + + return json?.data?.list?.[0]?.id ?? null; + } catch (err) { + console.error("createProduct error:", err); + return null; + } + }; + + const [sellId, agentId] = await Promise.all([ + createProduct(body.price, /true/.test(`${body.vatIncluded}`)), + createProduct(body.agentPrice, /true/.test(`${body.agentPriceVatIncluded}`)), + ]); + + return { + ok: !!(agentId && sellId), + status: agentId && sellId ? 200 : 500, + data: { + productIdSellPrice: sellId, + productIdAgentPrice: agentId, + }, + }; + }, + + // flowAccount PUT edit Product + async editProducts(sellPriceId: String, agentPriceId: String, body: JsonObject) { + const { token } = await flowAccountAPI.auth(); + + const commonBody = { + productStructureType: null, + type: "3", + name: body.name, + sellDescription: body.detail, + sellVatType: 3, + unitName: "Unit", + categoryName: "Car", + }; + + const editProduct = async (id: String, price: any, vatIncluded: boolean) => { + try { + const res = await fetch(api + `/products/${id}`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify({ + ...commonBody, + sellPrice: price, + sellVatType: vatIncluded ? 1 : 3, + }), + }); + + if (!res.ok) { + throw new Error(`Request failed with status ${res.status}`); + } + + let json: any = null; + try { + json = await res.json(); + } catch { + throw new Error("Response is not valid JSON"); + } + + return json?.data?.list?.[0]?.id ?? null; + } catch (err) { + console.error("createProduct error:", err); + return null; + } + }; + + const [sellId, agentId] = await Promise.all([ + editProduct(sellPriceId, body.price, /true/.test(`${body.vatIncluded}`)), + editProduct(agentPriceId, body.agentPrice, /true/.test(`${body.agentPriceVatIncluded}`)), + ]); + + return { + ok: !!(agentId && sellId), + status: agentId && sellId ? 200 : 500, + data: { + productIdSellPrice: sellId, + productIdAgentPrice: agentId, + }, + }; + }, + + // flowAccount DELETE Product + async deleteProduct(recordId: string) { + const { token } = await flowAccountAPI.auth(); + + const res = await fetch(api + `/products/${recordId}`, { + method: "DELETE", + headers: { + ["Authorization"]: `Bearer ${token}`, + }, + }); + + return { + ok: res.ok, + status: res.status, + }; + }, }; export default flowAccount; diff --git a/src/utils/string-template.ts b/src/utils/string-template.ts new file mode 100644 index 0000000..aaa477e --- /dev/null +++ b/src/utils/string-template.ts @@ -0,0 +1,67 @@ +export function formatNumberDecimal(num: number, point: number = 2): string { + return (num || 0).toLocaleString("eng", { + minimumFractionDigits: point, + maximumFractionDigits: point, + }); +} + +const templates = { + "quotation-labor": { + converter: (context?: { name: string[] }) => { + return context?.name.join("
") || ""; + }, + }, + "quotation-payment": { + converter: (context?: { + paymentType: "Full" | "Split" | "SplitCustom" | "BillFull" | "BillSplit" | "BillSplitCustom"; + + amount?: number; + installments?: { + no: number; + amount: number; + }[]; + }) => { + if (context?.paymentType === "Full") { + return [ + "**** เงื่อนไขเพิ่มเติม", + "- เงื่อนไขการชำระเงิน แบบเต็มจำนวน", + `  จำนวน ${formatNumberDecimal(context?.amount || 0, 2)}`, + ].join("
"); + } else { + return [ + "**** เงื่อนไขเพิ่มเติม", + `- เงื่อนไขการชำระเงิน แบบแบ่งจ่าย${context?.paymentType === "SplitCustom" ? " กำหนดเอง " : " "}${context?.installments?.length} งวด`, + ...(context?.installments?.map( + (v) => `  งวดที่ ${v.no} จำนวน ${formatNumberDecimal(v.amount, 2)}`, + ) || []), + ].join("
"); + } + }, + }, +} as const; + +type Template = typeof templates; +type TemplateName = keyof Template; +type TemplateContext = { + [key in TemplateName]?: Parameters[0]; +}; + +export function convertTemplate( + text: string, + context?: TemplateContext, + templateUse?: TemplateName[], +) { + let ret = text; + + for (const [name, template] of Object.entries(templates)) { + if (templateUse && !templateUse.includes(name as TemplateName)) continue; + ret = ret.replace( + new RegExp("\\#\\[" + name.replaceAll("-", "\\-") + "\\]", "g"), + typeof template.converter === "function" + ? template.converter(context?.[name as TemplateName] as any) + : template.converter, + ); + } + + return ret; +} diff --git a/src/utils/thailand-area.ts b/src/utils/thailand-area.ts index 1319e0b..7bcf360 100644 --- a/src/utils/thailand-area.ts +++ b/src/utils/thailand-area.ts @@ -62,85 +62,90 @@ export async function initThailandAreaDatabase() { return result; } - await prisma.$transaction(async (tx) => { - const meta = { - createdBy: null, - createdAt: new Date(), - updatedBy: null, - updatedAt: new Date(), - }; + await prisma.$transaction( + async (tx) => { + const meta = { + createdBy: null, + createdAt: new Date(), + updatedBy: null, + updatedAt: new Date(), + }; - await Promise.all( - splitChunk(province, 1000, async (r) => { - return await tx.$kysely - .insertInto("Province") - .columns(["id", "name", "nameEN", "createdBy", "createdAt", "updatedBy", "updatedAt"]) - .values(r.map((v) => ({ ...v, ...meta }))) - .onConflict((oc) => - oc.column("id").doUpdateSet({ - name: (eb) => eb.ref("excluded.name"), - nameEN: (eb) => eb.ref("excluded.nameEN"), - updatedAt: (eb) => eb.ref("excluded.updatedAt"), - }), - ) - .execute(); - }), - ); + await Promise.all( + splitChunk(province, 1000, async (r) => { + return await tx.$kysely + .insertInto("Province") + .columns(["id", "name", "nameEN", "createdBy", "createdAt", "updatedBy", "updatedAt"]) + .values(r.map((v) => ({ ...v, ...meta }))) + .onConflict((oc) => + oc.column("id").doUpdateSet({ + name: (eb) => eb.ref("excluded.name"), + nameEN: (eb) => eb.ref("excluded.nameEN"), + updatedAt: (eb) => eb.ref("excluded.updatedAt"), + }), + ) + .execute(); + }), + ); - await Promise.all( - splitChunk(district, 2000, async (r) => { - return await tx.$kysely - .insertInto("District") - .columns([ - "id", - "name", - "nameEN", - "provinceId", - "createdBy", - "createdAt", - "updatedBy", - "updatedAt", - ]) - .values(r.map((v) => ({ ...v, ...meta }))) - .onConflict((oc) => - oc.column("id").doUpdateSet({ - name: (eb) => eb.ref("excluded.name"), - nameEN: (eb) => eb.ref("excluded.nameEN"), - provinceId: (eb) => eb.ref("excluded.provinceId"), - updatedAt: (eb) => eb.ref("excluded.updatedAt"), - }), - ) - .execute(); - }), - ); + await Promise.all( + splitChunk(district, 2000, async (r) => { + return await tx.$kysely + .insertInto("District") + .columns([ + "id", + "name", + "nameEN", + "provinceId", + "createdBy", + "createdAt", + "updatedBy", + "updatedAt", + ]) + .values(r.map((v) => ({ ...v, ...meta }))) + .onConflict((oc) => + oc.column("id").doUpdateSet({ + name: (eb) => eb.ref("excluded.name"), + nameEN: (eb) => eb.ref("excluded.nameEN"), + provinceId: (eb) => eb.ref("excluded.provinceId"), + updatedAt: (eb) => eb.ref("excluded.updatedAt"), + }), + ) + .execute(); + }), + ); - await Promise.all( - splitChunk(subDistrict, 1000, async (r) => { - return await tx.$kysely - .insertInto("SubDistrict") - .columns([ - "id", - "name", - "nameEN", - "districtId", - "createdBy", - "createdAt", - "updatedBy", - "updatedAt", - ]) - .values(r.map((v) => ({ ...v, ...meta }))) - .onConflict((oc) => - oc.column("id").doUpdateSet({ - name: (eb) => eb.ref("excluded.name"), - nameEN: (eb) => eb.ref("excluded.nameEN"), - districtId: (eb) => eb.ref("excluded.districtId"), - updatedAt: (eb) => eb.ref("excluded.updatedAt"), - }), - ) - .execute(); - }), - ); - }); + await Promise.all( + splitChunk(subDistrict, 1000, async (r) => { + return await tx.$kysely + .insertInto("SubDistrict") + .columns([ + "id", + "name", + "nameEN", + "districtId", + "createdBy", + "createdAt", + "updatedBy", + "updatedAt", + ]) + .values(r.map((v) => ({ ...v, ...meta }))) + .onConflict((oc) => + oc.column("id").doUpdateSet({ + name: (eb) => eb.ref("excluded.name"), + nameEN: (eb) => eb.ref("excluded.nameEN"), + districtId: (eb) => eb.ref("excluded.districtId"), + updatedAt: (eb) => eb.ref("excluded.updatedAt"), + }), + ) + .execute(); + }), + ); + }, + { + timeout: 15_000, + }, + ); console.log("[INFO]: Sync thailand province, district and subdistrict, OK."); } @@ -170,67 +175,72 @@ export async function initEmploymentOffice() { const list = await prisma.province.findMany(); - await prisma.$transaction(async (tx) => { - await Promise.all( - list - .map(async (province) => { - if (special[province.id]) { - await tx.employmentOffice.deleteMany({ - where: { provinceId: province.id, district: { none: {} } }, - }); - return await Promise.all( - Object.entries(special[province.id]).map(async ([key, val]) => { - const id = province.id + "-" + key.padStart(2, "0"); - return tx.employmentOffice.upsert({ - where: { id }, - create: { - id, - name: nameSpecial(province.name, +key), - nameEN: nameSpecialEN(province.nameEN, +key), - provinceId: province.id, - district: { - createMany: { - data: val.map((districtId) => ({ districtId })), - skipDuplicates: true, + await prisma.$transaction( + async (tx) => { + await Promise.all( + list + .map(async (province) => { + if (special[province.id]) { + await tx.employmentOffice.deleteMany({ + where: { provinceId: province.id, district: { none: {} } }, + }); + return await Promise.all( + Object.entries(special[province.id]).map(async ([key, val]) => { + const id = province.id + "-" + key.padStart(2, "0"); + return tx.employmentOffice.upsert({ + where: { id }, + create: { + id, + name: nameSpecial(province.name, +key), + nameEN: nameSpecialEN(province.nameEN, +key), + provinceId: province.id, + district: { + createMany: { + data: val.map((districtId) => ({ districtId })), + skipDuplicates: true, + }, }, }, - }, - update: { - id, - name: nameSpecial(province.name, +key), - nameEN: nameSpecialEN(province.nameEN, +key), - provinceId: province.id, - district: { - deleteMany: { districtId: { notIn: val } }, - createMany: { - data: val.map((districtId) => ({ districtId })), - skipDuplicates: true, + update: { + id, + name: nameSpecial(province.name, +key), + nameEN: nameSpecialEN(province.nameEN, +key), + provinceId: province.id, + district: { + deleteMany: { districtId: { notIn: val } }, + createMany: { + data: val.map((districtId) => ({ districtId })), + skipDuplicates: true, + }, }, }, - }, - }); - }), - ); - } + }); + }), + ); + } - return tx.employmentOffice.upsert({ - where: { id: province.id }, - create: { - id: province.id, - name: name(province.name), - nameEN: nameEN(province.nameEN), - provinceId: province.id, - }, - update: { - name: name(province.name), - nameEN: nameEN(province.nameEN), - provinceId: province.id, - }, - }); - }) - .flat(), - ); - }); + return tx.employmentOffice.upsert({ + where: { id: province.id }, + create: { + id: province.id, + name: name(province.name), + nameEN: nameEN(province.nameEN), + provinceId: province.id, + }, + update: { + name: name(province.name), + nameEN: nameEN(province.nameEN), + provinceId: province.id, + }, + }); + }) + .flat(), + ); + }, + { + timeout: 15_000, + }, + ); console.log("[INFO]: Sync employment office, OK."); }