(data-hub): add etl feature
This commit is contained in:
parent
092efb1f53
commit
8be07e1f9b
|
|
@ -1,3 +1,5 @@
|
||||||
|
.local/
|
||||||
|
|
||||||
# compiled output
|
# compiled output
|
||||||
/dist
|
/dist
|
||||||
/node_modules
|
/node_modules
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
version: '3'
|
||||||
|
|
||||||
|
services:
|
||||||
|
avicenna-db:
|
||||||
|
image: postgres:16.3
|
||||||
|
ports:
|
||||||
|
- ${DATABASE_PORT}:${DATABASE_PORT}
|
||||||
|
env_file: .env
|
||||||
|
environment:
|
||||||
|
POSTGRES_HOST: ${DATABASE_HOST}
|
||||||
|
POSTGRES_PASSWORD: ${DATABASE_PASSWORD}
|
||||||
|
POSTGRES_USER: ${DATABASE_USERNAME}
|
||||||
|
POSTGRES_DB: ${DATABASE_DATABASE}
|
||||||
|
PG_DATA: /var/lib/postgresql/data
|
||||||
|
volumes:
|
||||||
|
- ./.local/pgdata:/var/lib/postgresql/data
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
pgdata:
|
||||||
|
|
@ -9,9 +9,16 @@
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"license": "UNLICENSED",
|
"license": "UNLICENSED",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@nestjs/axios": "^3.0.2",
|
||||||
"@nestjs/common": "^10.0.0",
|
"@nestjs/common": "^10.0.0",
|
||||||
|
"@nestjs/config": "^3.2.3",
|
||||||
"@nestjs/core": "^10.0.0",
|
"@nestjs/core": "^10.0.0",
|
||||||
|
"@nestjs/event-emitter": "^2.0.4",
|
||||||
"@nestjs/platform-express": "^10.0.0",
|
"@nestjs/platform-express": "^10.0.0",
|
||||||
|
"@nestjs/schedule": "^4.1.0",
|
||||||
|
"@prisma/client": "^5.16.2",
|
||||||
|
"date-fns": "^3.6.0",
|
||||||
|
"date-fns-tz": "^3.1.3",
|
||||||
"reflect-metadata": "^0.2.0",
|
"reflect-metadata": "^0.2.0",
|
||||||
"rxjs": "^7.8.1"
|
"rxjs": "^7.8.1"
|
||||||
},
|
},
|
||||||
|
|
@ -30,6 +37,7 @@
|
||||||
"eslint-plugin-prettier": "^5.0.0",
|
"eslint-plugin-prettier": "^5.0.0",
|
||||||
"jest": "^29.5.0",
|
"jest": "^29.5.0",
|
||||||
"prettier": "^3.0.0",
|
"prettier": "^3.0.0",
|
||||||
|
"prisma": "^5.16.2",
|
||||||
"source-map-support": "^0.5.21",
|
"source-map-support": "^0.5.21",
|
||||||
"supertest": "^7.0.0",
|
"supertest": "^7.0.0",
|
||||||
"ts-jest": "^29.1.0",
|
"ts-jest": "^29.1.0",
|
||||||
|
|
@ -1559,6 +1567,16 @@
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@nestjs/axios": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@nestjs/axios/-/axios-3.0.2.tgz",
|
||||||
|
"integrity": "sha512-Z6GuOUdNQjP7FX+OuV2Ybyamse+/e0BFdTWBX5JxpBDKA+YkdLynDgG6HTF04zy6e9zPa19UX0WA2VDoehwhXQ==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@nestjs/common": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0",
|
||||||
|
"axios": "^1.3.1",
|
||||||
|
"rxjs": "^6.0.0 || ^7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@nestjs/cli": {
|
"node_modules/@nestjs/cli": {
|
||||||
"version": "10.4.2",
|
"version": "10.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-10.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-10.4.2.tgz",
|
||||||
|
|
@ -1645,6 +1663,20 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@nestjs/config": {
|
||||||
|
"version": "3.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@nestjs/config/-/config-3.2.3.tgz",
|
||||||
|
"integrity": "sha512-p6yv/CvoBewJ72mBq4NXgOAi2rSQNWx3a+IMJLVKS2uiwFCOQQuiIatGwq6MRjXV3Jr+B41iUO8FIf4xBrZ4/w==",
|
||||||
|
"dependencies": {
|
||||||
|
"dotenv": "16.4.5",
|
||||||
|
"dotenv-expand": "10.0.0",
|
||||||
|
"lodash": "4.17.21"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0",
|
||||||
|
"rxjs": "^7.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@nestjs/core": {
|
"node_modules/@nestjs/core": {
|
||||||
"version": "10.3.10",
|
"version": "10.3.10",
|
||||||
"resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.3.10.tgz",
|
"resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.3.10.tgz",
|
||||||
|
|
@ -1682,6 +1714,18 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@nestjs/event-emitter": {
|
||||||
|
"version": "2.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@nestjs/event-emitter/-/event-emitter-2.0.4.tgz",
|
||||||
|
"integrity": "sha512-quMiw8yOwoSul0pp3mOonGz8EyXWHSBTqBy8B0TbYYgpnG1Ix2wGUnuTksLWaaBiiOTDhciaZ41Y5fJZsSJE1Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"eventemitter2": "6.4.9"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0",
|
||||||
|
"@nestjs/core": "^8.0.0 || ^9.0.0 || ^10.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@nestjs/platform-express": {
|
"node_modules/@nestjs/platform-express": {
|
||||||
"version": "10.3.10",
|
"version": "10.3.10",
|
||||||
"resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.3.10.tgz",
|
"resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.3.10.tgz",
|
||||||
|
|
@ -1702,6 +1746,19 @@
|
||||||
"@nestjs/core": "^10.0.0"
|
"@nestjs/core": "^10.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@nestjs/schedule": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@nestjs/schedule/-/schedule-4.1.0.tgz",
|
||||||
|
"integrity": "sha512-WEc96WTXZW+VI/Ng+uBpiBUwm6TWtAbQ4RKWkfbmzKvmbRGzA/9k/UyAWDS9k0pp+ZcbC+MaZQtt7TjQHrwX6g==",
|
||||||
|
"dependencies": {
|
||||||
|
"cron": "3.1.7",
|
||||||
|
"uuid": "10.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0",
|
||||||
|
"@nestjs/core": "^8.0.0 || ^9.0.0 || ^10.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@nestjs/schematics": {
|
"node_modules/@nestjs/schematics": {
|
||||||
"version": "10.1.2",
|
"version": "10.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-10.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-10.1.2.tgz",
|
||||||
|
|
@ -1825,6 +1882,68 @@
|
||||||
"url": "https://opencollective.com/unts"
|
"url": "https://opencollective.com/unts"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@prisma/client": {
|
||||||
|
"version": "5.16.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.16.2.tgz",
|
||||||
|
"integrity": "sha512-+1lmkhR9gHWcTC5oghm2ZKpWljyWdzfazCVlLKUWXVmwHSf52g81aZ8qb6Km5Bs025yBi7puLp3qSLEvktoUtw==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.13"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"prisma": "*"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"prisma": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@prisma/debug": {
|
||||||
|
"version": "5.16.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.16.2.tgz",
|
||||||
|
"integrity": "sha512-ItzB4nR4O8eLzuJiuP3WwUJfoIvewMHqpGCad+64gvThcKEVOtaUza9AEJo2DPqAOa/AWkFyK54oM4WwHeew+A==",
|
||||||
|
"devOptional": true
|
||||||
|
},
|
||||||
|
"node_modules/@prisma/engines": {
|
||||||
|
"version": "5.16.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.16.2.tgz",
|
||||||
|
"integrity": "sha512-qUxwMtrwoG3byd4PbX6T7EjHJ8AUhzTuwniOGkh/hIznBfcE2QQnGakyEq4VnwNuttMqvh/GgPFapHQ3lCuRHg==",
|
||||||
|
"devOptional": true,
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@prisma/debug": "5.16.2",
|
||||||
|
"@prisma/engines-version": "5.16.0-24.34ace0eb2704183d2c05b60b52fba5c43c13f303",
|
||||||
|
"@prisma/fetch-engine": "5.16.2",
|
||||||
|
"@prisma/get-platform": "5.16.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@prisma/engines-version": {
|
||||||
|
"version": "5.16.0-24.34ace0eb2704183d2c05b60b52fba5c43c13f303",
|
||||||
|
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.16.0-24.34ace0eb2704183d2c05b60b52fba5c43c13f303.tgz",
|
||||||
|
"integrity": "sha512-HkT2WbfmFZ9WUPyuJHhkiADxazHg8Y4gByrTSVeb3OikP6tjQ7txtSUGu9OBOBH0C13dPKN2qqH12xKtHu/Hiw==",
|
||||||
|
"devOptional": true
|
||||||
|
},
|
||||||
|
"node_modules/@prisma/fetch-engine": {
|
||||||
|
"version": "5.16.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.16.2.tgz",
|
||||||
|
"integrity": "sha512-sq51lfHKfH2jjYSjBtMjP+AznFqOJzXpqmq6B9auWrlTJrMgZ7lPyhWUW7VU7LsQU48/TJ+DZeIz8s9bMYvcHg==",
|
||||||
|
"devOptional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@prisma/debug": "5.16.2",
|
||||||
|
"@prisma/engines-version": "5.16.0-24.34ace0eb2704183d2c05b60b52fba5c43c13f303",
|
||||||
|
"@prisma/get-platform": "5.16.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@prisma/get-platform": {
|
||||||
|
"version": "5.16.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.16.2.tgz",
|
||||||
|
"integrity": "sha512-cXiHPgNLNyj22vLouPVNegklpRL/iX2jxTeap5GRO3DmCoVyIHmJAV1CgUMUJhHlcol9yYy7EHvsnXTDJ/PKEA==",
|
||||||
|
"devOptional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@prisma/debug": "5.16.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@sinclair/typebox": {
|
"node_modules/@sinclair/typebox": {
|
||||||
"version": "0.27.8",
|
"version": "0.27.8",
|
||||||
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
|
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
|
||||||
|
|
@ -2044,6 +2163,11 @@
|
||||||
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
|
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/luxon": {
|
||||||
|
"version": "3.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.4.2.tgz",
|
||||||
|
"integrity": "sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA=="
|
||||||
|
},
|
||||||
"node_modules/@types/methods": {
|
"node_modules/@types/methods": {
|
||||||
"version": "1.1.4",
|
"version": "1.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz",
|
||||||
|
|
@ -2706,8 +2830,18 @@
|
||||||
"node_modules/asynckit": {
|
"node_modules/asynckit": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||||
"dev": true
|
},
|
||||||
|
"node_modules/axios": {
|
||||||
|
"version": "1.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz",
|
||||||
|
"integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"follow-redirects": "^1.15.6",
|
||||||
|
"form-data": "^4.0.0",
|
||||||
|
"proxy-from-env": "^1.1.0"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"node_modules/babel-jest": {
|
"node_modules/babel-jest": {
|
||||||
"version": "29.7.0",
|
"version": "29.7.0",
|
||||||
|
|
@ -3310,7 +3444,6 @@
|
||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"delayed-stream": "~1.0.0"
|
"delayed-stream": "~1.0.0"
|
||||||
},
|
},
|
||||||
|
|
@ -3491,6 +3624,15 @@
|
||||||
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
|
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/cron": {
|
||||||
|
"version": "3.1.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/cron/-/cron-3.1.7.tgz",
|
||||||
|
"integrity": "sha512-tlBg7ARsAMQLzgwqVxy8AZl/qlTc5nibqYwtNGoCrd+cV+ugI+tvZC1oT/8dFH8W455YrywGykx/KMmAqOr7Jw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/luxon": "~3.4.0",
|
||||||
|
"luxon": "~3.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cross-spawn": {
|
"node_modules/cross-spawn": {
|
||||||
"version": "7.0.3",
|
"version": "7.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||||
|
|
@ -3505,6 +3647,23 @@
|
||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/date-fns": {
|
||||||
|
"version": "3.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz",
|
||||||
|
"integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==",
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/kossnocorp"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/date-fns-tz": {
|
||||||
|
"version": "3.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/date-fns-tz/-/date-fns-tz-3.1.3.tgz",
|
||||||
|
"integrity": "sha512-ZfbMu+nbzW0mEzC8VZrLiSWvUIaI3aRHeq33mTe7Y38UctKukgqPR4nTDwcwS4d64Gf8GghnVsroBuMY3eiTeA==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"date-fns": "^3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/debug": {
|
"node_modules/debug": {
|
||||||
"version": "4.3.5",
|
"version": "4.3.5",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz",
|
||||||
|
|
@ -3583,7 +3742,6 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.4.0"
|
"node": ">=0.4.0"
|
||||||
}
|
}
|
||||||
|
|
@ -3666,6 +3824,25 @@
|
||||||
"node": ">=6.0.0"
|
"node": ">=6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/dotenv": {
|
||||||
|
"version": "16.4.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz",
|
||||||
|
"integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://dotenvx.com"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/dotenv-expand": {
|
||||||
|
"version": "10.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-10.0.0.tgz",
|
||||||
|
"integrity": "sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/eastasianwidth": {
|
"node_modules/eastasianwidth": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
||||||
|
|
@ -4043,6 +4220,11 @@
|
||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/eventemitter2": {
|
||||||
|
"version": "6.4.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.9.tgz",
|
||||||
|
"integrity": "sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg=="
|
||||||
|
},
|
||||||
"node_modules/events": {
|
"node_modules/events": {
|
||||||
"version": "3.3.0",
|
"version": "3.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
|
||||||
|
|
@ -4356,6 +4538,26 @@
|
||||||
"integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==",
|
"integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/follow-redirects": {
|
||||||
|
"version": "1.15.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
|
||||||
|
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"debug": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/foreground-child": {
|
"node_modules/foreground-child": {
|
||||||
"version": "3.2.1",
|
"version": "3.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz",
|
||||||
|
|
@ -4426,7 +4628,6 @@
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"asynckit": "^0.4.0",
|
"asynckit": "^0.4.0",
|
||||||
"combined-stream": "^1.0.8",
|
"combined-stream": "^1.0.8",
|
||||||
|
|
@ -5968,8 +6169,7 @@
|
||||||
"node_modules/lodash": {
|
"node_modules/lodash": {
|
||||||
"version": "4.17.21",
|
"version": "4.17.21",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"node_modules/lodash.memoize": {
|
"node_modules/lodash.memoize": {
|
||||||
"version": "4.1.2",
|
"version": "4.1.2",
|
||||||
|
|
@ -6008,6 +6208,14 @@
|
||||||
"yallist": "^3.0.2"
|
"yallist": "^3.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/luxon": {
|
||||||
|
"version": "3.4.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.4.tgz",
|
||||||
|
"integrity": "sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/magic-string": {
|
"node_modules/magic-string": {
|
||||||
"version": "0.30.8",
|
"version": "0.30.8",
|
||||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.8.tgz",
|
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.8.tgz",
|
||||||
|
|
@ -6741,6 +6949,22 @@
|
||||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/prisma": {
|
||||||
|
"version": "5.16.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/prisma/-/prisma-5.16.2.tgz",
|
||||||
|
"integrity": "sha512-rFV/xoBR2hBGGlu4LPLQd4U8WVA+tSAmYyFWGPRVfj+xg7N4kiZV4lSk38htSpF+/IuHKzlrbh4SFk8Z18cI8A==",
|
||||||
|
"devOptional": true,
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@prisma/engines": "5.16.2"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"prisma": "build/index.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.13"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/process-nextick-args": {
|
"node_modules/process-nextick-args": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||||
|
|
@ -6771,6 +6995,12 @@
|
||||||
"node": ">= 0.10"
|
"node": ">= 0.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/proxy-from-env": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
"node_modules/punycode": {
|
"node_modules/punycode": {
|
||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||||
|
|
@ -8174,6 +8404,18 @@
|
||||||
"node": ">= 0.4.0"
|
"node": ">= 0.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/uuid": {
|
||||||
|
"version": "10.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz",
|
||||||
|
"integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==",
|
||||||
|
"funding": [
|
||||||
|
"https://github.com/sponsors/broofa",
|
||||||
|
"https://github.com/sponsors/ctavan"
|
||||||
|
],
|
||||||
|
"bin": {
|
||||||
|
"uuid": "dist/bin/uuid"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/v8-compile-cache-lib": {
|
"node_modules/v8-compile-cache-lib": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
|
||||||
|
|
@ -9643,6 +9885,12 @@
|
||||||
"resolved": "https://registry.npmjs.org/@lukeed/csprng/-/csprng-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@lukeed/csprng/-/csprng-1.1.0.tgz",
|
||||||
"integrity": "sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA=="
|
"integrity": "sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA=="
|
||||||
},
|
},
|
||||||
|
"@nestjs/axios": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@nestjs/axios/-/axios-3.0.2.tgz",
|
||||||
|
"integrity": "sha512-Z6GuOUdNQjP7FX+OuV2Ybyamse+/e0BFdTWBX5JxpBDKA+YkdLynDgG6HTF04zy6e9zPa19UX0WA2VDoehwhXQ==",
|
||||||
|
"requires": {}
|
||||||
|
},
|
||||||
"@nestjs/cli": {
|
"@nestjs/cli": {
|
||||||
"version": "10.4.2",
|
"version": "10.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-10.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-10.4.2.tgz",
|
||||||
|
|
@ -9688,6 +9936,16 @@
|
||||||
"uid": "2.0.2"
|
"uid": "2.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@nestjs/config": {
|
||||||
|
"version": "3.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@nestjs/config/-/config-3.2.3.tgz",
|
||||||
|
"integrity": "sha512-p6yv/CvoBewJ72mBq4NXgOAi2rSQNWx3a+IMJLVKS2uiwFCOQQuiIatGwq6MRjXV3Jr+B41iUO8FIf4xBrZ4/w==",
|
||||||
|
"requires": {
|
||||||
|
"dotenv": "16.4.5",
|
||||||
|
"dotenv-expand": "10.0.0",
|
||||||
|
"lodash": "4.17.21"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@nestjs/core": {
|
"@nestjs/core": {
|
||||||
"version": "10.3.10",
|
"version": "10.3.10",
|
||||||
"resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.3.10.tgz",
|
"resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.3.10.tgz",
|
||||||
|
|
@ -9701,6 +9959,14 @@
|
||||||
"uid": "2.0.2"
|
"uid": "2.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@nestjs/event-emitter": {
|
||||||
|
"version": "2.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@nestjs/event-emitter/-/event-emitter-2.0.4.tgz",
|
||||||
|
"integrity": "sha512-quMiw8yOwoSul0pp3mOonGz8EyXWHSBTqBy8B0TbYYgpnG1Ix2wGUnuTksLWaaBiiOTDhciaZ41Y5fJZsSJE1Q==",
|
||||||
|
"requires": {
|
||||||
|
"eventemitter2": "6.4.9"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@nestjs/platform-express": {
|
"@nestjs/platform-express": {
|
||||||
"version": "10.3.10",
|
"version": "10.3.10",
|
||||||
"resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.3.10.tgz",
|
"resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.3.10.tgz",
|
||||||
|
|
@ -9713,6 +9979,15 @@
|
||||||
"tslib": "2.6.3"
|
"tslib": "2.6.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@nestjs/schedule": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@nestjs/schedule/-/schedule-4.1.0.tgz",
|
||||||
|
"integrity": "sha512-WEc96WTXZW+VI/Ng+uBpiBUwm6TWtAbQ4RKWkfbmzKvmbRGzA/9k/UyAWDS9k0pp+ZcbC+MaZQtt7TjQHrwX6g==",
|
||||||
|
"requires": {
|
||||||
|
"cron": "3.1.7",
|
||||||
|
"uuid": "10.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@nestjs/schematics": {
|
"@nestjs/schematics": {
|
||||||
"version": "10.1.2",
|
"version": "10.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-10.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-10.1.2.tgz",
|
||||||
|
|
@ -9792,6 +10067,56 @@
|
||||||
"integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==",
|
"integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"@prisma/client": {
|
||||||
|
"version": "5.16.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.16.2.tgz",
|
||||||
|
"integrity": "sha512-+1lmkhR9gHWcTC5oghm2ZKpWljyWdzfazCVlLKUWXVmwHSf52g81aZ8qb6Km5Bs025yBi7puLp3qSLEvktoUtw==",
|
||||||
|
"requires": {}
|
||||||
|
},
|
||||||
|
"@prisma/debug": {
|
||||||
|
"version": "5.16.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.16.2.tgz",
|
||||||
|
"integrity": "sha512-ItzB4nR4O8eLzuJiuP3WwUJfoIvewMHqpGCad+64gvThcKEVOtaUza9AEJo2DPqAOa/AWkFyK54oM4WwHeew+A==",
|
||||||
|
"devOptional": true
|
||||||
|
},
|
||||||
|
"@prisma/engines": {
|
||||||
|
"version": "5.16.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.16.2.tgz",
|
||||||
|
"integrity": "sha512-qUxwMtrwoG3byd4PbX6T7EjHJ8AUhzTuwniOGkh/hIznBfcE2QQnGakyEq4VnwNuttMqvh/GgPFapHQ3lCuRHg==",
|
||||||
|
"devOptional": true,
|
||||||
|
"requires": {
|
||||||
|
"@prisma/debug": "5.16.2",
|
||||||
|
"@prisma/engines-version": "5.16.0-24.34ace0eb2704183d2c05b60b52fba5c43c13f303",
|
||||||
|
"@prisma/fetch-engine": "5.16.2",
|
||||||
|
"@prisma/get-platform": "5.16.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@prisma/engines-version": {
|
||||||
|
"version": "5.16.0-24.34ace0eb2704183d2c05b60b52fba5c43c13f303",
|
||||||
|
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.16.0-24.34ace0eb2704183d2c05b60b52fba5c43c13f303.tgz",
|
||||||
|
"integrity": "sha512-HkT2WbfmFZ9WUPyuJHhkiADxazHg8Y4gByrTSVeb3OikP6tjQ7txtSUGu9OBOBH0C13dPKN2qqH12xKtHu/Hiw==",
|
||||||
|
"devOptional": true
|
||||||
|
},
|
||||||
|
"@prisma/fetch-engine": {
|
||||||
|
"version": "5.16.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.16.2.tgz",
|
||||||
|
"integrity": "sha512-sq51lfHKfH2jjYSjBtMjP+AznFqOJzXpqmq6B9auWrlTJrMgZ7lPyhWUW7VU7LsQU48/TJ+DZeIz8s9bMYvcHg==",
|
||||||
|
"devOptional": true,
|
||||||
|
"requires": {
|
||||||
|
"@prisma/debug": "5.16.2",
|
||||||
|
"@prisma/engines-version": "5.16.0-24.34ace0eb2704183d2c05b60b52fba5c43c13f303",
|
||||||
|
"@prisma/get-platform": "5.16.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@prisma/get-platform": {
|
||||||
|
"version": "5.16.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.16.2.tgz",
|
||||||
|
"integrity": "sha512-cXiHPgNLNyj22vLouPVNegklpRL/iX2jxTeap5GRO3DmCoVyIHmJAV1CgUMUJhHlcol9yYy7EHvsnXTDJ/PKEA==",
|
||||||
|
"devOptional": true,
|
||||||
|
"requires": {
|
||||||
|
"@prisma/debug": "5.16.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@sinclair/typebox": {
|
"@sinclair/typebox": {
|
||||||
"version": "0.27.8",
|
"version": "0.27.8",
|
||||||
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
|
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
|
||||||
|
|
@ -10011,6 +10336,11 @@
|
||||||
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
|
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"@types/luxon": {
|
||||||
|
"version": "3.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.4.2.tgz",
|
||||||
|
"integrity": "sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA=="
|
||||||
|
},
|
||||||
"@types/methods": {
|
"@types/methods": {
|
||||||
"version": "1.1.4",
|
"version": "1.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz",
|
||||||
|
|
@ -10524,8 +10854,18 @@
|
||||||
"asynckit": {
|
"asynckit": {
|
||||||
"version": "0.4.0",
|
"version": "0.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||||
"dev": true
|
},
|
||||||
|
"axios": {
|
||||||
|
"version": "1.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz",
|
||||||
|
"integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==",
|
||||||
|
"peer": true,
|
||||||
|
"requires": {
|
||||||
|
"follow-redirects": "^1.15.6",
|
||||||
|
"form-data": "^4.0.0",
|
||||||
|
"proxy-from-env": "^1.1.0"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"babel-jest": {
|
"babel-jest": {
|
||||||
"version": "29.7.0",
|
"version": "29.7.0",
|
||||||
|
|
@ -10945,7 +11285,6 @@
|
||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"delayed-stream": "~1.0.0"
|
"delayed-stream": "~1.0.0"
|
||||||
}
|
}
|
||||||
|
|
@ -11079,6 +11418,15 @@
|
||||||
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
|
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"cron": {
|
||||||
|
"version": "3.1.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/cron/-/cron-3.1.7.tgz",
|
||||||
|
"integrity": "sha512-tlBg7ARsAMQLzgwqVxy8AZl/qlTc5nibqYwtNGoCrd+cV+ugI+tvZC1oT/8dFH8W455YrywGykx/KMmAqOr7Jw==",
|
||||||
|
"requires": {
|
||||||
|
"@types/luxon": "~3.4.0",
|
||||||
|
"luxon": "~3.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"cross-spawn": {
|
"cross-spawn": {
|
||||||
"version": "7.0.3",
|
"version": "7.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||||
|
|
@ -11090,6 +11438,17 @@
|
||||||
"which": "^2.0.1"
|
"which": "^2.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"date-fns": {
|
||||||
|
"version": "3.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz",
|
||||||
|
"integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww=="
|
||||||
|
},
|
||||||
|
"date-fns-tz": {
|
||||||
|
"version": "3.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/date-fns-tz/-/date-fns-tz-3.1.3.tgz",
|
||||||
|
"integrity": "sha512-ZfbMu+nbzW0mEzC8VZrLiSWvUIaI3aRHeq33mTe7Y38UctKukgqPR4nTDwcwS4d64Gf8GghnVsroBuMY3eiTeA==",
|
||||||
|
"requires": {}
|
||||||
|
},
|
||||||
"debug": {
|
"debug": {
|
||||||
"version": "4.3.5",
|
"version": "4.3.5",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz",
|
||||||
|
|
@ -11140,8 +11499,7 @@
|
||||||
"delayed-stream": {
|
"delayed-stream": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"depd": {
|
"depd": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
|
|
@ -11199,6 +11557,16 @@
|
||||||
"esutils": "^2.0.2"
|
"esutils": "^2.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"dotenv": {
|
||||||
|
"version": "16.4.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz",
|
||||||
|
"integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg=="
|
||||||
|
},
|
||||||
|
"dotenv-expand": {
|
||||||
|
"version": "10.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-10.0.0.tgz",
|
||||||
|
"integrity": "sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A=="
|
||||||
|
},
|
||||||
"eastasianwidth": {
|
"eastasianwidth": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
||||||
|
|
@ -11467,6 +11835,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
||||||
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="
|
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="
|
||||||
},
|
},
|
||||||
|
"eventemitter2": {
|
||||||
|
"version": "6.4.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.9.tgz",
|
||||||
|
"integrity": "sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg=="
|
||||||
|
},
|
||||||
"events": {
|
"events": {
|
||||||
"version": "3.3.0",
|
"version": "3.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
|
||||||
|
|
@ -11737,6 +12110,12 @@
|
||||||
"integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==",
|
"integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"follow-redirects": {
|
||||||
|
"version": "1.15.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
|
||||||
|
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
"foreground-child": {
|
"foreground-child": {
|
||||||
"version": "3.2.1",
|
"version": "3.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz",
|
||||||
|
|
@ -11792,7 +12171,6 @@
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||||
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"asynckit": "^0.4.0",
|
"asynckit": "^0.4.0",
|
||||||
"combined-stream": "^1.0.8",
|
"combined-stream": "^1.0.8",
|
||||||
|
|
@ -12929,8 +13307,7 @@
|
||||||
"lodash": {
|
"lodash": {
|
||||||
"version": "4.17.21",
|
"version": "4.17.21",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"lodash.memoize": {
|
"lodash.memoize": {
|
||||||
"version": "4.1.2",
|
"version": "4.1.2",
|
||||||
|
|
@ -12963,6 +13340,11 @@
|
||||||
"yallist": "^3.0.2"
|
"yallist": "^3.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"luxon": {
|
||||||
|
"version": "3.4.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.4.tgz",
|
||||||
|
"integrity": "sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA=="
|
||||||
|
},
|
||||||
"magic-string": {
|
"magic-string": {
|
||||||
"version": "0.30.8",
|
"version": "0.30.8",
|
||||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.8.tgz",
|
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.8.tgz",
|
||||||
|
|
@ -13489,6 +13871,15 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"prisma": {
|
||||||
|
"version": "5.16.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/prisma/-/prisma-5.16.2.tgz",
|
||||||
|
"integrity": "sha512-rFV/xoBR2hBGGlu4LPLQd4U8WVA+tSAmYyFWGPRVfj+xg7N4kiZV4lSk38htSpF+/IuHKzlrbh4SFk8Z18cI8A==",
|
||||||
|
"devOptional": true,
|
||||||
|
"requires": {
|
||||||
|
"@prisma/engines": "5.16.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"process-nextick-args": {
|
"process-nextick-args": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||||
|
|
@ -13513,6 +13904,12 @@
|
||||||
"ipaddr.js": "1.9.1"
|
"ipaddr.js": "1.9.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"proxy-from-env": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
"punycode": {
|
"punycode": {
|
||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||||
|
|
@ -14497,6 +14894,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
||||||
"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="
|
"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="
|
||||||
},
|
},
|
||||||
|
"uuid": {
|
||||||
|
"version": "10.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz",
|
||||||
|
"integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ=="
|
||||||
|
},
|
||||||
"v8-compile-cache-lib": {
|
"v8-compile-cache-lib": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
|
||||||
|
|
|
||||||
|
|
@ -20,9 +20,16 @@
|
||||||
"test:e2e": "jest --config ./test/jest-e2e.json"
|
"test:e2e": "jest --config ./test/jest-e2e.json"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@nestjs/axios": "^3.0.2",
|
||||||
"@nestjs/common": "^10.0.0",
|
"@nestjs/common": "^10.0.0",
|
||||||
|
"@nestjs/config": "^3.2.3",
|
||||||
"@nestjs/core": "^10.0.0",
|
"@nestjs/core": "^10.0.0",
|
||||||
|
"@nestjs/event-emitter": "^2.0.4",
|
||||||
"@nestjs/platform-express": "^10.0.0",
|
"@nestjs/platform-express": "^10.0.0",
|
||||||
|
"@nestjs/schedule": "^4.1.0",
|
||||||
|
"@prisma/client": "^5.16.2",
|
||||||
|
"date-fns": "^3.6.0",
|
||||||
|
"date-fns-tz": "^3.1.3",
|
||||||
"reflect-metadata": "^0.2.0",
|
"reflect-metadata": "^0.2.0",
|
||||||
"rxjs": "^7.8.1"
|
"rxjs": "^7.8.1"
|
||||||
},
|
},
|
||||||
|
|
@ -41,6 +48,7 @@
|
||||||
"eslint-plugin-prettier": "^5.0.0",
|
"eslint-plugin-prettier": "^5.0.0",
|
||||||
"jest": "^29.5.0",
|
"jest": "^29.5.0",
|
||||||
"prettier": "^3.0.0",
|
"prettier": "^3.0.0",
|
||||||
|
"prisma": "^5.16.2",
|
||||||
"source-map-support": "^0.5.21",
|
"source-map-support": "^0.5.21",
|
||||||
"supertest": "^7.0.0",
|
"supertest": "^7.0.0",
|
||||||
"ts-jest": "^29.1.0",
|
"ts-jest": "^29.1.0",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,473 @@
|
||||||
|
-- CreateExtension
|
||||||
|
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
|
||||||
|
|
||||||
|
-- CreateEnum
|
||||||
|
CREATE TYPE "TicketState" AS ENUM ('CREATED', 'APPROVAL_REQUIRED', 'READY', 'DONE', 'BILLABLE', 'ARCHIVED');
|
||||||
|
|
||||||
|
-- CreateEnum
|
||||||
|
CREATE TYPE "TicketValidationState" AS ENUM ('CREATED', 'USER_INTERACTION_REQUIRED', 'TD_UNCERTAIN', 'DOCUMENTS_MISSING', 'ARCHIVED');
|
||||||
|
|
||||||
|
-- CreateEnum
|
||||||
|
CREATE TYPE "ApprovalState" AS ENUM ('UNKNOWN', 'FREE', 'REQUIRED', 'REQUESTED', 'APPROVED');
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "TourSource" (
|
||||||
|
"internalId" UUID NOT NULL DEFAULT gen_random_uuid(),
|
||||||
|
"insertedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"jobId" TEXT NOT NULL,
|
||||||
|
"id" TEXT,
|
||||||
|
"check" TEXT,
|
||||||
|
"createTime" TEXT,
|
||||||
|
"empfangenVonId" TEXT,
|
||||||
|
"endgen" TEXT,
|
||||||
|
"serienId" TEXT,
|
||||||
|
"serienKategorName" TEXT,
|
||||||
|
"serienKategorie" TEXT,
|
||||||
|
"zadStatus" TEXT,
|
||||||
|
"abrDate" TEXT,
|
||||||
|
"abrNum" TEXT,
|
||||||
|
"abrTime" TEXT,
|
||||||
|
"ausrueckZeit" TEXT,
|
||||||
|
"startTime" TEXT,
|
||||||
|
"startDate" TEXT,
|
||||||
|
"date" TEXT,
|
||||||
|
"date_iso" TEXT,
|
||||||
|
"dauergenehmigung" TEXT,
|
||||||
|
"direction" TEXT,
|
||||||
|
"distanceValue" TEXT,
|
||||||
|
"besetztKm" TEXT,
|
||||||
|
"gefahren" TEXT,
|
||||||
|
"gesamtPreis" TEXT,
|
||||||
|
"infektion" TEXT,
|
||||||
|
"itDesc" TEXT,
|
||||||
|
"itKrz" TEXT,
|
||||||
|
"itName" TEXT,
|
||||||
|
"kkId" TEXT,
|
||||||
|
"krankenkasse" TEXT,
|
||||||
|
"patId" TEXT,
|
||||||
|
"patName" TEXT,
|
||||||
|
"patSurname" TEXT,
|
||||||
|
"patStreet" TEXT,
|
||||||
|
"patZip" TEXT,
|
||||||
|
"patCity" TEXT,
|
||||||
|
"startInstitution" TEXT,
|
||||||
|
"startStreet" TEXT,
|
||||||
|
"startZip" TEXT,
|
||||||
|
"startCity" TEXT,
|
||||||
|
"targetInstitution" TEXT,
|
||||||
|
"targetStreet" TEXT,
|
||||||
|
"targetZip" TEXT,
|
||||||
|
"targetCity" TEXT,
|
||||||
|
"transportKrz" TEXT,
|
||||||
|
"transportName" TEXT,
|
||||||
|
"transportart" TEXT,
|
||||||
|
"verordnungsId" TEXT,
|
||||||
|
"verordnungsName" TEXT,
|
||||||
|
"type" TEXT,
|
||||||
|
"startBegin" TEXT,
|
||||||
|
"startEnd" TEXT,
|
||||||
|
"frei" TEXT,
|
||||||
|
"ziel" TEXT,
|
||||||
|
"zielBegin" TEXT,
|
||||||
|
"endTime" TEXT,
|
||||||
|
"endDate" TEXT,
|
||||||
|
"einsatzNummer" TEXT,
|
||||||
|
"carName" TEXT,
|
||||||
|
"carNo" TEXT,
|
||||||
|
"beifahrerName" TEXT,
|
||||||
|
"beifahrer" TEXT,
|
||||||
|
"fahrerName" TEXT,
|
||||||
|
"fahrer" TEXT,
|
||||||
|
"color" TEXT,
|
||||||
|
|
||||||
|
CONSTRAINT "TourSource_pkey" PRIMARY KEY ("internalId")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "EmployeeDispoLiveSource" (
|
||||||
|
"internalId" UUID NOT NULL DEFAULT gen_random_uuid(),
|
||||||
|
"insertedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"jobId" TEXT NOT NULL,
|
||||||
|
"id" TEXT,
|
||||||
|
"pScheinAblaufDatum" TEXT,
|
||||||
|
"name" TEXT,
|
||||||
|
"surname" TEXT,
|
||||||
|
"persId" TEXT,
|
||||||
|
"importPersId" TEXT,
|
||||||
|
"qualifikation" TEXT,
|
||||||
|
|
||||||
|
CONSTRAINT "EmployeeDispoLiveSource_pkey" PRIMARY KEY ("internalId")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "AttendanceRegistrationSource" (
|
||||||
|
"internalId" UUID NOT NULL DEFAULT gen_random_uuid(),
|
||||||
|
"insertedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"jobId" TEXT NOT NULL,
|
||||||
|
"attendanceRegistrationId" TEXT,
|
||||||
|
"employeeId" TEXT,
|
||||||
|
"personnelNumber" TEXT,
|
||||||
|
"dateTime" TEXT,
|
||||||
|
"event" TEXT,
|
||||||
|
|
||||||
|
CONSTRAINT "AttendanceRegistrationSource_pkey" PRIMARY KEY ("internalId")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "PlannedTimeSource" (
|
||||||
|
"internalId" UUID NOT NULL DEFAULT gen_random_uuid(),
|
||||||
|
"insertedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"jobId" TEXT NOT NULL,
|
||||||
|
"id" TEXT,
|
||||||
|
"user_id" TEXT,
|
||||||
|
"firstname" TEXT,
|
||||||
|
"surname" TEXT,
|
||||||
|
"department_id" TEXT,
|
||||||
|
"department_name" TEXT,
|
||||||
|
"start_date" TEXT,
|
||||||
|
"end_date" TEXT,
|
||||||
|
"pauze" TEXT,
|
||||||
|
"duration" TEXT,
|
||||||
|
"deleted" TEXT,
|
||||||
|
"mark" TEXT,
|
||||||
|
|
||||||
|
CONSTRAINT "PlannedTimeSource_pkey" PRIMARY KEY ("internalId")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "WorkTimeSource" (
|
||||||
|
"internalId" UUID NOT NULL DEFAULT gen_random_uuid(),
|
||||||
|
"insertedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"jobId" TEXT NOT NULL,
|
||||||
|
"id" TEXT,
|
||||||
|
"user_id" TEXT,
|
||||||
|
"firstname" TEXT,
|
||||||
|
"surname" TEXT,
|
||||||
|
"department_id" TEXT,
|
||||||
|
"department_name" TEXT,
|
||||||
|
"start_date" TEXT,
|
||||||
|
"end_date" TEXT,
|
||||||
|
"pauze" TEXT,
|
||||||
|
"duration" TEXT,
|
||||||
|
|
||||||
|
CONSTRAINT "WorkTimeSource_pkey" PRIMARY KEY ("internalId")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "EmployeeDyflexisSource" (
|
||||||
|
"internalId" UUID NOT NULL DEFAULT gen_random_uuid(),
|
||||||
|
"insertedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"jobId" TEXT NOT NULL,
|
||||||
|
"id" TEXT,
|
||||||
|
"firstname" TEXT,
|
||||||
|
"surname" TEXT,
|
||||||
|
"contract_id" TEXT,
|
||||||
|
"contract_start" TEXT,
|
||||||
|
"contract_end" TEXT,
|
||||||
|
"contract_hours_week" TEXT,
|
||||||
|
"contract_salary_hour" TEXT,
|
||||||
|
|
||||||
|
CONSTRAINT "EmployeeDyflexisSource_pkey" PRIMARY KEY ("internalId")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "PatientSource" (
|
||||||
|
"internalId" UUID NOT NULL DEFAULT gen_random_uuid(),
|
||||||
|
"insertedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"jobId" TEXT NOT NULL,
|
||||||
|
"id" TEXT,
|
||||||
|
"surname" TEXT,
|
||||||
|
"name" TEXT,
|
||||||
|
"street" TEXT,
|
||||||
|
"zip" TEXT,
|
||||||
|
"city" TEXT,
|
||||||
|
"birthday" TEXT,
|
||||||
|
"kkId" TEXT,
|
||||||
|
"krankenkasse" TEXT,
|
||||||
|
"pflegestufe" TEXT,
|
||||||
|
"behinderung1" TEXT,
|
||||||
|
"behinderung2" TEXT,
|
||||||
|
|
||||||
|
CONSTRAINT "PatientSource_pkey" PRIMARY KEY ("internalId")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "TourStaging" (
|
||||||
|
"internalId" UUID NOT NULL DEFAULT gen_random_uuid(),
|
||||||
|
"id" TEXT,
|
||||||
|
"done" BOOLEAN,
|
||||||
|
"direction" TEXT,
|
||||||
|
"carName" TEXT,
|
||||||
|
"operationId" TEXT,
|
||||||
|
"driverId" TEXT,
|
||||||
|
"driverName" TEXT,
|
||||||
|
"codriverId" TEXT,
|
||||||
|
"codriverName" TEXT,
|
||||||
|
"patientId" TEXT,
|
||||||
|
"patientName" TEXT,
|
||||||
|
"patientSurname" TEXT,
|
||||||
|
"patientStreet" TEXT,
|
||||||
|
"patientZip" TEXT,
|
||||||
|
"patientCity" TEXT,
|
||||||
|
"healthInsurance" TEXT,
|
||||||
|
"healthInsuranceNumber" TEXT,
|
||||||
|
"type" TEXT,
|
||||||
|
"category" TEXT,
|
||||||
|
"transportType" TEXT,
|
||||||
|
"ordinanceType" TEXT,
|
||||||
|
"rangeEndDate" TIMESTAMP(3),
|
||||||
|
"startInstitution" TEXT,
|
||||||
|
"startStreet" TEXT,
|
||||||
|
"startZip" TEXT,
|
||||||
|
"startCity" TEXT,
|
||||||
|
"targetInstitution" TEXT,
|
||||||
|
"targetStreet" TEXT,
|
||||||
|
"targetZip" TEXT,
|
||||||
|
"targetCity" TEXT,
|
||||||
|
"startDate" TIMESTAMP(3),
|
||||||
|
"check" INTEGER NOT NULL DEFAULT 0,
|
||||||
|
"occupiedKm" DOUBLE PRECISION,
|
||||||
|
"totalKm" DOUBLE PRECISION,
|
||||||
|
"startBegin" TEXT,
|
||||||
|
"startEnd" TEXT,
|
||||||
|
"target" TEXT,
|
||||||
|
"targetBegin" TEXT,
|
||||||
|
"empty" TEXT,
|
||||||
|
"hasInfection" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"infectionName" TEXT,
|
||||||
|
"revenueDispoLive" DOUBLE PRECISION,
|
||||||
|
"billDate" TIMESTAMP(3),
|
||||||
|
"billNumber" TEXT,
|
||||||
|
"revenue" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||||
|
"revenueDeviation" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||||
|
"consumptionCosts" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||||
|
"createdAt" TIMESTAMP(3),
|
||||||
|
|
||||||
|
CONSTRAINT "TourStaging_pkey" PRIMARY KEY ("internalId")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "PatientStaging" (
|
||||||
|
"internalId" UUID NOT NULL DEFAULT gen_random_uuid(),
|
||||||
|
"id" TEXT,
|
||||||
|
"surname" TEXT,
|
||||||
|
"name" TEXT,
|
||||||
|
"street" TEXT,
|
||||||
|
"zip" TEXT,
|
||||||
|
"city" TEXT,
|
||||||
|
"birthday" TIMESTAMP(3),
|
||||||
|
"healthinsuranceId" TEXT,
|
||||||
|
"healthinsurance" TEXT,
|
||||||
|
"careDegree" TEXT,
|
||||||
|
"disabilityMark1" TEXT,
|
||||||
|
"disabilityMark2" TEXT,
|
||||||
|
|
||||||
|
CONSTRAINT "PatientStaging_pkey" PRIMARY KEY ("internalId")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "AttendanceRegistrationStaging" (
|
||||||
|
"internalId" UUID NOT NULL DEFAULT gen_random_uuid(),
|
||||||
|
"attendanceRegistrationId" INTEGER NOT NULL,
|
||||||
|
"employeeId" INTEGER NOT NULL,
|
||||||
|
"dateTime" TIMESTAMP(3) NOT NULL,
|
||||||
|
"event" TEXT NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "AttendanceRegistrationStaging_pkey" PRIMARY KEY ("internalId")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "PlannedTimeStaging" (
|
||||||
|
"internalId" UUID NOT NULL DEFAULT gen_random_uuid(),
|
||||||
|
"id" INTEGER NOT NULL,
|
||||||
|
"employeeId" INTEGER NOT NULL,
|
||||||
|
"firstname" TEXT,
|
||||||
|
"surname" TEXT,
|
||||||
|
"departmentName" TEXT,
|
||||||
|
"startDate" TIMESTAMP(3) NOT NULL,
|
||||||
|
"endDate" TIMESTAMP(3) NOT NULL,
|
||||||
|
"deleted" BOOLEAN NOT NULL,
|
||||||
|
"mark" TEXT,
|
||||||
|
|
||||||
|
CONSTRAINT "PlannedTimeStaging_pkey" PRIMARY KEY ("internalId")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "WorkTimeStaging" (
|
||||||
|
"internalId" UUID NOT NULL DEFAULT gen_random_uuid(),
|
||||||
|
"id" INTEGER NOT NULL,
|
||||||
|
"employeeId" INTEGER NOT NULL,
|
||||||
|
"firstname" TEXT NOT NULL,
|
||||||
|
"surname" TEXT NOT NULL,
|
||||||
|
"departmentName" TEXT,
|
||||||
|
"startDate" TIMESTAMP(3) NOT NULL,
|
||||||
|
"endDate" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "WorkTimeStaging_pkey" PRIMARY KEY ("internalId")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "EmployeeStaging" (
|
||||||
|
"internalId" UUID NOT NULL DEFAULT gen_random_uuid(),
|
||||||
|
"id" INTEGER NOT NULL,
|
||||||
|
"firstname" TEXT NOT NULL,
|
||||||
|
"surname" TEXT NOT NULL,
|
||||||
|
"contractStart" TIMESTAMP(3) NOT NULL,
|
||||||
|
"contractEnd" TIMESTAMP(3) NOT NULL,
|
||||||
|
"contractHoursWeek" DOUBLE PRECISION NOT NULL,
|
||||||
|
"contractSalaryHour" DOUBLE PRECISION NOT NULL,
|
||||||
|
"personTransportCertificate" TIMESTAMP(3),
|
||||||
|
"qualification" TEXT,
|
||||||
|
|
||||||
|
CONSTRAINT "EmployeeStaging_pkey" PRIMARY KEY ("internalId")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Tour" (
|
||||||
|
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
|
||||||
|
"done" BOOLEAN NOT NULL,
|
||||||
|
"direction" TEXT NOT NULL,
|
||||||
|
"carName" TEXT,
|
||||||
|
"operationId" TEXT NOT NULL,
|
||||||
|
"driverId" TEXT,
|
||||||
|
"driverName" TEXT,
|
||||||
|
"codriverId" TEXT,
|
||||||
|
"codriverName" TEXT,
|
||||||
|
"patientId" TEXT,
|
||||||
|
"patientName" TEXT,
|
||||||
|
"patientSurname" TEXT,
|
||||||
|
"patientStreet" TEXT,
|
||||||
|
"patientZip" TEXT,
|
||||||
|
"patientCity" TEXT,
|
||||||
|
"healthInsurance" TEXT,
|
||||||
|
"healthInsuranceNumber" TEXT,
|
||||||
|
"type" TEXT,
|
||||||
|
"category" TEXT,
|
||||||
|
"transportType" TEXT,
|
||||||
|
"ordinanceType" TEXT,
|
||||||
|
"rangeEndDate" TIMESTAMP(3),
|
||||||
|
"startInstitution" TEXT,
|
||||||
|
"startStreet" TEXT,
|
||||||
|
"startZip" TEXT,
|
||||||
|
"startCity" TEXT,
|
||||||
|
"targetInstitution" TEXT,
|
||||||
|
"targetStreet" TEXT,
|
||||||
|
"targetZip" TEXT,
|
||||||
|
"targetCity" TEXT,
|
||||||
|
"startDate" TIMESTAMP(3) NOT NULL,
|
||||||
|
"check" INTEGER NOT NULL DEFAULT 0,
|
||||||
|
"occupiedKm" DOUBLE PRECISION,
|
||||||
|
"totalKm" DOUBLE PRECISION,
|
||||||
|
"startBegin" TEXT,
|
||||||
|
"startEnd" TEXT,
|
||||||
|
"target" TEXT,
|
||||||
|
"targetBegin" TEXT,
|
||||||
|
"empty" TEXT,
|
||||||
|
"hasInfection" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"infectionName" TEXT,
|
||||||
|
"revenueDispoLive" DOUBLE PRECISION,
|
||||||
|
"billDate" TIMESTAMP(3),
|
||||||
|
"billNumber" TEXT,
|
||||||
|
"revenue" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||||
|
"consumptionCosts" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||||
|
"revenueDeviation" DOUBLE PRECISION NOT NULL DEFAULT 0,
|
||||||
|
"createdAt" TIMESTAMP(3),
|
||||||
|
"deletedAt" TIMESTAMP(3),
|
||||||
|
"ticketId" UUID,
|
||||||
|
|
||||||
|
CONSTRAINT "Tour_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Patient" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"surname" TEXT,
|
||||||
|
"name" TEXT,
|
||||||
|
"street" TEXT,
|
||||||
|
"zip" TEXT,
|
||||||
|
"city" TEXT,
|
||||||
|
"birthday" TIMESTAMP(3),
|
||||||
|
"healthinsuranceId" TEXT,
|
||||||
|
"healthinsurance" TEXT,
|
||||||
|
"careDegree" TEXT,
|
||||||
|
"disabilityMark1" TEXT,
|
||||||
|
"disabilityMark2" TEXT,
|
||||||
|
|
||||||
|
CONSTRAINT "Patient_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Ticket" (
|
||||||
|
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
|
||||||
|
"currentState" "TicketValidationState" NOT NULL,
|
||||||
|
"notes" TEXT[],
|
||||||
|
"errors" TEXT[],
|
||||||
|
"isUrgent" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"urgency" TIMESTAMP(3) NOT NULL DEFAULT '2099-12-31 23:59:59.999 +00:00',
|
||||||
|
"approvalState" "ApprovalState",
|
||||||
|
"documentInfo" JSONB DEFAULT '{"approval": {"isNeeded": null, "whoRequested": "","answeredFrom": ""},"tdLocation": ""}',
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
CONSTRAINT "Ticket_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "TicketStateHistory" (
|
||||||
|
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
|
||||||
|
"state" "TicketValidationState" NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"ticketId" UUID NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "TicketStateHistory_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "AttendanceRegistration" (
|
||||||
|
"attendanceRegistrationId" INTEGER NOT NULL,
|
||||||
|
"employeeId" INTEGER NOT NULL,
|
||||||
|
"personnelNumber" TEXT NOT NULL,
|
||||||
|
"dateTime" TIMESTAMP(3) NOT NULL,
|
||||||
|
"event" TEXT NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "AttendanceRegistration_pkey" PRIMARY KEY ("attendanceRegistrationId")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "WorkTime" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"userId" TEXT NOT NULL,
|
||||||
|
"firstname" TEXT NOT NULL,
|
||||||
|
"surname" TEXT NOT NULL,
|
||||||
|
"startDate" TIMESTAMP(3),
|
||||||
|
"endDate" TIMESTAMP(3),
|
||||||
|
"department" TEXT,
|
||||||
|
"pause" INTEGER NOT NULL,
|
||||||
|
"duration" INTEGER NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "WorkTime_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "PlannedTime" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"userId" TEXT NOT NULL,
|
||||||
|
"firstname" TEXT,
|
||||||
|
"surname" TEXT,
|
||||||
|
"startDate" TIMESTAMP(3),
|
||||||
|
"endDate" TIMESTAMP(3),
|
||||||
|
"department" TEXT,
|
||||||
|
"pause" INTEGER NOT NULL,
|
||||||
|
"duration" INTEGER NOT NULL,
|
||||||
|
"note" TEXT,
|
||||||
|
|
||||||
|
CONSTRAINT "PlannedTime_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "Tour_operationId_key" ON "Tour"("operationId");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Tour" ADD CONSTRAINT "Tour_ticketId_fkey" FOREIGN KEY ("ticketId") REFERENCES "Ticket"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "TicketStateHistory" ADD CONSTRAINT "TicketStateHistory_ticketId_fkey" FOREIGN KEY ("ticketId") REFERENCES "Ticket"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Please do not edit this file manually
|
||||||
|
# It should be added in your version-control system (i.e. Git)
|
||||||
|
provider = "postgresql"
|
||||||
|
|
@ -0,0 +1,480 @@
|
||||||
|
// This is your Prisma schema file,
|
||||||
|
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
||||||
|
|
||||||
|
// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
|
||||||
|
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
|
||||||
|
|
||||||
|
generator client {
|
||||||
|
provider = "prisma-client-js"
|
||||||
|
previewFeatures = ["postgresqlExtensions", "fieldReference"]
|
||||||
|
}
|
||||||
|
|
||||||
|
datasource db {
|
||||||
|
provider = "postgresql"
|
||||||
|
url = env("DATABASE_URL")
|
||||||
|
extensions = [pgcrypto]
|
||||||
|
}
|
||||||
|
|
||||||
|
model TourSource {
|
||||||
|
internalId String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
|
||||||
|
insertedAt DateTime @default(now())
|
||||||
|
jobId String
|
||||||
|
|
||||||
|
id String?
|
||||||
|
check String?
|
||||||
|
createTime String? // yyyy-MM-dd HH:mm:ss 'Europe/Berlin'
|
||||||
|
empfangenVonId String?
|
||||||
|
endgen String? // yyyy-MM-dd
|
||||||
|
serienId String?
|
||||||
|
serienKategorName String?
|
||||||
|
serienKategorie String?
|
||||||
|
zadStatus String?
|
||||||
|
abrDate String? // yyyy-MM-dd
|
||||||
|
abrNum String?
|
||||||
|
abrTime String? // HH:mm
|
||||||
|
|
||||||
|
ausrueckZeit String? // HH:mm 'Europe/Berlin'
|
||||||
|
startTime String? // HH:mm 'Europe/Berlin' (is after ausrueckZeit)
|
||||||
|
startDate String? // yyyy-MM-dd HH:mm:ss 'Europe/Berlin'
|
||||||
|
date String? // yyyy-MM-dd
|
||||||
|
date_iso String? // ISO 8601 String
|
||||||
|
dauergenehmigung String?
|
||||||
|
direction String?
|
||||||
|
distanceValue String? // meters (probably int)
|
||||||
|
besetztKm String? // kilometers (float)
|
||||||
|
gefahren String? // Boolean as number
|
||||||
|
gesamtPreis String? // float
|
||||||
|
infektion String? // Boolean as number
|
||||||
|
itDesc String?
|
||||||
|
itKrz String?
|
||||||
|
itName String?
|
||||||
|
kkId String?
|
||||||
|
krankenkasse String?
|
||||||
|
patId String?
|
||||||
|
patName String?
|
||||||
|
patSurname String?
|
||||||
|
patStreet String?
|
||||||
|
patZip String?
|
||||||
|
patCity String?
|
||||||
|
startInstitution String?
|
||||||
|
startStreet String?
|
||||||
|
startZip String?
|
||||||
|
startCity String?
|
||||||
|
targetInstitution String?
|
||||||
|
targetStreet String?
|
||||||
|
targetZip String?
|
||||||
|
targetCity String?
|
||||||
|
transportKrz String?
|
||||||
|
transportName String?
|
||||||
|
transportart String? // ID
|
||||||
|
verordnungsId String?
|
||||||
|
verordnungsName String?
|
||||||
|
type String?
|
||||||
|
|
||||||
|
startBegin String? // HH:mm 'Europe/Berlin'
|
||||||
|
startEnd String? // HH:mm 'Europe/Berlin'
|
||||||
|
frei String? // HH:mm 'Europe/Berlin'
|
||||||
|
ziel String? // HH:mm 'Europe/Berlin'
|
||||||
|
zielBegin String? // HH:mm 'Europe/Berlin'
|
||||||
|
endTime String? // HH:mm 'Europe/Berlin'
|
||||||
|
endDate String? // yyyy-MM-dd HH:mm:ss 'Europe/Berlin'
|
||||||
|
|
||||||
|
einsatzNummer String?
|
||||||
|
carName String?
|
||||||
|
carNo String? // ID
|
||||||
|
beifahrerName String?
|
||||||
|
beifahrer String? // ID
|
||||||
|
fahrerName String?
|
||||||
|
fahrer String? // ID
|
||||||
|
|
||||||
|
color String? // Hex rgb
|
||||||
|
}
|
||||||
|
|
||||||
|
model EmployeeDispoLiveSource {
|
||||||
|
internalId String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
|
||||||
|
insertedAt DateTime @default(now())
|
||||||
|
jobId String
|
||||||
|
|
||||||
|
id String?
|
||||||
|
pScheinAblaufDatum String? // yyyy-MM-dd
|
||||||
|
name String?
|
||||||
|
surname String?
|
||||||
|
persId String?
|
||||||
|
importPersId String? // Personal ID aus Dyflexis
|
||||||
|
qualifikation String? // ID
|
||||||
|
}
|
||||||
|
|
||||||
|
model AttendanceRegistrationSource {
|
||||||
|
internalId String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
|
||||||
|
insertedAt DateTime @default(now())
|
||||||
|
jobId String
|
||||||
|
|
||||||
|
attendanceRegistrationId String?
|
||||||
|
employeeId String?
|
||||||
|
personnelNumber String?
|
||||||
|
dateTime String? // ISO 8601 String with timezone
|
||||||
|
event String?
|
||||||
|
}
|
||||||
|
|
||||||
|
model PlannedTimeSource {
|
||||||
|
internalId String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
|
||||||
|
insertedAt DateTime @default(now())
|
||||||
|
jobId String
|
||||||
|
|
||||||
|
id String?
|
||||||
|
user_id String?
|
||||||
|
firstname String?
|
||||||
|
surname String?
|
||||||
|
department_id String?
|
||||||
|
department_name String?
|
||||||
|
start_date String? // yyyy-MM-dd HH:mm:ss 'Europe/Berlin'
|
||||||
|
end_date String? // yyyy-MM-dd HH:mm:ss 'Europe/Berlin'
|
||||||
|
pauze String?
|
||||||
|
duration String?
|
||||||
|
deleted String?
|
||||||
|
mark String?
|
||||||
|
}
|
||||||
|
|
||||||
|
model WorkTimeSource {
|
||||||
|
internalId String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
|
||||||
|
insertedAt DateTime @default(now())
|
||||||
|
jobId String
|
||||||
|
|
||||||
|
id String?
|
||||||
|
user_id String?
|
||||||
|
firstname String?
|
||||||
|
surname String?
|
||||||
|
department_id String?
|
||||||
|
department_name String?
|
||||||
|
start_date String? // yyyy-MM-dd HH:mm:ss 'Europe/Berlin'
|
||||||
|
end_date String? // yyyy-MM-dd HH:mm:ss 'Europe/Berlin'
|
||||||
|
pauze String?
|
||||||
|
duration String?
|
||||||
|
}
|
||||||
|
|
||||||
|
model EmployeeDyflexisSource {
|
||||||
|
internalId String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
|
||||||
|
insertedAt DateTime @default(now())
|
||||||
|
jobId String
|
||||||
|
|
||||||
|
id String?
|
||||||
|
firstname String?
|
||||||
|
surname String?
|
||||||
|
contract_id String?
|
||||||
|
contract_start String? // yyyy-MM-dd
|
||||||
|
contract_end String? // yyyy-MM-dd
|
||||||
|
contract_hours_week String? // Float string
|
||||||
|
contract_salary_hour String? // Float string
|
||||||
|
}
|
||||||
|
|
||||||
|
model PatientSource {
|
||||||
|
internalId String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
|
||||||
|
insertedAt DateTime @default(now())
|
||||||
|
jobId String
|
||||||
|
|
||||||
|
id String?
|
||||||
|
surname String?
|
||||||
|
name String?
|
||||||
|
street String?
|
||||||
|
zip String?
|
||||||
|
city String?
|
||||||
|
|
||||||
|
birthday String?
|
||||||
|
kkId String?
|
||||||
|
krankenkasse String?
|
||||||
|
pflegestufe String?
|
||||||
|
behinderung1 String?
|
||||||
|
behinderung2 String?
|
||||||
|
}
|
||||||
|
|
||||||
|
model TourStaging {
|
||||||
|
internalId String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
|
||||||
|
|
||||||
|
id String?
|
||||||
|
done Boolean?
|
||||||
|
direction String?
|
||||||
|
carName String?
|
||||||
|
operationId String?
|
||||||
|
|
||||||
|
driverId String?
|
||||||
|
driverName String?
|
||||||
|
codriverId String?
|
||||||
|
codriverName String?
|
||||||
|
|
||||||
|
patientId String?
|
||||||
|
patientName String?
|
||||||
|
patientSurname String?
|
||||||
|
patientStreet String?
|
||||||
|
patientZip String?
|
||||||
|
patientCity String?
|
||||||
|
healthInsurance String?
|
||||||
|
healthInsuranceNumber String?
|
||||||
|
|
||||||
|
type String?
|
||||||
|
category String?
|
||||||
|
transportType String?
|
||||||
|
ordinanceType String?
|
||||||
|
rangeEndDate DateTime?
|
||||||
|
|
||||||
|
startInstitution String?
|
||||||
|
startStreet String?
|
||||||
|
startZip String?
|
||||||
|
startCity String?
|
||||||
|
targetInstitution String?
|
||||||
|
targetStreet String?
|
||||||
|
targetZip String?
|
||||||
|
targetCity String?
|
||||||
|
|
||||||
|
startDate DateTime?
|
||||||
|
|
||||||
|
check Int @default(0)
|
||||||
|
occupiedKm Float?
|
||||||
|
totalKm Float?
|
||||||
|
|
||||||
|
startBegin String?
|
||||||
|
startEnd String?
|
||||||
|
target String?
|
||||||
|
targetBegin String?
|
||||||
|
empty String?
|
||||||
|
|
||||||
|
hasInfection Boolean @default(false)
|
||||||
|
infectionName String?
|
||||||
|
|
||||||
|
revenueDispoLive Float?
|
||||||
|
billDate DateTime?
|
||||||
|
billNumber String?
|
||||||
|
revenue Float @default(0)
|
||||||
|
revenueDeviation Float @default(0)
|
||||||
|
consumptionCosts Float @default(0)
|
||||||
|
|
||||||
|
createdAt DateTime?
|
||||||
|
}
|
||||||
|
|
||||||
|
model PatientStaging {
|
||||||
|
internalId String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
|
||||||
|
|
||||||
|
id String?
|
||||||
|
surname String?
|
||||||
|
name String?
|
||||||
|
street String?
|
||||||
|
zip String?
|
||||||
|
city String?
|
||||||
|
|
||||||
|
birthday DateTime?
|
||||||
|
healthinsuranceId String?
|
||||||
|
healthinsurance String?
|
||||||
|
careDegree String?
|
||||||
|
disabilityMark1 String?
|
||||||
|
disabilityMark2 String?
|
||||||
|
}
|
||||||
|
|
||||||
|
model AttendanceRegistrationStaging {
|
||||||
|
internalId String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
|
||||||
|
|
||||||
|
attendanceRegistrationId Int
|
||||||
|
employeeId Int
|
||||||
|
dateTime DateTime
|
||||||
|
event String
|
||||||
|
}
|
||||||
|
|
||||||
|
model PlannedTimeStaging {
|
||||||
|
internalId String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
|
||||||
|
|
||||||
|
id Int
|
||||||
|
employeeId Int
|
||||||
|
firstname String?
|
||||||
|
surname String?
|
||||||
|
departmentName String?
|
||||||
|
startDate DateTime
|
||||||
|
endDate DateTime
|
||||||
|
deleted Boolean
|
||||||
|
mark String?
|
||||||
|
}
|
||||||
|
|
||||||
|
model WorkTimeStaging {
|
||||||
|
internalId String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
|
||||||
|
|
||||||
|
id Int
|
||||||
|
employeeId Int
|
||||||
|
firstname String
|
||||||
|
surname String
|
||||||
|
departmentName String?
|
||||||
|
startDate DateTime
|
||||||
|
endDate DateTime
|
||||||
|
}
|
||||||
|
|
||||||
|
model EmployeeStaging {
|
||||||
|
internalId String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
|
||||||
|
|
||||||
|
id Int
|
||||||
|
firstname String
|
||||||
|
surname String
|
||||||
|
contractStart DateTime
|
||||||
|
contractEnd DateTime
|
||||||
|
contractHoursWeek Float
|
||||||
|
contractSalaryHour Float
|
||||||
|
personTransportCertificate DateTime?
|
||||||
|
qualification String?
|
||||||
|
}
|
||||||
|
|
||||||
|
model Tour {
|
||||||
|
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
|
||||||
|
done Boolean
|
||||||
|
direction String
|
||||||
|
carName String?
|
||||||
|
operationId String @unique
|
||||||
|
|
||||||
|
driverId String?
|
||||||
|
driverName String?
|
||||||
|
codriverId String?
|
||||||
|
codriverName String?
|
||||||
|
|
||||||
|
patientId String?
|
||||||
|
patientName String?
|
||||||
|
patientSurname String?
|
||||||
|
patientStreet String?
|
||||||
|
patientZip String?
|
||||||
|
patientCity String?
|
||||||
|
healthInsurance String?
|
||||||
|
healthInsuranceNumber String?
|
||||||
|
|
||||||
|
type String?
|
||||||
|
category String?
|
||||||
|
transportType String?
|
||||||
|
ordinanceType String?
|
||||||
|
rangeEndDate DateTime?
|
||||||
|
|
||||||
|
startInstitution String?
|
||||||
|
startStreet String?
|
||||||
|
startZip String?
|
||||||
|
startCity String?
|
||||||
|
targetInstitution String?
|
||||||
|
targetStreet String?
|
||||||
|
targetZip String?
|
||||||
|
targetCity String?
|
||||||
|
|
||||||
|
startDate DateTime
|
||||||
|
|
||||||
|
check Int @default(0)
|
||||||
|
occupiedKm Float?
|
||||||
|
totalKm Float?
|
||||||
|
|
||||||
|
startBegin String?
|
||||||
|
startEnd String?
|
||||||
|
target String?
|
||||||
|
targetBegin String?
|
||||||
|
empty String?
|
||||||
|
|
||||||
|
hasInfection Boolean @default(false)
|
||||||
|
infectionName String?
|
||||||
|
|
||||||
|
revenueDispoLive Float?
|
||||||
|
billDate DateTime?
|
||||||
|
billNumber String?
|
||||||
|
|
||||||
|
revenue Float @default(0)
|
||||||
|
consumptionCosts Float @default(0)
|
||||||
|
revenueDeviation Float @default(0)
|
||||||
|
|
||||||
|
createdAt DateTime?
|
||||||
|
deletedAt DateTime?
|
||||||
|
Ticket Ticket? @relation(fields: [ticketId], references: [id])
|
||||||
|
ticketId String? @db.Uuid
|
||||||
|
}
|
||||||
|
|
||||||
|
model Patient {
|
||||||
|
id String @id
|
||||||
|
surname String?
|
||||||
|
name String?
|
||||||
|
street String?
|
||||||
|
zip String?
|
||||||
|
city String?
|
||||||
|
|
||||||
|
birthday DateTime?
|
||||||
|
healthinsuranceId String?
|
||||||
|
healthinsurance String?
|
||||||
|
careDegree String?
|
||||||
|
disabilityMark1 String?
|
||||||
|
disabilityMark2 String?
|
||||||
|
}
|
||||||
|
|
||||||
|
enum TicketState {
|
||||||
|
CREATED
|
||||||
|
APPROVAL_REQUIRED
|
||||||
|
READY
|
||||||
|
DONE
|
||||||
|
BILLABLE
|
||||||
|
ARCHIVED
|
||||||
|
}
|
||||||
|
|
||||||
|
enum TicketValidationState {
|
||||||
|
CREATED
|
||||||
|
USER_INTERACTION_REQUIRED
|
||||||
|
TD_UNCERTAIN
|
||||||
|
DOCUMENTS_MISSING
|
||||||
|
ARCHIVED
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ApprovalState {
|
||||||
|
UNKNOWN
|
||||||
|
FREE
|
||||||
|
REQUIRED
|
||||||
|
REQUESTED
|
||||||
|
APPROVED
|
||||||
|
}
|
||||||
|
|
||||||
|
model Ticket {
|
||||||
|
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
|
||||||
|
tours Tour[]
|
||||||
|
state TicketStateHistory[]
|
||||||
|
currentState TicketValidationState
|
||||||
|
notes String[]
|
||||||
|
errors String[]
|
||||||
|
isUrgent Boolean @default(false)
|
||||||
|
urgency DateTime @default("2099-12-31T23:59:59.999Z")
|
||||||
|
approvalState ApprovalState?
|
||||||
|
documentInfo Json? @default("{\"approval\": {\"isNeeded\": null, \"whoRequested\": \"\",\"answeredFrom\": \"\"},\"tdLocation\": \"\"}")
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
}
|
||||||
|
|
||||||
|
model TicketStateHistory {
|
||||||
|
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
|
||||||
|
state TicketValidationState
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
|
||||||
|
Ticket Ticket @relation(fields: [ticketId], references: [id])
|
||||||
|
ticketId String @db.Uuid
|
||||||
|
}
|
||||||
|
|
||||||
|
model AttendanceRegistration {
|
||||||
|
attendanceRegistrationId Int @id
|
||||||
|
employeeId Int
|
||||||
|
personnelNumber String
|
||||||
|
dateTime DateTime
|
||||||
|
event String
|
||||||
|
}
|
||||||
|
|
||||||
|
model WorkTime {
|
||||||
|
id String @id
|
||||||
|
userId String
|
||||||
|
firstname String
|
||||||
|
surname String
|
||||||
|
startDate DateTime?
|
||||||
|
endDate DateTime?
|
||||||
|
department String?
|
||||||
|
pause Int
|
||||||
|
duration Int
|
||||||
|
}
|
||||||
|
|
||||||
|
model PlannedTime {
|
||||||
|
id String @id
|
||||||
|
userId String
|
||||||
|
firstname String?
|
||||||
|
surname String?
|
||||||
|
startDate DateTime?
|
||||||
|
endDate DateTime?
|
||||||
|
department String?
|
||||||
|
pause Int
|
||||||
|
duration Int
|
||||||
|
note String?
|
||||||
|
}
|
||||||
|
|
@ -1,12 +1,27 @@
|
||||||
import { Controller, Get } from '@nestjs/common';
|
import { Controller, Get, Query } from '@nestjs/common';
|
||||||
import { AppService } from './app.service';
|
import { ImporterService } from './modules/feat-importers/importer.service';
|
||||||
|
|
||||||
@Controller()
|
@Controller()
|
||||||
export class AppController {
|
export class AppController {
|
||||||
constructor(private readonly appService: AppService) {}
|
constructor(private readonly importer: ImporterService) {}
|
||||||
|
|
||||||
@Get()
|
@Get('etl')
|
||||||
getHello(): string {
|
etl(@Query('from') from: string, @Query('to') to: string) {
|
||||||
return this.appService.getHello();
|
if (!from || !to) {
|
||||||
|
return {
|
||||||
|
error: 'from and to must be defined',
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
const interval = {
|
||||||
|
start: new Date(from),
|
||||||
|
end: new Date(to),
|
||||||
|
};
|
||||||
|
return this.importer.runImport(interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call Extractors
|
||||||
|
|
||||||
|
// Call Transformers
|
||||||
|
|
||||||
|
// Call Loaders
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,20 @@
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
|
import { EventEmitterModule } from '@nestjs/event-emitter';
|
||||||
|
import { ScheduleModule } from '@nestjs/schedule';
|
||||||
import { AppController } from './app.controller';
|
import { AppController } from './app.controller';
|
||||||
import { AppService } from './app.service';
|
import { AppService } from './app.service';
|
||||||
|
import { PrismaModule } from './core/database/prisma.module';
|
||||||
|
import { FeatureImporterModule } from './modules/feat-importers/feature-importer.module';
|
||||||
|
import { ConfigModule } from './core/config/config.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [],
|
imports: [
|
||||||
|
ConfigModule,
|
||||||
|
PrismaModule.forRoot(),
|
||||||
|
ScheduleModule.forRoot(),
|
||||||
|
EventEmitterModule.forRoot(),
|
||||||
|
FeatureImporterModule,
|
||||||
|
],
|
||||||
controllers: [AppController],
|
controllers: [AppController],
|
||||||
providers: [AppService],
|
providers: [AppService],
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,169 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
// import { TicketService } from '@dks/avicenna/api-feature-ticket-system';
|
||||||
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
|
import { Cron, SchedulerRegistry } from '@nestjs/schedule';
|
||||||
|
import {
|
||||||
|
Interval,
|
||||||
|
addDays,
|
||||||
|
eachDayOfInterval,
|
||||||
|
endOfDay,
|
||||||
|
format,
|
||||||
|
set,
|
||||||
|
startOfDay,
|
||||||
|
} from 'date-fns';
|
||||||
|
import { concatMap, from, lastValueFrom, take, tap } from 'rxjs';
|
||||||
|
import { ImporterService } from './modules/feat-importers/importer.service';
|
||||||
|
import { chunk } from './utils';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AppService {
|
export class AppService {
|
||||||
getHello(): string {
|
private readonly logger = new Logger(AppService.name);
|
||||||
return 'Hello World!';
|
|
||||||
|
@Cron('0 */5 2-20 * * *')
|
||||||
|
async fetchDataContinously() {
|
||||||
|
const now = new Date();
|
||||||
|
const fetchInterval = {
|
||||||
|
start: startOfDay(addDays(now, -14)),
|
||||||
|
end: endOfDay(addDays(now, 7)),
|
||||||
|
};
|
||||||
|
this.importer
|
||||||
|
.runImport(fetchInterval)
|
||||||
|
.pipe(
|
||||||
|
take(1),
|
||||||
|
tap(() =>
|
||||||
|
this.logger.log(
|
||||||
|
`import took ${(Date.now() - now.getTime()) / 1000}s`,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// switchMap(() => from(this.ticketService.createTickets(fetchInterval))),
|
||||||
|
)
|
||||||
|
.subscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Interval(5 * 60 * 1000)
|
||||||
|
async fetchDataInIntervals() {
|
||||||
|
const timeouts = this.schedulerRegistry.getTimeouts();
|
||||||
|
this.logger.log(`Timeouts: ${timeouts.join(', ')}`);
|
||||||
|
if (timeouts.length) return;
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
const fetchInterval: Interval = {
|
||||||
|
start: set(new Date(), {
|
||||||
|
year: now.getFullYear(),
|
||||||
|
month: 0,
|
||||||
|
date: 1,
|
||||||
|
}),
|
||||||
|
end: endOfDay(now),
|
||||||
|
};
|
||||||
|
|
||||||
|
const intervals = [
|
||||||
|
...this.splitIntervalIntoPeriods(fetchInterval, 14),
|
||||||
|
].reverse();
|
||||||
|
|
||||||
|
intervals.forEach((interval, index) => {
|
||||||
|
const readableInterval = `${format(
|
||||||
|
new Date(interval.start),
|
||||||
|
'yyyy-MM-dd',
|
||||||
|
)} - ${format(new Date(interval.end), 'yyyy-MM-dd')}`;
|
||||||
|
const timeoutName = `Year: ${readableInterval}`;
|
||||||
|
const callback = () => {
|
||||||
|
this.logger.log(`Timeout "${timeoutName}"`);
|
||||||
|
from([interval])
|
||||||
|
.pipe(
|
||||||
|
concatMap((interval) => this.importer.runImport(interval)),
|
||||||
|
// switchMap(() => from(this.ticketService.createTickets(interval))),
|
||||||
|
)
|
||||||
|
.subscribe();
|
||||||
|
};
|
||||||
|
this.addTimeout(timeoutName, 60 * 1000 * index + 60 * 1000, callback);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Cron('0 2,7,12,17,22,27,32,37,42,47,52,57 2-20 * * *')
|
||||||
|
async fetchDataSixWeeks() {
|
||||||
|
const now = new Date();
|
||||||
|
const fetchInterval: Interval = {
|
||||||
|
start: startOfDay(addDays(now, -90)),
|
||||||
|
end: endOfDay(now),
|
||||||
|
};
|
||||||
|
|
||||||
|
const intervals = this.splitIntervalIntoPeriods(fetchInterval, 14);
|
||||||
|
|
||||||
|
await lastValueFrom(
|
||||||
|
from(intervals).pipe(
|
||||||
|
concatMap((interval, index) =>
|
||||||
|
this.importer
|
||||||
|
.runImport(interval)
|
||||||
|
.pipe(
|
||||||
|
tap(() =>
|
||||||
|
this.logger.log(
|
||||||
|
`${index + 1}. import three months took ${
|
||||||
|
(Date.now() - now.getTime()) / 1000
|
||||||
|
}s`,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
// await this.ticketService.createTickets(fetchInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Cron('* * 0 * * *')
|
||||||
|
async fetchDataYearly() {
|
||||||
|
const now = new Date();
|
||||||
|
const fetchInterval: Interval = {
|
||||||
|
start: set(new Date(), {
|
||||||
|
year: now.getFullYear(),
|
||||||
|
month: 0,
|
||||||
|
date: 1,
|
||||||
|
}),
|
||||||
|
end: endOfDay(now),
|
||||||
|
};
|
||||||
|
|
||||||
|
const intervals = this.splitIntervalIntoPeriods(fetchInterval);
|
||||||
|
|
||||||
|
from(intervals)
|
||||||
|
.pipe(
|
||||||
|
concatMap((interval, index) =>
|
||||||
|
this.importer
|
||||||
|
.runImport(interval)
|
||||||
|
.pipe(
|
||||||
|
tap(() =>
|
||||||
|
this.logger.log(
|
||||||
|
`${index + 1}. import year took ${
|
||||||
|
(Date.now() - now.getTime()) / 1000
|
||||||
|
}s`,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// switchMap(() => from(this.ticketService.createTickets(fetchInterval))),
|
||||||
|
)
|
||||||
|
.subscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly importer: ImporterService,
|
||||||
|
// private readonly ticketService: TicketService,
|
||||||
|
private readonly schedulerRegistry: SchedulerRegistry,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
private splitIntervalIntoPeriods(
|
||||||
|
interval: Interval,
|
||||||
|
chunkSize = 30,
|
||||||
|
): Interval[] {
|
||||||
|
return chunk(eachDayOfInterval(interval), chunkSize).map((dates) => ({
|
||||||
|
start: startOfDay(dates.at(0) as Date),
|
||||||
|
end: endOfDay(dates.at(-1) as Date),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
private addTimeout(
|
||||||
|
name: string,
|
||||||
|
milliseconds: number,
|
||||||
|
callback: () => void,
|
||||||
|
): void {
|
||||||
|
const timeout = setTimeout(callback, milliseconds);
|
||||||
|
this.schedulerRegistry.addTimeout(name, timeout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { Global, Module } from '@nestjs/common';
|
||||||
|
import { ConfigModule as NestConfigModule } from '@nestjs/config';
|
||||||
|
import { ConfigService } from './config.service';
|
||||||
|
|
||||||
|
@Global()
|
||||||
|
@Module({
|
||||||
|
imports: [NestConfigModule.forRoot()],
|
||||||
|
providers: [ConfigService],
|
||||||
|
exports: [ConfigService],
|
||||||
|
})
|
||||||
|
export class ConfigModule {}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { ConfigService as NestConfigService } from '@nestjs/config';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ConfigService extends NestConfigService {
|
||||||
|
dispoliveBaseUrl(): string {
|
||||||
|
return this.get('DISPO_LIVE_BASE_URL');
|
||||||
|
}
|
||||||
|
dispoliveUsername(): string {
|
||||||
|
return this.get('DISPO_LIVE_USERNAME');
|
||||||
|
}
|
||||||
|
dispolivePassword(): string {
|
||||||
|
return this.get('DISPO_LIVE_PASSWORD');
|
||||||
|
}
|
||||||
|
dyflexisV0BaseUrl(): string {
|
||||||
|
return this.get('DYFLEXIS_V0_BASE_URL');
|
||||||
|
}
|
||||||
|
dyflexisV0ApiKey(): string {
|
||||||
|
return this.get('DYFLEXIS_V0_API_KEY');
|
||||||
|
}
|
||||||
|
dyflexisV2BaseUrl(): string {
|
||||||
|
return this.get('DYFLEXIS_V2_BASE_URL');
|
||||||
|
}
|
||||||
|
dyflexisV2ApiKey(): string {
|
||||||
|
return this.get('DYFLEXIS_V2_API_KEY');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
export const PRISMA_SERVICE_OPTIONS = 'PRISMA_SERVICE_OPTIONS';
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
export interface PaginationInterface<T> {
|
||||||
|
items: T[];
|
||||||
|
meta: {
|
||||||
|
totalItems: number;
|
||||||
|
items: number;
|
||||||
|
totalPages: number;
|
||||||
|
page: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
import { ModuleMetadata, Type } from "@nestjs/common";
|
||||||
|
import { Prisma } from "@prisma/client";
|
||||||
|
|
||||||
|
export interface PrismaModuleOptions {
|
||||||
|
/**
|
||||||
|
* If true, registers `PrismaModule` as a global module.
|
||||||
|
* See: https://docs.nestjs.com/modules#global-modules
|
||||||
|
*/
|
||||||
|
isGlobal?: boolean;
|
||||||
|
prismaServiceOptions?: PrismaServiceOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PrismaServiceOptions {
|
||||||
|
/**
|
||||||
|
* Pass options directly to the `PrismaClient`.
|
||||||
|
* See: https://www.prisma.io/docs/reference/api-reference/prisma-client-reference/#prismaclient
|
||||||
|
*/
|
||||||
|
prismaOptions?: Prisma.PrismaClientOptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If true, `PrismaClient` explicitly creates a connection pool and your first query will respond instantly.
|
||||||
|
*
|
||||||
|
* For most use cases the lazy connect behavior of `PrismaClient` will do. The first query of `PrismaClient` creates the connection pool.
|
||||||
|
* See: https://www.prisma.io/docs/concepts/components/prisma-client/working-with-prismaclient/connection-management
|
||||||
|
*/
|
||||||
|
explicitConnect?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply Prisma middlewares to perform actions before or after db queries.
|
||||||
|
*
|
||||||
|
* See: https://www.prisma.io/docs/concepts/components/prisma-client/middleware
|
||||||
|
*/
|
||||||
|
middlewares?: Array<Prisma.Middleware>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PrismaOptionsFactory {
|
||||||
|
createPrismaOptions(): Promise<PrismaServiceOptions> | PrismaServiceOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PrismaModuleAsyncOptions
|
||||||
|
extends Pick<ModuleMetadata, 'imports'> {
|
||||||
|
isGlobal?: boolean;
|
||||||
|
useExisting?: Type<PrismaOptionsFactory>;
|
||||||
|
useClass?: Type<PrismaOptionsFactory>;
|
||||||
|
useFactory?: (
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
...args: any[]
|
||||||
|
) => Promise<PrismaServiceOptions> | PrismaServiceOptions;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
inject?: any[];
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,100 @@
|
||||||
|
import { DynamicModule, Module, Provider } from '@nestjs/common';
|
||||||
|
import { PRISMA_SERVICE_OPTIONS } from './constants';
|
||||||
|
import {
|
||||||
|
PrismaModuleAsyncOptions,
|
||||||
|
PrismaModuleOptions,
|
||||||
|
PrismaOptionsFactory,
|
||||||
|
} from './prisma-module.options';
|
||||||
|
import { PrismaService } from './prisma.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
providers: [PrismaService],
|
||||||
|
exports: [PrismaService],
|
||||||
|
})
|
||||||
|
export class PrismaModule {
|
||||||
|
static forRoot(options: PrismaModuleOptions = {}): DynamicModule {
|
||||||
|
return {
|
||||||
|
global: options.isGlobal,
|
||||||
|
module: PrismaModule,
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: PRISMA_SERVICE_OPTIONS,
|
||||||
|
useValue: options.prismaServiceOptions,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static forRootAsync(options: PrismaModuleAsyncOptions): DynamicModule {
|
||||||
|
return {
|
||||||
|
global: options.isGlobal,
|
||||||
|
module: PrismaModule,
|
||||||
|
imports: options.imports || [],
|
||||||
|
providers: this.createAsyncProviders(options),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static createAsyncProviders(
|
||||||
|
options: PrismaModuleAsyncOptions,
|
||||||
|
): Provider[] {
|
||||||
|
if (options.useExisting || options.useFactory) {
|
||||||
|
return this.createAsyncOptionsProvider(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.useClass) {
|
||||||
|
return [
|
||||||
|
...this.createAsyncOptionsProvider(options),
|
||||||
|
{
|
||||||
|
provide: options.useClass,
|
||||||
|
useClass: options.useClass,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return [...this.createAsyncOptionsProvider(options)];
|
||||||
|
}
|
||||||
|
|
||||||
|
private static createAsyncOptionsProvider(
|
||||||
|
options: PrismaModuleAsyncOptions,
|
||||||
|
): Provider[] {
|
||||||
|
if (options.useFactory) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
provide: PRISMA_SERVICE_OPTIONS,
|
||||||
|
useFactory: options.useFactory,
|
||||||
|
inject: options.inject || [],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.useExisting) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
provide: PRISMA_SERVICE_OPTIONS,
|
||||||
|
useFactory: async (optionsFactory: PrismaOptionsFactory) =>
|
||||||
|
await optionsFactory.createPrismaOptions(),
|
||||||
|
inject: [options.useExisting],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.useClass) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
provide: PRISMA_SERVICE_OPTIONS,
|
||||||
|
useFactory: async (optionsFactory: PrismaOptionsFactory) =>
|
||||||
|
await optionsFactory.createPrismaOptions(),
|
||||||
|
inject: [options.useClass],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
provide: PRISMA_SERVICE_OPTIONS,
|
||||||
|
useFactory: async (optionsFactory: PrismaOptionsFactory) =>
|
||||||
|
await optionsFactory.createPrismaOptions(),
|
||||||
|
inject: [],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { Test } from '@nestjs/testing';
|
||||||
|
import { PrismaService } from './prisma.service';
|
||||||
|
|
||||||
|
describe('PrismaService', () => {
|
||||||
|
let service: PrismaService;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module = await Test.createTestingModule({
|
||||||
|
providers: [PrismaService],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
service = module.get(PrismaService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
import {
|
||||||
|
Inject,
|
||||||
|
Injectable,
|
||||||
|
Logger,
|
||||||
|
OnModuleInit,
|
||||||
|
Optional,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { PrismaClient } from '@prisma/client';
|
||||||
|
import { PrismaClientOptions } from '@prisma/client/runtime/library';
|
||||||
|
|
||||||
|
import { PRISMA_SERVICE_OPTIONS } from './constants';
|
||||||
|
import { PrismaServiceOptions } from './prisma-module.options';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class PrismaService extends PrismaClient implements OnModuleInit {
|
||||||
|
private readonly logger = new Logger('Prisma');
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Optional()
|
||||||
|
@Inject(PRISMA_SERVICE_OPTIONS)
|
||||||
|
private readonly prismaServiceOptions: PrismaServiceOptions = {},
|
||||||
|
) {
|
||||||
|
super({
|
||||||
|
...prismaServiceOptions.prismaOptions,
|
||||||
|
// Make sure queries are always emitted as events, so that we can log them.
|
||||||
|
log: [
|
||||||
|
{ emit: 'event', level: 'query' },
|
||||||
|
...(prismaServiceOptions.prismaOptions?.log
|
||||||
|
? prismaServiceOptions.prismaOptions.log
|
||||||
|
: (['info', 'warn', 'error'] as PrismaClientOptions['log'])),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.prismaServiceOptions.middlewares) {
|
||||||
|
this.prismaServiceOptions.middlewares.forEach((middleware) =>
|
||||||
|
this.$use(middleware),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Sadly typescript does not recognize the Prisma Client configuration passed in the super call.
|
||||||
|
As Prisma defines the events generically, based on the configuration, the event type, typescript inferred, is wrong.
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
//@ts-ignore
|
||||||
|
this.$on('query', (e: QueryEvent) => {
|
||||||
|
const { timestamp, query, params, duration } = e;
|
||||||
|
const message = `${timestamp.toISOString()} ${query} ${params} ${duration}`;
|
||||||
|
// this.logger.log(message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async onModuleInit() {
|
||||||
|
if (this.prismaServiceOptions.explicitConnect) {
|
||||||
|
await this.$connect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// async enableShutdownHooks(app: INestApplication) {
|
||||||
|
// this.$on('beforeExit', async () => await app.close());
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { HttpModule } from '@nestjs/axios';
|
||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { PrismaModule } from 'src/core/database/prisma.module';
|
||||||
|
import { DispoLiveExtractorsService } from './dispo-live-extractors.service';
|
||||||
|
import { DyflexisExtractorsService } from './dyflexis-extractors.service';
|
||||||
|
import { ExtractorsService } from './extractors.service';
|
||||||
|
import { SourceRepository } from './source.repository';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [PrismaModule, HttpModule],
|
||||||
|
providers: [
|
||||||
|
ExtractorsService,
|
||||||
|
DispoLiveExtractorsService,
|
||||||
|
DyflexisExtractorsService,
|
||||||
|
SourceRepository,
|
||||||
|
],
|
||||||
|
exports: [ExtractorsService],
|
||||||
|
})
|
||||||
|
export class DataAccessExtractorsModule {}
|
||||||
|
|
@ -0,0 +1,481 @@
|
||||||
|
import { HttpService } from '@nestjs/axios';
|
||||||
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
|
import { Prisma } from '@prisma/client';
|
||||||
|
import {
|
||||||
|
Interval,
|
||||||
|
eachDayOfInterval,
|
||||||
|
endOfDay,
|
||||||
|
format,
|
||||||
|
startOfDay,
|
||||||
|
} from 'date-fns';
|
||||||
|
import {
|
||||||
|
Observable,
|
||||||
|
combineLatest,
|
||||||
|
concatMap,
|
||||||
|
delay,
|
||||||
|
forkJoin,
|
||||||
|
from,
|
||||||
|
map,
|
||||||
|
of,
|
||||||
|
reduce,
|
||||||
|
switchMap,
|
||||||
|
} from 'rxjs';
|
||||||
|
import { EmployeeDispoLiveRaw, PatientRaw, TourRaw } from './source.interface';
|
||||||
|
import { SourceRepository } from './source.repository';
|
||||||
|
import { chunk } from 'src/utils';
|
||||||
|
import { ConfigService } from 'src/core/config/config.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class DispoLiveExtractorsService {
|
||||||
|
private readonly logger = new Logger(DispoLiveExtractorsService.name);
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly http: HttpService,
|
||||||
|
private readonly repository: SourceRepository,
|
||||||
|
private readonly config: ConfigService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
extractTours(
|
||||||
|
interval: Interval,
|
||||||
|
jobId: string,
|
||||||
|
): Observable<Prisma.BatchPayload> {
|
||||||
|
const intervals = chunk(eachDayOfInterval(interval), 30).map((dates) => ({
|
||||||
|
start: startOfDay(dates.at(0)),
|
||||||
|
end: endOfDay(dates.at(-1)),
|
||||||
|
}));
|
||||||
|
|
||||||
|
return this.fetchTours(intervals).pipe(
|
||||||
|
map((rawTours) => {
|
||||||
|
return rawTours;
|
||||||
|
}),
|
||||||
|
switchMap((rawTours) =>
|
||||||
|
from(this.repository.createTours(rawTours, jobId)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private fetchTours(intervals: Interval[]): Observable<TourRaw[]> {
|
||||||
|
const archiveEndpoint = 'custom/getArchive/getArchive';
|
||||||
|
const carsEndpoint = 'data/stammdatenFahrzeuge';
|
||||||
|
|
||||||
|
const fetchCars = (
|
||||||
|
authCookies: string,
|
||||||
|
): Observable<{ id: string; name: string }[]> =>
|
||||||
|
this.http
|
||||||
|
.post<{ data: { _id: string; name: string }[] }>(
|
||||||
|
`${this.config.dispoliveBaseUrl()}/${carsEndpoint}`,
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Cookie: authCookies,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.pipe(
|
||||||
|
map((response) =>
|
||||||
|
response.data.data.map(({ _id, name }) => ({ id: _id, name })),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const fetchToursInInterval = (interval: Interval, authCookies: string) =>
|
||||||
|
this.http
|
||||||
|
.post<TourRaw[]>(
|
||||||
|
`${this.config.dispoliveBaseUrl()}/${archiveEndpoint}`,
|
||||||
|
{
|
||||||
|
dateFrom: format(new Date(interval.start), 'yyyy-MM-dd'),
|
||||||
|
dateTo: format(new Date(interval.end), 'yyyy-MM-dd'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Cookie: authCookies,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.pipe(
|
||||||
|
map((response) =>
|
||||||
|
response.data.map((tour) => {
|
||||||
|
const {
|
||||||
|
_id,
|
||||||
|
check,
|
||||||
|
createTime,
|
||||||
|
empfangenVonId,
|
||||||
|
endgen,
|
||||||
|
serienId,
|
||||||
|
serienKategorName,
|
||||||
|
serienKategorie,
|
||||||
|
zadStatus,
|
||||||
|
abrDate,
|
||||||
|
abrNum,
|
||||||
|
abrTime,
|
||||||
|
ausrueckZeit,
|
||||||
|
startTime,
|
||||||
|
startDate,
|
||||||
|
date,
|
||||||
|
date_iso,
|
||||||
|
dauergenehmigung,
|
||||||
|
direction,
|
||||||
|
distanceValue,
|
||||||
|
besetztKm,
|
||||||
|
gefahren,
|
||||||
|
gesamtPreis,
|
||||||
|
infektion,
|
||||||
|
itDesc,
|
||||||
|
itKrz,
|
||||||
|
itName,
|
||||||
|
kkId,
|
||||||
|
krankenkasse,
|
||||||
|
patId,
|
||||||
|
patName,
|
||||||
|
patSurname,
|
||||||
|
patStreet,
|
||||||
|
patZip,
|
||||||
|
patCity,
|
||||||
|
startInstitution,
|
||||||
|
startStreet,
|
||||||
|
startZip,
|
||||||
|
startCity,
|
||||||
|
targetInstitution,
|
||||||
|
targetStreet,
|
||||||
|
targetCity,
|
||||||
|
targetZip,
|
||||||
|
transportKrz,
|
||||||
|
transportName,
|
||||||
|
transportart,
|
||||||
|
verordnungsId,
|
||||||
|
verordnungsName,
|
||||||
|
type,
|
||||||
|
startBegin,
|
||||||
|
startEnd,
|
||||||
|
frei,
|
||||||
|
ziel,
|
||||||
|
zielBegin,
|
||||||
|
endTime,
|
||||||
|
endDate,
|
||||||
|
einsatzNummer,
|
||||||
|
carName,
|
||||||
|
carNo,
|
||||||
|
beifahrer,
|
||||||
|
beifahrerName,
|
||||||
|
fahrer,
|
||||||
|
fahrerName,
|
||||||
|
color,
|
||||||
|
} = tour;
|
||||||
|
return {
|
||||||
|
id: _id ? `${_id}` : undefined,
|
||||||
|
check: check ? `${check}` : undefined,
|
||||||
|
createTime: createTime ? `${createTime}` : undefined,
|
||||||
|
empfangenVonId: empfangenVonId
|
||||||
|
? `${empfangenVonId}`
|
||||||
|
: undefined,
|
||||||
|
endgen: endgen ? `${endgen}` : undefined,
|
||||||
|
serienId: serienId ? `${serienId}` : undefined,
|
||||||
|
serienKategorName: serienKategorName
|
||||||
|
? `${serienKategorName}`
|
||||||
|
: undefined,
|
||||||
|
serienKategorie: serienKategorie
|
||||||
|
? `${serienKategorie}`
|
||||||
|
: undefined,
|
||||||
|
zadStatus: zadStatus ? `${zadStatus}` : undefined,
|
||||||
|
abrDate: abrDate ? `${abrDate}` : undefined,
|
||||||
|
abrNum: abrNum ? `${abrNum}` : undefined,
|
||||||
|
abrTime: abrTime ? `${abrTime}` : undefined,
|
||||||
|
ausrueckZeit: ausrueckZeit ? `${ausrueckZeit}` : undefined,
|
||||||
|
startTime: startTime ? `${startTime}` : undefined,
|
||||||
|
startDate: startDate ? `${startDate}` : undefined,
|
||||||
|
date: date ? `${date}` : undefined,
|
||||||
|
date_iso: date_iso ? `${date_iso}` : undefined,
|
||||||
|
dauergenehmigung: dauergenehmigung
|
||||||
|
? `${dauergenehmigung}`
|
||||||
|
: undefined,
|
||||||
|
direction: direction ? `${direction}` : undefined,
|
||||||
|
distanceValue: distanceValue ? `${distanceValue}` : undefined,
|
||||||
|
besetztKm: besetztKm ? `${besetztKm}` : undefined,
|
||||||
|
gefahren: gefahren ? `${gefahren}` : undefined,
|
||||||
|
gesamtPreis: gesamtPreis ? `${gesamtPreis}` : undefined,
|
||||||
|
infektion: infektion ? `${infektion}` : undefined,
|
||||||
|
itDesc: itDesc ? `${itDesc}` : undefined,
|
||||||
|
itKrz: itKrz ? `${itKrz}` : undefined,
|
||||||
|
itName: itName ? `${itName}` : undefined,
|
||||||
|
kkId: kkId ? `${kkId}` : undefined,
|
||||||
|
krankenkasse: krankenkasse ? `${krankenkasse}` : undefined,
|
||||||
|
patId: patId ? `${patId}` : undefined,
|
||||||
|
patName: patName ? `${patName}` : undefined,
|
||||||
|
patSurname: patSurname ? `${patSurname}` : undefined,
|
||||||
|
patStreet: patStreet ? `${patStreet}` : undefined,
|
||||||
|
patZip: patZip ? `${patZip}` : undefined,
|
||||||
|
patCity: patCity ? `${patCity}` : undefined,
|
||||||
|
startInstitution: startInstitution
|
||||||
|
? `${startInstitution}`
|
||||||
|
: undefined,
|
||||||
|
startStreet: startStreet ? `${startStreet}` : undefined,
|
||||||
|
startZip: startZip ? `${startZip}` : undefined,
|
||||||
|
startCity: startCity ? `${startCity}` : undefined,
|
||||||
|
targetInstitution: targetInstitution
|
||||||
|
? `${targetInstitution}`
|
||||||
|
: undefined,
|
||||||
|
targetStreet: targetStreet ? `${targetStreet}` : undefined,
|
||||||
|
targetCity: targetCity ? `${targetCity}` : undefined,
|
||||||
|
targetZip: targetZip ? `${targetZip}` : undefined,
|
||||||
|
transportKrz: transportKrz ? `${transportKrz}` : undefined,
|
||||||
|
transportName: transportName ? `${transportName}` : undefined,
|
||||||
|
transportart: transportart ? `${transportart}` : undefined,
|
||||||
|
verordnungsId: verordnungsId ? `${verordnungsId}` : undefined,
|
||||||
|
verordnungsName: verordnungsName
|
||||||
|
? `${verordnungsName}`
|
||||||
|
: undefined,
|
||||||
|
type: type ? `${type}` : undefined,
|
||||||
|
startBegin: startBegin ? `${startBegin}` : undefined,
|
||||||
|
startEnd: startEnd ? `${startEnd}` : undefined,
|
||||||
|
frei: frei ? `${frei}` : undefined,
|
||||||
|
ziel: ziel ? `${ziel}` : undefined,
|
||||||
|
zielBegin: zielBegin ? `${zielBegin}` : undefined,
|
||||||
|
endTime: endTime ? `${endTime}` : undefined,
|
||||||
|
endDate: endDate ? `${endDate}` : undefined,
|
||||||
|
einsatzNummer: einsatzNummer ? `${einsatzNummer}` : undefined,
|
||||||
|
carName: carName ? `${carName}` : undefined,
|
||||||
|
carNo: carNo ? `${carNo}` : undefined,
|
||||||
|
beifahrer: beifahrer ? `${beifahrer}` : undefined,
|
||||||
|
beifahrerName: beifahrerName ? `${beifahrerName}` : undefined,
|
||||||
|
fahrer: fahrer ? `${fahrer}` : undefined,
|
||||||
|
fahrerName: fahrerName ? `${fahrerName}` : undefined,
|
||||||
|
color: color ? `${color}` : undefined,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return this.dispoliveLogin(
|
||||||
|
this.config.dispoliveUsername(),
|
||||||
|
this.config.dispolivePassword(),
|
||||||
|
).pipe(
|
||||||
|
switchMap((authCookies) =>
|
||||||
|
forkJoin([of(authCookies), fetchCars(authCookies)]),
|
||||||
|
),
|
||||||
|
switchMap(([authCookies, cars]) =>
|
||||||
|
from(intervals).pipe(
|
||||||
|
delay(300),
|
||||||
|
concatMap((interval) => fetchToursInInterval(interval, authCookies)),
|
||||||
|
reduce((full, tours) => [...full, ...tours], []),
|
||||||
|
map((tours) =>
|
||||||
|
tours.map(({ carNo, ...tour }) => {
|
||||||
|
return {
|
||||||
|
...tour,
|
||||||
|
carNo,
|
||||||
|
carName:
|
||||||
|
cars.find(({ id }) => id === carNo)?.name ?? tour.carName,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
extractEmployees(jobId: string): Observable<Prisma.BatchPayload> {
|
||||||
|
return this.dispoliveLogin(
|
||||||
|
this.config.dispoliveUsername(),
|
||||||
|
this.config.dispolivePassword(),
|
||||||
|
).pipe(
|
||||||
|
switchMap((authCookies) => this.fetchEmployees(authCookies)),
|
||||||
|
switchMap((rawEmployees) =>
|
||||||
|
from(this.repository.createEmployeesDispoLive(rawEmployees, jobId)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private fetchEmployees(
|
||||||
|
authCookies: string,
|
||||||
|
): Observable<EmployeeDispoLiveRaw[]> {
|
||||||
|
const employeeEndpoint = 'data/mitarbeiter';
|
||||||
|
|
||||||
|
return this.http
|
||||||
|
.post<{ data: EmployeeDispoLiveRaw[] }>(
|
||||||
|
`${this.config.dispoliveBaseUrl()}/${employeeEndpoint}`,
|
||||||
|
{ sort: { surname: 1, status_name: -1 } },
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Cookie: authCookies,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.pipe(
|
||||||
|
map((response) =>
|
||||||
|
response.data.data.map((employee) => {
|
||||||
|
const {
|
||||||
|
_id,
|
||||||
|
pScheinAblaufDatum,
|
||||||
|
name,
|
||||||
|
surname,
|
||||||
|
persId,
|
||||||
|
importPersId,
|
||||||
|
qualifikation,
|
||||||
|
} = employee;
|
||||||
|
return {
|
||||||
|
id: _id,
|
||||||
|
pScheinAblaufDatum,
|
||||||
|
name,
|
||||||
|
surname,
|
||||||
|
persId,
|
||||||
|
importPersId,
|
||||||
|
qualifikation,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
extractPatients(jobId: string): Observable<Prisma.BatchPayload> {
|
||||||
|
return this.dispoliveLogin(
|
||||||
|
this.config.dispoliveUsername(),
|
||||||
|
this.config.dispolivePassword(),
|
||||||
|
).pipe(
|
||||||
|
switchMap((authCookies) => this.fetchPatients(authCookies)),
|
||||||
|
switchMap((rawPatients) =>
|
||||||
|
from(this.repository.createPatients(rawPatients, jobId)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private fetchPatients(authCookies: string): Observable<PatientRaw[]> {
|
||||||
|
const patientEndpoint = 'data/patientenStammdaten';
|
||||||
|
const careDegreeEndpoint = 'data/pflegestufe';
|
||||||
|
const disabilityMarkEndpoint = 'data/schwerbehindertenausweis';
|
||||||
|
|
||||||
|
const fetchCareDegrees = (
|
||||||
|
authCookies: string,
|
||||||
|
): Observable<{ id: string; careDegree: string }[]> =>
|
||||||
|
this.http
|
||||||
|
.post<{ data: { _id: string; pflegestufe: string }[] }>(
|
||||||
|
`${this.config.dispoliveBaseUrl()}/${careDegreeEndpoint}`,
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Cookie: authCookies,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.pipe(
|
||||||
|
map((response) =>
|
||||||
|
response.data.data.map(({ _id, pflegestufe }) => ({
|
||||||
|
id: _id,
|
||||||
|
careDegree: pflegestufe,
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const fetchDisabilityMarks = (
|
||||||
|
authCookies: string,
|
||||||
|
): Observable<{ id: string; disabilityMark: string }[]> =>
|
||||||
|
this.http
|
||||||
|
.post<{ data: { _id: string; behinderungShortcut: string }[] }>(
|
||||||
|
`${this.config.dispoliveBaseUrl()}/${disabilityMarkEndpoint}`,
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Cookie: authCookies,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.pipe(
|
||||||
|
map((response) =>
|
||||||
|
response.data.data.map(({ _id, behinderungShortcut }) => ({
|
||||||
|
id: _id,
|
||||||
|
disabilityMark: behinderungShortcut,
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return combineLatest([
|
||||||
|
fetchCareDegrees(authCookies),
|
||||||
|
fetchDisabilityMarks(authCookies),
|
||||||
|
]).pipe(
|
||||||
|
switchMap(([careDegrees, disabilityMarks]) =>
|
||||||
|
this.http
|
||||||
|
.post<{ data: PatientRaw[] }>(
|
||||||
|
`${this.config.dispoliveBaseUrl()}/${patientEndpoint}`,
|
||||||
|
{ sort: { surname: 1, status_name: -1 } },
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Cookie: authCookies,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.pipe(
|
||||||
|
map((response) =>
|
||||||
|
response.data.data.map((patient) => {
|
||||||
|
const {
|
||||||
|
_id,
|
||||||
|
name,
|
||||||
|
surname,
|
||||||
|
street,
|
||||||
|
zip,
|
||||||
|
city,
|
||||||
|
birthday,
|
||||||
|
kkId,
|
||||||
|
krankenkasse,
|
||||||
|
pflegestufe,
|
||||||
|
behinderung1,
|
||||||
|
behinderung2,
|
||||||
|
} = patient;
|
||||||
|
return {
|
||||||
|
id: _id,
|
||||||
|
name,
|
||||||
|
surname,
|
||||||
|
street,
|
||||||
|
zip,
|
||||||
|
city,
|
||||||
|
birthday,
|
||||||
|
kkId,
|
||||||
|
krankenkasse,
|
||||||
|
pflegestufe: careDegrees.find(({ id }) => id === pflegestufe)
|
||||||
|
?.careDegree,
|
||||||
|
behinderung1: disabilityMarks.find(
|
||||||
|
({ id }) => id === behinderung1,
|
||||||
|
)?.disabilityMark,
|
||||||
|
behinderung2: disabilityMarks.find(
|
||||||
|
({ id }) => id === behinderung2,
|
||||||
|
)?.disabilityMark,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private dispoliveLogin(
|
||||||
|
username: string,
|
||||||
|
password: string,
|
||||||
|
): Observable<string> {
|
||||||
|
const endpoint = 'login';
|
||||||
|
|
||||||
|
return this.http
|
||||||
|
.post(
|
||||||
|
`${this.config.dispoliveBaseUrl()}/${endpoint}`,
|
||||||
|
{
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.pipe(
|
||||||
|
map((response) =>
|
||||||
|
response.headers['set-cookie']
|
||||||
|
// To retrieve only the necessary information of the 'set-cookie' header, we combine them with the same separator, which is used inside one 'set-cookie' request (e.g "key=value; path=/; httponly")
|
||||||
|
.join(';')
|
||||||
|
.split(';')
|
||||||
|
.filter((cookie) => cookie.indexOf('koa.sid') > -1)
|
||||||
|
.join('; '),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,343 @@
|
||||||
|
import { HttpService } from '@nestjs/axios';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { Prisma } from '@prisma/client';
|
||||||
|
import {
|
||||||
|
Interval,
|
||||||
|
eachDayOfInterval,
|
||||||
|
eachMonthOfInterval,
|
||||||
|
format,
|
||||||
|
getMonth,
|
||||||
|
getYear,
|
||||||
|
} from 'date-fns';
|
||||||
|
import {
|
||||||
|
EMPTY,
|
||||||
|
Observable,
|
||||||
|
catchError,
|
||||||
|
concatMap,
|
||||||
|
expand,
|
||||||
|
from,
|
||||||
|
map,
|
||||||
|
of,
|
||||||
|
reduce,
|
||||||
|
switchMap,
|
||||||
|
} from 'rxjs';
|
||||||
|
import {
|
||||||
|
AttendanceRegistrationRaw,
|
||||||
|
EmployeeDyflexisRaw,
|
||||||
|
PlannedTimeRaw,
|
||||||
|
WorkTimeRaw,
|
||||||
|
} from './source.interface';
|
||||||
|
import { SourceRepository } from './source.repository';
|
||||||
|
import { ConfigService } from 'src/core/config/config.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class DyflexisExtractorsService {
|
||||||
|
constructor(
|
||||||
|
private readonly http: HttpService,
|
||||||
|
private readonly repository: SourceRepository,
|
||||||
|
private readonly config: ConfigService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
extractEmployees(jobId: string): Observable<Prisma.BatchPayload> {
|
||||||
|
return this.fetchEmployees().pipe(
|
||||||
|
switchMap((rawEmployees) =>
|
||||||
|
from(this.repository.createEmployeesDyflexis(rawEmployees, jobId)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchEmployees(): Observable<EmployeeDyflexisRaw[]> {
|
||||||
|
const endpoint = '/employee';
|
||||||
|
|
||||||
|
const getEmployeesPage = (pageNumber: number) =>
|
||||||
|
this.http
|
||||||
|
.get<{ response: { data: EmployeeDyflexisRaw[] } }>(
|
||||||
|
`${this.config.dyflexisV0BaseUrl()}${endpoint}?page=${pageNumber}`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-Dyflexis-AuthToken': `${this.config.dyflexisV0ApiKey()}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.pipe(
|
||||||
|
map((response) =>
|
||||||
|
response.data.response.data.map((employee) => {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
firstname,
|
||||||
|
surname,
|
||||||
|
contract_id,
|
||||||
|
contract_start,
|
||||||
|
contract_end,
|
||||||
|
contract_hours_week,
|
||||||
|
contract_salary_hour,
|
||||||
|
} = employee;
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
firstname,
|
||||||
|
surname,
|
||||||
|
contract_id,
|
||||||
|
contract_start,
|
||||||
|
contract_end,
|
||||||
|
contract_hours_week,
|
||||||
|
contract_salary_hour,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return new Observable<EmployeeDyflexisRaw[]>((observer) =>
|
||||||
|
getEmployeesPage(0)
|
||||||
|
.pipe(
|
||||||
|
expand((data, i) => (data.length ? getEmployeesPage(i + 1) : EMPTY)),
|
||||||
|
reduce((acc, data) => [...acc, ...data], []),
|
||||||
|
catchError((error) => {
|
||||||
|
observer.error(error);
|
||||||
|
return of(error);
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.subscribe((employees) => {
|
||||||
|
observer.next(employees);
|
||||||
|
observer.complete();
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
extractAttendanceRegistrations(
|
||||||
|
interval: Interval,
|
||||||
|
jobId: string,
|
||||||
|
): Observable<Prisma.BatchPayload> {
|
||||||
|
const daysInInterval = eachDayOfInterval(interval);
|
||||||
|
return from(daysInInterval).pipe(
|
||||||
|
concatMap((day) => this.fetchAttendanceRegistrationsForDay(day)),
|
||||||
|
reduce(
|
||||||
|
(full, attendanceRegistrations) => [
|
||||||
|
...full,
|
||||||
|
...attendanceRegistrations,
|
||||||
|
],
|
||||||
|
[],
|
||||||
|
),
|
||||||
|
switchMap((rawAttendanceRegistrations) =>
|
||||||
|
from(
|
||||||
|
this.repository.createAttendanceRegistrations(
|
||||||
|
rawAttendanceRegistrations,
|
||||||
|
jobId,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchAttendanceRegistrationsForDay(
|
||||||
|
date: Date | string,
|
||||||
|
): Observable<AttendanceRegistrationRaw[]> {
|
||||||
|
const endpoint = '/attendance-registration';
|
||||||
|
|
||||||
|
return this.http
|
||||||
|
.get<{ attendanceRegistration: AttendanceRegistrationRaw[] }>(
|
||||||
|
`${this.config.dyflexisV2BaseUrl()}${endpoint}/${format(
|
||||||
|
new Date(date),
|
||||||
|
'yyyy-MM-dd',
|
||||||
|
)}`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Token ${this.config.dyflexisV2ApiKey()}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.pipe(
|
||||||
|
map((response) =>
|
||||||
|
response.data.attendanceRegistration.map((attendanceRegistration) => {
|
||||||
|
const {
|
||||||
|
attendanceRegistrationId,
|
||||||
|
employeeId,
|
||||||
|
personnelNumber,
|
||||||
|
dateTime,
|
||||||
|
event,
|
||||||
|
} = attendanceRegistration;
|
||||||
|
return {
|
||||||
|
attendanceRegistrationId: attendanceRegistrationId
|
||||||
|
? `${attendanceRegistrationId}`
|
||||||
|
: undefined,
|
||||||
|
employeeId: employeeId ? `${employeeId}` : undefined,
|
||||||
|
personnelNumber: personnelNumber
|
||||||
|
? `${personnelNumber}`
|
||||||
|
: undefined,
|
||||||
|
dateTime: dateTime ? `${dateTime}` : undefined,
|
||||||
|
event: event ? `${event}` : undefined,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
extractWorkTimes(
|
||||||
|
interval: Interval,
|
||||||
|
jobId: string,
|
||||||
|
): Observable<Prisma.BatchPayload> {
|
||||||
|
const monthsInInterval = eachMonthOfInterval(interval);
|
||||||
|
|
||||||
|
return from(monthsInInterval).pipe(
|
||||||
|
concatMap((month) =>
|
||||||
|
this.fetchWorkTimeForMonth(getMonth(month) + 1, getYear(month)),
|
||||||
|
),
|
||||||
|
reduce((full, workTimes) => [...full, ...workTimes], []),
|
||||||
|
switchMap((rawWorkTimes) =>
|
||||||
|
from(this.repository.createWorkTimes(rawWorkTimes, jobId)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private fetchWorkTimeForMonth(
|
||||||
|
month: number,
|
||||||
|
year: number,
|
||||||
|
): Observable<WorkTimeRaw[]> {
|
||||||
|
const endpoint = '/worked';
|
||||||
|
|
||||||
|
const getWorkTimePage = (pageNumber: number) =>
|
||||||
|
this.http
|
||||||
|
.get<{ response: { data: WorkTimeRaw[] } }>(
|
||||||
|
`${this.config.dyflexisV0BaseUrl()}${endpoint}?month=${month}&year=${year}&page=${pageNumber}`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-Dyflexis-AuthToken': `${this.config.dyflexisV0ApiKey()}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.pipe(
|
||||||
|
map((response) =>
|
||||||
|
response.data.response.data.map((workTime) => {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
user_id,
|
||||||
|
firstname,
|
||||||
|
surname,
|
||||||
|
department_id,
|
||||||
|
department_name,
|
||||||
|
start_date,
|
||||||
|
end_date,
|
||||||
|
pauze,
|
||||||
|
duration,
|
||||||
|
} = workTime;
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
user_id,
|
||||||
|
firstname,
|
||||||
|
surname,
|
||||||
|
department_id,
|
||||||
|
department_name,
|
||||||
|
start_date,
|
||||||
|
end_date,
|
||||||
|
pauze,
|
||||||
|
duration,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return new Observable<WorkTimeRaw[]>((observer) =>
|
||||||
|
getWorkTimePage(0)
|
||||||
|
.pipe(
|
||||||
|
expand((data, i) => (data.length ? getWorkTimePage(i + 1) : EMPTY)),
|
||||||
|
reduce((acc, data) => [...acc, ...data], []),
|
||||||
|
catchError((error) => {
|
||||||
|
observer.error(error);
|
||||||
|
return of(error);
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.subscribe((workTimes) => {
|
||||||
|
observer.next(workTimes);
|
||||||
|
observer.complete();
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
extractPlannedTimes(
|
||||||
|
interval: Interval,
|
||||||
|
jobId: string,
|
||||||
|
): Observable<Prisma.BatchPayload> {
|
||||||
|
const monthsInInterval = eachMonthOfInterval(interval);
|
||||||
|
|
||||||
|
return from(monthsInInterval).pipe(
|
||||||
|
concatMap((month) =>
|
||||||
|
this.fetchPlannedTimeForMonth(getMonth(month) + 1, getYear(month)),
|
||||||
|
),
|
||||||
|
reduce((full, plannedTimes) => [...full, ...plannedTimes], []),
|
||||||
|
switchMap((rawPlannedTimes) =>
|
||||||
|
from(this.repository.createPlannedTimes(rawPlannedTimes, jobId)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private fetchPlannedTimeForMonth(
|
||||||
|
month: number,
|
||||||
|
year: number,
|
||||||
|
): Observable<PlannedTimeRaw[]> {
|
||||||
|
const endpoint = '/planned';
|
||||||
|
|
||||||
|
const getPlannedTimePage = (pageNumber: number) =>
|
||||||
|
this.http
|
||||||
|
.get<{ response: { data: PlannedTimeRaw[] } }>(
|
||||||
|
`${this.config.dyflexisV0BaseUrl()}${endpoint}?month=${month}&year=${year}&page=${pageNumber}`,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-Dyflexis-AuthToken': `${this.config.dyflexisV0ApiKey()}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.pipe(
|
||||||
|
map((response) =>
|
||||||
|
response.data.response.data.map((plannedTime) => {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
user_id,
|
||||||
|
firstname,
|
||||||
|
surname,
|
||||||
|
department_id,
|
||||||
|
department_name,
|
||||||
|
start_date,
|
||||||
|
end_date,
|
||||||
|
pauze,
|
||||||
|
duration,
|
||||||
|
deleted,
|
||||||
|
mark,
|
||||||
|
} = plannedTime;
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
user_id,
|
||||||
|
firstname,
|
||||||
|
surname,
|
||||||
|
department_id,
|
||||||
|
department_name,
|
||||||
|
start_date,
|
||||||
|
end_date,
|
||||||
|
pauze,
|
||||||
|
duration,
|
||||||
|
deleted,
|
||||||
|
mark,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return new Observable<PlannedTimeRaw[]>((observer) =>
|
||||||
|
getPlannedTimePage(0)
|
||||||
|
.pipe(
|
||||||
|
expand((data, i) =>
|
||||||
|
data.length ? getPlannedTimePage(i + 1) : EMPTY,
|
||||||
|
),
|
||||||
|
reduce((acc, data) => [...acc, ...data], []),
|
||||||
|
catchError((error) => {
|
||||||
|
observer.error(error);
|
||||||
|
return of(error);
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.subscribe((plannedTimes) => {
|
||||||
|
observer.next(plannedTimes);
|
||||||
|
observer.complete();
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,141 @@
|
||||||
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
|
import {
|
||||||
|
AttendanceRegistrationSource,
|
||||||
|
EmployeeDispoLiveSource,
|
||||||
|
EmployeeDyflexisSource,
|
||||||
|
PatientSource,
|
||||||
|
PlannedTimeSource,
|
||||||
|
Prisma,
|
||||||
|
TourSource,
|
||||||
|
WorkTimeSource,
|
||||||
|
} from '@prisma/client';
|
||||||
|
import { Interval } from 'date-fns';
|
||||||
|
import { Observable, catchError, combineLatest, map } from 'rxjs';
|
||||||
|
import { DispoLiveExtractorsService } from './dispo-live-extractors.service';
|
||||||
|
import { DyflexisExtractorsService } from './dyflexis-extractors.service';
|
||||||
|
import { SourceRepository } from './source.repository';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ExtractorsService {
|
||||||
|
private logger = new Logger(ExtractorsService.name);
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly dispoLiveExtractors: DispoLiveExtractorsService,
|
||||||
|
private readonly dyflexisExtractors: DyflexisExtractorsService,
|
||||||
|
private readonly repository: SourceRepository,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
clearSource(): Promise<Prisma.BatchPayload[]> {
|
||||||
|
return this.repository.clearSource();
|
||||||
|
}
|
||||||
|
|
||||||
|
extractTours(
|
||||||
|
interval: Interval,
|
||||||
|
jobId: string,
|
||||||
|
): Observable<Prisma.BatchPayload> {
|
||||||
|
return this.dispoLiveExtractors.extractTours(interval, jobId).pipe(
|
||||||
|
catchError((error) => {
|
||||||
|
this.logger.error(error);
|
||||||
|
throw error;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
extractEmployees(jobId: string): Observable<Prisma.BatchPayload> {
|
||||||
|
return combineLatest([
|
||||||
|
this.dispoLiveExtractors.extractEmployees(jobId),
|
||||||
|
this.dyflexisExtractors.extractEmployees(jobId),
|
||||||
|
]).pipe(
|
||||||
|
map(([{ count: countDispoLive }, { count: countDyflexis }]) => {
|
||||||
|
return {
|
||||||
|
count: countDispoLive + countDyflexis,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
catchError((error) => {
|
||||||
|
this.logger.error(error);
|
||||||
|
throw error;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
extractPatients(jobId: string): Observable<Prisma.BatchPayload> {
|
||||||
|
return this.dispoLiveExtractors.extractPatients(jobId).pipe(
|
||||||
|
catchError((error) => {
|
||||||
|
this.logger.error(error);
|
||||||
|
throw error;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
extractPlannedTimes(
|
||||||
|
interval: Interval,
|
||||||
|
jobId: string,
|
||||||
|
): Observable<Prisma.BatchPayload> {
|
||||||
|
return this.dyflexisExtractors.extractPlannedTimes(interval, jobId).pipe(
|
||||||
|
catchError((error) => {
|
||||||
|
this.logger.error(error);
|
||||||
|
throw error;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
extractWorkTimes(
|
||||||
|
interval: Interval,
|
||||||
|
jobId: string,
|
||||||
|
): Observable<Prisma.BatchPayload> {
|
||||||
|
return this.dyflexisExtractors.extractWorkTimes(interval, jobId).pipe(
|
||||||
|
catchError((error) => {
|
||||||
|
this.logger.error(error);
|
||||||
|
throw error;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
extractAttendanceRegistrations(
|
||||||
|
interval: Interval,
|
||||||
|
jobId: string,
|
||||||
|
): Observable<Prisma.BatchPayload> {
|
||||||
|
return this.dyflexisExtractors
|
||||||
|
.extractAttendanceRegistrations(interval, jobId)
|
||||||
|
.pipe(
|
||||||
|
catchError((error) => {
|
||||||
|
this.logger.error(error);
|
||||||
|
throw error;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
findToursByJobId(jobId: string): Promise<TourSource[]> {
|
||||||
|
return this.repository.findToursByJobId(jobId);
|
||||||
|
}
|
||||||
|
|
||||||
|
findPatientsByJobId(jobId: string): Promise<PatientSource[]> {
|
||||||
|
return this.repository.findPatientsByJobId(jobId);
|
||||||
|
}
|
||||||
|
|
||||||
|
findPlannedTimesByJobId(jobId: string): Promise<PlannedTimeSource[]> {
|
||||||
|
return this.repository.findPlannedTimesByJobId(jobId);
|
||||||
|
}
|
||||||
|
|
||||||
|
findWorkTimesByJobId(jobId: string): Promise<WorkTimeSource[]> {
|
||||||
|
return this.repository.findWorkTimesByJobId(jobId);
|
||||||
|
}
|
||||||
|
|
||||||
|
findAttendanceRegistrationsByJobId(
|
||||||
|
jobId: string,
|
||||||
|
): Promise<AttendanceRegistrationSource[]> {
|
||||||
|
return this.repository.findAttendanceRegistrationsByJobId(jobId);
|
||||||
|
}
|
||||||
|
|
||||||
|
findEmployeesDispoLiveByJobId(
|
||||||
|
jobId: string,
|
||||||
|
): Promise<EmployeeDispoLiveSource[]> {
|
||||||
|
return this.repository.findEmployeesDispoLiveByJobId(jobId);
|
||||||
|
}
|
||||||
|
|
||||||
|
findEmployeesDyflexisByJobId(
|
||||||
|
jobId: string,
|
||||||
|
): Promise<EmployeeDyflexisSource[]> {
|
||||||
|
return this.repository.findEmployeesDyflexisByJobId(jobId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,142 @@
|
||||||
|
export interface TourRaw {
|
||||||
|
_id?: string;
|
||||||
|
check?: string;
|
||||||
|
createTime?: string; // yyyy-MM-dd HH:mm:ss 'Europe/Berlin' OR yyyy-MM-dd 'Europe/Berlin'
|
||||||
|
empfangenVonId?: string;
|
||||||
|
endgen?: string; // yyyy-MM-dd
|
||||||
|
serienId?: string;
|
||||||
|
serienKategorName?: string;
|
||||||
|
serienKategorie?: string;
|
||||||
|
zadStatus?: string;
|
||||||
|
abrDate?: string; // yyyy-MM-dd
|
||||||
|
abrNum?: string;
|
||||||
|
abrTime?: string; // HH:mm
|
||||||
|
|
||||||
|
ausrueckZeit?: string; // HH:mm 'Europe/Berlin'
|
||||||
|
startTime?: string; // HH:mm 'Europe/Berlin' (is after ausrueckZeit)
|
||||||
|
startDate?: string; // yyyy-MM-dd HH:mm:ss 'Europe/Berlin'
|
||||||
|
date?: string; // yyyy-MM-dd
|
||||||
|
date_iso?: string; // ISO 8601 String
|
||||||
|
dauergenehmigung?: string;
|
||||||
|
direction?: string;
|
||||||
|
distanceValue?: string; // meters (probably int)
|
||||||
|
besetztKm?: string; // kilometers (float)
|
||||||
|
gefahren?: string; // Boolean as number
|
||||||
|
gesamtPreis?: string; // float
|
||||||
|
infektion?: string; // Boolean as number
|
||||||
|
itDesc?: string;
|
||||||
|
itKrz?: string;
|
||||||
|
itName?: string;
|
||||||
|
kkId?: string;
|
||||||
|
krankenkasse?: string;
|
||||||
|
patId?: string;
|
||||||
|
patName?: string;
|
||||||
|
patSurname?: string;
|
||||||
|
patStreet?: string;
|
||||||
|
patZip?: string;
|
||||||
|
patCity?: string;
|
||||||
|
startInstitution?: string;
|
||||||
|
startStreet?: string;
|
||||||
|
startZip?: string;
|
||||||
|
startCity?: string;
|
||||||
|
targetInstitution?: string;
|
||||||
|
targetStreet?: string;
|
||||||
|
targetZip?: string;
|
||||||
|
targetCity?: string;
|
||||||
|
transportKrz?: string;
|
||||||
|
transportName?: string;
|
||||||
|
transportart?: string; // ID
|
||||||
|
verordnungsId?: string;
|
||||||
|
verordnungsName?: string;
|
||||||
|
type?: string;
|
||||||
|
|
||||||
|
startBegin?: string; // HH:mm 'Europe/Berlin'
|
||||||
|
startEnd?: string; // HH:mm 'Europe/Berlin'
|
||||||
|
frei?: string; // HH:mm 'Europe/Berlin'
|
||||||
|
ziel?: string; // HH:mm 'Europe/Berlin'
|
||||||
|
zielBegin?: string; // HH:mm 'Europe/Berlin'
|
||||||
|
endTime?: string; // HH:mm 'Europe/Berlin'
|
||||||
|
endDate?: string; // yyyy-MM-dd HH:mm:ss 'Europe/Berlin'
|
||||||
|
|
||||||
|
einsatzNummer?: string;
|
||||||
|
carName?: string;
|
||||||
|
carNo?: string; // ID
|
||||||
|
beifahrerName?: string;
|
||||||
|
beifahrer?: string; // ID
|
||||||
|
fahrerName?: string;
|
||||||
|
fahrer?: string; // ID
|
||||||
|
|
||||||
|
color?: string; // Hex rgb
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EmployeeDispoLiveRaw {
|
||||||
|
_id?: string;
|
||||||
|
pScheinAblaufDatum?: string; // yyyy-MM-dd
|
||||||
|
name?: string;
|
||||||
|
surname?: string;
|
||||||
|
persId?: string;
|
||||||
|
importPersId?: string; // Personal ID aus Dyflexis
|
||||||
|
qualifikation?: string; // ID
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PatientRaw {
|
||||||
|
_id?: string;
|
||||||
|
birthday?: string; // yyyy-MM-dd
|
||||||
|
name?: string;
|
||||||
|
surname?: string;
|
||||||
|
street?: string;
|
||||||
|
zip?: string;
|
||||||
|
city?: string;
|
||||||
|
kkId?: string;
|
||||||
|
krankenkasse?: string;
|
||||||
|
pflegestufe?: string;
|
||||||
|
behinderung1?: string;
|
||||||
|
behinderung2?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AttendanceRegistrationRaw {
|
||||||
|
attendanceRegistrationId?: string;
|
||||||
|
employeeId?: string;
|
||||||
|
personnelNumber?: string;
|
||||||
|
dateTime?: string; // ISO 8601 String with timezone
|
||||||
|
event?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PlannedTimeRaw {
|
||||||
|
id?: string;
|
||||||
|
user_id?: string;
|
||||||
|
firstname?: string;
|
||||||
|
surname?: string;
|
||||||
|
department_id?: string;
|
||||||
|
department_name?: string;
|
||||||
|
start_date?: string; // yyyy-MM-dd HH:mm:ss 'Europe/Berlin'
|
||||||
|
end_date?: string; // yyyy-MM-dd HH:mm:ss 'Europe/Berlin'
|
||||||
|
pauze?: string;
|
||||||
|
duration?: string;
|
||||||
|
deleted?: string;
|
||||||
|
mark?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WorkTimeRaw {
|
||||||
|
id?: string;
|
||||||
|
user_id?: string;
|
||||||
|
firstname?: string;
|
||||||
|
surname?: string;
|
||||||
|
department_id?: string;
|
||||||
|
department_name?: string;
|
||||||
|
start_date?: string; // yyyy-MM-dd HH:mm:ss 'Europe/Berlin'
|
||||||
|
end_date?: string; // yyyy-MM-dd HH:mm:ss 'Europe/Berlin'
|
||||||
|
pauze?: string;
|
||||||
|
duration?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EmployeeDyflexisRaw {
|
||||||
|
id?: string;
|
||||||
|
firstname?: string;
|
||||||
|
surname?: string;
|
||||||
|
contract_id?: string;
|
||||||
|
contract_start?: string; // yyyy-MM-dd
|
||||||
|
contract_end?: string; // yyyy-MM-dd
|
||||||
|
contract_hours_week?: string; // Float string
|
||||||
|
contract_salary_hour?: string; // Float string
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,163 @@
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import {
|
||||||
|
AttendanceRegistrationSource,
|
||||||
|
EmployeeDispoLiveSource,
|
||||||
|
EmployeeDyflexisSource,
|
||||||
|
PatientSource,
|
||||||
|
PlannedTimeSource,
|
||||||
|
Prisma,
|
||||||
|
TourSource,
|
||||||
|
WorkTimeSource,
|
||||||
|
} from '@prisma/client';
|
||||||
|
import { PrismaService } from 'src/core/database/prisma.service';
|
||||||
|
import {
|
||||||
|
AttendanceRegistrationRaw,
|
||||||
|
EmployeeDispoLiveRaw,
|
||||||
|
EmployeeDyflexisRaw,
|
||||||
|
PatientRaw,
|
||||||
|
PlannedTimeRaw,
|
||||||
|
TourRaw,
|
||||||
|
WorkTimeRaw,
|
||||||
|
} from './source.interface';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class SourceRepository {
|
||||||
|
constructor(private readonly prisma: PrismaService) {}
|
||||||
|
|
||||||
|
clearSource(): Promise<Prisma.BatchPayload[]> {
|
||||||
|
return Promise.all([
|
||||||
|
this.prisma.tourSource.deleteMany(),
|
||||||
|
this.prisma.employeeDispoLiveSource.deleteMany(),
|
||||||
|
this.prisma.employeeDyflexisSource.deleteMany(),
|
||||||
|
this.prisma.workTimeSource.deleteMany(),
|
||||||
|
this.prisma.plannedTimeSource.deleteMany(),
|
||||||
|
this.prisma.attendanceRegistrationSource.deleteMany(),
|
||||||
|
this.prisma.patientSource.deleteMany(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
createTours(
|
||||||
|
tours: TourRaw[],
|
||||||
|
jobId: string,
|
||||||
|
): Prisma.PrismaPromise<Prisma.BatchPayload> {
|
||||||
|
return this.prisma.tourSource.createMany({
|
||||||
|
data: tours.map((data) => ({ jobId, ...data })),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
createEmployeesDispoLive(
|
||||||
|
employees: EmployeeDispoLiveRaw[],
|
||||||
|
jobId: string,
|
||||||
|
): Prisma.PrismaPromise<Prisma.BatchPayload> {
|
||||||
|
return this.prisma.employeeDispoLiveSource.createMany({
|
||||||
|
data: employees.map((data) => ({ jobId, ...data })),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
createPatients(
|
||||||
|
patients: PatientRaw[],
|
||||||
|
jobId: string,
|
||||||
|
): Promise<Prisma.BatchPayload> {
|
||||||
|
return this.prisma.patientSource.createMany({
|
||||||
|
data: patients.map((data) => ({ jobId, ...data })),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
createEmployeesDyflexis(
|
||||||
|
employees: EmployeeDyflexisRaw[],
|
||||||
|
jobId: string,
|
||||||
|
): Prisma.PrismaPromise<Prisma.BatchPayload> {
|
||||||
|
return this.prisma.employeeDyflexisSource.createMany({
|
||||||
|
data: employees.map((data) => ({ jobId, ...data })),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
createAttendanceRegistrations(
|
||||||
|
attendanceRegistrations: AttendanceRegistrationRaw[],
|
||||||
|
jobId: string,
|
||||||
|
): Prisma.PrismaPromise<Prisma.BatchPayload> {
|
||||||
|
return this.prisma.attendanceRegistrationSource.createMany({
|
||||||
|
data: attendanceRegistrations.map((data) => ({ jobId, ...data })),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
createWorkTimes(
|
||||||
|
workTimes: WorkTimeRaw[],
|
||||||
|
jobId: string,
|
||||||
|
): Prisma.PrismaPromise<Prisma.BatchPayload> {
|
||||||
|
return this.prisma.workTimeSource.createMany({
|
||||||
|
data: workTimes.map((data) => ({ jobId, ...data })),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
createPlannedTimes(
|
||||||
|
plannedTimes: PlannedTimeRaw[],
|
||||||
|
jobId: string,
|
||||||
|
): Prisma.PrismaPromise<Prisma.BatchPayload> {
|
||||||
|
return this.prisma.plannedTimeSource.createMany({
|
||||||
|
data: plannedTimes.map((data) => ({ jobId, ...data })),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
findToursByJobId(jobId: string): Promise<TourSource[]> {
|
||||||
|
return this.prisma.tourSource.findMany({
|
||||||
|
where: {
|
||||||
|
jobId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
findPatientsByJobId(jobId: string): Promise<PatientSource[]> {
|
||||||
|
return this.prisma.patientSource.findMany({
|
||||||
|
where: {
|
||||||
|
jobId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
findPlannedTimesByJobId(jobId: string): Promise<PlannedTimeSource[]> {
|
||||||
|
return this.prisma.plannedTimeSource.findMany({
|
||||||
|
where: {
|
||||||
|
jobId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
findWorkTimesByJobId(jobId: string): Promise<WorkTimeSource[]> {
|
||||||
|
return this.prisma.workTimeSource.findMany({
|
||||||
|
where: {
|
||||||
|
jobId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
findAttendanceRegistrationsByJobId(
|
||||||
|
jobId: string,
|
||||||
|
): Promise<AttendanceRegistrationSource[]> {
|
||||||
|
return this.prisma.attendanceRegistrationSource.findMany({
|
||||||
|
where: {
|
||||||
|
jobId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
findEmployeesDyflexisByJobId(
|
||||||
|
jobId: string,
|
||||||
|
): Promise<EmployeeDyflexisSource[]> {
|
||||||
|
return this.prisma.employeeDyflexisSource.findMany({
|
||||||
|
where: {
|
||||||
|
jobId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
findEmployeesDispoLiveByJobId(
|
||||||
|
jobId: string,
|
||||||
|
): Promise<EmployeeDispoLiveSource[]> {
|
||||||
|
return this.prisma.employeeDispoLiveSource.findMany({
|
||||||
|
where: {
|
||||||
|
jobId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
export enum EventTypes {
|
||||||
|
ExtractionDone = 'etl.extraction',
|
||||||
|
TransformationDone = 'etl.transformation',
|
||||||
|
LoadingDone = 'etl.loading',
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
export class ExtractionDoneEvent {
|
||||||
|
constructor(public readonly jobId: string) {}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
export class LoadingDoneEvent {
|
||||||
|
constructor(public readonly jobId: string) {}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
export class TransformationDoneEvent {
|
||||||
|
constructor(public readonly jobId: string) {}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { DataAccessExtractorsModule } from '../feat-extractors/data-access-extractors.module';
|
||||||
|
import { DataAccessLoadersModule } from '../feat-loaders/data-access-loaders.module';
|
||||||
|
import { DataAccessTransformersModule } from '../feat-transformers/data-access-transformers.module';
|
||||||
|
import { ImporterService } from './importer.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [
|
||||||
|
DataAccessExtractorsModule,
|
||||||
|
DataAccessTransformersModule,
|
||||||
|
DataAccessLoadersModule,
|
||||||
|
],
|
||||||
|
providers: [ImporterService],
|
||||||
|
exports: [ImporterService],
|
||||||
|
})
|
||||||
|
export class FeatureImporterModule {}
|
||||||
|
|
@ -0,0 +1,113 @@
|
||||||
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
|
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||||
|
import { randomUUID } from 'crypto';
|
||||||
|
import { Interval } from 'date-fns';
|
||||||
|
import { Observable, combineLatest, from, map, switchMap, tap } from 'rxjs';
|
||||||
|
|
||||||
|
import { ExtractorsService } from '../feat-extractors/extractors.service';
|
||||||
|
import { LoadersService } from '../feat-loaders/loaders.service';
|
||||||
|
import { TransformersService } from '../feat-transformers/transformers.service';
|
||||||
|
import { EventTypes } from './events/event-types.enum';
|
||||||
|
import { ExtractionDoneEvent } from './events/extraction-done.event';
|
||||||
|
import { LoadingDoneEvent } from './events/loading-done.event';
|
||||||
|
import { TransformationDoneEvent } from './events/transformation-done.event';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ImporterService {
|
||||||
|
private logger = new Logger(ImporterService.name);
|
||||||
|
constructor(
|
||||||
|
private readonly extractorsService: ExtractorsService,
|
||||||
|
private readonly transformersService: TransformersService,
|
||||||
|
private readonly loadersService: LoadersService,
|
||||||
|
private readonly eventEmitter: EventEmitter2,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
runImport(interval: Interval) {
|
||||||
|
const startTS = new Date().getTime();
|
||||||
|
const jobId = randomUUID();
|
||||||
|
return from(this.extractorsService.clearSource()).pipe(
|
||||||
|
switchMap(() => this.runExtractors(interval, jobId)),
|
||||||
|
tap(() =>
|
||||||
|
this.logger.log(
|
||||||
|
`Extraction of ${jobId} took ${new Date().getTime() - startTS}ms`,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
switchMap(() =>
|
||||||
|
from(this.transformersService.clearStaging()).pipe(
|
||||||
|
switchMap(() => this.runTransformers(jobId)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
tap(() =>
|
||||||
|
this.logger.log(
|
||||||
|
`Transformation of ${jobId} took ${new Date().getTime() - startTS}ms`,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
switchMap(() => this.runLoaders(jobId)),
|
||||||
|
tap(() =>
|
||||||
|
this.logger.log(
|
||||||
|
`Loading of ${jobId} took ${new Date().getTime() - startTS}ms`,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private runExtractors(interval: Interval, jobId: string): Observable<void> {
|
||||||
|
return combineLatest([
|
||||||
|
this.extractorsService.extractAttendanceRegistrations(interval, jobId),
|
||||||
|
this.extractorsService.extractEmployees(jobId),
|
||||||
|
this.extractorsService.extractPatients(jobId),
|
||||||
|
this.extractorsService.extractPlannedTimes(interval, jobId),
|
||||||
|
this.extractorsService.extractTours(interval, jobId),
|
||||||
|
this.extractorsService.extractWorkTimes(interval, jobId),
|
||||||
|
]).pipe(
|
||||||
|
tap(() =>
|
||||||
|
this.eventEmitter.emit(
|
||||||
|
EventTypes.ExtractionDone,
|
||||||
|
new ExtractionDoneEvent(jobId),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
map(() => {
|
||||||
|
return;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private runTransformers(jobId: string): Observable<void> {
|
||||||
|
return combineLatest([
|
||||||
|
this.transformersService.transformAttendanceRegistrations(jobId),
|
||||||
|
this.transformersService.transformEmployees(jobId),
|
||||||
|
this.transformersService.transformPatients(jobId),
|
||||||
|
this.transformersService.transformPlannedTimes(jobId),
|
||||||
|
this.transformersService.transformWorkTimes(jobId),
|
||||||
|
this.transformersService.transformTours(jobId),
|
||||||
|
]).pipe(
|
||||||
|
tap(() =>
|
||||||
|
this.eventEmitter.emit(
|
||||||
|
EventTypes.TransformationDone,
|
||||||
|
new TransformationDoneEvent(jobId),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
map(() => {
|
||||||
|
return;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private runLoaders(jobId: string): Observable<void> {
|
||||||
|
return this.loadersService.loadTours().pipe(
|
||||||
|
switchMap(() => this.loadersService.loadPatients()),
|
||||||
|
switchMap(() => this.loadersService.loadAttendanceRegistrations()),
|
||||||
|
switchMap(() => this.loadersService.loadPlannedTimes()),
|
||||||
|
switchMap(() => this.loadersService.loadWorkTimes()),
|
||||||
|
tap(() =>
|
||||||
|
this.eventEmitter.emit(
|
||||||
|
EventTypes.LoadingDone,
|
||||||
|
new LoadingDoneEvent(jobId),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
map(() => {
|
||||||
|
return;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { PrismaModule } from 'src/core/database/prisma.module';
|
||||||
|
import { DataAccessTransformersModule } from '../feat-transformers/data-access-transformers.module';
|
||||||
|
import { FinalRepository } from './final.repository';
|
||||||
|
import { LoadersService } from './loaders.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [PrismaModule, DataAccessTransformersModule],
|
||||||
|
providers: [LoadersService, FinalRepository],
|
||||||
|
exports: [LoadersService],
|
||||||
|
})
|
||||||
|
export class DataAccessLoadersModule {}
|
||||||
|
|
@ -0,0 +1,278 @@
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import {
|
||||||
|
AttendanceRegistration,
|
||||||
|
Patient,
|
||||||
|
PlannedTime,
|
||||||
|
Prisma,
|
||||||
|
Tour,
|
||||||
|
WorkTime,
|
||||||
|
} from '@prisma/client';
|
||||||
|
import { Interval } from 'date-fns';
|
||||||
|
|
||||||
|
import { PrismaService } from 'src/core/database/prisma.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class FinalRepository {
|
||||||
|
constructor(private readonly prisma: PrismaService) {}
|
||||||
|
|
||||||
|
findToursInInterval(
|
||||||
|
interval: Interval,
|
||||||
|
operationIds: string[],
|
||||||
|
): Promise<Tour[]> {
|
||||||
|
return this.prisma.tour.findMany({
|
||||||
|
where: {
|
||||||
|
OR: [
|
||||||
|
{
|
||||||
|
AND: [
|
||||||
|
{
|
||||||
|
startDate: {
|
||||||
|
gte: new Date(interval.start),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
startDate: {
|
||||||
|
lte: new Date(interval.end),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
operationId: {
|
||||||
|
in: operationIds,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
createTours(
|
||||||
|
tourCreateInput: Prisma.TourCreateInput[],
|
||||||
|
): Promise<Prisma.BatchPayload> {
|
||||||
|
return this.prisma.tour.createMany({
|
||||||
|
data: tourCreateInput,
|
||||||
|
skipDuplicates: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateTours(
|
||||||
|
tourCreateInput: Prisma.TourCreateInput[],
|
||||||
|
): Promise<Prisma.BatchPayload> {
|
||||||
|
await this.prisma.tour.deleteMany({
|
||||||
|
where: {
|
||||||
|
operationId: {
|
||||||
|
in: tourCreateInput.map(({ operationId }) => operationId),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return this.createTours(tourCreateInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteTours(operationIds: string[]): Promise<Prisma.BatchPayload> {
|
||||||
|
return this.prisma.tour.updateMany({
|
||||||
|
where: {
|
||||||
|
operationId: {
|
||||||
|
in: operationIds,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
deletedAt: new Date(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
findPatients(): Promise<Patient[]> {
|
||||||
|
return this.prisma.patient.findMany({
|
||||||
|
orderBy: {
|
||||||
|
surname: 'asc',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
createPatients(
|
||||||
|
patientCreateInput: Prisma.PatientCreateInput[],
|
||||||
|
): Promise<Prisma.BatchPayload> {
|
||||||
|
return this.prisma.patient.createMany({
|
||||||
|
data: patientCreateInput,
|
||||||
|
skipDuplicates: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async updatePatients(
|
||||||
|
patientCreateInput: Prisma.PatientCreateInput[],
|
||||||
|
): Promise<Prisma.BatchPayload> {
|
||||||
|
await this.deletePatients(patientCreateInput.map(({ id }) => id));
|
||||||
|
return this.createPatients(patientCreateInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
deletePatients(ids: string[]): Promise<Prisma.BatchPayload> {
|
||||||
|
return this.prisma.patient.deleteMany({
|
||||||
|
where: {
|
||||||
|
id: {
|
||||||
|
in: ids,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
findAttendanceRegistrationsInInterval(
|
||||||
|
interval: Interval,
|
||||||
|
): Promise<AttendanceRegistration[]> {
|
||||||
|
return this.prisma.attendanceRegistration.findMany({
|
||||||
|
where: {
|
||||||
|
AND: [
|
||||||
|
{
|
||||||
|
dateTime: {
|
||||||
|
gte: new Date(interval.start),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
dateTime: {
|
||||||
|
lte: new Date(interval.end),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
createAttendanceRegistrations(
|
||||||
|
attendanceRegistrationCreateInput: Prisma.AttendanceRegistrationCreateInput[],
|
||||||
|
): Promise<Prisma.BatchPayload> {
|
||||||
|
return this.prisma.attendanceRegistration.createMany({
|
||||||
|
data: attendanceRegistrationCreateInput,
|
||||||
|
skipDuplicates: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateAttendanceRegistrations(
|
||||||
|
attendanceRegistrationCreateInput: Prisma.AttendanceRegistrationCreateInput[],
|
||||||
|
): Promise<Prisma.BatchPayload> {
|
||||||
|
await this.prisma.attendanceRegistration.deleteMany({
|
||||||
|
where: {
|
||||||
|
attendanceRegistrationId: {
|
||||||
|
in: attendanceRegistrationCreateInput.map(
|
||||||
|
({ attendanceRegistrationId }) => attendanceRegistrationId,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return this.createAttendanceRegistrations(
|
||||||
|
attendanceRegistrationCreateInput,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteAttendanceRegistrations(
|
||||||
|
attendanceRegistrationIds: number[],
|
||||||
|
): Promise<Prisma.BatchPayload> {
|
||||||
|
return this.prisma.attendanceRegistration.deleteMany({
|
||||||
|
where: {
|
||||||
|
attendanceRegistrationId: {
|
||||||
|
in: attendanceRegistrationIds,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
findPlannedTimesInInterval(interval: Interval): Promise<PlannedTime[]> {
|
||||||
|
return this.prisma.plannedTime.findMany({
|
||||||
|
where: {
|
||||||
|
AND: [
|
||||||
|
{
|
||||||
|
startDate: {
|
||||||
|
gte: new Date(interval.start),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
startDate: {
|
||||||
|
lte: new Date(interval.end),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
createPlannedTimes(
|
||||||
|
plannedTimeCreateInput: Prisma.PlannedTimeCreateInput[],
|
||||||
|
): Promise<Prisma.BatchPayload> {
|
||||||
|
return this.prisma.plannedTime.createMany({
|
||||||
|
data: plannedTimeCreateInput,
|
||||||
|
skipDuplicates: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async updatePlannedTimes(
|
||||||
|
plannedTimeCreateInput: Prisma.PlannedTimeCreateInput[],
|
||||||
|
): Promise<Prisma.BatchPayload> {
|
||||||
|
await this.prisma.plannedTime.deleteMany({
|
||||||
|
where: {
|
||||||
|
id: {
|
||||||
|
in: plannedTimeCreateInput.map(({ id }) => id),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return this.createPlannedTimes(plannedTimeCreateInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
deletePlannedTimes(plannedTimeIds: string[]): Promise<Prisma.BatchPayload> {
|
||||||
|
return this.prisma.plannedTime.deleteMany({
|
||||||
|
where: {
|
||||||
|
id: {
|
||||||
|
in: plannedTimeIds,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
findWorkTimesInInterval(interval: Interval): Promise<WorkTime[]> {
|
||||||
|
return this.prisma.workTime.findMany({
|
||||||
|
where: {
|
||||||
|
AND: [
|
||||||
|
{
|
||||||
|
startDate: {
|
||||||
|
gte: new Date(interval.start),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
startDate: {
|
||||||
|
lte: new Date(interval.end),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
createWorkTimes(
|
||||||
|
workTimeCreateInput: Prisma.WorkTimeCreateInput[],
|
||||||
|
): Promise<Prisma.BatchPayload> {
|
||||||
|
return this.prisma.workTime.createMany({
|
||||||
|
data: workTimeCreateInput,
|
||||||
|
skipDuplicates: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateWorkTimes(
|
||||||
|
workTimeCreateInput: Prisma.WorkTimeCreateInput[],
|
||||||
|
): Promise<Prisma.BatchPayload> {
|
||||||
|
await this.prisma.workTime.deleteMany({
|
||||||
|
where: {
|
||||||
|
id: {
|
||||||
|
in: workTimeCreateInput.map(({ id }) => id),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return this.createWorkTimes(workTimeCreateInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteWorkTimes(workTimeIds: string[]): Promise<Prisma.BatchPayload> {
|
||||||
|
return this.prisma.workTime.deleteMany({
|
||||||
|
where: {
|
||||||
|
id: {
|
||||||
|
in: workTimeIds,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,204 @@
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { from, map, of, switchMap } from 'rxjs';
|
||||||
|
|
||||||
|
import { TransformersService } from '../feat-transformers/transformers.service';
|
||||||
|
import { FinalRepository } from './final.repository';
|
||||||
|
import { AttendanceRegistrationLoader } from './loaders/attendance-registration.loader';
|
||||||
|
import { PatientLoader } from './loaders/patient.loader';
|
||||||
|
import { PlannedTimeLoader } from './loaders/planned-time.loader';
|
||||||
|
import { TourLoader } from './loaders/tour.loader';
|
||||||
|
import { WorkTimeLoader } from './loaders/work-time.loader';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class LoadersService {
|
||||||
|
constructor(
|
||||||
|
private readonly repository: FinalRepository,
|
||||||
|
private readonly transformersService: TransformersService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
loadTours() {
|
||||||
|
return from(this.transformersService.findTours()).pipe(
|
||||||
|
switchMap((stagingTours) => {
|
||||||
|
if (stagingTours.length === 0) {
|
||||||
|
return of({
|
||||||
|
staging: [],
|
||||||
|
final: [],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const { startDate: firstTourDate } = stagingTours.at(0);
|
||||||
|
const { startDate: lastTourDate } = stagingTours.at(-1);
|
||||||
|
return from(
|
||||||
|
this.repository.findToursInInterval(
|
||||||
|
{
|
||||||
|
start: firstTourDate,
|
||||||
|
end: lastTourDate,
|
||||||
|
},
|
||||||
|
stagingTours.map(({ operationId }) => operationId),
|
||||||
|
),
|
||||||
|
).pipe(
|
||||||
|
map((finalTours) => ({
|
||||||
|
final: finalTours,
|
||||||
|
staging: stagingTours,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
map(({ staging, final }) =>
|
||||||
|
TourLoader.determineLoadOperations(staging, final),
|
||||||
|
),
|
||||||
|
switchMap(({ deleted, updated, created }) => {
|
||||||
|
return from(
|
||||||
|
Promise.all([
|
||||||
|
this.repository.deleteTours(deleted),
|
||||||
|
this.repository.updateTours(updated),
|
||||||
|
this.repository.createTours(created),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadPatients() {
|
||||||
|
return from(this.transformersService.findPatients()).pipe(
|
||||||
|
switchMap((stagingPatients) =>
|
||||||
|
from(this.repository.findPatients()).pipe(
|
||||||
|
map((finalPatients) => ({
|
||||||
|
final: finalPatients,
|
||||||
|
staging: stagingPatients,
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
map(({ staging, final }) =>
|
||||||
|
PatientLoader.determineLoadOperations(staging, final),
|
||||||
|
),
|
||||||
|
switchMap(({ deleted, updated, created }) =>
|
||||||
|
from(
|
||||||
|
Promise.all([
|
||||||
|
this.repository.deletePatients(deleted),
|
||||||
|
this.repository.updatePatients(updated),
|
||||||
|
this.repository.createPatients(created),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadAttendanceRegistrations() {
|
||||||
|
return from(this.transformersService.findAttendanceRegistrations()).pipe(
|
||||||
|
switchMap((stagingAttendanceRegistrations) => {
|
||||||
|
if (stagingAttendanceRegistrations.length === 0) {
|
||||||
|
return of({
|
||||||
|
staging: [],
|
||||||
|
final: [],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const { dateTime: firstAttendanceRegistration } = [
|
||||||
|
...stagingAttendanceRegistrations,
|
||||||
|
].shift();
|
||||||
|
const { dateTime: lastAttendanceRegistration } = [
|
||||||
|
...stagingAttendanceRegistrations,
|
||||||
|
].pop();
|
||||||
|
return from(
|
||||||
|
this.repository.findAttendanceRegistrationsInInterval({
|
||||||
|
start: firstAttendanceRegistration,
|
||||||
|
end: lastAttendanceRegistration,
|
||||||
|
}),
|
||||||
|
).pipe(
|
||||||
|
map((finalAttendanceRegistrations) => ({
|
||||||
|
final: finalAttendanceRegistrations,
|
||||||
|
staging: stagingAttendanceRegistrations,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
map(({ staging, final }) =>
|
||||||
|
AttendanceRegistrationLoader.determineLoadOperations(staging, final),
|
||||||
|
),
|
||||||
|
switchMap(({ deleted, updated, created }) => {
|
||||||
|
return from(
|
||||||
|
Promise.all([
|
||||||
|
this.repository.deleteAttendanceRegistrations(deleted),
|
||||||
|
this.repository.updateAttendanceRegistrations(updated),
|
||||||
|
this.repository.createAttendanceRegistrations(created),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadPlannedTimes() {
|
||||||
|
return from(this.transformersService.findPlannedTimes()).pipe(
|
||||||
|
switchMap((stagingPlannedTimes) => {
|
||||||
|
if (stagingPlannedTimes.length === 0) {
|
||||||
|
return of({
|
||||||
|
staging: [],
|
||||||
|
final: [],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const { startDate: firstPlannedTime } = [
|
||||||
|
...stagingPlannedTimes,
|
||||||
|
].shift();
|
||||||
|
const { startDate: lastPlannedTime } = [...stagingPlannedTimes].pop();
|
||||||
|
return from(
|
||||||
|
this.repository.findPlannedTimesInInterval({
|
||||||
|
start: firstPlannedTime,
|
||||||
|
end: lastPlannedTime,
|
||||||
|
}),
|
||||||
|
).pipe(
|
||||||
|
map((finalPlannedTimes) => ({
|
||||||
|
final: finalPlannedTimes,
|
||||||
|
staging: stagingPlannedTimes,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
map(({ staging, final }) =>
|
||||||
|
PlannedTimeLoader.determineLoadOperations(staging, final),
|
||||||
|
),
|
||||||
|
switchMap(({ deleted, updated, created }) => {
|
||||||
|
return from(
|
||||||
|
Promise.all([
|
||||||
|
this.repository.deletePlannedTimes(deleted),
|
||||||
|
this.repository.updatePlannedTimes(updated),
|
||||||
|
this.repository.createPlannedTimes(created),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadWorkTimes() {
|
||||||
|
return from(this.transformersService.findWorkTimes()).pipe(
|
||||||
|
switchMap((stagingWorkTimes) => {
|
||||||
|
if (stagingWorkTimes.length === 0) {
|
||||||
|
return of({
|
||||||
|
staging: [],
|
||||||
|
final: [],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const { startDate: firstWorkTime } = [...stagingWorkTimes].shift();
|
||||||
|
const { startDate: lastWorkTime } = [...stagingWorkTimes].pop();
|
||||||
|
return from(
|
||||||
|
this.repository.findWorkTimesInInterval({
|
||||||
|
start: firstWorkTime,
|
||||||
|
end: lastWorkTime,
|
||||||
|
}),
|
||||||
|
).pipe(
|
||||||
|
map((finalTours) => ({
|
||||||
|
final: finalTours,
|
||||||
|
staging: stagingWorkTimes,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
map(({ staging, final }) =>
|
||||||
|
WorkTimeLoader.determineLoadOperations(staging, final),
|
||||||
|
),
|
||||||
|
switchMap(({ deleted, updated, created }) => {
|
||||||
|
return from(
|
||||||
|
Promise.all([
|
||||||
|
this.repository.deleteWorkTimes(deleted),
|
||||||
|
this.repository.updateWorkTimes(updated),
|
||||||
|
this.repository.createWorkTimes(created),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,82 @@
|
||||||
|
import {
|
||||||
|
Prisma,
|
||||||
|
AttendanceRegistration,
|
||||||
|
AttendanceRegistrationStaging,
|
||||||
|
} from '@prisma/client';
|
||||||
|
|
||||||
|
export class AttendanceRegistrationLoader {
|
||||||
|
public static determineLoadOperations(
|
||||||
|
stagingAttendanceRegistrations: AttendanceRegistrationStaging[],
|
||||||
|
finalAttendanceRegistrations: AttendanceRegistration[],
|
||||||
|
): {
|
||||||
|
deleted: number[];
|
||||||
|
updated: Prisma.AttendanceRegistrationCreateInput[];
|
||||||
|
created: Prisma.AttendanceRegistrationCreateInput[];
|
||||||
|
} {
|
||||||
|
const deletedAttendanceRegistrations = finalAttendanceRegistrations.filter(
|
||||||
|
(finalAttendanceRegistration) =>
|
||||||
|
!stagingAttendanceRegistrations.find(
|
||||||
|
(stagingAttendanceRegistration) =>
|
||||||
|
stagingAttendanceRegistration.attendanceRegistrationId ===
|
||||||
|
finalAttendanceRegistration.attendanceRegistrationId,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const attendanceRegistrationsToInsert =
|
||||||
|
stagingAttendanceRegistrations.filter(
|
||||||
|
({ attendanceRegistrationId }) =>
|
||||||
|
!!attendanceRegistrationId &&
|
||||||
|
!deletedAttendanceRegistrations.find(
|
||||||
|
(deletedAttendanceRegistration) =>
|
||||||
|
deletedAttendanceRegistration.attendanceRegistrationId ===
|
||||||
|
attendanceRegistrationId,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const newAttendanceRegistrations: Prisma.AttendanceRegistrationCreateInput[] =
|
||||||
|
[];
|
||||||
|
const updatedAttendanceRegistrations: Prisma.AttendanceRegistrationCreateInput[] =
|
||||||
|
[];
|
||||||
|
for (const stagingAttendanceRegistration of attendanceRegistrationsToInsert) {
|
||||||
|
const finalAttendanceRegistration = finalAttendanceRegistrations.find(
|
||||||
|
(finalAttendanceRegistration) =>
|
||||||
|
finalAttendanceRegistration.attendanceRegistrationId ===
|
||||||
|
stagingAttendanceRegistration.attendanceRegistrationId,
|
||||||
|
);
|
||||||
|
if (finalAttendanceRegistration) {
|
||||||
|
updatedAttendanceRegistrations.push(
|
||||||
|
this.attendanceRegistrationStagingToFinal(
|
||||||
|
stagingAttendanceRegistration,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
newAttendanceRegistrations.push(
|
||||||
|
this.attendanceRegistrationStagingToFinal(
|
||||||
|
stagingAttendanceRegistration,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
deleted: deletedAttendanceRegistrations.map(
|
||||||
|
({ attendanceRegistrationId }) => attendanceRegistrationId,
|
||||||
|
),
|
||||||
|
updated: updatedAttendanceRegistrations,
|
||||||
|
created: newAttendanceRegistrations,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static attendanceRegistrationStagingToFinal(
|
||||||
|
attendanceregistration: AttendanceRegistrationStaging,
|
||||||
|
): Prisma.AttendanceRegistrationCreateInput {
|
||||||
|
const { attendanceRegistrationId, employeeId, dateTime, event } =
|
||||||
|
attendanceregistration;
|
||||||
|
|
||||||
|
return {
|
||||||
|
attendanceRegistrationId,
|
||||||
|
employeeId,
|
||||||
|
dateTime,
|
||||||
|
event,
|
||||||
|
personnelNumber: '',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
import { Prisma, Patient, PatientStaging } from '@prisma/client';
|
||||||
|
|
||||||
|
export class PatientLoader {
|
||||||
|
public static determineLoadOperations(
|
||||||
|
stagingPatients: PatientStaging[],
|
||||||
|
finalPatients: Patient[],
|
||||||
|
): {
|
||||||
|
deleted: string[];
|
||||||
|
updated: Prisma.PatientCreateInput[];
|
||||||
|
created: Prisma.PatientCreateInput[];
|
||||||
|
} {
|
||||||
|
const deletedPatients = finalPatients.filter(
|
||||||
|
(finalPatient) =>
|
||||||
|
!stagingPatients.find(
|
||||||
|
(stagingPatient) => stagingPatient.id === finalPatient.id,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const patientsToInsert = stagingPatients.filter(
|
||||||
|
({ id }) =>
|
||||||
|
!!id &&
|
||||||
|
!deletedPatients.find((deletedPatient) => deletedPatient.id === id),
|
||||||
|
);
|
||||||
|
const newPatients: Prisma.PatientCreateInput[] = [];
|
||||||
|
const updatedPatients: Prisma.PatientCreateInput[] = [];
|
||||||
|
|
||||||
|
for (const stagingPatient of patientsToInsert) {
|
||||||
|
const finalPatient = finalPatients.find(
|
||||||
|
(finalPatient) => finalPatient.id === stagingPatient.id,
|
||||||
|
);
|
||||||
|
if (finalPatient) {
|
||||||
|
updatedPatients.push(this.patientStagingToFinal(stagingPatient));
|
||||||
|
} else {
|
||||||
|
newPatients.push(this.patientStagingToFinal(stagingPatient));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
deleted: deletedPatients.map(({ id }) => id),
|
||||||
|
updated: updatedPatients,
|
||||||
|
created: newPatients,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static patientStagingToFinal(
|
||||||
|
patient: PatientStaging,
|
||||||
|
): Prisma.PatientCreateInput {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
surname,
|
||||||
|
name,
|
||||||
|
street,
|
||||||
|
zip,
|
||||||
|
city,
|
||||||
|
birthday,
|
||||||
|
healthinsurance,
|
||||||
|
healthinsuranceId,
|
||||||
|
careDegree,
|
||||||
|
disabilityMark1,
|
||||||
|
disabilityMark2,
|
||||||
|
} = patient;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
surname,
|
||||||
|
name,
|
||||||
|
street,
|
||||||
|
zip,
|
||||||
|
city,
|
||||||
|
birthday,
|
||||||
|
healthinsurance,
|
||||||
|
healthinsuranceId,
|
||||||
|
careDegree,
|
||||||
|
disabilityMark1,
|
||||||
|
disabilityMark2,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
import { Prisma, PlannedTime, PlannedTimeStaging } from '@prisma/client';
|
||||||
|
|
||||||
|
export class PlannedTimeLoader {
|
||||||
|
public static determineLoadOperations(
|
||||||
|
stagingPlannedTimes: PlannedTimeStaging[],
|
||||||
|
finalPlannedTimes: PlannedTime[],
|
||||||
|
): {
|
||||||
|
deleted: string[];
|
||||||
|
updated: Prisma.PlannedTimeCreateInput[];
|
||||||
|
created: Prisma.PlannedTimeCreateInput[];
|
||||||
|
} {
|
||||||
|
const deletedPlannedTimes = stagingPlannedTimes.filter(
|
||||||
|
({ deleted }) => deleted,
|
||||||
|
);
|
||||||
|
console.log(deletedPlannedTimes.length);
|
||||||
|
const plannedTimesToInsert = stagingPlannedTimes.filter(
|
||||||
|
({ id }) =>
|
||||||
|
!!id &&
|
||||||
|
!deletedPlannedTimes.find(
|
||||||
|
(deletedPlannedTime) => deletedPlannedTime.id === id,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const newPlannedTimes: Prisma.PlannedTimeCreateInput[] = [];
|
||||||
|
const updatedPlannedTimes: Prisma.PlannedTimeCreateInput[] = [];
|
||||||
|
for (const stagingPlannedTime of plannedTimesToInsert) {
|
||||||
|
const finalPlannedTime = finalPlannedTimes.find(
|
||||||
|
(finalPlannedTime) =>
|
||||||
|
finalPlannedTime.id === `${stagingPlannedTime.id}`,
|
||||||
|
);
|
||||||
|
if (finalPlannedTime) {
|
||||||
|
updatedPlannedTimes.push(
|
||||||
|
this.plannedTimeStagingToFinal(stagingPlannedTime),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
newPlannedTimes.push(
|
||||||
|
this.plannedTimeStagingToFinal(stagingPlannedTime),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
deleted: deletedPlannedTimes.map(({ id }) => `${id}`),
|
||||||
|
updated: updatedPlannedTimes,
|
||||||
|
created: newPlannedTimes,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static plannedTimeStagingToFinal(
|
||||||
|
plannedtime: PlannedTimeStaging,
|
||||||
|
): Prisma.PlannedTimeCreateInput {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
employeeId,
|
||||||
|
firstname,
|
||||||
|
surname,
|
||||||
|
departmentName,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
|
mark,
|
||||||
|
} = plannedtime;
|
||||||
|
|
||||||
|
return {
|
||||||
|
firstname,
|
||||||
|
surname,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
|
id: `${id}`,
|
||||||
|
userId: `${employeeId}`,
|
||||||
|
department: departmentName,
|
||||||
|
pause: 0,
|
||||||
|
duration: 0,
|
||||||
|
note: mark,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,149 @@
|
||||||
|
import { Prisma, Tour, TourStaging } from '@prisma/client';
|
||||||
|
|
||||||
|
export class TourLoader {
|
||||||
|
public static determineLoadOperations(
|
||||||
|
stagingTours: TourStaging[],
|
||||||
|
finalTours: Tour[],
|
||||||
|
): {
|
||||||
|
deleted: string[];
|
||||||
|
updated: Prisma.TourCreateInput[];
|
||||||
|
created: Prisma.TourCreateInput[];
|
||||||
|
} {
|
||||||
|
const deletedTours = finalTours.filter(
|
||||||
|
(finalTour) =>
|
||||||
|
!stagingTours.find(
|
||||||
|
(stagingTour) => stagingTour.operationId === finalTour.operationId,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const toursToInsert = stagingTours.filter(
|
||||||
|
({ id, operationId }) =>
|
||||||
|
!!id &&
|
||||||
|
!deletedTours.find(
|
||||||
|
(deletedTour) => deletedTour.operationId === operationId,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const newTours: Prisma.TourCreateInput[] = [];
|
||||||
|
const updatedTours: Prisma.TourCreateInput[] = [];
|
||||||
|
for (const stagingTour of toursToInsert) {
|
||||||
|
const finalTour = finalTours.find(
|
||||||
|
(finalTour) => finalTour.operationId === stagingTour.operationId,
|
||||||
|
);
|
||||||
|
if (finalTour) {
|
||||||
|
updatedTours.push(
|
||||||
|
this.tourStagingToFinal(stagingTour, finalTour.ticketId),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
newTours.push(this.tourStagingToFinal(stagingTour));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
deleted: deletedTours.map(({ operationId }) => operationId),
|
||||||
|
updated: updatedTours,
|
||||||
|
created: newTours,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static tourStagingToFinal(tour: TourStaging, ticketId?: string) {
|
||||||
|
const {
|
||||||
|
done,
|
||||||
|
direction,
|
||||||
|
carName,
|
||||||
|
operationId,
|
||||||
|
driverId,
|
||||||
|
driverName,
|
||||||
|
codriverId,
|
||||||
|
codriverName,
|
||||||
|
patientId,
|
||||||
|
patientName,
|
||||||
|
patientSurname,
|
||||||
|
patientStreet,
|
||||||
|
patientZip,
|
||||||
|
patientCity,
|
||||||
|
healthInsurance,
|
||||||
|
healthInsuranceNumber,
|
||||||
|
type,
|
||||||
|
category,
|
||||||
|
transportType,
|
||||||
|
ordinanceType,
|
||||||
|
rangeEndDate,
|
||||||
|
startInstitution,
|
||||||
|
startStreet,
|
||||||
|
startZip,
|
||||||
|
startCity,
|
||||||
|
targetInstitution,
|
||||||
|
targetStreet,
|
||||||
|
targetZip,
|
||||||
|
targetCity,
|
||||||
|
startDate,
|
||||||
|
check,
|
||||||
|
occupiedKm,
|
||||||
|
totalKm,
|
||||||
|
startBegin,
|
||||||
|
startEnd,
|
||||||
|
target,
|
||||||
|
targetBegin,
|
||||||
|
empty,
|
||||||
|
hasInfection,
|
||||||
|
infectionName,
|
||||||
|
revenueDispoLive,
|
||||||
|
revenue,
|
||||||
|
revenueDeviation,
|
||||||
|
consumptionCosts,
|
||||||
|
billDate,
|
||||||
|
billNumber,
|
||||||
|
createdAt,
|
||||||
|
} = tour;
|
||||||
|
|
||||||
|
return {
|
||||||
|
done,
|
||||||
|
direction,
|
||||||
|
carName,
|
||||||
|
operationId,
|
||||||
|
driverId,
|
||||||
|
driverName,
|
||||||
|
codriverId,
|
||||||
|
codriverName,
|
||||||
|
patientId,
|
||||||
|
patientName,
|
||||||
|
patientSurname,
|
||||||
|
patientStreet,
|
||||||
|
patientZip,
|
||||||
|
patientCity,
|
||||||
|
healthInsurance,
|
||||||
|
healthInsuranceNumber,
|
||||||
|
type,
|
||||||
|
category,
|
||||||
|
transportType,
|
||||||
|
ordinanceType,
|
||||||
|
rangeEndDate,
|
||||||
|
startInstitution,
|
||||||
|
startStreet,
|
||||||
|
startZip,
|
||||||
|
startCity,
|
||||||
|
targetInstitution,
|
||||||
|
targetStreet,
|
||||||
|
targetZip,
|
||||||
|
targetCity,
|
||||||
|
startDate,
|
||||||
|
check,
|
||||||
|
occupiedKm,
|
||||||
|
totalKm,
|
||||||
|
startBegin,
|
||||||
|
startEnd,
|
||||||
|
target,
|
||||||
|
targetBegin,
|
||||||
|
empty,
|
||||||
|
hasInfection,
|
||||||
|
infectionName,
|
||||||
|
revenueDispoLive,
|
||||||
|
revenue,
|
||||||
|
revenueDeviation,
|
||||||
|
consumptionCosts,
|
||||||
|
billDate,
|
||||||
|
billNumber,
|
||||||
|
createdAt,
|
||||||
|
...(ticketId ? { ticketId } : {}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
import { Prisma, WorkTime, WorkTimeStaging } from '@prisma/client';
|
||||||
|
|
||||||
|
export class WorkTimeLoader {
|
||||||
|
public static determineLoadOperations(
|
||||||
|
stagingWorkTimes: WorkTimeStaging[],
|
||||||
|
finalWorkTimes: WorkTime[],
|
||||||
|
): {
|
||||||
|
deleted: string[];
|
||||||
|
updated: Prisma.WorkTimeCreateInput[];
|
||||||
|
created: Prisma.WorkTimeCreateInput[];
|
||||||
|
} {
|
||||||
|
const deletedWorkTimes = finalWorkTimes.filter(
|
||||||
|
(finalWorkTime) =>
|
||||||
|
!stagingWorkTimes.find(
|
||||||
|
(stagingWorkTime) => `${stagingWorkTime.id}` === finalWorkTime.id,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const workTimesToInsert = stagingWorkTimes.filter(
|
||||||
|
({ id }) =>
|
||||||
|
!!id &&
|
||||||
|
!deletedWorkTimes.find(
|
||||||
|
(deletedWorkTime) => deletedWorkTime.id === `${id}`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const newWorkTimes: Prisma.WorkTimeCreateInput[] = [];
|
||||||
|
const updatedWorkTimes: Prisma.WorkTimeCreateInput[] = [];
|
||||||
|
for (const stagingWorkTime of workTimesToInsert) {
|
||||||
|
const finalWorkTime = finalWorkTimes.find(
|
||||||
|
(finalWorkTime) => finalWorkTime.id === `${stagingWorkTime.id}`,
|
||||||
|
);
|
||||||
|
if (finalWorkTime) {
|
||||||
|
updatedWorkTimes.push(this.workTimeStagingToFinal(stagingWorkTime));
|
||||||
|
} else {
|
||||||
|
newWorkTimes.push(this.workTimeStagingToFinal(stagingWorkTime));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
deleted: deletedWorkTimes.map(({ id }) => id),
|
||||||
|
updated: updatedWorkTimes,
|
||||||
|
created: newWorkTimes,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static workTimeStagingToFinal(
|
||||||
|
worktime: WorkTimeStaging,
|
||||||
|
): Prisma.WorkTimeCreateInput {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
employeeId,
|
||||||
|
firstname,
|
||||||
|
surname,
|
||||||
|
departmentName,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
|
} = worktime;
|
||||||
|
|
||||||
|
return {
|
||||||
|
firstname,
|
||||||
|
surname,
|
||||||
|
startDate,
|
||||||
|
endDate,
|
||||||
|
id: `${id}`,
|
||||||
|
userId: `${employeeId}`,
|
||||||
|
department: departmentName,
|
||||||
|
pause: 0,
|
||||||
|
duration: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
// import { CalculationService } from '@dks/avicenna/api-util-calculation';
|
||||||
|
// import { Injectable } from '@nestjs/common';
|
||||||
|
// import { Tour } from '@prisma/client';
|
||||||
|
// import { chunk } from 'src/utils';
|
||||||
|
// import { TourPreStaging } from '../staging.interface';
|
||||||
|
|
||||||
|
// @Injectable()
|
||||||
|
// export class ToursCalculationAdapater {
|
||||||
|
// constructor(private readonly calculationService: CalculationService) {}
|
||||||
|
|
||||||
|
// public async addRevenueAndConsumptionCostsToTours(
|
||||||
|
// tours: TourPreStaging[],
|
||||||
|
// ): Promise<TourPreStaging[]> {
|
||||||
|
// const resultTours = [];
|
||||||
|
// for (const tourChunk of chunk(tours, 5)) {
|
||||||
|
// resultTours.push(
|
||||||
|
// await Promise.all(
|
||||||
|
// tourChunk.map((tour) =>
|
||||||
|
// this.addRevenueAndConsumptionCostsToTour(tour),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// return resultTours.flat();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// public async addRevenueAndConsumptionCostsToTour(
|
||||||
|
// tour: TourPreStaging,
|
||||||
|
// ): Promise<TourPreStaging> {
|
||||||
|
// if (!tour?.ordinanceType) {
|
||||||
|
// return tour;
|
||||||
|
// }
|
||||||
|
// const revenue = await this.calculationService.calculateTourRevenue(
|
||||||
|
// tour as unknown as Tour,
|
||||||
|
// );
|
||||||
|
// const revenueDeviation =
|
||||||
|
// tour.revenueDispoLive > 0
|
||||||
|
// ? Math.floor((revenue - tour.revenueDispoLive) * 100) / 100
|
||||||
|
// : 0;
|
||||||
|
|
||||||
|
// const { consumptionCostsPerTour } =
|
||||||
|
// this.calculationService.getBillingInformationByOrdinanceType(
|
||||||
|
// tour.ordinanceType.match(/([A-Z])\w+/).at(0),
|
||||||
|
// tour.startDate,
|
||||||
|
// );
|
||||||
|
|
||||||
|
// return {
|
||||||
|
// ...tour,
|
||||||
|
// revenue,
|
||||||
|
// revenueDeviation,
|
||||||
|
// consumptionCosts: consumptionCostsPerTour,
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
// import { ToursCalculationAdapater } from './adapter/tours-calculation.adapter';
|
||||||
|
import { StagingRepository } from './staging.repository';
|
||||||
|
import { TransformersService } from './transformers.service';
|
||||||
|
import { PrismaModule } from 'src/core/database/prisma.module';
|
||||||
|
import { DataAccessExtractorsModule } from '../feat-extractors/data-access-extractors.module';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [PrismaModule, DataAccessExtractorsModule],
|
||||||
|
providers: [TransformersService, StagingRepository],
|
||||||
|
exports: [TransformersService],
|
||||||
|
})
|
||||||
|
export class DataAccessTransformersModule {}
|
||||||
|
|
@ -0,0 +1,117 @@
|
||||||
|
export interface TourPreStaging {
|
||||||
|
id?: string;
|
||||||
|
done: boolean;
|
||||||
|
direction: string;
|
||||||
|
carName?: string;
|
||||||
|
operationId: string;
|
||||||
|
|
||||||
|
driverId?: string;
|
||||||
|
driverName?: string;
|
||||||
|
codriverId?: string;
|
||||||
|
codriverName?: string;
|
||||||
|
|
||||||
|
patientId?: string;
|
||||||
|
patientName?: string;
|
||||||
|
patientSurname?: string;
|
||||||
|
patientStreet?: string;
|
||||||
|
patientZip?: string;
|
||||||
|
patientCity?: string;
|
||||||
|
healthInsurance?: string;
|
||||||
|
healthInsuranceNumber?: string;
|
||||||
|
|
||||||
|
type?: string;
|
||||||
|
category?: string;
|
||||||
|
transportType?: string;
|
||||||
|
ordinanceType?: string;
|
||||||
|
rangeEndDate?: string;
|
||||||
|
|
||||||
|
startInstitution?: string;
|
||||||
|
startStreet: string;
|
||||||
|
startZip: string;
|
||||||
|
startCity: string;
|
||||||
|
targetInstitution?: string;
|
||||||
|
targetStreet: string;
|
||||||
|
targetZip: string;
|
||||||
|
targetCity: string;
|
||||||
|
|
||||||
|
startDate: Date;
|
||||||
|
|
||||||
|
check: number;
|
||||||
|
occupiedKm?: number;
|
||||||
|
totalKm?: number;
|
||||||
|
|
||||||
|
startBegin?: string;
|
||||||
|
startEnd?: string;
|
||||||
|
target?: string;
|
||||||
|
targetBegin?: string;
|
||||||
|
empty?: string;
|
||||||
|
|
||||||
|
hasInfection: boolean;
|
||||||
|
infectionName?: string;
|
||||||
|
|
||||||
|
revenueDispoLive?: number;
|
||||||
|
billDate?: Date;
|
||||||
|
billNumber?: string;
|
||||||
|
revenue?: number;
|
||||||
|
revenueDeviation?: number;
|
||||||
|
consumptionCosts?: number;
|
||||||
|
|
||||||
|
createdAt?: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PatientPreStaging {
|
||||||
|
id?: string;
|
||||||
|
surname?: string;
|
||||||
|
name?: string;
|
||||||
|
street?: string;
|
||||||
|
zip?: string;
|
||||||
|
city?: string;
|
||||||
|
|
||||||
|
birthday?: Date;
|
||||||
|
healthinsuranceId?: string;
|
||||||
|
healthinsurance?: string;
|
||||||
|
careDegree?: string;
|
||||||
|
disabilityMark1?: string;
|
||||||
|
disabilityMark2?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AttendanceRegistrationPreStaging {
|
||||||
|
attendanceRegistrationId: number;
|
||||||
|
employeeId: number;
|
||||||
|
dateTime: Date;
|
||||||
|
event: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PlannedTimePreStaging {
|
||||||
|
id: number;
|
||||||
|
employeeId: number;
|
||||||
|
firstname?: string;
|
||||||
|
surname?: string;
|
||||||
|
departmentName: string;
|
||||||
|
startDate: Date;
|
||||||
|
endDate: Date;
|
||||||
|
deleted: boolean;
|
||||||
|
mark?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WorkTimePreStaging {
|
||||||
|
id: number;
|
||||||
|
employeeId: number;
|
||||||
|
firstname: string;
|
||||||
|
surname: string;
|
||||||
|
departmentName: string;
|
||||||
|
startDate: Date;
|
||||||
|
endDate: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EmployeePreStaging {
|
||||||
|
id: number;
|
||||||
|
firstname: string;
|
||||||
|
surname: string;
|
||||||
|
contractStart: Date;
|
||||||
|
contractEnd: Date;
|
||||||
|
contractHoursWeek: number;
|
||||||
|
contractSalaryHour: number;
|
||||||
|
personTransportCertificate?: Date;
|
||||||
|
qualification?: string;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,118 @@
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import {
|
||||||
|
AttendanceRegistrationStaging,
|
||||||
|
PatientStaging,
|
||||||
|
PlannedTimeStaging,
|
||||||
|
Prisma,
|
||||||
|
TourStaging,
|
||||||
|
WorkTimeStaging,
|
||||||
|
} from '@prisma/client';
|
||||||
|
import { PrismaService } from 'src/core/database/prisma.service';
|
||||||
|
import {
|
||||||
|
AttendanceRegistrationPreStaging,
|
||||||
|
EmployeePreStaging,
|
||||||
|
PatientPreStaging,
|
||||||
|
PlannedTimePreStaging,
|
||||||
|
TourPreStaging,
|
||||||
|
WorkTimePreStaging,
|
||||||
|
} from './staging.interface';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class StagingRepository {
|
||||||
|
constructor(private readonly prisma: PrismaService) {}
|
||||||
|
|
||||||
|
clearStaging(): Promise<Prisma.BatchPayload[]> {
|
||||||
|
return Promise.all([
|
||||||
|
this.prisma.tourStaging.deleteMany(),
|
||||||
|
this.prisma.employeeStaging.deleteMany(),
|
||||||
|
this.prisma.workTimeStaging.deleteMany(),
|
||||||
|
this.prisma.plannedTimeStaging.deleteMany(),
|
||||||
|
this.prisma.attendanceRegistrationStaging.deleteMany(),
|
||||||
|
this.prisma.patientStaging.deleteMany(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
createPatients(patients: PatientPreStaging[]): Promise<Prisma.BatchPayload> {
|
||||||
|
return this.prisma.patientStaging.createMany({
|
||||||
|
data: patients,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
createPlannedTimes(
|
||||||
|
plannedTimes: PlannedTimePreStaging[],
|
||||||
|
): Promise<Prisma.BatchPayload> {
|
||||||
|
return this.prisma.plannedTimeStaging.createMany({
|
||||||
|
data: plannedTimes,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
createWorkTimes(
|
||||||
|
workTimes: WorkTimePreStaging[],
|
||||||
|
): Promise<Prisma.BatchPayload> {
|
||||||
|
return this.prisma.workTimeStaging.createMany({
|
||||||
|
data: workTimes,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
createAttendanceRegistrations(
|
||||||
|
attendanceRegistrations: AttendanceRegistrationPreStaging[],
|
||||||
|
): Promise<Prisma.BatchPayload> {
|
||||||
|
return this.prisma.attendanceRegistrationStaging.createMany({
|
||||||
|
data: attendanceRegistrations,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
createTours(tours: TourPreStaging[]): Promise<Prisma.BatchPayload> {
|
||||||
|
return this.prisma.tourStaging.createMany({
|
||||||
|
data: tours,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
createEmployees(
|
||||||
|
employees: EmployeePreStaging[],
|
||||||
|
): Promise<Prisma.BatchPayload> {
|
||||||
|
return this.prisma.employeeStaging.createMany({
|
||||||
|
data: employees,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
findPatients(): Promise<PatientStaging[]> {
|
||||||
|
return this.prisma.patientStaging.findMany({
|
||||||
|
orderBy: {
|
||||||
|
surname: 'asc',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
findTours(): Promise<TourStaging[]> {
|
||||||
|
return this.prisma.tourStaging.findMany({
|
||||||
|
orderBy: {
|
||||||
|
startDate: 'asc',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
findAttendanceRegistrations(): Promise<AttendanceRegistrationStaging[]> {
|
||||||
|
return this.prisma.attendanceRegistrationStaging.findMany({
|
||||||
|
orderBy: {
|
||||||
|
dateTime: 'asc',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
findPlannedTimes(): Promise<PlannedTimeStaging[]> {
|
||||||
|
return this.prisma.plannedTimeStaging.findMany({
|
||||||
|
orderBy: {
|
||||||
|
startDate: 'asc',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
findWorkTimes(): Promise<WorkTimeStaging[]> {
|
||||||
|
return this.prisma.workTimeStaging.findMany({
|
||||||
|
orderBy: {
|
||||||
|
startDate: 'asc',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
import { parse, parseISO } from 'date-fns';
|
||||||
|
import { fromZonedTime } from 'date-fns-tz';
|
||||||
|
|
||||||
|
export const parseDateTime = (format: string) => (value: string) =>
|
||||||
|
parse(value, format, new Date());
|
||||||
|
export const parseIsoDateTime = (value: string) => parseISO(value);
|
||||||
|
|
||||||
|
export const parseZonedDateTime =
|
||||||
|
(tz: string) => (format: string) => (value: string) =>
|
||||||
|
fromZonedTime(parse(value, format, new Date()), tz);
|
||||||
|
|
||||||
|
export const parseCETDateTime = parseZonedDateTime('Europe/Berlin');
|
||||||
|
|
||||||
|
export const parseInteger = (value = '') => parseInt(value);
|
||||||
|
|
||||||
|
export const parseFloat = (value = '') =>
|
||||||
|
Number((value ?? '').replace(',', '.'));
|
||||||
|
|
||||||
|
export const parseBoolean = (value = '') => value === '1' || value === 'true';
|
||||||
|
|
||||||
|
export const pick =
|
||||||
|
<T extends Record<string, T[keyof T]>>(keys: (keyof T)[]) =>
|
||||||
|
(value: Record<keyof T, T[keyof T]>) =>
|
||||||
|
keys.reduce(
|
||||||
|
(obj, key) => {
|
||||||
|
if (value && Object.prototype.hasOwnProperty.call(value, key)) {
|
||||||
|
obj[key] = value[key];
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
},
|
||||||
|
{} as Record<keyof T, T[keyof T]>,
|
||||||
|
);
|
||||||
|
|
||||||
|
export const pick2 = <T extends Record<string, unknown>>(
|
||||||
|
keys: (keyof T)[],
|
||||||
|
value: T,
|
||||||
|
) =>
|
||||||
|
keys.reduce((obj, key) => {
|
||||||
|
if (value && Object.prototype.hasOwnProperty.call(value, key)) {
|
||||||
|
obj[key] = value[key];
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}, {} as T);
|
||||||
|
|
||||||
|
export const transformProperties =
|
||||||
|
<T extends string, U>(transformer: (value: T) => U) =>
|
||||||
|
(value: Record<string, T>) =>
|
||||||
|
Object.entries(value).reduce(
|
||||||
|
(obj, [key, value]) => ({
|
||||||
|
...obj,
|
||||||
|
[key]: transformer(value),
|
||||||
|
}),
|
||||||
|
{} as Record<string, U>,
|
||||||
|
);
|
||||||
|
|
@ -0,0 +1,149 @@
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import {
|
||||||
|
AttendanceRegistrationStaging,
|
||||||
|
EmployeeDispoLiveSource,
|
||||||
|
EmployeeDyflexisSource,
|
||||||
|
PatientStaging,
|
||||||
|
PlannedTimeStaging,
|
||||||
|
Prisma,
|
||||||
|
TourStaging,
|
||||||
|
WorkTimeStaging,
|
||||||
|
} from '@prisma/client';
|
||||||
|
import { combineLatest, from, map, switchMap } from 'rxjs';
|
||||||
|
|
||||||
|
import { ExtractorsService } from '../feat-extractors/extractors.service';
|
||||||
|
// import { ToursCalculationAdapater } from './adapter/tours-calculation.adapter';
|
||||||
|
import { StagingRepository } from './staging.repository';
|
||||||
|
import { AttendanceRegistrationTransformer } from './transformers/attendance-registration.transformer';
|
||||||
|
import { EmployeeTransformer } from './transformers/employee.transformer';
|
||||||
|
import { PatientTransformer } from './transformers/patient.transformer';
|
||||||
|
import { PlannedTimeTransformer } from './transformers/planned-time.transformer';
|
||||||
|
import { TourTransformer } from './transformers/tour.transformer';
|
||||||
|
import { WorkTimeTransformer } from './transformers/work-time.transformer';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TransformersService {
|
||||||
|
constructor(
|
||||||
|
private readonly extractorsService: ExtractorsService,
|
||||||
|
private readonly repository: StagingRepository,
|
||||||
|
// private readonly tourCalculationAdapter: ToursCalculationAdapater,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
clearStaging(): Promise<Prisma.BatchPayload[]> {
|
||||||
|
return this.repository.clearStaging();
|
||||||
|
}
|
||||||
|
|
||||||
|
transformPlannedTimes(jobId: string) {
|
||||||
|
return from(this.extractorsService.findPlannedTimesByJobId(jobId)).pipe(
|
||||||
|
map((plannedTimes) =>
|
||||||
|
plannedTimes.map((plannedTime) =>
|
||||||
|
PlannedTimeTransformer.transform(plannedTime),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
switchMap((plannedTimes) =>
|
||||||
|
this.repository.createPlannedTimes(plannedTimes),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
transformWorkTimes(jobId: string) {
|
||||||
|
return from(this.extractorsService.findWorkTimesByJobId(jobId)).pipe(
|
||||||
|
map((workTimes) =>
|
||||||
|
workTimes.map((workTime) => WorkTimeTransformer.transform(workTime)),
|
||||||
|
),
|
||||||
|
switchMap((workTimes) => this.repository.createWorkTimes(workTimes)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
transformAttendanceRegistrations(jobId: string) {
|
||||||
|
return from(
|
||||||
|
this.extractorsService.findAttendanceRegistrationsByJobId(jobId),
|
||||||
|
).pipe(
|
||||||
|
map((attendanceRegistrations) =>
|
||||||
|
attendanceRegistrations.map((attendanceRegistration) =>
|
||||||
|
AttendanceRegistrationTransformer.transform(attendanceRegistration),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
switchMap((attendanceRegistrations) =>
|
||||||
|
this.repository.createAttendanceRegistrations(attendanceRegistrations),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
transformTours(jobId: string) {
|
||||||
|
return from(this.extractorsService.findToursByJobId(jobId)).pipe(
|
||||||
|
// TODO: Add validation filter
|
||||||
|
map((tours) =>
|
||||||
|
tours
|
||||||
|
.filter(({ einsatzNummer }) => !!einsatzNummer)
|
||||||
|
.map((tour) => TourTransformer.transform(tour)),
|
||||||
|
),
|
||||||
|
// switchMap((tours) =>
|
||||||
|
// from(
|
||||||
|
// this.tourCalculationAdapter.addRevenueAndConsumptionCostsToTours(
|
||||||
|
// tours,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
switchMap((tours) => this.repository.createTours(tours)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
transformEmployees(jobId: string) {
|
||||||
|
return combineLatest([
|
||||||
|
from(this.extractorsService.findEmployeesDispoLiveByJobId(jobId)),
|
||||||
|
from(this.extractorsService.findEmployeesDyflexisByJobId(jobId)),
|
||||||
|
]).pipe(
|
||||||
|
map(([dispoLiveEmployees, dyflexisEmployees]) =>
|
||||||
|
dyflexisEmployees
|
||||||
|
.map((dyflexisEmployee) => {
|
||||||
|
const dispoLiveEmployee = dispoLiveEmployees.find(
|
||||||
|
(e) => e.importPersId === dyflexisEmployee.id,
|
||||||
|
);
|
||||||
|
if (!dispoLiveEmployee) return undefined;
|
||||||
|
|
||||||
|
return [dyflexisEmployee, dispoLiveEmployee];
|
||||||
|
})
|
||||||
|
.filter(Boolean),
|
||||||
|
),
|
||||||
|
map((employees) =>
|
||||||
|
employees.map(([dyflexisEmployee, dispoLiveEmployee]) =>
|
||||||
|
EmployeeTransformer.transform(
|
||||||
|
dyflexisEmployee as EmployeeDyflexisSource,
|
||||||
|
dispoLiveEmployee as EmployeeDispoLiveSource,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
switchMap((employees) => this.repository.createEmployees(employees)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
transformPatients(jobId: string) {
|
||||||
|
return from(this.extractorsService.findPatientsByJobId(jobId)).pipe(
|
||||||
|
map((patients) =>
|
||||||
|
patients.map((patient) => PatientTransformer.transform(patient)),
|
||||||
|
),
|
||||||
|
switchMap((patients) => this.repository.createPatients(patients)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
findPatients(): Promise<PatientStaging[]> {
|
||||||
|
return this.repository.findPatients();
|
||||||
|
}
|
||||||
|
|
||||||
|
findTours(): Promise<TourStaging[]> {
|
||||||
|
return this.repository.findTours();
|
||||||
|
}
|
||||||
|
|
||||||
|
findAttendanceRegistrations(): Promise<AttendanceRegistrationStaging[]> {
|
||||||
|
return this.repository.findAttendanceRegistrations();
|
||||||
|
}
|
||||||
|
|
||||||
|
findPlannedTimes(): Promise<PlannedTimeStaging[]> {
|
||||||
|
return this.repository.findPlannedTimes();
|
||||||
|
}
|
||||||
|
|
||||||
|
findWorkTimes(): Promise<WorkTimeStaging[]> {
|
||||||
|
return this.repository.findWorkTimes();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
import { AttendanceRegistrationSource } from '@prisma/client';
|
||||||
|
import { AttendanceRegistrationPreStaging } from '../staging.interface';
|
||||||
|
import {
|
||||||
|
parseInteger,
|
||||||
|
parseIsoDateTime,
|
||||||
|
pick,
|
||||||
|
transformProperties,
|
||||||
|
} from '../transformer.functions';
|
||||||
|
|
||||||
|
export class AttendanceRegistrationTransformer {
|
||||||
|
public static transform(
|
||||||
|
attendanceRegistrationSource: AttendanceRegistrationSource,
|
||||||
|
): AttendanceRegistrationPreStaging {
|
||||||
|
const attendanceRegistrationParsed = {
|
||||||
|
...attendanceRegistrationSource,
|
||||||
|
...transformProperties(parseInteger)(
|
||||||
|
pick(['attendanceRegistrationId', 'employeeId'])(
|
||||||
|
attendanceRegistrationSource,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
...transformProperties(parseIsoDateTime)(
|
||||||
|
pick(['dateTime'])(attendanceRegistrationSource),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
const { attendanceRegistrationId, employeeId, dateTime, event } =
|
||||||
|
attendanceRegistrationParsed;
|
||||||
|
|
||||||
|
return {
|
||||||
|
attendanceRegistrationId,
|
||||||
|
employeeId,
|
||||||
|
dateTime,
|
||||||
|
event,
|
||||||
|
} as unknown as AttendanceRegistrationPreStaging;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
import {
|
||||||
|
EmployeeDispoLiveSource,
|
||||||
|
EmployeeDyflexisSource,
|
||||||
|
} from '@prisma/client';
|
||||||
|
|
||||||
|
import { isValidDate } from 'src/utils';
|
||||||
|
import { EmployeePreStaging } from '../staging.interface';
|
||||||
|
import {
|
||||||
|
parseDateTime,
|
||||||
|
parseFloat,
|
||||||
|
parseInteger,
|
||||||
|
pick,
|
||||||
|
transformProperties,
|
||||||
|
} from '../transformer.functions';
|
||||||
|
|
||||||
|
export class EmployeeTransformer {
|
||||||
|
public static transform(
|
||||||
|
dyflexisEmployeeSource: EmployeeDyflexisSource,
|
||||||
|
dispoLiveEmployeeSource: EmployeeDispoLiveSource,
|
||||||
|
): EmployeePreStaging {
|
||||||
|
const dyflexisEmployeeParsed = {
|
||||||
|
...dyflexisEmployeeSource,
|
||||||
|
...transformProperties(parseFloat)(
|
||||||
|
pick(['contract_hours_week', 'contract_salary_hour'])(
|
||||||
|
dyflexisEmployeeSource,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
...transformProperties(parseInteger)(
|
||||||
|
pick(['id'])(dyflexisEmployeeSource),
|
||||||
|
),
|
||||||
|
...transformProperties(parseDateTime('yyyy-MM-dd'))(
|
||||||
|
pick(['contract_start', 'contract_end'])(dyflexisEmployeeSource),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
const dispoLiveEmployeeParsed = {
|
||||||
|
...dispoLiveEmployeeSource,
|
||||||
|
...transformProperties(parseDateTime('yyyy-MM-dd'))(
|
||||||
|
pick(['pScheinAblaufDatum'])(dispoLiveEmployeeSource),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
firstname,
|
||||||
|
surname,
|
||||||
|
contract_start,
|
||||||
|
contract_end,
|
||||||
|
contract_hours_week,
|
||||||
|
contract_salary_hour,
|
||||||
|
} = dyflexisEmployeeParsed;
|
||||||
|
const { pScheinAblaufDatum, qualifikation } = dispoLiveEmployeeParsed;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
firstname,
|
||||||
|
surname,
|
||||||
|
contractStart: contract_start,
|
||||||
|
contractEnd: contract_end,
|
||||||
|
contractHoursWeek: contract_hours_week,
|
||||||
|
contractSalaryHour: contract_salary_hour,
|
||||||
|
personTransportCertificate: isValidDate(new Date(pScheinAblaufDatum))
|
||||||
|
? pScheinAblaufDatum
|
||||||
|
: undefined,
|
||||||
|
qualification: qualifikation,
|
||||||
|
} as unknown as EmployeePreStaging;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
import { PatientSource } from '@prisma/client';
|
||||||
|
import { isValidDate } from 'src/utils';
|
||||||
|
import { PatientPreStaging } from '../staging.interface';
|
||||||
|
import {
|
||||||
|
parseDateTime,
|
||||||
|
pick,
|
||||||
|
transformProperties,
|
||||||
|
} from '../transformer.functions';
|
||||||
|
|
||||||
|
export class PatientTransformer {
|
||||||
|
public static transform(patientSource: PatientSource): PatientPreStaging {
|
||||||
|
const patientParsed = {
|
||||||
|
...patientSource,
|
||||||
|
...transformProperties(parseDateTime('yyyy-MM-dd'))(
|
||||||
|
pick(['birthday'])(patientSource),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
surname,
|
||||||
|
name,
|
||||||
|
street,
|
||||||
|
zip,
|
||||||
|
city,
|
||||||
|
|
||||||
|
birthday,
|
||||||
|
kkId,
|
||||||
|
krankenkasse,
|
||||||
|
pflegestufe,
|
||||||
|
behinderung1,
|
||||||
|
behinderung2,
|
||||||
|
} = patientParsed;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
surname,
|
||||||
|
name,
|
||||||
|
street,
|
||||||
|
zip,
|
||||||
|
city,
|
||||||
|
birthday: isValidDate(new Date(birthday)) ? birthday : undefined,
|
||||||
|
healthinsurance: krankenkasse,
|
||||||
|
healthinsuranceId: kkId,
|
||||||
|
careDegree: pflegestufe,
|
||||||
|
disabilityMark1: behinderung1,
|
||||||
|
disabilityMark2: behinderung2,
|
||||||
|
} as unknown as PatientPreStaging;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
import { PlannedTimeSource } from '@prisma/client';
|
||||||
|
import { PlannedTimePreStaging } from '../staging.interface';
|
||||||
|
import {
|
||||||
|
parseBoolean,
|
||||||
|
parseCETDateTime,
|
||||||
|
parseInteger,
|
||||||
|
pick,
|
||||||
|
transformProperties,
|
||||||
|
} from '../transformer.functions';
|
||||||
|
|
||||||
|
export class PlannedTimeTransformer {
|
||||||
|
public static transform(
|
||||||
|
plannedTimeSource: PlannedTimeSource,
|
||||||
|
): PlannedTimePreStaging {
|
||||||
|
const plannedTimeParsed = {
|
||||||
|
...plannedTimeSource,
|
||||||
|
...transformProperties(parseInteger)(
|
||||||
|
pick(['id', 'user_id'])(plannedTimeSource),
|
||||||
|
),
|
||||||
|
...transformProperties(parseCETDateTime('yyyy-MM-dd HH:mm:ss'))(
|
||||||
|
pick(['start_date', 'end_date'])(plannedTimeSource),
|
||||||
|
),
|
||||||
|
...transformProperties(parseBoolean)(
|
||||||
|
pick(['deleted'])(plannedTimeSource),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
user_id,
|
||||||
|
firstname,
|
||||||
|
surname,
|
||||||
|
department_name,
|
||||||
|
start_date,
|
||||||
|
end_date,
|
||||||
|
deleted,
|
||||||
|
mark,
|
||||||
|
} = plannedTimeParsed;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
firstname,
|
||||||
|
surname,
|
||||||
|
deleted,
|
||||||
|
mark,
|
||||||
|
employeeId: user_id,
|
||||||
|
departmentName: department_name,
|
||||||
|
startDate: start_date,
|
||||||
|
endDate: end_date,
|
||||||
|
} as unknown as PlannedTimePreStaging;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,142 @@
|
||||||
|
import { TourSource } from '@prisma/client';
|
||||||
|
import { TourPreStaging } from '../staging.interface';
|
||||||
|
import {
|
||||||
|
parseBoolean,
|
||||||
|
parseCETDateTime,
|
||||||
|
parseDateTime,
|
||||||
|
parseFloat,
|
||||||
|
parseInteger,
|
||||||
|
parseIsoDateTime,
|
||||||
|
pick,
|
||||||
|
transformProperties,
|
||||||
|
} from '../transformer.functions';
|
||||||
|
|
||||||
|
export class TourTransformer {
|
||||||
|
public static transform(tourSource: TourSource): TourPreStaging {
|
||||||
|
const tourParsed = {
|
||||||
|
...tourSource,
|
||||||
|
...transformProperties(parseInteger)(pick(['check'])(tourSource)),
|
||||||
|
...transformProperties(parseFloat)(
|
||||||
|
pick(['besetztKm', 'gesamtPreis'])(tourSource),
|
||||||
|
),
|
||||||
|
...transformProperties(parseIsoDateTime)(pick(['date_iso'])(tourSource)),
|
||||||
|
...(tourSource.abrDate
|
||||||
|
? transformProperties(parseDateTime('yyyy-MM-dd'))(
|
||||||
|
pick(['abrDate'])(tourSource),
|
||||||
|
)
|
||||||
|
: undefined),
|
||||||
|
...(tourSource.endgen
|
||||||
|
? transformProperties(parseDateTime('yyyy-MM-dd'))(
|
||||||
|
pick(['endgen'])(tourSource),
|
||||||
|
)
|
||||||
|
: undefined),
|
||||||
|
...(tourSource.createTime.length > 10
|
||||||
|
? transformProperties(parseCETDateTime('yyyy-MM-dd HH:mm:ss'))(
|
||||||
|
pick(['createTime'])(tourSource),
|
||||||
|
)
|
||||||
|
: transformProperties(parseCETDateTime('yyyy-MM-dd'))(
|
||||||
|
pick(['createTime'])(tourSource),
|
||||||
|
)),
|
||||||
|
...transformProperties(parseBoolean)(
|
||||||
|
pick(['infektion', 'gefahren'])(tourSource),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
gefahren,
|
||||||
|
direction,
|
||||||
|
carName,
|
||||||
|
einsatzNummer,
|
||||||
|
fahrer,
|
||||||
|
fahrerName,
|
||||||
|
beifahrer,
|
||||||
|
beifahrerName,
|
||||||
|
patId,
|
||||||
|
patName,
|
||||||
|
patSurname,
|
||||||
|
patStreet,
|
||||||
|
patZip,
|
||||||
|
patCity,
|
||||||
|
krankenkasse,
|
||||||
|
kkId,
|
||||||
|
type,
|
||||||
|
serienKategorName,
|
||||||
|
transportKrz,
|
||||||
|
verordnungsName,
|
||||||
|
endgen,
|
||||||
|
startInstitution,
|
||||||
|
startStreet,
|
||||||
|
startZip,
|
||||||
|
startCity,
|
||||||
|
targetInstitution,
|
||||||
|
targetStreet,
|
||||||
|
targetZip,
|
||||||
|
targetCity,
|
||||||
|
date_iso,
|
||||||
|
check,
|
||||||
|
besetztKm,
|
||||||
|
startBegin,
|
||||||
|
startEnd,
|
||||||
|
ziel,
|
||||||
|
zielBegin,
|
||||||
|
frei,
|
||||||
|
infektion,
|
||||||
|
itKrz,
|
||||||
|
gesamtPreis,
|
||||||
|
abrDate,
|
||||||
|
abrNum,
|
||||||
|
createTime,
|
||||||
|
} = tourParsed;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
direction,
|
||||||
|
carName,
|
||||||
|
startInstitution,
|
||||||
|
startStreet,
|
||||||
|
startZip,
|
||||||
|
startCity,
|
||||||
|
targetInstitution,
|
||||||
|
targetStreet,
|
||||||
|
targetZip,
|
||||||
|
targetCity,
|
||||||
|
check,
|
||||||
|
type,
|
||||||
|
done: gefahren,
|
||||||
|
operationId: einsatzNummer,
|
||||||
|
driverId: fahrer,
|
||||||
|
driverName: fahrerName,
|
||||||
|
codriverId: beifahrer,
|
||||||
|
codriverName: beifahrerName,
|
||||||
|
patientId: patId,
|
||||||
|
patientName: patName,
|
||||||
|
patientSurname: patSurname,
|
||||||
|
patientStreet: patStreet,
|
||||||
|
patientZip: patZip,
|
||||||
|
patientCity: patCity,
|
||||||
|
healthInsurance: krankenkasse,
|
||||||
|
healthInsuranceNumber: kkId,
|
||||||
|
category: serienKategorName,
|
||||||
|
transportType: transportKrz,
|
||||||
|
ordinanceType: verordnungsName,
|
||||||
|
rangeEndDate: endgen,
|
||||||
|
startDate: date_iso,
|
||||||
|
occupiedKm: besetztKm,
|
||||||
|
totalKm: besetztKm,
|
||||||
|
startBegin,
|
||||||
|
startEnd,
|
||||||
|
target: ziel,
|
||||||
|
targetBegin: zielBegin,
|
||||||
|
empty: frei,
|
||||||
|
hasInfection: infektion,
|
||||||
|
infectionName: itKrz,
|
||||||
|
revenueDispoLive: gesamtPreis,
|
||||||
|
billDate: abrDate,
|
||||||
|
billNumber: abrNum,
|
||||||
|
createdAt: createTime,
|
||||||
|
revenue: 0,
|
||||||
|
consumptionCosts: 0,
|
||||||
|
} as unknown as TourPreStaging;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
import { WorkTimeSource } from '@prisma/client';
|
||||||
|
import { WorkTimePreStaging } from '../staging.interface';
|
||||||
|
import {
|
||||||
|
parseCETDateTime,
|
||||||
|
parseInteger,
|
||||||
|
pick,
|
||||||
|
transformProperties,
|
||||||
|
} from '../transformer.functions';
|
||||||
|
|
||||||
|
export class WorkTimeTransformer {
|
||||||
|
public static transform(workTimeSource: WorkTimeSource): WorkTimePreStaging {
|
||||||
|
const workTimeParsed = {
|
||||||
|
...workTimeSource,
|
||||||
|
...transformProperties(parseInteger)(
|
||||||
|
pick(['id', 'user_id'])(workTimeSource),
|
||||||
|
),
|
||||||
|
...transformProperties(parseCETDateTime('yyyy-MM-dd HH:mm:ss'))(
|
||||||
|
pick(['start_date', 'end_date'])(workTimeSource),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
user_id,
|
||||||
|
firstname,
|
||||||
|
surname,
|
||||||
|
department_name,
|
||||||
|
start_date,
|
||||||
|
end_date,
|
||||||
|
} = workTimeParsed;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
firstname,
|
||||||
|
surname,
|
||||||
|
employeeId: user_id,
|
||||||
|
departmentName: department_name,
|
||||||
|
startDate: start_date,
|
||||||
|
endDate: end_date,
|
||||||
|
} as unknown as WorkTimePreStaging;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { chunk } from './chunk';
|
||||||
|
import { range } from './range';
|
||||||
|
|
||||||
|
describe('chunk', () => {
|
||||||
|
it('should split an array with 10 elements into two chunks with 5 elements', () => {
|
||||||
|
const given = range(10);
|
||||||
|
const when = chunk(given, 5);
|
||||||
|
expect(when).toHaveLength(2);
|
||||||
|
when.map((sub) => expect(sub).toHaveLength(5));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should split an array with 8 elements into two chunks, first with 5 elements, second with 3', () => {
|
||||||
|
const given = range(8);
|
||||||
|
const when = chunk(given, 5);
|
||||||
|
expect(when).toHaveLength(2);
|
||||||
|
expect(when[0]).toHaveLength(5);
|
||||||
|
expect(when[1]).toHaveLength(3);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
export function chunk<T>(input: T[], size: number): T[][] {
|
||||||
|
return input.reduce(
|
||||||
|
(array, item, index) =>
|
||||||
|
index % size === 0
|
||||||
|
? [...array, [item]]
|
||||||
|
: [...array.slice(0, -1), [...array.slice(-1)[0], item]],
|
||||||
|
[] as T[][]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
export * from './chunk';
|
||||||
|
export * from './is-valid-date';
|
||||||
|
export * from './range';
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { isValidDate } from './is-valid-date';
|
||||||
|
|
||||||
|
describe('isValidDate', () => {
|
||||||
|
it('should return false if date is invalid', () => {
|
||||||
|
const given = new Date(NaN);
|
||||||
|
expect(isValidDate(given)).toBe(false);
|
||||||
|
});
|
||||||
|
it('should return true if date is valid', () => {
|
||||||
|
const given = new Date();
|
||||||
|
expect(isValidDate(given)).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
export function isValidDate(date: Date): boolean {
|
||||||
|
return date instanceof Date && !isNaN(date.getTime());
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { range } from './range';
|
||||||
|
|
||||||
|
describe('range', () => {
|
||||||
|
it('should generate an array with the supplied length and numbers ascending from 0', () => {
|
||||||
|
const when = range(10);
|
||||||
|
expect(when).toHaveLength(10);
|
||||||
|
when.map((item, index) => expect(item).toBe(index));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
export function range(length: number): number[] {
|
||||||
|
return Array.from({ length }, (_, i) => i);
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue