Merge branch 'main' of git.targo.ca:Targo/targo_backend
This commit is contained in:
commit
750dd95071
46
Dockerfile
Normal file
46
Dockerfile
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
# Use the official Node.js image as the base image
|
||||
FROM node:22
|
||||
|
||||
# Set the working directory inside the container
|
||||
WORKDIR /app
|
||||
|
||||
# Set the environment variables
|
||||
ENV DATABASE_URL_PROD="postgresql://apptargo:6wLAZrb0HZnd3mrmqXiArPcqLyui0o9e@10.100.0.116/app_targo_db?schema=public"
|
||||
ENV DATABASE_URL_STAGING="postgresql://apptargo:6wLAZrb0HZnd3mrmqXiArPcqLyui0o9e@10.100.0.116/app_targo_db_staging?schema=public"
|
||||
ENV DATABASE_URL_DEV="postgresql://apptargo:6wLAZrb0HZnd3mrmqXiArPcqLyui0o9e@10.100.0.116/app_targo_db_dev?schema=public"
|
||||
|
||||
ENV AUTHENTIK_ISSUER="https://auth.targo.ca/application/o/montargo/"
|
||||
ENV AUTHENTIK_CLIENT_ID="KUmSmvpu2aDDy4JfNwas7XriNFtPcj2Ka2PyLO5v"
|
||||
ENV AUTHENTIK_CLIENT_SECRET="N55BgX1mxT7eiY99LOo5zXr5cKz9FgTsaCA9MdC7D8ZuhOGqozvqtNXVGbpY1eCg2kkYwJeJLP89sQ8R4cYybIJI7EwKijb19bzZQpUPwBosWwG3irUwdTnZOyw8yW5i"
|
||||
ENV AUTHENTIK_CALLBACK_URL="http://10.100.251.2:3420/auth/callback"
|
||||
ENV AUTHENTIK_AUTH_URL="https://auth.targo.ca/application/o/authorize/"
|
||||
ENV AUTHENTIK_TOKEN_URL="https://auth.targo.ca/application/o/token/"
|
||||
ENV AUTHENTIK_USERINFO_URL="https://auth.targo.ca/application/o/userinfo/"
|
||||
|
||||
ENV TARGO_FRONTEND_URI="http://10.100.251.2/"
|
||||
|
||||
ENV ATTACHMENTS_SERVER_ID="server"
|
||||
ENV ATTACHMENTS_ROOT=C:/
|
||||
ENV MAX_UPLOAD_MB=25
|
||||
ENV ALLOWED_MIME=image/jpeg,image/png,image/webp,application/pdf
|
||||
|
||||
# Copy package.json and package-lock.json to the working directory
|
||||
COPY package*.json ./
|
||||
|
||||
# Install the application dependencies
|
||||
RUN npm install
|
||||
|
||||
# Copy the rest of the application files
|
||||
COPY . .
|
||||
|
||||
# Generate Prisma client
|
||||
RUN npx prisma generate
|
||||
|
||||
# Build the NestJS application
|
||||
RUN npm run build
|
||||
|
||||
# Expose the application port
|
||||
EXPOSE 3000
|
||||
|
||||
# Command to run the application
|
||||
CMD ["node", "dist/src/main"]
|
||||
File diff suppressed because it is too large
Load Diff
185
package-lock.json
generated
185
package-lock.json
generated
|
|
@ -17,7 +17,7 @@
|
|||
"@nestjs/platform-express": "^11.1.6",
|
||||
"@nestjs/schedule": "^6.0.0",
|
||||
"@nestjs/swagger": "^11.2.0",
|
||||
"@prisma/client": "^6.17.1",
|
||||
"@prisma/client": "^6.18.0",
|
||||
"bullmq": "^5.58.0",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.2",
|
||||
|
|
@ -54,7 +54,7 @@
|
|||
"globals": "^16.0.0",
|
||||
"jest": "^29.7.0",
|
||||
"prettier": "^3.4.2",
|
||||
"prisma": "^6.17.1",
|
||||
"prisma": "^6.18.0",
|
||||
"source-map-support": "^0.5.21",
|
||||
"supertest": "^7.0.0",
|
||||
"ts-jest": "^29.2.5",
|
||||
|
|
@ -243,6 +243,7 @@
|
|||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz",
|
||||
"integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@ampproject/remapping": "^2.2.0",
|
||||
"@babel/code-frame": "^7.27.1",
|
||||
|
|
@ -3113,6 +3114,7 @@
|
|||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
|
||||
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"fast-uri": "^3.0.1",
|
||||
|
|
@ -3268,13 +3270,14 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@nestjs/common": {
|
||||
"version": "11.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.1.6.tgz",
|
||||
"integrity": "sha512-krKwLLcFmeuKDqngG2N/RuZHCs2ycsKcxWIDgcm7i1lf3sQ0iG03ci+DsP/r3FcT/eJDFsIHnKtNta2LIi7PzQ==",
|
||||
"version": "11.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.1.7.tgz",
|
||||
"integrity": "sha512-lwlObwGgIlpXSXYOTpfzdCepUyWomz6bv9qzGzzvpgspUxkj0Uz0fUJcvD44V8Ps7QhKW3lZBoYbXrH25UZrbA==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"file-type": "21.0.0",
|
||||
"iterare": "1.2.1",
|
||||
"load-esm": "1.0.2",
|
||||
"load-esm": "1.0.3",
|
||||
"tslib": "2.8.1",
|
||||
"uid": "2.0.2"
|
||||
},
|
||||
|
|
@ -3312,15 +3315,16 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@nestjs/core": {
|
||||
"version": "11.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.1.6.tgz",
|
||||
"integrity": "sha512-siWX7UDgErisW18VTeJA+x+/tpNZrJewjTBsRPF3JVxuWRuAB1kRoiJcxHgln8Lb5UY9NdvklITR84DUEXD0Cg==",
|
||||
"version": "11.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.1.7.tgz",
|
||||
"integrity": "sha512-TyXFOwjhHv/goSgJ8i20K78jwTM0iSpk9GBcC2h3mf4MxNy+znI8m7nWjfoACjTkb89cTwDQetfTHtSfGLLaiA==",
|
||||
"hasInstallScript": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@nuxt/opencollective": "0.4.1",
|
||||
"fast-safe-stringify": "2.1.1",
|
||||
"iterare": "1.2.1",
|
||||
"path-to-regexp": "8.2.0",
|
||||
"path-to-regexp": "8.3.0",
|
||||
"tslib": "2.8.1",
|
||||
"uid": "2.0.2"
|
||||
},
|
||||
|
|
@ -3392,14 +3396,15 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@nestjs/platform-express": {
|
||||
"version": "11.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.1.6.tgz",
|
||||
"integrity": "sha512-HErwPmKnk+loTq8qzu1up+k7FC6Kqa8x6lJ4cDw77KnTxLzsCaPt+jBvOq6UfICmfqcqCCf3dKXg+aObQp+kIQ==",
|
||||
"version": "11.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.1.7.tgz",
|
||||
"integrity": "sha512-5T+GLdvTiGPKB4/P4PM9ftKUKNHJy8ThEFhZA3vQnXVL7Vf0rDr07TfVTySVu+XTh85m1lpFVuyFM6u6wLNsRA==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"cors": "2.8.5",
|
||||
"express": "5.1.0",
|
||||
"multer": "2.0.2",
|
||||
"path-to-regexp": "8.2.0",
|
||||
"path-to-regexp": "8.3.0",
|
||||
"tslib": "2.8.1"
|
||||
},
|
||||
"funding": {
|
||||
|
|
@ -3547,19 +3552,10 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@nestjs/swagger/node_modules/path-to-regexp": {
|
||||
"version": "8.3.0",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz",
|
||||
"integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/@nestjs/testing": {
|
||||
"version": "11.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-11.1.6.tgz",
|
||||
"integrity": "sha512-srYzzDNxGvVCe1j0SpTS9/ix75PKt6Sn6iMaH1rpJ6nj2g8vwNrhK0CoJJXvpCYgrnI+2WES2pprYnq8rAMYHA==",
|
||||
"version": "11.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-11.1.7.tgz",
|
||||
"integrity": "sha512-QbtrgSlc3QVo6RHNxTTlyhaiobLLy8kvhOlgWHsoXRknybuRs7vZg4k5mo3ye6pITGeT3CrWIRpZjUsh5Wps5Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"tslib": "2.8.1"
|
||||
|
|
@ -3667,9 +3663,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@prisma/client": {
|
||||
"version": "6.17.1",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.17.1.tgz",
|
||||
"integrity": "sha512-zL58jbLzYamjnNnmNA51IOZdbk5ci03KviXCuB0Tydc9btH2kDWsi1pQm2VecviRTM7jGia0OPPkgpGnT3nKvw==",
|
||||
"version": "6.18.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.18.0.tgz",
|
||||
"integrity": "sha512-jnL2I9gDnPnw4A+4h5SuNn8Gc+1mL1Z79U/3I9eE2gbxJG1oSA+62ByPW4xkeDgwE0fqMzzpAZ7IHxYnLZ4iQA==",
|
||||
"hasInstallScript": true,
|
||||
"engines": {
|
||||
"node": ">=18.18"
|
||||
|
|
@ -3688,60 +3684,60 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@prisma/config": {
|
||||
"version": "6.17.1",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.17.1.tgz",
|
||||
"integrity": "sha512-fs8wY6DsvOCzuiyWVckrVs1LOcbY4LZNz8ki4uUIQ28jCCzojTGqdLhN2Jl5lDnC1yI8/gNIKpsWDM8pLhOdwA==",
|
||||
"version": "6.18.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.18.0.tgz",
|
||||
"integrity": "sha512-rgFzspCpwsE+q3OF/xkp0fI2SJ3PfNe9LLMmuSVbAZ4nN66WfBiKqJKo/hLz3ysxiPQZf8h1SMf2ilqPMeWATQ==",
|
||||
"devOptional": true,
|
||||
"dependencies": {
|
||||
"c12": "3.1.0",
|
||||
"deepmerge-ts": "7.1.5",
|
||||
"effect": "3.16.12",
|
||||
"effect": "3.18.4",
|
||||
"empathic": "2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@prisma/debug": {
|
||||
"version": "6.17.1",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.17.1.tgz",
|
||||
"integrity": "sha512-Vf7Tt5Wh9XcndpbmeotuqOMLWPTjEKCsgojxXP2oxE1/xYe7PtnP76hsouG9vis6fctX+TxgmwxTuYi/+xc7dQ==",
|
||||
"version": "6.18.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.18.0.tgz",
|
||||
"integrity": "sha512-PMVPMmxPj0ps1VY75DIrT430MoOyQx9hmm174k6cmLZpcI95rAPXOQ+pp8ANQkJtNyLVDxnxVJ0QLbrm/ViBcg==",
|
||||
"devOptional": true
|
||||
},
|
||||
"node_modules/@prisma/engines": {
|
||||
"version": "6.17.1",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.17.1.tgz",
|
||||
"integrity": "sha512-D95Ik3GYZkqZ8lSR4EyFOJ/tR33FcYRP8kK61o+WMsyD10UfJwd7+YielflHfKwiGodcqKqoraWw8ElAgMDbPw==",
|
||||
"version": "6.18.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.18.0.tgz",
|
||||
"integrity": "sha512-i5RzjGF/ex6AFgqEe2o1IW8iIxJGYVQJVRau13kHPYEL1Ck8Zvwuzamqed/1iIljs5C7L+Opiz5TzSsUebkriA==",
|
||||
"devOptional": true,
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@prisma/debug": "6.17.1",
|
||||
"@prisma/engines-version": "6.17.1-1.272a37d34178c2894197e17273bf937f25acdeac",
|
||||
"@prisma/fetch-engine": "6.17.1",
|
||||
"@prisma/get-platform": "6.17.1"
|
||||
"@prisma/debug": "6.18.0",
|
||||
"@prisma/engines-version": "6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f",
|
||||
"@prisma/fetch-engine": "6.18.0",
|
||||
"@prisma/get-platform": "6.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@prisma/engines-version": {
|
||||
"version": "6.17.1-1.272a37d34178c2894197e17273bf937f25acdeac",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.17.1-1.272a37d34178c2894197e17273bf937f25acdeac.tgz",
|
||||
"integrity": "sha512-17140E3huOuD9lMdJ9+SF/juOf3WR3sTJMVyyenzqUPbuH+89nPhSWcrY+Mf7tmSs6HvaO+7S+HkELinn6bhdg==",
|
||||
"version": "6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f.tgz",
|
||||
"integrity": "sha512-T7Af4QsJQnSgWN1zBbX+Cha5t4qjHRxoeoWpK4JugJzG/ipmmDMY5S+O0N1ET6sCBNVkf6lz+Y+ZNO9+wFU8pQ==",
|
||||
"devOptional": true
|
||||
},
|
||||
"node_modules/@prisma/fetch-engine": {
|
||||
"version": "6.17.1",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.17.1.tgz",
|
||||
"integrity": "sha512-AYZiHOs184qkDMiTeshyJCtyL4yERkjfTkJiSJdYuSfc24m94lTNL5+GFinZ6vVz+ktX4NJzHKn1zIFzGTWrWg==",
|
||||
"version": "6.18.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.18.0.tgz",
|
||||
"integrity": "sha512-TdaBvTtBwP3IoqVYoGIYpD4mWlk0pJpjTJjir/xLeNWlwog7Sl3bD2J0jJ8+5+q/6RBg+acb9drsv5W6lqae7A==",
|
||||
"devOptional": true,
|
||||
"dependencies": {
|
||||
"@prisma/debug": "6.17.1",
|
||||
"@prisma/engines-version": "6.17.1-1.272a37d34178c2894197e17273bf937f25acdeac",
|
||||
"@prisma/get-platform": "6.17.1"
|
||||
"@prisma/debug": "6.18.0",
|
||||
"@prisma/engines-version": "6.18.0-8.34b5a692b7bd79939a9a2c3ef97d816e749cda2f",
|
||||
"@prisma/get-platform": "6.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@prisma/get-platform": {
|
||||
"version": "6.17.1",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.17.1.tgz",
|
||||
"integrity": "sha512-AKEn6fsfz0r482S5KRDFlIGEaq9wLNcgalD1adL+fPcFFblIKs1sD81kY/utrHdqKuVC6E1XSRpegDK3ZLL4Qg==",
|
||||
"version": "6.18.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.18.0.tgz",
|
||||
"integrity": "sha512-uXNJCJGhxTCXo2B25Ta91Rk1/Nmlqg9p7G9GKh8TPhxvAyXCvMNQoogj4JLEUy+3ku8g59cpyQIKFhqY2xO2bg==",
|
||||
"devOptional": true,
|
||||
"dependencies": {
|
||||
"@prisma/debug": "6.17.1"
|
||||
"@prisma/debug": "6.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@scarf/scarf": {
|
||||
|
|
@ -3803,6 +3799,7 @@
|
|||
"resolved": "https://registry.npmjs.org/@swc/cli/-/cli-0.6.0.tgz",
|
||||
"integrity": "sha512-Q5FsI3Cw0fGMXhmsg7c08i4EmXCrcl+WnAxb6LYOLHw4JFFC3yzmx9LaXZ7QMbA+JZXbigU2TirI7RAfO0Qlnw==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@swc/counter": "^0.1.3",
|
||||
"@xhmikosr/bin-wrapper": "^13.0.5",
|
||||
|
|
@ -3871,6 +3868,7 @@
|
|||
"integrity": "sha512-CJSn2vstd17ddWIHBsjuD4OQnn9krQfaq6EO+w9YfId5DKznyPmzxAARlOXG99cC8/3Kli8ysKy6phL43bSr0w==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@swc/counter": "^0.1.3",
|
||||
"@swc/types": "^0.1.23"
|
||||
|
|
@ -4207,6 +4205,7 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz",
|
||||
"integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/estree": "*",
|
||||
"@types/json-schema": "*"
|
||||
|
|
@ -4366,6 +4365,7 @@
|
|||
"version": "22.17.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.17.2.tgz",
|
||||
"integrity": "sha512-gL6z5N9Jm9mhY+U2KXZpteb+09zyffliRkZyZOHODGATyC5B1Jt/7TzuuiLkFsSUMLbS1OLmlj/E+/3KF4Q/4w==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~6.21.0"
|
||||
}
|
||||
|
|
@ -4546,6 +4546,7 @@
|
|||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.37.0.tgz",
|
||||
"integrity": "sha512-kVIaQE9vrN9RLCQMQ3iyRlVJpTiDUY6woHGb30JDkfJErqrQEmtdWH3gV0PBAfGZgQXoqzXOO0T3K6ioApbbAA==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.37.0",
|
||||
"@typescript-eslint/types": "8.37.0",
|
||||
|
|
@ -5449,6 +5450,7 @@
|
|||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
|
|
@ -5461,7 +5463,6 @@
|
|||
"resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz",
|
||||
"integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
},
|
||||
|
|
@ -5495,6 +5496,7 @@
|
|||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
|
|
@ -5962,6 +5964,7 @@
|
|||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"caniuse-lite": "^1.0.30001726",
|
||||
"electron-to-chromium": "^1.5.173",
|
||||
|
|
@ -6240,6 +6243,7 @@
|
|||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
||||
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
|
||||
"devOptional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"readdirp": "^4.0.1"
|
||||
},
|
||||
|
|
@ -6292,12 +6296,14 @@
|
|||
"node_modules/class-transformer": {
|
||||
"version": "0.5.1",
|
||||
"resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz",
|
||||
"integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw=="
|
||||
"integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/class-validator": {
|
||||
"version": "0.14.2",
|
||||
"resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.2.tgz",
|
||||
"integrity": "sha512-3kMVRF2io8N8pY1IFIXlho9r8IPUUIfHe2hYVtiebvAzU2XeQFXTv+XI4WX+TnXmtwXMDcjngcpkiPM0O9PvLw==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/validator": "^13.11.8",
|
||||
"libphonenumber-js": "^1.11.1",
|
||||
|
|
@ -6953,9 +6959,9 @@
|
|||
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
|
||||
},
|
||||
"node_modules/effect": {
|
||||
"version": "3.16.12",
|
||||
"resolved": "https://registry.npmjs.org/effect/-/effect-3.16.12.tgz",
|
||||
"integrity": "sha512-N39iBk0K71F9nb442TLbTkjl24FLUzuvx2i1I2RsEAQsdAdUTuUoW0vlfUXgkMTUOnYqKnWcFfqw4hK4Pw27hg==",
|
||||
"version": "3.18.4",
|
||||
"resolved": "https://registry.npmjs.org/effect/-/effect-3.18.4.tgz",
|
||||
"integrity": "sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA==",
|
||||
"devOptional": true,
|
||||
"dependencies": {
|
||||
"@standard-schema/spec": "^1.0.0",
|
||||
|
|
@ -7160,6 +7166,7 @@
|
|||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.31.0.tgz",
|
||||
"integrity": "sha512-QldCVh/ztyKJJZLr4jXNUByx3gR+TDYZCRXEktiZoUR3PGy4qCmSbkxcIle8GEwGpb5JBZazlaJ/CxLidXdEbQ==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.2.0",
|
||||
"@eslint-community/regexpp": "^4.12.1",
|
||||
|
|
@ -7220,6 +7227,7 @@
|
|||
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.5.tgz",
|
||||
"integrity": "sha512-zc1UmCpNltmVY34vuLRV61r1K27sWuX39E+uyUnY8xS2Bex88VV9cugG+UZbRSRGtGyFboj+D8JODyme1plMpw==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"eslint-config-prettier": "bin/cli.js"
|
||||
},
|
||||
|
|
@ -8726,6 +8734,7 @@
|
|||
"resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz",
|
||||
"integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@jest/core": "^29.7.0",
|
||||
"@jest/types": "^29.6.3",
|
||||
|
|
@ -9542,9 +9551,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"node_modules/load-esm": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/load-esm/-/load-esm-1.0.2.tgz",
|
||||
"integrity": "sha512-nVAvWk/jeyrWyXEAs84mpQCYccxRqgKY4OznLuJhJCa0XsPSfdOIr2zvBZEj3IHEHbX97jjscKRRV539bW0Gpw==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/load-esm/-/load-esm-1.0.3.tgz",
|
||||
"integrity": "sha512-v5xlu8eHD1+6r8EHTg6hfmO97LN8ugKtiXcy5e6oN72iD2r6u0RPfLl6fxM+7Wnh2ZRq15o0russMst44WauPA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
|
|
@ -10362,6 +10371,7 @@
|
|||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/passport/-/passport-0.7.0.tgz",
|
||||
"integrity": "sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"passport-strategy": "1.x.x",
|
||||
"pause": "0.0.1",
|
||||
|
|
@ -10468,11 +10478,12 @@
|
|||
}
|
||||
},
|
||||
"node_modules/path-to-regexp": {
|
||||
"version": "8.2.0",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz",
|
||||
"integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==",
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
"version": "8.3.0",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz",
|
||||
"integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/path-type": {
|
||||
|
|
@ -10654,6 +10665,7 @@
|
|||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
|
||||
"integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"prettier": "bin/prettier.cjs"
|
||||
},
|
||||
|
|
@ -10703,14 +10715,15 @@
|
|||
}
|
||||
},
|
||||
"node_modules/prisma": {
|
||||
"version": "6.17.1",
|
||||
"resolved": "https://registry.npmjs.org/prisma/-/prisma-6.17.1.tgz",
|
||||
"integrity": "sha512-ac6h0sM1Tg3zu8NInY+qhP/S9KhENVaw9n1BrGKQVFu05JT5yT5Qqqmb8tMRIE3ZXvVj4xcRA5yfrsy4X7Yy5g==",
|
||||
"version": "6.18.0",
|
||||
"resolved": "https://registry.npmjs.org/prisma/-/prisma-6.18.0.tgz",
|
||||
"integrity": "sha512-bXWy3vTk8mnRmT+SLyZBQoC2vtV9Z8u7OHvEu+aULYxwiop/CPiFZ+F56KsNRNf35jw+8wcu8pmLsjxpBxAO9g==",
|
||||
"devOptional": true,
|
||||
"hasInstallScript": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@prisma/config": "6.17.1",
|
||||
"@prisma/engines": "6.17.1"
|
||||
"@prisma/config": "6.18.0",
|
||||
"@prisma/engines": "6.18.0"
|
||||
},
|
||||
"bin": {
|
||||
"prisma": "build/index.js"
|
||||
|
|
@ -10927,7 +10940,8 @@
|
|||
"node_modules/reflect-metadata": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz",
|
||||
"integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q=="
|
||||
"integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/repeat-string": {
|
||||
"version": "1.6.1",
|
||||
|
|
@ -11116,6 +11130,7 @@
|
|||
"version": "7.8.2",
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
|
||||
"integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
|
|
@ -11925,6 +11940,7 @@
|
|||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
|
||||
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"fast-uri": "^3.0.1",
|
||||
|
|
@ -12233,6 +12249,7 @@
|
|||
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
|
||||
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@cspotcode/source-map-support": "^0.8.0",
|
||||
"@tsconfig/node10": "^1.0.7",
|
||||
|
|
@ -12390,6 +12407,7 @@
|
|||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
|
||||
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
||||
"devOptional": true,
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
|
|
@ -12572,9 +12590,10 @@
|
|||
}
|
||||
},
|
||||
"node_modules/validator": {
|
||||
"version": "13.15.15",
|
||||
"resolved": "https://registry.npmjs.org/validator/-/validator-13.15.15.tgz",
|
||||
"integrity": "sha512-BgWVbCI72aIQy937xbawcs+hrVaN/CZ2UwutgaJ36hGqRrLNM+f5LUT/YPRbo8IV/ASeFzXszezV+y2+rq3l8A==",
|
||||
"version": "13.15.20",
|
||||
"resolved": "https://registry.npmjs.org/validator/-/validator-13.15.20.tgz",
|
||||
"integrity": "sha512-KxPOq3V2LmfQPP4eqf3Mq/zrT0Dqp2Vmx2Bn285LwVahLc+CsxOM0crBHczm8ijlcjZ0Q5Xd6LW3z3odTPnlrw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
|
|
@ -12719,7 +12738,6 @@
|
|||
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",
|
||||
"integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"ajv": "^8.0.0"
|
||||
},
|
||||
|
|
@ -12737,7 +12755,6 @@
|
|||
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz",
|
||||
"integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.3"
|
||||
},
|
||||
|
|
@ -12750,7 +12767,6 @@
|
|||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
|
||||
"integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esrecurse": "^4.3.0",
|
||||
"estraverse": "^4.1.1"
|
||||
|
|
@ -12764,7 +12780,6 @@
|
|||
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
|
||||
"integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
}
|
||||
|
|
@ -12773,15 +12788,13 @@
|
|||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
|
||||
"dev": true,
|
||||
"peer": true
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/webpack/node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
|
|
@ -12791,7 +12804,6 @@
|
|||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
|
|
@ -12804,7 +12816,6 @@
|
|||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz",
|
||||
"integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==",
|
||||
"dev": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/json-schema": "^7.0.9",
|
||||
"ajv": "^8.9.0",
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@
|
|||
"@nestjs/platform-express": "^11.1.6",
|
||||
"@nestjs/schedule": "^6.0.0",
|
||||
"@nestjs/swagger": "^11.2.0",
|
||||
"@prisma/client": "^6.17.1",
|
||||
"@prisma/client": "^6.18.0",
|
||||
"bullmq": "^5.58.0",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.2",
|
||||
|
|
@ -86,7 +86,7 @@
|
|||
"globals": "^16.0.0",
|
||||
"jest": "^29.7.0",
|
||||
"prettier": "^3.4.2",
|
||||
"prisma": "^6.17.1",
|
||||
"prisma": "^6.18.0",
|
||||
"source-map-support": "^0.5.21",
|
||||
"supertest": "^7.0.0",
|
||||
"ts-jest": "^29.2.5",
|
||||
|
|
|
|||
|
|
@ -1,28 +1,28 @@
|
|||
import { PrismaClient, Roles } from '@prisma/client';
|
||||
// import { PrismaClient, Roles } from '@prisma/client';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
// const prisma = new PrismaClient();
|
||||
|
||||
async function main() {
|
||||
const customerUsers = await prisma.users.findMany({
|
||||
where: { role: Roles.CUSTOMER },
|
||||
orderBy: { email: 'asc' },
|
||||
});
|
||||
// async function main() {
|
||||
// const customerUsers = await prisma.users.findMany({
|
||||
// where: { role: Roles.CUSTOMER },
|
||||
// orderBy: { email: 'asc' },
|
||||
// });
|
||||
|
||||
let i = 0;
|
||||
for (const u of customerUsers) {
|
||||
await prisma.customers.upsert({
|
||||
where: { user_id: u.id },
|
||||
update: {},
|
||||
create: {
|
||||
user_id: u.id,
|
||||
invoice_id: i % 2 === 0 ? 100000 + i : null, // 1 sur 2 a un invoice_id
|
||||
},
|
||||
});
|
||||
i++;
|
||||
}
|
||||
// let i = 0;
|
||||
// for (const u of customerUsers) {
|
||||
// await prisma.customers.upsert({
|
||||
// where: { user_id: u.id },
|
||||
// update: {},
|
||||
// create: {
|
||||
// user_id: u.id,
|
||||
// invoice_id: i % 2 === 0 ? 100000 + i : null, // 1 sur 2 a un invoice_id
|
||||
// },
|
||||
// });
|
||||
// i++;
|
||||
// }
|
||||
|
||||
const total = await prisma.customers.count();
|
||||
console.log(`✓ Customers: ${total} rows`);
|
||||
}
|
||||
// const total = await prisma.customers.count();
|
||||
// console.log(`✓ Customers: ${total} rows`);
|
||||
// }
|
||||
|
||||
main().finally(() => prisma.$disconnect());
|
||||
// main().finally(() => prisma.$disconnect());
|
||||
|
|
|
|||
|
|
@ -1,45 +1,45 @@
|
|||
import { PrismaClient } from '@prisma/client';
|
||||
// import { PrismaClient } from '@prisma/client';
|
||||
|
||||
if (process.env.SKIP_LEAVE_REQUESTS === 'true') {
|
||||
console.log("⏭ Seed leave-requests ignoré (SKIP_LEAVE_REQUESTS=true)");
|
||||
process.exit(0);
|
||||
}
|
||||
// if (process.env.SKIP_LEAVE_REQUESTS === 'true') {
|
||||
// console.log("⏭ Seed leave-requests ignoré (SKIP_LEAVE_REQUESTS=true)");
|
||||
// process.exit(0);
|
||||
// }
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
// const prisma = new PrismaClient();
|
||||
|
||||
function daysAgo(n: number) {
|
||||
const d = new Date();
|
||||
d.setDate(d.getDate() - n);
|
||||
d.setHours(0,0,0,0);
|
||||
return d;
|
||||
}
|
||||
// function daysAgo(n: number) {
|
||||
// const d = new Date();
|
||||
// d.setDate(d.getDate() - n);
|
||||
// d.setHours(0,0,0,0);
|
||||
// return d;
|
||||
// }
|
||||
|
||||
async function main() {
|
||||
const employees = await prisma.employees.findMany({
|
||||
include: { user: true },
|
||||
take: 10, // archive 10
|
||||
});
|
||||
// async function main() {
|
||||
// const employees = await prisma.employees.findMany({
|
||||
// include: { user: true },
|
||||
// take: 10, // archive 10
|
||||
// });
|
||||
|
||||
for (const e of employees) {
|
||||
await prisma.employeesArchive.create({
|
||||
data: {
|
||||
employee_id: e.id,
|
||||
user_id: e.user_id,
|
||||
first_name: e.user.first_name,
|
||||
last_name: e.user.last_name,
|
||||
external_payroll_id: e.external_payroll_id,
|
||||
company_code: e.company_code,
|
||||
first_work_day: e.first_work_day,
|
||||
last_work_day: daysAgo(30),
|
||||
supervisor_id: e.supervisor_id ?? null,
|
||||
job_title: e.job_title,
|
||||
is_supervisor: e.is_supervisor,
|
||||
},
|
||||
});
|
||||
}
|
||||
// for (const e of employees) {
|
||||
// await prisma.employeesArchive.create({
|
||||
// data: {
|
||||
// employee_id: e.id,
|
||||
// user_id: e.user_id,
|
||||
// first_name: e.user.first_name,
|
||||
// last_name: e.user.last_name,
|
||||
// external_payroll_id: e.external_payroll_id,
|
||||
// company_code: e.company_code,
|
||||
// first_work_day: e.first_work_day,
|
||||
// last_work_day: daysAgo(30),
|
||||
// supervisor_id: e.supervisor_id ?? null,
|
||||
// job_title: e.job_title,
|
||||
// is_supervisor: e.is_supervisor,
|
||||
// },
|
||||
// });
|
||||
// }
|
||||
|
||||
const total = await prisma.employeesArchive.count();
|
||||
console.log(`✓ EmployeesArchive: ${total} rows (added 10)`);
|
||||
}
|
||||
// const total = await prisma.employeesArchive.count();
|
||||
// console.log(`✓ EmployeesArchive: ${total} rows (added 10)`);
|
||||
// }
|
||||
|
||||
main().finally(() => prisma.$disconnect());
|
||||
// main().finally(() => prisma.$disconnect());
|
||||
|
|
|
|||
|
|
@ -1,35 +1,35 @@
|
|||
// prisma/mock-seeds-scripts/06-customers-archive.ts
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
// // prisma/mock-seeds-scripts/06-customers-archive.ts
|
||||
// import { PrismaClient } from '@prisma/client';
|
||||
|
||||
if (process.env.SKIP_LEAVE_REQUESTS === 'true') {
|
||||
console.log("⏭ Seed leave-requests ignoré (SKIP_LEAVE_REQUESTS=true)");
|
||||
process.exit(0);
|
||||
}
|
||||
// if (process.env.SKIP_LEAVE_REQUESTS === 'true') {
|
||||
// console.log("⏭ Seed leave-requests ignoré (SKIP_LEAVE_REQUESTS=true)");
|
||||
// process.exit(0);
|
||||
// }
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
// const prisma = new PrismaClient();
|
||||
|
||||
async function main() {
|
||||
const customers = await prisma.customers.findMany({
|
||||
orderBy: { id: 'asc' },
|
||||
take: 5,
|
||||
});
|
||||
// async function main() {
|
||||
// const customers = await prisma.customers.findMany({
|
||||
// orderBy: { id: 'asc' },
|
||||
// take: 5,
|
||||
// });
|
||||
|
||||
for (const c of customers) {
|
||||
const invoiceId = 200000 + c.id; // déterministe, stable entre runs
|
||||
// for (const c of customers) {
|
||||
// const invoiceId = 200000 + c.id; // déterministe, stable entre runs
|
||||
|
||||
await prisma.customersArchive.upsert({
|
||||
where: { invoice_id: invoiceId }, // invoice_id est unique
|
||||
update: {}, // idempotent
|
||||
create: {
|
||||
customer_id: c.id,
|
||||
user_id: c.user_id,
|
||||
invoice_id: invoiceId,
|
||||
},
|
||||
});
|
||||
}
|
||||
// await prisma.customersArchive.upsert({
|
||||
// where: { invoice_id: invoiceId }, // invoice_id est unique
|
||||
// update: {}, // idempotent
|
||||
// create: {
|
||||
// customer_id: c.id,
|
||||
// user_id: c.user_id,
|
||||
// invoice_id: invoiceId,
|
||||
// },
|
||||
// });
|
||||
// }
|
||||
|
||||
const total = await prisma.customersArchive.count();
|
||||
console.log(`✓ CustomersArchive upserted for ${customers.length} customers (total=${total})`);
|
||||
}
|
||||
// const total = await prisma.customersArchive.count();
|
||||
// console.log(`✓ CustomersArchive upserted for ${customers.length} customers (total=${total})`);
|
||||
// }
|
||||
|
||||
main().finally(() => prisma.$disconnect());
|
||||
// main().finally(() => prisma.$disconnect());
|
||||
|
|
|
|||
|
|
@ -1,87 +1,87 @@
|
|||
import { PrismaClient, LeaveApprovalStatus, LeaveTypes } from '@prisma/client';
|
||||
// import { PrismaClient, LeaveApprovalStatus, LeaveTypes } from '@prisma/client';
|
||||
|
||||
if (process.env.SKIP_LEAVE_REQUESTS === 'true') {
|
||||
console.log('?? Seed leave-requests ignoré (SKIP_LEAVE_REQUESTS=true)');
|
||||
process.exit(0);
|
||||
}
|
||||
// if (process.env.SKIP_LEAVE_REQUESTS === 'true') {
|
||||
// console.log('?? Seed leave-requests ignor<6F> (SKIP_LEAVE_REQUESTS=true)');
|
||||
// process.exit(0);
|
||||
// }
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
// const prisma = new PrismaClient();
|
||||
|
||||
function daysAgo(n: number) {
|
||||
const d = new Date();
|
||||
d.setUTCDate(d.getUTCDate() - n);
|
||||
d.setUTCHours(0, 0, 0, 0);
|
||||
return d;
|
||||
}
|
||||
// function daysAgo(n: number) {
|
||||
// const d = new Date();
|
||||
// d.setUTCDate(d.getUTCDate() - n);
|
||||
// d.setUTCHours(0, 0, 0, 0);
|
||||
// return d;
|
||||
// }
|
||||
|
||||
async function main() {
|
||||
const employees = await prisma.employees.findMany({ select: { id: true } });
|
||||
if (!employees.length) {
|
||||
throw new Error('Aucun employé trouvé. Exécute le seed employees avant celui-ci.');
|
||||
}
|
||||
// async function main() {
|
||||
// const employees = await prisma.employees.findMany({ select: { id: true } });
|
||||
// if (!employees.length) {
|
||||
// throw new Error('Aucun employ<6F> trouv<75>. Ex<45>cute le seed employees avant celui-ci.');
|
||||
// }
|
||||
|
||||
const leaveCodes = await prisma.bankCodes.findMany({
|
||||
where: { type: { in: ['SICK', 'VACATION', 'HOLIDAY'] } },
|
||||
select: { id: true, type: true },
|
||||
});
|
||||
if (!leaveCodes.length) {
|
||||
throw new Error("Aucun bank code trouvé avec type in ('SICK','VACATION','HOLIDAY'). Vérifie ta table bank_codes.");
|
||||
}
|
||||
// const leaveCodes = await prisma.bankCodes.findMany({
|
||||
// where: { type: { in: ['SICK', 'VACATION', 'HOLIDAY'] } },
|
||||
// select: { id: true, type: true },
|
||||
// });
|
||||
// if (!leaveCodes.length) {
|
||||
// throw new Error("Aucun bank code trouv<75> avec type in ('SICK','VACATION','HOLIDAY'). V<>rifie ta table bank_codes.");
|
||||
// }
|
||||
|
||||
const statuses = Object.values(LeaveApprovalStatus);
|
||||
const created = [] as Array<{ id: number; employee_id: number; leave_type: LeaveTypes; date: Date; comment: string; approval_status: LeaveApprovalStatus; requested_hours: number; payable_hours: number | null }>;
|
||||
// const statuses = Object.values(LeaveApprovalStatus);
|
||||
// const created = [] as Array<{ id: number; employee_id: number; leave_type: LeaveTypes; date: Date; comment: string; approval_status: LeaveApprovalStatus; requested_hours: number; payable_hours: number | null }>;
|
||||
|
||||
const COUNT = 12;
|
||||
for (let i = 0; i < COUNT; i++) {
|
||||
const emp = employees[i % employees.length];
|
||||
const leaveCode = leaveCodes[Math.floor(Math.random() * leaveCodes.length)];
|
||||
// const COUNT = 12;
|
||||
// for (let i = 0; i < COUNT; i++) {
|
||||
// const emp = employees[i % employees.length];
|
||||
// const leaveCode = leaveCodes[Math.floor(Math.random() * leaveCodes.length)];
|
||||
|
||||
const date = daysAgo(120 - i * 3);
|
||||
const status = statuses[(i + 2) % statuses.length];
|
||||
const requestedHours = 4 + (i % 5); // 4 ? 8 h
|
||||
const payableHours = status === LeaveApprovalStatus.APPROVED ? Math.min(requestedHours, 8) : null;
|
||||
// const date = daysAgo(120 - i * 3);
|
||||
// const status = statuses[(i + 2) % statuses.length];
|
||||
// const requestedHours = 4 + (i % 5); // 4 ? 8 h
|
||||
// const payableHours = status === LeaveApprovalStatus.APPROVED ? Math.min(requestedHours, 8) : null;
|
||||
|
||||
const lr = await prisma.leaveRequests.create({
|
||||
data: {
|
||||
employee_id: emp.id,
|
||||
bank_code_id: leaveCode.id,
|
||||
leave_type: leaveCode.type as LeaveTypes,
|
||||
date,
|
||||
comment: `Past leave #${i + 1} (${leaveCode.type})`,
|
||||
approval_status: status,
|
||||
requested_hours: requestedHours,
|
||||
payable_hours: payableHours,
|
||||
},
|
||||
});
|
||||
// const lr = await prisma.leaveRequests.create({
|
||||
// data: {
|
||||
// employee_id: emp.id,
|
||||
// bank_code_id: leaveCode.id,
|
||||
// leave_type: leaveCode.type as LeaveTypes,
|
||||
// date,
|
||||
// comment: `Past leave #${i + 1} (${leaveCode.type})`,
|
||||
// approval_status: status,
|
||||
// requested_hours: requestedHours,
|
||||
// payable_hours: payableHours,
|
||||
// },
|
||||
// });
|
||||
|
||||
created.push({
|
||||
id: lr.id,
|
||||
employee_id: lr.employee_id,
|
||||
leave_type: lr.leave_type,
|
||||
date: lr.date,
|
||||
comment: lr.comment,
|
||||
approval_status: lr.approval_status,
|
||||
requested_hours: requestedHours,
|
||||
payable_hours: payableHours,
|
||||
});
|
||||
}
|
||||
// created.push({
|
||||
// id: lr.id,
|
||||
// employee_id: lr.employee_id,
|
||||
// leave_type: lr.leave_type,
|
||||
// date: lr.date,
|
||||
// comment: lr.comment,
|
||||
// approval_status: lr.approval_status,
|
||||
// requested_hours: requestedHours,
|
||||
// payable_hours: payableHours,
|
||||
// });
|
||||
// }
|
||||
|
||||
for (const lr of created) {
|
||||
await prisma.leaveRequestsArchive.create({
|
||||
data: {
|
||||
leave_request_id: lr.id,
|
||||
employee_id: lr.employee_id,
|
||||
leave_type: lr.leave_type,
|
||||
date: lr.date,
|
||||
comment: lr.comment,
|
||||
approval_status: lr.approval_status,
|
||||
requested_hours: lr.requested_hours,
|
||||
payable_hours: lr.payable_hours,
|
||||
},
|
||||
});
|
||||
}
|
||||
// for (const lr of created) {
|
||||
// await prisma.leaveRequestsArchive.create({
|
||||
// data: {
|
||||
// leave_request_id: lr.id,
|
||||
// employee_id: lr.employee_id,
|
||||
// leave_type: lr.leave_type,
|
||||
// date: lr.date,
|
||||
// comment: lr.comment,
|
||||
// approval_status: lr.approval_status,
|
||||
// requested_hours: lr.requested_hours,
|
||||
// payable_hours: lr.payable_hours,
|
||||
// },
|
||||
// });
|
||||
// }
|
||||
|
||||
console.log(`? LeaveRequestsArchive: ${created.length} rows`);
|
||||
}
|
||||
// console.log(`? LeaveRequestsArchive: ${created.length} rows`);
|
||||
// }
|
||||
|
||||
main().finally(() => prisma.$disconnect());
|
||||
// main().finally(() => prisma.$disconnect());
|
||||
|
|
@ -1,71 +1,71 @@
|
|||
import { PrismaClient } from '@prisma/client';
|
||||
// import { PrismaClient } from '@prisma/client';
|
||||
|
||||
if (process.env.SKIP_LEAVE_REQUESTS === 'true') {
|
||||
console.log("⏭ Seed leave-requests ignoré (SKIP_LEAVE_REQUESTS=true)");
|
||||
process.exit(0);
|
||||
}
|
||||
// if (process.env.SKIP_LEAVE_REQUESTS === 'true') {
|
||||
// console.log("⏭ Seed leave-requests ignoré (SKIP_LEAVE_REQUESTS=true)");
|
||||
// process.exit(0);
|
||||
// }
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
// const prisma = new PrismaClient();
|
||||
|
||||
function timeAt(h:number,m:number) {
|
||||
return new Date(Date.UTC(1970,0,1,h,m,0));
|
||||
}
|
||||
function daysAgo(n:number) {
|
||||
const d = new Date();
|
||||
d.setUTCDate(d.getUTCDate() - n);
|
||||
d.setUTCHours(0,0,0,0);
|
||||
return d;
|
||||
}
|
||||
// function timeAt(h:number,m:number) {
|
||||
// return new Date(Date.UTC(1970,0,1,h,m,0));
|
||||
// }
|
||||
// function daysAgo(n:number) {
|
||||
// const d = new Date();
|
||||
// d.setUTCDate(d.getUTCDate() - n);
|
||||
// d.setUTCHours(0,0,0,0);
|
||||
// return d;
|
||||
// }
|
||||
|
||||
async function main() {
|
||||
const bankCodes = await prisma.bankCodes.findMany({ where: { categorie: 'SHIFT' }, select: { id: true } });
|
||||
const employees = await prisma.employees.findMany({ select: { id: true } });
|
||||
// async function main() {
|
||||
// const bankCodes = await prisma.bankCodes.findMany({ where: { categorie: 'SHIFT' }, select: { id: true } });
|
||||
// const employees = await prisma.employees.findMany({ select: { id: true } });
|
||||
|
||||
for (const e of employees) {
|
||||
const tss = await prisma.timesheets.findMany({ where: { employee_id: e.id }, select: { id: true } });
|
||||
if (!tss.length) continue;
|
||||
// for (const e of employees) {
|
||||
// const tss = await prisma.timesheets.findMany({ where: { employee_id: e.id }, select: { id: true } });
|
||||
// if (!tss.length) continue;
|
||||
|
||||
const createdShiftIds: number[] = [];
|
||||
for (let i = 0; i < 8; i++) {
|
||||
const ts = tss[i % tss.length];
|
||||
const bc = bankCodes[i % bankCodes.length];
|
||||
const date = daysAgo(200 + i); // bien dans le passé
|
||||
const startH = 7 + (i % 4); // 7..10
|
||||
const endH = startH + 8; // 15..18
|
||||
// const createdShiftIds: number[] = [];
|
||||
// for (let i = 0; i < 8; i++) {
|
||||
// const ts = tss[i % tss.length];
|
||||
// const bc = bankCodes[i % bankCodes.length];
|
||||
// const date = daysAgo(200 + i); // bien dans le passé
|
||||
// const startH = 7 + (i % 4); // 7..10
|
||||
// const endH = startH + 8; // 15..18
|
||||
|
||||
const sh = await prisma.shifts.create({
|
||||
data: {
|
||||
timesheet_id: ts.id,
|
||||
bank_code_id: bc.id,
|
||||
comment: `Archived-era shift ${i + 1} for emp ${e.id}`,
|
||||
date,
|
||||
start_time: timeAt(startH, 0),
|
||||
end_time: timeAt(endH, 0),
|
||||
is_approved: true,
|
||||
},
|
||||
});
|
||||
createdShiftIds.push(sh.id);
|
||||
}
|
||||
// const sh = await prisma.shifts.create({
|
||||
// data: {
|
||||
// timesheet_id: ts.id,
|
||||
// bank_code_id: bc.id,
|
||||
// comment: `Archived-era shift ${i + 1} for emp ${e.id}`,
|
||||
// date,
|
||||
// start_time: timeAt(startH, 0),
|
||||
// end_time: timeAt(endH, 0),
|
||||
// is_approved: true,
|
||||
// },
|
||||
// });
|
||||
// createdShiftIds.push(sh.id);
|
||||
// }
|
||||
|
||||
for (const sid of createdShiftIds) {
|
||||
const s = await prisma.shifts.findUnique({ where: { id: sid } });
|
||||
if (!s) continue;
|
||||
await prisma.shiftsArchive.create({
|
||||
data: {
|
||||
shift_id: s.id,
|
||||
timesheet_id: s.timesheet_id,
|
||||
bank_code_id: s.bank_code_id,
|
||||
comment: s.comment,
|
||||
date: s.date,
|
||||
start_time: s.start_time,
|
||||
end_time: s.end_time,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
// for (const sid of createdShiftIds) {
|
||||
// const s = await prisma.shifts.findUnique({ where: { id: sid } });
|
||||
// if (!s) continue;
|
||||
// await prisma.shiftsArchive.create({
|
||||
// data: {
|
||||
// shift_id: s.id,
|
||||
// timesheet_id: s.timesheet_id,
|
||||
// bank_code_id: s.bank_code_id,
|
||||
// comment: s.comment,
|
||||
// date: s.date,
|
||||
// start_time: s.start_time,
|
||||
// end_time: s.end_time,
|
||||
// },
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
|
||||
const total = await prisma.shiftsArchive.count();
|
||||
console.log(`✓ ShiftsArchive: ${total} rows total`);
|
||||
}
|
||||
// const total = await prisma.shiftsArchive.count();
|
||||
// console.log(`✓ ShiftsArchive: ${total} rows total`);
|
||||
// }
|
||||
|
||||
main().finally(() => prisma.$disconnect());
|
||||
// main().finally(() => prisma.$disconnect());
|
||||
|
|
|
|||
|
|
@ -1,65 +1,65 @@
|
|||
// 13-expenses-archive.ts
|
||||
import { PrismaClient, Expenses } from '@prisma/client';
|
||||
// // 13-expenses-archive.ts
|
||||
// import { PrismaClient, Expenses } from '@prisma/client';
|
||||
|
||||
if (process.env.SKIP_LEAVE_REQUESTS === 'true') {
|
||||
console.log("⏭ Seed leave-requests ignoré (SKIP_LEAVE_REQUESTS=true)");
|
||||
process.exit(0);
|
||||
}
|
||||
// if (process.env.SKIP_LEAVE_REQUESTS === 'true') {
|
||||
// console.log("⏭ Seed leave-requests ignoré (SKIP_LEAVE_REQUESTS=true)");
|
||||
// process.exit(0);
|
||||
// }
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
// const prisma = new PrismaClient();
|
||||
|
||||
function daysAgo(n:number) {
|
||||
const d = new Date();
|
||||
d.setUTCDate(d.getUTCDate() - n);
|
||||
d.setUTCHours(0,0,0,0);
|
||||
return d;
|
||||
}
|
||||
// function daysAgo(n:number) {
|
||||
// const d = new Date();
|
||||
// d.setUTCDate(d.getUTCDate() - n);
|
||||
// d.setUTCHours(0,0,0,0);
|
||||
// return d;
|
||||
// }
|
||||
|
||||
async function main() {
|
||||
const expenseCodes = await prisma.bankCodes.findMany({ where: { categorie: 'EXPENSE' }, select: { id: true } });
|
||||
const timesheets = await prisma.timesheets.findMany({ select: { id: true } });
|
||||
// async function main() {
|
||||
// const expenseCodes = await prisma.bankCodes.findMany({ where: { categorie: 'EXPENSE' }, select: { id: true } });
|
||||
// const timesheets = await prisma.timesheets.findMany({ select: { id: true } });
|
||||
|
||||
// ✅ typer pour éviter never[]
|
||||
const created: Expenses[] = [];
|
||||
// // ✅ typer pour éviter never[]
|
||||
// const created: Expenses[] = [];
|
||||
|
||||
for (let i = 0; i < 4; i++) {
|
||||
const ts = timesheets[i % timesheets.length];
|
||||
const bc = expenseCodes[i % expenseCodes.length];
|
||||
// for (let i = 0; i < 4; i++) {
|
||||
// const ts = timesheets[i % timesheets.length];
|
||||
// const bc = expenseCodes[i % expenseCodes.length];
|
||||
|
||||
const exp = await prisma.expenses.create({
|
||||
data: {
|
||||
timesheet_id: ts.id,
|
||||
bank_code_id: bc.id,
|
||||
date: daysAgo(60 + i),
|
||||
amount: (20 + i * 3.5).toFixed(2), // ok: Decimal accepte string
|
||||
attachment: null,
|
||||
comment: `Old expense #${i + 1}`,
|
||||
is_approved: true,
|
||||
supervisor_comment: null,
|
||||
},
|
||||
});
|
||||
// const exp = await prisma.expenses.create({
|
||||
// data: {
|
||||
// timesheet_id: ts.id,
|
||||
// bank_code_id: bc.id,
|
||||
// date: daysAgo(60 + i),
|
||||
// amount: (20 + i * 3.5).toFixed(2), // ok: Decimal accepte string
|
||||
// attachment: null,
|
||||
// comment: `Old expense #${i + 1}`,
|
||||
// is_approved: true,
|
||||
// supervisor_comment: null,
|
||||
// },
|
||||
// });
|
||||
|
||||
created.push(exp);
|
||||
}
|
||||
// created.push(exp);
|
||||
// }
|
||||
|
||||
for (const e of created) {
|
||||
await prisma.expensesArchive.create({
|
||||
data: {
|
||||
expense_id: e.id,
|
||||
timesheet_id: e.timesheet_id,
|
||||
bank_code_id: e.bank_code_id,
|
||||
date: e.date,
|
||||
amount: e.amount,
|
||||
attachment: e.attachment,
|
||||
comment: e.comment,
|
||||
is_approved: e.is_approved,
|
||||
supervisor_comment: e.supervisor_comment,
|
||||
},
|
||||
});
|
||||
}
|
||||
// for (const e of created) {
|
||||
// await prisma.expensesArchive.create({
|
||||
// data: {
|
||||
// expense_id: e.id,
|
||||
// timesheet_id: e.timesheet_id,
|
||||
// bank_code_id: e.bank_code_id,
|
||||
// date: e.date,
|
||||
// amount: e.amount,
|
||||
// attachment: e.attachment,
|
||||
// comment: e.comment,
|
||||
// is_approved: e.is_approved,
|
||||
// supervisor_comment: e.supervisor_comment,
|
||||
// },
|
||||
// });
|
||||
// }
|
||||
|
||||
const total = await prisma.expensesArchive.count();
|
||||
console.log(`✓ ExpensesArchive: ${total} total rows (added ${created.length})`);
|
||||
}
|
||||
// const total = await prisma.expensesArchive.count();
|
||||
// console.log(`✓ ExpensesArchive: ${total} total rows (added ${created.length})`);
|
||||
// }
|
||||
|
||||
main().finally(() => prisma.$disconnect());
|
||||
// main().finally(() => prisma.$disconnect());
|
||||
|
|
|
|||
|
|
@ -24,10 +24,7 @@ model Users {
|
|||
role Roles @default(GUEST)
|
||||
|
||||
employee Employees? @relation("UserEmployee")
|
||||
customer Customers? @relation("UserCustomer")
|
||||
oauth_sessions OAuthSessions[] @relation("UserOAuthSessions")
|
||||
employees_archive EmployeesArchive[] @relation("UsersToEmployeesToArchive")
|
||||
customer_archive CustomersArchive[] @relation("UserToCustomersToArchive")
|
||||
preferences Preferences? @relation("UserPreferences")
|
||||
|
||||
@@map("users")
|
||||
|
|
@ -49,61 +46,13 @@ model Employees {
|
|||
|
||||
|
||||
crew Employees[] @relation("EmployeeSupervisor")
|
||||
archive EmployeesArchive[] @relation("EmployeeToArchive")
|
||||
timesheet Timesheets[] @relation("TimesheetEmployee")
|
||||
leave_request LeaveRequests[] @relation("LeaveRequestEmployee")
|
||||
supervisor_archive EmployeesArchive[] @relation("EmployeeSupervisorToArchive")
|
||||
schedule_presets SchedulePresets[] @relation("SchedulePreset")
|
||||
|
||||
@@map("employees")
|
||||
}
|
||||
|
||||
model EmployeesArchive {
|
||||
id Int @id @default(autoincrement())
|
||||
employee Employees @relation("EmployeeToArchive", fields: [employee_id], references: [id])
|
||||
employee_id Int
|
||||
user_id String @db.Uuid
|
||||
user Users @relation("UsersToEmployeesToArchive", fields: [user_id], references: [id])
|
||||
supervisor Employees? @relation("EmployeeSupervisorToArchive", fields: [supervisor_id], references: [id])
|
||||
supervisor_id Int?
|
||||
|
||||
archived_at DateTime @default(now())
|
||||
first_name String
|
||||
last_name String
|
||||
job_title String?
|
||||
is_supervisor Boolean
|
||||
external_payroll_id Int
|
||||
company_code Int
|
||||
first_work_day DateTime @db.Date
|
||||
last_work_day DateTime @db.Date
|
||||
|
||||
@@map("employees_archive")
|
||||
}
|
||||
|
||||
model Customers {
|
||||
id Int @id @default(autoincrement())
|
||||
user Users @relation("UserCustomer", fields: [user_id], references: [id])
|
||||
user_id String @unique @db.Uuid
|
||||
invoice_id Int? @unique
|
||||
|
||||
archive CustomersArchive[] @relation("CustomerToArchive")
|
||||
|
||||
@@map("customers")
|
||||
}
|
||||
|
||||
model CustomersArchive {
|
||||
id Int @id @default(autoincrement())
|
||||
customer Customers @relation("CustomerToArchive", fields: [customer_id], references: [id])
|
||||
customer_id Int
|
||||
user Users @relation("UserToCustomersToArchive", fields: [user_id], references: [id])
|
||||
user_id String @db.Uuid
|
||||
|
||||
archived_at DateTime @default(now())
|
||||
invoice_id Int? @unique
|
||||
|
||||
@@map("customers_archive")
|
||||
}
|
||||
|
||||
model LeaveRequests {
|
||||
id Int @id @default(autoincrement())
|
||||
employee Employees @relation("LeaveRequestEmployee", fields: [employee_id], references: [id])
|
||||
|
|
@ -216,13 +165,6 @@ model SchedulePresetShifts {
|
|||
@@map("schedule_preset_shifts")
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
model Shifts {
|
||||
id Int @id @default(autoincrement())
|
||||
timesheet Timesheets @relation("ShiftTimesheet", fields: [timesheet_id], references: [id])
|
||||
|
|
@ -283,7 +225,7 @@ model Expenses {
|
|||
attachment Int?
|
||||
|
||||
date DateTime @db.Date
|
||||
amount Decimal @db.Money
|
||||
amount Decimal? @db.Decimal(12,2)
|
||||
mileage Decimal? @db.Decimal(12,2)
|
||||
comment String
|
||||
supervisor_comment String?
|
||||
|
|
@ -305,7 +247,7 @@ model ExpensesArchive {
|
|||
archived_at DateTime @default(now())
|
||||
bank_code_id Int
|
||||
date DateTime @db.Date
|
||||
amount Decimal? @db.Money
|
||||
amount Decimal? @db.Decimal(12,2)
|
||||
mileage Decimal? @db.Decimal(12,2)
|
||||
comment String?
|
||||
is_approved Boolean
|
||||
|
|
@ -373,7 +315,7 @@ model AttachmentVariants {
|
|||
attachment_id Int
|
||||
attachment Attachments @relation("attachmentVariantAttachment",fields: [attachment_id], references: [id], onDelete: Cascade)
|
||||
variant String
|
||||
patch String
|
||||
path String
|
||||
bytes Int
|
||||
width Int?
|
||||
height Int?
|
||||
|
|
|
|||
|
|
@ -1,12 +1,4 @@
|
|||
import { Controller, Get } from '@nestjs/common';
|
||||
import { AppService } from './app.service';
|
||||
|
||||
@Controller()
|
||||
export class AppController {
|
||||
constructor(private readonly appService: AppService) {}
|
||||
|
||||
@Get()
|
||||
getHello(): string {
|
||||
return this.appService.getHello();
|
||||
}
|
||||
}
|
||||
export class AppController { }
|
||||
|
|
|
|||
|
|
@ -1,61 +1,39 @@
|
|||
import { BadRequestException, Module, ValidationPipe } from '@nestjs/common';
|
||||
import { AppController } from './app.controller';
|
||||
import { AppService } from './app.service';
|
||||
// import { ArchivalModule } from './modules/archival/archival.module';
|
||||
import { AuthenticationModule } from './modules/authentication/auth.module';
|
||||
import { BankCodesModule } from './modules/bank-codes/bank-codes.module';
|
||||
import { BusinessLogicsModule } from './modules/business-logics/business-logics.module';
|
||||
import { AuthenticationModule } from './identity-and-account/authentication/auth.module';
|
||||
// import { CsvExportModule } from './modules/exports/csv-exports.module';
|
||||
import { CustomersModule } from './modules/customers/customers.module';
|
||||
import { EmployeesModule } from './modules/employees/employees.module';
|
||||
// import { ExpensesModule } from './modules/expenses/expenses.module';
|
||||
import { HealthModule } from './health/health.module';
|
||||
import { HealthController } from './health/health.controller';
|
||||
// import { LeaveRequestsModule } from './modules/leave-requests/leave-requests.module';
|
||||
import { NotificationsModule } from './modules/notifications/notifications.module';
|
||||
import { OauthSessionsModule } from './modules/oauth-sessions/oauth-sessions.module';
|
||||
import { OvertimeService } from './modules/business-logics/services/overtime.service';
|
||||
import { PreferencesModule } from './modules/preferences/preferences.module';
|
||||
import { PreferencesModule } from './identity-and-account/preferences/preferences.module';
|
||||
import { PrismaModule } from './prisma/prisma.module';
|
||||
import { ScheduleModule } from '@nestjs/schedule';
|
||||
import { ShiftsModule } from './modules/shifts/shifts.module';
|
||||
import { TimesheetsModule } from './modules/timesheets/timesheets.module';
|
||||
import { UsersModule } from './modules/users-management/users.module';
|
||||
import { UsersModule } from './identity-and-account/users-management/users.module';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { APP_FILTER, APP_PIPE } from '@nestjs/core';
|
||||
import { HttpExceptionFilter } from './common/filters/http-exception.filter';
|
||||
import { ValidationError } from 'class-validator';
|
||||
import { SchedulePresetsModule } from './modules/schedule-presets/schedule-presets.module';
|
||||
import { PayperiodsModule } from './modules/pay-periods/pay-periods.module';
|
||||
import { TimeAndAttendanceModule } from 'src/time-and-attendance/time-and-attendance.module';
|
||||
import { PayperiodsModule } from 'src/time-and-attendance/pay-period/pay-periods.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
// ArchivalModule,
|
||||
AuthenticationModule,
|
||||
BankCodesModule,
|
||||
BusinessLogicsModule,
|
||||
ConfigModule.forRoot({isGlobal: true}),
|
||||
// CsvExportModule,
|
||||
CustomersModule,
|
||||
EmployeesModule,
|
||||
// ExpensesModule,
|
||||
HealthModule,
|
||||
// LeaveRequestsModule,
|
||||
NotificationsModule,
|
||||
OauthSessionsModule,
|
||||
PayperiodsModule,
|
||||
PreferencesModule,
|
||||
PrismaModule,
|
||||
ScheduleModule.forRoot(), //cronjobs
|
||||
SchedulePresetsModule,
|
||||
ShiftsModule,
|
||||
TimesheetsModule,
|
||||
TimeAndAttendanceModule,
|
||||
UsersModule,
|
||||
],
|
||||
controllers: [AppController, HealthController],
|
||||
providers: [
|
||||
AppService,
|
||||
OvertimeService,
|
||||
{
|
||||
provide: APP_FILTER,
|
||||
useClass: HttpExceptionFilter
|
||||
|
|
|
|||
|
|
@ -1,8 +1,4 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class AppService {
|
||||
getHello(): string {
|
||||
return 'Hello World!';
|
||||
}
|
||||
}
|
||||
export class AppService { }
|
||||
|
|
|
|||
|
|
@ -37,9 +37,9 @@ export class RolesGuard implements CanActivate {
|
|||
* or returns `false` if the user is not authenticated.
|
||||
*/
|
||||
canActivate(ctx: ExecutionContext): boolean {
|
||||
const requiredRoles = this.reflector.get<Roles[]>(
|
||||
const requiredRoles = this.reflector.getAllAndOverride<Roles[]>(
|
||||
ROLES_KEY,
|
||||
ctx.getHandler(),
|
||||
[ctx.getHandler(), ctx.getClass()],
|
||||
);
|
||||
//for "deny-by-default" when role is wrong or unavailable
|
||||
if (!requiredRoles || requiredRoles.length === 0) {
|
||||
|
|
|
|||
|
|
@ -18,14 +18,14 @@ export abstract class BaseApprovalService<T> {
|
|||
//returns the corresponding Prisma delegate
|
||||
protected abstract get delegate(): UpdatableDelegate<T>;
|
||||
|
||||
protected abstract delegateFor(transaction: Prisma.TransactionClient): UpdatableDelegate<T>;
|
||||
protected abstract delegateFor(tx: Prisma.TransactionClient): UpdatableDelegate<T>;
|
||||
|
||||
//standard update Aproval
|
||||
async updateApproval(id: number, isApproved: boolean): Promise<T> {
|
||||
async updateApproval(id: number, is_approved: boolean): Promise<T> {
|
||||
try{
|
||||
return await this.delegate.update({
|
||||
where: { id },
|
||||
data: { is_approved: isApproved },
|
||||
data: { is_approved: is_approved },
|
||||
});
|
||||
}catch (error: any) {
|
||||
if (error instanceof PrismaClientKnownRequestError && error.code === "P2025") {
|
||||
|
|
@ -36,11 +36,11 @@ export abstract class BaseApprovalService<T> {
|
|||
}
|
||||
|
||||
//approval with transaction to avoid many requests
|
||||
async updateApprovalWithTransaction(transaction: Prisma.TransactionClient, id: number, isApproved: boolean): Promise<T> {
|
||||
async updateApprovalWithTransaction(tx: Prisma.TransactionClient, id: number, is_approved: boolean): Promise<T> {
|
||||
try {
|
||||
return await this.delegateFor(transaction).update({
|
||||
return await this.delegateFor(tx).update({
|
||||
where: { id },
|
||||
data: { is_approved: isApproved },
|
||||
data: { is_approved: is_approved },
|
||||
});
|
||||
} catch (error: any ){
|
||||
if(error instanceof PrismaClientKnownRequestError && error.code === 'P2025') {
|
||||
|
|
|
|||
15
src/common/shared/role-groupes.ts
Normal file
15
src/common/shared/role-groupes.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import { Roles as RoleEnum } from ".prisma/client";
|
||||
|
||||
export const GLOBAL_CONTROLLER_ROLES: readonly RoleEnum[] = [
|
||||
RoleEnum.EMPLOYEE,
|
||||
RoleEnum.ACCOUNTING,
|
||||
RoleEnum.HR,
|
||||
RoleEnum.SUPERVISOR,
|
||||
RoleEnum.ADMIN,
|
||||
];
|
||||
|
||||
export const MANAGER_ROLES: readonly RoleEnum[] = [
|
||||
RoleEnum.HR,
|
||||
RoleEnum.SUPERVISOR,
|
||||
RoleEnum.ADMIN,
|
||||
]
|
||||
|
|
@ -12,6 +12,7 @@ export class AuthController {
|
|||
@Get('/callback')
|
||||
@UseGuards(OIDCLoginGuard)
|
||||
loginCallback(@Req() req: Request, @Res() res: Response) {
|
||||
// res.redirect('http://10.100.251.2:9011/#/login-success');
|
||||
res.redirect('http://localhost:9000/#/login-success');
|
||||
}
|
||||
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { UsersService } from 'src/modules/users-management/services/users.service';
|
||||
import { UsersService } from 'src/identity-and-account/users-management/services/users.service';
|
||||
|
||||
@Injectable()
|
||||
export class AuthentikAuthService {
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
import { Controller, Get, Patch, Param, Body, NotFoundException, Req, Post } from "@nestjs/common";
|
||||
import { Employees } from "@prisma/client";
|
||||
import { RolesAllowed } from "src/common/decorators/roles.decorators";
|
||||
import { GLOBAL_CONTROLLER_ROLES, MANAGER_ROLES } from "src/common/shared/role-groupes";
|
||||
import { CreateEmployeeDto } from "src/identity-and-account/employees/dtos/create-employee.dto";
|
||||
import { EmployeeListItemDto } from "src/identity-and-account/employees/dtos/list-employee.dto";
|
||||
import { EmployeeProfileItemDto } from "src/identity-and-account/employees/dtos/profil-employee.dto";
|
||||
import { UpdateEmployeeDto } from "src/identity-and-account/employees/dtos/update-employee.dto";
|
||||
import { EmployeesArchivalService } from "src/identity-and-account/employees/services/employees-archival.service";
|
||||
import { EmployeesService } from "src/identity-and-account/employees/services/employees.service";
|
||||
|
||||
@RolesAllowed(...GLOBAL_CONTROLLER_ROLES)
|
||||
@Controller('employees')
|
||||
export class EmployeesController {
|
||||
constructor(
|
||||
private readonly employeesService: EmployeesService,
|
||||
private readonly archiveService: EmployeesArchivalService,
|
||||
) { }
|
||||
|
||||
@Get('profile')
|
||||
findOneProfile(@Req() req): Promise<EmployeeProfileItemDto> {
|
||||
const email = req.user?.email;
|
||||
return this.employeesService.findOneProfile(email);
|
||||
}
|
||||
|
||||
@Get('employee-list')
|
||||
@RolesAllowed(...MANAGER_ROLES)
|
||||
findListEmployees(): Promise<EmployeeListItemDto[]> {
|
||||
return this.employeesService.findListEmployees();
|
||||
}
|
||||
|
||||
@Patch()
|
||||
@RolesAllowed(...MANAGER_ROLES)
|
||||
async updateOrArchiveOrRestore(@Req() req, @Body() dto: UpdateEmployeeDto,) {
|
||||
// if last_work_day is set => archive the employee
|
||||
// else if employee is archived and first_work_day or last_work_day = null => restore
|
||||
//otherwise => standard update
|
||||
const email = req.user?.email;
|
||||
const result = await this.archiveService.patchEmployee(email, dto);
|
||||
if (!result) {
|
||||
throw new NotFoundException(`Employee with email: ${email} is not found in active or archive.`)
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Post()
|
||||
@RolesAllowed(...MANAGER_ROLES)
|
||||
create(@Body() dto: CreateEmployeeDto): Promise<Employees> {
|
||||
return this.employeesService.create(dto);
|
||||
}
|
||||
|
||||
}
|
||||
12
src/identity-and-account/employees/employees.module.ts
Normal file
12
src/identity-and-account/employees/employees.module.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
// import { Module } from '@nestjs/common';
|
||||
// import { EmployeesController } from './controllers/employees.controller';
|
||||
// import { EmployeesService } from './services/employees.service';
|
||||
// import { SharedModule } from '../../time-and-attendance/modules/shared/shared.module';
|
||||
|
||||
// @Module({
|
||||
// imports: [SharedModule],
|
||||
// controllers: [EmployeesController],
|
||||
// providers: [EmployeesService],
|
||||
// exports: [EmployeesService ],
|
||||
// })
|
||||
// export class EmployeesModule {}
|
||||
|
|
@ -0,0 +1,173 @@
|
|||
import { Injectable } from "@nestjs/common";
|
||||
import { Employees, Users } from "@prisma/client";
|
||||
import { UpdateEmployeeDto } from "src/identity-and-account/employees/dtos/update-employee.dto";
|
||||
import { toDateOrUndefined, toDateOrNull } from "src/identity-and-account/employees/utils/employee.utils";
|
||||
import { PrismaService } from "src/prisma/prisma.service";
|
||||
|
||||
@Injectable()
|
||||
export class EmployeesArchivalService {
|
||||
constructor(private readonly prisma: PrismaService) { }
|
||||
|
||||
async patchEmployee(email: string, dto: UpdateEmployeeDto): Promise<Employees | null> {
|
||||
// 1) Tenter sur employés actifs
|
||||
const active = await this.prisma.employees.findFirst({
|
||||
where: { user: { email } },
|
||||
include: { user: true },
|
||||
});
|
||||
|
||||
if (active) {
|
||||
// Archivage : si on reçoit un last_work_day défini et que l'employé n’est pas déjà terminé
|
||||
// if (dto.last_work_day !== undefined && active.last_work_day == null && dto.last_work_day !== null) {
|
||||
// return this.archiveOnTermination(active, dto);
|
||||
// }
|
||||
|
||||
// Sinon, update standard (split Users/Employees)
|
||||
const {
|
||||
first_name,
|
||||
last_name,
|
||||
email: new_email,
|
||||
phone_number,
|
||||
residence,
|
||||
external_payroll_id,
|
||||
company_code,
|
||||
job_title,
|
||||
first_work_day,
|
||||
last_work_day,
|
||||
supervisor_id,
|
||||
is_supervisor,
|
||||
} = dto as any;
|
||||
|
||||
const first_work_d = toDateOrUndefined(first_work_day);
|
||||
const last_work_d = Object.prototype.hasOwnProperty('last_work_day')
|
||||
? toDateOrNull(last_work_day ?? null)
|
||||
: undefined;
|
||||
|
||||
await this.prisma.$transaction(async (transaction) => {
|
||||
if (
|
||||
first_name !== undefined ||
|
||||
last_name !== undefined ||
|
||||
new_email !== undefined ||
|
||||
phone_number !== undefined ||
|
||||
residence !== undefined
|
||||
) {
|
||||
await transaction.users.update({
|
||||
where: { id: active.user_id },
|
||||
data: {
|
||||
...(first_name !== undefined ? { first_name } : {}),
|
||||
...(last_name !== undefined ? { last_name } : {}),
|
||||
...(email !== undefined ? { email: new_email } : {}),
|
||||
...(phone_number !== undefined ? { phone_number } : {}),
|
||||
...(residence !== undefined ? { residence } : {}),
|
||||
},
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
const updated = await transaction.employees.update({
|
||||
where: { id: active.id },
|
||||
data: {
|
||||
...(external_payroll_id !== undefined ? { external_payroll_id } : {}),
|
||||
...(company_code !== undefined ? { company_code } : {}),
|
||||
...(job_title !== undefined ? { job_title } : {}),
|
||||
...(first_work_d !== undefined ? { first_work_day: first_work_d } : {}),
|
||||
...(last_work_d !== undefined ? { last_work_day: last_work_d } : {}),
|
||||
...(is_supervisor !== undefined ? { is_supervisor } : {}),
|
||||
...(supervisor_id !== undefined ? { supervisor_id } : {}),
|
||||
},
|
||||
include: { user: true },
|
||||
});
|
||||
|
||||
return updated;
|
||||
});
|
||||
|
||||
return this.prisma.employees.findFirst({ where: { user: { email } } });
|
||||
}
|
||||
|
||||
const user = await this.prisma.users.findUnique({ where: { email } });
|
||||
if (!user) return null;
|
||||
// 2) Pas trouvé en actifs → regarder en archive (pour restauration)
|
||||
// const archived = await this.prisma.employeesArchive.findFirst({
|
||||
// where: { user_id: user.id },
|
||||
// include: { user: true },
|
||||
// });
|
||||
|
||||
// if (archived) {
|
||||
// // Condition de restauration : last_work_day === null ou first_work_day fourni
|
||||
// const restore = dto.last_work_day === null || dto.first_work_day != null;
|
||||
// if (restore) {
|
||||
// return this.restoreEmployee(archived, dto);
|
||||
// }
|
||||
// }
|
||||
// 3) Ni actif, ni archivé → 404 dans le controller
|
||||
return null;
|
||||
}
|
||||
|
||||
//transfers the employee to archive and then delete from employees table
|
||||
// private async archiveOnTermination(active: Employees & { user: Users }, dto: UpdateEmployeeDto): Promise<EmployeesArchive> {
|
||||
// const last_work_d = toDateOrNull(dto.last_work_day!);
|
||||
// if (!last_work_d) throw new Error('invalide last_work_day for archive');
|
||||
// return this.prisma.$transaction(async transaction => {
|
||||
// //detach crew from supervisor if employee is a supervisor
|
||||
// await transaction.employees.updateMany({
|
||||
// where: { supervisor_id: active.id },
|
||||
// data: { supervisor_id: null },
|
||||
// })
|
||||
// const archived = await transaction.employeesArchive.create({
|
||||
// data: {
|
||||
// employee_id: active.id,
|
||||
// user_id: active.user_id,
|
||||
// first_name: active.user.first_name,
|
||||
// last_name: active.user.last_name,
|
||||
// company_code: active.company_code,
|
||||
// job_title: active.job_title,
|
||||
// first_work_day: active.first_work_day,
|
||||
// last_work_day: last_work_d,
|
||||
// supervisor_id: active.supervisor_id ?? null,
|
||||
// is_supervisor: active.is_supervisor,
|
||||
// external_payroll_id: active.external_payroll_id,
|
||||
// },
|
||||
// include: { user: true }
|
||||
// });
|
||||
// //delete from employees table
|
||||
// await transaction.employees.delete({ where: { id: active.id } });
|
||||
// //return archived employee
|
||||
// return archived
|
||||
// });
|
||||
// }
|
||||
|
||||
// //transfers the employee from archive to the employees table
|
||||
// private async restoreEmployee(archived: EmployeesArchive & { user: Users }, dto: UpdateEmployeeDto): Promise<Employees> {
|
||||
// // const first_work_d = toDateOrUndefined(dto.first_work_day);
|
||||
// return this.prisma.$transaction(async transaction => {
|
||||
// //restores the archived employee into the employees table
|
||||
// const restored = await transaction.employees.create({
|
||||
// data: {
|
||||
// user_id: archived.user_id,
|
||||
// company_code: archived.company_code,
|
||||
// job_title: archived.job_title,
|
||||
// first_work_day: archived.first_work_day,
|
||||
// last_work_day: null,
|
||||
// is_supervisor: archived.is_supervisor ?? false,
|
||||
// external_payroll_id: archived.external_payroll_id,
|
||||
// },
|
||||
// });
|
||||
// //deleting archived entry by id
|
||||
// await transaction.employeesArchive.delete({ where: { id: archived.id } });
|
||||
|
||||
// //return restored employee
|
||||
// return restored;
|
||||
// });
|
||||
// }
|
||||
|
||||
// //fetches all archived employees
|
||||
// async findAllArchived(): Promise<EmployeesArchive[]> {
|
||||
// return this.prisma.employeesArchive.findMany();
|
||||
// }
|
||||
|
||||
// //fetches an archived employee
|
||||
// async findOneArchived(id: number): Promise<EmployeesArchive> {
|
||||
// return this.prisma.employeesArchive.findUniqueOrThrow({ where: { id } });
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { PrismaService } from 'src/prisma/prisma.service';
|
||||
import { EmployeeListItemDto } from '../dtos/list-employee.dto';
|
||||
import { EmployeeProfileItemDto } from '../dtos/profil-employee.dto';
|
||||
import { Injectable, NotFoundException } from "@nestjs/common";
|
||||
import { Employees, Users } from "@prisma/client";
|
||||
import { CreateEmployeeDto } from "src/identity-and-account/employees/dtos/create-employee.dto";
|
||||
import { EmployeeListItemDto } from "src/identity-and-account/employees/dtos/list-employee.dto";
|
||||
import { EmployeeProfileItemDto } from "src/identity-and-account/employees/dtos/profil-employee.dto";
|
||||
import { PrismaService } from "src/prisma/prisma.service";
|
||||
|
||||
@Injectable()
|
||||
export class EmployeesService {
|
||||
|
|
@ -88,48 +90,50 @@ export class EmployeesService {
|
|||
};
|
||||
}
|
||||
|
||||
async create(dto: CreateEmployeeDto): Promise<Employees> {
|
||||
const {
|
||||
first_name,
|
||||
last_name,
|
||||
email,
|
||||
phone_number,
|
||||
residence,
|
||||
external_payroll_id,
|
||||
company_code,
|
||||
job_title,
|
||||
first_work_day,
|
||||
last_work_day,
|
||||
is_supervisor,
|
||||
} = dto;
|
||||
|
||||
return this.prisma.$transaction(async (transaction) => {
|
||||
const user: Users = await transaction.users.create({
|
||||
data: {
|
||||
first_name,
|
||||
last_name,
|
||||
email,
|
||||
phone_number,
|
||||
residence,
|
||||
},
|
||||
});
|
||||
return transaction.employees.create({
|
||||
data: {
|
||||
user_id: user.id,
|
||||
external_payroll_id,
|
||||
company_code,
|
||||
job_title,
|
||||
first_work_day,
|
||||
last_work_day,
|
||||
is_supervisor,
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
//_____________________________________________________________________________________________
|
||||
// Deprecated or unused methods
|
||||
//_____________________________________________________________________________________________
|
||||
|
||||
// async create(dto: CreateEmployeeDto): Promise<Employees> {
|
||||
// const {
|
||||
// first_name,
|
||||
// last_name,
|
||||
// email,
|
||||
// phone_number,
|
||||
// residence,
|
||||
// external_payroll_id,
|
||||
// company_code,
|
||||
// job_title,
|
||||
// first_work_day,
|
||||
// last_work_day,
|
||||
// is_supervisor,
|
||||
// } = dto;
|
||||
|
||||
// return this.prisma.$transaction(async (transaction) => {
|
||||
// const user: Users = await transaction.users.create({
|
||||
// data: {
|
||||
// first_name,
|
||||
// last_name,
|
||||
// email,
|
||||
// phone_number,
|
||||
// residence,
|
||||
// },
|
||||
// });
|
||||
// return transaction.employees.create({
|
||||
// data: {
|
||||
// user_id: user.id,
|
||||
// external_payroll_id,
|
||||
// company_code,
|
||||
// job_title,
|
||||
// first_work_day,
|
||||
// last_work_day,
|
||||
// is_supervisor,
|
||||
// },
|
||||
// });
|
||||
// });
|
||||
// }
|
||||
|
||||
// findAll(): Promise<Employees[]> {
|
||||
// return this.prisma.employees.findMany({
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { Body, Controller, Param, Patch } from "@nestjs/common";
|
||||
import { Body, Controller, Patch } from "@nestjs/common";
|
||||
import { PreferencesService } from "../services/preferences.service";
|
||||
import { PreferencesDto } from "../dtos/preferences.dto";
|
||||
|
||||
|
|
@ -6,9 +6,12 @@ import { PreferencesDto } from "../dtos/preferences.dto";
|
|||
export class PreferencesController {
|
||||
constructor(private readonly service: PreferencesService){}
|
||||
|
||||
@Patch(':email')
|
||||
async updatePreferences(@Param('email') email: string, @Body()payload: PreferencesDto) {
|
||||
return this.service.updatePreferences(email, payload);
|
||||
@Patch()
|
||||
async updatePreferences(
|
||||
@Body() user_id: number,
|
||||
@Body() payload: PreferencesDto
|
||||
) {
|
||||
return this.service.updatePreferences(user_id, payload);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,10 +1,8 @@
|
|||
import { Module } from "@nestjs/common";
|
||||
import { PreferencesController } from "./controllers/preferences.controller";
|
||||
import { PreferencesService } from "./services/preferences.service";
|
||||
import { SharedModule } from "../shared/shared.module";
|
||||
import { Module } from "@nestjs/common";
|
||||
|
||||
@Module({
|
||||
imports: [SharedModule],
|
||||
controllers: [ PreferencesController ],
|
||||
providers: [ PreferencesService ],
|
||||
exports: [ PreferencesService ],
|
||||
|
|
@ -1,20 +1,15 @@
|
|||
import { Injectable } from "@nestjs/common";
|
||||
import { Preferences } from "@prisma/client";
|
||||
import { PrismaService } from "src/prisma/prisma.service";
|
||||
import { PreferencesDto } from "../dtos/preferences.dto";
|
||||
import { EmailToIdResolver } from "src/modules/shared/utils/resolve-email-id.utils";
|
||||
import { PrismaService } from "src/prisma/prisma.service";
|
||||
import { Preferences } from "@prisma/client";
|
||||
import { Injectable } from "@nestjs/common";
|
||||
|
||||
@Injectable()
|
||||
export class PreferencesService {
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly emailResolver: EmailToIdResolver ,
|
||||
){}
|
||||
constructor( private readonly prisma: PrismaService ){}
|
||||
|
||||
async updatePreferences(email: string, dto: PreferencesDto ): Promise<Preferences> {
|
||||
const user_id = await this.emailResolver.resolveUserIdWithEmail(email);
|
||||
async updatePreferences(user_id: number, dto: PreferencesDto ): Promise<Preferences> {
|
||||
return this.prisma.preferences.update({
|
||||
where: { user_id },
|
||||
where: { id: user_id },
|
||||
data: {
|
||||
notifications: dto.notifications,
|
||||
dark_mode: dto.dark_mode,
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { Users } from '@prisma/client';
|
||||
import { PrismaService } from 'src/prisma/prisma.service';
|
||||
|
||||
@Injectable()
|
||||
export abstract class AbstractUserService {
|
||||
constructor(protected readonly prisma: PrismaService) { }
|
||||
|
||||
findAll(): Promise<Users[]> {
|
||||
return this.prisma.users.findMany();
|
||||
}
|
||||
|
||||
async findOne(id: string): Promise<Users> {
|
||||
const user = await this.prisma.users.findUnique({ where: { id } });
|
||||
if (!user) {
|
||||
throw new NotFoundException(`User #${id} not found`);
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
async findOneByEmail(email: string): Promise<Partial<Users>> {
|
||||
const user = await this.prisma.users.findUnique({ where: { email } });
|
||||
if (!user) {
|
||||
throw new NotFoundException(`No user with email #${email} exists`);
|
||||
}
|
||||
|
||||
const clean_user = {
|
||||
first_name: user.first_name,
|
||||
last_name: user.last_name,
|
||||
email: user.email,
|
||||
role: user.role,
|
||||
}
|
||||
|
||||
return clean_user;
|
||||
}
|
||||
|
||||
async remove(id: string): Promise<Users> {
|
||||
await this.findOne(id);
|
||||
return this.prisma.users.delete({ where: { id } });
|
||||
}
|
||||
}
|
||||
|
|
@ -19,6 +19,8 @@ import { writeFileSync } from 'fs';
|
|||
import * as session from 'express-session';
|
||||
import * as passport from 'passport';
|
||||
|
||||
const SESSION_TOKEN_DURATION_MINUTES = 180
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
|
||||
|
|
@ -37,7 +39,7 @@ async function bootstrap() {
|
|||
saveUninitialized: false,
|
||||
rolling: true,
|
||||
cookie: {
|
||||
maxAge: 30 * 60 * 1000,
|
||||
maxAge: SESSION_TOKEN_DURATION_MINUTES * 60 * 1000, // property maxAge requires milliseconds
|
||||
httpOnly: true,
|
||||
}
|
||||
}))
|
||||
|
|
@ -46,7 +48,7 @@ async function bootstrap() {
|
|||
|
||||
// Enable CORS
|
||||
app.enableCors({
|
||||
origin: 'http://localhost:9000',
|
||||
origin: ['http://10.100.251.2:9011', 'http://10.100.251.2:9012', 'http://10.100.251.2:9013', 'http://localhost:9000'],
|
||||
credentials: true,
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,33 +1,33 @@
|
|||
import { Controller, Get, NotFoundException, Param, ParseIntPipe, UseGuards } from "@nestjs/common";
|
||||
import { ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger";
|
||||
import { RolesAllowed } from "src/common/decorators/roles.decorators";
|
||||
import { EmployeesArchive, Roles as RoleEnum } from '@prisma/client';
|
||||
import { EmployeesArchivalService } from "src/modules/employees/services/employees-archival.service";
|
||||
// import { Controller, Get, NotFoundException, Param, ParseIntPipe, UseGuards } from "@nestjs/common";
|
||||
// import { ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger";
|
||||
// import { RolesAllowed } from "src/common/decorators/roles.decorators";
|
||||
// import { EmployeesArchive, Roles as RoleEnum } from '@prisma/client';
|
||||
// import { EmployeesArchivalService } from "src/modules/employees/services/employees-archival.service";
|
||||
|
||||
@ApiTags('Employee Archives')
|
||||
// @UseGuards()
|
||||
@Controller('archives/employees')
|
||||
export class EmployeesArchiveController {
|
||||
constructor(private readonly employeesArchiveService: EmployeesArchivalService) {}
|
||||
// @ApiTags('Employee Archives')
|
||||
// // @UseGuards()
|
||||
// @Controller('archives/employees')
|
||||
// export class EmployeesArchiveController {
|
||||
// constructor(private readonly employeesArchiveService: EmployeesArchivalService) {}
|
||||
|
||||
@Get()
|
||||
//@RolesAllowed(RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||
@ApiOperation({ summary: 'List of archived employees'})
|
||||
@ApiResponse({ status: 200, description: 'List of archived employees', isArray: true })
|
||||
async findAllArchived(): Promise<EmployeesArchive[]> {
|
||||
return this.employeesArchiveService.findAllArchived();
|
||||
}
|
||||
// @Get()
|
||||
// //@RolesAllowed(RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||
// @ApiOperation({ summary: 'List of archived employees'})
|
||||
// @ApiResponse({ status: 200, description: 'List of archived employees', isArray: true })
|
||||
// async findAllArchived(): Promise<EmployeesArchive[]> {
|
||||
// return this.employeesArchiveService.findAllArchived();
|
||||
// }
|
||||
|
||||
@Get()
|
||||
//@RolesAllowed(RoleEnum.ADMIN, RoleEnum.HR,RoleEnum.SUPERVISOR)
|
||||
@ApiOperation({ summary: 'Fetch employee in archives with its Id'})
|
||||
@ApiResponse({ status: 200, description: 'Archived employee found'})
|
||||
async findOneArchived(@Param('id', ParseIntPipe) id: number ): Promise<EmployeesArchive> {
|
||||
try{
|
||||
return await this.employeesArchiveService.findOneArchived(id);
|
||||
}catch {
|
||||
throw new NotFoundException(`Archived employee #${id} not found`);
|
||||
}
|
||||
}
|
||||
// @Get()
|
||||
// //@RolesAllowed(RoleEnum.ADMIN, RoleEnum.HR,RoleEnum.SUPERVISOR)
|
||||
// @ApiOperation({ summary: 'Fetch employee in archives with its Id'})
|
||||
// @ApiResponse({ status: 200, description: 'Archived employee found'})
|
||||
// async findOneArchived(@Param('id', ParseIntPipe) id: number ): Promise<EmployeesArchive> {
|
||||
// try{
|
||||
// return await this.employeesArchiveService.findOneArchived(id);
|
||||
// }catch {
|
||||
// throw new NotFoundException(`Archived employee #${id} not found`);
|
||||
// }
|
||||
// }
|
||||
|
||||
}
|
||||
// }
|
||||
|
|
@ -1,32 +1,32 @@
|
|||
import { UseGuards, Controller, Get, Param, ParseIntPipe, NotFoundException } from "@nestjs/common";
|
||||
import { ApiTags, ApiOperation, ApiResponse } from "@nestjs/swagger";
|
||||
import { ExpensesArchive,Roles as RoleEnum } from "@prisma/client";
|
||||
import { RolesAllowed } from "src/common/decorators/roles.decorators";
|
||||
import { ExpensesArchivalService } from "src/modules/expenses/services/expenses-archival.service";
|
||||
// import { UseGuards, Controller, Get, Param, ParseIntPipe, NotFoundException } from "@nestjs/common";
|
||||
// import { ApiTags, ApiOperation, ApiResponse } from "@nestjs/swagger";
|
||||
// import { ExpensesArchive,Roles as RoleEnum } from "@prisma/client";
|
||||
// import { RolesAllowed } from "src/common/decorators/roles.decorators";
|
||||
// import { ExpensesArchivalService } from "src/time-and-attendance/modules/expenses/services/expenses-archival.service";
|
||||
|
||||
@ApiTags('Expense Archives')
|
||||
// @UseGuards()
|
||||
@Controller('archives/expenses')
|
||||
export class ExpensesArchiveController {
|
||||
constructor(private readonly expensesService: ExpensesArchivalService) {}
|
||||
// @ApiTags('Expense Archives')
|
||||
// // @UseGuards()
|
||||
// @Controller('archives/expenses')
|
||||
// export class ExpensesArchiveController {
|
||||
// constructor(private readonly expensesService: ExpensesArchivalService) {}
|
||||
|
||||
@Get()
|
||||
//@RolesAllowed(RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||
@ApiOperation({ summary: 'List of archived expenses'})
|
||||
@ApiResponse({ status: 200, description: 'List of archived expenses', isArray: true })
|
||||
async findAllArchived(): Promise<ExpensesArchive[]> {
|
||||
return this.expensesService.findAllArchived();
|
||||
}
|
||||
// @Get()
|
||||
// //@RolesAllowed(RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||
// @ApiOperation({ summary: 'List of archived expenses'})
|
||||
// @ApiResponse({ status: 200, description: 'List of archived expenses', isArray: true })
|
||||
// async findAllArchived(): Promise<ExpensesArchive[]> {
|
||||
// return this.expensesService.findAllArchived();
|
||||
// }
|
||||
|
||||
@Get()
|
||||
//@RolesAllowed(RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||
@ApiOperation({ summary: 'Fetch expense in archives with its Id'})
|
||||
@ApiResponse({ status: 200, description: 'Archived expense found'})
|
||||
async findOneArchived(@Param('id', ParseIntPipe) id: number ): Promise<ExpensesArchive> {
|
||||
try{
|
||||
return await this.expensesService.findOneArchived(id);
|
||||
}catch {
|
||||
throw new NotFoundException(`Archived expense #${id} not found`);
|
||||
}
|
||||
}
|
||||
}
|
||||
// @Get()
|
||||
// //@RolesAllowed(RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||
// @ApiOperation({ summary: 'Fetch expense in archives with its Id'})
|
||||
// @ApiResponse({ status: 200, description: 'Archived expense found'})
|
||||
// async findOneArchived(@Param('id', ParseIntPipe) id: number ): Promise<ExpensesArchive> {
|
||||
// try{
|
||||
// return await this.expensesService.findOneArchived(id);
|
||||
// }catch {
|
||||
// throw new NotFoundException(`Archived expense #${id} not found`);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { Controller } from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
// import { Controller } from '@nestjs/common';
|
||||
// import { ApiTags } from '@nestjs/swagger';
|
||||
|
||||
@ApiTags('LeaveRequests Archives')
|
||||
// @UseGuards()
|
||||
@Controller('archives/leaveRequests')
|
||||
export class LeaveRequestsArchiveController {}
|
||||
// @ApiTags('LeaveRequests Archives')
|
||||
// // @UseGuards()
|
||||
// @Controller('archives/leaveRequests')
|
||||
// export class LeaveRequestsArchiveController {}
|
||||
|
|
@ -1,32 +1,32 @@
|
|||
import { Get, Param, ParseIntPipe, NotFoundException, Controller, UseGuards } from "@nestjs/common";
|
||||
import { ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger";
|
||||
import { ShiftsArchive, Roles as RoleEnum } from "@prisma/client";
|
||||
import { RolesAllowed } from "src/common/decorators/roles.decorators";
|
||||
import { ShiftsArchivalService } from "src/modules/shifts/services/shifts-archival.service";
|
||||
// import { Get, Param, ParseIntPipe, NotFoundException, Controller, UseGuards } from "@nestjs/common";
|
||||
// import { ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger";
|
||||
// import { ShiftsArchive, Roles as RoleEnum } from "@prisma/client";
|
||||
// import { RolesAllowed } from "src/common/decorators/roles.decorators";
|
||||
// import { ShiftsArchivalService } from "src/time-and-attendance/modules/time-tracker/shifts/services/shifts-archival.service";
|
||||
|
||||
@ApiTags('Shift Archives')
|
||||
// @UseGuards()
|
||||
@Controller('archives/shifts')
|
||||
export class ShiftsArchiveController {
|
||||
constructor(private readonly shiftsService: ShiftsArchivalService) {}
|
||||
// @ApiTags('Shift Archives')
|
||||
// // @UseGuards()
|
||||
// @Controller('archives/shifts')
|
||||
// export class ShiftsArchiveController {
|
||||
// constructor(private readonly shiftsService: ShiftsArchivalService) {}
|
||||
|
||||
@Get()
|
||||
//@RolesAllowed(RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||
@ApiOperation({ summary: 'List of archived shifts'})
|
||||
@ApiResponse({ status: 200, description: 'List of archived shifts', isArray: true })
|
||||
async findAllArchived(): Promise<ShiftsArchive[]> {
|
||||
return this.shiftsService.findAllArchived();
|
||||
}
|
||||
// @Get()
|
||||
// //@RolesAllowed(RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||
// @ApiOperation({ summary: 'List of archived shifts'})
|
||||
// @ApiResponse({ status: 200, description: 'List of archived shifts', isArray: true })
|
||||
// async findAllArchived(): Promise<ShiftsArchive[]> {
|
||||
// return this.shiftsService.findAllArchived();
|
||||
// }
|
||||
|
||||
@Get()
|
||||
//@RolesAllowed(RoleEnum.ADMIN, RoleEnum.HR,RoleEnum.SUPERVISOR)
|
||||
@ApiOperation({ summary: 'Fetch shift in archives with its Id'})
|
||||
@ApiResponse({ status: 200, description: 'Archived shift found'})
|
||||
async findOneArchived(@Param('id', ParseIntPipe) id: number ): Promise<ShiftsArchive> {
|
||||
try{
|
||||
return await this.shiftsService.findOneArchived(id);
|
||||
}catch {
|
||||
throw new NotFoundException(`Archived shift #${id} not found`);
|
||||
}
|
||||
}
|
||||
}
|
||||
// @Get()
|
||||
// //@RolesAllowed(RoleEnum.ADMIN, RoleEnum.HR,RoleEnum.SUPERVISOR)
|
||||
// @ApiOperation({ summary: 'Fetch shift in archives with its Id'})
|
||||
// @ApiResponse({ status: 200, description: 'Archived shift found'})
|
||||
// async findOneArchived(@Param('id', ParseIntPipe) id: number ): Promise<ShiftsArchive> {
|
||||
// try{
|
||||
// return await this.shiftsService.findOneArchived(id);
|
||||
// }catch {
|
||||
// throw new NotFoundException(`Archived shift #${id} not found`);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
|
@ -1,33 +1,33 @@
|
|||
import { Controller, Get, NotFoundException, Param, ParseIntPipe, UseGuards } from "@nestjs/common";
|
||||
import { ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger";
|
||||
import { RolesAllowed } from "src/common/decorators/roles.decorators";
|
||||
import { TimesheetsArchive, Roles as RoleEnum } from '@prisma/client';
|
||||
import { TimesheetArchiveService } from "src/modules/timesheets/services/timesheet-archive.service";
|
||||
// import { Controller, Get, NotFoundException, Param, ParseIntPipe, UseGuards } from "@nestjs/common";
|
||||
// import { ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger";
|
||||
// import { RolesAllowed } from "src/common/decorators/roles.decorators";
|
||||
// import { TimesheetsArchive, Roles as RoleEnum } from '@prisma/client';
|
||||
// import { TimesheetArchiveService } from "src/time-and-attendance/modules/time-tracker/timesheets/services/timesheet-archive.service";
|
||||
|
||||
@ApiTags('Timesheet Archives')
|
||||
// @UseGuards()
|
||||
@Controller('archives/timesheets')
|
||||
export class TimesheetsArchiveController {
|
||||
constructor(private readonly timesheetsService: TimesheetArchiveService) {}
|
||||
// @ApiTags('Timesheet Archives')
|
||||
// // @UseGuards()
|
||||
// @Controller('archives/timesheets')
|
||||
// export class TimesheetsArchiveController {
|
||||
// constructor(private readonly timesheetsService: TimesheetArchiveService) {}
|
||||
|
||||
@Get()
|
||||
//@RolesAllowed(RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||
@ApiOperation({ summary: 'List of archived timesheets'})
|
||||
@ApiResponse({ status: 200, description: 'List of archived timesheets', isArray: true })
|
||||
async findAllArchived(): Promise<TimesheetsArchive[]> {
|
||||
return this.timesheetsService.findAllArchived();
|
||||
}
|
||||
// @Get()
|
||||
// //@RolesAllowed(RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||
// @ApiOperation({ summary: 'List of archived timesheets'})
|
||||
// @ApiResponse({ status: 200, description: 'List of archived timesheets', isArray: true })
|
||||
// async findAllArchived(): Promise<TimesheetsArchive[]> {
|
||||
// return this.timesheetsService.findAllArchived();
|
||||
// }
|
||||
|
||||
@Get()
|
||||
//@RolesAllowed(RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||
@ApiOperation({ summary: 'Fetch timesheet in archives with its Id'})
|
||||
@ApiResponse({ status: 200, description: 'Archived timesheet found'})
|
||||
async findOneArchived(@Param('id', ParseIntPipe) id: number ): Promise<TimesheetsArchive> {
|
||||
try{
|
||||
return await this.timesheetsService.findOneArchived(id);
|
||||
}catch {
|
||||
throw new NotFoundException(`Archived timesheet #${id} not found`);
|
||||
}
|
||||
}
|
||||
// @Get()
|
||||
// //@RolesAllowed(RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||
// @ApiOperation({ summary: 'Fetch timesheet in archives with its Id'})
|
||||
// @ApiResponse({ status: 200, description: 'Archived timesheet found'})
|
||||
// async findOneArchived(@Param('id', ParseIntPipe) id: number ): Promise<TimesheetsArchive> {
|
||||
// try{
|
||||
// return await this.timesheetsService.findOneArchived(id);
|
||||
// }catch {
|
||||
// throw new NotFoundException(`Archived timesheet #${id} not found`);
|
||||
// }
|
||||
// }
|
||||
|
||||
}
|
||||
// }
|
||||
|
|
@ -1,38 +1,38 @@
|
|||
import { Injectable, Logger } from "@nestjs/common";
|
||||
import { Cron } from "@nestjs/schedule";
|
||||
import { ExpensesArchivalService } from "src/modules/expenses/services/expenses-archival.service";
|
||||
import { ShiftsArchivalService } from "src/modules/shifts/services/shifts-archival.service";
|
||||
import { TimesheetArchiveService } from "src/modules/timesheets/services/timesheet-archive.service";
|
||||
// import { TimesheetArchiveService } from "src/time-and-attendance/modules/time-tracker/timesheets/services/timesheet-archive.service";
|
||||
// import { ExpensesArchivalService } from "src/time-and-attendance/modules/expenses/services/expenses-archival.service";
|
||||
// import { ShiftsArchivalService } from "src/time-and-attendance/modules/time-tracker/shifts/services/shifts-archival.service";
|
||||
// import { Injectable, Logger } from "@nestjs/common";
|
||||
// import { Cron } from "@nestjs/schedule";
|
||||
|
||||
@Injectable()
|
||||
export class ArchivalService {
|
||||
private readonly logger = new Logger(ArchivalService.name);
|
||||
// @Injectable()
|
||||
// export class ArchivalService {
|
||||
// private readonly logger = new Logger(ArchivalService.name);
|
||||
|
||||
constructor(
|
||||
private readonly timesheetsService: TimesheetArchiveService,
|
||||
private readonly expensesService: ExpensesArchivalService,
|
||||
private readonly shiftsService: ShiftsArchivalService,
|
||||
) {}
|
||||
// constructor(
|
||||
// private readonly timesheetsService: TimesheetArchiveService,
|
||||
// private readonly expensesService: ExpensesArchivalService,
|
||||
// private readonly shiftsService: ShiftsArchivalService,
|
||||
// ) {}
|
||||
|
||||
@Cron('0 0 3 * * 1', {timeZone:'America/Toronto'}) // chaque premier lundi du mois à 03h00
|
||||
async handleMonthlyArchival() {
|
||||
const today = new Date();
|
||||
const dayOfMonth = today.getDate();
|
||||
// @Cron('0 0 3 * * 1', {timeZone:'America/Toronto'}) // chaque premier lundi du mois à 03h00
|
||||
// async handleMonthlyArchival() {
|
||||
// const today = new Date();
|
||||
// const dayOfMonth = today.getDate();
|
||||
|
||||
if (dayOfMonth > 7) {
|
||||
this.logger.warn('Archive {awaiting 1st monday of the month for archivation process}')
|
||||
return;
|
||||
}
|
||||
// if (dayOfMonth > 7) {
|
||||
// this.logger.warn('Archive {awaiting 1st monday of the month for archivation process}')
|
||||
// return;
|
||||
// }
|
||||
|
||||
this.logger.log('monthly archivation in process');
|
||||
try {
|
||||
await this.timesheetsService.archiveOld();
|
||||
await this.expensesService.archiveOld();
|
||||
await this.shiftsService.archiveOld();
|
||||
// await this.leaveRequestsService.archiveExpired();
|
||||
this.logger.log('archivation process done');
|
||||
} catch (err) {
|
||||
this.logger.error('an error occured during archivation process ', err);
|
||||
}
|
||||
}
|
||||
}
|
||||
// this.logger.log('monthly archivation in process');
|
||||
// try {
|
||||
// await this.timesheetsService.archiveOld();
|
||||
// await this.expensesService.archiveOld();
|
||||
// await this.shiftsService.archiveOld();
|
||||
// // await this.leaveRequestsService.archiveExpired();
|
||||
// this.logger.log('archivation process done');
|
||||
// } catch (err) {
|
||||
// this.logger.error('an error occured during archivation process ', err);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
|
@ -1,15 +1,19 @@
|
|||
import { ScheduleModule } from "@nestjs/schedule";
|
||||
import { PrismaService } from "src/prisma/prisma.service";
|
||||
import { ArchivalAttachmentService } from "./services/archival-attachment.service";
|
||||
import { ArchivalAttachmentService } from "src/modules/attachments/services/archival-attachment.service";
|
||||
import { GarbargeCollectorService } from "src/modules/attachments/services/garbage-collector.service";
|
||||
import { AttachmentsController } from "src/modules/attachments/controllers/attachments.controller";
|
||||
import { DiskStorageService } from "src/modules/attachments/services/disk-storage.service";
|
||||
// import { ScheduleModule } from "@nestjs/schedule";
|
||||
import { VariantsQueue } from "src/modules/attachments/services/variants.queue";
|
||||
import { Module } from "@nestjs/common";
|
||||
import { GarbargeCollectorService } from "./services/garbage-collector.service";
|
||||
|
||||
@Module({
|
||||
imports: [ScheduleModule.forRoot()],
|
||||
// imports: [ScheduleModule.forRoot()],
|
||||
controllers: [ AttachmentsController],
|
||||
providers: [
|
||||
PrismaService,
|
||||
ArchivalAttachmentService,
|
||||
GarbargeCollectorService,
|
||||
DiskStorageService,
|
||||
VariantsQueue,
|
||||
],
|
||||
exports: [
|
||||
ArchivalAttachmentService,
|
||||
|
|
|
|||
|
|
@ -4,8 +4,6 @@ import {
|
|||
Controller,NotFoundException, UseInterceptors, Post, Get, Param, Res,
|
||||
UploadedFile, BadRequestException, UnsupportedMediaTypeException, Body, Delete,
|
||||
Query,
|
||||
DefaultValuePipe,
|
||||
ParseIntPipe
|
||||
} from "@nestjs/common";
|
||||
import { maxUploadBytes, allowedMimes } from "../config/upload.config";
|
||||
import { memoryStorage } from 'multer';
|
||||
|
|
@ -76,7 +74,7 @@ export class AttachmentsController {
|
|||
return this.prisma.attachmentVariants.findMany({
|
||||
where: { attachment_id: num_id },
|
||||
orderBy: { variant: 'asc'},
|
||||
select: { variant: true, bytes: true, width: true, height: true, patch: true, created_at: true },
|
||||
select: { variant: true, bytes: true, width: true, height: true, path: true, created_at: true },
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { createHash } from 'node:crypto';
|
||||
import { promises as fsp } from 'node:fs';
|
||||
import { createWriteStream, statSync, existsSync } from 'node:fs';
|
||||
|
|
@ -7,6 +8,7 @@ import { ATT_TMP_DIR, resolveAttachmentsRoot } from 'src/config/attachment.confi
|
|||
|
||||
export type SaveResult = { sha256:string, storage_path:string, size:number};
|
||||
|
||||
@Injectable()
|
||||
export class DiskStorageService {
|
||||
private root = resolveAttachmentsRoot();
|
||||
|
||||
|
|
@ -38,7 +40,7 @@ export class DiskStorageService {
|
|||
|
||||
const hash = createHash('sha256');
|
||||
const tmpOut = createWriteStream(tmpPath);
|
||||
input.on('date', (chunk) => hash.update(chunk));
|
||||
input.on('data', (chunk) => hash.update(chunk));
|
||||
await pipeline(input, tmpOut); //await end of writing stream
|
||||
|
||||
const sha = hash.digest('hex');
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import { Injectable } from "@nestjs/common";
|
||||
import { Queue } from "bullmq";
|
||||
|
||||
@Injectable()
|
||||
export class VariantsQueue {
|
||||
private queue : Queue;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { Module } from "@nestjs/common";
|
||||
import { PrismaService } from "src/prisma/prisma.service";
|
||||
import { BankCodesControllers } from "./controllers/bank-codes.controller";
|
||||
import { BankCodesService } from "./services/bank-codes.service";
|
||||
// import { Module } from "@nestjs/common";
|
||||
// import { PrismaService } from "src/prisma/prisma.service";
|
||||
// import { BankCodesControllers } from "./controllers/bank-codes.controller";
|
||||
// import { BankCodesService } from "./services/bank-codes.service";
|
||||
|
||||
@Module({
|
||||
controllers: [BankCodesControllers],
|
||||
providers: [BankCodesService, PrismaService],
|
||||
})
|
||||
// @Module({
|
||||
// controllers: [BankCodesControllers],
|
||||
// providers: [BankCodesService, PrismaService],
|
||||
// })
|
||||
|
||||
export class BankCodesModule {}
|
||||
// export class BankCodesModule {}
|
||||
|
|
@ -1,49 +1,49 @@
|
|||
import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post } from "@nestjs/common";
|
||||
import { BankCodesService } from "../services/bank-codes.service";
|
||||
import { CreateBankCodeDto } from "../dtos/create-bank-code.dto";
|
||||
import { UpdateBankCodeDto } from "../dtos/update-bank-code.dto";
|
||||
import { ApiBadRequestResponse, ApiNotFoundResponse, ApiOperation, ApiResponse } from "@nestjs/swagger";
|
||||
// import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post } from "@nestjs/common";
|
||||
// import { BankCodesService } from "../services/bank-codes.service";
|
||||
// import { CreateBankCodeDto } from "../dtos/create-bank-code.dto";
|
||||
// import { UpdateBankCodeDto } from "../dtos/update-bank-code.dto";
|
||||
// import { ApiBadRequestResponse, ApiNotFoundResponse, ApiOperation, ApiResponse } from "@nestjs/swagger";
|
||||
|
||||
@Controller('bank-codes')
|
||||
export class BankCodesControllers {
|
||||
constructor(private readonly bankCodesService: BankCodesService) {}
|
||||
//_____________________________________________________________________________________________
|
||||
// Deprecated or unused methods
|
||||
//_____________________________________________________________________________________________
|
||||
// @Controller('bank-codes')
|
||||
// export class BankCodesControllers {
|
||||
// constructor(private readonly bankCodesService: BankCodesService) {}
|
||||
// //_____________________________________________________________________________________________
|
||||
// // Deprecated or unused methods
|
||||
// //_____________________________________________________________________________________________
|
||||
|
||||
// @Post()
|
||||
// @ApiOperation({ summary: 'Create a new bank code' })
|
||||
// @ApiResponse({ status: 201, description: 'Bank code successfully created.' })
|
||||
// @ApiBadRequestResponse({ description: 'Invalid input data.' })
|
||||
// create(@Body() dto: CreateBankCodeDto) {
|
||||
// return this.bankCodesService.create(dto);
|
||||
// // @Post()
|
||||
// // @ApiOperation({ summary: 'Create a new bank code' })
|
||||
// // @ApiResponse({ status: 201, description: 'Bank code successfully created.' })
|
||||
// // @ApiBadRequestResponse({ description: 'Invalid input data.' })
|
||||
// // create(@Body() dto: CreateBankCodeDto) {
|
||||
// // return this.bankCodesService.create(dto);
|
||||
// // }
|
||||
|
||||
// // @Get()
|
||||
// // @ApiOperation({ summary: 'Retrieve all bank codes' })
|
||||
// // @ApiResponse({ status: 200, description: 'List of bank codes.' })
|
||||
// // findAll() {
|
||||
// // return this.bankCodesService.findAll();
|
||||
// // }
|
||||
|
||||
// // @Get(':id')
|
||||
// // @ApiOperation({ summary: 'Retrieve a bank code by its ID' })
|
||||
// // @ApiNotFoundResponse({ description: 'Bank code not found.' })
|
||||
// // findOne(@Param('id', ParseIntPipe) id: number){
|
||||
// // return this.bankCodesService.findOne(id);
|
||||
// // }
|
||||
|
||||
// // @Patch(':id')
|
||||
// // @ApiOperation({ summary: 'Update an existing bank code' })
|
||||
// // @ApiNotFoundResponse({ description: 'Bank code not found.' })
|
||||
// // update(@Param('id', ParseIntPipe) id: number, @Body() dto: UpdateBankCodeDto) {
|
||||
// // return this.bankCodesService.update(id, dto)
|
||||
// // }
|
||||
|
||||
// // @Delete(':id')
|
||||
// // @ApiOperation({ summary: 'Delete a bank code' })
|
||||
// // @ApiNotFoundResponse({ description: 'Bank code not found.' })
|
||||
// // remove(@Param('id', ParseIntPipe) id: number) {
|
||||
// // return this.bankCodesService.remove(id);
|
||||
// // }
|
||||
// }
|
||||
|
||||
// @Get()
|
||||
// @ApiOperation({ summary: 'Retrieve all bank codes' })
|
||||
// @ApiResponse({ status: 200, description: 'List of bank codes.' })
|
||||
// findAll() {
|
||||
// return this.bankCodesService.findAll();
|
||||
// }
|
||||
|
||||
// @Get(':id')
|
||||
// @ApiOperation({ summary: 'Retrieve a bank code by its ID' })
|
||||
// @ApiNotFoundResponse({ description: 'Bank code not found.' })
|
||||
// findOne(@Param('id', ParseIntPipe) id: number){
|
||||
// return this.bankCodesService.findOne(id);
|
||||
// }
|
||||
|
||||
// @Patch(':id')
|
||||
// @ApiOperation({ summary: 'Update an existing bank code' })
|
||||
// @ApiNotFoundResponse({ description: 'Bank code not found.' })
|
||||
// update(@Param('id', ParseIntPipe) id: number, @Body() dto: UpdateBankCodeDto) {
|
||||
// return this.bankCodesService.update(id, dto)
|
||||
// }
|
||||
|
||||
// @Delete(':id')
|
||||
// @ApiOperation({ summary: 'Delete a bank code' })
|
||||
// @ApiNotFoundResponse({ description: 'Bank code not found.' })
|
||||
// remove(@Param('id', ParseIntPipe) id: number) {
|
||||
// return this.bankCodesService.remove(id);
|
||||
// }
|
||||
}
|
||||
|
|
@ -1,46 +1,46 @@
|
|||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { Type } from "class-transformer";
|
||||
import { Allow, IsNotEmpty, IsNumber, IsString } from "class-validator";
|
||||
// import { ApiProperty } from "@nestjs/swagger";
|
||||
// import { Type } from "class-transformer";
|
||||
// import { Allow, IsNotEmpty, IsNumber, IsString } from "class-validator";
|
||||
|
||||
export class CreateBankCodeDto {
|
||||
@ApiProperty({
|
||||
example: 1,
|
||||
description: 'Unique ID of a bank-code (auto-generated)',
|
||||
readOnly: true,
|
||||
})
|
||||
@Allow()
|
||||
id: number;
|
||||
// export class CreateBankCodeDto {
|
||||
// @ApiProperty({
|
||||
// example: 1,
|
||||
// description: 'Unique ID of a bank-code (auto-generated)',
|
||||
// readOnly: true,
|
||||
// })
|
||||
// @Allow()
|
||||
// id: number;
|
||||
|
||||
@ApiProperty({
|
||||
example: 'regular, vacation, emergency, sick, parental, etc',
|
||||
description: 'Type of codes',
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
type: string;
|
||||
// @ApiProperty({
|
||||
// example: 'regular, vacation, emergency, sick, parental, etc',
|
||||
// description: 'Type of codes',
|
||||
// })
|
||||
// @IsString()
|
||||
// @IsNotEmpty()
|
||||
// type: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: 'shift, expense, leave',
|
||||
description: 'categorie of the related code',
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
categorie: string;
|
||||
// @ApiProperty({
|
||||
// example: 'shift, expense, leave',
|
||||
// description: 'categorie of the related code',
|
||||
// })
|
||||
// @IsString()
|
||||
// @IsNotEmpty()
|
||||
// categorie: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: '0, 0.72, 1, 1.5, 2',
|
||||
description: 'modifier number to apply to salary',
|
||||
})
|
||||
@Type(()=> Number)
|
||||
@IsNumber()
|
||||
@IsNotEmpty()
|
||||
modifier: number;
|
||||
// @ApiProperty({
|
||||
// example: '0, 0.72, 1, 1.5, 2',
|
||||
// description: 'modifier number to apply to salary',
|
||||
// })
|
||||
// @Type(()=> Number)
|
||||
// @IsNumber()
|
||||
// @IsNotEmpty()
|
||||
// modifier: number;
|
||||
|
||||
@ApiProperty({
|
||||
example: 'G1, G345, G501, G43, G700',
|
||||
description: 'codes given by the bank',
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
bank_code: string;
|
||||
}
|
||||
// @ApiProperty({
|
||||
// example: 'G1, G345, G501, G43, G700',
|
||||
// description: 'codes given by the bank',
|
||||
// })
|
||||
// @IsString()
|
||||
// @IsNotEmpty()
|
||||
// bank_code: string;
|
||||
// }
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { PartialType } from "@nestjs/swagger";
|
||||
import { CreateBankCodeDto } from "./create-bank-code.dto";
|
||||
// import { PartialType } from "@nestjs/swagger";
|
||||
// import { CreateBankCodeDto } from "./create-bank-code.dto";
|
||||
|
||||
export class UpdateBankCodeDto extends PartialType(CreateBankCodeDto) {}
|
||||
// export class UpdateBankCodeDto extends PartialType(CreateBankCodeDto) {}
|
||||
|
|
@ -1,51 +1,51 @@
|
|||
import { Injectable, NotFoundException } from "@nestjs/common";
|
||||
import { PrismaService } from "src/prisma/prisma.service";
|
||||
import { CreateBankCodeDto } from "../dtos/create-bank-code.dto";
|
||||
import { BankCodes } from "@prisma/client";
|
||||
import { UpdateBankCodeDto } from "../dtos/update-bank-code.dto";
|
||||
// import { Injectable, NotFoundException } from "@nestjs/common";
|
||||
// import { PrismaService } from "src/prisma/prisma.service";
|
||||
// import { CreateBankCodeDto } from "../dtos/create-bank-code.dto";
|
||||
// import { BankCodes } from "@prisma/client";
|
||||
// import { UpdateBankCodeDto } from "../dtos/update-bank-code.dto";
|
||||
|
||||
@Injectable()
|
||||
export class BankCodesService {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
// @Injectable()
|
||||
// export class BankCodesService {
|
||||
// constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
async create(dto: CreateBankCodeDto): Promise<BankCodes>{
|
||||
return this.prisma.bankCodes.create({
|
||||
data: {
|
||||
type: dto.type,
|
||||
categorie: dto.categorie,
|
||||
modifier: dto.modifier,
|
||||
bank_code: dto.bank_code,
|
||||
},
|
||||
});
|
||||
}
|
||||
// async create(dto: CreateBankCodeDto): Promise<BankCodes>{
|
||||
// return this.prisma.bankCodes.create({
|
||||
// data: {
|
||||
// type: dto.type,
|
||||
// categorie: dto.categorie,
|
||||
// modifier: dto.modifier,
|
||||
// bank_code: dto.bank_code,
|
||||
// },
|
||||
// });
|
||||
// }
|
||||
|
||||
findAll() {
|
||||
return this.prisma.bankCodes.findMany();
|
||||
}
|
||||
// findAll() {
|
||||
// return this.prisma.bankCodes.findMany();
|
||||
// }
|
||||
|
||||
async findOne(id: number) {
|
||||
const bankCode = await this.prisma.bankCodes.findUnique({ where: {id} });
|
||||
// async findOne(id: number) {
|
||||
// const bankCode = await this.prisma.bankCodes.findUnique({ where: {id} });
|
||||
|
||||
if(!bankCode) throw new NotFoundException(`Bank Code #${id} not found`);
|
||||
// if(!bankCode) throw new NotFoundException(`Bank Code #${id} not found`);
|
||||
|
||||
return bankCode;
|
||||
}
|
||||
// return bankCode;
|
||||
// }
|
||||
|
||||
async update(id:number, dto: UpdateBankCodeDto) {
|
||||
return await this.prisma.bankCodes.update({
|
||||
where: { id },
|
||||
data: {
|
||||
type: dto.type,
|
||||
categorie: dto.categorie,
|
||||
modifier: dto.modifier as any,
|
||||
bank_code: dto.bank_code,
|
||||
},
|
||||
});
|
||||
}
|
||||
// async update(id:number, dto: UpdateBankCodeDto) {
|
||||
// return await this.prisma.bankCodes.update({
|
||||
// where: { id },
|
||||
// data: {
|
||||
// type: dto.type,
|
||||
// categorie: dto.categorie,
|
||||
// modifier: dto.modifier as any,
|
||||
// bank_code: dto.bank_code,
|
||||
// },
|
||||
// });
|
||||
// }
|
||||
|
||||
async remove(id: number) {
|
||||
await this.findOne(id);
|
||||
return this.prisma.bankCodes.delete({ where: {id} });
|
||||
}
|
||||
// async remove(id: number) {
|
||||
// await this.findOne(id);
|
||||
// return this.prisma.bankCodes.delete({ where: {id} });
|
||||
// }
|
||||
|
||||
}
|
||||
// }
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
import { BadRequestException, Injectable, Logger } from "@nestjs/common";
|
||||
import { PrismaService } from "../../../prisma/prisma.service";
|
||||
|
||||
|
||||
//THIS SERVICE IS NOT USED, RULES TO BE DETERMINED WITH MIKE/HR/ACCOUNTING
|
||||
@Injectable()
|
||||
export class AfterHoursService {
|
||||
private readonly logger = new Logger(AfterHoursService.name);
|
||||
private static readonly BUSINESS_START = 7;
|
||||
private static readonly BUSINESS_END = 18;
|
||||
private static readonly ROUND_MINUTES = 15;
|
||||
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
|
||||
private getPreBusinessMinutes(start: Date, end: Date): number {
|
||||
const biz_start = new Date(start);
|
||||
biz_start.setHours(AfterHoursService.BUSINESS_START, 0,0,0);
|
||||
|
||||
if (end>= start || start >= biz_start) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const segment_end = end < biz_start ? end : biz_start;
|
||||
const minutes = (segment_end.getTime() - start.getTime()) / 60000;
|
||||
|
||||
this.logger.debug(`getPreBusinessMintutes -> ${minutes.toFixed(1)}min`);
|
||||
return minutes;
|
||||
|
||||
}
|
||||
|
||||
private getPostBusinessMinutes(start: Date, end: Date): number {
|
||||
const biz_end = new Date(start);
|
||||
biz_end.setHours(AfterHoursService.BUSINESS_END,0,0,0);
|
||||
|
||||
if( end <= biz_end ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const segment_start = start > biz_end ? start : biz_end;
|
||||
const minutes = (end.getTime() - segment_start.getTime()) / 60000;
|
||||
|
||||
this.logger.debug(`getPostBusinessMintutes -> ${minutes.toFixed(1)}min`);
|
||||
return minutes;
|
||||
|
||||
}
|
||||
|
||||
private roundToNearestQUarterMinute(minutes: number): number {
|
||||
const rounded = Math.round(minutes / AfterHoursService.ROUND_MINUTES)
|
||||
* AfterHoursService.ROUND_MINUTES;
|
||||
this.logger.debug(`roundToNearestQuarterMinute -> raw=${minutes.toFixed(1)}min, rounded= ${rounded}min`);
|
||||
return rounded;
|
||||
}
|
||||
|
||||
public computeAfterHours(start: Date, end:Date): number {
|
||||
if(end.getTime() <= start.getTime()) {
|
||||
throw new BadRequestException('The end cannot be before the starting of the shift');
|
||||
}
|
||||
|
||||
if (start.toDateString() !== end.toDateString()) {
|
||||
throw new BadRequestException('you cannot enter a shift that start in a day and end in the next' +
|
||||
'You must create 2 instances, one on the first day and the second during the next day.');
|
||||
}
|
||||
|
||||
const pre_min = this.getPreBusinessMinutes(start, end);
|
||||
const post_min = this.getPostBusinessMinutes(start, end);
|
||||
const raw_aftermin = pre_min + post_min;
|
||||
|
||||
const rounded_min = this.roundToNearestQUarterMinute(raw_aftermin);
|
||||
|
||||
const hours = rounded_min / 60;
|
||||
const result = parseFloat(hours.toFixed(2));
|
||||
|
||||
this.logger.debug(`computeAfterHours -> raw_aftermin = ${raw_aftermin.toFixed(1)}min, +
|
||||
rounded = ${rounded_min}min, hours = ${result.toFixed(2)}`);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,247 +0,0 @@
|
|||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { PrismaService } from '../../../prisma/prisma.service';
|
||||
import { getWeekStart, getWeekEnd, computeHours } from 'src/common/utils/date-utils';
|
||||
import { Prisma, PrismaClient } from '@prisma/client';
|
||||
|
||||
type Tx = Prisma.TransactionClient | PrismaClient;
|
||||
|
||||
export type WeekOvertimeSummary = {
|
||||
week_start:string;
|
||||
week_end: string;
|
||||
week_total_hours: number;
|
||||
weekly_overtime: number;
|
||||
daily_overtime_kept: number;
|
||||
total_overtime: number;
|
||||
breakdown: Array<{
|
||||
date:string;
|
||||
day_hours: number;
|
||||
day_overtime: number;
|
||||
daily_kept: number;
|
||||
running_total_before: number;
|
||||
}>;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class OvertimeService {
|
||||
|
||||
private logger = new Logger(OvertimeService.name);
|
||||
private daily_max = 8; // maximum for regular hours per day
|
||||
private weekly_max = 40; // maximum for regular hours per week
|
||||
private INCLUDED_TYPES = ['EMERGENCY', 'EVENING','OVERTIME','REGULAR'] as const; // included types for weekly overtime calculation
|
||||
|
||||
constructor(private prisma: PrismaService) {}
|
||||
|
||||
async getWeekOvertimeSummary( timesheet_id: number, date: Date, tx?: Tx ): Promise<WeekOvertimeSummary>{
|
||||
const db = tx ?? this.prisma;
|
||||
|
||||
const week_start = getWeekStart(date);
|
||||
const week_end = getWeekEnd(week_start);
|
||||
|
||||
const shifts = await db.shifts.findMany({
|
||||
where: {
|
||||
timesheet_id,
|
||||
date: { gte: week_start, lte: week_end },
|
||||
bank_code: { type: { in: this.INCLUDED_TYPES as unknown as string[] } },
|
||||
},
|
||||
select: { date: true, start_time: true, end_time: true },
|
||||
orderBy: [{date: 'asc'}, {start_time: 'asc'}],
|
||||
});
|
||||
|
||||
const day_totals = new Map<string, number>();
|
||||
for (const shift of shifts){
|
||||
const key = shift.date.toISOString().slice(0,10);
|
||||
const hours = computeHours(shift.start_time, shift.end_time, 5);
|
||||
day_totals.set(key, (day_totals.get(key) ?? 0) + hours);
|
||||
}
|
||||
|
||||
const days: string[] = [];
|
||||
for(let i = 0; i < 7; i++){
|
||||
const day = new Date(week_start.getTime() + i * 24 * 60 * 60 * 1000);
|
||||
days.push(day.toISOString().slice(0,10));
|
||||
}
|
||||
|
||||
const week_total_hours = [ ...day_totals.values()].reduce((a,b) => a + b, 0);
|
||||
const weekly_overtime = Math.max(0, week_total_hours - this.weekly_max);
|
||||
|
||||
let running = 0;
|
||||
let daily_kept_sum = 0;
|
||||
const breakdown: WeekOvertimeSummary['breakdown'] = [];
|
||||
|
||||
for (const key of days) {
|
||||
const day_hours = day_totals.get(key) ?? 0;
|
||||
const day_overtime = Math.max(0, day_hours - this.daily_max);
|
||||
|
||||
const cap_before_40 = Math.max(0, this.weekly_max - running);
|
||||
const daily_kept = Math.min(day_overtime, cap_before_40);
|
||||
|
||||
breakdown.push({
|
||||
date: key,
|
||||
day_hours,
|
||||
day_overtime,
|
||||
daily_kept,
|
||||
running_total_before: running,
|
||||
});
|
||||
|
||||
daily_kept_sum += daily_kept;
|
||||
running += day_hours;
|
||||
}
|
||||
const total_overtime = weekly_overtime + daily_kept_sum;
|
||||
|
||||
this.logger.debug(
|
||||
`[OVERTIME][SUMMARY][ts=${timesheet_id}] week=${week_start.toISOString().slice(0,10)}..${week_end
|
||||
.toISOString()
|
||||
.slice(0,10)} week_total=${week_total_hours.toFixed(2)}h weekly=${weekly_overtime.toFixed(
|
||||
2,
|
||||
)}h daily_kept=${daily_kept_sum.toFixed(2)}h total=${total_overtime.toFixed(2)}h`,
|
||||
);
|
||||
return {
|
||||
week_start: week_start.toISOString().slice(0, 10),
|
||||
week_end: week_end.toISOString().slice(0, 10),
|
||||
week_total_hours,
|
||||
weekly_overtime,
|
||||
daily_overtime_kept: daily_kept_sum,
|
||||
total_overtime,
|
||||
breakdown,
|
||||
};
|
||||
}
|
||||
|
||||
// //calculate daily overtime
|
||||
// async getDailyOvertimeHours(timesheet_id: number, date: Date): Promise<number> {
|
||||
// const shifts = await this.prisma.shifts.findMany({
|
||||
// where: {
|
||||
// timesheet_id,
|
||||
// date: date,
|
||||
// bank_code: { type: { in: this.INCLUDED_TYPES as unknown as string[] } },
|
||||
// },
|
||||
// select: { start_time: true, end_time: true },
|
||||
// orderBy: [{ start_time: 'asc' }],
|
||||
// });
|
||||
|
||||
// const total = shifts.map((shift)=>
|
||||
// computeHours(shift.start_time, shift.end_time, 5)).
|
||||
// reduce((sum, hours)=> sum + hours, 0);
|
||||
|
||||
// const overtime = Math.max(0, total - this.daily_max);
|
||||
|
||||
// this.logger.debug(`[OVERTIME]-[DAILY] total=${total.toFixed(2)}h, overtime= ${overtime.toFixed(2)}h`);
|
||||
// return overtime;
|
||||
// }
|
||||
|
||||
// //calculate Weekly overtime
|
||||
// async getWeeklyOvertimeHours(timesheet_id: number, ref_date: Date): Promise<number> {
|
||||
// const week_start = getWeekStart(ref_date);
|
||||
// const week_end = getWeekEnd(week_start);
|
||||
|
||||
// //fetches all shifts from INCLUDED_TYPES array
|
||||
// const included_shifts = await this.prisma.shifts.findMany({
|
||||
// where: {
|
||||
// timesheet_id,
|
||||
// date: { gte:week_start, lte: week_end },
|
||||
// bank_code: { type: { in: this.INCLUDED_TYPES as unknown as string[] } },
|
||||
// },
|
||||
// select: { start_time: true, end_time: true },
|
||||
// orderBy: [{date: 'asc'}, {start_time:'asc'}],
|
||||
// });
|
||||
|
||||
// //calculate total hours of those shifts minus weekly Max to find total overtime hours
|
||||
// const total = included_shifts.map(shift =>
|
||||
// computeHours(shift.start_time, shift.end_time, 5)).
|
||||
// reduce((sum, hours)=> sum+hours, 0);
|
||||
|
||||
// const overtime = Math.max(0, total - this.weekly_max);
|
||||
|
||||
// this.logger.debug(`[OVERTIME]-[WEEKLY] total=${total.toFixed(2)}h, overtime= ${overtime.toFixed(2)}h`);
|
||||
// return overtime;
|
||||
// }
|
||||
|
||||
|
||||
// //transform REGULAR shifts to OVERTIME when exceed 40hrs of included_types of shift
|
||||
// async transformRegularHoursToWeeklyOvertime(
|
||||
// employee_id: number,
|
||||
// ref_date: Date,
|
||||
// tx?: Prisma.TransactionClient,
|
||||
// ): Promise<void> {
|
||||
// //ensures the use of the transaction if needed. fallback to this.prisma if no transaction is detected.
|
||||
// const db = tx ?? this.prisma;
|
||||
|
||||
// //calculate weekly overtime
|
||||
// const overtime_hours = await this.getWeeklyOvertimeHours(employee_id, ref_date);
|
||||
// if(overtime_hours <= 0) return;
|
||||
|
||||
// const convert_to_minutes = Math.round(overtime_hours * 60);
|
||||
|
||||
// const [regular, overtime] = await Promise.all([
|
||||
// db.bankCodes.findFirst({where: { type: 'REGULAR' }, select: { id: true } }),
|
||||
// db.bankCodes.findFirst({where: { type: 'OVERTIME'}, select: { id: true } }),
|
||||
// ]);
|
||||
// if(!regular || !overtime) return;
|
||||
|
||||
// const week_start = getWeekStart(ref_date);
|
||||
// const week_end = getWeekEnd(week_start);
|
||||
|
||||
// //gets all regular shifts and order them by desc
|
||||
// const regular_shifts_desc = await db.shifts.findMany({
|
||||
// where: {
|
||||
// date: { gte:week_start, lte: week_end },
|
||||
// timesheet: { employee_id },
|
||||
// bank_code_id: regular.id,
|
||||
// },
|
||||
// select: {
|
||||
// id: true,
|
||||
// timesheet_id: true,
|
||||
// date: true,
|
||||
// start_time: true,
|
||||
// end_time: true,
|
||||
// is_remote: true,
|
||||
// comment: true,
|
||||
// },
|
||||
// orderBy: [{date: 'desc'}, {start_time:'desc'}],
|
||||
// });
|
||||
|
||||
// let remaining_minutes = convert_to_minutes;
|
||||
|
||||
// for(const shift of regular_shifts_desc) {
|
||||
// if(remaining_minutes <= 0) break;
|
||||
|
||||
// const start = shift.start_time;
|
||||
// const end = shift.end_time;
|
||||
// const duration_in_minutes = Math.max(0, Math.round((end.getTime() - start.getTime())/60000));
|
||||
// if(duration_in_minutes === 0) continue;
|
||||
|
||||
// if(duration_in_minutes <= remaining_minutes) {
|
||||
// await db.shifts.update({
|
||||
// where: { id: shift.id },
|
||||
// data: { bank_code_id: overtime.id },
|
||||
// });
|
||||
// remaining_minutes -= duration_in_minutes;
|
||||
// continue;
|
||||
// }
|
||||
// //sets the start_time of the new overtime shift
|
||||
// const new_overtime_start = new Date(end.getTime() - remaining_minutes * 60000);
|
||||
|
||||
// //shorten the regular shift
|
||||
// await db.shifts.update({
|
||||
// where: { id: shift.id },
|
||||
// data: { end_time: new_overtime_start },
|
||||
// });
|
||||
|
||||
// //creates the new overtime shift to replace the shorten regular shift
|
||||
// await db.shifts.create({
|
||||
// data: {
|
||||
// timesheet_id: shift.timesheet_id,
|
||||
// date: shift.date,
|
||||
// start_time: new_overtime_start,
|
||||
// end_time: end,
|
||||
// is_remote: shift.is_remote,
|
||||
// comment: shift.comment,
|
||||
// bank_code_id: overtime.id,
|
||||
// },
|
||||
// });
|
||||
// remaining_minutes = 0;
|
||||
// }
|
||||
// this.logger.debug(`[OVERTIME]-[WEEKLY]-[TRANSFORM] emp=${employee_id}
|
||||
// week: ${week_start.toISOString().slice(0,10)}..${week_end.toISOString().slice(0,10)}
|
||||
// converted= ${(convert_to_minutes-remaining_minutes)/60}h`);
|
||||
// }
|
||||
|
||||
}
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post, UseGuards } from '@nestjs/common';
|
||||
import { CustomersService } from '../services/customers.service';
|
||||
import { Customers } from '@prisma/client';
|
||||
import { CreateCustomerDto } from '../dtos/create-customer.dto';
|
||||
import { UpdateCustomerDto } from '../dtos/update-customer.dto';
|
||||
import { RolesAllowed } from "src/common/decorators/roles.decorators";
|
||||
import { Roles as RoleEnum } from '.prisma/client';
|
||||
import { ApiBearerAuth, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
|
||||
|
||||
@ApiTags('Customers')
|
||||
@ApiBearerAuth('access-token')
|
||||
// @UseGuards()
|
||||
@Controller('customers')
|
||||
export class CustomersController {
|
||||
constructor(private readonly customersService: CustomersService) {}
|
||||
|
||||
//_____________________________________________________________________________________________
|
||||
// Deprecated or unused methods
|
||||
//_____________________________________________________________________________________________
|
||||
|
||||
// @Post()
|
||||
// //@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.SUPERVISOR)
|
||||
// @ApiOperation({ summary: 'Create customer' })
|
||||
// @ApiResponse({ status: 201, description: 'Customer created', type: CreateCustomerDto })
|
||||
// @ApiResponse({ status: 400, description: 'Invalid task or invalid data' })
|
||||
// create(@Body() dto: CreateCustomerDto): Promise<Customers> {
|
||||
// return this.customersService.create(dto);
|
||||
// }
|
||||
|
||||
// @Get()
|
||||
// //@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||
// @ApiOperation({ summary: 'Find all customers' })
|
||||
// @ApiResponse({ status: 201, description: 'List of customers found', type: CreateCustomerDto, isArray: true })
|
||||
// @ApiResponse({ status: 400, description: 'List of customers not found' })
|
||||
// findAll(): Promise<Customers[]> {
|
||||
// return this.customersService.findAll();
|
||||
// }
|
||||
|
||||
// @Get(':id')
|
||||
// //@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||
// @ApiOperation({ summary: 'Find customer' })
|
||||
// @ApiResponse({ status: 201, description: 'Customer found', type: CreateCustomerDto })
|
||||
// @ApiResponse({ status: 400, description: 'Customer not found' })
|
||||
// findOne(@Param('id', ParseIntPipe) id: number): Promise<Customers> {
|
||||
// return this.customersService.findOne(id);
|
||||
// }
|
||||
|
||||
// @Patch(':id')
|
||||
// //@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.EMPLOYEE,RoleEnum.SUPERVISOR)
|
||||
// @ApiOperation({ summary: 'Update customer' })
|
||||
// @ApiResponse({ status: 201, description: 'Customer updated', type: CreateCustomerDto })
|
||||
// @ApiResponse({ status: 400, description: 'Customer not found' })
|
||||
// update(
|
||||
// @Param('id', ParseIntPipe) id: number,
|
||||
// @Body() dto: UpdateCustomerDto,
|
||||
// ): Promise<Customers> {
|
||||
// return this.customersService.update(id, dto);
|
||||
// }
|
||||
|
||||
// @Delete(':id')
|
||||
// //@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.SUPERVISOR)
|
||||
// @ApiOperation({ summary: 'Delete customer' })
|
||||
// @ApiResponse({ status: 201, description: 'Customer deleted', type: CreateCustomerDto })
|
||||
// @ApiResponse({ status: 400, description: 'Customer not found' })
|
||||
// remove(@Param('id', ParseIntPipe) id: number): Promise<Customers>{
|
||||
// return this.customersService.remove(id);
|
||||
// }
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
|
||||
import { Module } from '@nestjs/common';
|
||||
import { CustomersController } from './controllers/customers.controller';
|
||||
import { CustomersService } from './services/customers.service';
|
||||
|
||||
@Module({
|
||||
controllers:[CustomersController],
|
||||
providers:[CustomersService],
|
||||
})
|
||||
export class CustomersModule {}
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { Type } from "class-transformer";
|
||||
import {
|
||||
Allow,
|
||||
IsEmail,
|
||||
IsInt,
|
||||
IsNotEmpty,
|
||||
IsOptional,
|
||||
IsPositive,
|
||||
IsString,
|
||||
IsUUID,
|
||||
} from "class-validator";
|
||||
|
||||
export class CreateCustomerDto {
|
||||
@ApiProperty({
|
||||
example: 1,
|
||||
description: 'Unique ID of a customer(primary-key, auto-incremented)',
|
||||
})
|
||||
@Allow()
|
||||
id?: number;
|
||||
|
||||
@ApiProperty({
|
||||
example: '0e6e2e1f-b157-4c7c-ae3f-999b3e4f914d',
|
||||
description: 'UUID of the user linked to that customer',
|
||||
})
|
||||
@IsUUID()
|
||||
@IsOptional()
|
||||
user_id?: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: 'Gandalf',
|
||||
description: 'Customer`s first name',
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
first_name: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: 'TheGray',
|
||||
description: 'Customer`s last name',
|
||||
})
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
last_name: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: 'you_shall_not_pass@middleEarth.com',
|
||||
description: 'Customer`s email',
|
||||
})
|
||||
@IsEmail()
|
||||
@IsOptional()
|
||||
email: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: '8436637464',
|
||||
description: 'Customer`s phone number',
|
||||
})
|
||||
@IsString()
|
||||
phone_number: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: '1 Ringbearer`s way, Mount Doom city, ME, T1R 1N6 ',
|
||||
description: 'Customer`s residence',
|
||||
required: false,
|
||||
})
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
residence?: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: '4263253',
|
||||
description: 'Customer`s invoice number',
|
||||
required: false,
|
||||
})
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
@IsNotEmpty()
|
||||
invoice_id: number;
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
import { PartialType } from "@nestjs/swagger";
|
||||
import { CreateCustomerDto } from "./create-customer.dto";
|
||||
|
||||
export class UpdateCustomerDto extends PartialType(CreateCustomerDto) {}
|
||||
|
|
@ -1,93 +0,0 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class CustomersService {
|
||||
|
||||
//_____________________________________________________________________________________________
|
||||
// Deprecated or unused methods
|
||||
//_____________________________________________________________________________________________
|
||||
|
||||
// constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
// async create(dto: CreateCustomerDto): Promise<Customers> {
|
||||
// const {
|
||||
// first_name,
|
||||
// last_name,
|
||||
// email,
|
||||
// phone_number,
|
||||
// residence,
|
||||
// invoice_id,
|
||||
// } = dto;
|
||||
|
||||
// return this.prisma.$transaction(async (transaction) => {
|
||||
// const user: Users = await transaction.users.create({
|
||||
// data: {
|
||||
// first_name,
|
||||
// last_name,
|
||||
// email,
|
||||
// phone_number,
|
||||
// residence,
|
||||
// },
|
||||
// });
|
||||
// return transaction.customers.create({
|
||||
// data: {
|
||||
// user_id: user.id,
|
||||
// invoice_id,
|
||||
// },
|
||||
// });
|
||||
// });
|
||||
// }
|
||||
|
||||
// findAll(): Promise<Customers[]> {
|
||||
// return this.prisma.customers.findMany({
|
||||
// include: { user: true },
|
||||
// })
|
||||
// }
|
||||
|
||||
// async findOne(id:number): Promise<Customers> {
|
||||
// const customer = await this.prisma.customers.findUnique({
|
||||
// where: { id },
|
||||
// include: { user: true },
|
||||
// });
|
||||
// if(!customer) throw new NotFoundException(`Customer #${id} not found`);
|
||||
// return customer;
|
||||
// }
|
||||
|
||||
// async update(id: number,dto: UpdateCustomerDto): Promise<Customers> {
|
||||
// const customer = await this.findOne(id);
|
||||
|
||||
// const {
|
||||
// first_name,
|
||||
// last_name,
|
||||
// email,
|
||||
// phone_number,
|
||||
// residence,
|
||||
// invoice_id,
|
||||
// } = dto;
|
||||
|
||||
// return this.prisma.$transaction(async (transaction) => {
|
||||
// await transaction.users.update({
|
||||
// where: { id: customer.user_id },
|
||||
// data: {
|
||||
// ...(first_name !== undefined && { first_name }),
|
||||
// ...(last_name !== undefined && { last_name }),
|
||||
// ...(email !== undefined && { email }),
|
||||
// ...(phone_number !== undefined && { phone_number }),
|
||||
// ...(residence !== undefined && { residence }),
|
||||
// },
|
||||
// });
|
||||
|
||||
// return transaction.customers.update({
|
||||
// where: { id },
|
||||
// data: {
|
||||
// ...(invoice_id !== undefined && { invoice_id }),
|
||||
// },
|
||||
// });
|
||||
// });
|
||||
// }
|
||||
|
||||
// async remove(id: number): Promise<Customers> {
|
||||
// await this.findOne(id);
|
||||
// return this.prisma.customers.delete({ where: { id }});
|
||||
// }
|
||||
}
|
||||
|
|
@ -1,99 +0,0 @@
|
|||
import { Body,Controller,Get,NotFoundException,Param,Patch } from '@nestjs/common';
|
||||
import { EmployeesService } from '../services/employees.service';
|
||||
import { CreateEmployeeDto } from '../dtos/create-employee.dto';
|
||||
import { UpdateEmployeeDto } from '../dtos/update-employee.dto';
|
||||
import { RolesAllowed } from '../../../common/decorators/roles.decorators';
|
||||
import { ApiBearerAuth, ApiOperation, ApiParam, ApiResponse, ApiTags } from '@nestjs/swagger';
|
||||
import { EmployeeListItemDto } from '../dtos/list-employee.dto';
|
||||
import { EmployeesArchivalService } from '../services/employees-archival.service';
|
||||
import { EmployeeProfileItemDto } from 'src/modules/employees/dtos/profil-employee.dto';
|
||||
|
||||
@ApiTags('Employees')
|
||||
@ApiBearerAuth('access-token')
|
||||
// @UseGuards()
|
||||
@Controller('employees')
|
||||
export class EmployeesController {
|
||||
constructor(
|
||||
private readonly employeesService: EmployeesService,
|
||||
private readonly archiveService: EmployeesArchivalService,
|
||||
) {}
|
||||
|
||||
@Get('employee-list')
|
||||
//@RolesAllowed(RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR, RoleEnum.ACCOUNTING)
|
||||
@ApiOperation({summary: 'Find all employees with scoped info' })
|
||||
@ApiResponse({ status: 200, description: 'List of employees with scoped info found', type: EmployeeListItemDto, isArray: true })
|
||||
@ApiResponse({ status: 400, description: 'List of employees with scoped info not found' })
|
||||
findListEmployees(): Promise<EmployeeListItemDto[]> {
|
||||
return this.employeesService.findListEmployees();
|
||||
}
|
||||
|
||||
@Patch(':email')
|
||||
//@RolesAllowed(RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||
@ApiBearerAuth('access-token')
|
||||
@ApiOperation({ summary: 'Update, archive or restore an employee' })
|
||||
@ApiParam({ name: 'email', type: Number, description: 'Email of the employee' })
|
||||
@ApiResponse({ status: 200, description: 'Employee updated or restored', type: CreateEmployeeDto })
|
||||
@ApiResponse({ status: 202, description: 'Employee archived successfully', type: CreateEmployeeDto })
|
||||
@ApiResponse({ status: 404, description: 'Employee not found in active or archive' })
|
||||
async updateOrArchiveOrRestore(@Param('email') email: string, @Body() dto: UpdateEmployeeDto,) {
|
||||
// if last_work_day is set => archive the employee
|
||||
// else if employee is archived and first_work_day or last_work_day = null => restore
|
||||
//otherwise => standard update
|
||||
const result = await this.archiveService.patchEmployee(email, dto);
|
||||
if(!result) {
|
||||
throw new NotFoundException(`Employee with email: ${ email } is not found in active or archive.`)
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
//_____________________________________________________________________________________________
|
||||
// Deprecated or unused methods
|
||||
//_____________________________________________________________________________________________
|
||||
|
||||
// @Post()
|
||||
// //@RolesAllowed(RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||
// @ApiOperation({summary: 'Create employee' })
|
||||
// @ApiResponse({ status: 201, description: 'Employee created', type: CreateEmployeeDto })
|
||||
// @ApiResponse({ status: 400, description: 'Incomplete task or invalid data' })
|
||||
// create(@Body() dto: CreateEmployeeDto): Promise<Employees> {
|
||||
// return this.employeesService.create(dto);
|
||||
// }
|
||||
// @Get()
|
||||
// //@RolesAllowed(RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR, RoleEnum.ACCOUNTING)
|
||||
// @ApiOperation({summary: 'Find all employees' })
|
||||
// @ApiResponse({ status: 200, description: 'List of employees found', type: CreateEmployeeDto, isArray: true })
|
||||
// @ApiResponse({ status: 400, description: 'List of employees not found' })
|
||||
// findAll(): Promise<Employees[]> {
|
||||
// return this.employeesService.findAll();
|
||||
// }
|
||||
|
||||
|
||||
// @Get(':email')
|
||||
// //@RolesAllowed(RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR,RoleEnum.ACCOUNTING )
|
||||
// @ApiOperation({summary: 'Find employee' })
|
||||
// @ApiResponse({ status: 200, description: 'Employee found', type: CreateEmployeeDto })
|
||||
// @ApiResponse({ status: 400, description: 'Employee not found' })
|
||||
// findOne(@Param('email', ParseIntPipe) email: string): Promise<Employees> {
|
||||
// return this.employeesService.findOne(email);
|
||||
// }
|
||||
|
||||
@Get('profile/:email')
|
||||
@ApiOperation({summary: 'Find employee profile' })
|
||||
@ApiParam({ name: 'email', type: String, description: 'Identifier of the employee' })
|
||||
@ApiResponse({ status: 200, description: 'Employee profile found', type: EmployeeProfileItemDto })
|
||||
@ApiResponse({ status: 400, description: 'Employee profile not found' })
|
||||
findOneProfile(@Param('email') email: string): Promise<EmployeeProfileItemDto> {
|
||||
return this.employeesService.findOneProfile(email);
|
||||
}
|
||||
|
||||
// @Delete(':email')
|
||||
// //@RolesAllowed(RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR )
|
||||
// @ApiOperation({summary: 'Delete employee' })
|
||||
// @ApiParam({ name: 'email', type: Number, description: 'Email of the employee to delete' })
|
||||
// @ApiResponse({ status: 204, description: 'Employee deleted' })
|
||||
// @ApiResponse({ status: 404, description: 'Employee not found' })
|
||||
// remove(@Param('email', ParseIntPipe) email: string): Promise<Employees> {
|
||||
// return this.employeesService.remove(email);
|
||||
// }
|
||||
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { EmployeesController } from './controllers/employees.controller';
|
||||
import { EmployeesService } from './services/employees.service';
|
||||
import { EmployeesArchivalService } from './services/employees-archival.service';
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
|
||||
@Module({
|
||||
imports: [SharedModule],
|
||||
controllers: [EmployeesController],
|
||||
providers: [EmployeesService, EmployeesArchivalService],
|
||||
exports: [EmployeesService, EmployeesArchivalService],
|
||||
})
|
||||
export class EmployeesModule {}
|
||||
|
|
@ -1,173 +0,0 @@
|
|||
import { Injectable } from "@nestjs/common";
|
||||
import { Employees, EmployeesArchive, Users } from "@prisma/client";
|
||||
import { PrismaService } from "src/prisma/prisma.service";
|
||||
import { UpdateEmployeeDto } from "../dtos/update-employee.dto";
|
||||
import { toDateOrUndefined, toDateOrNull } from "../utils/employee.utils";
|
||||
|
||||
@Injectable()
|
||||
export class EmployeesArchivalService {
|
||||
constructor(private readonly prisma: PrismaService) { }
|
||||
|
||||
async patchEmployee(email: string, dto: UpdateEmployeeDto): Promise<Employees | EmployeesArchive | null> {
|
||||
// 1) Tenter sur employés actifs
|
||||
const active = await this.prisma.employees.findFirst({
|
||||
where: { user: { email } },
|
||||
include: { user: true },
|
||||
});
|
||||
|
||||
if (active) {
|
||||
// Archivage : si on reçoit un last_work_day défini et que l'employé n’est pas déjà terminé
|
||||
if (dto.last_work_day !== undefined && active.last_work_day == null && dto.last_work_day !== null) {
|
||||
return this.archiveOnTermination(active, dto);
|
||||
}
|
||||
|
||||
// Sinon, update standard (split Users/Employees)
|
||||
const {
|
||||
first_name,
|
||||
last_name,
|
||||
email: new_email,
|
||||
phone_number,
|
||||
residence,
|
||||
external_payroll_id,
|
||||
company_code,
|
||||
job_title,
|
||||
first_work_day,
|
||||
last_work_day,
|
||||
supervisor_id,
|
||||
is_supervisor,
|
||||
} = dto as any;
|
||||
|
||||
const first_work_d = toDateOrUndefined(first_work_day);
|
||||
const last_work_d = Object.prototype.hasOwnProperty('last_work_day')
|
||||
? toDateOrNull(last_work_day ?? null)
|
||||
: undefined;
|
||||
|
||||
await this.prisma.$transaction(async (transaction) => {
|
||||
if (
|
||||
first_name !== undefined ||
|
||||
last_name !== undefined ||
|
||||
new_email !== undefined ||
|
||||
phone_number !== undefined ||
|
||||
residence !== undefined
|
||||
) {
|
||||
await transaction.users.update({
|
||||
where: { id: active.user_id },
|
||||
data: {
|
||||
...(first_name !== undefined ? { first_name } : {}),
|
||||
...(last_name !== undefined ? { last_name } : {}),
|
||||
...(email !== undefined ? { email: new_email } : {}),
|
||||
...(phone_number !== undefined ? { phone_number } : {}),
|
||||
...(residence !== undefined ? { residence } : {}),
|
||||
},
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
const updated = await transaction.employees.update({
|
||||
where: { id: active.id },
|
||||
data: {
|
||||
...(external_payroll_id !== undefined ? { external_payroll_id } : {}),
|
||||
...(company_code !== undefined ? { company_code } : {}),
|
||||
...(job_title !== undefined ? { job_title } : {}),
|
||||
...(first_work_d !== undefined ? { first_work_day: first_work_d } : {}),
|
||||
...(last_work_d !== undefined ? { last_work_day: last_work_d } : {}),
|
||||
...(is_supervisor !== undefined ? { is_supervisor } : {}),
|
||||
...(supervisor_id !== undefined ? { supervisor_id } : {}),
|
||||
},
|
||||
include: { user: true },
|
||||
});
|
||||
|
||||
return updated;
|
||||
});
|
||||
|
||||
return this.prisma.employees.findFirst({ where: { user: { email } } });
|
||||
}
|
||||
|
||||
const user = await this.prisma.users.findUnique({ where: { email } });
|
||||
if (!user) return null;
|
||||
// 2) Pas trouvé en actifs → regarder en archive (pour restauration)
|
||||
const archived = await this.prisma.employeesArchive.findFirst({
|
||||
where: { user_id: user.id },
|
||||
include: { user: true },
|
||||
});
|
||||
|
||||
if (archived) {
|
||||
// Condition de restauration : last_work_day === null ou first_work_day fourni
|
||||
const restore = dto.last_work_day === null || dto.first_work_day != null;
|
||||
if (restore) {
|
||||
return this.restoreEmployee(archived, dto);
|
||||
}
|
||||
}
|
||||
// 3) Ni actif, ni archivé → 404 dans le controller
|
||||
return null;
|
||||
}
|
||||
|
||||
//transfers the employee to archive and then delete from employees table
|
||||
private async archiveOnTermination(active: Employees & { user: Users }, dto: UpdateEmployeeDto): Promise<EmployeesArchive> {
|
||||
const last_work_d = toDateOrNull(dto.last_work_day!);
|
||||
if (!last_work_d) throw new Error('invalide last_work_day for archive');
|
||||
return this.prisma.$transaction(async transaction => {
|
||||
//detach crew from supervisor if employee is a supervisor
|
||||
await transaction.employees.updateMany({
|
||||
where: { supervisor_id: active.id },
|
||||
data: { supervisor_id: null },
|
||||
})
|
||||
const archived = await transaction.employeesArchive.create({
|
||||
data: {
|
||||
employee_id: active.id,
|
||||
user_id: active.user_id,
|
||||
first_name: active.user.first_name,
|
||||
last_name: active.user.last_name,
|
||||
company_code: active.company_code,
|
||||
job_title: active.job_title,
|
||||
first_work_day: active.first_work_day,
|
||||
last_work_day: last_work_d,
|
||||
supervisor_id: active.supervisor_id ?? null,
|
||||
is_supervisor: active.is_supervisor,
|
||||
external_payroll_id: active.external_payroll_id,
|
||||
},
|
||||
include: { user: true }
|
||||
});
|
||||
//delete from employees table
|
||||
await transaction.employees.delete({ where: { id: active.id } });
|
||||
//return archived employee
|
||||
return archived
|
||||
});
|
||||
}
|
||||
|
||||
//transfers the employee from archive to the employees table
|
||||
private async restoreEmployee(archived: EmployeesArchive & { user: Users }, dto: UpdateEmployeeDto): Promise<Employees> {
|
||||
// const first_work_d = toDateOrUndefined(dto.first_work_day);
|
||||
return this.prisma.$transaction(async transaction => {
|
||||
//restores the archived employee into the employees table
|
||||
const restored = await transaction.employees.create({
|
||||
data: {
|
||||
user_id: archived.user_id,
|
||||
company_code: archived.company_code,
|
||||
job_title: archived.job_title,
|
||||
first_work_day: archived.first_work_day,
|
||||
last_work_day: null,
|
||||
is_supervisor: archived.is_supervisor ?? false,
|
||||
external_payroll_id: archived.external_payroll_id,
|
||||
},
|
||||
});
|
||||
//deleting archived entry by id
|
||||
await transaction.employeesArchive.delete({ where: { id: archived.id } });
|
||||
|
||||
//return restored employee
|
||||
return restored;
|
||||
});
|
||||
}
|
||||
|
||||
//fetches all archived employees
|
||||
async findAllArchived(): Promise<EmployeesArchive[]> {
|
||||
return this.prisma.employeesArchive.findMany();
|
||||
}
|
||||
|
||||
//fetches an archived employee
|
||||
async findOneArchived(id: number): Promise<EmployeesArchive> {
|
||||
return this.prisma.employeesArchive.findUniqueOrThrow({ where: { id } });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
import { Body, Controller, Param, ParseIntPipe, Post } from "@nestjs/common";
|
||||
import { PrismaService } from "src/prisma/prisma.service";
|
||||
import { ExpenseDto } from "../dtos/expense.dto";
|
||||
import { CreateResult, ExpenseUpsertService } from "../services/expense-upsert.service";
|
||||
|
||||
|
||||
@Controller('expense')
|
||||
export class ExpenseController {
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly upsert_service: ExpenseUpsertService,
|
||||
){}
|
||||
|
||||
|
||||
// @Post(':timesheet_id')
|
||||
// create(
|
||||
// @Param('timesheet_id', ParseIntPipe) timesheet_id: number,
|
||||
// @Body() dto: ExpenseDto): Promise<CreateResult>{
|
||||
// return this.upsert_service.createExpense(timesheet_id, dto);
|
||||
// }
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
import { OmitType, PartialType } from "@nestjs/swagger";
|
||||
import { ExpenseDto } from "./expense.dto";
|
||||
|
||||
export class updateExpenseDto extends PartialType (
|
||||
OmitType(ExpenseDto, ['is_approved', 'timesheet_id'] as const)
|
||||
){}
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
// import { ExpensesController } from "./controllers/expenses.controller";
|
||||
// import { Module } from "@nestjs/common";
|
||||
// import { ExpensesQueryService } from "./services/expenses-query.service";
|
||||
// import { BusinessLogicsModule } from "src/modules/business-logics/business-logics.module";
|
||||
// import { ExpensesCommandService } from "./services/expenses-command.service";
|
||||
// import { ExpensesArchivalService } from "./services/expenses-archival.service";
|
||||
// import { SharedModule } from "../shared/shared.module";
|
||||
|
||||
// @Module({
|
||||
// imports: [BusinessLogicsModule, SharedModule],
|
||||
// controllers: [ExpensesController],
|
||||
// providers: [
|
||||
// ExpensesQueryService,
|
||||
// ExpensesArchivalService,
|
||||
// ExpensesCommandService,
|
||||
// ],
|
||||
// exports: [
|
||||
// ExpensesQueryService,
|
||||
// ExpensesArchivalService,
|
||||
// ],
|
||||
// })
|
||||
|
||||
// export class ExpensesModule {}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
export const toDateFromString = (ymd: string): Date => {
|
||||
return new Date(`${ymd}T00:00:00:000Z`);
|
||||
}
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
import { Injectable } from "@nestjs/common";
|
||||
import { PrismaService } from "src/prisma/prisma.service";
|
||||
import { GetExpenseDto } from "../dtos/get-expense.dto";
|
||||
import { updateExpenseDto } from "../dtos/update-expense.dto";
|
||||
import { ExpenseDto } from "../dtos/expense.dto";
|
||||
import { toDateFromString } from "../helpers/expenses-date-time-helpers";
|
||||
|
||||
type Normalized = { date: Date; comment: string; supervisor_comment: string; };
|
||||
|
||||
export type CreateResult = { ok: true; data: GetExpenseDto } | { ok: false; error: any };
|
||||
export type UpdatePayload = { id: number; dto: updateExpenseDto };
|
||||
export type UpdateResult = { ok: true; id: number; data: GetExpenseDto } | { ok: false; id: number; error: any };
|
||||
export type DeleteResult = { ok: true; id: number; } | { ok: false; id: number; error: any };
|
||||
|
||||
type NormedOk = { dto: GetExpenseDto; normed: Normalized };
|
||||
type NormedErr = { error: any };
|
||||
|
||||
@Injectable()
|
||||
export class ExpenseUpsertService {
|
||||
constructor(private readonly prisma: PrismaService){}
|
||||
|
||||
//_________________________________________________________________
|
||||
// CREATE
|
||||
//_________________________________________________________________
|
||||
//normalized frontend data to match DB
|
||||
async createExpense(timesheet_id: number, dto: ExpenseDto){
|
||||
const normed_expense = this.normalizeExpenseDto(dto)
|
||||
|
||||
|
||||
}
|
||||
|
||||
//_________________________________________________________________
|
||||
// LOCAL HELPERS
|
||||
//_________________________________________________________________
|
||||
private normalizeExpenseDto(dto: ExpenseDto): Normalized {
|
||||
const date = toDateFromString(dto.date);
|
||||
const comment = this.truncate280(dto.comment);
|
||||
const supervisor_comment = this.truncate280(dto.supervisor_comment? dto.supervisor_comment : '');
|
||||
return { date, comment, supervisor_comment };
|
||||
}
|
||||
|
||||
//makes sure that a string cannot exceed 280 chars
|
||||
private truncate280 = (input: string): string => {
|
||||
return input.length > 280 ? input.slice(0, 280) : input;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,9 @@
|
|||
import { Module } from "@nestjs/common";
|
||||
import { CsvExportController } from "./controllers/csv-exports.controller";
|
||||
import { CsvExportService } from "./services/csv-exports.service";
|
||||
import { SharedModule } from "../shared/shared.module";
|
||||
|
||||
@Module({
|
||||
providers:[CsvExportService, SharedModule],
|
||||
providers:[CsvExportService],
|
||||
controllers: [CsvExportController],
|
||||
})
|
||||
export class CsvExportModule {}
|
||||
|
|
|
|||
|
|
@ -1,30 +0,0 @@
|
|||
// import { Body, Controller, Post } from "@nestjs/common";
|
||||
// import { ApiBearerAuth, ApiTags } from "@nestjs/swagger";
|
||||
// import { LeaveRequestsService } from "../services/leave-request.service";
|
||||
// import { UpsertLeaveRequestDto } from "../dtos/upsert-leave-request.dto";
|
||||
// import { LeaveTypes } from "@prisma/client";
|
||||
|
||||
// @ApiTags('Leave Requests')
|
||||
// @ApiBearerAuth('access-token')
|
||||
// // @UseGuards()
|
||||
// @Controller('leave-requests')
|
||||
// export class LeaveRequestController {
|
||||
// constructor(private readonly leave_service: LeaveRequestsService){}
|
||||
|
||||
// @Post('upsert')
|
||||
// async upsertLeaveRequest(@Body() dto: UpsertLeaveRequestDto) {
|
||||
// const { action, leave_requests } = await this.leave_service.handle(dto);
|
||||
// return { action, leave_requests };
|
||||
// }q
|
||||
|
||||
// //TODO:
|
||||
// /*
|
||||
// @Get('archive')
|
||||
// findAllArchived(){...}
|
||||
|
||||
// @Get('archive/:id')
|
||||
// findOneArchived(id){...}
|
||||
// */
|
||||
|
||||
// }
|
||||
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
// import { PrismaService } from "src/prisma/prisma.service";
|
||||
// import { LeaveRequestController } from "./controllers/leave-requests.controller";
|
||||
// import { HolidayLeaveRequestsService } from "./services/holiday-leave-requests.service";
|
||||
// import { Module } from "@nestjs/common";
|
||||
// import { BusinessLogicsModule } from "src/modules/business-logics/business-logics.module";
|
||||
// import { VacationLeaveRequestsService } from "./services/vacation-leave-requests.service";
|
||||
// import { SickLeaveRequestsService } from "./services/sick-leave-requests.service";
|
||||
// import { LeaveRequestsService } from "./services/leave-request.service";
|
||||
// import { ShiftsModule } from "../shifts/shifts.module";
|
||||
// import { LeaveRequestsUtils } from "./utils/leave-request.util";
|
||||
// import { SharedModule } from "../shared/shared.module";
|
||||
|
||||
// @Module({
|
||||
// imports: [BusinessLogicsModule, ShiftsModule, SharedModule],
|
||||
// controllers: [LeaveRequestController],
|
||||
// providers: [
|
||||
// VacationLeaveRequestsService,
|
||||
// SickLeaveRequestsService,
|
||||
// HolidayLeaveRequestsService,
|
||||
// LeaveRequestsService,
|
||||
// PrismaService,
|
||||
// LeaveRequestsUtils,
|
||||
// ],
|
||||
// exports: [
|
||||
// LeaveRequestsService,
|
||||
// ],
|
||||
// })
|
||||
|
||||
// export class LeaveRequestsModule {}
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
// import { UpsertLeaveRequestDto, UpsertResult } from '../dtos/upsert-leave-request.dto';
|
||||
// import { LeaveRequestViewDto } from '../dtos/leave-request-view.dto';
|
||||
// import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
|
||||
// import { LeaveApprovalStatus, LeaveTypes } from '@prisma/client';
|
||||
// import { HolidayService } from 'src/modules/business-logics/services/holiday.service';
|
||||
// import { PrismaService } from 'src/prisma/prisma.service';
|
||||
// import { mapRowToView } from '../mappers/leave-requests.mapper';
|
||||
// import { leaveRequestsSelect } from '../utils/leave-requests.select';
|
||||
// import { LeaveRequestsUtils} from '../utils/leave-request.util';
|
||||
// import { normalizeDates, toDateOnly } from 'src/modules/shared/helpers/date-time.helpers';
|
||||
// import { BankCodesResolver } from 'src/modules/shared/utils/resolve-bank-type-id.utils';
|
||||
// import { EmailToIdResolver } from 'src/modules/shared/utils/resolve-email-id.utils';
|
||||
|
||||
|
||||
// @Injectable()
|
||||
// export class HolidayLeaveRequestsService {
|
||||
// constructor(
|
||||
// private readonly prisma: PrismaService,
|
||||
// private readonly holidayService: HolidayService,
|
||||
// private readonly leaveUtils: LeaveRequestsUtils,
|
||||
// private readonly emailResolver: EmailToIdResolver,
|
||||
// private readonly typeResolver: BankCodesResolver,
|
||||
// ) {}
|
||||
|
||||
// async create(dto: UpsertLeaveRequestDto): Promise<UpsertResult> {
|
||||
// const email = dto.email.trim();
|
||||
// const employee_id = await this.emailResolver.findIdByEmail(email);
|
||||
// const bank_code = await this.typeResolver.findByType(LeaveTypes.HOLIDAY);
|
||||
// if(!bank_code) throw new NotFoundException(`bank_code not found`);
|
||||
// const dates = normalizeDates(dto.dates);
|
||||
// if (!dates.length) throw new BadRequestException('Dates array must not be empty');
|
||||
|
||||
// const created: LeaveRequestViewDto[] = [];
|
||||
|
||||
// for (const iso_date of dates) {
|
||||
// const date = toDateOnly(iso_date);
|
||||
|
||||
// const existing = await this.prisma.leaveRequests.findUnique({
|
||||
// where: {
|
||||
// leave_per_employee_date: {
|
||||
// employee_id: employee_id,
|
||||
// leave_type: LeaveTypes.HOLIDAY,
|
||||
// date,
|
||||
// },
|
||||
// },
|
||||
// select: { id: true },
|
||||
// });
|
||||
// if (existing) {
|
||||
// throw new BadRequestException(`Holiday request already exists for ${iso_date}`);
|
||||
// }
|
||||
|
||||
// const payable = await this.holidayService.calculateHolidayPay(email, date, bank_code.modifier);
|
||||
// const row = await this.prisma.leaveRequests.create({
|
||||
// data: {
|
||||
// employee_id: employee_id,
|
||||
// bank_code_id: bank_code.id,
|
||||
// leave_type: LeaveTypes.HOLIDAY,
|
||||
// date,
|
||||
// comment: dto.comment ?? '',
|
||||
// requested_hours: dto.requested_hours ?? 8,
|
||||
// payable_hours: payable,
|
||||
// approval_status: dto.approval_status ?? LeaveApprovalStatus.PENDING,
|
||||
// },
|
||||
// select: leaveRequestsSelect,
|
||||
// });
|
||||
|
||||
// const hours = Number(row.payable_hours ?? row.requested_hours ?? 0);
|
||||
// if (row.approval_status === LeaveApprovalStatus.APPROVED) {
|
||||
// await this.leaveUtils.syncShift(email, employee_id, iso_date, hours,LeaveTypes.HOLIDAY, row.comment);
|
||||
// }
|
||||
|
||||
// created.push({ ...mapRowToView(row), action: 'create' });
|
||||
// }
|
||||
|
||||
// return { action: 'create', leave_requests: created };
|
||||
// }
|
||||
// }
|
||||
|
||||
|
|
@ -1,248 +0,0 @@
|
|||
// import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common";
|
||||
// import { LeaveApprovalStatus, LeaveTypes } from "@prisma/client";
|
||||
// import { roundToQuarterHour } from "src/common/utils/date-utils";
|
||||
// import { UpsertLeaveRequestDto, UpsertResult } from "../dtos/upsert-leave-request.dto";
|
||||
// import { LeaveRequestViewDto } from "../dtos/leave-request-view.dto";
|
||||
// import { mapRowToView } from "../mappers/leave-requests.mapper";
|
||||
// import { leaveRequestsSelect } from "../utils/leave-requests.select";
|
||||
// import { HolidayLeaveRequestsService } from "./holiday-leave-requests.service";
|
||||
// import { SickLeaveRequestsService } from "./sick-leave-requests.service";
|
||||
// import { VacationLeaveRequestsService } from "./vacation-leave-requests.service";
|
||||
// import { HolidayService } from "src/modules/business-logics/services/holiday.service";
|
||||
// import { SickLeaveService } from "src/modules/business-logics/services/sick-leave.service";
|
||||
// import { VacationService } from "src/modules/business-logics/services/vacation.service";
|
||||
// import { PrismaService } from "src/prisma/prisma.service";
|
||||
// import { LeaveRequestsUtils } from "../utils/leave-request.util";
|
||||
// import { normalizeDates, toDateOnly, toISODateKey } from "src/modules/shared/helpers/date-time.helpers";
|
||||
// import { EmailToIdResolver } from "src/modules/shared/utils/resolve-email-id.utils";
|
||||
// import { BankCodesResolver } from "src/modules/shared/utils/resolve-bank-type-id.utils";
|
||||
|
||||
// @Injectable()
|
||||
// export class LeaveRequestsService {
|
||||
// constructor(
|
||||
// private readonly prisma: PrismaService,
|
||||
// private readonly holidayLeaveService: HolidayLeaveRequestsService,
|
||||
// private readonly holidayService: HolidayService,
|
||||
// private readonly sickLogic: SickLeaveService,
|
||||
// private readonly sickLeaveService: SickLeaveRequestsService,
|
||||
// private readonly vacationLeaveService: VacationLeaveRequestsService,
|
||||
// private readonly vacationLogic: VacationService,
|
||||
// private readonly leaveUtils: LeaveRequestsUtils,
|
||||
// private readonly emailResolver: EmailToIdResolver,
|
||||
// private readonly typeResolver: BankCodesResolver,
|
||||
// ) {}
|
||||
|
||||
// //handle distribution to the right service according to the selected type and action
|
||||
// async handle(dto: UpsertLeaveRequestDto): Promise<UpsertResult> {
|
||||
// switch (dto.type) {
|
||||
// case LeaveTypes.HOLIDAY:
|
||||
// if( dto.action === 'create'){
|
||||
// return this.holidayLeaveService.create(dto);
|
||||
// } else if (dto.action === 'update') {
|
||||
// return this.update(dto, LeaveTypes.HOLIDAY);
|
||||
// } else if (dto.action === 'delete'){
|
||||
// return this.delete(dto, LeaveTypes.HOLIDAY);
|
||||
// }
|
||||
// case LeaveTypes.VACATION:
|
||||
// if( dto.action === 'create'){
|
||||
// return this.vacationLeaveService.create(dto);
|
||||
// } else if (dto.action === 'update') {
|
||||
// return this.update(dto, LeaveTypes.VACATION);
|
||||
// } else if (dto.action === 'delete'){
|
||||
// return this.delete(dto, LeaveTypes.VACATION);
|
||||
// }
|
||||
// case LeaveTypes.SICK:
|
||||
// if( dto.action === 'create'){
|
||||
// return this.sickLeaveService.create(dto);
|
||||
// } else if (dto.action === 'update') {
|
||||
// return this.update(dto, LeaveTypes.SICK);
|
||||
// } else if (dto.action === 'delete'){
|
||||
// return this.delete(dto, LeaveTypes.SICK);
|
||||
// }
|
||||
// default:
|
||||
// throw new BadRequestException(`Unsupported leave type: ${dto.type} or action: ${dto.action}`);
|
||||
// }
|
||||
// }
|
||||
|
||||
// async delete(dto: UpsertLeaveRequestDto, type: LeaveTypes): Promise<UpsertResult> {
|
||||
// const email = dto.email.trim();
|
||||
// const dates = normalizeDates(dto.dates);
|
||||
// const employee_id = await this.emailResolver.findIdByEmail(email);
|
||||
// if (!dates.length) throw new BadRequestException("Dates array must not be empty");
|
||||
|
||||
// const rows = await this.prisma.leaveRequests.findMany({
|
||||
// where: {
|
||||
// employee_id: employee_id,
|
||||
// leave_type: type,
|
||||
// date: { in: dates.map((d) => toDateOnly(d)) },
|
||||
// },
|
||||
// select: leaveRequestsSelect,
|
||||
// });
|
||||
|
||||
// if (rows.length !== dates.length) {
|
||||
// const missing = dates.filter((isoDate) => !rows.some((row) => toISODateKey(row.date) === isoDate));
|
||||
// throw new NotFoundException(`No Leave request found for: ${missing.join(", ")}`);
|
||||
// }
|
||||
|
||||
// for (const row of rows) {
|
||||
// if (row.approval_status === LeaveApprovalStatus.APPROVED) {
|
||||
// const iso = toISODateKey(row.date);
|
||||
// await this.leaveUtils.removeShift(email, employee_id, iso, type);
|
||||
// }
|
||||
// }
|
||||
|
||||
// await this.prisma.leaveRequests.deleteMany({
|
||||
// where: { id: { in: rows.map((row) => row.id) } },
|
||||
// });
|
||||
|
||||
// const deleted = rows.map((row) => ({ ...mapRowToView(row), action: "delete" as const }));
|
||||
// return { action: "delete", leave_requests: deleted };
|
||||
// }
|
||||
|
||||
// async update(dto: UpsertLeaveRequestDto, type: LeaveTypes): Promise<UpsertResult> {
|
||||
// const email = dto.email.trim();
|
||||
// const employee_id = await this.emailResolver.findIdByEmail(email);
|
||||
// const bank_code = await this.typeResolver.findByType(type);
|
||||
// if(!bank_code) throw new NotFoundException(`bank_code not found`);
|
||||
// const modifier = Number(bank_code.modifier ?? 1);
|
||||
// const dates = normalizeDates(dto.dates);
|
||||
// if (!dates.length) {
|
||||
// throw new BadRequestException("Dates array must not be empty");
|
||||
// }
|
||||
|
||||
// const entries = await Promise.all(
|
||||
// dates.map(async (iso_date) => {
|
||||
// const date = toDateOnly(iso_date);
|
||||
// const existing = await this.prisma.leaveRequests.findUnique({
|
||||
// where: {
|
||||
// leave_per_employee_date: {
|
||||
// employee_id: employee_id,
|
||||
// leave_type: type,
|
||||
// date,
|
||||
// },
|
||||
// },
|
||||
// select: leaveRequestsSelect,
|
||||
// });
|
||||
// if (!existing) throw new NotFoundException(`No Leave request found for ${iso_date}`);
|
||||
// return { iso_date, date, existing };
|
||||
// }),
|
||||
// );
|
||||
|
||||
// const updated: LeaveRequestViewDto[] = [];
|
||||
|
||||
// if (type === LeaveTypes.SICK) {
|
||||
// const firstExisting = entries[0].existing;
|
||||
// const fallbackRequested =
|
||||
// firstExisting.requested_hours !== null && firstExisting.requested_hours !== undefined
|
||||
// ? Number(firstExisting.requested_hours)
|
||||
// : 8;
|
||||
// const requested_hours_per_day = dto.requested_hours ?? fallbackRequested;
|
||||
// const reference_date = entries.reduce(
|
||||
// (latest, entry) => (entry.date > latest ? entry.date : latest),
|
||||
// entries[0].date,
|
||||
// );
|
||||
// const total_payable_hours = await this.sickLogic.calculateSickLeavePay(
|
||||
// employee_id,
|
||||
// reference_date,
|
||||
// entries.length,
|
||||
// requested_hours_per_day,
|
||||
// modifier,
|
||||
// );
|
||||
// let remaining_payable_hours = roundToQuarterHour(Math.max(0, total_payable_hours));
|
||||
// const daily_payable_cap = roundToQuarterHour(requested_hours_per_day * modifier);
|
||||
|
||||
// for (const { iso_date, existing } of entries) {
|
||||
// const previous_status = existing.approval_status;
|
||||
// const payable = Math.min(remaining_payable_hours, daily_payable_cap);
|
||||
// const payable_rounded = roundToQuarterHour(Math.max(0, payable));
|
||||
// remaining_payable_hours = roundToQuarterHour(
|
||||
// Math.max(0, remaining_payable_hours - payable_rounded),
|
||||
// );
|
||||
|
||||
// const row = await this.prisma.leaveRequests.update({
|
||||
// where: { id: existing.id },
|
||||
// data: {
|
||||
// comment: dto.comment ?? existing.comment,
|
||||
// requested_hours: requested_hours_per_day,
|
||||
// payable_hours: payable_rounded,
|
||||
// bank_code_id: bank_code.id,
|
||||
// approval_status: dto.approval_status ?? existing.approval_status,
|
||||
// },
|
||||
// select: leaveRequestsSelect,
|
||||
// });
|
||||
|
||||
// const was_approved = previous_status === LeaveApprovalStatus.APPROVED;
|
||||
// const is_approved = row.approval_status === LeaveApprovalStatus.APPROVED;
|
||||
// const hours = Number(row.payable_hours ?? row.requested_hours ?? 0);
|
||||
|
||||
// if (!was_approved && is_approved) {
|
||||
// await this.leaveUtils.syncShift(email, employee_id, iso_date, hours, type, row.comment);
|
||||
// } else if (was_approved && !is_approved) {
|
||||
// await this.leaveUtils.removeShift(email, employee_id, iso_date, type);
|
||||
// } else if (was_approved && is_approved) {
|
||||
// await this.leaveUtils.syncShift(email, employee_id, iso_date, hours, type, row.comment);
|
||||
// }
|
||||
// updated.push({ ...mapRowToView(row), action: "update" });
|
||||
// }
|
||||
// return { action: "update", leave_requests: updated };
|
||||
// }
|
||||
|
||||
// for (const { iso_date, date, existing } of entries) {
|
||||
// const previous_status = existing.approval_status;
|
||||
// const fallbackRequested =
|
||||
// existing.requested_hours !== null && existing.requested_hours !== undefined
|
||||
// ? Number(existing.requested_hours)
|
||||
// : 8;
|
||||
// const requested_hours = dto.requested_hours ?? fallbackRequested;
|
||||
|
||||
// let payable: number;
|
||||
// switch (type) {
|
||||
// case LeaveTypes.HOLIDAY:
|
||||
// payable = await this.holidayService.calculateHolidayPay(email, date, modifier);
|
||||
// break;
|
||||
// case LeaveTypes.VACATION: {
|
||||
// const days_requested = requested_hours / 8;
|
||||
// payable = await this.vacationLogic.calculateVacationPay(
|
||||
// employee_id,
|
||||
// date,
|
||||
// Math.max(0, days_requested),
|
||||
// modifier,
|
||||
// );
|
||||
// break;
|
||||
// }
|
||||
// default:
|
||||
// payable = existing.payable_hours !== null && existing.payable_hours !== undefined
|
||||
// ? Number(existing.payable_hours)
|
||||
// : requested_hours;
|
||||
// }
|
||||
|
||||
// const row = await this.prisma.leaveRequests.update({
|
||||
// where: { id: existing.id },
|
||||
// data: {
|
||||
// requested_hours,
|
||||
// comment: dto.comment ?? existing.comment,
|
||||
// payable_hours: payable,
|
||||
// bank_code_id: bank_code.id,
|
||||
// approval_status: dto.approval_status ?? existing.approval_status,
|
||||
// },
|
||||
// select: leaveRequestsSelect,
|
||||
// });
|
||||
|
||||
// const was_approved = previous_status === LeaveApprovalStatus.APPROVED;
|
||||
// const is_approved = row.approval_status === LeaveApprovalStatus.APPROVED;
|
||||
// const hours = Number(row.payable_hours ?? row.requested_hours ?? 0);
|
||||
|
||||
// if (!was_approved && is_approved) {
|
||||
// await this.leaveUtils.syncShift(email, employee_id, iso_date, hours, type, row.comment);
|
||||
// } else if (was_approved && !is_approved) {
|
||||
// await this.leaveUtils.removeShift(email, employee_id, iso_date, type);
|
||||
// } else if (was_approved && is_approved) {
|
||||
// await this.leaveUtils.syncShift(email, employee_id, iso_date, hours, type, row.comment);
|
||||
// }
|
||||
// updated.push({ ...mapRowToView(row), action: "update" });
|
||||
// }
|
||||
// return { action: "update", leave_requests: updated };
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
|
|
@ -1,98 +0,0 @@
|
|||
// import { UpsertLeaveRequestDto, UpsertResult } from "../dtos/upsert-leave-request.dto";
|
||||
// import { LeaveRequestViewDto } from "../dtos/leave-request-view.dto";
|
||||
// import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common";
|
||||
// import { LeaveApprovalStatus, LeaveTypes } from "@prisma/client";
|
||||
// import { leaveRequestsSelect } from "../utils/leave-requests.select";
|
||||
// import { mapRowToView } from "../mappers/leave-requests.mapper";
|
||||
// import { PrismaService } from "src/prisma/prisma.service";
|
||||
// import { SickLeaveService } from "src/modules/business-logics/services/sick-leave.service";
|
||||
// import { roundToQuarterHour } from "src/common/utils/date-utils";
|
||||
// import { LeaveRequestsUtils } from "../utils/leave-request.util";
|
||||
// import { normalizeDates, toDateOnly } from "src/modules/shared/helpers/date-time.helpers";
|
||||
// import { EmailToIdResolver } from "src/modules/shared/utils/resolve-email-id.utils";
|
||||
// import { BankCodesResolver } from "src/modules/shared/utils/resolve-bank-type-id.utils";
|
||||
|
||||
// @Injectable()
|
||||
// export class SickLeaveRequestsService {
|
||||
// constructor(
|
||||
// private readonly prisma: PrismaService,
|
||||
// private readonly sickService: SickLeaveService,
|
||||
// private readonly leaveUtils: LeaveRequestsUtils,
|
||||
// private readonly emailResolver: EmailToIdResolver,
|
||||
// private readonly typeResolver: BankCodesResolver,
|
||||
// ) {}
|
||||
|
||||
// async create(dto: UpsertLeaveRequestDto): Promise<UpsertResult> {
|
||||
// const email = dto.email.trim();
|
||||
// const employee_id = await this.emailResolver.findIdByEmail(email);
|
||||
// const bank_code = await this.typeResolver.findByType(LeaveTypes.SICK);
|
||||
// if(!bank_code) throw new NotFoundException(`bank_code not found`);
|
||||
|
||||
// const modifier = bank_code.modifier ?? 1;
|
||||
// const dates = normalizeDates(dto.dates);
|
||||
// if (!dates.length) throw new BadRequestException("Dates array must not be empty");
|
||||
// const requested_hours_per_day = dto.requested_hours ?? 8;
|
||||
|
||||
// const entries = dates.map((iso) => ({ iso, date: toDateOnly(iso) }));
|
||||
// const reference_date = entries.reduce(
|
||||
// (latest, entry) => (entry.date > latest ? entry.date : latest),
|
||||
// entries[0].date,
|
||||
// );
|
||||
// const total_payable_hours = await this.sickService.calculateSickLeavePay(
|
||||
// employee_id,
|
||||
// reference_date,
|
||||
// entries.length,
|
||||
// requested_hours_per_day,
|
||||
// modifier,
|
||||
// );
|
||||
// let remaining_payable_hours = roundToQuarterHour(Math.max(0, total_payable_hours));
|
||||
// const daily_payable_cap = roundToQuarterHour(requested_hours_per_day * modifier);
|
||||
|
||||
// const created: LeaveRequestViewDto[] = [];
|
||||
|
||||
// for (const { iso, date } of entries) {
|
||||
// const existing = await this.prisma.leaveRequests.findUnique({
|
||||
// where: {
|
||||
// leave_per_employee_date: {
|
||||
// employee_id: employee_id,
|
||||
// leave_type: LeaveTypes.SICK,
|
||||
// date,
|
||||
// },
|
||||
// },
|
||||
// select: { id: true },
|
||||
// });
|
||||
// if (existing) {
|
||||
// throw new BadRequestException(`Sick request already exists for ${iso}`);
|
||||
// }
|
||||
|
||||
// const payable = Math.min(remaining_payable_hours, daily_payable_cap);
|
||||
// const payable_rounded = roundToQuarterHour(Math.max(0, payable));
|
||||
// remaining_payable_hours = roundToQuarterHour(
|
||||
// Math.max(0, remaining_payable_hours - payable_rounded),
|
||||
// );
|
||||
|
||||
// const row = await this.prisma.leaveRequests.create({
|
||||
// data: {
|
||||
// employee_id: employee_id,
|
||||
// bank_code_id: bank_code.id,
|
||||
// leave_type: LeaveTypes.SICK,
|
||||
// comment: dto.comment ?? "",
|
||||
// requested_hours: requested_hours_per_day,
|
||||
// payable_hours: payable_rounded,
|
||||
// approval_status: dto.approval_status ?? LeaveApprovalStatus.PENDING,
|
||||
// date,
|
||||
// },
|
||||
// select: leaveRequestsSelect,
|
||||
// });
|
||||
|
||||
// const hours = Number(row.payable_hours ?? row.requested_hours ?? 0);
|
||||
// if (row.approval_status === LeaveApprovalStatus.APPROVED) {
|
||||
// await this.leaveUtils.syncShift(email, employee_id, iso, hours,LeaveTypes.SICK, row.comment);
|
||||
// }
|
||||
|
||||
// created.push({ ...mapRowToView(row), action: "create" });
|
||||
// }
|
||||
|
||||
// return { action: "create", leave_requests: created };
|
||||
// }
|
||||
// }
|
||||
|
|
@ -1,93 +0,0 @@
|
|||
|
||||
// import { UpsertLeaveRequestDto, UpsertResult } from "../dtos/upsert-leave-request.dto";
|
||||
// import { LeaveRequestViewDto } from "../dtos/leave-request-view.dto";
|
||||
// import { BadRequestException, Injectable, NotFoundException } from "@nestjs/common";
|
||||
// import { LeaveApprovalStatus, LeaveTypes } from "@prisma/client";
|
||||
// import { VacationService } from "src/modules/business-logics/services/vacation.service";
|
||||
// import { PrismaService } from "src/prisma/prisma.service";
|
||||
// import { mapRowToView } from "../mappers/leave-requests.mapper";
|
||||
// import { leaveRequestsSelect } from "../utils/leave-requests.select";
|
||||
// import { roundToQuarterHour } from "src/common/utils/date-utils";
|
||||
// import { LeaveRequestsUtils } from "../utils/leave-request.util";
|
||||
// import { normalizeDates, toDateOnly } from "src/modules/shared/helpers/date-time.helpers";
|
||||
// import { EmailToIdResolver } from "src/modules/shared/utils/resolve-email-id.utils";
|
||||
// import { BankCodesResolver } from "src/modules/shared/utils/resolve-bank-type-id.utils";
|
||||
|
||||
// @Injectable()
|
||||
// export class VacationLeaveRequestsService {
|
||||
// constructor(
|
||||
// private readonly prisma: PrismaService,
|
||||
// private readonly vacationService: VacationService,
|
||||
// private readonly leaveUtils: LeaveRequestsUtils,
|
||||
// private readonly emailResolver: EmailToIdResolver,
|
||||
// private readonly typeResolver: BankCodesResolver,
|
||||
// ) {}
|
||||
|
||||
// async create(dto: UpsertLeaveRequestDto): Promise<UpsertResult> {
|
||||
// const email = dto.email.trim();
|
||||
// const employee_id = await this.emailResolver.findIdByEmail(email);
|
||||
// const bank_code = await this.typeResolver.findByType(LeaveTypes.VACATION);
|
||||
// if(!bank_code) throw new NotFoundException(`bank_code not found`);
|
||||
|
||||
// const modifier = bank_code.modifier ?? 1;
|
||||
// const dates = normalizeDates(dto.dates);
|
||||
// const requested_hours_per_day = dto.requested_hours ?? 8;
|
||||
// if (!dates.length) throw new BadRequestException("Dates array must not be empty");
|
||||
|
||||
// const entries = dates
|
||||
// .map((iso) => ({ iso, date: toDateOnly(iso) }))
|
||||
// .sort((a, b) => a.date.getTime() - b.date.getTime());
|
||||
// const start_date = entries[0].date;
|
||||
// const total_payable_hours = await this.vacationService.calculateVacationPay(
|
||||
// employee_id,
|
||||
// start_date,
|
||||
// entries.length,
|
||||
// modifier,
|
||||
// );
|
||||
// let remaining_payable_hours = roundToQuarterHour(Math.max(0, total_payable_hours));
|
||||
// const daily_payable_cap = roundToQuarterHour(requested_hours_per_day * modifier);
|
||||
|
||||
// const created: LeaveRequestViewDto[] = [];
|
||||
|
||||
// for (const { iso, date } of entries) {
|
||||
// const existing = await this.prisma.leaveRequests.findUnique({
|
||||
// where: {
|
||||
// leave_per_employee_date: {
|
||||
// employee_id: employee_id,
|
||||
// leave_type: LeaveTypes.VACATION,
|
||||
// date,
|
||||
// },
|
||||
// },
|
||||
// select: { id: true },
|
||||
// });
|
||||
// if (existing) throw new BadRequestException(`Vacation request already exists for ${iso}`);
|
||||
|
||||
// const payable = Math.min(remaining_payable_hours, daily_payable_cap);
|
||||
// const payable_rounded = roundToQuarterHour(Math.max(0, payable));
|
||||
// remaining_payable_hours = roundToQuarterHour(
|
||||
// Math.max(0, remaining_payable_hours - payable_rounded),
|
||||
// );
|
||||
|
||||
// const row = await this.prisma.leaveRequests.create({
|
||||
// data: {
|
||||
// employee_id: employee_id,
|
||||
// bank_code_id: bank_code.id,
|
||||
// payable_hours: payable_rounded,
|
||||
// requested_hours: requested_hours_per_day,
|
||||
// leave_type: LeaveTypes.VACATION,
|
||||
// comment: dto.comment ?? "",
|
||||
// approval_status: dto.approval_status ?? LeaveApprovalStatus.PENDING,
|
||||
// date,
|
||||
// },
|
||||
// select: leaveRequestsSelect,
|
||||
// });
|
||||
|
||||
// const hours = Number(row.payable_hours ?? row.requested_hours ?? 0);
|
||||
// if (row.approval_status === LeaveApprovalStatus.APPROVED) {
|
||||
// await this.leaveUtils.syncShift(email, employee_id, iso, hours, LeaveTypes.VACATION, row.comment);
|
||||
// }
|
||||
// created.push({ ...mapRowToView(row), action: "create" });
|
||||
// }
|
||||
// return { action: "create", leave_requests: created };
|
||||
// }
|
||||
// }
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
import { LeaveRequestViewDto } from '../dtos/leave-request-view.dto';
|
||||
import { mapArchiveRowToView } from '../mappers/leave-requests-archive.mapper';
|
||||
import { mapRowToView } from '../mappers/leave-requests.mapper';
|
||||
import { LeaveRequestArchiveRow } from './leave-requests-archive.select';
|
||||
import { LeaveRequestRow } from './leave-requests.select';
|
||||
|
||||
/** Active (table leave_requests) : proxy to base mapper */
|
||||
export function mapRowToViewWithDays(row: LeaveRequestRow): LeaveRequestViewDto {
|
||||
return mapRowToView(row);
|
||||
}
|
||||
|
||||
/** Archive (table leave_requests_archive) : proxy to base mapper */
|
||||
export function mapArchiveRowToViewWithDays(
|
||||
row: LeaveRequestArchiveRow,
|
||||
email: string,
|
||||
employee_full_name?: string,
|
||||
): LeaveRequestViewDto {
|
||||
return mapArchiveRowToView(row, email, employee_full_name!);
|
||||
}
|
||||
|
|
@ -1,104 +0,0 @@
|
|||
// import { hhmmFromLocal, toDateOnly, toStringFromDate } from "src/modules/shared/helpers/date-time.helpers";
|
||||
// import { BadRequestException, Injectable } from "@nestjs/common";
|
||||
// import { PrismaService } from "src/prisma/prisma.service";
|
||||
// import { LeaveTypes } from "@prisma/client";
|
||||
// import { UpsertAction } from "src/modules/shared/types/upsert-actions.types";
|
||||
|
||||
// @Injectable()
|
||||
// export class LeaveRequestsUtils {
|
||||
// constructor(
|
||||
// private readonly prisma: PrismaService,
|
||||
// private readonly shiftsCommand: ShiftsCommandService,
|
||||
// ){}
|
||||
|
||||
// async syncShift(
|
||||
// email: string,
|
||||
// employee_id: number,
|
||||
// date: string,
|
||||
// hours: number,
|
||||
// type: LeaveTypes,
|
||||
// comment?: string,
|
||||
// ) {
|
||||
// if (hours <= 0) return;
|
||||
|
||||
// const duration_minutes = Math.round(hours * 60);
|
||||
// if (duration_minutes > 8 * 60) {
|
||||
// throw new BadRequestException("Amount of hours cannot exceed 8 hours per day.");
|
||||
// }
|
||||
// const date_only = toDateOnly(date);
|
||||
// const yyyy_mm_dd = toStringFromDate(date_only);
|
||||
|
||||
|
||||
|
||||
// const start_minutes = 8 * 60;
|
||||
// const end_minutes = start_minutes + duration_minutes;
|
||||
// const toHHmm = (total: number) =>
|
||||
// `${String(Math.floor(total / 60)).padStart(2, "0")}:${String(total % 60).padStart(2, "0")}`;
|
||||
|
||||
// const existing = await this.prisma.shifts.findFirst({
|
||||
// where: {
|
||||
// date: date_only,
|
||||
// bank_code: { type },
|
||||
// timesheet: { employee_id: employee_id },
|
||||
// },
|
||||
// include: { bank_code: true },
|
||||
// });
|
||||
|
||||
// const action: UpsertAction = existing ? 'update' : 'create';
|
||||
|
||||
// await this.shiftsCommand.upsertShifts(email, action, {
|
||||
// old_shift: existing
|
||||
// ? {
|
||||
// date: yyyy_mm_dd,
|
||||
// start_time: existing.start_time.toISOString().slice(11, 16),
|
||||
// end_time: existing.end_time.toISOString().slice(11, 16),
|
||||
// type: existing.bank_code?.type ?? type,
|
||||
// is_remote: existing.is_remote,
|
||||
// is_approved:existing.is_approved,
|
||||
// comment: existing.comment ?? undefined,
|
||||
// }
|
||||
// : undefined,
|
||||
// new_shift: {
|
||||
// date: yyyy_mm_dd,
|
||||
// start_time: toHHmm(start_minutes),
|
||||
// end_time: toHHmm(end_minutes),
|
||||
// is_remote: existing?.is_remote ?? false,
|
||||
// is_approved:existing?.is_approved ?? false,
|
||||
// comment: comment ?? existing?.comment ?? "",
|
||||
// type: type,
|
||||
// },
|
||||
// });
|
||||
// }
|
||||
|
||||
// async removeShift(
|
||||
// email: string,
|
||||
// employee_id: number,
|
||||
// iso_date: string,
|
||||
// type: LeaveTypes,
|
||||
// ) {
|
||||
// const date_only = toDateOnly(iso_date);
|
||||
// const yyyy_mm_dd = toStringFromDate(date_only);
|
||||
// const existing = await this.prisma.shifts.findFirst({
|
||||
// where: {
|
||||
// date: date_only,
|
||||
// bank_code: { type },
|
||||
// timesheet: { employee_id: employee_id },
|
||||
// },
|
||||
// include: { bank_code: true },
|
||||
// });
|
||||
// if (!existing) return;
|
||||
|
||||
// await this.shiftsCommand.upsertShifts(email, 'delete', {
|
||||
// old_shift: {
|
||||
// date: yyyy_mm_dd,
|
||||
// start_time: hhmmFromLocal(existing.start_time),
|
||||
// end_time: hhmmFromLocal(existing.end_time),
|
||||
// type: existing.bank_code?.type ?? type,
|
||||
// is_remote: existing.is_remote,
|
||||
// is_approved:existing.is_approved,
|
||||
// comment: existing.comment ?? undefined,
|
||||
// },
|
||||
// });
|
||||
// }
|
||||
|
||||
// }
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
import { Prisma } from "@prisma/client";
|
||||
|
||||
//custom prisma select to avoid employee_id exposure
|
||||
export const leaveRequestsSelect = {
|
||||
id: true,
|
||||
bank_code_id: true,
|
||||
leave_type: true,
|
||||
date: true,
|
||||
payable_hours: true,
|
||||
requested_hours: true,
|
||||
comment: true,
|
||||
approval_status: true,
|
||||
employee: { select: {
|
||||
id: true,
|
||||
user: { select: {
|
||||
email: true,
|
||||
first_name: true,
|
||||
last_name: true,
|
||||
}},
|
||||
}},
|
||||
} satisfies Prisma.LeaveRequestsSelect;
|
||||
|
||||
export type LeaveRequestRow = Prisma.LeaveRequestsGetPayload<{ select: typeof leaveRequestsSelect}>;
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
import { Body, Controller, Delete, Get, Param, Patch, Post, UseGuards } from '@nestjs/common';
|
||||
import { OAuthSessions } from '@prisma/client';
|
||||
import { RolesAllowed } from "src/common/decorators/roles.decorators";
|
||||
import { Roles as RoleEnum } from '.prisma/client';
|
||||
import { ApiBearerAuth, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
|
||||
import { CreateOauthSessionDto } from '../dtos/create-oauth-session.dto';
|
||||
import { OauthSessionsService } from '../services/oauth-sessions.service';
|
||||
import { UpdateOauthSessionDto } from '../dtos/update-oauth-session.dto';
|
||||
|
||||
@ApiTags('OAuth Sessions')
|
||||
@ApiBearerAuth('sessions')
|
||||
//@UseGuards(JwtAuthGuard)
|
||||
@Controller('oauth-sessions')
|
||||
export class OauthSessionsController {
|
||||
constructor(private readonly oauthSessionsService: OauthSessionsService){}
|
||||
|
||||
@Post()
|
||||
// @RolesAllowed(RoleEnum.ADMIN)
|
||||
@ApiOperation({summary: 'Create OAuth session' })
|
||||
@ApiResponse({ status: 201, description: 'OAuth session created', type: CreateOauthSessionDto })
|
||||
@ApiResponse({ status: 400, description: 'Incomplete task or invalid data' })
|
||||
create(@Body()dto: CreateOauthSessionDto): Promise<OAuthSessions> {
|
||||
return this.oauthSessionsService.create(dto);
|
||||
}
|
||||
|
||||
@Get()
|
||||
//@RolesAllowed(RoleEnum.ADMIN)
|
||||
@ApiOperation({summary: 'Find all OAuth session' })
|
||||
@ApiResponse({ status: 201, description: 'List of OAuth session found', type: CreateOauthSessionDto, isArray: true })
|
||||
@ApiResponse({ status: 400, description: 'List of OAuth session not found' })
|
||||
findAll(): Promise<OAuthSessions[]> {
|
||||
return this.oauthSessionsService.findAll();
|
||||
}
|
||||
|
||||
@Get(':id')
|
||||
//@RolesAllowed(RoleEnum.ADMIN)
|
||||
@ApiOperation({summary: 'Find OAuth session' })
|
||||
@ApiResponse({ status: 201, description: 'OAuth session found', type: CreateOauthSessionDto })
|
||||
@ApiResponse({ status: 400, description: 'OAuth session not found' })
|
||||
findOne(@Param('id') id: string): Promise<OAuthSessions> {
|
||||
return this.oauthSessionsService.findOne(id);
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
//@RolesAllowed(RoleEnum.ADMIN)
|
||||
@ApiOperation({summary: 'Update OAuth session' })
|
||||
@ApiResponse({ status: 201, description: 'OAuth session updated', type: CreateOauthSessionDto })
|
||||
@ApiResponse({ status: 400, description: 'OAuth session not found' })
|
||||
update(@Param('id') id: string, @Body() dto: UpdateOauthSessionDto): Promise<OAuthSessions> {
|
||||
return this.oauthSessionsService.update(id,dto);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
//@RolesAllowed(RoleEnum.ADMIN)
|
||||
@ApiOperation({summary: 'Delete OAuth session' })
|
||||
@ApiResponse({ status: 201, description: 'OAuth session deleted', type: CreateOauthSessionDto })
|
||||
@ApiResponse({ status: 400, description: 'OAuth session not found' })
|
||||
remove(@Param('id') id: string): Promise<OAuthSessions> {
|
||||
return this.oauthSessionsService.remove(id);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { Type } from "class-transformer";
|
||||
import { IsArray, IsDate, IsOptional, IsString, IsUUID } from "class-validator";
|
||||
|
||||
export class CreateOauthSessionDto {
|
||||
@ApiProperty({
|
||||
example: 'cklwi0vb70000z2z20q6f19qk',
|
||||
description: 'Unique ID of an OAuth token (auto-generated)',
|
||||
})
|
||||
id: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: 'S7A2U8R7O6N6',
|
||||
description: 'User`s unique identification number',
|
||||
})
|
||||
@IsUUID()
|
||||
user_id: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: 'app.targo.ca',
|
||||
description: 'URL in which the access token is used for',
|
||||
})
|
||||
@IsString()
|
||||
application: string;
|
||||
|
||||
@IsString()
|
||||
sid: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: 'L5O6R4D3/O6F3#T8H4E3&R6I4N6G4S7 ...',
|
||||
description: 'Access token',
|
||||
})
|
||||
@IsString()
|
||||
access_token: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: 'Th3731102h1p07Th3R1n92',
|
||||
description: 'Refresh token',
|
||||
})
|
||||
@IsString()
|
||||
refresh_token: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: '25/12/3018',
|
||||
description: 'Access token`s expiry date',
|
||||
})
|
||||
@Type(()=> Date)
|
||||
@IsDate()
|
||||
access_token_expiry: Date;
|
||||
|
||||
@ApiProperty({
|
||||
example: '26/02/3019',
|
||||
description: 'Refresh token`s expiry date',
|
||||
required: false,
|
||||
})
|
||||
@Type(()=> Date)
|
||||
@IsDate()
|
||||
@IsOptional()
|
||||
refresh_token_expiry?: Date;
|
||||
|
||||
@ApiProperty({
|
||||
example: 'access tolkiens, email, etc... ',
|
||||
description: 'scopes of infos linked to the access token',
|
||||
required: false,
|
||||
})
|
||||
@IsArray()
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
scopes?: string[];
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
import { PartialType } from "@nestjs/swagger";
|
||||
import { CreateOauthSessionDto } from "./create-oauth-session.dto";
|
||||
|
||||
export class UpdateOauthSessionDto extends PartialType(CreateOauthSessionDto) {}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { PrismaService } from 'src/prisma/prisma.service';
|
||||
import { OauthSessionsController } from './controllers/oauth-sessions.controller';
|
||||
import { OauthSessionsService } from './services/oauth-sessions.service';
|
||||
|
||||
@Module({
|
||||
controllers: [OauthSessionsController],
|
||||
providers: [OauthSessionsService, PrismaService]
|
||||
})
|
||||
export class OauthSessionsModule {}
|
||||
|
|
@ -1,86 +0,0 @@
|
|||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { PrismaService } from 'src/prisma/prisma.service';
|
||||
import { CreateOauthSessionDto } from '../dtos/create-oauth-session.dto';
|
||||
import { OAuthSessions } from '@prisma/client';
|
||||
import { UpdateOauthSessionDto } from '../dtos/update-oauth-session.dto';
|
||||
|
||||
@Injectable()
|
||||
export class OauthSessionsService {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
async create(dto: CreateOauthSessionDto): Promise<OAuthSessions> {
|
||||
const {
|
||||
user_id,
|
||||
application,
|
||||
access_token,
|
||||
refresh_token,
|
||||
sid,
|
||||
access_token_expiry,
|
||||
refresh_token_expiry,
|
||||
scopes,
|
||||
} = dto;
|
||||
|
||||
return this.prisma.oAuthSessions.create({
|
||||
data: {
|
||||
user_id,
|
||||
application,
|
||||
access_token,
|
||||
refresh_token,
|
||||
sid,
|
||||
access_token_expiry,
|
||||
refresh_token_expiry,
|
||||
scopes,
|
||||
},
|
||||
include: { user: true },
|
||||
});
|
||||
}
|
||||
|
||||
findAll(): Promise<OAuthSessions[]> {
|
||||
return this.prisma.oAuthSessions.findMany({
|
||||
include: { user: true },
|
||||
});
|
||||
}
|
||||
|
||||
async findOne(id: string): Promise<OAuthSessions> {
|
||||
const token = await this.prisma.oAuthSessions.findUnique({
|
||||
where: { id },
|
||||
include: { user: true },
|
||||
});
|
||||
if(!token) {
|
||||
throw new NotFoundException(`token #${ id } not found`);
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
async update(id: string, dto: UpdateOauthSessionDto): Promise<OAuthSessions> {
|
||||
await this.findOne(id);
|
||||
const {
|
||||
user_id,
|
||||
application,
|
||||
access_token,
|
||||
refresh_token,
|
||||
access_token_expiry,
|
||||
refresh_token_expiry,
|
||||
scopes,
|
||||
} = dto;
|
||||
|
||||
return this.prisma.oAuthSessions.update({
|
||||
where: { id },
|
||||
data: {
|
||||
...(user_id !== undefined && { user_id }),
|
||||
...(application !== undefined && { application }),
|
||||
...(access_token !== undefined && { access_token }),
|
||||
...(refresh_token !== undefined && { refresh_token }),
|
||||
...(access_token_expiry !== undefined && { access_token_expiry }),
|
||||
...(refresh_token_expiry !== undefined && { refresh_token_expiry }),
|
||||
...(scopes !== undefined && { scopes }),
|
||||
},
|
||||
include: { user: true },
|
||||
});
|
||||
}
|
||||
|
||||
async remove(id: string): Promise<OAuthSessions> {
|
||||
await this.findOne(id);
|
||||
return this.prisma.oAuthSessions.delete({ where: { id }});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,103 +0,0 @@
|
|||
import { Body, Controller, Get, Param, ParseBoolPipe, ParseIntPipe, Patch, Query } from "@nestjs/common";
|
||||
import { ApiNotFoundResponse, ApiOperation, ApiParam, ApiQuery, ApiResponse, ApiTags } from "@nestjs/swagger";
|
||||
import { PayPeriodDto } from "../dtos/pay-period.dto";
|
||||
import { PayPeriodOverviewDto } from "../dtos/overview-pay-period.dto";
|
||||
import { PayPeriodsQueryService } from "../services/pay-periods-query.service";
|
||||
import { RolesAllowed } from "src/common/decorators/roles.decorators";
|
||||
import { Roles as RoleEnum } from '.prisma/client';
|
||||
// import { PayPeriodsCommandService } from "../services/pay-periods-command.service";
|
||||
import { PayPeriodBundleDto } from "../dtos/bundle-pay-period.dto";
|
||||
import { BulkCrewApprovalDto } from "../dtos/bulk-crew-approval.dto";
|
||||
|
||||
@ApiTags('pay-periods')
|
||||
@Controller('pay-periods')
|
||||
export class PayPeriodsController {
|
||||
|
||||
constructor(
|
||||
private readonly queryService: PayPeriodsQueryService,
|
||||
// private readonly commandService: PayPeriodsCommandService,
|
||||
) {}
|
||||
|
||||
@Get('current-and-all')
|
||||
@ApiOperation({summary: 'Return current pay period and the full list'})
|
||||
@ApiQuery({name: 'date', required:false, example: '2025-08-11', description:'Override for resolving the current period'})
|
||||
@ApiResponse({status: 200, description:'Find current and all pay periods', type: PayPeriodBundleDto})
|
||||
async getCurrentAndAll(@Query('date') date?: string): Promise<PayPeriodBundleDto> {
|
||||
const [current, periods] = await Promise.all([
|
||||
this.queryService.findCurrent(date),
|
||||
this.queryService.findAll(),
|
||||
]);
|
||||
return { current, periods };
|
||||
}
|
||||
|
||||
@Get("date/:date")
|
||||
@ApiOperation({ summary: "Resolve a period by a date within it" })
|
||||
@ApiResponse({ status: 200, description: "Pay period found for the selected date", type: PayPeriodDto })
|
||||
@ApiNotFoundResponse({ description: "Pay period not found for the selected date" })
|
||||
async findByDate(@Param("date") date: string) {
|
||||
return this.queryService.findByDate(date);
|
||||
}
|
||||
|
||||
@Get(":year/:periodNumber")
|
||||
@ApiOperation({ summary: "Find pay period by year and period number" })
|
||||
@ApiParam({ name: "year", type: Number, example: 2024 })
|
||||
@ApiParam({ name: "periodNumber", type: Number, example: 1, description: "1..26" })
|
||||
@ApiResponse({ status: 200, description: "Pay period found", type: PayPeriodDto })
|
||||
@ApiNotFoundResponse({ description: "Pay period not found" })
|
||||
async findOneByYear(
|
||||
@Param("year", ParseIntPipe) year: number,
|
||||
@Param("periodNumber", ParseIntPipe) period_no: number,
|
||||
) {
|
||||
return this.queryService.findOneByYearPeriod(year, period_no);
|
||||
}
|
||||
|
||||
// @Patch("crew/bulk-approval")
|
||||
// //@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||
// @ApiOperation({ summary: "Approve all selected timesheets in the period" })
|
||||
// @ApiResponse({ status: 200, description: "Pay period approved" })
|
||||
// async bulkApproval(@Body() dto: BulkCrewApprovalDto) {
|
||||
// return this.commandService.bulkApproveCrew(dto);
|
||||
// }
|
||||
|
||||
@Get(':year/:periodNumber/:email')
|
||||
//@RolesAllowed(RoleEnum.SUPERVISOR)
|
||||
@ApiOperation({ summary: 'Supervisor crew overview for a given pay period' })
|
||||
@ApiParam({ name: 'year', type: Number, example: 2024 })
|
||||
@ApiParam({ name: 'periodNumber', type: Number, example: 1, description: '1..26' })
|
||||
@ApiQuery({ name: 'includeSubtree', required: false, type: Boolean, example: false, description: 'Include indirect reports' })
|
||||
@ApiResponse({ status: 200, description: 'Crew overview', type: PayPeriodOverviewDto })
|
||||
@ApiNotFoundResponse({ description: 'Pay period not found' })
|
||||
async getCrewOverview(
|
||||
@Param('year', ParseIntPipe) year: number,
|
||||
@Param('periodNumber', ParseIntPipe) period_no: number,
|
||||
@Param('email') email: string,
|
||||
@Query('includeSubtree', new ParseBoolPipe({ optional: true })) include_subtree = false,
|
||||
): Promise<PayPeriodOverviewDto> {
|
||||
return this.queryService.getCrewOverview(year, period_no, email, include_subtree);
|
||||
}
|
||||
|
||||
@Get('overview/:year/:periodNumber')
|
||||
@ApiOperation({ summary: 'Detailed view of a pay period by year + number' })
|
||||
@ApiParam({ name: 'year', type: Number, example: 2024 })
|
||||
@ApiParam({ name: 'periodNumber', type: Number, example: 1, description: '1..26' })
|
||||
@ApiResponse({ status: 200, description: 'Pay period overview found', type: PayPeriodOverviewDto })
|
||||
@ApiNotFoundResponse({ description: 'Pay period not found' })
|
||||
async getOverviewByYear(
|
||||
@Param('year', ParseIntPipe) year: number,
|
||||
@Param('periodNumber', ParseIntPipe) period_no: number,
|
||||
): Promise<PayPeriodOverviewDto> {
|
||||
return this.queryService.getOverviewByYearPeriod(year, period_no);
|
||||
}
|
||||
|
||||
|
||||
//_____________________________________________________________________________________________
|
||||
// Deprecated or unused methods
|
||||
//_____________________________________________________________________________________________
|
||||
|
||||
// @Get()
|
||||
// @ApiOperation({ summary: 'Find all pay period' })
|
||||
// @ApiResponse({status: 200,description: 'List of pay period found', type: PayPeriodDto, isArray: true })
|
||||
// async findAll(): Promise<PayPeriodDto[]> {
|
||||
// return this.queryService.findAll();
|
||||
// }
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { PayPeriodDto } from "./pay-period.dto";
|
||||
|
||||
export class PayPeriodBundleDto {
|
||||
|
||||
@ApiProperty({ type: PayPeriodDto, description: 'Current pay period (resolved from date)' })
|
||||
current: PayPeriodDto;
|
||||
|
||||
@ApiProperty({ type: [PayPeriodDto], description: 'All pay periods' })
|
||||
periods: PayPeriodDto[];
|
||||
}
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class EmployeePeriodOverviewDto {
|
||||
// @ApiProperty({
|
||||
// example: 42,
|
||||
// description: "Employees.id (clé primaire num.)",
|
||||
// })
|
||||
// @Allow()
|
||||
// @IsOptional()
|
||||
// employee_id: number;
|
||||
|
||||
|
||||
email: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: 'Alex Dupont',
|
||||
description: 'Nom complet de lemployé',
|
||||
})
|
||||
employee_name: string;
|
||||
|
||||
@ApiProperty({ example: 40, description: 'pay-period`s regular hours' })
|
||||
regular_hours: number;
|
||||
|
||||
@ApiProperty({ example: 0, description: 'pay-period`s other hours' })
|
||||
other_hours: {
|
||||
evening_hours: number;
|
||||
|
||||
emergency_hours: number;
|
||||
|
||||
overtime_hours: number;
|
||||
|
||||
sick_hours: number;
|
||||
|
||||
holiday_hours: number;
|
||||
|
||||
vacation_hours: number;
|
||||
};
|
||||
|
||||
total_hours: number;
|
||||
|
||||
@ApiProperty({ example: 420.69, description: 'pay-period`s total expenses ($)' })
|
||||
expenses: number;
|
||||
|
||||
@ApiProperty({ example: 40, description: 'pay-period total mileages (km)' })
|
||||
mileage: number;
|
||||
|
||||
@ApiProperty({
|
||||
example: true,
|
||||
description: 'Tous les timesheets de la période sont approuvés pour cet employé',
|
||||
})
|
||||
is_approved: boolean;
|
||||
|
||||
is_remote: boolean;
|
||||
}
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { EmployeePeriodOverviewDto } from './overview-employee-period.dto';
|
||||
|
||||
export class PayPeriodOverviewDto {
|
||||
@ApiProperty({ example: 1, description: 'Period number (1–26)' })
|
||||
pay_period_no: number;
|
||||
|
||||
@ApiProperty({ example: 2023, description: 'Calendar year of the period' })
|
||||
pay_year: number;
|
||||
|
||||
@ApiProperty({
|
||||
example: '2023-12-17',
|
||||
type: String,
|
||||
format: 'date',
|
||||
description: "Period start date (YYYY-MM-DD)",
|
||||
})
|
||||
period_start: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: '2023-12-30',
|
||||
type: String,
|
||||
format: 'date',
|
||||
description: "Period end date (YYYY-MM-DD)",
|
||||
})
|
||||
period_end: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: '2023-12-30',
|
||||
type: String,
|
||||
format: 'date',
|
||||
description: "Period pay day(YYYY-MM-DD)",
|
||||
})
|
||||
payday: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: '2023-12-17 → 2023-12-30',
|
||||
description: 'Human-readable label',
|
||||
})
|
||||
label: string;
|
||||
|
||||
@ApiProperty({
|
||||
type: [EmployeePeriodOverviewDto],
|
||||
description: 'Per-employee overview for the period',
|
||||
})
|
||||
employees_overview: EmployeePeriodOverviewDto[];
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
import { ApiProperty } from "@nestjs/swagger";
|
||||
|
||||
export class PayPeriodDto {
|
||||
@ApiProperty({ example: 1,
|
||||
description: 'numéro cyclique de la période entre 1 et 26' })
|
||||
pay_period_no: number;
|
||||
|
||||
@ApiProperty({ example: '2023-12-17',
|
||||
type: String, format: 'date' })
|
||||
period_start: string;
|
||||
|
||||
@ApiProperty({ example: '2023-12-30',
|
||||
type: String, format: 'date' })
|
||||
period_end: string;
|
||||
|
||||
@ApiProperty({ example: '2023-01-04',
|
||||
type: String, format: 'date' })
|
||||
payday: string;
|
||||
|
||||
@ApiProperty({ example: 2023 })
|
||||
pay_year: number;
|
||||
|
||||
@ApiProperty({ example: '2023-12-17 → 2023-12-30' })
|
||||
label: string;
|
||||
}
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
import { PrismaModule } from "src/prisma/prisma.module";
|
||||
import { PayPeriodsController } from "./controllers/pay-periods.controller";
|
||||
import { Module } from "@nestjs/common";
|
||||
import { PayPeriodsQueryService } from "./services/pay-periods-query.service";
|
||||
import { TimesheetsModule } from "../timesheets/timesheets.module";
|
||||
import { SharedModule } from "../shared/shared.module";
|
||||
import { PrismaService } from "src/prisma/prisma.service";
|
||||
import { BusinessLogicsModule } from "../business-logics/business-logics.module";
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule, TimesheetsModule, SharedModule, BusinessLogicsModule],
|
||||
providers: [
|
||||
PayPeriodsQueryService,
|
||||
PrismaService,
|
||||
],
|
||||
controllers: [PayPeriodsController],
|
||||
exports: [ PayPeriodsQueryService ],
|
||||
})
|
||||
|
||||
export class PayperiodsModule {}
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
// import { BadRequestException, ForbiddenException, Injectable, NotFoundException } from "@nestjs/common";
|
||||
// import { PrismaService } from "src/prisma/prisma.service";
|
||||
// import { BulkCrewApprovalDto } from "../dtos/bulk-crew-approval.dto";
|
||||
// import { PayPeriodsQueryService } from "./pay-periods-query.service";
|
||||
// import { TimesheetApprovalService } from "src/modules/timesheets/services/timesheet-approval.service";
|
||||
|
||||
// @Injectable()
|
||||
// export class PayPeriodsCommandService {
|
||||
// constructor(
|
||||
// private readonly prisma: PrismaService,
|
||||
// private readonly timesheets_approval: TimesheetApprovalService,
|
||||
// private readonly query: PayPeriodsQueryService,
|
||||
// ) {}
|
||||
|
||||
// //function to approve pay-periods according to selected crew members
|
||||
// async bulkApproveCrew(dto:BulkCrewApprovalDto): Promise<{updated: number}> {
|
||||
// const { supervisor_email, include_subtree, items } = dto;
|
||||
// if(!items?.length) throw new BadRequestException('no items to process');
|
||||
|
||||
// //fetch and validate supervisor status
|
||||
// const supervisor = await this.query.getSupervisor(supervisor_email);
|
||||
// if(!supervisor) throw new NotFoundException('No employee record linked to current user');
|
||||
// if(!supervisor.is_supervisor) throw new ForbiddenException('Employee is not a supervisor');
|
||||
|
||||
// //fetches emails of crew members linked to supervisor
|
||||
// const crew_emails = await this.query.resolveCrewEmails(supervisor.id, include_subtree);
|
||||
|
||||
|
||||
// for(const item of items) {
|
||||
// if(!crew_emails.has(item.employee_email)) {
|
||||
// throw new ForbiddenException(`Employee ${item.employee_email} not in supervisor crew`);
|
||||
// }
|
||||
// }
|
||||
|
||||
// const period_cache = new Map<string, {period_start: Date, period_end: Date}>();
|
||||
// const getPeriod = async (y:number, no: number) => {
|
||||
// const key = `${y}-${no}`;
|
||||
// if(!period_cache.has(key)) return period_cache.get(key)!;
|
||||
// const period = await this.query.getPeriodWindow(y,no);
|
||||
// if(!period) throw new NotFoundException(`Pay period ${y}-${no} not found`);
|
||||
// period_cache.set(key, period);
|
||||
// return period;
|
||||
// };
|
||||
|
||||
// let updated = 0;
|
||||
|
||||
// await this.prisma.$transaction(async (transaction) => {
|
||||
// for(const item of items) {
|
||||
// const { period_start, period_end } = await getPeriod(item.pay_year, item.period_no);
|
||||
|
||||
// const t_sheets = await transaction.timesheets.findMany({
|
||||
// where: {
|
||||
// employee: { user: { email: item.employee_email } },
|
||||
// OR: [
|
||||
// {shift : { some: { date: { gte: period_start, lte: period_end } } } },
|
||||
// {expense: { some: { date: { gte: period_start, lte: period_end } } } },
|
||||
// ],
|
||||
// },
|
||||
// select: { id: true },
|
||||
// });
|
||||
|
||||
// for(const { id } of t_sheets) {
|
||||
// await this.timesheets_approval.updateApprovalWithTransaction(transaction, id, item.approve);
|
||||
// updated++;
|
||||
// }
|
||||
|
||||
// }
|
||||
// });
|
||||
// return {updated};
|
||||
// }
|
||||
// }
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
export const ANCHOR_ISO = '2023-12-17'; // ancre date
|
||||
const PERIOD_DAYS = 14;
|
||||
const PERIODS_PER_YEAR = 26;
|
||||
const MS_PER_DAY = 86_400_000;
|
||||
|
||||
const toUTCDate = (iso: string | Date) => {
|
||||
const d = typeof iso === 'string' ? new Date(iso + 'T00:00:00.000Z') : iso;
|
||||
return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()));
|
||||
};
|
||||
export const toDateString = (d: Date) => d.toISOString().slice(0, 10);
|
||||
|
||||
export function payYearOfDate(date: string | Date, anchorISO = ANCHOR_ISO): number {
|
||||
const ANCHOR = toUTCDate(anchorISO);
|
||||
const d = toUTCDate(date);
|
||||
const days = Math.floor((+d - +ANCHOR) / MS_PER_DAY);
|
||||
const cycles = Math.floor(days / (PERIODS_PER_YEAR * PERIOD_DAYS));
|
||||
return ANCHOR.getUTCFullYear() + 1 + cycles;
|
||||
}
|
||||
//compute labels for periods
|
||||
export function computePeriod(pay_year: number, period_no: number, anchorISO = ANCHOR_ISO) {
|
||||
const ANCHOR = toUTCDate(anchorISO);
|
||||
const cycles = pay_year - (ANCHOR.getUTCFullYear() + 1);
|
||||
const offsetPeriods = cycles * PERIODS_PER_YEAR + (period_no - 1);
|
||||
const start = new Date(+ANCHOR + offsetPeriods * PERIOD_DAYS * MS_PER_DAY);
|
||||
const end = new Date(+start + (PERIOD_DAYS - 1) * MS_PER_DAY);
|
||||
const pay = new Date(end.getTime() + 6 * MS_PER_DAY);
|
||||
return {
|
||||
period_no: period_no,
|
||||
pay_year: pay_year,
|
||||
payday: toDateString(pay),
|
||||
period_start: toDateString(start),
|
||||
period_end: toDateString(end),
|
||||
label: `${toDateString(start)}.${toDateString(end)}`,
|
||||
start, end,
|
||||
};
|
||||
}
|
||||
|
||||
//list of all 26 periods for a full year
|
||||
export function listPayYear(pay_year: number, anchorISO = ANCHOR_ISO) {
|
||||
return Array.from({ length: PERIODS_PER_YEAR }, (_, i) => computePeriod(pay_year, i + 1, anchorISO));
|
||||
}
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
import { BadRequestException, Body, Controller, Get, NotFoundException, Param, Post, Put, Query } from "@nestjs/common";
|
||||
import { SchedulePresetsDto } from "../dtos/create-schedule-presets.dto";
|
||||
import { SchedulePresetsCommandService } from "../services/schedule-presets-command.service";
|
||||
import { UpsertAction } from "src/modules/shared/types/upsert-actions.types";
|
||||
import { SchedulePresetsQueryService } from "../services/schedule-presets-query.service";
|
||||
|
||||
@Controller('schedule-presets')
|
||||
export class SchedulePresetsController {
|
||||
constructor(
|
||||
private readonly commandService: SchedulePresetsCommandService,
|
||||
private readonly queryService: SchedulePresetsQueryService,
|
||||
){}
|
||||
|
||||
//used to create, update or delete a schedule preset
|
||||
@Put(':email')
|
||||
async upsert(
|
||||
@Param('email') email: string,
|
||||
@Query('action') action: UpsertAction,
|
||||
@Body() dto: SchedulePresetsDto,
|
||||
) {
|
||||
const actions: UpsertAction[] = ['create','update','delete'];
|
||||
if(!actions) throw new NotFoundException(`No action found for ${actions}`)
|
||||
return this.commandService.upsertSchedulePreset(email, action, dto);
|
||||
}
|
||||
|
||||
//used to show the list of available schedule presets
|
||||
@Get(':email')
|
||||
async findListByEmail(
|
||||
@Param('email') email: string,
|
||||
) {
|
||||
return this.queryService.findSchedulePresetsByEmail(email);
|
||||
}
|
||||
//used to apply a preset to a timesheet
|
||||
@Post('/apply-presets/:email')
|
||||
async applyPresets(
|
||||
@Param('email') email: string,
|
||||
@Query('preset') preset_name: string,
|
||||
@Query('start') start_date: string,
|
||||
) {
|
||||
if(!preset_name?.trim()) throw new BadRequestException('Query "preset" is required');
|
||||
if(!start_date?.trim()) throw new BadRequestException('Query "start" is required YYYY-MM-DD');
|
||||
return this.applyPresets(email, preset_name, start_date);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
import { ArrayMinSize, IsArray, IsBoolean, IsEmail, IsOptional, IsString } from "class-validator";
|
||||
import { SchedulePresetShiftsDto } from "./create-schedule-preset-shifts.dto";
|
||||
|
||||
export class SchedulePresetsDto {
|
||||
@IsString()
|
||||
name!: string;
|
||||
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
is_default: boolean;
|
||||
|
||||
@IsArray()
|
||||
@ArrayMinSize(1)
|
||||
preset_shifts: SchedulePresetShiftsDto[];
|
||||
}
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
import { Module } from "@nestjs/common";
|
||||
import { SchedulePresetsCommandService } from "./services/schedule-presets-command.service";
|
||||
import { SchedulePresetsQueryService } from "./services/schedule-presets-query.service";
|
||||
import { SchedulePresetsController } from "./controller/schedule-presets.controller";
|
||||
import { PrismaService } from "src/prisma/prisma.service";
|
||||
import { SchedulePresetsApplyService } from "./services/schedule-presets-apply.service";
|
||||
import { SharedModule } from "../shared/shared.module";
|
||||
|
||||
@Module({
|
||||
imports: [SharedModule],
|
||||
controllers: [SchedulePresetsController],
|
||||
providers: [
|
||||
PrismaService,
|
||||
SchedulePresetsCommandService,
|
||||
SchedulePresetsQueryService,
|
||||
SchedulePresetsApplyService,
|
||||
],
|
||||
exports:[
|
||||
SchedulePresetsCommandService,
|
||||
SchedulePresetsQueryService,
|
||||
SchedulePresetsApplyService,
|
||||
],
|
||||
}) export class SchedulePresetsModule {}
|
||||
|
|
@ -1,238 +0,0 @@
|
|||
import { BadRequestException, ConflictException, Injectable, NotFoundException } from "@nestjs/common";
|
||||
import { BankCodesResolver } from "src/modules/shared/utils/resolve-bank-type-id.utils";
|
||||
import { EmailToIdResolver } from "src/modules/shared/utils/resolve-email-id.utils";
|
||||
import { UpsertAction } from "src/modules/shared/types/upsert-actions.types";
|
||||
import { PrismaService } from "src/prisma/prisma.service";
|
||||
import { SchedulePresetsDto } from "../dtos/create-schedule-presets.dto";
|
||||
import { Prisma, Weekday } from "@prisma/client";
|
||||
|
||||
@Injectable()
|
||||
export class SchedulePresetsCommandService {
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly emailResolver: EmailToIdResolver,
|
||||
private readonly typeResolver : BankCodesResolver,
|
||||
){}
|
||||
|
||||
//_________________________________________________________________
|
||||
// MASTER CRUD FUNCTION
|
||||
//_________________________________________________________________
|
||||
async upsertSchedulePreset(
|
||||
email: string,
|
||||
action: UpsertAction,
|
||||
dto: SchedulePresetsDto,
|
||||
): Promise<{
|
||||
action: UpsertAction;
|
||||
preset_id?: number;
|
||||
total_items?: number;
|
||||
}>{
|
||||
if(!dto.name?.trim()) throw new BadRequestException(`A Name is required`);
|
||||
|
||||
//resolve employee_id using email
|
||||
const employee_id = await this.emailResolver.findIdByEmail(email);
|
||||
if(!employee_id) throw new NotFoundException(`employee with email: ${email} not found`);
|
||||
|
||||
//DELETE
|
||||
if(action === 'delete') {
|
||||
return this.deletePreset(employee_id, dto.name);
|
||||
}
|
||||
|
||||
if(!Array.isArray(dto.preset_shifts) || dto.preset_shifts.length === 0) {
|
||||
throw new BadRequestException(`Empty array, no detected shifts`);
|
||||
}
|
||||
const shifts_data = await this.resolveAndBuildPresetShifts(dto);
|
||||
|
||||
//CREATE AND UPDATE
|
||||
if(action === 'create') {
|
||||
return this.createPreset(employee_id, dto, shifts_data);
|
||||
} else if (action === 'update') {
|
||||
return this.updatePreset(employee_id, dto, shifts_data);
|
||||
}
|
||||
throw new BadRequestException(`Unknown action: ${ action }`);
|
||||
}
|
||||
|
||||
//_________________________________________________________________
|
||||
// CREATE
|
||||
//_________________________________________________________________
|
||||
private async createPreset(
|
||||
employee_id: number,
|
||||
dto: SchedulePresetsDto,
|
||||
shifts_data: Prisma.SchedulePresetShiftsCreateWithoutPresetInput[],
|
||||
): Promise<{
|
||||
action: UpsertAction;
|
||||
preset_id: number;
|
||||
total_items: number;
|
||||
}> {
|
||||
try {
|
||||
const result = await this.prisma.$transaction(async (tx)=> {
|
||||
if(dto.is_default) {
|
||||
await tx.schedulePresets.updateMany({
|
||||
where: { employee_id, is_default: true },
|
||||
data: { is_default: false },
|
||||
});
|
||||
}
|
||||
const created = await tx.schedulePresets.create({
|
||||
data: {
|
||||
employee_id,
|
||||
name: dto.name,
|
||||
is_default: !!dto.is_default,
|
||||
shifts: { create: shifts_data},
|
||||
},
|
||||
include: { shifts: true },
|
||||
});
|
||||
return created;
|
||||
});
|
||||
return { action: 'create', preset_id: result.id, total_items: result.shifts.length };
|
||||
} catch (error: unknown) {
|
||||
if(error instanceof Prisma.PrismaClientKnownRequestError){
|
||||
if(error?.code === 'P2002') {
|
||||
throw new ConflictException(`The name ${dto.name} is already used for another schedule preset`);
|
||||
}
|
||||
if (error.code === 'P2003' || error.code === 'P2011') {
|
||||
throw new ConflictException('Invalid constraint on preset shifts');
|
||||
}
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
//_________________________________________________________________
|
||||
// UPDATE
|
||||
//_________________________________________________________________
|
||||
private async updatePreset(
|
||||
employee_id: number,
|
||||
dto: SchedulePresetsDto,
|
||||
shifts_data: Prisma.SchedulePresetShiftsCreateWithoutPresetInput[],
|
||||
): Promise<{
|
||||
action: UpsertAction;
|
||||
preset_id?: number;
|
||||
total_items?: number;
|
||||
}> {
|
||||
const existing = await this.prisma.schedulePresets.findFirst({
|
||||
where: { employee_id, name: dto.name },
|
||||
select: { id:true, is_default: true },
|
||||
});
|
||||
if(!existing) throw new NotFoundException(`Preset "${dto.name}" not found`);
|
||||
|
||||
try {
|
||||
const result = await this.prisma.$transaction(async (tx) => {
|
||||
if(typeof dto.is_default === 'boolean'){
|
||||
if(dto.is_default) {
|
||||
await tx.schedulePresets.updateMany({
|
||||
where: { employee_id, is_default: true, NOT: { id: existing.id } },
|
||||
data: { is_default: false },
|
||||
});
|
||||
}
|
||||
await tx.schedulePresets.update({
|
||||
where: { id: existing.id },
|
||||
data: { is_default: dto.is_default },
|
||||
});
|
||||
}
|
||||
|
||||
await tx.schedulePresetShifts.deleteMany({ where: { preset_id: existing.id } });
|
||||
|
||||
const create_many_data: Prisma.SchedulePresetShiftsCreateManyInput[] =
|
||||
shifts_data.map((shift)=> {
|
||||
if(!shift.bank_code || !('connect' in shift.bank_code) || typeof shift.bank_code.connect?.id !=='number'){
|
||||
throw new NotFoundException(`Bank code is required for updates( ${shift.week_day}, ${shift.sort_order})`);
|
||||
}
|
||||
const bank_code_id = shift.bank_code.connect.id;
|
||||
return {
|
||||
preset_id: existing.id,
|
||||
week_day: shift.week_day,
|
||||
sort_order: shift.sort_order,
|
||||
start_time: shift.start_time,
|
||||
end_time: shift.end_time,
|
||||
is_remote: shift.is_remote ?? false,
|
||||
bank_code_id: bank_code_id,
|
||||
};
|
||||
});
|
||||
await tx.schedulePresetShifts.createMany({data: create_many_data});
|
||||
|
||||
const count = await tx.schedulePresetShifts.count({ where: { preset_id: existing.id } });
|
||||
return { id: existing.id, total: count };
|
||||
});
|
||||
return { action: 'update', preset_id: result.id, total_items: result.total };
|
||||
} catch (error: unknown){
|
||||
if(error instanceof Prisma.PrismaClientKnownRequestError){
|
||||
if(error?.code === 'P2003' || error?.code === 'P2011') {
|
||||
throw new ConflictException(`Invalid constraint on preset shifts`);
|
||||
}
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
//_________________________________________________________________
|
||||
// DELETE
|
||||
//_________________________________________________________________
|
||||
private async deletePreset(
|
||||
employee_id: number,
|
||||
name: string,
|
||||
): Promise<{
|
||||
action: UpsertAction;
|
||||
preset_id?: number;
|
||||
total_items?: number;
|
||||
}> {
|
||||
const existing = await this.prisma.schedulePresets.findFirst({
|
||||
where: { employee_id, name },
|
||||
select: { id: true },
|
||||
});
|
||||
if(!existing) throw new NotFoundException(`Preset "${name}" not found`);
|
||||
await this.prisma.$transaction(async (tx) => {
|
||||
await tx.schedulePresetShifts.deleteMany({ where: { preset_id: existing.id } });
|
||||
await tx.schedulePresets.delete({where: { id: existing.id } });
|
||||
});
|
||||
return { action: 'delete', preset_id: existing.id, total_items: 0 };
|
||||
}
|
||||
|
||||
//PRIVATE HELPER
|
||||
//resolve bank_code_id using type and convert hours to TIME and valid shifts end/start
|
||||
private async resolveAndBuildPresetShifts(
|
||||
dto: SchedulePresetsDto
|
||||
): Promise<Prisma.SchedulePresetShiftsCreateWithoutPresetInput[]>{
|
||||
|
||||
if(!dto.preset_shifts?.length) throw new NotFoundException(`Empty or preset shifts not found`);
|
||||
|
||||
const types = Array.from(new Set(dto.preset_shifts.map((shift)=> shift.type)));
|
||||
const bank_code_set = new Map<string, number>();
|
||||
|
||||
for (const type of types) {
|
||||
const { id } = await this.typeResolver.findByType(type);
|
||||
bank_code_set.set(type, id)
|
||||
}
|
||||
const toTime = (hhmm: string) => new Date(`1970-01-01T${hhmm}:00.000Z`);
|
||||
|
||||
const pair_set = new Set<string>();
|
||||
for (const shift of dto.preset_shifts) {
|
||||
const key = `${shift.week_day}:${shift.sort_order}`;
|
||||
if (pair_set.has(key)) {
|
||||
throw new ConflictException(`Duplicate shift for day/order (${shift.week_day}, ${shift.sort_order})`);
|
||||
}
|
||||
pair_set.add(key);
|
||||
}
|
||||
|
||||
const items: Prisma.SchedulePresetShiftsCreateWithoutPresetInput[] = dto.preset_shifts.map((shift)=> {
|
||||
const bank_code_id = bank_code_set.get(shift.type);
|
||||
if(!bank_code_id) throw new NotFoundException(`Bank code not found for type ${shift.type}`);
|
||||
if (!shift.start_time || !shift.end_time) {
|
||||
throw new BadRequestException(`start_time and end_time are required for (${shift.week_day}, ${shift.sort_order})`);
|
||||
}
|
||||
const start = toTime(shift.start_time);
|
||||
const end = toTime(shift.end_time);
|
||||
if(end.getTime() <= start.getTime()) {
|
||||
throw new ConflictException(`end_time must be > start_time ( day: ${shift.week_day}, order: ${shift.sort_order})`);
|
||||
}
|
||||
|
||||
return {
|
||||
week_day: shift.week_day as Weekday,
|
||||
sort_order: shift.sort_order,
|
||||
bank_code: { connect: { id: bank_code_id} },
|
||||
start_time: start,
|
||||
end_time: end,
|
||||
is_remote: !!shift.is_remote,
|
||||
};
|
||||
});
|
||||
return items;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
export type ShiftResponse = {
|
||||
week_day: string;
|
||||
sort_order: number;
|
||||
start_time: string;
|
||||
end_time: string;
|
||||
is_remote: boolean;
|
||||
type: string;
|
||||
};
|
||||
|
||||
export type PresetResponse = {
|
||||
id: number;
|
||||
name: string;
|
||||
is_default: boolean;
|
||||
shifts: ShiftResponse[];
|
||||
}
|
||||
|
||||
export type ApplyResult = {
|
||||
timesheet_id: number;
|
||||
created: number;
|
||||
skipped: number;
|
||||
}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
export const MS_PER_DAY = 86_400_000;
|
||||
export const MS_PER_HOUR = 3_600_000;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user