diff --git a/Dockerfile b/Dockerfile index f29dc9a..7b7967d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,22 +1,34 @@ -FROM node:20-slim +FROM node:23-slim AS base -RUN apt-get update -y \ - && apt-get install -y openssl \ - && npm install -g pnpm \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* +ENV PNPM_HOME="/pnpm" +ENV PATH="$PNPM_HOME:$PATH" + +RUN corepack enable +RUN apt-get update && apt-get install -y openssl fontconfig +RUN fc-cache -f -v +RUN pnpm i -g prisma prisma-kysely WORKDIR /app -COPY package.json pnpm-lock.yaml ./ -RUN pnpm install --frozen-lockfile - COPY . . +FROM base AS deps +RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod --frozen-lockfile +RUN pnpm prisma generate + +FROM base AS build +RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile RUN pnpm prisma generate RUN pnpm run build -COPY entrypoint.sh /entrypoint.sh -RUN chmod +x /entrypoint.sh +FROM base AS prod -ENTRYPOINT ["/entrypoint.sh"] +ENV NODE_ENV="production" + +COPY --from=deps /app/node_modules /app/node_modules +COPY --from=build /app/dist /app/dist +COPY --from=base /app/static /app/static + +RUN chmod u+x ./entrypoint.sh + +ENTRYPOINT ["./entrypoint.sh"] diff --git a/package.json b/package.json index 69799c0..2f64c4f 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.16.2", + "prisma": "^6.3.0", "prisma-kysely": "^1.8.0", "ts-node": "^10.9.2", "typescript": "^5.7.2", @@ -40,14 +40,12 @@ "dependencies": { "@elastic/elasticsearch": "^8.17.0", "@fast-csv/parse": "^5.0.2", - "@prisma/client": "6.16.2", + "@prisma/client": "^6.3.0", "@scalar/express-api-reference": "^0.4.182", "@tsoa/runtime": "^6.6.0", - "@types/html-to-text": "^9.0.4", "canvas": "^3.1.0", "cors": "^2.8.5", "cron": "^3.3.1", - "csv-parse": "^6.1.0", "dayjs": "^1.11.13", "dayjs-plugin-utc": "^0.1.2", "docx-templates": "^4.13.0", @@ -55,7 +53,6 @@ "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", @@ -63,7 +60,6 @@ "morgan": "^1.10.0", "multer": "^1.4.5-lts.2", "nodemailer": "^6.10.0", - "pnpm": "^10.18.3", "prisma-extension-kysely": "^3.0.0", "promise.any": "^2.0.6", "thai-baht-text": "^2.0.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d724159..8471a8c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,17 +15,14 @@ importers: specifier: ^5.0.2 version: 5.0.2 '@prisma/client': - specifier: 6.16.2 - version: 6.16.2(prisma@6.16.2(typescript@5.7.2))(typescript@5.7.2) + specifier: ^6.3.0 + version: 6.3.0(prisma@6.3.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 @@ -35,9 +32,6 @@ importers: cron: specifier: ^3.3.1 version: 3.3.1 - csv-parse: - specifier: ^6.1.0 - version: 6.1.0 dayjs: specifier: ^1.11.13 version: 1.11.13 @@ -59,9 +53,6 @@ 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 @@ -83,12 +74,9 @@ importers: nodemailer: specifier: ^6.10.0 version: 6.10.0 - pnpm: - specifier: ^10.18.3 - version: 10.28.0 prisma-extension-kysely: specifier: ^3.0.0 - version: 3.0.0(@prisma/client@6.16.2(prisma@6.16.2(typescript@5.7.2))(typescript@5.7.2)) + version: 3.0.0(@prisma/client@6.3.0(prisma@6.3.0(typescript@5.7.2))(typescript@5.7.2)) promise.any: specifier: ^2.0.6 version: 2.0.6 @@ -139,8 +127,8 @@ importers: specifier: ^3.4.2 version: 3.4.2 prisma: - specifier: 6.16.2 - version: 6.16.2(typescript@5.7.2) + specifier: ^6.3.0 + version: 6.3.0(typescript@5.7.2) prisma-kysely: specifier: ^1.8.0 version: 1.8.0(encoding@0.1.13) @@ -152,7 +140,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)(jiti@2.5.1)(yaml@2.6.1) + version: 3.1.4(@types/node@20.17.10)(@vitest/ui@3.1.4)(yaml@2.6.1) packages: @@ -528,8 +516,8 @@ packages: '@polka/url@1.0.0-next.29': resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} - '@prisma/client@6.16.2': - resolution: {integrity: sha512-E00PxBcalMfYO/TWnXobBVUai6eW/g5OsifWQsQDzJYm7yaY+IRLo7ZLsaefi0QkTpxfuhFcQ/w180i6kX3iJw==} + '@prisma/client@6.3.0': + resolution: {integrity: sha512-BY3Fi28PUSk447Bpv22LhZp4HgNPo7NsEN+EteM1CLDnLjig5863jpW+3c3HHLFmml+nB/eJv1CjSriFZ8z7Cg==} engines: {node: '>=18.18'} peerDependencies: prisma: '*' @@ -540,29 +528,26 @@ packages: typescript: optional: true - '@prisma/config@6.16.2': - resolution: {integrity: sha512-mKXSUrcqXj0LXWPmJsK2s3p9PN+aoAbyMx7m5E1v1FufofR1ZpPoIArjjzOIm+bJRLLvYftoNYLx1tbHgF9/yg==} - '@prisma/debug@5.3.1': resolution: {integrity: sha512-eYrxqslEKf+wpMFIIHgbcNYuZBXUdiJLA85Or3TwOhgPIN1ZoXT9CwJph3ynW8H1Xg0LkdYLwVmuULCwiMoU5A==} - '@prisma/debug@6.16.2': - resolution: {integrity: sha512-bo4/gA/HVV6u8YK2uY6glhNsJ7r+k/i5iQ9ny/3q5bt9ijCj7WMPUwfTKPvtEgLP+/r26Z686ly11hhcLiQ8zA==} + '@prisma/debug@6.3.0': + resolution: {integrity: sha512-m1lQv//0Rc5RG8TBpNUuLCxC35Ghi5XfpPmL83Gh04/GICHD2J5H2ndMlaljrUNaQDF9dOxIuFAYP1rE9wkXkg==} - '@prisma/engines-version@6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43': - resolution: {integrity: sha512-ThvlDaKIVrnrv97ujNFDYiQbeMQpLa0O86HFA2mNoip4mtFqM7U5GSz2ie1i2xByZtvPztJlNRgPsXGeM/kqAA==} + '@prisma/engines-version@6.3.0-17.acc0b9dd43eb689cbd20c9470515d719db10d0b0': + resolution: {integrity: sha512-R/ZcMuaWZT2UBmgX3Ko6PAV3f8//ZzsjRIG1eKqp3f2rqEqVtCv+mtzuH2rBPUC9ujJ5kCb9wwpxeyCkLcHVyA==} '@prisma/engines@5.3.1': resolution: {integrity: sha512-6QkILNyfeeN67BNEPEtkgh3Xo2tm6D7V+UhrkBbRHqKw9CTaz/vvTP/ROwYSP/3JT2MtIutZm/EnhxUiuOPVDA==} - '@prisma/engines@6.16.2': - resolution: {integrity: sha512-7yf3AjfPUgsg/l7JSu1iEhsmZZ/YE00yURPjTikqm2z4btM0bCl2coFtTGfeSOWbQMmq45Jab+53yGUIAT1sjA==} + '@prisma/engines@6.3.0': + resolution: {integrity: sha512-RXqYhlZb9sx/xkUfYIZuEPn7sT0WgTxNOuEYQ7AGw3IMpP9QGVEDVsluc/GcNkM8NTJszeqk8AplJzI9lm7Jxw==} '@prisma/fetch-engine@5.3.1': resolution: {integrity: sha512-w1yk1YiK8N82Pobdq58b85l6e8akyrkxuzwV9DoiUTRf3gpsuhJJesHc4Yi0WzUC9/3znizl1UfCsI6dhkj3Vw==} - '@prisma/fetch-engine@6.16.2': - resolution: {integrity: sha512-wPnZ8DMRqpgzye758ZvfAMiNJRuYpz+rhgEBZi60ZqDIgOU2694oJxiuu3GKFeYeR/hXxso4/2oBC243t/whxQ==} + '@prisma/fetch-engine@6.3.0': + resolution: {integrity: sha512-GBy0iT4f1mH31ePzfcpVSUa7JLRTeq4914FG2vR3LqDwRweSm4ja1o5flGDz+eVIa/BNYfkBvRRxv4D6ve6Eew==} '@prisma/generator-helper@5.3.1': resolution: {integrity: sha512-zrYS0iHLgPlOJjYnd5KvVMMvSS+ktOL39EwooS5EnyvfzwfzxlKCeOUgxTfiKYs0WUWqzEvyNAYtramYgSknsQ==} @@ -570,8 +555,8 @@ packages: '@prisma/get-platform@5.3.1': resolution: {integrity: sha512-3IiZY2BUjKnAuZ0569zppZE6/rZbVAM09//c2nvPbbkGG9MqrirA8fbhhF7tfVmhyVfdmVCHnf/ujWPHJ8B46Q==} - '@prisma/get-platform@6.16.2': - resolution: {integrity: sha512-U/P36Uke5wS7r1+omtAgJpEB94tlT4SdlgaeTc6HVTTT93pXj7zZ+B/cZnmnvjcNPfWddgoDx8RLjmQwqGDYyA==} + '@prisma/get-platform@6.3.0': + resolution: {integrity: sha512-V8zZ1d0xfyi6FjpNP4AcYuwSpGcdmu35OXWnTPm8IW594PYALzKXHwIa9+o0f+Lo9AecFWrwrwaoYe56UNfTtQ==} '@prisma/internals@5.3.1': resolution: {integrity: sha512-zkW73hPHHNrMD21PeYgCTBfMu71vzJf+WtfydtJbS0JVJKyLfOel0iWSQg7wjNeQfccKp+NdHJ/5rTJ4NEUzgA==} @@ -691,12 +676,6 @@ 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==} @@ -763,9 +742,6 @@ 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==} @@ -1096,14 +1072,6 @@ 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'} @@ -1153,10 +1121,6 @@ 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==} @@ -1164,9 +1128,6 @@ 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==} @@ -1226,13 +1187,6 @@ 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==} @@ -1289,9 +1243,6 @@ packages: resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==} engines: {node: '>=8'} - csv-parse@6.1.0: - resolution: {integrity: sha512-CEE+jwpgLn+MmtCpVcPtiCZpVtB6Z2OKPTr34pycYYoL7sxdOkXDdQ4lRiw6ioC0q6BLqhc6cKweCVvral8yhw==} - data-view-buffer@1.0.1: resolution: {integrity: sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==} engines: {node: '>= 0.4'} @@ -1356,14 +1307,6 @@ 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'} @@ -1372,9 +1315,6 @@ 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'} @@ -1383,9 +1323,6 @@ 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} @@ -1410,19 +1347,6 @@ 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'} @@ -1431,10 +1355,6 @@ 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'} @@ -1451,9 +1371,6 @@ 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'} @@ -1464,10 +1381,6 @@ 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==} @@ -1485,10 +1398,6 @@ 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'} @@ -1586,13 +1495,6 @@ 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'} @@ -1750,10 +1652,6 @@ 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==} @@ -1842,13 +1740,6 @@ 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'} @@ -2107,10 +1998,6 @@ 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==} @@ -2153,9 +2040,6 @@ 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==} @@ -2425,9 +2309,6 @@ 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} @@ -2479,11 +2360,6 @@ 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'} @@ -2513,9 +2389,6 @@ 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'} @@ -2594,9 +2467,6 @@ 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'} @@ -2634,12 +2504,6 @@ 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==} @@ -2662,14 +2526,6 @@ packages: resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} engines: {node: '>=8'} - pkg-types@2.3.0: - resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} - - pnpm@10.28.0: - resolution: {integrity: sha512-Bd9x0UIfITmeBT/eVnzqNNRG+gLHZXFEG/wceVbpjjYwiJgtlARl/TRIDU2QoGaLwSNi+KqIAApk6D0LDke+SA==} - engines: {node: '>=18.12'} - hasBin: true - possible-typed-array-names@1.0.0: resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} engines: {node: '>= 0.4'} @@ -2697,8 +2553,8 @@ packages: resolution: {integrity: sha512-VpNpolZ8RXRgfU+j4R+fPZmX8EE95w3vJ2tt7+FwuiQc0leNTfLK5QLf3KbbPDes2rfjh3g20AjDxefQIo5GIA==} hasBin: true - prisma@6.16.2: - resolution: {integrity: sha512-aRvldGE5UUJTtVmFiH3WfNFNiqFlAtePUxcI0UEGlnXCX7DqhiMT5TRYwncHFeA/Reca5W6ToXXyCMTeFPdSXA==} + prisma@6.3.0: + resolution: {integrity: sha512-y+Zh3Qg+xGCWyyrNUUNaFW/OltaV/yXYuTa0WRgYkz5LGyifmAsgpv94I47+qGRocZrMGcbF2A/78/oO2zgifA==} engines: {node: '>=18.18'} hasBin: true peerDependencies: @@ -2745,9 +2601,6 @@ 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'} @@ -2770,9 +2623,6 @@ 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 @@ -2799,10 +2649,6 @@ 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==} @@ -2900,9 +2746,6 @@ 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 @@ -3177,9 +3020,6 @@ 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'} @@ -3993,20 +3833,11 @@ snapshots: '@polka/url@1.0.0-next.29': {} - '@prisma/client@6.16.2(prisma@6.16.2(typescript@5.7.2))(typescript@5.7.2)': + '@prisma/client@6.3.0(prisma@6.3.0(typescript@5.7.2))(typescript@5.7.2)': optionalDependencies: - prisma: 6.16.2(typescript@5.7.2) + prisma: 6.3.0(typescript@5.7.2) typescript: 5.7.2 - '@prisma/config@6.16.2': - 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 @@ -4015,18 +3846,18 @@ snapshots: transitivePeerDependencies: - supports-color - '@prisma/debug@6.16.2': {} + '@prisma/debug@6.3.0': {} - '@prisma/engines-version@6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43': {} + '@prisma/engines-version@6.3.0-17.acc0b9dd43eb689cbd20c9470515d719db10d0b0': {} '@prisma/engines@5.3.1': {} - '@prisma/engines@6.16.2': + '@prisma/engines@6.3.0': dependencies: - '@prisma/debug': 6.16.2 - '@prisma/engines-version': 6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43 - '@prisma/fetch-engine': 6.16.2 - '@prisma/get-platform': 6.16.2 + '@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/fetch-engine@5.3.1(encoding@0.1.13)': dependencies: @@ -4051,11 +3882,11 @@ snapshots: - encoding - supports-color - '@prisma/fetch-engine@6.16.2': + '@prisma/fetch-engine@6.3.0': dependencies: - '@prisma/debug': 6.16.2 - '@prisma/engines-version': 6.16.0-7.1c57fdcd7e44b29b9313256c76699e91c3ac3c43 - '@prisma/get-platform': 6.16.2 + '@prisma/debug': 6.3.0 + '@prisma/engines-version': 6.3.0-17.acc0b9dd43eb689cbd20c9470515d719db10d0b0 + '@prisma/get-platform': 6.3.0 '@prisma/generator-helper@5.3.1': dependencies: @@ -4081,9 +3912,9 @@ snapshots: transitivePeerDependencies: - supports-color - '@prisma/get-platform@6.16.2': + '@prisma/get-platform@6.3.0': dependencies: - '@prisma/debug': 6.16.2 + '@prisma/debug': 6.3.0 '@prisma/internals@5.3.1(encoding@0.1.13)': dependencies: @@ -4206,13 +4037,6 @@ 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 @@ -4312,8 +4136,6 @@ 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': {} @@ -4392,13 +4214,13 @@ snapshots: chai: 5.2.0 tinyrainbow: 2.0.0 - '@vitest/mocker@3.1.4(vite@6.3.5(@types/node@20.17.10)(jiti@2.5.1)(yaml@2.6.1))': + '@vitest/mocker@3.1.4(vite@6.3.5(@types/node@20.17.10)(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)(jiti@2.5.1)(yaml@2.6.1) + vite: 6.3.5(@types/node@20.17.10)(yaml@2.6.1) '@vitest/pretty-format@3.1.4': dependencies: @@ -4428,7 +4250,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)(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/utils@3.1.4': dependencies: @@ -4702,21 +4524,6 @@ 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: @@ -4796,18 +4603,10 @@ 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 @@ -4883,10 +4682,6 @@ 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 @@ -4938,8 +4733,6 @@ snapshots: crypto-random-string@2.0.0: {} - csv-parse@6.1.0: {} - data-view-buffer@1.0.1: dependencies: call-bind: 1.0.8 @@ -4988,10 +4781,6 @@ 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 @@ -5004,8 +4793,6 @@ 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 @@ -5019,8 +4806,6 @@ snapshots: depd@2.0.0: {} - destr@2.0.5: {} - destroy@1.2.0: {} detect-libc@2.0.4: {} @@ -5038,30 +4823,10 @@ 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 @@ -5080,11 +4845,6 @@ 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 @@ -5133,8 +4893,6 @@ snapshots: emoji-regex@9.2.2: {} - empathic@2.0.0: {} - enabled@2.0.0: {} encodeurl@1.0.2: {} @@ -5150,8 +4908,6 @@ snapshots: dependencies: once: 1.4.0 - entities@4.5.0: {} - env-paths@2.2.1: {} error-callsites@2.0.4: @@ -5368,12 +5124,6 @@ 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 @@ -5551,15 +5301,6 @@ 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: @@ -5658,21 +5399,6 @@ 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 @@ -5925,8 +5651,6 @@ snapshots: optionalDependencies: '@pkgjs/parseargs': 0.11.0 - jiti@2.5.1: {} - js-tokens@4.0.0: {} jsbarcode@3.11.6: {} @@ -5965,8 +5689,6 @@ snapshots: dependencies: readable-stream: 2.3.8 - leac@0.6.0: {} - lie@3.3.0: dependencies: immediate: 3.0.6 @@ -6212,8 +5934,6 @@ 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 @@ -6267,14 +5987,6 @@ 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: @@ -6305,8 +6017,6 @@ snapshots: obliterator@2.0.4: {} - ohash@2.0.11: {} - on-finished@2.3.0: dependencies: ee-first: 1.1.1 @@ -6386,11 +6096,6 @@ 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: {} @@ -6414,10 +6119,6 @@ snapshots: pathval@2.0.0: {} - peberminta@0.9.0: {} - - perfect-debounce@1.0.0: {} - picocolors@1.1.1: {} picomatch@2.3.1: {} @@ -6442,14 +6143,6 @@ 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 - - pnpm@10.28.0: {} - possible-typed-array-names@1.0.0: {} postcss@8.5.3: @@ -6475,9 +6168,9 @@ snapshots: prettier@3.4.2: {} - prisma-extension-kysely@3.0.0(@prisma/client@6.16.2(prisma@6.16.2(typescript@5.7.2))(typescript@5.7.2)): + prisma-extension-kysely@3.0.0(@prisma/client@6.3.0(prisma@6.3.0(typescript@5.7.2))(typescript@5.7.2)): dependencies: - '@prisma/client': 6.16.2(prisma@6.16.2(typescript@5.7.2))(typescript@5.7.2) + '@prisma/client': 6.3.0(prisma@6.3.0(typescript@5.7.2))(typescript@5.7.2) prisma-kysely@1.8.0(encoding@0.1.13): dependencies: @@ -6490,14 +6183,12 @@ snapshots: - encoding - supports-color - prisma@6.16.2(typescript@5.7.2): + prisma@6.3.0(typescript@5.7.2): dependencies: - '@prisma/config': 6.16.2 - '@prisma/engines': 6.16.2 + '@prisma/engines': 6.3.0 optionalDependencies: + fsevents: 2.3.3 typescript: 5.7.2 - transitivePeerDependencies: - - magicast process-nextick-args@2.0.1: {} @@ -6543,8 +6234,6 @@ snapshots: punycode@2.3.1: optional: true - pure-rand@6.1.0: {} - qs@6.13.0: dependencies: side-channel: 1.1.0 @@ -6570,11 +6259,6 @@ 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 @@ -6619,8 +6303,6 @@ snapshots: dependencies: picomatch: 2.3.1 - readdirp@4.1.2: {} - reflect-metadata@0.2.2: {} reflect.getprototypeof@1.0.8: @@ -6746,10 +6428,6 @@ snapshots: secure-json-parse@2.7.0: {} - selderee@0.11.0: - dependencies: - parseley: 0.12.1 - semver@5.7.2: {} semver@6.3.1: {} @@ -7066,8 +6744,6 @@ snapshots: tinyexec@0.3.2: {} - tinyexec@1.0.1: {} - tinyglobby@0.2.13: dependencies: fdir: 6.4.4(picomatch@4.0.2) @@ -7273,13 +6949,13 @@ snapshots: vary@1.1.2: {} - vite-node@3.1.4(@types/node@20.17.10)(jiti@2.5.1)(yaml@2.6.1): + vite-node@3.1.4(@types/node@20.17.10)(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)(jiti@2.5.1)(yaml@2.6.1) + vite: 6.3.5(@types/node@20.17.10)(yaml@2.6.1) transitivePeerDependencies: - '@types/node' - jiti @@ -7294,7 +6970,7 @@ snapshots: - tsx - yaml - vite@6.3.5(@types/node@20.17.10)(jiti@2.5.1)(yaml@2.6.1): + vite@6.3.5(@types/node@20.17.10)(yaml@2.6.1): dependencies: esbuild: 0.25.4 fdir: 6.4.4(picomatch@4.0.2) @@ -7305,13 +6981,12 @@ 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)(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): dependencies: '@vitest/expect': 3.1.4 - '@vitest/mocker': 3.1.4(vite@6.3.5(@types/node@20.17.10)(jiti@2.5.1)(yaml@2.6.1)) + '@vitest/mocker': 3.1.4(vite@6.3.5(@types/node@20.17.10)(yaml@2.6.1)) '@vitest/pretty-format': 3.1.4 '@vitest/runner': 3.1.4 '@vitest/snapshot': 3.1.4 @@ -7328,8 +7003,8 @@ snapshots: tinyglobby: 0.2.13 tinypool: 1.0.2 tinyrainbow: 2.0.0 - 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) + 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) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 20.17.10 diff --git a/prisma/migrations/20250716023822_allow_foreign_address/migration.sql b/prisma/migrations/20250716023822_allow_foreign_address/migration.sql deleted file mode 100644 index a255d89..0000000 --- a/prisma/migrations/20250716023822_allow_foreign_address/migration.sql +++ /dev/null @@ -1,6 +0,0 @@ --- AlterTable -ALTER TABLE "User" ADD COLUMN "addressForeign" BOOLEAN NOT NULL DEFAULT false, -ADD COLUMN "districtText" TEXT, -ADD COLUMN "provinceText" TEXT, -ADD COLUMN "subDistrictText" TEXT, -ADD COLUMN "zipCodeText" TEXT; diff --git a/prisma/migrations/20250716024423_add_address_text_en/migration.sql b/prisma/migrations/20250716024423_add_address_text_en/migration.sql deleted file mode 100644 index e55a4d0..0000000 --- a/prisma/migrations/20250716024423_add_address_text_en/migration.sql +++ /dev/null @@ -1,4 +0,0 @@ --- AlterTable -ALTER TABLE "User" ADD COLUMN "districtTextEN" TEXT, -ADD COLUMN "provinceTextEN" TEXT, -ADD COLUMN "subDistrictTextEN" TEXT; diff --git a/prisma/migrations/20250814060937_add_updated_at_to_work_step/migration.sql b/prisma/migrations/20250814060937_add_updated_at_to_work_step/migration.sql deleted file mode 100644 index d1bf6c5..0000000 --- a/prisma/migrations/20250814060937_add_updated_at_to_work_step/migration.sql +++ /dev/null @@ -1,2 +0,0 @@ --- AlterTable -ALTER TABLE "RequestWorkStepStatus" ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP; diff --git a/prisma/migrations/20250911021745_add_more_payment_metadata/migration.sql b/prisma/migrations/20250911021745_add_more_payment_metadata/migration.sql deleted file mode 100644 index 27036c0..0000000 --- a/prisma/migrations/20250911021745_add_more_payment_metadata/migration.sql +++ /dev/null @@ -1,4 +0,0 @@ --- 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 deleted file mode 100644 index 0ef2494..0000000 --- a/prisma/migrations/20250911092303_add_flow_account_product_id_sell_price_and_flow_account_product_id_agent_price_field_in_product_table/migration.sql +++ /dev/null @@ -1,3 +0,0 @@ --- AlterTable -ALTER TABLE "public"."Product" ADD COLUMN "flowAccountProductIdAgentPrice" TEXT, -ADD COLUMN "flowAccountProductIdSellPrice" TEXT; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 2f25bb2..e444ae1 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -398,24 +398,14 @@ model User { street String? streetEN String? - addressForeign Boolean @default(false) + province Province? @relation(fields: [provinceId], references: [id], onDelete: SetNull) + provinceId String? - provinceText String? - provinceTextEN String? - province Province? @relation(fields: [provinceId], references: [id], onDelete: SetNull) - provinceId String? + district District? @relation(fields: [districtId], references: [id], onDelete: SetNull) + districtId String? - districtText String? - districtTextEN String? - district District? @relation(fields: [districtId], references: [id], onDelete: SetNull) - districtId String? - - subDistrictText String? - subDistrictTextEN String? - subDistrict SubDistrict? @relation(fields: [subDistrictId], references: [id], onDelete: SetNull) - subDistrictId String? - - zipCodeText String? + subDistrict SubDistrict? @relation(fields: [subDistrictId], references: [id], onDelete: SetNull) + subDistrictId String? email String telephoneNo String @@ -1243,9 +1233,6 @@ model Product { productGroup ProductGroup @relation(fields: [productGroupId], references: [id], onDelete: Cascade) productGroupId String - flowAccountProductIdSellPrice String? - flowAccountProductIdAgentPrice String? - workProduct WorkProduct[] quotationProductServiceList QuotationProductServiceList[] taskProduct TaskProduct[] @@ -1527,11 +1514,8 @@ model Payment { paymentStatus PaymentStatus - amount Float - date DateTime? - channel String? - account String? - reference String? + amount Float + date DateTime? createdAt DateTime @default(now()) createdBy User? @relation(name: "PaymentCreatedByUser", fields: [createdByUserId], references: [id], onDelete: SetNull) @@ -1618,7 +1602,6 @@ model RequestWork { model RequestWorkStepStatus { step Int workStatus RequestWorkStatus @default(Pending) - updatedAt DateTime @default(now()) @updatedAt requestWork RequestWork @relation(fields: [requestWorkId], references: [id], onDelete: Cascade) requestWorkId String diff --git a/src/controllers/00-doc-template-controller.ts b/src/controllers/00-doc-template-controller.ts index 09dab18..bda7956 100644 --- a/src/controllers/00-doc-template-controller.ts +++ b/src/controllers/00-doc-template-controller.ts @@ -34,11 +34,6 @@ const quotationData = (id: string) => }, }, customerBranch: { - omit: { - otpCode: true, - otpExpires: true, - userId: true, - }, include: { customer: true, businessType: true, @@ -293,7 +288,6 @@ function replaceEmptyField(data: T): T { } type FullAddress = { - addressForeign?: boolean; address: string; addressEN: string; moo?: string; @@ -302,14 +296,8 @@ type FullAddress = { soiEN?: string; street?: string; streetEN?: string; - provinceText?: string | null; - provinceTextEN?: string | null; province?: Province | null; - districtText?: string | null; - districtTextEN?: string | null; district?: District | null; - subDistrictText?: string | null; - subDistrictTextEN?: string | null; subDistrict?: SubDistrict | null; en?: boolean; }; @@ -343,22 +331,13 @@ function addressFull(addr: FullAddress, lang: "th" | "en" = "en") { if (addr.soi) fragments.push(`ซอย ${addr.soi},`); if (addr.street) fragments.push(`ถนน${addr.street},`); - if (!addr.addressForeign && addr.subDistrict) { - fragments.push(`${addr.province?.id === "10" ? "แขวง" : "ตำบล"}${addr.subDistrict.name}`); + if (addr.subDistrict) { + fragments.push(`${addr.province?.id === "10" ? "แขวง" : "ตำบล"}${addr.subDistrict.name},`); } - if (addr.addressForeign && addr.subDistrictText) { - fragments.push(`ตำบล${addr.subDistrictText}`); + if (addr.district) { + fragments.push(`${addr.province?.id === "10" ? "เขต" : "อำเภอ"}${addr.district.name},`); } - - if (!addr.addressForeign && addr.district) { - fragments.push(`${addr.province?.id === "10" ? "เขต" : "อำเภอ"}${addr.district.name}`); - } - if (addr.addressForeign && addr.districtText) { - fragments.push(`อำเภอ${addr.districtText}`); - } - - if (!addr.addressForeign && addr.province) fragments.push(`จังหวัด${addr.province.name}`); - if (addr.addressForeign && addr.provinceText) fragments.push(`จังหวัด${addr.provinceText}`); + if (addr.province) fragments.push(`จังหวัด${addr.province.name},`); break; default: @@ -367,26 +346,11 @@ function addressFull(addr: FullAddress, lang: "th" | "en" = "en") { if (addr.soiEN) fragments.push(`Soi ${addr.soiEN},`); if (addr.streetEN) fragments.push(`${addr.streetEN} Rd.`); - if (!addr.addressForeign && addr.subDistrict) { + if (addr.subDistrict) { fragments.push(`${addr.subDistrict.nameEN} sub-district,`); } - if (addr.addressForeign && addr.subDistrictTextEN) { - fragments.push(`${addr.subDistrictTextEN} sub-district,`); - } - - if (!addr.addressForeign && addr.district) { - fragments.push(`${addr.district.nameEN} district,`); - } - if (addr.addressForeign && addr.districtTextEN) { - fragments.push(`${addr.districtTextEN} district,`); - } - - if (!addr.addressForeign && addr.province) { - fragments.push(`${addr.province.nameEN},`); - } - if (addr.addressForeign && addr.provinceTextEN) { - fragments.push(`${addr.provinceTextEN} district,`); - } + if (addr.district) fragments.push(`${addr.district.nameEN} district,`); + if (addr.province) fragments.push(`${addr.province.nameEN},`); break; } diff --git a/src/controllers/00-stats-controller.ts b/src/controllers/00-stats-controller.ts index 47e803e..b4aef31 100644 --- a/src/controllers/00-stats-controller.ts +++ b/src/controllers/00-stats-controller.ts @@ -618,22 +618,9 @@ export class StatsController extends Controller { startDate = dayjs(startDate).startOf("month").add(1, "month").toDate(); } - const invoices = await tx.invoice.findMany({ - select: { id: true }, - where: { - quotation: { - quotationStatus: { notIn: [QuotationStatus.Canceled] }, - registeredBranch: { OR: permissionCondCompany(req.user) }, - }, - }, - }); - - if (invoices.length === 0) return []; - return await Promise.all( months.map(async (v) => { const date = dayjs(v); - return { month: date.format("MM"), year: date.format("YYYY"), @@ -642,7 +629,11 @@ export class StatsController extends Controller { _sum: { amount: true }, where: { createdAt: { gte: v, lte: date.endOf("month").toDate() }, - invoiceId: { in: invoices.map((v) => v.id) }, + invoice: { + quotation: { + registeredBranch: { OR: permissionCondCompany(req.user) }, + }, + }, }, by: "paymentStatus", }) diff --git a/src/controllers/01-branch-controller.ts b/src/controllers/01-branch-controller.ts index 943413b..a09e24b 100644 --- a/src/controllers/01-branch-controller.ts +++ b/src/controllers/01-branch-controller.ts @@ -387,14 +387,6 @@ export class BranchController extends Controller { return record; } - @Get("{branchId}/bank") - @Security("keycloak") - async getBranchBankById(@Path() branchId: string) { - return await prisma.branchBank.findMany({ - where: { branchId }, - }); - } - @Post() @Security("keycloak", MANAGE_ROLES) async createBranch(@Request() req: RequestWithUser, @Body() body: BranchCreate) { diff --git a/src/controllers/02-user-controller.ts b/src/controllers/02-user-controller.ts index e05fcd1..143eb71 100644 --- a/src/controllers/02-user-controller.ts +++ b/src/controllers/02-user-controller.ts @@ -111,7 +111,6 @@ type UserCreate = { responsibleArea?: string[] | null; birthDate?: Date | null; - addressForeign?: boolean; address: string; addressEN: string; soi?: string | null; @@ -123,16 +122,9 @@ type UserCreate = { email: string; telephoneNo: string; - subDistrictText?: string | null; - subDistrictTextEN?: string | null; subDistrictId?: string | null; - districtText?: string | null; - districtTextEN?: string | null; districtId?: string | null; - provinceText?: string | null; - provinceTextEN?: string | null; provinceId?: string | null; - zipCodeText?: string | null; selectedImage?: string; @@ -181,7 +173,6 @@ type UserUpdate = { responsibleArea?: string[] | null; birthDate?: Date | null; - addressForeign?: boolean; address?: string; addressEN?: string; soi?: string | null; @@ -195,16 +186,9 @@ type UserUpdate = { selectedImage?: string; - subDistrictText?: string | null; - subDistrictTextEN?: string | null; subDistrictId?: string | null; - districtText?: string | null; - districtTextEN?: string | null; districtId?: string | null; - provinceText?: string | null; - provinceTextEN?: string | null; provinceId?: string | null; - zipCodeText?: string | null; branchId?: string | string[]; diff --git a/src/controllers/03-customer-branch-controller.ts b/src/controllers/03-customer-branch-controller.ts index a4d87e4..aada50c 100644 --- a/src/controllers/03-customer-branch-controller.ts +++ b/src/controllers/03-customer-branch-controller.ts @@ -238,11 +238,6 @@ 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, @@ -267,11 +262,6 @@ 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, @@ -362,11 +352,6 @@ export class CustomerBranchController extends Controller { include: branchRelationPermInclude(req.user), }, branch: { - omit: { - otpCode: true, - otpExpires: true, - userId: true, - }, take: 1, orderBy: { createdAt: "asc" }, }, @@ -638,7 +623,7 @@ export class CustomerBranchFileController extends Controller { }, }); if (!data) throw notFoundError("Customer Branch"); - await permissionCheckCompany(user, data.customer.registeredBranch); + await permissionCheck(user, data.customer.registeredBranch); } @Get("attachment") diff --git a/src/controllers/03-customer-controller.ts b/src/controllers/03-customer-controller.ts index bd3bc4d..0ff2b19 100644 --- a/src/controllers/03-customer-controller.ts +++ b/src/controllers/03-customer-controller.ts @@ -37,7 +37,6 @@ 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", @@ -170,10 +169,6 @@ export class CustomerController extends Controller { @Query() activeBranchOnly?: boolean, @Query() startDate?: Date, @Query() endDate?: Date, - @Query() businessTypeId?: string, - @Query() provinceId?: string, - @Query() districtId?: string, - @Query() subDistrictId?: string, ) { const where = { OR: queryOrNot(query, [ @@ -196,35 +191,6 @@ export class CustomerController extends Controller { : permissionCond(req.user, { activeOnly: activeBranchOnly }), }, }, - branch: { - some: { - AND: [ - businessTypeId - ? { - OR: [{ businessType: { id: businessTypeId } }], - } - : {}, - - provinceId - ? { - OR: [{ province: { id: provinceId } }], - } - : {}, - - districtId - ? { - OR: [{ district: { id: districtId } }], - } - : {}, - - subDistrictId - ? { - OR: [{ subDistrict: { id: subDistrictId } }], - } - : {}, - ], - }, - }, ...whereDateQuery(startDate, endDate), } satisfies Prisma.CustomerWhereInput; @@ -240,11 +206,6 @@ export class CustomerController extends Controller { district: true, subDistrict: true, }, - omit: { - otpCode: true, - otpExpires: true, - userId: true, - }, orderBy: [{ statusOrder: "asc" }, { createdAt: "asc" }], } : { @@ -253,11 +214,6 @@ export class CustomerController extends Controller { district: true, subDistrict: true, }, - omit: { - otpCode: true, - otpExpires: true, - userId: true, - }, take: 1, orderBy: { createdAt: "asc" }, }, @@ -288,11 +244,6 @@ export class CustomerController extends Controller { district: true, subDistrict: true, }, - omit: { - otpCode: true, - otpExpires: true, - userId: true, - }, orderBy: { createdAt: "asc" }, }, createdBy: true, @@ -364,11 +315,6 @@ export class CustomerController extends Controller { district: true, subDistrict: true, }, - omit: { - otpCode: true, - otpExpires: true, - userId: true, - }, }, createdBy: true, updatedBy: true, @@ -380,8 +326,6 @@ export class CustomerController extends Controller { ...v, code: `${runningKey.replace(`CUSTOMER_BRANCH_${company}_`, "")}-${`${last.value - branch.length + i}`.padStart(2, "0")}`, codeCustomer: runningKey.replace(`CUSTOMER_BRANCH_${company}_`, ""), - businessType: connectOrNot(v.businessTypeId), - businessTypeId: undefined, agentUser: connectOrNot(v.agentUserId), agentUserId: undefined, province: connectOrNot(v.provinceId), @@ -468,11 +412,6 @@ export class CustomerController extends Controller { district: true, subDistrict: true, }, - omit: { - otpCode: true, - otpExpires: true, - userId: true, - }, }, createdBy: true, updatedBy: true, @@ -511,13 +450,7 @@ export class CustomerController extends Controller { await deleteFolder(`customer/${customerId}`); const data = await tx.customer.delete({ include: { - branch: { - omit: { - otpCode: true, - otpExpires: true, - userId: true, - }, - }, + branch: true, registeredBranch: { include: { headOffice: true, @@ -612,52 +545,3 @@ 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, - @Query() businessTypeId?: string, - @Query() provinceId?: string, - @Query() districtId?: string, - @Query() subDistrictId?: string, - ) { - const ret = await this.list( - req, - customerType, - query, - status, - page, - pageSize, - includeBranch, - company, - activeBranchOnly, - startDate, - endDate, - businessTypeId, - provinceId, - districtId, - subDistrictId, - ); - - 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 303ba15..99d69ef 100644 --- a/src/controllers/03-employee-controller.ts +++ b/src/controllers/03-employee-controller.ts @@ -42,7 +42,6 @@ import { listFile, setFile, } from "../utils/minio"; -import { json2csv } from "json-2-csv"; if (!process.env.MINIO_BUCKET) { throw Error("Require MinIO bucket."); @@ -66,9 +65,7 @@ function globalAllow(user: RequestWithUser["user"]) { return user.roles?.some((v) => listAllowed.includes(v)) || false; } -const permissionCondCompany = createPermCondition((_) => true); const permissionCond = createPermCondition(globalAllow); -const permissionCheckCompany = createPermCheck((_) => true); const permissionCheck = createPermCheck(globalAllow); type EmployeeCreate = { @@ -250,6 +247,7 @@ export class EmployeeController extends Controller { endDate, ); } + @Post("list") @Security("keycloak") async listByCriteria( @@ -671,7 +669,7 @@ export class EmployeeFileController extends Controller { }, }); if (!data) throw notFoundError("Employee"); - await permissionCheckCompany(user, data.customerBranch.customer.registeredBranch); + await permissionCheck(user, data.customerBranch.customer.registeredBranch); } @Get("image") @@ -927,55 +925,3 @@ 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 512fff8..b50462c 100644 --- a/src/controllers/04-product-controller.ts +++ b/src/controllers/04-product-controller.ts @@ -30,8 +30,6 @@ 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"; -import { json2csv } from "json-2-csv"; const MANAGE_ROLES = [ "system", @@ -78,7 +76,6 @@ type ProductCreate = { type ProductUpdate = { status?: "ACTIVE" | "INACTIVE"; - code?: string; name?: string; detail?: string; process?: number; @@ -302,21 +299,13 @@ export class ProductController extends Controller { }, update: { value: { increment: 1 } }, }); - - const listId = await flowAccount.createProducts( - `${body.code.toLocaleUpperCase()}${last.value.toString().padStart(3, "0")}`, - body, - ); - - return await tx.product.create({ + return await prisma.product.create({ include: { createdBy: true, updatedBy: true, }, data: { ...body, - flowAccountProductIdAgentPrice: `${listId.data.productIdAgentPrice}`, - flowAccountProductIdSellPrice: `${listId.data.productIdSellPrice}`, document: body.document ? { createMany: { data: body.document.map((v) => ({ name: v })) }, @@ -390,30 +379,6 @@ export class ProductController extends Controller { await permissionCheck(req.user, productGroup.registeredBranch); } - if ( - product.flowAccountProductIdSellPrice !== null && - product.flowAccountProductIdAgentPrice !== null - ) { - const mergedBody = { - ...body, - code: body.code ?? product.code, - price: body.price ?? product.price, - agentPrice: body.agentPrice ?? product.agentPrice, - serviceCharge: body.serviceCharge ?? product.serviceCharge, - vatIncluded: body.vatIncluded ?? product.vatIncluded, - agentPriceVatIncluded: body.agentPriceVatIncluded ?? product.agentPriceVatIncluded, - serviceChargeVatIncluded: body.serviceChargeVatIncluded ?? product.serviceChargeVatIncluded, - }; - - await flowAccount.editProducts( - product.flowAccountProductIdSellPrice, - product.flowAccountProductIdAgentPrice, - mergedBody, - ); - } else { - throw notFoundError("FlowAccountProductId"); - } - const record = await prisma.product.update({ include: { productGroup: true, @@ -476,18 +441,6 @@ 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({ @@ -689,43 +642,3 @@ export class ProductFileController extends Controller { return await deleteFile(fileLocation.product.img(productId, name)); } } - -@Route("api/v1/product-export") -@Tags("Product") -export class ProductExportController extends ProductController { - @Get() - @Security("keycloak") - async exportCustomer( - @Request() req: RequestWithUser, - @Query() status?: Status, - @Query() shared?: boolean, - @Query() productGroupId?: string, - @Query() query: string = "", - @Query() page: number = 1, - @Query() pageSize: number = 30, - @Query() orderField?: keyof Product, - @Query() orderBy?: "asc" | "desc", - @Query() activeOnly?: boolean, - @Query() startDate?: Date, - @Query() endDate?: Date, - ) { - const ret = await this.getProduct( - req, - status, - shared, - productGroupId, - query, - page, - pageSize, - orderField, - orderBy, - activeOnly, - startDate, - endDate, - ); - - this.setHeader("Content-Type", "text/csv"); - - return json2csv(ret.result, { useDateIso8601Format: true, expandNestedObjects: true }); - } -} diff --git a/src/controllers/05-payment-controller.ts b/src/controllers/05-payment-controller.ts index 7ad9584..196054f 100644 --- a/src/controllers/05-payment-controller.ts +++ b/src/controllers/05-payment-controller.ts @@ -113,15 +113,7 @@ 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; - channel?: string | null; - account?: string | null; - reference?: string | null; - }, + @Body() body: { amount?: number; date?: Date; paymentStatus?: PaymentStatus }, @Request() req: RequestWithUser, ) { const record = await prisma.payment.findUnique({ @@ -152,18 +144,7 @@ export class QuotationPayment extends Controller { if (!record) throw notFoundError("Payment"); - 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, - }, - }); - } + if (record.paymentStatus === "PaymentSuccess") return record; return await prisma.$transaction(async (tx) => { const current = new Date(); @@ -268,7 +249,7 @@ export class QuotationPayment extends Controller { }, }); - if (quotation.quotationStatus === "PaymentInProcess") { + if (quotation.quotationStatus === "PaymentPending") { await prisma.notification.create({ data: { title: "รายการคำขอใหม่ / New Request", diff --git a/src/controllers/05-quotation-controller.ts b/src/controllers/05-quotation-controller.ts index f85b125..04353bb 100644 --- a/src/controllers/05-quotation-controller.ts +++ b/src/controllers/05-quotation-controller.ts @@ -527,15 +527,16 @@ export class QuotationController extends Controller { const vatIncluded = body.agentPrice ? p.agentPriceVatIncluded : p.vatIncluded; const originalPrice = body.agentPrice ? p.agentPrice : p.price; - const finalPrice = precisionRound( + const finalPriceWithVat = precisionRound( originalPrice + (vatIncluded ? 0 : originalPrice * VAT_DEFAULT), ); - const pricePerUnit = finalPrice / (1 + VAT_DEFAULT); + + const price = finalPriceWithVat; + const pricePerUnit = price / (1 + VAT_DEFAULT); const vat = (body.agentPrice ? p.agentPriceCalcVat : p.calcVat) - ? ((pricePerUnit * (1 + VAT_DEFAULT) * v.amount - (v.discount || 0)) / - (1 + VAT_DEFAULT)) * - VAT_DEFAULT + ? (pricePerUnit * v.amount - (v.discount || 0)) * VAT_DEFAULT : 0; + return { order: i + 1, productId: v.productId, @@ -556,13 +557,13 @@ export class QuotationController extends Controller { const price = list.reduce( (a, c) => { - 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.totalPrice = precisionRound(a.totalPrice + c.pricePerUnit * c.amount); a.totalDiscount = precisionRound(a.totalDiscount + c.discount); a.vat = precisionRound(a.vat + c.vat); - a.vatExcluded = c.vat === 0 ? precisionRound(a.vatExcluded + price) : a.vatExcluded; + a.vatExcluded = + c.vat === 0 + ? precisionRound(a.vatExcluded + (c.pricePerUnit * c.amount - (c.discount || 0))) + : a.vatExcluded; a.finalPrice = precisionRound( Math.max(a.totalPrice - a.totalDiscount + a.vat - (body.discount || 0), 0), ); @@ -663,14 +664,7 @@ export class QuotationController extends Controller { title: "ใบเสนอราคาใหม่ / New Quotation", detail: "รหัส / code : " + ret.code, registeredBranchId: ret.registeredBranchId, - groupReceiver: { - create: [ - { name: "sale" }, - { name: "head_of_sale" }, - { name: "accountant" }, - { name: "branch_accountant" }, - ], - }, + groupReceiver: { create: [{ name: "sale" }, { name: "head_of_sale" }] }, }, }); @@ -814,14 +808,14 @@ export class QuotationController extends Controller { const vatIncluded = record.agentPrice ? p.agentPriceVatIncluded : p.vatIncluded; const originalPrice = record.agentPrice ? p.agentPrice : p.price; - const finalPrice = precisionRound( + const finalPriceWithVat = precisionRound( originalPrice + (vatIncluded ? 0 : originalPrice * VAT_DEFAULT), ); - const pricePerUnit = finalPrice / (1 + VAT_DEFAULT); + + const price = finalPriceWithVat; + const pricePerUnit = price / (1 + VAT_DEFAULT); const vat = (record.agentPrice ? p.agentPriceCalcVat : p.calcVat) - ? ((pricePerUnit * (1 + VAT_DEFAULT) * v.amount - (v.discount || 0)) / - (1 + VAT_DEFAULT)) * - VAT_DEFAULT + ? (pricePerUnit * v.amount - (v.discount || 0)) * VAT_DEFAULT : 0; return { @@ -844,13 +838,15 @@ export class QuotationController extends Controller { const price = list?.reduce( (a, c) => { - 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.totalPrice = precisionRound(a.totalPrice + c.pricePerUnit * c.amount); a.totalDiscount = precisionRound(a.totalDiscount + c.discount); a.vat = precisionRound(a.vat + c.vat); - a.vatExcluded = c.vat === 0 ? precisionRound(a.vatExcluded + price) : a.vatExcluded; + a.vatExcluded = + c.vat === 0 + ? precisionRound( + a.vatExcluded + (c.pricePerUnit * c.amount - (c.discount || 0)) * VAT_DEFAULT, + ) + : a.vatExcluded; a.finalPrice = precisionRound( Math.max(a.totalPrice - a.totalDiscount + a.vat - (body.discount || 0), 0), ); @@ -866,7 +862,6 @@ export class QuotationController extends Controller { finalPrice: 0, }, ); - const changed = list?.some((lhs) => { const found = record.productServiceList.find((rhs) => { return ( @@ -900,20 +895,6 @@ export class QuotationController extends Controller { }), ]); - if (customerBranch) { - await tx.customerBranch.update({ - where: { id: customerBranch.id }, - data: { - customer: { - update: { - status: Status.ACTIVE, - }, - }, - status: Status.ACTIVE, - }, - }); - } - return await tx.quotation.update({ include: { productServiceList: { diff --git a/src/controllers/06-request-list-controller.ts b/src/controllers/06-request-list-controller.ts index a453716..37fd8e2 100644 --- a/src/controllers/06-request-list-controller.ts +++ b/src/controllers/06-request-list-controller.ts @@ -293,62 +293,34 @@ export class RequestDataController extends Controller { async updateRequestData( @Request() req: RequestWithUser, @Body() - body: { + boby: { defaultMessengerId: string; requestDataId: string[]; }, ) { - 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), - }, + const record = await prisma.requestData.updateManyAndReturn({ + where: { + id: { in: boby.requestDataId }, + quotation: { + registeredBranch: { + OR: permissionCond(req.user), }, }, - 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]; + }, + data: { + defaultMessengerId: boby.defaultMessengerId, + }, }); + + if (record.length <= 0) throw notFoundError("Request Data"); + + return record[0]; } } @Route("/api/v1/request-data/{requestDataId}") @Tags("Request List") export class RequestDataActionController extends Controller { - async #getLineToken() { - if (!process.env.LINE_MESSAGING_API_TOKEN) { - console.warn("Line Webhook Activated but LINE_MESSAGING_API_TOKEN not set."); - } - - return process.env.LINE_MESSAGING_API_TOKEN; - } - @Post("reject-request-cancel") @Security("keycloak") async rejectRequestCancel( @@ -423,17 +395,6 @@ export class RequestDataActionController extends Controller { }, }, }, - include: { - quotation: { - include: { - customerBranch: { - include: { - customer: { include: { branch: { where: { userId: { not: null } } } } }, - }, - }, - }, - }, - }, }); if (!result) throw notFoundError("Request Data"); @@ -476,88 +437,23 @@ export class RequestDataActionController extends Controller { data: { quotationStatus: QuotationStatus.Canceled, urgent: false }, }) .then(async (res) => { - await Promise.all( - res.map((v) => - tx.notification.create({ - data: { - title: "สถานะใบเสนอราคาเปลี่ยนแปลง / Quotation Status Updated", - detail: "รหัส / code : " + v.code + " Canceled", - receiverId: v.createdByUserId, - registeredBranchId: v.registeredBranchId, - groupReceiver: { create: { name: "document_checker" } }, - }, - }), - ), - ); + await tx.notification.createMany({ + data: res.map((v) => ({ + title: "สถานะใบเสนอราคาเปลี่ยนแปลง / Quotation Status Updated", + detail: "รหัส / code : " + v.code + " Canceled", + receiverId: v.createdByUserId, + })), + }); }), - tx.taskOrder - .updateManyAndReturn({ - where: { - taskList: { - every: { taskStatus: TaskStatus.Canceled }, - }, + tx.taskOrder.updateMany({ + where: { + taskList: { + every: { taskStatus: TaskStatus.Canceled }, }, - data: { taskOrderStatus: TaskStatus.Canceled }, - }) - .then(async (res) => { - await Promise.all( - res.map((v) => - tx.notification.create({ - data: { - title: "สถานะใบเสนอราคาเปลี่ยนแปลง / Quotation Status Updated", - detail: "รหัส / code : " + v.code + " Canceled", - receiverId: v.createdByUserId, - registeredBranchId: v.registeredBranchId, - groupReceiver: { create: { name: "document_checker" } }, - }, - }), - ), - ); - }), - ]); - - const token = await this.#getLineToken(); - if (!token) return; - - const textHead = "JWS ALERT:"; - - const textAlert = "ขอแจ้งให้ทราบว่าใบเสนอราคา"; - const textAlert2 = "ได้ดำเนินการยกเลิกเรียบร้อยแล้ว"; - const textAlert3 = "หากต้องการข้อมูลเพิ่มเติม กรุณาแจ้งให้ฝ่ายที่เกี่ยวข้องทราบ 🙏"; - let finalTextWork = ""; - let textData = ""; - - let dataCustomerId: string[] = []; - let dataUserId: string[] = []; - - result.quotation.customerBranch.customer.branch.forEach((item) => { - if (!dataCustomerId?.includes(item.id) && item.userId) { - dataCustomerId.push(item.id); - dataUserId.push(item.userId); - } - }); - finalTextWork = `เลขที่ใบเสนอราคา: ${result.code} ${result.quotation.workName}`; - - textData = `${textHead}\n\n${textAlert}\n${finalTextWork}\n${textAlert2}\n\n${textAlert3}`; - - const data = { - to: dataUserId, - messages: [ - { - type: "text", - text: textData, }, - ], - }; - - await fetch("https://api.line.me/v2/bot/message/multicast", { - method: "POST", - headers: { - Authorization: `Bearer ${token}`, - "Content-Type": "application/json", - }, - body: JSON.stringify(data), - }); + data: { taskOrderStatus: TaskStatus.Canceled }, + }), + ]); }); } @@ -689,19 +585,13 @@ export class RequestDataActionController extends Controller { data: { quotationStatus: QuotationStatus.Canceled, urgent: false }, }) .then(async (res) => { - await Promise.all( - res.map((v) => - tx.notification.create({ - data: { - title: "สถานะใบเสนอราคาเปลี่ยนแปลง / Quotation Status Updated", - detail: "รหัส / code : " + v.code + " Canceled", - receiverId: v.createdByUserId, - registeredBranchId: v.registeredBranchId, - groupReceiver: { create: { name: "document_checker" } }, - }, - }), - ), - ); + await tx.notification.createMany({ + data: res.map((v) => ({ + title: "สถานะใบเสนอราคาเปลี่ยนแปลง / Quotation Status Updated", + detail: "รหัส / code : " + v.code + " Canceled", + receiverId: v.createdByUserId, + })), + }); }), tx.taskOrder.updateMany({ where: { @@ -784,83 +674,14 @@ export class RequestDataActionController extends Controller { }, }, data: { quotationStatus: QuotationStatus.ProcessComplete, urgent: false }, - include: { - customerBranch: { - include: { - customer: { - include: { - branch: { - where: { userId: { not: null } }, - }, - }, - }, - }, - }, - }, }) .then(async (res) => { - await Promise.all( - res.map((v) => - tx.notification.create({ - data: { - title: "สถานะใบเสนอราคาเปลี่ยนแปลง / Quotation Status Updated", - detail: "รหัส / code : " + v.code + " Completed", - receiverId: v.createdByUserId, - registeredBranchId: v.registeredBranchId, - groupReceiver: { create: { name: "document_checker" } }, - }, - }), - ), - ); - - const token = await this.#getLineToken(); - if (!token) return; - - const textHead = "JWS ALERT:"; - - const textAlert = "ขอแจ้งให้ทราบว่าใบเสนอราคา"; - const textAlert2 = "ได้ดำเนินการเสร็จสิ้นทุกกระบวนการเรียบร้อยแล้ว"; - const textAlert3 = "หากต้องการข้อมูลเพิ่มเติม กรุณาแจ้งให้ฝ่ายที่เกี่ยวข้องทราบ 🙏"; - let finalTextWork = ""; - let textData = ""; - - let dataCustomerId: string[] = []; - let textWorkList: string[] = []; - let dataUserId: string[] = []; - - if (res) { - res.forEach((data, index) => { - data.customerBranch.customer.branch.forEach((item) => { - if (!dataCustomerId?.includes(item.id) && item.userId) { - dataCustomerId.push(item.id); - dataUserId.push(item.userId); - } - }); - textWorkList.push(`${index + 1}. เลขที่ใบเสนอราคา ${data.code} ${data.workName}`); - }); - - finalTextWork = textWorkList.join("\n"); - } - - textData = `${textHead}\n\n${textAlert}\n${finalTextWork}\n${textAlert2}\n\n${textAlert3}`; - - const data = { - to: dataUserId, - messages: [ - { - type: "text", - text: textData, - }, - ], - }; - - await fetch("https://api.line.me/v2/bot/message/multicast", { - method: "POST", - headers: { - Authorization: `Bearer ${token}`, - "Content-Type": "application/json", - }, - body: JSON.stringify(data), + await tx.notification.createMany({ + data: res.map((v) => ({ + title: "สถานะใบเสนอราคาเปลี่ยนแปลง / Quotation Status Updated", + detail: "รหัส / code : " + v.code + " Completed", + receiverId: v.createdByUserId, + })), }); }); // dataRecord.push(record); @@ -1217,19 +1038,13 @@ export class RequestListController extends Controller { data: { quotationStatus: QuotationStatus.Canceled, urgent: false }, }) .then(async (res) => { - await Promise.all( - res.map((v) => - tx.notification.create({ - data: { - title: "สถานะใบเสนอราคาเปลี่ยนแปลง / Quotation Status Updated", - detail: "รหัส / code : " + v.code + " Canceled", - receiverId: v.createdByUserId, - registeredBranchId: v.registeredBranchId, - groupReceiver: { create: { name: "document_checker" } }, - }, - }), - ), - ); + await tx.notification.createMany({ + data: res.map((v) => ({ + title: "สถานะใบเสนอราคาเปลี่ยนแปลง / Quotation Status Updated", + detail: "รหัส / code : " + v.code + " Canceled", + receiverId: v.createdByUserId, + })), + }); }), tx.taskOrder.updateMany({ where: { @@ -1337,19 +1152,13 @@ export class RequestListController extends Controller { }, }) .then(async (res) => { - await Promise.all( - res.map((v) => - tx.notification.create({ - data: { - title: "สถานะใบเสนอราคาเปลี่ยนแปลง / Quotation Status Updated", - detail: "รหัส / code : " + v.code + " Completed", - receiverId: v.createdByUserId, - registeredBranchId: v.registeredBranchId, - groupReceiver: { create: { name: "document_checker" } }, - }, - }), - ), - ); + await tx.notification.createMany({ + data: res.map((v) => ({ + title: "สถานะใบเสนอราคาเปลี่ยนแปลง / Quotation Status Updated", + detail: "รหัส / code : " + v.code + " Completed", + receiverId: v.createdByUserId, + })), + }); const token = await this.#getLineToken(); if (!token) return; diff --git a/src/controllers/07-task-controller.ts b/src/controllers/07-task-controller.ts index 02def1e..6707781 100644 --- a/src/controllers/07-task-controller.ts +++ b/src/controllers/07-task-controller.ts @@ -70,14 +70,12 @@ const permissionCheckCompany = createPermCheck((_) => true); @Tags("Task Order") export class TaskController extends Controller { @Get("stats") - @Security("keycloak") async getTaskOrderStats(@Request() req: RequestWithUser) { const task = await prisma.taskOrder.groupBy({ where: { registeredBranch: { OR: permissionCondCompany(req.user) } }, by: ["taskOrderStatus"], _count: true, }); - return task.reduce>( (a, c) => Object.assign(a, { [c.taskOrderStatus]: c._count }), { @@ -265,12 +263,6 @@ export class TaskController extends Controller { taskProduct?: { productId: string; discount?: number }[]; }, ) { - if (body.taskList.length < 1 || !body.registeredBranchId) - throw new HttpError( - HttpStatus.BAD_REQUEST, - "Your created invalid task order", - "taskOrderInvalid", - ); return await prisma.$transaction(async (tx) => { const last = await tx.runningNo.upsert({ where: { @@ -320,8 +312,8 @@ export class TaskController extends Controller { if (updated.count !== taskList.length) { throw new HttpError( HttpStatus.PRECONDITION_FAILED, - "all request work to issue task order must be in ready state.", - "requestworkmustready", + "All request work to issue task order must be in ready state.", + "requestWorkMustReady", ); } await tx.institution.updateMany({ @@ -344,51 +336,49 @@ export class TaskController extends Controller { where: { OR: taskList }, }); - return await tx.taskOrder - .create({ - include: { - taskList: { - include: { - requestWorkStep: { - include: { - requestWork: { - include: { - request: { - include: { - employee: true, - quotation: { - include: { - customerBranch: { - include: { - customer: true, - }, + return await tx.taskOrder.create({ + include: { + taskList: { + include: { + requestWorkStep: { + include: { + requestWork: { + include: { + request: { + include: { + employee: true, + quotation: { + include: { + customerBranch: { + include: { + customer: true, }, }, }, }, }, - productService: { - include: { - service: { - include: { - workflow: { - include: { - step: { - include: { - value: true, - responsiblePerson: { - include: { user: true }, - }, - responsibleInstitution: true, + }, + productService: { + include: { + service: { + include: { + workflow: { + include: { + step: { + include: { + value: true, + responsiblePerson: { + include: { user: true }, }, + responsibleInstitution: true, }, }, }, }, }, - work: true, - product: true, }, + work: true, + product: true, }, }, }, @@ -396,30 +386,20 @@ export class TaskController extends Controller { }, }, }, - institution: true, - createdBy: true, }, - data: { - ...rest, - code, - urgent: work.some((v) => v.requestWork.request.quotation.urgent), - registeredBranchId: userAffiliatedBranch.id, - createdByUserId: req.user.sub, - taskList: { create: taskList }, - taskProduct: { create: taskProduct }, - }, - }) - .then(async (v) => { - await prisma.notification.create({ - data: { - title: "ใบสั่งงานใหม่ / New Task Order", - detail: "รหัส / code : " + v.code, - registeredBranchId: v.registeredBranchId, - groupReceiver: { create: { name: "document_checker" } }, - }, - }); - return v; - }); + institution: true, + createdBy: true, + }, + data: { + ...rest, + code, + urgent: work.some((v) => v.requestWork.request.quotation.urgent), + registeredBranchId: userAffiliatedBranch.id, + createdByUserId: req.user.sub, + taskList: { create: taskList }, + taskProduct: { create: taskProduct }, + }, + }); }); } @@ -562,8 +542,6 @@ export class TaskController extends Controller { title: "มีการส่งงาน / Task Submitted", detail: "รหัสใบสั่งงาน / Order : " + record.code, receiverId: record.createdByUserId, - registeredBranchId: record.registeredBranchId, - groupReceiver: { create: { name: "document_checker" } }, }, }); } @@ -758,8 +736,6 @@ export class TaskActionController extends Controller { title: "มีการส่งงาน / Task Submitted", detail: "รหัสใบสั่งงาน / Order : " + record.code, receiverId: record.createdByUserId, - registeredBranchId: record.registeredBranchId, - groupReceiver: { create: { name: "document_checker" } }, }, }), ]); @@ -792,34 +768,22 @@ export class TaskActionController extends Controller { const code = `RI${year}${month}${last.value.toString().padStart(6, "0")}`; await Promise.all([ - tx.taskOrder - .update({ - where: { id: taskOrderId }, - data: { - urgent: false, - taskOrderStatus: TaskOrderStatus.Complete, - codeProductReceived: code, - userTask: { - updateMany: { - where: { taskOrderId }, - data: { - userTaskStatus: UserTaskStatus.Submit, - }, + tx.taskOrder.update({ + where: { id: taskOrderId }, + data: { + urgent: false, + taskOrderStatus: TaskOrderStatus.Complete, + codeProductReceived: code, + userTask: { + updateMany: { + where: { taskOrderId }, + data: { + userTaskStatus: UserTaskStatus.Submit, }, }, }, - }) - .then(async (record) => { - await tx.notification.create({ - data: { - title: "ใบงานเสร็จสิ้น / Task Complete", - detail: "รหัสใบสั่งงาน / Order : " + record.code, - receiverId: record.createdByUserId, - registeredBranchId: record.registeredBranchId, - groupReceiver: { create: { name: "document_checker" } }, - }, - }); - }), + }, + }), tx.requestWorkStepStatus.updateMany({ where: { task: { @@ -923,34 +887,10 @@ export class TaskActionController extends Controller { if (completeCheck) completed.push(item.id); }); - await tx.requestData - .updateManyAndReturn({ - where: { id: { in: completed } }, - include: { - quotation: { - select: { - registeredBranchId: true, - createdByUserId: true, - }, - }, - }, - data: { requestDataStatus: RequestDataStatus.Completed }, - }) - .then(async (res) => { - await Promise.all( - res.map((v) => - tx.notification.create({ - data: { - title: "สถานะใบเสนอราคาเปลี่ยนแปลง / Quotation Status Updated", - detail: "รหัส / code : " + v.code + " Completed", - receiverId: v.quotation.createdByUserId, - registeredBranchId: v.quotation.registeredBranchId, - groupReceiver: { create: { name: "document_checker" } }, - }, - }), - ), - ); - }); + await tx.requestData.updateMany({ + where: { id: { in: completed } }, + data: { requestDataStatus: RequestDataStatus.Completed }, + }); await tx.quotation .updateManyAndReturn({ where: { @@ -990,19 +930,13 @@ export class TaskActionController extends Controller { }, }) .then(async (res) => { - await Promise.all( - res.map((v) => - tx.notification.create({ - data: { - title: "สถานะใบเสนอราคาเปลี่ยนแปลง / Quotation Status Updated", - detail: "รหัส / code : " + v.code + " Completed", - receiverId: v.createdByUserId, - registeredBranchId: v.registeredBranchId, - groupReceiver: { create: { name: "document_checker" } }, - }, - }), - ), - ); + await tx.notification.createMany({ + data: res.map((v) => ({ + title: "สถานะใบเสนอราคาเปลี่ยนแปลง / Quotation Status Updated", + detail: "รหัส / code : " + v.code + " Completed", + receiverId: v.createdByUserId, + })), + }); const token = await this.#getLineToken(); @@ -1241,23 +1175,19 @@ export class UserTaskController extends Controller { }, }) .then(async (v) => { - await tx.notification.create({ - data: { - title: "สถานะใบส่งงานมีการเปลี่ยนแปลง / Order Status Changed", - detail: "รหัสใบสั่งงาน / Order : " + v.code + " InProgress", - receiverId: v.createdByUserId, - registeredBranchId: v.registeredBranchId, - groupReceiver: { create: { name: "document_checker" } }, - }, - }); - await tx.notification.create({ - data: { - title: "มีการรับงาน / Task Accepted", - detail: "รหัสใบสั่งงาน / Order : " + v.code, - receiverId: v.createdByUserId, - registeredBranchId: v.registeredBranchId, - groupReceiver: { create: { name: "document_checker" } }, - }, + await tx.notification.createMany({ + data: [ + { + title: "สถานะใบส่งงานมีการเปลี่ยนแปลง / Order Status Changed", + detail: "รหัสใบสั่งงาน / Order : " + v.code + " InProgress", + receiverId: v.createdByUserId, + }, + { + title: "มีการรับงาน / Task Accepted", + detail: "รหัสใบสั่งงาน / Order : " + v.code, + receiverId: v.createdByUserId, + }, + ], }); }), tx.task.updateMany({ diff --git a/src/controllers/08-credit-note-controller.ts b/src/controllers/08-credit-note-controller.ts index a8e8767..fd7a2a3 100644 --- a/src/controllers/08-credit-note-controller.ts +++ b/src/controllers/08-credit-note-controller.ts @@ -13,7 +13,6 @@ import { Security, Tags, } from "tsoa"; -import config from "../config.json"; import prisma from "../db"; @@ -54,7 +53,6 @@ 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); @@ -85,14 +83,6 @@ type CreditNoteUpdate = { @Route("api/v1/credit-note") @Tags("Credit Note") export class CreditNoteController extends Controller { - async #getLineToken() { - if (!process.env.LINE_MESSAGING_API_TOKEN) { - console.warn("Line Webhook Activated but LINE_MESSAGING_API_TOKEN not set."); - } - - return process.env.LINE_MESSAGING_API_TOKEN; - } - @Get("stats") @Security("keycloak") async getCreditNoteStats(@Request() req: RequestWithUser, @Query() quotationId?: string) { @@ -156,6 +146,7 @@ export class CreditNoteController extends Controller { @Query() creditNoteStatus?: CreditNoteStatus, @Query() startDate?: Date, @Query() endDate?: Date, + @Body() body?: {}, ) { const where = { OR: queryOrNot(query, [ @@ -217,8 +208,6 @@ export class CreditNoteController extends Controller { const [result, total] = await prisma.$transaction([ prisma.creditNote.findMany({ where, - take: pageSize, - skip: (page - 1) * pageSize, include: { quotation: { include: { @@ -349,8 +338,9 @@ export class CreditNoteController extends Controller { ).length; const price = - c.productService.pricePerUnit * (1 + (c.productService.vat > 0 ? VAT_DEFAULT : 0)) - - c.productService.discount; + c.productService.pricePerUnit - + c.productService.discount / c.productService.amount + + c.productService.vat / c.productService.amount; if (serviceChargeStepCount && successCount) { return a + price - c.productService.product.serviceCharge * successCount; @@ -376,91 +366,33 @@ export class CreditNoteController extends Controller { update: { value: { increment: 1 } }, }); - return await prisma.creditNote - .create({ - include: { - requestWork: { - include: { - request: true, - }, - }, - quotation: { - include: { - customerBranch: { - include: { - customer: { include: { branch: { where: { userId: { not: null } } } } }, - }, - }, - }, + return await prisma.creditNote.create({ + include: { + requestWork: { + include: { + request: true, }, }, - data: { - reason: body.reason, - detail: body.detail, - remark: body.remark, - paybackType: body.paybackType, - paybackBank: body.paybackBank, - paybackAccount: body.paybackAccount, - paybackAccountName: body.paybackAccountName, - code: `CN${currentYear.toString().padStart(2, "0")}${currentMonth.toString().padStart(2, "0")}${last.value.toString().padStart(6, "0")}`, - value, - requestWork: { - connect: body.requestWorkId.map((v) => ({ - id: v, - })), - }, - quotationId: body.quotationId, + quotation: true, + }, + data: { + reason: body.reason, + detail: body.detail, + remark: body.remark, + paybackType: body.paybackType, + paybackBank: body.paybackBank, + paybackAccount: body.paybackAccount, + paybackAccountName: body.paybackAccountName, + code: `CN${currentYear.toString().padStart(2, "0")}${currentMonth.toString().padStart(2, "0")}${last.value.toString().padStart(6, "0")}`, + value, + requestWork: { + connect: body.requestWorkId.map((v) => ({ + id: v, + })), }, - }) - .then(async (res) => { - const token = await this.#getLineToken(); - if (!token) return; - - const textHead = "JWS ALERT:"; - - const textAlert = "ขอแจ้งให้ทราบว่าใบลดหนี้"; - const textAlert2 = "ได้ถูกสร้างขึ้นเรียบร้อยแล้ว"; - const textAlert3 = - "หากท่านต้องการข้อมูลเพิ่มเติมหรือมีข้อสงสัยประการใด โปรดแจ้งให้ฝ่ายที่เกี่ยวข้องทราบ ทางเรายินดีให้ความช่วยเหลืออย่างเต็มที่ 🙏"; - let finalTextWork = ""; - let textData = ""; - - let dataCustomerId: string[] = []; - let dataUserId: string[] = []; - - if (res) { - res.quotation.customerBranch.customer.branch.forEach((item) => { - if (!dataCustomerId?.includes(item.id) && item.userId) { - dataCustomerId.push(item.id); - dataUserId.push(item.userId); - } - }); - finalTextWork = `จำนวนเงิน ${res.value.toFixed(2)} บาท `; - } - - textData = `${textHead}\n\n${textAlert}\n${finalTextWork}${textAlert2}\n\n${textAlert3}`; - - const data = { - to: dataUserId, - messages: [ - { - type: "text", - text: textData, - }, - ], - }; - - await fetch("https://api.line.me/v2/bot/message/multicast", { - method: "POST", - headers: { - Authorization: `Bearer ${token}`, - "Content-Type": "application/json", - }, - body: JSON.stringify(data), - }); - - return res; - }); + quotationId: body.quotationId, + }, + }); }, { isolationLevel: Prisma.TransactionIsolationLevel.Serializable }, ); @@ -542,8 +474,9 @@ export class CreditNoteController extends Controller { ).length; const price = - c.productService.pricePerUnit * (1 + (c.productService.vat > 0 ? VAT_DEFAULT : 0)) - - c.productService.discount; + c.productService.pricePerUnit - + c.productService.discount / c.productService.amount + + c.productService.vat / c.productService.amount; if (serviceChargeStepCount && successCount) { return a + price - c.productService.product.serviceCharge * successCount; @@ -640,14 +573,6 @@ export class CreditNoteActionController extends Controller { return creditNoteData; } - async #getLineToken() { - if (!process.env.LINE_MESSAGING_API_TOKEN) { - console.warn("Line Webhook Activated but LINE_MESSAGING_API_TOKEN not set."); - } - - return process.env.LINE_MESSAGING_API_TOKEN; - } - @Post("accept") @Security("keycloak", MANAGE_ROLES) async acceptCreditNote(@Request() req: RequestWithUser, @Path() creditNoteId: string) { @@ -666,81 +591,23 @@ export class CreditNoteActionController extends Controller { @Body() body: { paybackStatus: PaybackStatus }, ) { await this.#checkPermission(req.user, creditNoteId); - return await prisma.creditNote - .update({ - where: { id: creditNoteId }, - include: { - requestWork: { - include: { - request: true, - }, - }, - quotation: { - include: { - customerBranch: { - include: { - customer: { include: { branch: { where: { userId: { not: null } } } } }, - }, - }, - }, + return await prisma.creditNote.update({ + where: { id: creditNoteId }, + include: { + requestWork: { + include: { + request: true, }, }, - data: { - creditNoteStatus: - body.paybackStatus === PaybackStatus.Done ? CreditNoteStatus.Success : undefined, - paybackStatus: body.paybackStatus, - paybackDate: body.paybackStatus === PaybackStatus.Done ? new Date() : undefined, - }, - }) - .then(async (res) => { - const token = await this.#getLineToken(); - if (!token) return; - - const textHead = "JWS ALERT:"; - - const textAlert = "ทางเราขอแจ้งให้ทราบว่าการดำเนินการคืนเงินสำหรับใบลดหนี้"; - const textAlert2 = "ได้รับการอนุมัติและเสร็จสมบูรณ์เรียบร้อยแล้ว"; - const textAlert3 = - "หากท่านต้องการข้อมูลเพิ่มเติมหรือมีข้อสงสัยประการใด โปรดแจ้งให้ฝ่ายที่เกี่ยวข้องทราบ ทางเรายินดีให้ความช่วยเหลืออย่างเต็มที่ 🙏"; - let finalTextWork = ""; - let textData = ""; - - let dataCustomerId: string[] = []; - let textWorkList: string[] = []; - let dataUserId: string[] = []; - - if (res) { - res.quotation.customerBranch.customer.branch.forEach((item) => { - if (!dataCustomerId?.includes(item.id) && item.userId) { - dataCustomerId.push(item.id); - dataUserId.push(item.userId); - } - }); - finalTextWork = `จำนวนเงิน ${res.value.toFixed(2)} บาท `; - } - - textData = `${textHead}\n\n${textAlert}\n${finalTextWork}${textAlert2}\n\n${textAlert3}`; - - const data = { - to: dataUserId, - messages: [ - { - type: "text", - text: textData, - }, - ], - }; - body.paybackStatus === PaybackStatus.Done - ? await fetch("https://api.line.me/v2/bot/message/multicast", { - method: "POST", - headers: { - Authorization: `Bearer ${token}`, - "Content-Type": "application/json", - }, - body: JSON.stringify(data), - }) - : undefined; - }); + quotation: true, + }, + data: { + creditNoteStatus: + body.paybackStatus === PaybackStatus.Done ? CreditNoteStatus.Success : undefined, + paybackStatus: body.paybackStatus, + paybackDate: body.paybackStatus === PaybackStatus.Done ? new Date() : undefined, + }, + }); } } diff --git a/src/controllers/09-debit-note-controller.ts b/src/controllers/09-debit-note-controller.ts index f87bbe9..5fcdee9 100644 --- a/src/controllers/09-debit-note-controller.ts +++ b/src/controllers/09-debit-note-controller.ts @@ -430,18 +430,12 @@ export class DebitNoteController extends Controller { const list = body.productServiceList.map((v, i) => { const p = product.find((p) => p.id === v.productId)!; - - 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 + 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) : 0; return { @@ -464,13 +458,15 @@ export class DebitNoteController extends Controller { const price = list.reduce( (a, c) => { - 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.totalPrice = precisionRound(a.totalPrice + c.pricePerUnit * c.amount); a.totalDiscount = precisionRound(a.totalDiscount + c.discount); a.vat = precisionRound(a.vat + c.vat); - a.vatExcluded = c.vat === 0 ? precisionRound(a.vatExcluded + price) : a.vatExcluded; + a.vatExcluded = + c.vat === 0 + ? precisionRound( + a.vatExcluded + (c.pricePerUnit * c.amount - (c.discount || 0)) * VAT_DEFAULT, + ) + : a.vatExcluded; a.finalPrice = precisionRound( Math.max(a.totalPrice - a.totalDiscount + a.vat - (body.discount || 0), 0), ); @@ -677,18 +673,12 @@ export class DebitNoteController extends Controller { } const list = body.productServiceList.map((v, i) => { const p = product.find((p) => p.id === v.productId)!; - - 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 + 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) : 0; return { @@ -711,13 +701,15 @@ export class DebitNoteController extends Controller { const price = list.reduce( (a, c) => { - 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.totalPrice = precisionRound(a.totalPrice + c.pricePerUnit * c.amount); a.totalDiscount = precisionRound(a.totalDiscount + c.discount); a.vat = precisionRound(a.vat + c.vat); - a.vatExcluded = c.vat === 0 ? precisionRound(a.vatExcluded + price) : a.vatExcluded; + a.vatExcluded = + c.vat === 0 + ? precisionRound( + a.vatExcluded + (c.pricePerUnit * c.amount - (c.discount || 0)) * VAT_DEFAULT, + ) + : a.vatExcluded; a.finalPrice = precisionRound( Math.max(a.totalPrice - a.totalDiscount + a.vat - (body.discount || 0), 0), ); diff --git a/src/controllers/09-line-controller.ts b/src/controllers/09-line-controller.ts index 87480b7..8e708be 100644 --- a/src/controllers/09-line-controller.ts +++ b/src/controllers/09-line-controller.ts @@ -613,22 +613,39 @@ export class LineController extends Controller { @Query() endDate?: Date, ) { const where = { - OR: queryOrNot(query, [ - { code: { contains: query, mode: "insensitive" } }, - { workName: { contains: query, mode: "insensitive" } }, - { - customerBranch: { - OR: [ - { code: { contains: query, mode: "insensitive" } }, - { registerName: { contains: query, mode: "insensitive" } }, - { firstName: { contains: query, mode: "insensitive" } }, - { firstNameEN: { contains: query, mode: "insensitive" } }, - { lastName: { contains: query, mode: "insensitive" } }, - { lastNameEN: { contains: query, mode: "insensitive" } }, - ], - }, - }, - ]), + OR: + query || pendingOnly + ? [ + ...(queryOrNot(query, [ + { code: { contains: query, mode: "insensitive" } }, + { workName: { contains: query, mode: "insensitive" } }, + { + customerBranch: { + OR: [ + { code: { contains: query, mode: "insensitive" } }, + { registerName: { contains: query, mode: "insensitive" } }, + { firstName: { contains: query, mode: "insensitive" } }, + { firstNameEN: { contains: query, mode: "insensitive" } }, + { lastName: { contains: query, mode: "insensitive" } }, + { lastNameEN: { contains: query, mode: "insensitive" } }, + ], + }, + }, + ]) || []), + ...(queryOrNot(!!pendingOnly, [ + { + requestData: { + some: { + requestDataStatus: "Pending", + }, + }, + }, + { + requestData: { none: {} }, + }, + ]) || []), + ] + : undefined, isDebitNote: false, code, payCondition, @@ -650,22 +667,6 @@ export class LineController extends Controller { }, } : undefined, - AND: pendingOnly - ? { - OR: [ - { - requestData: { - some: { - requestDataStatus: "Pending", - }, - }, - }, - { - requestData: { none: {} }, - }, - ], - } - : undefined, ...whereDateQuery(startDate, endDate), } satisfies Prisma.QuotationWhereInput; diff --git a/src/services/flowaccount.ts b/src/services/flowaccount.ts index 7faf492..730482d 100644 --- a/src/services/flowaccount.ts +++ b/src/services/flowaccount.ts @@ -1,10 +1,6 @@ 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"); @@ -236,29 +232,6 @@ 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, @@ -289,58 +262,19 @@ const flowAccount = { const quotation = data.quotation; const customer = quotation.customerBranch; - - const summary = { - subTotal: 0, - discountAmount: 0, - vatableAmount: 0, - exemptAmount: 0, - vatAmount: 0, - grandTotal: 0, - }; - - const products = ( + const product = quotation.payCondition === PayCondition.BillFull || quotation.payCondition === PayCondition.Full ? quotation.productServiceList : quotation.productServiceList.filter((lhs) => data.installments.some((rhs) => rhs.no === lhs.installmentNo), - ) - ).map((v) => { - // TODO: Use product's VAT field (not implemented) instead. - const VAT_RATE = VAT_DEFAULT; - - summary.subTotal += - precisionRound(v.pricePerUnit * (1 + (v.vat > 0 ? VAT_RATE : 0))) * v.amount; - summary.discountAmount += v.discount; - - const total = - precisionRound(v.pricePerUnit * (1 + (v.vat > 0 ? VAT_RATE : 0))) * v.amount - - (v.discount ?? 0); - - if (v.vat > 0) { - summary.vatableAmount += precisionRound(total / (1 + VAT_RATE)); - summary.vatAmount += v.vat; - } else { - summary.exemptAmount += total; - } - - summary.grandTotal += total; - - return { - type: ProductAndServiceType.ProductNonInv, - name: v.product.name, - pricePerUnit: precisionRound(v.pricePerUnit), - quantity: v.amount, - discountAmount: v.discount, - vatRate: v.vat === 0 ? 0 : Math.round(VAT_RATE * 100), - total, - }; - }); - + ); const payload = { contactCode: customer.code, - contactName: customer.contactName || "-", + contactName: + (customer.customer.customerType === CustomerType.PERS + ? [customer.firstName, customer.lastName].join(" ").trim() + : customer.registerName) || "-", contactAddress: [ customer.address, !!customer.moo ? "หมู่ " + customer.moo : null, @@ -349,10 +283,11 @@ const flowAccount = { (customer.province?.id === "10" ? "แขวง" : "อำเภอ") + customer.subDistrict?.name, (customer.province?.id === "10" ? "เขต" : "ตำบล") + customer.district?.name, "จังหวัด" + customer.province?.name, + customer.subDistrict?.zipCode, ] .filter(Boolean) .join(" "), - contactTaxId: customer.citizenId || customer.legalPersonNo || "-", + contactTaxId: customer.citizenId || customer.code, contactBranch: (customer.customer.customerType === CustomerType.PERS ? [customer.firstName, customer.lastName].join(" ").trim() @@ -370,35 +305,36 @@ const flowAccount = { isVat: true, useReceiptDeduction: false, - useInlineVat: true, discounPercentage: 0, discountAmount: quotation.totalDiscount, - subTotal: summary.subTotal, - totalAfterDiscount: summary.subTotal - summary.discountAmount, - vatableAmount: summary.vatableAmount, - exemptAmount: summary.exemptAmount, - vatAmount: summary.vatAmount, - grandTotal: summary.grandTotal, + subTotal: + quotation.payCondition === "BillSplitCustom" || quotation.payCondition === "SplitCustom" + ? 0 + : quotation.totalPrice, + totalAfterDiscount: + quotation.payCondition === "BillSplitCustom" || quotation.payCondition === "SplitCustom" + ? 0 + : quotation.finalPrice, + vatAmount: + quotation.payCondition === "BillSplitCustom" || quotation.payCondition === "SplitCustom" + ? 0 + : quotation.vat, + grandTotal: + quotation.payCondition === "BillSplitCustom" || quotation.payCondition === "SplitCustom" + ? 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: products, + items: product.map((v) => ({ + type: ProductAndServiceType.ProductNonInv, + name: v.product.name, + pricePerUnit: v.pricePerUnit, + quantity: v.amount, + discountAmount: v.discount, + total: (v.pricePerUnit - (v.discount || 0)) * v.amount + v.vat, + vatRate: v.vat === 0 ? 0 : Math.round(VAT_DEFAULT * 100), + })), }; return await flowAccountAPI.createReceipt(payload, false); @@ -411,219 +347,6 @@ 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(code: string, body: JsonObject) { - const { token } = await flowAccountAPI.auth(); - - const commonBody = { - productStructureType: null, - type: 3, - name: body.name, - sellDescription: body.detail, - sellVatType: 3, - buyPrice: body.serviceCharge, - buyVatType: body.serviceChargeVatIncluded ? 1 : 3, - buyDescription: body.detail, - }; - - const createProduct = async (name: string, 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, - name, - sellPrice: price, - sellVatType: vatIncluded ? 1 : 3, - }), - }); - - if (!res.ok) throw new Error(`HTTP ${res.status}: Failed to create product`); - - const json = await res.json().catch(() => { - throw new Error("Invalid JSON response from FlowAccount API"); - }); - - return json?.data?.list?.[0]?.id ?? null; - } catch (err) { - console.error("createProduct error:", err); - return null; - } - }; - - const deleteProduct = async (id: string) => { - try { - await fetch(`${api}/products/${id}`, { - method: "DELETE", - headers: { Authorization: `Bearer ${token}` }, - }); - } catch (err) { - console.error("Rollback delete failed:", err); - } - }; - - const [sellResult, agentResult] = await Promise.allSettled([ - createProduct(`${code} ${body.name}`, body.price, /true/.test(`${body.vatIncluded}`)), - createProduct( - `${code} ${body.name} (ราคาตัวแทน)`, - body.agentPrice, - /true/.test(`${body.agentPriceVatIncluded}`), - ), - ]); - - const sellId = sellResult.status === "fulfilled" ? sellResult.value : null; - const agentId = agentResult.status === "fulfilled" ? agentResult.value : null; - - // --- validation --- - if (!sellId && !agentId) { - throw new Error("FlowAccountProductError.BOTH_CREATION_FAILED"); - } - if (!sellId && agentId) { - await deleteProduct(agentId); - throw new Error("FlowAccountProductError.SELL_PRICE_CREATION_FAILED"); - } - if (sellId && !agentId) { - await deleteProduct(sellId); - throw new Error("FlowAccountProductError.AGENT_PRICE_CREATION_FAILED"); - } - - return { - ok: true, - status: 200, - 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, - buyPrice: body.serviceCharge, - buyVatType: body.serviceChargeVatIncluded ? 1 : 3, - buyDescription: body.detail, - }; - - const editProduct = async (id: String, name: 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, - name: name, - sellPrice: price, - sellVatType: vatIncluded ? 1 : 3, - }), - }); - - if (!res.ok) { - throw new Error(`Request failed with status ${res.status} ${res}`); - } - - 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; - } - }; - - await Promise.all([ - editProduct( - sellPriceId, - `${body.code} ${body.name}`, - body.price, - /true/.test(`${body.vatIncluded}`), - ), - editProduct( - agentPriceId, - `${body.code} ${body.name} (ราคาตัวแทน)`, - body.agentPrice, - /true/.test(`${body.agentPriceVatIncluded}`), - ), - ]); - }, - - // 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 deleted file mode 100644 index aaa477e..0000000 --- a/src/utils/string-template.ts +++ /dev/null @@ -1,67 +0,0 @@ -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 7bcf360..1319e0b 100644 --- a/src/utils/thailand-area.ts +++ b/src/utils/thailand-area.ts @@ -62,90 +62,85 @@ 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(); - }), - ); - }, - { - timeout: 15_000, - }, - ); + 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(); + }), + ); + }); console.log("[INFO]: Sync thailand province, district and subdistrict, OK."); } @@ -175,72 +170,67 @@ 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, - }, - }, - }, - 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, - }, + 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: {} } }, }); - }) - .flat(), - ); - }, - { - timeout: 15_000, - }, - ); + 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, + }, + }, + }, + }); + }), + ); + } + + 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(), + ); + }); console.log("[INFO]: Sync employment office, OK."); }