refactor(shifts): massive do-over of the whole module. exposed delete route only and simplified find and create/update functions.
This commit is contained in:
parent
bba6c84b6f
commit
7537c2ff0d
300
package-lock.json
generated
300
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.14.0",
|
||||
"@prisma/client": "^6.17.1",
|
||||
"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.0",
|
||||
"prisma": "^6.17.1",
|
||||
"source-map-support": "^0.5.21",
|
||||
"supertest": "^7.0.0",
|
||||
"ts-jest": "^29.2.5",
|
||||
|
|
@ -1751,6 +1751,15 @@
|
|||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@inquirer/ansi": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.1.tgz",
|
||||
"integrity": "sha512-yqq0aJW/5XPhi5xOAL1xRCpe1eh8UFVgYFpFsjEqmIR8rKLyP+HINvFXwUaxYICflJrVlxnp7lLN6As735kVpw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@inquirer/checkbox": {
|
||||
"version": "4.1.9",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.1.9.tgz",
|
||||
|
|
@ -1797,14 +1806,14 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@inquirer/core": {
|
||||
"version": "10.1.14",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.1.14.tgz",
|
||||
"integrity": "sha512-Ma+ZpOJPewtIYl6HZHZckeX1STvDnHTCB2GVINNUlSEn2Am6LddWwfPkIGY0IUFVjUUrr/93XlBwTK6mfLjf0A==",
|
||||
"version": "10.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.3.0.tgz",
|
||||
"integrity": "sha512-Uv2aPPPSK5jeCplQmQ9xadnFx2Zhj9b5Dj7bU6ZeCdDNNY11nhYy4btcSdtDguHqCT2h5oNeQTcUNSGGLA7NTA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@inquirer/figures": "^1.0.12",
|
||||
"@inquirer/type": "^3.0.7",
|
||||
"ansi-escapes": "^4.3.2",
|
||||
"@inquirer/ansi": "^1.0.1",
|
||||
"@inquirer/figures": "^1.0.14",
|
||||
"@inquirer/type": "^3.0.9",
|
||||
"cli-width": "^4.1.0",
|
||||
"mute-stream": "^2.0.0",
|
||||
"signal-exit": "^4.1.0",
|
||||
|
|
@ -1824,14 +1833,14 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@inquirer/editor": {
|
||||
"version": "4.2.14",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.14.tgz",
|
||||
"integrity": "sha512-yd2qtLl4QIIax9DTMZ1ZN2pFrrj+yL3kgIWxm34SS6uwCr0sIhsNyudUjAo5q3TqI03xx4SEBkUJqZuAInp9uA==",
|
||||
"version": "4.2.21",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.21.tgz",
|
||||
"integrity": "sha512-MjtjOGjr0Kh4BciaFShYpZ1s9400idOdvQ5D7u7lE6VztPFoyLcVNE5dXBmEEIQq5zi4B9h2kU+q7AVBxJMAkQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@inquirer/core": "^10.1.14",
|
||||
"@inquirer/type": "^3.0.7",
|
||||
"external-editor": "^3.1.0"
|
||||
"@inquirer/core": "^10.3.0",
|
||||
"@inquirer/external-editor": "^1.0.2",
|
||||
"@inquirer/type": "^3.0.9"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
|
|
@ -1867,10 +1876,47 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@inquirer/external-editor": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.2.tgz",
|
||||
"integrity": "sha512-yy9cOoBnx58TlsPrIxauKIFQTiyH+0MK4e97y4sV9ERbI+zDxw7i2hxHLCIEGIE/8PPvDxGhgzIOTSOWcs6/MQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"chardet": "^2.1.0",
|
||||
"iconv-lite": "^0.7.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/node": ">=18"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/node": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@inquirer/external-editor/node_modules/iconv-lite": {
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz",
|
||||
"integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/@inquirer/figures": {
|
||||
"version": "1.0.12",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.12.tgz",
|
||||
"integrity": "sha512-MJttijd8rMFcKJC8NYmprWr6hD3r9Gd9qUC0XwPNwoEPWSMVJwA2MlXxF+nhZZNMY+HXsWa+o7KY2emWYIn0jQ==",
|
||||
"version": "1.0.14",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.14.tgz",
|
||||
"integrity": "sha512-DbFgdt+9/OZYFM+19dbpXOSeAstPy884FPy1KjDu4anWwymZeOYhMY1mdFri172htv6mvc/uvIAAi7b7tvjJBQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
|
|
@ -2039,9 +2085,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@inquirer/type": {
|
||||
"version": "3.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.7.tgz",
|
||||
"integrity": "sha512-PfunHQcjwnju84L+ycmcMKB/pTPIngjUJvfnRhKY6FKPuYXlM4aQCb/nIdTFR6BEhMjFvngzvng/vBAJMZpLSA==",
|
||||
"version": "3.0.9",
|
||||
"resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.9.tgz",
|
||||
"integrity": "sha512-QPaNt/nmE2bLGQa9b7wwyRJoLZ7pN6rcyXvzU0YCmivmJyq1BVo94G98tStRWkoD1RgDX5C+dPlhhHzNdu/W/w==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
|
|
@ -3222,9 +3268,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@nestjs/common": {
|
||||
"version": "11.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.1.3.tgz",
|
||||
"integrity": "sha512-ogEK+GriWodIwCw6buQ1rpcH4Kx+G7YQ9EwuPySI3rS05pSdtQ++UhucjusSI9apNidv+QURBztJkRecwwJQXg==",
|
||||
"version": "11.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.1.6.tgz",
|
||||
"integrity": "sha512-krKwLLcFmeuKDqngG2N/RuZHCs2ycsKcxWIDgcm7i1lf3sQ0iG03ci+DsP/r3FcT/eJDFsIHnKtNta2LIi7PzQ==",
|
||||
"dependencies": {
|
||||
"file-type": "21.0.0",
|
||||
"iterare": "1.2.1",
|
||||
|
|
@ -3266,9 +3312,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@nestjs/core": {
|
||||
"version": "11.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.1.3.tgz",
|
||||
"integrity": "sha512-5lTni0TCh8x7bXETRD57pQFnKnEg1T6M+VLE7wAmyQRIecKQU+2inRGZD+A4v2DC1I04eA0WffP0GKLxjOKlzw==",
|
||||
"version": "11.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.1.6.tgz",
|
||||
"integrity": "sha512-siWX7UDgErisW18VTeJA+x+/tpNZrJewjTBsRPF3JVxuWRuAB1kRoiJcxHgln8Lb5UY9NdvklITR84DUEXD0Cg==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@nuxt/opencollective": "0.4.1",
|
||||
|
|
@ -3306,11 +3352,11 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@nestjs/jwt": {
|
||||
"version": "11.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/jwt/-/jwt-11.0.0.tgz",
|
||||
"integrity": "sha512-v7YRsW3Xi8HNTsO+jeHSEEqelX37TVWgwt+BcxtkG/OfXJEOs6GZdbdza200d6KqId1pJQZ6UPj1F0M6E+mxaA==",
|
||||
"version": "11.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/jwt/-/jwt-11.0.1.tgz",
|
||||
"integrity": "sha512-HXSsc7SAnCnjA98TsZqrE7trGtHDnYXWp4Ffy6LwSmck1QvbGYdMzBquXofX5l6tIRpeY4Qidl2Ti2CVG77Pdw==",
|
||||
"dependencies": {
|
||||
"@types/jsonwebtoken": "9.0.7",
|
||||
"@types/jsonwebtoken": "9.0.10",
|
||||
"jsonwebtoken": "9.0.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
|
@ -3366,11 +3412,11 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@nestjs/schedule": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/schedule/-/schedule-6.0.0.tgz",
|
||||
"integrity": "sha512-aQySMw6tw2nhitELXd3EiRacQRgzUKD9mFcUZVOJ7jPLqIBvXOyvRWLsK9SdurGA+jjziAlMef7iB5ZEFFoQpw==",
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/schedule/-/schedule-6.0.1.tgz",
|
||||
"integrity": "sha512-v3yO6cSPAoBSSyH67HWnXHzuhPhSNZhRmLY38JvCt2sqY8sPMOODpcU1D79iUMFf7k16DaMEbL4Mgx61ZhiC8Q==",
|
||||
"dependencies": {
|
||||
"cron": "4.3.0"
|
||||
"cron": "4.3.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@nestjs/common": "^10.0.0 || ^11.0.0",
|
||||
|
|
@ -3470,16 +3516,16 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@nestjs/swagger": {
|
||||
"version": "11.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-11.2.0.tgz",
|
||||
"integrity": "sha512-5wolt8GmpNcrQv34tIPUtPoV1EeFbCetm40Ij3+M0FNNnf2RJ3FyWfuQvI8SBlcJyfaounYVTKzKHreFXsUyOg==",
|
||||
"version": "11.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-11.2.1.tgz",
|
||||
"integrity": "sha512-1MS7xf0pzc1mofG53xrrtrurnziafPUHkqzRm4YUVPA/egeiMaSerQBD/feiAeQ2BnX0WiLsTX4HQFO0icvOjQ==",
|
||||
"dependencies": {
|
||||
"@microsoft/tsdoc": "0.15.1",
|
||||
"@nestjs/mapped-types": "2.1.0",
|
||||
"js-yaml": "4.1.0",
|
||||
"lodash": "4.17.21",
|
||||
"path-to-regexp": "8.2.0",
|
||||
"swagger-ui-dist": "5.21.0"
|
||||
"path-to-regexp": "8.3.0",
|
||||
"swagger-ui-dist": "5.29.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@fastify/static": "^8.0.0",
|
||||
|
|
@ -3501,10 +3547,19 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"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.3",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-11.1.3.tgz",
|
||||
"integrity": "sha512-CeXG6/eEqgFIkPkmU00y18Dd3DLOIDFhPItzJK1SWckKo6IhcnfoRJzGx75bmuvUMjb51j6An96S/+MJ2ty9jA==",
|
||||
"version": "11.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-11.1.6.tgz",
|
||||
"integrity": "sha512-srYzzDNxGvVCe1j0SpTS9/ix75PKt6Sn6iMaH1rpJ6nj2g8vwNrhK0CoJJXvpCYgrnI+2WES2pprYnq8rAMYHA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"tslib": "2.8.1"
|
||||
|
|
@ -3612,9 +3667,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@prisma/client": {
|
||||
"version": "6.14.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.14.0.tgz",
|
||||
"integrity": "sha512-8E/Nk3eL5g7RQIg/LUj1ICyDmhD053STjxrPxUtCRybs2s/2sOEcx9NpITuAOPn07HEpWBfhAVe1T/HYWXUPOw==",
|
||||
"version": "6.17.1",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.17.1.tgz",
|
||||
"integrity": "sha512-zL58jbLzYamjnNnmNA51IOZdbk5ci03KviXCuB0Tydc9btH2kDWsi1pQm2VecviRTM7jGia0OPPkgpGnT3nKvw==",
|
||||
"hasInstallScript": true,
|
||||
"engines": {
|
||||
"node": ">=18.18"
|
||||
|
|
@ -3633,9 +3688,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@prisma/config": {
|
||||
"version": "6.17.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.17.0.tgz",
|
||||
"integrity": "sha512-k8tuChKpkO/Vj7ZEzaQMNflNGbaW4X0r8+PC+W2JaqVRdiS2+ORSv1SrDwNxsb8YyzIQJucXqLGZbgxD97ZhsQ==",
|
||||
"version": "6.17.1",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.17.1.tgz",
|
||||
"integrity": "sha512-fs8wY6DsvOCzuiyWVckrVs1LOcbY4LZNz8ki4uUIQ28jCCzojTGqdLhN2Jl5lDnC1yI8/gNIKpsWDM8pLhOdwA==",
|
||||
"devOptional": true,
|
||||
"dependencies": {
|
||||
"c12": "3.1.0",
|
||||
|
|
@ -3645,48 +3700,48 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@prisma/debug": {
|
||||
"version": "6.17.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.17.0.tgz",
|
||||
"integrity": "sha512-eE2CB32nr1hRqyLVnOAVY6c//iSJ/PN+Yfoa/2sEzLGpORaCg61d+nvdAkYSh+6Y2B8L4BVyzkRMANLD6nnC2g==",
|
||||
"version": "6.17.1",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.17.1.tgz",
|
||||
"integrity": "sha512-Vf7Tt5Wh9XcndpbmeotuqOMLWPTjEKCsgojxXP2oxE1/xYe7PtnP76hsouG9vis6fctX+TxgmwxTuYi/+xc7dQ==",
|
||||
"devOptional": true
|
||||
},
|
||||
"node_modules/@prisma/engines": {
|
||||
"version": "6.17.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.17.0.tgz",
|
||||
"integrity": "sha512-XhE9v3hDQTNgCYMjogcCYKi7HCEkZf9WwTGuXy8cmY8JUijvU0ap4M7pGLx4pBblkp5EwUsYzw1YLtH7yi0GZw==",
|
||||
"version": "6.17.1",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.17.1.tgz",
|
||||
"integrity": "sha512-D95Ik3GYZkqZ8lSR4EyFOJ/tR33FcYRP8kK61o+WMsyD10UfJwd7+YielflHfKwiGodcqKqoraWw8ElAgMDbPw==",
|
||||
"devOptional": true,
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@prisma/debug": "6.17.0",
|
||||
"@prisma/engines-version": "6.17.0-16.c0aafc03b8ef6cdced8654b9a817999e02457d6a",
|
||||
"@prisma/fetch-engine": "6.17.0",
|
||||
"@prisma/get-platform": "6.17.0"
|
||||
"@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"
|
||||
}
|
||||
},
|
||||
"node_modules/@prisma/engines-version": {
|
||||
"version": "6.17.0-16.c0aafc03b8ef6cdced8654b9a817999e02457d6a",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.17.0-16.c0aafc03b8ef6cdced8654b9a817999e02457d6a.tgz",
|
||||
"integrity": "sha512-G0VU4uFDreATgTz4sh3dTtU2C+jn+J6c060ixavWZaUaSRZsNQhSPW26lbfez7GHzR02RGCdqs5UcSuGBC3yLw==",
|
||||
"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==",
|
||||
"devOptional": true
|
||||
},
|
||||
"node_modules/@prisma/fetch-engine": {
|
||||
"version": "6.17.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.17.0.tgz",
|
||||
"integrity": "sha512-YSl5R3WIAPrmshYPkaaszOsBIWRAovOCHn3y7gkTNGG51LjYW4pi6PFNkGouW6CA06qeTjTbGrDRCgFjnmVWDg==",
|
||||
"version": "6.17.1",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.17.1.tgz",
|
||||
"integrity": "sha512-AYZiHOs184qkDMiTeshyJCtyL4yERkjfTkJiSJdYuSfc24m94lTNL5+GFinZ6vVz+ktX4NJzHKn1zIFzGTWrWg==",
|
||||
"devOptional": true,
|
||||
"dependencies": {
|
||||
"@prisma/debug": "6.17.0",
|
||||
"@prisma/engines-version": "6.17.0-16.c0aafc03b8ef6cdced8654b9a817999e02457d6a",
|
||||
"@prisma/get-platform": "6.17.0"
|
||||
"@prisma/debug": "6.17.1",
|
||||
"@prisma/engines-version": "6.17.1-1.272a37d34178c2894197e17273bf937f25acdeac",
|
||||
"@prisma/get-platform": "6.17.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@prisma/get-platform": {
|
||||
"version": "6.17.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.17.0.tgz",
|
||||
"integrity": "sha512-3tEKChrnlmLXPd870oiVfRvj7vVKuxqP349hYaMDsbV4TZd3+lFqw8KTI2Tbq5DopamfNuNqhVCj+R6ZxKKYGQ==",
|
||||
"version": "6.17.1",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.17.1.tgz",
|
||||
"integrity": "sha512-AKEn6fsfz0r482S5KRDFlIGEaq9wLNcgalD1adL+fPcFFblIKs1sD81kY/utrHdqKuVC6E1XSRpegDK3ZLL4Qg==",
|
||||
"devOptional": true,
|
||||
"dependencies": {
|
||||
"@prisma/debug": "6.17.0"
|
||||
"@prisma/debug": "6.17.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@scarf/scarf": {
|
||||
|
|
@ -4268,17 +4323,18 @@
|
|||
"dev": true
|
||||
},
|
||||
"node_modules/@types/jsonwebtoken": {
|
||||
"version": "9.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.7.tgz",
|
||||
"integrity": "sha512-ugo316mmTYBl2g81zDFnZ7cfxlut3o+/EQdaP7J8QN2kY6lJ22hmQYCK5EHcJHbrW+dkCGSCPgbG8JtYj6qSrg==",
|
||||
"version": "9.0.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz",
|
||||
"integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==",
|
||||
"dependencies": {
|
||||
"@types/ms": "*",
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/luxon": {
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.6.2.tgz",
|
||||
"integrity": "sha512-R/BdP7OxEMc44l2Ex5lSXHoIXTB2JLNa3y2QISIbr58U/YcsffyQrYW//hZSdrfxrjRZj3GcUoxMPGdO8gSYuw=="
|
||||
"version": "3.7.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.7.1.tgz",
|
||||
"integrity": "sha512-H3iskjFIAn5SlJU7OuxUmTEpebK6TKB8rxZShDslBMZJ5u9S//KM1sbdAisiSrqwLQncVjnpi2OK2J51h+4lsg=="
|
||||
},
|
||||
"node_modules/@types/methods": {
|
||||
"version": "1.1.4",
|
||||
|
|
@ -4292,6 +4348,11 @@
|
|||
"integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/ms": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
|
||||
"integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="
|
||||
},
|
||||
"node_modules/@types/multer": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/multer/-/multer-2.0.0.tgz",
|
||||
|
|
@ -6169,9 +6230,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/chardet": {
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
|
||||
"integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.0.tgz",
|
||||
"integrity": "sha512-bNFETTG/pM5ryzQ9Ad0lJOTa6HWD/YsScAR3EnCPZRPlQh77JocYktSHOUHelyhm8IARL+o4c4F1bP5KVOjiRA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/chokidar": {
|
||||
|
|
@ -6614,12 +6675,12 @@
|
|||
"dev": true
|
||||
},
|
||||
"node_modules/cron": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/cron/-/cron-4.3.0.tgz",
|
||||
"integrity": "sha512-ciiYNLfSlF9MrDqnbMdRWFiA6oizSF7kA1osPP9lRzNu0Uu+AWog1UKy7SkckiDY2irrNjeO6qLyKnXC8oxmrw==",
|
||||
"version": "4.3.3",
|
||||
"resolved": "https://registry.npmjs.org/cron/-/cron-4.3.3.tgz",
|
||||
"integrity": "sha512-B/CJj5yL3sjtlun6RtYHvoSB26EmQ2NUmhq9ZiJSyKIM4K/fqfh9aelDFlIayD2YMeFZqWLi9hHV+c+pq2Djkw==",
|
||||
"dependencies": {
|
||||
"@types/luxon": "~3.6.0",
|
||||
"luxon": "~3.6.0"
|
||||
"@types/luxon": "~3.7.0",
|
||||
"luxon": "~3.7.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.x"
|
||||
|
|
@ -7493,32 +7554,6 @@
|
|||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/external-editor": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
|
||||
"integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"chardet": "^0.7.0",
|
||||
"iconv-lite": "^0.4.24",
|
||||
"tmp": "^0.0.33"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/external-editor/node_modules/iconv-lite": {
|
||||
"version": "0.4.24",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-check": {
|
||||
"version": "3.23.2",
|
||||
"resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz",
|
||||
|
|
@ -9648,9 +9683,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/luxon": {
|
||||
"version": "3.6.1",
|
||||
"resolved": "https://registry.npmjs.org/luxon/-/luxon-3.6.1.tgz",
|
||||
"integrity": "sha512-tJLxrKJhO2ukZ5z0gyjY1zPh3Rh88Ej9P7jNrZiHMUXHae1yvI2imgOZtL1TO8TW6biMMKfTtAOoEJANgtWBMQ==",
|
||||
"version": "3.7.2",
|
||||
"resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz",
|
||||
"integrity": "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
|
|
@ -10231,15 +10266,6 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/os-tmpdir": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
|
||||
"integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/p-cancelable": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz",
|
||||
|
|
@ -10677,14 +10703,14 @@
|
|||
}
|
||||
},
|
||||
"node_modules/prisma": {
|
||||
"version": "6.17.0",
|
||||
"resolved": "https://registry.npmjs.org/prisma/-/prisma-6.17.0.tgz",
|
||||
"integrity": "sha512-rcvldz98r+2bVCs0MldQCBaaVJRCj9Ew4IqphLATF89OJcSzwRQpwnKXR+W2+2VjK7/o2x3ffu5+2N3Muu6Dbw==",
|
||||
"version": "6.17.1",
|
||||
"resolved": "https://registry.npmjs.org/prisma/-/prisma-6.17.1.tgz",
|
||||
"integrity": "sha512-ac6h0sM1Tg3zu8NInY+qhP/S9KhENVaw9n1BrGKQVFu05JT5yT5Qqqmb8tMRIE3ZXvVj4xcRA5yfrsy4X7Yy5g==",
|
||||
"devOptional": true,
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@prisma/config": "6.17.0",
|
||||
"@prisma/engines": "6.17.0"
|
||||
"@prisma/config": "6.17.1",
|
||||
"@prisma/engines": "6.17.1"
|
||||
},
|
||||
"bin": {
|
||||
"prisma": "build/index.js"
|
||||
|
|
@ -11791,9 +11817,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/swagger-ui-dist": {
|
||||
"version": "5.21.0",
|
||||
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.21.0.tgz",
|
||||
"integrity": "sha512-E0K3AB6HvQd8yQNSMR7eE5bk+323AUxjtCz/4ZNKiahOlPhPJxqn3UPIGs00cyY/dhrTDJ61L7C/a8u6zhGrZg==",
|
||||
"version": "5.29.4",
|
||||
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.29.4.tgz",
|
||||
"integrity": "sha512-gJFDz/gyLOCQtWwAgqs6Rk78z9ONnqTnlW11gimG9nLap8drKa3AJBKpzIQMIjl5PD2Ix+Tn+mc/tfoT2tgsng==",
|
||||
"dependencies": {
|
||||
"@scarf/scarf": "=1.4.0"
|
||||
}
|
||||
|
|
@ -12055,18 +12081,6 @@
|
|||
"integrity": "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==",
|
||||
"devOptional": true
|
||||
},
|
||||
"node_modules/tmp": {
|
||||
"version": "0.0.33",
|
||||
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
|
||||
"integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"os-tmpdir": "~1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tmpl": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@
|
|||
"@nestjs/platform-express": "^11.1.6",
|
||||
"@nestjs/schedule": "^6.0.0",
|
||||
"@nestjs/swagger": "^11.2.0",
|
||||
"@prisma/client": "^6.14.0",
|
||||
"@prisma/client": "^6.17.1",
|
||||
"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.0",
|
||||
"prisma": "^6.17.1",
|
||||
"source-map-support": "^0.5.21",
|
||||
"supertest": "^7.0.0",
|
||||
"ts-jest": "^29.2.5",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { hhmmFromLocal, toDateOnly, toStringFromDate } from "src/modules/shared/helpers/date-time.helpers";
|
||||
import { BadRequestException, Injectable } from "@nestjs/common";
|
||||
import { ShiftsCommandService } from "src/modules/shifts/services/shifts-command.service";
|
||||
import { ShiftsCommandService } from "src/modules/shifts/_deprecated-files/shifts-command.service";
|
||||
import { PrismaService } from "src/prisma/prisma.service";
|
||||
import { LeaveTypes } from "@prisma/client";
|
||||
import { UpsertAction } from "src/modules/shared/types/upsert-actions.types";
|
||||
|
|
|
|||
|
|
@ -6,11 +6,11 @@ import { PayPeriodsQueryService } from "./services/pay-periods-query.service";
|
|||
import { TimesheetsModule } from "../timesheets/timesheets.module";
|
||||
import { TimesheetsCommandService } from "../timesheets/services/timesheets-command.service";
|
||||
import { ExpensesCommandService } from "../expenses/services/expenses-command.service";
|
||||
import { ShiftsCommandService } from "../shifts/services/shifts-command.service";
|
||||
import { ShiftsCommandService } from "../shifts/_deprecated-files/shifts-command.service";
|
||||
import { SharedModule } from "../shared/shared.module";
|
||||
import { PrismaService } from "src/prisma/prisma.service";
|
||||
import { BusinessLogicsModule } from "../business-logics/business-logics.module";
|
||||
import { ShiftsHelpersService } from "../shifts/helpers/shifts.helpers";
|
||||
import { ShiftsHelpersService } from "../shifts/_deprecated-files/shifts.helpers";
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule, TimesheetsModule, SharedModule, BusinessLogicsModule],
|
||||
|
|
|
|||
|
|
@ -2,10 +2,14 @@ export class Session {
|
|||
user_id: number;
|
||||
|
||||
}
|
||||
|
||||
|
||||
export class Timesheets {
|
||||
employee_fullname: string;
|
||||
timesheets: Timesheet[];
|
||||
}
|
||||
|
||||
export class Timesheet {
|
||||
timesheet_id: number;
|
||||
is_approved: boolean;
|
||||
days: TimesheetDay[];
|
||||
weekly_hours: TotalHours[];
|
||||
weekly_expenses: TotalExpenses[];
|
||||
|
|
@ -36,9 +40,9 @@ export class TotalExpenses {
|
|||
}
|
||||
|
||||
export class Shift {
|
||||
date: Date;
|
||||
start_time: Date;
|
||||
end_time: Date;
|
||||
date: string;
|
||||
start_time: string;
|
||||
end_time: string;
|
||||
type: string;
|
||||
is_remote: boolean;
|
||||
is_approved: boolean;
|
||||
|
|
|
|||
|
|
@ -1,13 +1,17 @@
|
|||
//newer version that uses Express session data
|
||||
|
||||
import { Controller } from "@nestjs/common";
|
||||
import { ShiftService } from "../services/shift.service";
|
||||
import { Controller, Delete, Param } from "@nestjs/common";
|
||||
import { ShiftsUpsertService } from "../services/shifts-upsert.service";
|
||||
import { Shifts } from "@prisma/client";
|
||||
|
||||
|
||||
|
||||
@Controller('shifts')
|
||||
@Controller('shift')
|
||||
export class ShiftController {
|
||||
constructor(private readonly service: ShiftService){}
|
||||
|
||||
constructor(
|
||||
private readonly upser_service: ShiftsUpsertService,
|
||||
){}
|
||||
|
||||
@Delete(':shift_id')
|
||||
remove(@Param('shift_id') shift_id: number): Promise<Shifts>{
|
||||
return this.upser_service.deleteShift(shift_id);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
import { Body, Controller, Delete, Get, Header, Param, ParseBoolPipe, ParseIntPipe, Patch, Put, Query, } from "@nestjs/common";
|
||||
import { RolesAllowed } from "src/common/decorators/roles.decorators";
|
||||
import { Roles as RoleEnum } from '.prisma/client';
|
||||
import { ApiBearerAuth, ApiTags } from "@nestjs/swagger";
|
||||
import { ShiftsCommandService } from "../services/shifts-command.service";
|
||||
import { ShiftsQueryService } from "../services/shifts-query.service";
|
||||
import { GetShiftsOverviewDto } from "../dtos/get-shift-overview.dto";
|
||||
import { ShiftPayloadDto, UpsertShiftDto } from "../dtos/upsert-shift.dto";
|
||||
import { OverviewRow } from "../types-and-interfaces/shifts-overview-row.interface";
|
||||
import { UpsertAction } from "src/modules/shared/types/upsert-actions.types";
|
||||
|
||||
@ApiTags('Shifts')
|
||||
@ApiBearerAuth('access-token')
|
||||
// @UseGuards()
|
||||
@Controller('shifts')
|
||||
export class ShiftsController {
|
||||
constructor(
|
||||
private readonly shiftsService: ShiftsQueryService,
|
||||
private readonly shiftsCommandService: ShiftsCommandService,
|
||||
){}
|
||||
|
||||
@Put('upsert/:email')
|
||||
async upsert_by_date(
|
||||
@Param('email') email_param: string,
|
||||
@Query('action') action: UpsertAction,
|
||||
@Body() payload: UpsertShiftDto,
|
||||
) {
|
||||
return this.shiftsCommandService.upsertShifts(email_param, action, payload);
|
||||
}
|
||||
|
||||
@Delete('delete/:email/:date')
|
||||
async remove(
|
||||
@Param('email') email: string,
|
||||
@Param('date') date: string,
|
||||
@Body() payload: UpsertShiftDto,
|
||||
) {
|
||||
return this.shiftsCommandService.deleteShift(email, date, payload);
|
||||
}
|
||||
|
||||
@Patch('approval/:id')
|
||||
//@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||
async approve(@Param('id', ParseIntPipe) id: number, @Body('is_approved', ParseBoolPipe) isApproved: boolean) {
|
||||
return this.shiftsCommandService.updateApproval(id, isApproved);
|
||||
}
|
||||
|
||||
@Get('summary')
|
||||
async getSummary( @Query() query: GetShiftsOverviewDto): Promise<OverviewRow[]> {
|
||||
return this.shiftsService.getSummary(query.period_id);
|
||||
}
|
||||
|
||||
@Get('export.csv')
|
||||
@Header('Content-Type', 'text/csv; charset=utf-8')
|
||||
@Header('Content-Disposition', 'attachment; filename="shifts-validation.csv"')
|
||||
async exportCsv(@Query() query: GetShiftsOverviewDto): Promise<Buffer>{
|
||||
const rows = await this.shiftsService.getSummary(query.period_id);
|
||||
//CSV Headers
|
||||
const header = [
|
||||
'full_name',
|
||||
'supervisor',
|
||||
'total_regular_hrs',
|
||||
'total_evening_hrs',
|
||||
'total_overtime_hrs',
|
||||
'total_expenses',
|
||||
'total_mileage',
|
||||
'is_validated'
|
||||
].join(',') + '\n';
|
||||
|
||||
//CSV rows
|
||||
const body = rows.map(r => {
|
||||
const esc = (str: string) => `"${str.replace(/"/g, '""')}"`;
|
||||
|
||||
return [
|
||||
esc(r.full_name),
|
||||
esc(r.supervisor),
|
||||
r.total_regular_hrs.toFixed(2),
|
||||
r.total_evening_hrs.toFixed(2),
|
||||
r.total_overtime_hrs.toFixed(2),
|
||||
r.total_expenses.toFixed(2),
|
||||
r.total_mileage.toFixed(2),
|
||||
r.is_approved,
|
||||
].join(',');
|
||||
}).join('\n');
|
||||
|
||||
return Buffer.from('\uFEFF' + header + body, 'utf8');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
//newer version that uses Express session data
|
||||
|
||||
import { IsBoolean, IsOptional, IsString, MaxLength } from "class-validator";
|
||||
|
||||
export class createShift {
|
||||
timesheet_id: number;
|
||||
bank_code_id: number;
|
||||
date: string;
|
||||
start_time: string;
|
||||
end_time: string;
|
||||
|
||||
@IsBoolean()
|
||||
is_remote: boolean;
|
||||
|
||||
@IsBoolean()
|
||||
is_approved: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(280)
|
||||
comment?: string;
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
import { Type } from "class-transformer";
|
||||
import { IsInt, Min, Max } from "class-validator";
|
||||
|
||||
export class GetShiftsOverviewDto {
|
||||
@Type(()=> Number)
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
@Max(26)
|
||||
period_id: number;
|
||||
}
|
||||
|
|
@ -1,14 +1,12 @@
|
|||
//newer version that uses Express session data
|
||||
|
||||
export class getShift {
|
||||
shift_id?: number;
|
||||
timesheet_id?: number;
|
||||
bank_code_id?: number;
|
||||
date?: string;
|
||||
start_time?: string;
|
||||
end_time?: string;
|
||||
is_remote?: boolean;
|
||||
is_approved?: boolean;
|
||||
export class GetShiftDto {
|
||||
timesheet_id: number;
|
||||
bank_code_id: number;
|
||||
date: string;
|
||||
start_time: string;
|
||||
end_time: string;
|
||||
is_remote: boolean;
|
||||
is_approved: boolean;
|
||||
comment?: string;
|
||||
}
|
||||
|
||||
}
|
||||
17
src/modules/shifts/dtos/shift.dto.ts
Normal file
17
src/modules/shifts/dtos/shift.dto.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
//newer version that uses Express session data
|
||||
|
||||
import { IsBoolean, IsInt, IsOptional, IsString, MaxLength } from "class-validator";
|
||||
|
||||
export class ShiftDto {
|
||||
@IsInt() timesheet_id!: number;
|
||||
@IsInt() bank_code_id!: number;
|
||||
|
||||
@IsString() date!: string;
|
||||
@IsString() start_time!: string;
|
||||
@IsString() end_time!: string;
|
||||
|
||||
@IsBoolean() is_approved!: boolean;
|
||||
@IsBoolean() is_remote!: boolean;
|
||||
|
||||
@IsOptional() @IsString() @MaxLength(280) comment?: string;
|
||||
}
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
//newer version that uses Express session data
|
||||
|
||||
export class updateShift {
|
||||
import { PartialType, OmitType } from "@nestjs/swagger";
|
||||
import { ShiftDto } from "./shift.dto";
|
||||
|
||||
date!: string;
|
||||
start_time!: string;
|
||||
end_time!: string;
|
||||
|
||||
}
|
||||
export class updateShiftDto extends PartialType (
|
||||
//allows update using ShiftDto and preventing OmitType variables to be modified
|
||||
OmitType(ShiftDto, [ 'is_approved', 'timesheet_id'] as const)
|
||||
){}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
import { Type } from "class-transformer";
|
||||
import { IsBoolean, IsOptional, IsString, Matches, MaxLength, ValidateNested } from "class-validator";
|
||||
|
||||
export const COMMENT_MAX_LENGTH = 280;
|
||||
|
||||
export class ShiftPayloadDto {
|
||||
|
||||
@Matches(/^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/)
|
||||
date!: string;
|
||||
|
||||
@Matches(/^([01]\d|2[0-3]):([0-5]\d)$/)
|
||||
start_time!: string;
|
||||
|
||||
@Matches(/^([01]\d|2[0-3]):([0-5]\d)$/)
|
||||
end_time!: string;
|
||||
|
||||
@IsString()
|
||||
type!: string;
|
||||
|
||||
@IsBoolean()
|
||||
is_remote!: boolean;
|
||||
|
||||
@IsBoolean()
|
||||
is_approved!: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(COMMENT_MAX_LENGTH)
|
||||
comment?: string;
|
||||
};
|
||||
|
||||
export class UpsertShiftDto {
|
||||
|
||||
@IsOptional()
|
||||
@ValidateNested()
|
||||
@Type(()=> ShiftPayloadDto)
|
||||
old_shift?: ShiftPayloadDto;
|
||||
|
||||
@IsOptional()
|
||||
@ValidateNested()
|
||||
@Type(()=> ShiftPayloadDto)
|
||||
new_shift?: ShiftPayloadDto;
|
||||
};
|
||||
|
|
@ -1,15 +1,3 @@
|
|||
export function timeFromHHMM(hhmm: string): Date {
|
||||
const [h, m] = hhmm.split(':').map(Number);
|
||||
return new Date(1970, 0, 1, h, m, 0, 0);
|
||||
}
|
||||
|
||||
export function toDateOnly(ymd: string): Date {
|
||||
const y = Number(ymd.slice(0, 4));
|
||||
const m = Number(ymd.slice(5, 7)) - 1;
|
||||
const d = Number(ymd.slice(8, 10));
|
||||
return new Date(y, m, d, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
export function weekStartSunday(date_local: Date): Date {
|
||||
const start = new Date(Date.UTC(date_local.getFullYear(), date_local.getMonth(), date_local.getDate()));
|
||||
const dow = start.getDay(); // 0 = dimanche
|
||||
|
|
@ -18,8 +6,26 @@ export function weekStartSunday(date_local: Date): Date {
|
|||
return start;
|
||||
}
|
||||
|
||||
export function formatHHmm(t: Date): string {
|
||||
const hh = String(t.getHours()).padStart(2, '0');
|
||||
const mm = String(t.getMinutes()).padStart(2, '0');
|
||||
//converts string to HHmm format
|
||||
export const toStringFromHHmm = (date: Date): string => {
|
||||
const hh = date.getUTCHours().toString().padStart(2, '0');
|
||||
const mm = date.getUTCMinutes().toString().padStart(2, '0');
|
||||
return `${hh}:${mm}`;
|
||||
}
|
||||
|
||||
//converts string to Date format
|
||||
export const toStringFromDate = (date: Date) =>
|
||||
date.toISOString().slice(0,10);
|
||||
|
||||
//converts HHmm format to string
|
||||
export const toHHmmFromString = (hhmm: string): Date => {
|
||||
const [hh, mm] = hhmm.split(':').map(Number);
|
||||
const date = new Date('1970-01-01T00:00:00.000Z');
|
||||
date.setUTCHours(hh, mm, 0, 0);
|
||||
return new Date(date);
|
||||
}
|
||||
|
||||
//converts Date format to string
|
||||
export const toDateFromString = (ymd: string): Date => {
|
||||
return new Date(`${ymd}T00:00:00:000Z`);
|
||||
}
|
||||
|
|
@ -1,138 +0,0 @@
|
|||
import { BadRequestException, UnprocessableEntityException, NotFoundException, ConflictException } from "@nestjs/common";
|
||||
import { Prisma, Shifts } from "@prisma/client";
|
||||
import { UpsertShiftDto } from "../dtos/upsert-shift.dto";
|
||||
import { DayShiftResponse } from "../types-and-interfaces/shifts-upsert.types";
|
||||
import { normalizeShiftPayload, overlaps } from "../utils/shifts.utils";
|
||||
import { weekStartSunday, formatHHmm } from "./shifts-date-time-helpers";
|
||||
import { BankCodesResolver } from "src/modules/shared/utils/resolve-bank-type-id.utils";
|
||||
import { OvertimeService } from "src/modules/business-logics/services/overtime.service";
|
||||
|
||||
|
||||
export type Tx = Prisma.TransactionClient;
|
||||
export type Normalized = Awaited<ReturnType<typeof normalizeShiftPayload>>;
|
||||
|
||||
export class ShiftsHelpersService {
|
||||
|
||||
constructor(
|
||||
private readonly bankTypeResolver: BankCodesResolver,
|
||||
private readonly overtimeService: OvertimeService,
|
||||
) { }
|
||||
|
||||
async ensureTimesheet(tx: Tx, employee_id: number, date_only: Date) {
|
||||
const start_of_week = weekStartSunday(date_only);
|
||||
return tx.timesheets.findUnique({
|
||||
where: { employee_id_start_date: { employee_id, start_date: start_of_week } },
|
||||
select: { id: true },
|
||||
});
|
||||
}
|
||||
|
||||
async normalizeRequired(
|
||||
raw: UpsertShiftDto['new_shift'] | UpsertShiftDto['old_shift'] | undefined | null,
|
||||
label: 'old_shift' | 'new_shift' = 'new_shift',
|
||||
): Promise<Normalized> {
|
||||
if (!raw) throw new BadRequestException(`${label} is required`);
|
||||
const norm = await normalizeShiftPayload(raw);
|
||||
if (norm.end_time.getTime() <= norm.start_time.getTime()) {
|
||||
throw new UnprocessableEntityException(` ${label}.end_time must be > ${label}.start_time`);
|
||||
}
|
||||
return norm;
|
||||
}
|
||||
|
||||
async resolveBankIdRequired(tx: Tx, type: string, label: 'old_shift' | 'new_shift'): Promise<number> {
|
||||
const found = await this.bankTypeResolver.findByType(type, tx);
|
||||
const id = found?.id;
|
||||
if (typeof id !== 'number') {
|
||||
throw new NotFoundException(`bank code not found for ${label}.type: ${type ?? ''}`);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
async getDayShifts(tx: Tx, timesheet_id: number, date_only: Date) {
|
||||
return tx.shifts.findMany({
|
||||
where: { timesheet_id, date: date_only },
|
||||
include: { bank_code: true },
|
||||
orderBy: { start_time: 'asc' },
|
||||
});
|
||||
}
|
||||
|
||||
async assertNoOverlap(
|
||||
day_shifts: Array<Shifts & { bank_code: { type: string } | null }>,
|
||||
new_norm: Normalized | undefined,
|
||||
exclude_id?: number,
|
||||
) {
|
||||
if (!new_norm) return;
|
||||
const conflicts = day_shifts.filter((s) => {
|
||||
if (exclude_id && s.id === exclude_id) return false;
|
||||
return overlaps(
|
||||
new_norm.start_time.getTime(),
|
||||
new_norm.end_time.getTime(),
|
||||
s.start_time.getTime(),
|
||||
s.end_time.getTime(),
|
||||
);
|
||||
});
|
||||
if (conflicts.length) {
|
||||
const payload = conflicts.map((s) => ({
|
||||
start_time: formatHHmm(s.start_time),
|
||||
end_time: formatHHmm(s.end_time),
|
||||
type: s.bank_code?.type ?? 'UNKNOWN',
|
||||
}));
|
||||
throw new ConflictException({
|
||||
error_code: 'SHIFT_OVERLAP',
|
||||
message: 'New shift overlaps with existing shift(s)',
|
||||
conflicts: payload,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async findExactOldShift(
|
||||
tx: Tx,
|
||||
params: {
|
||||
timesheet_id: number;
|
||||
date_only: Date;
|
||||
norm: Normalized;
|
||||
bank_code_id: number;
|
||||
comment?: string;
|
||||
},
|
||||
) {
|
||||
const { timesheet_id, date_only, norm, bank_code_id } = params;
|
||||
return tx.shifts.findFirst({
|
||||
where: {
|
||||
timesheet_id,
|
||||
date: date_only,
|
||||
start_time: norm.start_time,
|
||||
end_time: norm.end_time,
|
||||
is_remote: norm.is_remote,
|
||||
is_approved: norm.is_approved,
|
||||
comment: norm.comment ?? null,
|
||||
bank_code_id,
|
||||
},
|
||||
select: { id: true },
|
||||
});
|
||||
}
|
||||
|
||||
async afterWriteOvertimeAndLog(tx: Tx, employee_id: number, date_only: Date) {
|
||||
// Switch regular → weekly overtime si > 40h
|
||||
await this.overtimeService.transformRegularHoursToWeeklyOvertime(employee_id, date_only, tx);
|
||||
const daily = await this.overtimeService.getDailyOvertimeHours(employee_id, date_only);
|
||||
const weekly = await this.overtimeService.getWeeklyOvertimeHours(employee_id, date_only);
|
||||
// const [daily, weekly] = await Promise.all([
|
||||
// this.overtimeService.getDailyOvertimeHoursForDay(employee_id, date_only),
|
||||
// this.overtimeService.getWeeklyOvertimeHours(employee_id, date_only),
|
||||
// ]);
|
||||
return { daily, weekly };
|
||||
}
|
||||
|
||||
async mapDay(
|
||||
fresh: Array<Shifts & { bank_code: { type: string } | null }>,
|
||||
): Promise<DayShiftResponse[]> {
|
||||
return fresh.map((s) => ({
|
||||
start_time: formatHHmm(s.start_time),
|
||||
end_time: formatHHmm(s.end_time),
|
||||
type: s.bank_code?.type ?? 'UNKNOWN',
|
||||
is_remote: s.is_remote,
|
||||
comment: s.comment ?? null,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
//newer version that uses Express session data
|
||||
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { PrismaService } from "src/prisma/prisma.service";
|
||||
|
||||
@Injectable()
|
||||
export class ShiftService {
|
||||
constructor(private readonly prisma: PrismaService){}
|
||||
}
|
||||
|
|
@ -1,194 +0,0 @@
|
|||
import { BadRequestException, Injectable, Logger, NotFoundException } from "@nestjs/common";
|
||||
import { DayShiftResponse } from "../types-and-interfaces/shifts-upsert.types";
|
||||
import { EmailToIdResolver } from "src/modules/shared/utils/resolve-email-id.utils";
|
||||
import { Prisma, Shifts } from "@prisma/client";
|
||||
import { UpsertShiftDto } from "../dtos/upsert-shift.dto";
|
||||
import { BaseApprovalService } from "src/common/shared/base-approval.service";
|
||||
import { PrismaService } from "src/prisma/prisma.service";
|
||||
import { toDateOnly } from "../helpers/shifts-date-time-helpers";
|
||||
import { UpsertAction } from "src/modules/shared/types/upsert-actions.types";
|
||||
import { ShiftsHelpersService } from "../helpers/shifts.helpers";
|
||||
import { BankCodesResolver } from "src/modules/shared/utils/resolve-bank-type-id.utils";
|
||||
|
||||
@Injectable()
|
||||
export class ShiftsCommandService extends BaseApprovalService<Shifts> {
|
||||
private readonly logger = new Logger(ShiftsCommandService.name);
|
||||
|
||||
constructor(
|
||||
prisma: PrismaService,
|
||||
private readonly emailResolver: EmailToIdResolver,
|
||||
private readonly typeResolver: BankCodesResolver,
|
||||
private readonly helpersService: ShiftsHelpersService,
|
||||
) { super(prisma); }
|
||||
|
||||
//_____________________________________________________________________________________________
|
||||
// APPROVAL AND DELEGATE METHODS
|
||||
//_____________________________________________________________________________________________
|
||||
protected get delegate() {
|
||||
return this.prisma.shifts;
|
||||
}
|
||||
|
||||
protected delegateFor(transaction: Prisma.TransactionClient) {
|
||||
return transaction.shifts;
|
||||
}
|
||||
|
||||
async updateApproval(id: number, is_approved: boolean): Promise<Shifts> {
|
||||
return this.prisma.$transaction((transaction) =>
|
||||
this.updateApprovalWithTransaction(transaction, id, is_approved),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
//TODO: modifier le Master Crud pour recevoir l'ensemble des shifts de la pay-period et trier sur l'action 'create'| 'update' | 'delete'
|
||||
//_____________________________________________________________________________________________
|
||||
// MASTER CRUD METHOD
|
||||
//_____________________________________________________________________________________________
|
||||
async upsertShifts(
|
||||
email: string,
|
||||
action: UpsertAction,
|
||||
dto: UpsertShiftDto,
|
||||
): Promise<{
|
||||
action: UpsertAction;
|
||||
day: DayShiftResponse[];
|
||||
}> {
|
||||
if (!dto.old_shift && !dto.new_shift) throw new BadRequestException('At least one of old or new shift must be provided');
|
||||
|
||||
const date = dto.new_shift?.date ?? dto.old_shift?.date;
|
||||
if (!date) throw new BadRequestException("A date (YYYY-MM-DD) must be provided in old_shift or new_shift");
|
||||
if (dto.old_shift?.date && dto.new_shift?.date && dto.old_shift.date !== dto.new_shift.date) {
|
||||
throw new BadRequestException('old_shift.date and new_shift.date must be identical');
|
||||
}
|
||||
|
||||
const employee_id = await this.emailResolver.findIdByEmail(email);//resolve employee id using email
|
||||
|
||||
if(action === 'create') {
|
||||
if(!dto.new_shift || dto.old_shift) {
|
||||
throw new BadRequestException(`Only new_shift must be provided for create`);
|
||||
}
|
||||
return this.createShift(employee_id, date, dto);
|
||||
}
|
||||
if(action === 'update'){
|
||||
if(!dto.old_shift || !dto.new_shift) {
|
||||
throw new BadRequestException(`Both new_shift and old_shift must be provided for update`);
|
||||
}
|
||||
return this.updateShift(employee_id, date, dto);
|
||||
}
|
||||
throw new BadRequestException(`Unknown action: ${action}`);
|
||||
}
|
||||
|
||||
//_________________________________________________________________
|
||||
// CREATE
|
||||
//_________________________________________________________________
|
||||
private async createShift(
|
||||
employee_id: number,
|
||||
date_iso: string,
|
||||
dto: UpsertShiftDto,
|
||||
): Promise<{action: UpsertAction; day: DayShiftResponse[]}> {
|
||||
return this.prisma.$transaction(async (tx) => {
|
||||
const date_only = toDateOnly(date_iso);
|
||||
const timesheet = await this.helpersService.ensureTimesheet(tx, employee_id, date_only);
|
||||
if(!timesheet) throw new NotFoundException('Timesheet not found')
|
||||
const new_norm_shift = await this.helpersService.normalizeRequired(dto.new_shift);
|
||||
const new_bank_code_id = await this.helpersService.resolveBankIdRequired(tx, new_norm_shift.type, 'new_shift');
|
||||
|
||||
const day_shifts = await this.helpersService.getDayShifts(tx, timesheet.id, date_only);
|
||||
|
||||
await this.helpersService.assertNoOverlap(day_shifts, new_norm_shift);
|
||||
|
||||
await tx.shifts.create({
|
||||
data: {
|
||||
timesheet_id: timesheet.id,
|
||||
date: date_only,
|
||||
start_time: new_norm_shift.start_time,
|
||||
end_time: new_norm_shift.end_time,
|
||||
is_remote: new_norm_shift.is_remote,
|
||||
is_approved: new_norm_shift.is_approved,
|
||||
comment: new_norm_shift.comment ?? null,
|
||||
bank_code_id: new_bank_code_id,
|
||||
},
|
||||
});
|
||||
await this.helpersService.afterWriteOvertimeAndLog(tx, employee_id, date_only);
|
||||
const fresh_shift = await this.helpersService.getDayShifts(tx, timesheet.id, date_only);
|
||||
return { action: 'create', day: await this.helpersService.mapDay(fresh_shift)};
|
||||
});
|
||||
}
|
||||
|
||||
//_________________________________________________________________
|
||||
// UPDATE
|
||||
//_________________________________________________________________
|
||||
private async updateShift(
|
||||
employee_id: number,
|
||||
date_iso: string,
|
||||
dto: UpsertShiftDto,
|
||||
): Promise<{ action: UpsertAction; day: DayShiftResponse[];}>{
|
||||
return this.prisma.$transaction(async (tx) => {
|
||||
const date_only = toDateOnly(date_iso);
|
||||
const timesheet = await this.helpersService.ensureTimesheet(tx, employee_id, date_only);
|
||||
if(!timesheet) throw new NotFoundException('Timesheet not found')
|
||||
|
||||
const old_norm_shift = await this.helpersService.normalizeRequired(dto.old_shift, 'old_shift');
|
||||
const new_norm_shift = await this.helpersService.normalizeRequired(dto.new_shift, 'new_shift');
|
||||
|
||||
const old_bank_code = await this.typeResolver.findByType(old_norm_shift.type);
|
||||
const new_bank_code = await this.typeResolver.findByType(new_norm_shift.type);
|
||||
|
||||
const day_shifts = await this.helpersService.getDayShifts(tx, timesheet.id, date_only);
|
||||
const existing = await this.helpersService.findExactOldShift(tx, {
|
||||
timesheet_id: timesheet.id,
|
||||
date_only,
|
||||
norm: old_norm_shift,
|
||||
bank_code_id: old_bank_code.id,
|
||||
});
|
||||
if(!existing) throw new NotFoundException('[SHIFT_STALE]- The shift was modified or deleted by someone else');
|
||||
|
||||
await this.helpersService.assertNoOverlap(day_shifts, new_norm_shift, existing.id);
|
||||
|
||||
await tx.shifts.update({
|
||||
where: { id: existing.id },
|
||||
data: {
|
||||
start_time: new_norm_shift.start_time,
|
||||
end_time: new_norm_shift.end_time,
|
||||
is_remote: new_norm_shift.is_remote,
|
||||
comment: new_norm_shift.comment ?? null,
|
||||
bank_code_id: new_bank_code.id,
|
||||
},
|
||||
});
|
||||
await this.helpersService.afterWriteOvertimeAndLog(tx, employee_id, date_only);
|
||||
const fresh_shift = await this.helpersService.getDayShifts(tx, timesheet.id, date_only);
|
||||
return { action: 'update', day: await this.helpersService.mapDay(fresh_shift)};
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
//_________________________________________________________________
|
||||
// DELETE
|
||||
//_________________________________________________________________
|
||||
async deleteShift(
|
||||
email: string,
|
||||
date_iso: string,
|
||||
dto: UpsertShiftDto,
|
||||
){
|
||||
return this.prisma.$transaction(async (tx) => {
|
||||
const date_only = toDateOnly(date_iso); //converts to Date format
|
||||
const employee_id = await this.emailResolver.findIdByEmail(email);
|
||||
|
||||
const timesheet = await this.helpersService.ensureTimesheet(tx, employee_id, date_only);
|
||||
if(!timesheet) throw new NotFoundException('Timesheet not found')
|
||||
const norm_shift = await this.helpersService.normalizeRequired(dto.old_shift, 'old_shift');
|
||||
const bank_code_id = await this.typeResolver.findByType(norm_shift.type);
|
||||
|
||||
const existing = await this.helpersService.findExactOldShift(tx, {
|
||||
timesheet_id: timesheet.id,
|
||||
date_only,
|
||||
norm: norm_shift,
|
||||
bank_code_id: bank_code_id.id,
|
||||
});
|
||||
if(!existing) throw new NotFoundException('[SHIFT_STALE]- The shift was modified or deleted by someone else');
|
||||
|
||||
await tx.shifts.delete({ where: { id: existing.id } });
|
||||
|
||||
await this.helpersService.afterWriteOvertimeAndLog(tx, employee_id, date_only);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
50
src/modules/shifts/services/shifts-get.service.ts
Normal file
50
src/modules/shifts/services/shifts-get.service.ts
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
import { Injectable, NotFoundException } from "@nestjs/common";
|
||||
import { PrismaService } from "src/prisma/prisma.service";
|
||||
import { GetShiftDto } from "../dtos/get-shift.dto";
|
||||
import { toStringFromDate, toStringFromHHmm } from "../helpers/shifts-date-time-helpers";
|
||||
import { Shifts } from "@prisma/client";
|
||||
|
||||
@Injectable()
|
||||
export class ShiftsGetService {
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
){}
|
||||
|
||||
//fetch a shift using shift_id and return all that shift's info
|
||||
async getShiftByShiftId(shift_id: number): Promise<GetShiftDto> {
|
||||
const shift = await this.prisma.shifts.findUnique({
|
||||
where: { id: shift_id },
|
||||
select: {
|
||||
timesheet_id: true,
|
||||
bank_code_id: true,
|
||||
date: true,
|
||||
start_time: true,
|
||||
end_time: true,
|
||||
is_remote: true,
|
||||
is_approved: true,
|
||||
comment: true,
|
||||
}
|
||||
});
|
||||
if(!shift) throw new NotFoundException(`Shift with id #${shift_id} not found`);
|
||||
|
||||
return {
|
||||
timesheet_id: shift.timesheet_id,
|
||||
bank_code_id: shift.bank_code_id,
|
||||
date: toStringFromDate(shift.date),
|
||||
start_time: toStringFromHHmm(shift.start_time),
|
||||
end_time: toStringFromHHmm(shift.end_time),
|
||||
is_remote: shift.is_remote,
|
||||
is_approved: shift.is_approved,
|
||||
comment: shift.comment ?? undefined,
|
||||
};
|
||||
}
|
||||
|
||||
//finds all shifts in a single day to return an array of shifts
|
||||
async loadShiftsFromSameDay( timesheet_id: number, date_only: Date,
|
||||
): Promise<Array<Shifts & { bank_code: { type: string } | null }>> {
|
||||
return this.prisma.shifts.findMany({
|
||||
where: { timesheet_id, date: date_only },
|
||||
include: { bank_code: { select: { type: true } } },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,114 +0,0 @@
|
|||
import { Injectable, NotFoundException } from "@nestjs/common";
|
||||
import { PrismaService } from "src/prisma/prisma.service";
|
||||
import { NotificationsService } from "src/modules/notifications/services/notifications.service";
|
||||
import { computeHours } from "src/common/utils/date-utils";
|
||||
import { OverviewRow } from "../types-and-interfaces/shifts-overview-row.interface";
|
||||
|
||||
// const DAILY_LIMIT_HOURS = Number(process.env.DAILY_LIMIT_HOURS ?? 12);
|
||||
|
||||
@Injectable()
|
||||
export class ShiftsQueryService {
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly notifs: NotificationsService,
|
||||
) {}
|
||||
|
||||
async getSummary(period_id: number): Promise<OverviewRow[]> {
|
||||
//fetch pay-period to display
|
||||
const period = await this.prisma.payPeriods.findFirst({
|
||||
where: { pay_period_no: period_id },
|
||||
});
|
||||
if(!period) {
|
||||
throw new NotFoundException(`pay-period ${period_id} not found`);
|
||||
}
|
||||
const { period_start, period_end } = period;
|
||||
|
||||
//prepare shifts and expenses for display
|
||||
const shifts = await this.prisma.shifts.findMany({
|
||||
where: { date: { gte: period_start, lte: period_end } },
|
||||
include: {
|
||||
bank_code: true,
|
||||
timesheet: { include: {
|
||||
employee: { include: {
|
||||
user:true,
|
||||
supervisor: { include: { user: true } },
|
||||
} },
|
||||
} },
|
||||
},
|
||||
});
|
||||
|
||||
const expenses = await this.prisma.expenses.findMany({
|
||||
where: { date: { gte: period_start, lte: period_end } },
|
||||
include: {
|
||||
bank_code: true,
|
||||
timesheet: { include: { employee: {
|
||||
include: { user:true,
|
||||
supervisor: { include: { user:true } },
|
||||
} },
|
||||
} },
|
||||
},
|
||||
});
|
||||
|
||||
const mapRow = new Map<string, OverviewRow>();
|
||||
|
||||
for(const shift of shifts) {
|
||||
const employeeId = shift.timesheet.employee.user_id;
|
||||
const user = shift.timesheet.employee.user;
|
||||
const sup = shift.timesheet.employee.supervisor?.user;
|
||||
|
||||
let row = mapRow.get(employeeId);
|
||||
if(!row) {
|
||||
row = {
|
||||
full_name: `${user.first_name} ${user.last_name}`,
|
||||
supervisor: sup? `${sup.first_name} ${sup.last_name }` : '',
|
||||
total_regular_hrs: 0,
|
||||
total_evening_hrs: 0,
|
||||
total_overtime_hrs: 0,
|
||||
total_expenses: 0,
|
||||
total_mileage: 0,
|
||||
is_approved: false,
|
||||
};
|
||||
}
|
||||
const hours = computeHours(shift.start_time, shift.end_time);
|
||||
|
||||
switch(shift.bank_code.type) {
|
||||
case 'regular' : row.total_regular_hrs += hours;
|
||||
break;
|
||||
case 'evening' : row.total_evening_hrs += hours;
|
||||
break;
|
||||
case 'overtime' : row.total_overtime_hrs += hours;
|
||||
break;
|
||||
default: row.total_regular_hrs += hours;
|
||||
}
|
||||
mapRow.set(employeeId, row);
|
||||
}
|
||||
|
||||
for(const exp of expenses) {
|
||||
const employee_id = exp.timesheet.employee.user_id;
|
||||
const user = exp.timesheet.employee.user;
|
||||
const sup = exp.timesheet.employee.supervisor?.user;
|
||||
|
||||
let row = mapRow.get(employee_id);
|
||||
if(!row) {
|
||||
row = {
|
||||
full_name: `${user.first_name} ${user.last_name}`,
|
||||
supervisor: sup? `${sup.first_name} ${sup.last_name }` : '',
|
||||
total_regular_hrs: 0,
|
||||
total_evening_hrs: 0,
|
||||
total_overtime_hrs: 0,
|
||||
total_expenses: 0,
|
||||
total_mileage: 0,
|
||||
is_approved: false,
|
||||
};
|
||||
}
|
||||
const amount = Number(exp.amount);
|
||||
row.total_expenses += amount;
|
||||
if(exp.bank_code.type === 'mileage') {
|
||||
row.total_mileage += amount;
|
||||
}
|
||||
mapRow.set(employee_id, row);
|
||||
}
|
||||
//return by default the list of employee in ascending alphabetical order
|
||||
return Array.from(mapRow.values()).sort((a,b) => a.full_name.localeCompare(b.full_name));
|
||||
}
|
||||
}
|
||||
208
src/modules/shifts/services/shifts-upsert.service.ts
Normal file
208
src/modules/shifts/services/shifts-upsert.service.ts
Normal file
|
|
@ -0,0 +1,208 @@
|
|||
import { toDateFromString, toHHmmFromString, toStringFromDate, toStringFromHHmm } from "../helpers/shifts-date-time-helpers";
|
||||
import { BadRequestException, ConflictException, Injectable, NotFoundException } from "@nestjs/common";
|
||||
import { ShiftsGetService } from "./shifts-get.service";
|
||||
import { updateShiftDto } from "../dtos/update-shift.dto";
|
||||
import { PrismaService } from "src/prisma/prisma.service";
|
||||
import { GetShiftDto } from "../dtos/get-shift.dto";
|
||||
import { ShiftDto } from "../dtos/shift.dto";
|
||||
import { Shifts } from "@prisma/client";
|
||||
|
||||
type Normalized = { date: Date; start_time: Date; end_time: Date; };
|
||||
|
||||
@Injectable()
|
||||
export class ShiftsUpsertService {
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly getService: ShiftsGetService,
|
||||
){}
|
||||
|
||||
//converts all string hours and date to Date and HHmm formats
|
||||
private normalizeShiftDto = (dto: ShiftDto): Normalized => {
|
||||
const date = toDateFromString(dto.date);
|
||||
const start_time = toHHmmFromString(dto.start_time);
|
||||
const end_time = toHHmmFromString(dto.end_time);
|
||||
return { date, start_time, end_time };
|
||||
}
|
||||
|
||||
// used to compare shifts and detect overlaps between them
|
||||
private overlaps = (
|
||||
a_start: number,
|
||||
a_end: number,
|
||||
b_start: number,
|
||||
b_end: number,
|
||||
) => a_start < b_end && b_start < a_end;
|
||||
|
||||
//checked if a new shift overlaps already existing shifts
|
||||
private assertNoOverlap = async (
|
||||
day_shifts: Array<Shifts & { bank_code: { type: string } | null }>,
|
||||
new_norm: Normalized | undefined,
|
||||
exclude_id?: number,
|
||||
) => {
|
||||
if (!new_norm) return;
|
||||
const conflicts = day_shifts.filter((shift) => {
|
||||
if (exclude_id && shift.id === exclude_id) return false;
|
||||
return this.overlaps(
|
||||
new_norm.start_time.getTime(),
|
||||
new_norm.end_time.getTime(),
|
||||
shift.start_time.getTime(),
|
||||
shift.end_time.getTime(),
|
||||
);
|
||||
});
|
||||
if (conflicts.length) {
|
||||
const payload = conflicts.map((shift) => ({
|
||||
start_time: toStringFromHHmm(shift.start_time),
|
||||
end_time: toStringFromHHmm(shift.end_time),
|
||||
type: shift.bank_code?.type ?? 'UNKNOWN',
|
||||
}));
|
||||
throw new ConflictException({
|
||||
error_code: 'SHIFT_OVERLAP',
|
||||
message: 'New shift overlaps with existing shift(s)',
|
||||
conflicts: payload,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//normalized frontend data to match DB
|
||||
//loads all shift from a selected day to check for overlaping shifts
|
||||
//checks for overlaping shifts
|
||||
//create a new shifts
|
||||
//return an object of type GetShiftDto for the frontend to display
|
||||
async createShift(timesheet_id: number, dto: ShiftDto): Promise<GetShiftDto> {
|
||||
const normed_shift = await this.normalizeShiftDto(dto);
|
||||
if(normed_shift.end_time <= normed_shift.start_time){
|
||||
throw new BadRequestException('end_time must be greater than start_time')
|
||||
}
|
||||
|
||||
//call to a function to load all shifts contain in single day
|
||||
const day_shifts = await this.getService.loadShiftsFromSameDay(timesheet_id, normed_shift.date);
|
||||
|
||||
//call to a function to detect overlaps between shifts
|
||||
await this.assertNoOverlap( day_shifts, normed_shift )
|
||||
|
||||
//create the shift with normalized date and times
|
||||
const shift = await this.prisma.shifts.create({
|
||||
data: {
|
||||
timesheet_id,
|
||||
bank_code_id: dto.bank_code_id,
|
||||
date: normed_shift.date,
|
||||
start_time: normed_shift.start_time,
|
||||
end_time: normed_shift.end_time,
|
||||
is_remote: dto.is_remote,
|
||||
comment: dto.comment ?? undefined,
|
||||
},
|
||||
select: {
|
||||
timesheet_id: true,
|
||||
bank_code_id: true,
|
||||
date: true,
|
||||
start_time: true,
|
||||
end_time: true,
|
||||
is_remote: true,
|
||||
comment: true,
|
||||
},
|
||||
});
|
||||
if(!shift) throw new BadRequestException(`a shift cannot be created, missing value(s).`);
|
||||
|
||||
return {
|
||||
timesheet_id: shift.timesheet_id,
|
||||
bank_code_id: shift.bank_code_id,
|
||||
date: toStringFromDate(shift.date),
|
||||
start_time: toStringFromHHmm(shift.start_time),
|
||||
end_time: toStringFromHHmm(shift.end_time),
|
||||
is_remote: shift.is_remote,
|
||||
is_approved: false,
|
||||
comment: shift.comment ?? undefined,
|
||||
};
|
||||
}
|
||||
|
||||
//finds existing shift in DB
|
||||
//verify if shift is already approved
|
||||
//normalized Date and Time format to string
|
||||
//check for valid start and end times
|
||||
//check for overlaping possibility
|
||||
//buil a set of data to manipulate modified data only
|
||||
//update shift in DB and return an updated version to display
|
||||
async updateShift(shift_id: number, dto: updateShiftDto): Promise<GetShiftDto> {
|
||||
//search for original shift using shift_id
|
||||
const existing = await this.prisma.shifts.findUnique({
|
||||
where: { id: shift_id },
|
||||
select: {
|
||||
id: true,
|
||||
timesheet_id: true,
|
||||
bank_code_id: true,
|
||||
date: true,
|
||||
start_time: true,
|
||||
end_time: true,
|
||||
is_remote: true,
|
||||
is_approved: true,
|
||||
comment: true,
|
||||
},
|
||||
});
|
||||
if(!existing) throw new NotFoundException(`Shift with id: ${shift_id} not found`);
|
||||
if(existing.is_approved) throw new BadRequestException('Approved shift cannot be updated');
|
||||
|
||||
const date_string = dto.date ?? toStringFromDate(existing.date);
|
||||
const start_string = dto.start_time ?? toStringFromHHmm(existing.start_time);
|
||||
const end_string = dto.end_time ?? toStringFromHHmm(existing.end_time);
|
||||
|
||||
const norm: Normalized = {
|
||||
date: toDateFromString(date_string),
|
||||
start_time: toHHmmFromString(start_string),
|
||||
end_time: toHHmmFromString(end_string),
|
||||
};
|
||||
if(norm.end_time <= norm.start_time) throw new BadRequestException('end time must be greater than start time');
|
||||
|
||||
//call to a function to detect overlaps between shifts
|
||||
const day_shifts = await this.getService.loadShiftsFromSameDay(existing.timesheet_id, norm.date);
|
||||
|
||||
//call to a function to detect overlaps between shifts
|
||||
await this.assertNoOverlap(day_shifts, norm, shift_id);
|
||||
|
||||
//partial build, update only modified datas
|
||||
const data: any = {};
|
||||
if(dto.date !== undefined) data.date = norm.date;
|
||||
if(dto.start_time !== undefined) data.start_time = norm.start_time;
|
||||
if(dto.end_time !== undefined) data.end_time = norm.end_time;
|
||||
if(dto.bank_code_id !== undefined) data.bank_code_id = dto.bank_code_id;
|
||||
if(dto.is_remote !== undefined) data.is_remote = dto.is_remote;
|
||||
if(dto.comment !== undefined) data.comment = dto.comment ?? null;
|
||||
|
||||
//sends updated data to DB
|
||||
const updated_shift = await this.prisma.shifts.update({
|
||||
where: { id: shift_id },
|
||||
data,
|
||||
select: {
|
||||
timesheet_id: true,
|
||||
bank_code_id: true,
|
||||
date: true,
|
||||
start_time: true,
|
||||
end_time: true,
|
||||
is_remote: true,
|
||||
is_approved: true,
|
||||
comment: true,
|
||||
},
|
||||
});
|
||||
//returns updated shift to frontend
|
||||
return {
|
||||
timesheet_id: updated_shift.timesheet_id,
|
||||
bank_code_id: updated_shift.bank_code_id,
|
||||
date: toStringFromDate(updated_shift.date),
|
||||
start_time: toStringFromHHmm(updated_shift.start_time),
|
||||
end_time: toStringFromHHmm(updated_shift.end_time),
|
||||
is_approved: updated_shift.is_approved,
|
||||
is_remote: updated_shift.is_remote,
|
||||
comment: updated_shift.comment ?? undefined,
|
||||
};
|
||||
}
|
||||
|
||||
async deleteShift(shift_id: number) {
|
||||
const shift = await this.prisma.shifts.findUnique({
|
||||
where: { id: shift_id },
|
||||
select: { id: true },
|
||||
});
|
||||
if(!shift) throw new NotFoundException(`Shift with id #${shift_id} not found`);
|
||||
|
||||
return this.prisma.shifts.delete({
|
||||
where: { id: shift.id }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,30 +1,24 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { ShiftsController } from './controllers/shifts.controller';
|
||||
import { BusinessLogicsModule } from 'src/modules/business-logics/business-logics.module';
|
||||
import { ShiftsCommandService } from './services/shifts-command.service';
|
||||
import { NotificationsModule } from '../notifications/notifications.module';
|
||||
import { ShiftsQueryService } from './services/shifts-query.service';
|
||||
import { ShiftsArchivalService } from './services/shifts-archival.service';
|
||||
import { BusinessLogicsModule } from 'src/modules/business-logics/business-logics.module';
|
||||
import { NotificationsModule } from '../notifications/notifications.module';
|
||||
import { ShiftsUpsertService } from './services/shifts-upsert.service';
|
||||
import { ShiftsGetService } from './services/shifts-get.service';
|
||||
import { ShiftController } from './controllers/shift.controller';
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { ShiftsHelpersService } from './helpers/shifts.helpers';
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
BusinessLogicsModule,
|
||||
NotificationsModule,
|
||||
BusinessLogicsModule,
|
||||
NotificationsModule,
|
||||
SharedModule,
|
||||
],
|
||||
controllers: [ShiftsController],
|
||||
providers: [
|
||||
ShiftsQueryService,
|
||||
ShiftsCommandService,
|
||||
controllers: [ShiftController],
|
||||
providers: [
|
||||
ShiftsArchivalService,
|
||||
ShiftsHelpersService,
|
||||
],
|
||||
exports: [
|
||||
ShiftsQueryService,
|
||||
ShiftsCommandService,
|
||||
ShiftsArchivalService,
|
||||
ShiftsGetService,
|
||||
ShiftsUpsertService,
|
||||
],
|
||||
exports: [ ShiftsUpsertService, ShiftsGetService ],
|
||||
})
|
||||
export class ShiftsModule {}
|
||||
|
|
|
|||
|
|
@ -1,10 +0,0 @@
|
|||
export interface OverviewRow {
|
||||
full_name: string;
|
||||
supervisor: string;
|
||||
total_regular_hrs: number;
|
||||
total_evening_hrs: number;
|
||||
total_overtime_hrs: number;
|
||||
total_expenses: number;
|
||||
total_mileage: number;
|
||||
is_approved: boolean;
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
export type DayShiftResponse = {
|
||||
start_time: string;
|
||||
end_time: string;
|
||||
type: string;
|
||||
is_remote: boolean;
|
||||
comment: string | null;
|
||||
}
|
||||
|
||||
export type ShiftPayload = {
|
||||
date: string;
|
||||
start_time: string;
|
||||
end_time: string;
|
||||
type: string;
|
||||
is_remote: boolean;
|
||||
is_approved: boolean;
|
||||
comment?: string | null;
|
||||
}
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
import { NotFoundException } from "@nestjs/common";
|
||||
|
||||
export function overlaps(
|
||||
a_start_ms: number,
|
||||
a_end_ms: number,
|
||||
b_start_ms: number,
|
||||
b_end_ms: number,
|
||||
): boolean {
|
||||
return a_start_ms < b_end_ms && b_start_ms < a_end_ms;
|
||||
}
|
||||
|
||||
export function resolveBankCodeByType(type: string): Promise<number> {
|
||||
const bank = this.prisma.bankCodes.findFirst({
|
||||
where: { type },
|
||||
select: { id: true },
|
||||
});
|
||||
if (!bank) {
|
||||
throw new NotFoundException({ error_code: 'SHIFT_TYPE_UNKNOWN', message: `unknown shift type: ${type}` });
|
||||
}
|
||||
return bank.id;
|
||||
}
|
||||
|
||||
export function normalizeShiftPayload(payload: {
|
||||
date: string,
|
||||
start_time: string,
|
||||
end_time: string,
|
||||
type: string,
|
||||
is_remote: boolean,
|
||||
is_approved: boolean,
|
||||
comment?: string | null,
|
||||
}) {
|
||||
//normalize shift's infos
|
||||
const date = payload.date?.trim();
|
||||
const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(date ?? '');
|
||||
if (!m) throw new Error(`Invalid date format (expected YYYY-MM-DD): "${payload.date}"`);
|
||||
const year = Number(m[1]), mo = Number(m[2]), d = Number(m[3]);
|
||||
|
||||
const asLocalDateOn = (input: string): Date => {
|
||||
// HH:mm ?
|
||||
const hm = /^(\d{2}):(\d{2})$/.exec((input ?? '').trim());
|
||||
if (hm) return new Date(year, mo - 1, d, Number(hm[1]), Number(hm[2]), 0, 0);
|
||||
const iso = new Date(input);
|
||||
if (isNaN(iso.getTime())) throw new Error(`Invalid time: "${input}"`);
|
||||
return new Date(year, mo - 1, d, iso.getHours(), iso.getMinutes(), iso.getSeconds(), iso.getMilliseconds());
|
||||
};
|
||||
|
||||
const start_time = asLocalDateOn(payload.start_time);
|
||||
const end_time = asLocalDateOn(payload.end_time);
|
||||
|
||||
const type = (payload.type || '').trim().toUpperCase();
|
||||
const is_remote = payload.is_remote;
|
||||
const is_approved = payload.is_approved;
|
||||
//normalize comment
|
||||
const trimmed = typeof payload.comment === 'string' ? payload.comment.trim() : null;
|
||||
const comment = trimmed && trimmed.length > 0 ? trimmed : null;
|
||||
|
||||
return { date, start_time, end_time, type, is_remote, is_approved, comment };
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
// import { Type } from "class-transformer";
|
||||
// import { IsInt, Min, Max } from "class-validator";
|
||||
|
||||
// export class GetShiftsOverviewDto {
|
||||
// @Type(()=> Number)
|
||||
// @IsInt()
|
||||
// @Min(1)
|
||||
// @Max(26)
|
||||
// period_id: number;
|
||||
// }
|
||||
|
|
@ -0,0 +1,194 @@
|
|||
// import { BadRequestException, Injectable, Logger, NotFoundException } from "@nestjs/common";
|
||||
// import { DayShiftResponse } from "../types-and-interfaces/shifts-upsert.types";
|
||||
// import { EmailToIdResolver } from "src/modules/shared/utils/resolve-email-id.utils";
|
||||
// import { Prisma, Shifts } from "@prisma/client";
|
||||
// import { UpsertShiftDto } from "../dtos/upsert-shift.dto";
|
||||
// import { BaseApprovalService } from "src/common/shared/base-approval.service";
|
||||
// import { PrismaService } from "src/prisma/prisma.service";
|
||||
// import { toDateOnly } from "../helpers/shifts-date-time-helpers";
|
||||
// import { UpsertAction } from "src/modules/shared/types/upsert-actions.types";
|
||||
// import { ShiftsHelpersService } from "../helpers/shifts.helpers";
|
||||
// import { BankCodesResolver } from "src/modules/shared/utils/resolve-bank-type-id.utils";
|
||||
|
||||
// @Injectable()
|
||||
// export class ShiftsCommandService extends BaseApprovalService<Shifts> {
|
||||
// private readonly logger = new Logger(ShiftsCommandService.name);
|
||||
|
||||
// constructor(
|
||||
// prisma: PrismaService,
|
||||
// private readonly emailResolver: EmailToIdResolver,
|
||||
// private readonly typeResolver: BankCodesResolver,
|
||||
// private readonly helpersService: ShiftsHelpersService,
|
||||
// ) { super(prisma); }
|
||||
|
||||
// //_____________________________________________________________________________________________
|
||||
// // APPROVAL AND DELEGATE METHODS
|
||||
// //_____________________________________________________________________________________________
|
||||
// protected get delegate() {
|
||||
// return this.prisma.shifts;
|
||||
// }
|
||||
|
||||
// protected delegateFor(transaction: Prisma.TransactionClient) {
|
||||
// return transaction.shifts;
|
||||
// }
|
||||
|
||||
// async updateApproval(id: number, is_approved: boolean): Promise<Shifts> {
|
||||
// return this.prisma.$transaction((transaction) =>
|
||||
// this.updateApprovalWithTransaction(transaction, id, is_approved),
|
||||
// );
|
||||
// }
|
||||
|
||||
|
||||
// //TODO: modifier le Master Crud pour recevoir l'ensemble des shifts de la pay-period et trier sur l'action 'create'| 'update' | 'delete'
|
||||
// //_____________________________________________________________________________________________
|
||||
// // MASTER CRUD METHOD
|
||||
// //_____________________________________________________________________________________________
|
||||
// async upsertShifts(
|
||||
// email: string,
|
||||
// action: UpsertAction,
|
||||
// dto: UpsertShiftDto,
|
||||
// ): Promise<{
|
||||
// action: UpsertAction;
|
||||
// day: DayShiftResponse[];
|
||||
// }> {
|
||||
// if (!dto.old_shift && !dto.new_shift) throw new BadRequestException('At least one of old or new shift must be provided');
|
||||
|
||||
// const date = dto.new_shift?.date ?? dto.old_shift?.date;
|
||||
// if (!date) throw new BadRequestException("A date (YYYY-MM-DD) must be provided in old_shift or new_shift");
|
||||
// if (dto.old_shift?.date && dto.new_shift?.date && dto.old_shift.date !== dto.new_shift.date) {
|
||||
// throw new BadRequestException('old_shift.date and new_shift.date must be identical');
|
||||
// }
|
||||
|
||||
// const employee_id = await this.emailResolver.findIdByEmail(email);//resolve employee id using email
|
||||
|
||||
// if(action === 'create') {
|
||||
// if(!dto.new_shift || dto.old_shift) {
|
||||
// throw new BadRequestException(`Only new_shift must be provided for create`);
|
||||
// }
|
||||
// return this.createShift(employee_id, date, dto);
|
||||
// }
|
||||
// if(action === 'update'){
|
||||
// if(!dto.old_shift || !dto.new_shift) {
|
||||
// throw new BadRequestException(`Both new_shift and old_shift must be provided for update`);
|
||||
// }
|
||||
// return this.updateShift(employee_id, date, dto);
|
||||
// }
|
||||
// throw new BadRequestException(`Unknown action: ${action}`);
|
||||
// }
|
||||
|
||||
// //_________________________________________________________________
|
||||
// // CREATE
|
||||
// //_________________________________________________________________
|
||||
// private async createShift(
|
||||
// employee_id: number,
|
||||
// date_iso: string,
|
||||
// dto: UpsertShiftDto,
|
||||
// ): Promise<{action: UpsertAction; day: DayShiftResponse[]}> {
|
||||
// return this.prisma.$transaction(async (tx) => {
|
||||
// const date_only = toDateOnly(date_iso);
|
||||
// const timesheet = await this.helpersService.ensureTimesheet(tx, employee_id, date_only);
|
||||
// if(!timesheet) throw new NotFoundException('Timesheet not found')
|
||||
// const new_norm_shift = await this.helpersService.normalizeRequired(dto.new_shift);
|
||||
// const new_bank_code_id = await this.helpersService.resolveBankIdRequired(tx, new_norm_shift.type, 'new_shift');
|
||||
|
||||
// const day_shifts = await this.helpersService.getDayShifts(tx, timesheet.id, date_only);
|
||||
|
||||
// await this.helpersService.assertNoOverlap(day_shifts, new_norm_shift);
|
||||
|
||||
// await tx.shifts.create({
|
||||
// data: {
|
||||
// timesheet_id: timesheet.id,
|
||||
// date: date_only,
|
||||
// start_time: new_norm_shift.start_time,
|
||||
// end_time: new_norm_shift.end_time,
|
||||
// is_remote: new_norm_shift.is_remote,
|
||||
// is_approved: new_norm_shift.is_approved,
|
||||
// comment: new_norm_shift.comment ?? null,
|
||||
// bank_code_id: new_bank_code_id,
|
||||
// },
|
||||
// });
|
||||
// await this.helpersService.afterWriteOvertimeAndLog(tx, employee_id, date_only);
|
||||
// const fresh_shift = await this.helpersService.getDayShifts(tx, timesheet.id, date_only);
|
||||
// return { action: 'create', day: await this.helpersService.mapDay(fresh_shift)};
|
||||
// });
|
||||
// }
|
||||
|
||||
// //_________________________________________________________________
|
||||
// // UPDATE
|
||||
// //_________________________________________________________________
|
||||
// private async updateShift(
|
||||
// employee_id: number,
|
||||
// date_iso: string,
|
||||
// dto: UpsertShiftDto,
|
||||
// ): Promise<{ action: UpsertAction; day: DayShiftResponse[];}>{
|
||||
// return this.prisma.$transaction(async (tx) => {
|
||||
// const date_only = toDateOnly(date_iso);
|
||||
// const timesheet = await this.helpersService.ensureTimesheet(tx, employee_id, date_only);
|
||||
// if(!timesheet) throw new NotFoundException('Timesheet not found')
|
||||
|
||||
// const old_norm_shift = await this.helpersService.normalizeRequired(dto.old_shift, 'old_shift');
|
||||
// const new_norm_shift = await this.helpersService.normalizeRequired(dto.new_shift, 'new_shift');
|
||||
|
||||
// const old_bank_code = await this.typeResolver.findByType(old_norm_shift.type);
|
||||
// const new_bank_code = await this.typeResolver.findByType(new_norm_shift.type);
|
||||
|
||||
// const day_shifts = await this.helpersService.getDayShifts(tx, timesheet.id, date_only);
|
||||
// const existing = await this.helpersService.findExactOldShift(tx, {
|
||||
// timesheet_id: timesheet.id,
|
||||
// date_only,
|
||||
// norm: old_norm_shift,
|
||||
// bank_code_id: old_bank_code.id,
|
||||
// });
|
||||
// if(!existing) throw new NotFoundException('[SHIFT_STALE]- The shift was modified or deleted by someone else');
|
||||
|
||||
// await this.helpersService.assertNoOverlap(day_shifts, new_norm_shift, existing.id);
|
||||
|
||||
// await tx.shifts.update({
|
||||
// where: { id: existing.id },
|
||||
// data: {
|
||||
// start_time: new_norm_shift.start_time,
|
||||
// end_time: new_norm_shift.end_time,
|
||||
// is_remote: new_norm_shift.is_remote,
|
||||
// comment: new_norm_shift.comment ?? null,
|
||||
// bank_code_id: new_bank_code.id,
|
||||
// },
|
||||
// });
|
||||
// await this.helpersService.afterWriteOvertimeAndLog(tx, employee_id, date_only);
|
||||
// const fresh_shift = await this.helpersService.getDayShifts(tx, timesheet.id, date_only);
|
||||
// return { action: 'update', day: await this.helpersService.mapDay(fresh_shift)};
|
||||
// });
|
||||
|
||||
// }
|
||||
|
||||
// //_________________________________________________________________
|
||||
// // DELETE
|
||||
// //_________________________________________________________________
|
||||
// async deleteShift(
|
||||
// email: string,
|
||||
// date_iso: string,
|
||||
// dto: UpsertShiftDto,
|
||||
// ){
|
||||
// return this.prisma.$transaction(async (tx) => {
|
||||
// const date_only = toDateOnly(date_iso); //converts to Date format
|
||||
// const employee_id = await this.emailResolver.findIdByEmail(email);
|
||||
|
||||
// const timesheet = await this.helpersService.ensureTimesheet(tx, employee_id, date_only);
|
||||
// if(!timesheet) throw new NotFoundException('Timesheet not found')
|
||||
// const norm_shift = await this.helpersService.normalizeRequired(dto.old_shift, 'old_shift');
|
||||
// const bank_code_id = await this.typeResolver.findByType(norm_shift.type);
|
||||
|
||||
// const existing = await this.helpersService.findExactOldShift(tx, {
|
||||
// timesheet_id: timesheet.id,
|
||||
// date_only,
|
||||
// norm: norm_shift,
|
||||
// bank_code_id: bank_code_id.id,
|
||||
// });
|
||||
// if(!existing) throw new NotFoundException('[SHIFT_STALE]- The shift was modified or deleted by someone else');
|
||||
|
||||
// await tx.shifts.delete({ where: { id: existing.id } });
|
||||
|
||||
// await this.helpersService.afterWriteOvertimeAndLog(tx, employee_id, date_only);
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
// export interface OverviewRow {
|
||||
// full_name: string;
|
||||
// supervisor: string;
|
||||
// total_regular_hrs: number;
|
||||
// total_evening_hrs: number;
|
||||
// total_overtime_hrs: number;
|
||||
// total_expenses: number;
|
||||
// total_mileage: number;
|
||||
// is_approved: boolean;
|
||||
// }
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
// import { Injectable, NotFoundException } from "@nestjs/common";
|
||||
// import { PrismaService } from "src/prisma/prisma.service";
|
||||
// import { NotificationsService } from "src/modules/notifications/services/notifications.service";
|
||||
// import { computeHours } from "src/common/utils/date-utils";
|
||||
// import { OverviewRow } from "../types-and-interfaces/shifts-overview-row.interface";
|
||||
|
||||
// // const DAILY_LIMIT_HOURS = Number(process.env.DAILY_LIMIT_HOURS ?? 12);
|
||||
|
||||
// @Injectable()
|
||||
// export class ShiftsQueryService {
|
||||
// constructor(
|
||||
// private readonly prisma: PrismaService,
|
||||
// private readonly notifs: NotificationsService,
|
||||
// ) {}
|
||||
|
||||
// async getSummary(period_id: number): Promise<OverviewRow[]> {
|
||||
// //fetch pay-period to display
|
||||
// const period = await this.prisma.payPeriods.findFirst({
|
||||
// where: { pay_period_no: period_id },
|
||||
// });
|
||||
// if(!period) {
|
||||
// throw new NotFoundException(`pay-period ${period_id} not found`);
|
||||
// }
|
||||
// const { period_start, period_end } = period;
|
||||
|
||||
// //prepare shifts and expenses for display
|
||||
// const shifts = await this.prisma.shifts.findMany({
|
||||
// where: { date: { gte: period_start, lte: period_end } },
|
||||
// include: {
|
||||
// bank_code: true,
|
||||
// timesheet: { include: {
|
||||
// employee: { include: {
|
||||
// user:true,
|
||||
// supervisor: { include: { user: true } },
|
||||
// } },
|
||||
// } },
|
||||
// },
|
||||
// });
|
||||
|
||||
// const expenses = await this.prisma.expenses.findMany({
|
||||
// where: { date: { gte: period_start, lte: period_end } },
|
||||
// include: {
|
||||
// bank_code: true,
|
||||
// timesheet: { include: { employee: {
|
||||
// include: { user:true,
|
||||
// supervisor: { include: { user:true } },
|
||||
// } },
|
||||
// } },
|
||||
// },
|
||||
// });
|
||||
|
||||
// const mapRow = new Map<string, OverviewRow>();
|
||||
|
||||
// for(const shift of shifts) {
|
||||
// const employeeId = shift.timesheet.employee.user_id;
|
||||
// const user = shift.timesheet.employee.user;
|
||||
// const sup = shift.timesheet.employee.supervisor?.user;
|
||||
|
||||
// let row = mapRow.get(employeeId);
|
||||
// if(!row) {
|
||||
// row = {
|
||||
// full_name: `${user.first_name} ${user.last_name}`,
|
||||
// supervisor: sup? `${sup.first_name} ${sup.last_name }` : '',
|
||||
// total_regular_hrs: 0,
|
||||
// total_evening_hrs: 0,
|
||||
// total_overtime_hrs: 0,
|
||||
// total_expenses: 0,
|
||||
// total_mileage: 0,
|
||||
// is_approved: false,
|
||||
// };
|
||||
// }
|
||||
// const hours = computeHours(shift.start_time, shift.end_time);
|
||||
|
||||
// switch(shift.bank_code.type) {
|
||||
// case 'regular' : row.total_regular_hrs += hours;
|
||||
// break;
|
||||
// case 'evening' : row.total_evening_hrs += hours;
|
||||
// break;
|
||||
// case 'overtime' : row.total_overtime_hrs += hours;
|
||||
// break;
|
||||
// default: row.total_regular_hrs += hours;
|
||||
// }
|
||||
// mapRow.set(employeeId, row);
|
||||
// }
|
||||
|
||||
// for(const exp of expenses) {
|
||||
// const employee_id = exp.timesheet.employee.user_id;
|
||||
// const user = exp.timesheet.employee.user;
|
||||
// const sup = exp.timesheet.employee.supervisor?.user;
|
||||
|
||||
// let row = mapRow.get(employee_id);
|
||||
// if(!row) {
|
||||
// row = {
|
||||
// full_name: `${user.first_name} ${user.last_name}`,
|
||||
// supervisor: sup? `${sup.first_name} ${sup.last_name }` : '',
|
||||
// total_regular_hrs: 0,
|
||||
// total_evening_hrs: 0,
|
||||
// total_overtime_hrs: 0,
|
||||
// total_expenses: 0,
|
||||
// total_mileage: 0,
|
||||
// is_approved: false,
|
||||
// };
|
||||
// }
|
||||
// const amount = Number(exp.amount);
|
||||
// row.total_expenses += amount;
|
||||
// if(exp.bank_code.type === 'mileage') {
|
||||
// row.total_mileage += amount;
|
||||
// }
|
||||
// mapRow.set(employee_id, row);
|
||||
// }
|
||||
// //return by default the list of employee in ascending alphabetical order
|
||||
// return Array.from(mapRow.values()).sort((a,b) => a.full_name.localeCompare(b.full_name));
|
||||
// }
|
||||
// }
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
// export type DayShiftResponse = {
|
||||
// start_time: string;
|
||||
// end_time: string;
|
||||
// type: string;
|
||||
// is_remote: boolean;
|
||||
// comment: string | null;
|
||||
// }
|
||||
|
||||
// export type ShiftPayload = {
|
||||
// date: string;
|
||||
// start_time: string;
|
||||
// end_time: string;
|
||||
// type: string;
|
||||
// is_remote: boolean;
|
||||
// is_approved: boolean;
|
||||
// comment?: string | null;
|
||||
// }
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
// import { Body, Controller, Delete, Get, Header, Param, ParseBoolPipe, ParseIntPipe, Patch, Put, Query, } from "@nestjs/common";
|
||||
// import { RolesAllowed } from "src/common/decorators/roles.decorators";
|
||||
// import { Roles as RoleEnum } from '.prisma/client';
|
||||
// import { ApiBearerAuth, ApiTags } from "@nestjs/swagger";
|
||||
// import { ShiftsCommandService } from "../services/shifts-command.service";
|
||||
// import { ShiftsQueryService } from "../services/shifts-query.service";
|
||||
// import { GetShiftsOverviewDto } from "../dtos/get-shift-overview.dto";
|
||||
// import { ShiftPayloadDto, UpsertShiftDto } from "../dtos/upsert-shift.dto";
|
||||
// import { OverviewRow } from "../types-and-interfaces/shifts-overview-row.interface";
|
||||
// import { UpsertAction } from "src/modules/shared/types/upsert-actions.types";
|
||||
|
||||
// @ApiTags('Shifts')
|
||||
// @ApiBearerAuth('access-token')
|
||||
// // @UseGuards()
|
||||
// @Controller('shifts')
|
||||
// export class ShiftsController {
|
||||
// constructor(
|
||||
// private readonly shiftsService: ShiftsQueryService,
|
||||
// private readonly shiftsCommandService: ShiftsCommandService,
|
||||
// ){}
|
||||
|
||||
// @Put('upsert/:email')
|
||||
// async upsert_by_date(
|
||||
// @Param('email') email_param: string,
|
||||
// @Query('action') action: UpsertAction,
|
||||
// @Body() payload: UpsertShiftDto,
|
||||
// ) {
|
||||
// return this.shiftsCommandService.upsertShifts(email_param, action, payload);
|
||||
// }
|
||||
|
||||
// @Delete('delete/:email/:date')
|
||||
// async remove(
|
||||
// @Param('email') email: string,
|
||||
// @Param('date') date: string,
|
||||
// @Body() payload: UpsertShiftDto,
|
||||
// ) {
|
||||
// return this.shiftsCommandService.deleteShift(email, date, payload);
|
||||
// }
|
||||
|
||||
// @Patch('approval/:id')
|
||||
// //@RolesAllowed(RoleEnum.ACCOUNTING, RoleEnum.ADMIN, RoleEnum.HR, RoleEnum.SUPERVISOR)
|
||||
// async approve(@Param('id', ParseIntPipe) id: number, @Body('is_approved', ParseBoolPipe) isApproved: boolean) {
|
||||
// return this.shiftsCommandService.updateApproval(id, isApproved);
|
||||
// }
|
||||
|
||||
// @Get('summary')
|
||||
// async getSummary( @Query() query: GetShiftsOverviewDto): Promise<OverviewRow[]> {
|
||||
// return this.shiftsService.getSummary(query.period_id);
|
||||
// }
|
||||
|
||||
// @Get('export.csv')
|
||||
// @Header('Content-Type', 'text/csv; charset=utf-8')
|
||||
// @Header('Content-Disposition', 'attachment; filename="shifts-validation.csv"')
|
||||
// async exportCsv(@Query() query: GetShiftsOverviewDto): Promise<Buffer>{
|
||||
// const rows = await this.shiftsService.getSummary(query.period_id);
|
||||
// //CSV Headers
|
||||
// const header = [
|
||||
// 'full_name',
|
||||
// 'supervisor',
|
||||
// 'total_regular_hrs',
|
||||
// 'total_evening_hrs',
|
||||
// 'total_overtime_hrs',
|
||||
// 'total_expenses',
|
||||
// 'total_mileage',
|
||||
// 'is_validated'
|
||||
// ].join(',') + '\n';
|
||||
|
||||
// //CSV rows
|
||||
// const body = rows.map(r => {
|
||||
// const esc = (str: string) => `"${str.replace(/"/g, '""')}"`;
|
||||
|
||||
// return [
|
||||
// esc(r.full_name),
|
||||
// esc(r.supervisor),
|
||||
// r.total_regular_hrs.toFixed(2),
|
||||
// r.total_evening_hrs.toFixed(2),
|
||||
// r.total_overtime_hrs.toFixed(2),
|
||||
// r.total_expenses.toFixed(2),
|
||||
// r.total_mileage.toFixed(2),
|
||||
// r.is_approved,
|
||||
// ].join(',');
|
||||
// }).join('\n');
|
||||
|
||||
// return Buffer.from('\uFEFF' + header + body, 'utf8');
|
||||
// }
|
||||
|
||||
// }
|
||||
107
src/modules/shifts/~misc_deprecated-files/shifts.helpers.ts
Normal file
107
src/modules/shifts/~misc_deprecated-files/shifts.helpers.ts
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
// import { BadRequestException, UnprocessableEntityException, NotFoundException, ConflictException } from "@nestjs/common";
|
||||
// import { Prisma, Shifts } from "@prisma/client";
|
||||
// import { UpsertShiftDto } from "../deprecated-files/upsert-shift.dto";
|
||||
// import { DayShiftResponse } from "../types-and-interfaces/shifts-upsert.types";
|
||||
// import { normalizeShiftPayload } from "../utils/shifts.utils";
|
||||
// import { toStringFromHHmm, weekStartSunday } from "./shifts-date-time-helpers";
|
||||
// import { BankCodesResolver } from "src/modules/shared/utils/resolve-bank-type-id.utils";
|
||||
// import { OvertimeService } from "src/modules/business-logics/services/overtime.service";
|
||||
|
||||
// export type Tx = Prisma.TransactionClient;
|
||||
// export type Normalized = Awaited<ReturnType<typeof normalizeShiftPayload>>;
|
||||
|
||||
// export class ShiftsHelpersService {
|
||||
|
||||
// constructor(
|
||||
// private readonly bankTypeResolver: BankCodesResolver,
|
||||
// private readonly overtimeService: OvertimeService,
|
||||
// ) { }
|
||||
|
||||
// async ensureTimesheet(tx: Tx, employee_id: number, date_only: Date) {
|
||||
// const start_of_week = weekStartSunday(date_only);
|
||||
// return tx.timesheets.findUnique({
|
||||
// where: { employee_id_start_date: { employee_id, start_date: start_of_week } },
|
||||
// select: { id: true },
|
||||
// });
|
||||
// }
|
||||
|
||||
// async normalizeRequired(
|
||||
// raw: UpsertShiftDto['new_shift'] | UpsertShiftDto['old_shift'] | undefined | null,
|
||||
// label: 'old_shift' | 'new_shift' = 'new_shift',
|
||||
// ): Promise<Normalized> {
|
||||
// if (!raw) throw new BadRequestException(`${label} is required`);
|
||||
// const norm = await normalizeShiftPayload(raw);
|
||||
// if (norm.end_time.getTime() <= norm.start_time.getTime()) {
|
||||
// throw new UnprocessableEntityException(` ${label}.end_time must be > ${label}.start_time`);
|
||||
// }
|
||||
// return norm;
|
||||
// }
|
||||
|
||||
// async resolveBankIdRequired(tx: Tx, type: string, label: 'old_shift' | 'new_shift'): Promise<number> {
|
||||
// const found = await this.bankTypeResolver.findByType(type, tx);
|
||||
// const id = found?.id;
|
||||
// if (typeof id !== 'number') {
|
||||
// throw new NotFoundException(`bank code not found for ${label}.type: ${type ?? ''}`);
|
||||
// }
|
||||
// return id;
|
||||
// }
|
||||
|
||||
// async getDayShifts(tx: Tx, timesheet_id: number, date_only: Date) {
|
||||
// return tx.shifts.findMany({
|
||||
// where: { timesheet_id, date: date_only },
|
||||
// include: { bank_code: true },
|
||||
// orderBy: { start_time: 'asc' },
|
||||
// });
|
||||
// }
|
||||
|
||||
// async findExactOldShift(
|
||||
// tx: Tx,
|
||||
// params: {
|
||||
// timesheet_id: number;
|
||||
// date_only: Date;
|
||||
// norm: Normalized;
|
||||
// bank_code_id: number;
|
||||
// comment?: string;
|
||||
// },
|
||||
// ) {
|
||||
// const { timesheet_id, date_only, norm, bank_code_id } = params;
|
||||
// return tx.shifts.findFirst({
|
||||
// where: {
|
||||
// timesheet_id,
|
||||
// date: date_only,
|
||||
// start_time: norm.start_time,
|
||||
// end_time: norm.end_time,
|
||||
// is_remote: norm.is_remote,
|
||||
// is_approved: norm.is_approved,
|
||||
// comment: norm.comment ?? null,
|
||||
// bank_code_id,
|
||||
// },
|
||||
// select: { id: true },
|
||||
// });
|
||||
// }
|
||||
|
||||
// async afterWriteOvertimeAndLog(tx: Tx, employee_id: number, date_only: Date) {
|
||||
// // Switch regular → weekly overtime si > 40h
|
||||
// await this.overtimeService.transformRegularHoursToWeeklyOvertime(employee_id, date_only, tx);
|
||||
// const daily = await this.overtimeService.getDailyOvertimeHours(employee_id, date_only);
|
||||
// const weekly = await this.overtimeService.getWeeklyOvertimeHours(employee_id, date_only);
|
||||
// // const [daily, weekly] = await Promise.all([
|
||||
// // this.overtimeService.getDailyOvertimeHoursForDay(employee_id, date_only),
|
||||
// // this.overtimeService.getWeeklyOvertimeHours(employee_id, date_only),
|
||||
// // ]);
|
||||
// return { daily, weekly };
|
||||
// }
|
||||
|
||||
// async mapDay(
|
||||
// fresh: Array<Shifts & { bank_code: { type: string } | null }>,
|
||||
// ): Promise<DayShiftResponse[]> {
|
||||
// return fresh.map((s) => ({
|
||||
// start_time: toStringFromHHmm(s.start_time),
|
||||
// end_time: toStringFromHHmm(s.end_time),
|
||||
// type: s.bank_code?.type ?? 'UNKNOWN',
|
||||
// is_remote: s.is_remote,
|
||||
// comment: s.comment ?? null,
|
||||
// }));
|
||||
// }
|
||||
// }
|
||||
|
||||
58
src/modules/shifts/~misc_deprecated-files/shifts.utils.ts
Normal file
58
src/modules/shifts/~misc_deprecated-files/shifts.utils.ts
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
// import { NotFoundException } from "@nestjs/common";
|
||||
|
||||
// export function overlaps(
|
||||
// a_start_ms: number,
|
||||
// a_end_ms: number,
|
||||
// b_start_ms: number,
|
||||
// b_end_ms: number,
|
||||
// ): boolean {
|
||||
// return a_start_ms < b_end_ms && b_start_ms < a_end_ms;
|
||||
// }
|
||||
|
||||
// export function resolveBankCodeByType(type: string): Promise<number> {
|
||||
// const bank = this.prisma.bankCodes.findFirst({
|
||||
// where: { type },
|
||||
// select: { id: true },
|
||||
// });
|
||||
// if (!bank) {
|
||||
// throw new NotFoundException({ error_code: 'SHIFT_TYPE_UNKNOWN', message: `unknown shift type: ${type}` });
|
||||
// }
|
||||
// return bank.id;
|
||||
// }
|
||||
|
||||
// export function normalizeShiftPayload(payload: {
|
||||
// date: string,
|
||||
// start_time: string,
|
||||
// end_time: string,
|
||||
// type: string,
|
||||
// is_remote: boolean,
|
||||
// is_approved: boolean,
|
||||
// comment?: string | null,
|
||||
// }) {
|
||||
// //normalize shift's infos
|
||||
// const date = payload.date?.trim();
|
||||
// const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(date ?? '');
|
||||
// if (!m) throw new Error(`Invalid date format (expected YYYY-MM-DD): "${payload.date}"`);
|
||||
// const year = Number(m[1]), mo = Number(m[2]), d = Number(m[3]);
|
||||
|
||||
// const asLocalDateOn = (input: string): Date => {
|
||||
// // HH:mm ?
|
||||
// const hm = /^(\d{2}):(\d{2})$/.exec((input ?? '').trim());
|
||||
// if (hm) return new Date(year, mo - 1, d, Number(hm[1]), Number(hm[2]), 0, 0);
|
||||
// const iso = new Date(input);
|
||||
// if (isNaN(iso.getTime())) throw new Error(`Invalid time: "${input}"`);
|
||||
// return new Date(year, mo - 1, d, iso.getHours(), iso.getMinutes(), iso.getSeconds(), iso.getMilliseconds());
|
||||
// };
|
||||
|
||||
// const start_time = asLocalDateOn(payload.start_time);
|
||||
// const end_time = asLocalDateOn(payload.end_time);
|
||||
|
||||
// const type = (payload.type || '').trim().toUpperCase();
|
||||
// const is_remote = payload.is_remote;
|
||||
// const is_approved = payload.is_approved;
|
||||
// //normalize comment
|
||||
// const trimmed = typeof payload.comment === 'string' ? payload.comment.trim() : null;
|
||||
// const comment = trimmed && trimmed.length > 0 ? trimmed : null;
|
||||
|
||||
// return { date, start_time, end_time, type, is_remote, is_approved, comment };
|
||||
// }
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
// import { Type } from "class-transformer";
|
||||
// import { IsBoolean, IsOptional, IsString, Matches, MaxLength, ValidateNested } from "class-validator";
|
||||
|
||||
// export const COMMENT_MAX_LENGTH = 280;
|
||||
|
||||
// export class ShiftPayloadDto {
|
||||
|
||||
// @Matches(/^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/)
|
||||
// date!: string;
|
||||
|
||||
// @Matches(/^([01]\d|2[0-3]):([0-5]\d)$/)
|
||||
// start_time!: string;
|
||||
|
||||
// @Matches(/^([01]\d|2[0-3]):([0-5]\d)$/)
|
||||
// end_time!: string;
|
||||
|
||||
// @IsString()
|
||||
// type!: string;
|
||||
|
||||
// @IsBoolean()
|
||||
// is_remote!: boolean;
|
||||
|
||||
// @IsBoolean()
|
||||
// is_approved!: boolean;
|
||||
|
||||
// @IsOptional()
|
||||
// @IsString()
|
||||
// @MaxLength(COMMENT_MAX_LENGTH)
|
||||
// comment?: string;
|
||||
// };
|
||||
|
||||
// export class UpsertShiftDto {
|
||||
|
||||
// @IsOptional()
|
||||
// @ValidateNested()
|
||||
// @Type(()=> ShiftPayloadDto)
|
||||
// old_shift?: ShiftPayloadDto;
|
||||
|
||||
// @IsOptional()
|
||||
// @ValidateNested()
|
||||
// @Type(()=> ShiftPayloadDto)
|
||||
// new_shift?: ShiftPayloadDto;
|
||||
// };
|
||||
|
|
@ -2,13 +2,13 @@ import { TimesheetsController } from './controllers/timesheets.controller';
|
|||
import { TimesheetsQueryService } from './services/timesheets-query.service';
|
||||
import { TimesheetArchiveService } from './services/timesheet-archive.service';
|
||||
import { TimesheetsCommandService } from './services/timesheets-command.service';
|
||||
import { ShiftsCommandService } from '../shifts/services/shifts-command.service';
|
||||
import { ShiftsCommandService } from '../shifts/_deprecated-files/shifts-command.service';
|
||||
import { ExpensesCommandService } from '../expenses/services/expenses-command.service';
|
||||
import { BusinessLogicsModule } from 'src/modules/business-logics/business-logics.module';
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TimesheetSelectorsService } from './utils-helpers-others/timesheet.selectors';
|
||||
import { ShiftsHelpersService } from '../shifts/helpers/shifts.helpers';
|
||||
import { ShiftsHelpersService } from '../shifts/_deprecated-files/shifts.helpers';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user