Signed-off-by: Denis Bykhov <80476319+BykhovDenis@users.noreply.github.com>
This commit is contained in:
Denis Bykhov 2022-06-20 10:24:08 +06:00 committed by GitHub
parent da07828036
commit 5e38409a88
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
74 changed files with 2222 additions and 55 deletions

View File

@ -41,6 +41,9 @@ specifiers:
'@rush-temp/gmail': file:./projects/gmail.tgz
'@rush-temp/gmail-assets': file:./projects/gmail-assets.tgz
'@rush-temp/gmail-resources': file:./projects/gmail-resources.tgz
'@rush-temp/hr': file:./projects/hr.tgz
'@rush-temp/hr-assets': file:./projects/hr-assets.tgz
'@rush-temp/hr-resources': file:./projects/hr-resources.tgz
'@rush-temp/image-cropper': file:./projects/image-cropper.tgz
'@rush-temp/image-cropper-resources': file:./projects/image-cropper-resources.tgz
'@rush-temp/inventory': file:./projects/inventory.tgz
@ -65,6 +68,7 @@ specifiers:
'@rush-temp/model-core': file:./projects/model-core.tgz
'@rush-temp/model-demo': file:./projects/model-demo.tgz
'@rush-temp/model-gmail': file:./projects/model-gmail.tgz
'@rush-temp/model-hr': file:./projects/model-hr.tgz
'@rush-temp/model-inventory': file:./projects/model-inventory.tgz
'@rush-temp/model-lead': file:./projects/model-lead.tgz
'@rush-temp/model-notification': file:./projects/model-notification.tgz
@ -79,6 +83,7 @@ specifiers:
'@rush-temp/model-server-contact': file:./projects/model-server-contact.tgz
'@rush-temp/model-server-core': file:./projects/model-server-core.tgz
'@rush-temp/model-server-gmail': file:./projects/model-server-gmail.tgz
'@rush-temp/model-server-hr': file:./projects/model-server-hr.tgz
'@rush-temp/model-server-inventory': file:./projects/model-server-inventory.tgz
'@rush-temp/model-server-lead': file:./projects/model-server-lead.tgz
'@rush-temp/model-server-notification': file:./projects/model-server-notification.tgz
@ -131,6 +136,8 @@ specifiers:
'@rush-temp/server-core': file:./projects/server-core.tgz
'@rush-temp/server-gmail': file:./projects/server-gmail.tgz
'@rush-temp/server-gmail-resources': file:./projects/server-gmail-resources.tgz
'@rush-temp/server-hr': file:./projects/server-hr.tgz
'@rush-temp/server-hr-resources': file:./projects/server-hr-resources.tgz
'@rush-temp/server-inventory': file:./projects/server-inventory.tgz
'@rush-temp/server-inventory-resources': file:./projects/server-inventory-resources.tgz
'@rush-temp/server-lead': file:./projects/server-lead.tgz
@ -331,6 +338,9 @@ dependencies:
'@rush-temp/gmail': file:projects/gmail.tgz
'@rush-temp/gmail-assets': file:projects/gmail-assets.tgz
'@rush-temp/gmail-resources': file:projects/gmail-resources.tgz_1e3963ebf0ceeb25b2fa6a1cc87e253c
'@rush-temp/hr': file:projects/hr.tgz
'@rush-temp/hr-assets': file:projects/hr-assets.tgz_typescript@4.7.2
'@rush-temp/hr-resources': file:projects/hr-resources.tgz_1e3963ebf0ceeb25b2fa6a1cc87e253c
'@rush-temp/image-cropper': file:projects/image-cropper.tgz
'@rush-temp/image-cropper-resources': file:projects/image-cropper-resources.tgz_1e3963ebf0ceeb25b2fa6a1cc87e253c
'@rush-temp/inventory': file:projects/inventory.tgz
@ -355,6 +365,7 @@ dependencies:
'@rush-temp/model-core': file:projects/model-core.tgz_typescript@4.7.2
'@rush-temp/model-demo': file:projects/model-demo.tgz_typescript@4.7.2
'@rush-temp/model-gmail': file:projects/model-gmail.tgz_typescript@4.7.2
'@rush-temp/model-hr': file:projects/model-hr.tgz_typescript@4.7.2
'@rush-temp/model-inventory': file:projects/model-inventory.tgz_typescript@4.7.2
'@rush-temp/model-lead': file:projects/model-lead.tgz_typescript@4.7.2
'@rush-temp/model-notification': file:projects/model-notification.tgz_typescript@4.7.2
@ -369,6 +380,7 @@ dependencies:
'@rush-temp/model-server-contact': file:projects/model-server-contact.tgz_typescript@4.7.2
'@rush-temp/model-server-core': file:projects/model-server-core.tgz_typescript@4.7.2
'@rush-temp/model-server-gmail': file:projects/model-server-gmail.tgz_typescript@4.7.2
'@rush-temp/model-server-hr': file:projects/model-server-hr.tgz_typescript@4.7.2
'@rush-temp/model-server-inventory': file:projects/model-server-inventory.tgz_typescript@4.7.2
'@rush-temp/model-server-lead': file:projects/model-server-lead.tgz_typescript@4.7.2
'@rush-temp/model-server-notification': file:projects/model-server-notification.tgz_typescript@4.7.2
@ -421,6 +433,8 @@ dependencies:
'@rush-temp/server-core': file:projects/server-core.tgz
'@rush-temp/server-gmail': file:projects/server-gmail.tgz
'@rush-temp/server-gmail-resources': file:projects/server-gmail-resources.tgz
'@rush-temp/server-hr': file:projects/server-hr.tgz
'@rush-temp/server-hr-resources': file:projects/server-hr-resources.tgz
'@rush-temp/server-inventory': file:projects/server-inventory.tgz
'@rush-temp/server-inventory-resources': file:projects/server-inventory-resources.tgz
'@rush-temp/server-lead': file:projects/server-lead.tgz
@ -10925,6 +10939,89 @@ packages:
- supports-color
dev: false
file:projects/hr-assets.tgz_typescript@4.7.2:
resolution: {integrity: sha512-Or7JUoPjJeVKIbJt6RExTLlWhvCJxWe1dZBA0332S7HIUgWDnev8Qx0EzBxV1FKXne/XV1w8ZEusJXPReGz3mw==, tarball: file:projects/hr-assets.tgz}
id: file:projects/hr-assets.tgz
name: '@rush-temp/hr-assets'
version: 0.0.0
dependencies:
'@rushstack/heft': 0.45.5
'@types/heft-jest': 1.0.2
'@types/node': 16.11.38
'@typescript-eslint/eslint-plugin': 5.27.0_738fa17fa57f8a69ace69c90e5cfa1d5
'@typescript-eslint/parser': 5.27.0_eslint@7.32.0+typescript@4.7.2
eslint: 7.32.0
eslint-config-standard-with-typescript: 21.0.1_99a5fe2f2ae1dc64d6b59974c931eb2a
eslint-plugin-import: 2.26.0_c21022bc9feaeb7b200d3d631eeae46c
eslint-plugin-node: 11.1.0_eslint@7.32.0
eslint-plugin-promise: 5.2.0_eslint@7.32.0
prettier: 2.6.2
transitivePeerDependencies:
- eslint-import-resolver-typescript
- eslint-import-resolver-webpack
- supports-color
- typescript
dev: false
file:projects/hr-resources.tgz_1e3963ebf0ceeb25b2fa6a1cc87e253c:
resolution: {integrity: sha512-sYnkYAs2h/gH6VD8c6+QBHMemRZfIMVthpYzi1+TXliv/EmebzxdFyPCPjLbxgQxpq4mUwuMLlAusMIrmpVNrg==, tarball: file:projects/hr-resources.tgz}
id: file:projects/hr-resources.tgz
name: '@rush-temp/hr-resources'
version: 0.0.0
dependencies:
'@typescript-eslint/eslint-plugin': 5.27.0_738fa17fa57f8a69ace69c90e5cfa1d5
'@typescript-eslint/parser': 5.27.0_eslint@7.32.0+typescript@4.7.2
eslint: 7.32.0
eslint-config-standard-with-typescript: 21.0.1_99a5fe2f2ae1dc64d6b59974c931eb2a
eslint-plugin-import: 2.26.0_c21022bc9feaeb7b200d3d631eeae46c
eslint-plugin-node: 11.1.0_eslint@7.32.0
eslint-plugin-promise: 5.2.0_eslint@7.32.0
eslint-plugin-svelte3: 4.0.0_eslint@7.32.0+svelte@3.48.0
prettier: 2.6.2
prettier-plugin-svelte: 2.7.0_prettier@2.6.2+svelte@3.48.0
sass: 1.52.2
svelte: 3.48.0
svelte-check: 2.7.2_c1788f0bf13b393830d6c30602bd01af
svelte-loader: 3.1.3_svelte@3.48.0
svelte-preprocess: 4.10.6_0757fe126296bf9639251bcd13609b29
typescript: 4.7.2
transitivePeerDependencies:
- '@babel/core'
- coffeescript
- eslint-import-resolver-typescript
- eslint-import-resolver-webpack
- less
- node-sass
- postcss
- postcss-load-config
- pug
- stylus
- sugarss
- supports-color
dev: false
file:projects/hr.tgz:
resolution: {integrity: sha512-8jSKJNCIhxfPjP8hislK8NLocwO4VwDJ/jjYzq6tlRwgiI3pMAuftX/NsZEKARgHcHebssy63acqJNImdp2ALQ==, tarball: file:projects/hr.tgz}
name: '@rush-temp/hr'
version: 0.0.0
dependencies:
'@rushstack/heft': 0.45.5
'@types/heft-jest': 1.0.2
'@typescript-eslint/eslint-plugin': 5.27.0_738fa17fa57f8a69ace69c90e5cfa1d5
'@typescript-eslint/parser': 5.27.0_eslint@7.32.0+typescript@4.7.2
eslint: 7.32.0
eslint-config-standard-with-typescript: 21.0.1_99a5fe2f2ae1dc64d6b59974c931eb2a
eslint-plugin-import: 2.26.0_c21022bc9feaeb7b200d3d631eeae46c
eslint-plugin-node: 11.1.0_eslint@7.32.0
eslint-plugin-promise: 5.2.0_eslint@7.32.0
prettier: 2.6.2
typescript: 4.7.2
transitivePeerDependencies:
- eslint-import-resolver-typescript
- eslint-import-resolver-webpack
- supports-color
dev: false
file:projects/image-cropper-resources.tgz_1e3963ebf0ceeb25b2fa6a1cc87e253c:
resolution: {integrity: sha512-8whE1ZAsPDruwYl6MT6HEWi5zFPGLg3fsYmvdDL2cx1KmDH421bm+M5kkq+Ok5HUV8jYoQRK00zvX8Awz4e+lQ==, tarball: file:projects/image-cropper-resources.tgz}
id: file:projects/image-cropper-resources.tgz
@ -11318,7 +11415,7 @@ packages:
dev: false
file:projects/model-all.tgz_typescript@4.7.2:
resolution: {integrity: sha512-3KLZ8VdyN6qL7q5SOOeD9sU9xUoDeqJRxAbyLtFhAgaTU1tX9n3C+cnmlDZhXq8kcK/Ss/xK2hrHfq0kOI7ILw==, tarball: file:projects/model-all.tgz}
resolution: {integrity: sha512-VrR3QpQKo2qizq7xi/uS9Jj+OyrB/mPDLnAc8/pvwEx8jF5PwySSsP63uSNGcO3fwv7ZjZvAiuvEnaIHUZ5Ivg==, tarball: file:projects/model-all.tgz}
id: file:projects/model-all.tgz
name: '@rush-temp/model-all'
version: 0.0.0
@ -11528,6 +11625,29 @@ packages:
- typescript
dev: false
file:projects/model-hr.tgz_typescript@4.7.2:
resolution: {integrity: sha512-HtAgUigvoyvjSpGUHwcRfTqM+RpMKyvDrieHpRBIvFans/2Kg3FPlacTHa9vhBilcze0pNBDrlSXyGuKMBLl8g==, tarball: file:projects/model-hr.tgz}
id: file:projects/model-hr.tgz
name: '@rush-temp/model-hr'
version: 0.0.0
dependencies:
'@rushstack/heft': 0.45.5
'@types/heft-jest': 1.0.2
'@typescript-eslint/eslint-plugin': 5.27.0_738fa17fa57f8a69ace69c90e5cfa1d5
'@typescript-eslint/parser': 5.27.0_eslint@7.32.0+typescript@4.7.2
eslint: 7.32.0
eslint-config-standard-with-typescript: 21.0.1_99a5fe2f2ae1dc64d6b59974c931eb2a
eslint-plugin-import: 2.26.0_c21022bc9feaeb7b200d3d631eeae46c
eslint-plugin-node: 11.1.0_eslint@7.32.0
eslint-plugin-promise: 5.2.0_eslint@7.32.0
prettier: 2.6.2
transitivePeerDependencies:
- eslint-import-resolver-typescript
- eslint-import-resolver-webpack
- supports-color
- typescript
dev: false
file:projects/model-inventory.tgz_typescript@4.7.2:
resolution: {integrity: sha512-ZrDM3hzsPJZPI1EGZpP4sEkEnJmmfzW77c2g7JejOCROBy6gd7237XN/SPjPk85CcSg7iHyueJiG9QGgoRB5SQ==, tarball: file:projects/model-inventory.tgz}
id: file:projects/model-inventory.tgz
@ -11847,6 +11967,29 @@ packages:
- typescript
dev: false
file:projects/model-server-hr.tgz_typescript@4.7.2:
resolution: {integrity: sha512-jSk/2YD9iHX6gPWUlc+RstSQVCpJGeoEsRNyaI3MMPtwytRCNa6Bdp1FjwFr3WId2wFQ+eHdpg9EtGr5ooGhUg==, tarball: file:projects/model-server-hr.tgz}
id: file:projects/model-server-hr.tgz
name: '@rush-temp/model-server-hr'
version: 0.0.0
dependencies:
'@rushstack/heft': 0.45.5
'@types/heft-jest': 1.0.2
'@typescript-eslint/eslint-plugin': 5.27.0_738fa17fa57f8a69ace69c90e5cfa1d5
'@typescript-eslint/parser': 5.27.0_eslint@7.32.0+typescript@4.7.2
eslint: 7.32.0
eslint-config-standard-with-typescript: 21.0.1_99a5fe2f2ae1dc64d6b59974c931eb2a
eslint-plugin-import: 2.26.0_c21022bc9feaeb7b200d3d631eeae46c
eslint-plugin-node: 11.1.0_eslint@7.32.0
eslint-plugin-promise: 5.2.0_eslint@7.32.0
prettier: 2.6.2
transitivePeerDependencies:
- eslint-import-resolver-typescript
- eslint-import-resolver-webpack
- supports-color
- typescript
dev: false
file:projects/model-server-inventory.tgz_typescript@4.7.2:
resolution: {integrity: sha512-fEZjpHFjw7k95RXySO8e9RpoaicF9jI2fVN9SSDEFUSmBsUKz3yR1wa1JrALrsH0YjcKzL1dWQK6AIJj4PuMCA==, tarball: file:projects/model-server-inventory.tgz}
id: file:projects/model-server-inventory.tgz
@ -12193,7 +12336,7 @@ packages:
dev: false
file:projects/model-tracker.tgz_typescript@4.7.2:
resolution: {integrity: sha512-3giT5Ql8N6I6UaeU038P16HSmmhnQdy2eIYaKok1cF+AX0Z/0dXcR/bSvgQ88WJxMrtAsusZkOkF+PyvUUJOYg==, tarball: file:projects/model-tracker.tgz}
resolution: {integrity: sha512-3NvQ5qG0MQi1q/7nydGTluAJYjDlwLDG+AZXSG3I0tMEG5YpQguM399GXlGlWZzhFAc++KUK1nDR7JWtaPmXcA==, tarball: file:projects/model-tracker.tgz}
id: file:projects/model-tracker.tgz
name: '@rush-temp/model-tracker'
version: 0.0.0
@ -12679,7 +12822,7 @@ packages:
dev: false
file:projects/prod.tgz_d1c3762ecb2c185353d3f02936f6ec22:
resolution: {integrity: sha512-PehFsaG21mA1q8ou+ldHAfMX0O+nPv2zVXTuj/K5TPMZ/DEkCtQHZtaVElryGq4xD1PlMyaID/1mqppbYEpbPQ==, tarball: file:projects/prod.tgz}
resolution: {integrity: sha512-Ruj8noHdrzO1bGVEYxLSgza5VdloFea3VbtIutq8UnZThqIzyBmJRsEo1lLmN5WAKBW50oEBKsKDZxuXB/ZuYQ==, tarball: file:projects/prod.tgz}
id: file:projects/prod.tgz
name: '@rush-temp/prod'
version: 0.0.0
@ -13182,6 +13325,51 @@ packages:
- supports-color
dev: false
file:projects/server-hr-resources.tgz:
resolution: {integrity: sha512-D073FS8B1EjB1D7fFgFm1Tdq6u69DaNcLVx65szIypERkUyFKyNr5KXqSyHSeDbwPWwz7nyOw2ecN8rlCPHnJg==, tarball: file:projects/server-hr-resources.tgz}
name: '@rush-temp/server-hr-resources'
version: 0.0.0
dependencies:
'@rushstack/heft': 0.45.5
'@types/heft-jest': 1.0.2
'@typescript-eslint/eslint-plugin': 5.27.0_738fa17fa57f8a69ace69c90e5cfa1d5
'@typescript-eslint/parser': 5.27.0_eslint@7.32.0+typescript@4.7.2
eslint: 7.32.0
eslint-config-standard-with-typescript: 21.0.1_99a5fe2f2ae1dc64d6b59974c931eb2a
eslint-plugin-import: 2.26.0_c21022bc9feaeb7b200d3d631eeae46c
eslint-plugin-node: 11.1.0_eslint@7.32.0
eslint-plugin-promise: 5.2.0_eslint@7.32.0
prettier: 2.6.2
typescript: 4.7.2
transitivePeerDependencies:
- eslint-import-resolver-typescript
- eslint-import-resolver-webpack
- supports-color
dev: false
file:projects/server-hr.tgz:
resolution: {integrity: sha512-VPn9I/49Q/15y/R3yinhsKHb5CwTxzHZFemEfj0b/sALUamdf3S3sJpdMorZkNI44hFOns6LzHRRKUf/ka9zOA==, tarball: file:projects/server-hr.tgz}
name: '@rush-temp/server-hr'
version: 0.0.0
dependencies:
'@rushstack/heft': 0.45.5
'@types/heft-jest': 1.0.2
'@types/node': 16.11.38
'@typescript-eslint/eslint-plugin': 5.27.0_738fa17fa57f8a69ace69c90e5cfa1d5
'@typescript-eslint/parser': 5.27.0_eslint@7.32.0+typescript@4.7.2
eslint: 7.32.0
eslint-config-standard-with-typescript: 21.0.1_99a5fe2f2ae1dc64d6b59974c931eb2a
eslint-plugin-import: 2.26.0_c21022bc9feaeb7b200d3d631eeae46c
eslint-plugin-node: 11.1.0_eslint@7.32.0
eslint-plugin-promise: 5.2.0_eslint@7.32.0
prettier: 2.6.2
typescript: 4.7.2
transitivePeerDependencies:
- eslint-import-resolver-typescript
- eslint-import-resolver-webpack
- supports-color
dev: false
file:projects/server-inventory-resources.tgz:
resolution: {integrity: sha512-QXv09ktLX1fL3VlWS5ofrZxuL21p9T0IWM0X+hLCpoqmxPA/Bhpi52CtRM/89DsgohhEPPpWNG9byQci8A5vOw==, tarball: file:projects/server-inventory-resources.tgz}
name: '@rush-temp/server-inventory-resources'
@ -13693,7 +13881,7 @@ packages:
dev: false
file:projects/server.tgz:
resolution: {integrity: sha512-YMw6QleaQlQlzkQhIncun4YVr77XcZSRLy8xzfJiy6X1PaBSve9WY9mGkKiiw119bo2/72HEHEHMnsHQU3VtSQ==, tarball: file:projects/server.tgz}
resolution: {integrity: sha512-xJg2GMws1k8PADTrUVUbp6eEynFM+iEVYmsaU5lWpPk2nz4MrDTnirbDITNKPbmKlTdODDWJSaT6zIjhi+GRMg==, tarball: file:projects/server.tgz}
name: '@rush-temp/server'
version: 0.0.0
dependencies:
@ -14252,7 +14440,7 @@ packages:
dev: false
file:projects/tool.tgz:
resolution: {integrity: sha512-cLyyAtdkGqG4pxA7QhbNhoNwhIoxpUktmo6OA8kJzcjisF23dXsuuDv097gM6Y0IOdIRGWET8XRaM1sIE/4ogQ==, tarball: file:projects/tool.tgz}
resolution: {integrity: sha512-gvdRnHRhzst5gkHEFg9GVc3D04b+BAks8qPdrVxHQSWvWiiUmh3kG+uNWQKklSa9rtRzlSHUrf6NvUvgEg2c5w==, tarball: file:projects/tool.tgz}
name: '@rush-temp/tool'
version: 0.0.0
dependencies:
@ -14321,7 +14509,7 @@ packages:
dev: false
file:projects/tracker-resources.tgz_1e3963ebf0ceeb25b2fa6a1cc87e253c:
resolution: {integrity: sha512-z3TfQ4BzAQK48+svzVwLJMfJP8kF7Skk6XgUYiW6NGYmWTsauxTAO43pbnH0+zrx112vGahLduOnBsYNURRLXQ==, tarball: file:projects/tracker-resources.tgz}
resolution: {integrity: sha512-bNqQvwzdPa61s2z+M0xrwW5bSvv46yBY8BXt6cqKEHwp1Zv78P5NHU/ScAgVRuJSvwknhrykBK1mK7zghMr3Hg==, tarball: file:projects/tracker-resources.tgz}
id: file:projects/tracker-resources.tgz
name: '@rush-temp/tracker-resources'
version: 0.0.0
@ -14358,7 +14546,7 @@ packages:
dev: false
file:projects/tracker.tgz:
resolution: {integrity: sha512-fj497PawVB5mdi7yIw96vQs81XXIcJ3SzrQe0UHyb4IqTbFCoN4/HHGnZ6JXrUY/wdPLHfJzUVMlZKZVKcjD6A==, tarball: file:projects/tracker.tgz}
resolution: {integrity: sha512-wGhZrDr9Vkg1fm9mUcBfkmQXKQlFpiUUOVIa3WEzoIlCzuAzSs4RiAk5XtVeryvQziu6G0dAzbOka9lV9Fxa+A==, tarball: file:projects/tracker.tgz}
name: '@rush-temp/tracker'
version: 0.0.0
dependencies:

View File

@ -144,6 +144,11 @@
"@anticrm/text-editor": "~0.6.0",
"@anticrm/board": "~0.6.0",
"@anticrm/board-assets": "~0.6.0",
"@anticrm/board-resources": "~0.6.0"
"@anticrm/board-resources": "~0.6.0",
"@anticrm/hr": "~0.6.0",
"@anticrm/hr-assets": "~0.6.0",
"@anticrm/hr-resources": "~0.6.0",
"@anticrm/server-hr": "~0.6.0",
"@anticrm/server-hr-resources": "~0.6.0"
}
}

View File

@ -32,7 +32,7 @@ import { serverTagsId } from '@anticrm/server-tags'
import { serverCalendarId } from '@anticrm/server-calendar'
import { serverGmailId } from '@anticrm/server-gmail'
import { serverTelegramId } from '@anticrm/server-telegram'
import { serverHrId } from '@anticrm/server-hr'
import { setMetadata } from '@anticrm/platform'
@ -60,6 +60,7 @@ export function configurePlatformDev() {
addLocation(serverCalendarId, () => import/* webpackChunkName: "server-calendar" */ ('@anticrm/server-calendar-resources'))
addLocation(serverGmailId, () => import/* webpackChunkName: "server-gmail" */ ('@anticrm/server-gmail-resources'))
addLocation(serverTelegramId, () => import/* webpackChunkName: "server-telegram" */ ('@anticrm/server-telegram-resources'))
addLocation(serverHrId, () => import(/* webpackChunkName: "server-attachment" */ '@anticrm/server-hr-resources'))
// Set devmodel to hook client to be able to present all activity
enableDevModel()
}

View File

@ -39,6 +39,7 @@ import { tagsId } from '@anticrm/tags'
import { calendarId } from '@anticrm/calendar'
import { trackerId } from '@anticrm/tracker'
import { boardId } from '@anticrm/board'
import { hrId } from '@anticrm/hr'
import rekoni from '@anticrm/rekoni'
import '@anticrm/login-assets'
@ -62,6 +63,7 @@ import '@anticrm/calendar-assets'
import '@anticrm/tracker-assets'
import '@anticrm/board-assets'
import '@anticrm/preference-assets'
import '@anticrm/hr-assets'
import presentation, { presentationId } from '@anticrm/presentation'
import { coreId } from '@anticrm/core'
import { textEditorId } from '@anticrm/text-editor'
@ -115,6 +117,7 @@ export async function configurePlatform() {
addLocation(trackerId, () => import(/* webpackChunkName: "tracker" */ '@anticrm/tracker-resources'))
addLocation(boardId, () => import(/* webpackChunkName: "board" */ '@anticrm/board-resources'))
addLocation(hrId, () => import(/* webpackChunkName: "hr" */ '@anticrm/hr-resources'))
setMetadata(workbench.metadata.PlatformTitle, 'Platform')
}

View File

@ -103,6 +103,8 @@
"@anticrm/server-gmail-resources": "~0.6.0",
"@anticrm/server-telegram": "~0.6.0",
"@anticrm/server-telegram-resources": "~0.6.0",
"@anticrm/server-hr": "~0.6.0",
"@anticrm/server-hr-resources": "~0.6.0",
"@anticrm/rekoni": "~0.6.0",
"got": "^11.8.3",
"@anticrm/tags": "~0.6.2",

View File

@ -70,6 +70,7 @@ import { serverTagsId } from '@anticrm/server-tags'
import { serverTaskId } from '@anticrm/server-task'
import { serverTrackerId } from '@anticrm/server-tracker'
import { serverTelegramId } from '@anticrm/server-telegram'
import { serverHrId } from '@anticrm/server-hr'
import { Client as ElasticClient } from '@elastic/elasticsearch'
import { Client } from 'minio'
import { Db, MongoClient } from 'mongodb'
@ -139,6 +140,7 @@ export class ElasticTool {
addLocation(serverCalendarId, () => import('@anticrm/server-calendar-resources'))
addLocation(serverGmailId, () => import('@anticrm/server-gmail-resources'))
addLocation(serverTelegramId, () => import('@anticrm/server-telegram-resources'))
addLocation(serverHrId, () => import('@anticrm/server-hr-resources'))
this.mongoClient = new MongoClient(mongoUrl)
}

View File

@ -71,6 +71,8 @@
"@anticrm/model-server-telegram": "~0.6.0",
"@anticrm/model-tracker": "~0.6.0",
"@anticrm/model-board": "~0.6.0",
"@anticrm/model-preference": "~0.6.0"
"@anticrm/model-preference": "~0.6.0",
"@anticrm/model-hr": "~0.6.0",
"@anticrm/model-server-hr": "~0.6.0"
}
}

View File

@ -56,6 +56,8 @@ import { createModel as serverTelegramModel } from '@anticrm/model-server-telegr
import { createModel as trackerModel } from '@anticrm/model-tracker'
import { createModel as boardModel } from '@anticrm/model-board'
import { createModel as preferenceModel } from '@anticrm/model-preference'
import { createModel as hrModel } from '@anticrm/model-hr'
import { createModel as serverHrModel } from '@anticrm/model-server-hr'
export const version: Data<Version> = jsonVersion as Data<Version>
@ -82,6 +84,7 @@ const builders: [(b: Builder) => void, string][] = [
[textEditorModel, 'text-editor'],
[notificationModel, 'notification'],
[preferenceModel, 'preference'],
[hrModel, 'hr'],
[serverCoreModel, 'server-core'],
[serverAttachmentModel, 'server-attachment'],
@ -99,6 +102,7 @@ const builders: [(b: Builder) => void, string][] = [
[serverCalendarModel, 'server-calendar'],
[serverGmailModel, 'server-gmail'],
[serverTelegramModel, 'server-telegram'],
[serverHrModel, 'server-hr'],
[trackerModel, 'tracker'],
[boardModel, 'board'],
[calendarModel, 'calendar']

View File

@ -32,6 +32,7 @@ import { viewOperation } from '@anticrm/model-view'
import { trackerOperation } from '@anticrm/model-tracker'
import { boardOperation } from '@anticrm/model-board'
import { demoOperation } from '@anticrm/model-demo'
import { hrOperation } from '@anticrm/model-hr'
export const migrateOperations: MigrateOperation[] = [
coreOperation,
@ -50,5 +51,6 @@ export const migrateOperations: MigrateOperation[] = [
notificationOperation,
settingOperation,
trackerOperation,
boardOperation
boardOperation,
hrOperation
]

7
models/hr/.eslintrc.js Normal file
View File

@ -0,0 +1,7 @@
module.exports = {
extends: ['./node_modules/@anticrm/model-rig/profiles/default/config/eslint.config.json'],
parserOptions: {
tsconfigRootDir: __dirname,
project: './tsconfig.json'
}
}

4
models/hr/.npmignore Normal file
View File

@ -0,0 +1,4 @@
*
!/lib/**
!CHANGELOG.md
/lib/**/__tests__/

18
models/hr/config/rig.json Normal file
View File

@ -0,0 +1,18 @@
// The "rig.json" file directs tools to look for their config files in an external package.
// Documentation for this system: https://www.npmjs.com/package/@rushstack/rig-package
{
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
/**
* (Required) The name of the rig package to inherit from.
* It should be an NPM package name with the "-rig" suffix.
*/
"rigPackageName": "@anticrm/model-rig"
/**
* (Optional) Selects a config profile from the rig package. The name must consist of
* lowercase alphanumeric words separated by hyphens, for example "sample-profile".
* If omitted, then the "default" profile will be used."
*/
// "rigProfile": "your-profile-name"
}

41
models/hr/package.json Normal file
View File

@ -0,0 +1,41 @@
{
"name": "@anticrm/model-hr",
"version": "0.6.0",
"main": "lib/index.js",
"author": "Anticrm Platform Contributors",
"license": "EPL-2.0",
"scripts": {
"build": "heft build",
"build:watch": "tsc",
"lint:fix": "eslint --fix src",
"lint": "eslint src",
"format": "prettier --write src && eslint --fix src"
},
"devDependencies": {
"@anticrm/model-rig": "~0.6.0",
"@typescript-eslint/eslint-plugin": "^5.21.0",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-promise": "^5.1.1",
"eslint-plugin-node": "^11.1.0",
"eslint": "^7.32.0",
"@types/heft-jest": "^1.0.2",
"@typescript-eslint/parser": "^5.4.0",
"eslint-config-standard-with-typescript": "^21.0.1",
"prettier": "^2.4.1",
"@rushstack/heft": "^0.45.5"
},
"dependencies": {
"@anticrm/core": "~0.6.16",
"@anticrm/model": "~0.6.0",
"@anticrm/ui": "~0.6.0",
"@anticrm/contact": "~0.6.5",
"@anticrm/platform": "~0.6.6",
"@anticrm/model-core": "~0.6.0",
"@anticrm/model-view": "~0.6.0",
"@anticrm/model-workbench": "~0.6.1",
"@anticrm/model-contact": "~0.6.1",
"@anticrm/hr": "~0.6.0",
"@anticrm/hr-resources": "~0.6.0",
"@anticrm/view": "~0.6.0"
}
}

137
models/hr/src/index.ts Normal file
View File

@ -0,0 +1,137 @@
//
// Copyright © 2022 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import { Employee } from '@anticrm/contact'
import contact, { TEmployee } from '@anticrm/model-contact'
import { IndexKind, Ref } from '@anticrm/core'
import type { Department, Staff } from '@anticrm/hr'
import { Builder, Index, Mixin, Model, Prop, TypeRef, TypeString, UX } from '@anticrm/model'
import core, { TSpace } from '@anticrm/model-core'
import workbench from '@anticrm/model-workbench'
import hr from './plugin'
import view, { createAction } from '@anticrm/model-view'
@Model(hr.class.Department, core.class.Space)
@UX(hr.string.Department, hr.icon.Department)
export class TDepartment extends TSpace implements Department {
@Prop(TypeRef(hr.class.Department), hr.string.ParentDepartmentLabel)
declare space: Ref<Department>
@Prop(TypeString(), core.string.Name)
@Index(IndexKind.FullText)
name!: string
avatar?: string | null
@Prop(TypeRef(contact.class.Employee), hr.string.TeamLead)
teamLead!: Ref<Employee> | null
}
@Mixin(hr.mixin.Staff, contact.class.Employee)
@UX(contact.string.Employee, hr.icon.HR)
export class TStaff extends TEmployee implements Staff {
@Prop(TypeRef(hr.class.Department), hr.string.Department)
department!: Ref<Department>
}
export function createModel (builder: Builder): void {
builder.createModel(TDepartment, TStaff)
builder.createDoc(
workbench.class.Application,
core.space.Model,
{
label: hr.string.HRApplication,
icon: hr.icon.HR,
hidden: false,
navigatorModel: {
specials: [
{
id: 'structure',
component: hr.component.Structure,
icon: hr.icon.Structure,
label: hr.string.Structure,
position: 'top'
}
],
spaces: []
}
},
hr.app.HR
)
builder.mixin(hr.class.Department, core.class.Class, view.mixin.AttributeEditor, {
inlineEditor: hr.component.DepartmentEditor
})
createAction(
builder,
{
action: view.actionImpl.ShowPanel,
actionProps: {
component: hr.component.EditDepartment
},
label: view.string.Open,
icon: view.icon.Open,
keyBinding: ['e'],
input: 'any',
category: hr.category.HR,
target: hr.class.Department,
context: { mode: 'context', application: hr.app.HR, group: 'top' }
},
hr.action.EditDepartment
)
createAction(
builder,
{
action: view.actionImpl.ShowPopup,
actionProps: {
component: hr.component.DepartmentStaff,
element: 'float'
},
label: hr.string.ShowEmployees,
icon: contact.icon.Person,
keyBinding: ['m'],
input: 'any',
category: hr.category.HR,
target: hr.class.Department,
context: { mode: 'context', application: hr.app.HR, group: 'top' }
},
hr.action.ShowEmployees
)
createAction(
builder,
{
action: view.actionImpl.Delete,
label: view.string.Delete,
icon: view.icon.Delete,
input: 'any',
category: hr.category.HR,
keyBinding: ['Meta + Backspace', 'Ctrl + Backspace'],
query: {
'members.length': 0,
_id: { $nin: [hr.ids.Head] }
},
target: hr.class.Department,
context: { mode: 'context', application: hr.app.HR, group: 'top' }
},
hr.action.DeleteDepartment
)
}
export { hrOperation } from './migration'
export { default } from './plugin'

View File

@ -0,0 +1,48 @@
//
// Copyright © 2022 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import { TxOperations } from '@anticrm/core'
import { MigrateOperation, MigrationClient, MigrationUpgradeClient } from '@anticrm/model'
import core from '@anticrm/model-core'
import hr from './index'
async function createSpace (tx: TxOperations): Promise<void> {
const current = await tx.findOne(core.class.Space, {
_id: hr.ids.Head
})
if (current === undefined) {
await tx.createDoc(
hr.class.Department,
core.space.Space,
{
name: 'Organization',
description: '',
private: false,
archived: false,
members: [],
teamLead: null
},
hr.ids.Head
)
}
}
export const hrOperation: MigrateOperation = {
async migrate (client: MigrationClient): Promise<void> {},
async upgrade (client: MigrationUpgradeClient): Promise<void> {
const tx = new TxOperations(client, core.account.System)
await createSpace(tx)
}
}

43
models/hr/src/plugin.ts Normal file
View File

@ -0,0 +1,43 @@
//
// Copyright © 2022 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import { Ref } from '@anticrm/core'
import { hrId } from '@anticrm/hr'
import hr from '@anticrm/hr-resources/src/plugin'
import { IntlString, mergeIds } from '@anticrm/platform'
import { AnyComponent } from '@anticrm/ui'
import { Action, ActionCategory } from '@anticrm/view'
export default mergeIds(hrId, hr, {
string: {
HRApplication: '' as IntlString,
Departments: '' as IntlString,
ShowEmployees: '' as IntlString
},
component: {
Structure: '' as AnyComponent,
EditDepartment: '' as AnyComponent,
DepartmentStaff: '' as AnyComponent,
DepartmentEditor: '' as AnyComponent
},
category: {
HR: '' as Ref<ActionCategory>
},
action: {
EditDepartment: '' as Ref<Action>,
ShowEmployees: '' as Ref<Action>,
DeleteDepartment: '' as Ref<Action>
}
})

8
models/hr/tsconfig.json Normal file
View File

@ -0,0 +1,8 @@
{
"extends": "./node_modules/@anticrm/model-rig/profiles/default/tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib",
}
}

View File

@ -0,0 +1,7 @@
module.exports = {
extends: ['./node_modules/@anticrm/model-rig/profiles/default/config/eslint.config.json'],
parserOptions: {
tsconfigRootDir: __dirname,
project: './tsconfig.json'
}
}

View File

@ -0,0 +1,4 @@
*
!/lib/**
!CHANGELOG.md
/lib/**/__tests__/

View File

@ -0,0 +1,18 @@
// The "rig.json" file directs tools to look for their config files in an external package.
// Documentation for this system: https://www.npmjs.com/package/@rushstack/rig-package
{
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
/**
* (Required) The name of the rig package to inherit from.
* It should be an NPM package name with the "-rig" suffix.
*/
"rigPackageName": "@anticrm/model-rig"
/**
* (Optional) Selects a config profile from the rig package. The name must consist of
* lowercase alphanumeric words separated by hyphens, for example "sample-profile".
* If omitted, then the "default" profile will be used."
*/
// "rigProfile": "your-profile-name"
}

View File

@ -0,0 +1,34 @@
{
"name": "@anticrm/model-server-hr",
"version": "0.6.0",
"main": "lib/index.js",
"author": "Anticrm Platform Contributors",
"license": "EPL-2.0",
"scripts": {
"build": "heft build",
"build:watch": "tsc",
"lint:fix": "eslint --fix src",
"lint": "eslint src",
"format": "prettier --write src && eslint --fix src"
},
"devDependencies": {
"@anticrm/model-rig": "~0.6.0",
"@typescript-eslint/eslint-plugin": "^5.21.0",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-promise": "^5.1.1",
"eslint-plugin-node": "^11.1.0",
"eslint": "^7.32.0",
"@types/heft-jest": "^1.0.2",
"@typescript-eslint/parser": "^5.4.0",
"eslint-config-standard-with-typescript": "^21.0.1",
"prettier": "^2.4.1",
"@rushstack/heft": "^0.45.5"
},
"dependencies": {
"@anticrm/core": "~0.6.16",
"@anticrm/model": "~0.6.0",
"@anticrm/platform": "~0.6.6",
"@anticrm/server-hr": "~0.6.0",
"@anticrm/server-core": "~0.6.1"
}
}

View File

@ -0,0 +1,26 @@
//
// Copyright © 2022 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import { Builder } from '@anticrm/model'
import serverCore from '@anticrm/server-core'
import core from '@anticrm/core'
import serverHr from '@anticrm/server-hr'
export function createModel (builder: Builder): void {
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverHr.trigger.OnDepartmentStaff
})
}

View File

@ -0,0 +1,8 @@
{
"extends": "./node_modules/@anticrm/model-rig/profiles/default/tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib",
}
}

View File

@ -14,11 +14,11 @@
//
import { PlatformError, Severity, Status } from '@anticrm/platform'
import { Lookup, ReverseLookups } from '.'
import { getObjectValue, Lookup, ReverseLookups } from '.'
import type { Class, Doc, Ref } from './classes'
import core from './component'
import { Hierarchy } from './hierarchy'
import { matchQuery, resultSort } from './query'
import { matchQuery, resultSort, checkMixinKey } from './query'
import type { DocumentQuery, FindOptions, FindResult, LookupData, Storage, TxResult, WithLookup } from './storage'
import type { Tx, TxCreateDoc, TxMixin, TxPutBag, TxRemoveDoc, TxUpdateDoc } from './tx'
import { TxProcessor } from './tx'
@ -77,25 +77,31 @@ export abstract class MemDb extends TxProcessor {
return doc as T
}
private async getLookupValue<T extends Doc>(doc: T, lookup: Lookup<T>, result: LookupData<T>): Promise<void> {
private async getLookupValue<T extends Doc>(
_class: Ref<Class<T>>,
doc: T,
lookup: Lookup<T>,
result: LookupData<T>
): Promise<void> {
for (const key in lookup) {
if (key === '_id') {
await this.getReverseLookupValue(doc, lookup, result)
continue
}
const value = (lookup as any)[key]
const tkey = checkMixinKey(key, _class, this.hierarchy)
if (Array.isArray(value)) {
const [_class, nested] = value
const objects = await this.findAll(_class, { _id: (doc as any)[key] })
const objects = await this.findAll(_class, { _id: getObjectValue(tkey, doc) })
;(result as any)[key] = objects[0]
const nestedResult = {}
const parent = (result as any)[key]
await this.getLookupValue(parent, nested, nestedResult)
await this.getLookupValue(_class, parent, nested, nestedResult)
Object.assign(parent, {
$lookup: nestedResult
})
} else {
const objects = await this.findAll(value, { _id: (doc as any)[key] })
const objects = await this.findAll(value, { _id: getObjectValue(tkey, doc) })
;(result as any)[key] = objects[0]
}
}
@ -118,11 +124,11 @@ export abstract class MemDb extends TxProcessor {
}
}
private async lookup<T extends Doc>(docs: T[], lookup: Lookup<T>): Promise<WithLookup<T>[]> {
private async lookup<T extends Doc>(_class: Ref<Class<T>>, docs: T[], lookup: Lookup<T>): Promise<WithLookup<T>[]> {
const withLookup: WithLookup<T>[] = []
for (const doc of docs) {
const result: LookupData<T> = {}
await this.getLookupValue(doc, lookup, result)
await this.getLookupValue(_class, doc, lookup, result)
withLookup.push(Object.assign({}, doc, { $lookup: result }))
}
return withLookup
@ -152,7 +158,7 @@ export abstract class MemDb extends TxProcessor {
}
if (options?.lookup !== undefined) {
result = await this.lookup(result as T[], options.lookup)
result = await this.lookup(_class, result as T[], options.lookup)
result = matchQuery(result, query, _class, this.hierarchy)
}

View File

@ -42,6 +42,9 @@ function $push (document: Doc, keyval: Record<string, PropertyType>): void {
function $pull (document: Doc, keyval: Record<string, PropertyType>): void {
const doc = document as any
for (const key in keyval) {
if (doc[key] === undefined) {
doc[key] = []
}
const arr = doc[key] as Array<any>
if (typeof keyval[key] === 'object') {
const { $in } = keyval[key] as PullArray<PropertyType>
@ -55,6 +58,9 @@ function $pull (document: Doc, keyval: Record<string, PropertyType>): void {
function $move (document: Doc, keyval: Record<string, PropertyType>): void {
const doc = document as any
for (const key in keyval) {
if (doc[key] === undefined) {
doc[key] = []
}
const arr = doc[key] as Array<any>
const desc = keyval[key]
doc[key] = arr.filter((val) => val !== desc.$value)

View File

@ -14,15 +14,17 @@
-->
<script lang="ts">
import { createEventDispatcher } from 'svelte'
import { IconSize, showPopup } from '@anticrm/ui'
import { AnySvelteComponent, IconSize, showPopup } from '@anticrm/ui'
import Avatar from './Avatar.svelte'
import EditAvatarPopup from './EditAvatarPopup.svelte'
import { getFileUrl } from '../utils'
import { Asset } from '@anticrm/platform'
export let avatar: string | null | undefined = undefined
export let size: IconSize
export let direct: Blob | undefined = undefined
export let icon: Asset | AnySvelteComponent | undefined = undefined
const dispatch = createEventDispatcher()
@ -75,6 +77,6 @@
</script>
<div class="cursor-pointer" on:click={onClick}>
<Avatar {avatar} {direct} {size} />
<Avatar {avatar} {direct} {size} {icon} />
<input style="display: none;" type="file" bind:this={inputRef} on:change={onSelect} accept={targetMimes.join(',')} />
</div>

View File

@ -25,11 +25,14 @@
getFocusManager,
AnyComponent,
Tooltip,
TooltipAlignment
TooltipAlignment,
ButtonKind,
ButtonSize
} from '@anticrm/ui'
import SpacesPopup from './SpacesPopup.svelte'
import type { Ref, Class, Space, DocumentQuery } from '@anticrm/core'
import { createEventDispatcher } from 'svelte'
export let _class: Ref<Class<Space>>
export let spaceQuery: DocumentQuery<Space> | undefined = { archived: false }
@ -44,10 +47,15 @@
}
| undefined = undefined
export let labelDirection: TooltipAlignment | undefined = undefined
export let kind: ButtonKind = 'no-border'
export let size: ButtonSize = 'small'
export let justify: 'left' | 'center' = 'center'
export let width: string | undefined = undefined
let selected: Space | undefined
const client = getClient()
const dispatch = createEventDispatcher()
const mgr = getFocusManager()
async function updateSelected (value: Ref<Space> | undefined) {
@ -71,6 +79,7 @@
(result) => {
if (result) {
value = result._id
dispatch('change', value)
mgr?.setFocusPos(focusIndex)
}
}
@ -79,7 +88,7 @@
</script>
<Tooltip {label} fill={false} direction={labelDirection}>
<Button {focus} {focusIndex} icon={IconFolder} size={'small'} kind={'no-border'} on:click={showSpacesPopup}>
<Button {focus} {focusIndex} icon={IconFolder} {size} {kind} {justify} {width} on:click={showSpacesPopup}>
<span slot="content" class="overflow-label disabled text-sm">
{#if selected}{selected.name}{:else}<Label {label} />{/if}
</span>

View File

@ -15,13 +15,17 @@
<script lang="ts">
import type { Class, DocumentQuery, Ref, Space } from '@anticrm/core'
import type { IntlString } from '@anticrm/platform'
import { AnyComponent } from '@anticrm/ui'
import { AnyComponent, ButtonKind, ButtonSize } from '@anticrm/ui'
import SpaceSelect from './SpaceSelect.svelte'
export let space: Ref<Space> | undefined = undefined
export let _class: Ref<Class<Space>>
export let query: DocumentQuery<Space> = { archived: false }
export let label: IntlString
export let kind: ButtonKind = 'no-border'
export let size: ButtonSize = 'small'
export let justify: 'left' | 'center' = 'center'
export let width: string | undefined = undefined
export let create:
| {
@ -31,4 +35,17 @@
| undefined = undefined
</script>
<SpaceSelect {create} focus focusIndex={-10} {_class} spaceQuery={query} {label} bind:value={space} />
<SpaceSelect
{create}
focus
focusIndex={-10}
{_class}
spaceQuery={query}
{label}
{size}
{kind}
{justify}
{width}
bind:value={space}
on:change
/>

View File

@ -15,6 +15,7 @@
import core, {
AttachedDoc,
checkMixinKey,
Class,
Client,
Doc,
@ -26,11 +27,13 @@ import core, {
Hierarchy,
Lookup,
LookupData,
matchQuery,
ModelDb,
Ref,
resultSort,
ReverseLookups,
SortingQuery,
toFindResult,
Tx,
TxBulkWrite,
TxCollectionCUD,
@ -41,10 +44,7 @@ import core, {
TxRemoveDoc,
TxResult,
TxUpdateDoc,
WithLookup,
toFindResult,
checkMixinKey,
matchQuery
WithLookup
} from '@anticrm/core'
import { deepEqual } from 'fast-equals'
@ -195,7 +195,7 @@ export class LiveQuery extends TxProcessor implements Client {
})
this.queries.set(_class, queries)
if (this.queue.length > CACHE_SIZE) {
while (this.queue.length > CACHE_SIZE) {
this.remove()
}
return q
@ -271,6 +271,7 @@ export class LiveQuery extends TxProcessor implements Client {
if (updatedDoc !== undefined) {
// Create or apply mixin value
updatedDoc = TxProcessor.updateMixin4Doc(updatedDoc, tx)
await this.__updateLookup(q, updatedDoc, tx.attributes)
await this.updatedDocCallback(updatedDoc, q)
} else if (isMixin) {
// Mixin potentially added to object we doesn't have in out results
@ -477,25 +478,31 @@ export class LiveQuery extends TxProcessor implements Client {
return false
}
private async getLookupValue<T extends Doc>(doc: T, lookup: Lookup<T>, result: LookupData<T>): Promise<void> {
private async getLookupValue<T extends Doc>(
_class: Ref<Class<T>>,
doc: T,
lookup: Lookup<T>,
result: LookupData<T>
): Promise<void> {
for (const key in lookup) {
if (key === '_id') {
await this.getReverseLookupValue(doc, lookup, result)
continue
}
const value = (lookup as any)[key]
const tkey = checkMixinKey(key, _class, this.client.getHierarchy())
if (Array.isArray(value)) {
const [_class, nested] = value
const objects = await this.findAll(_class, { _id: (doc as any)[key] })
const objects = await this.findAll(_class, { _id: getObjectValue(tkey, doc) })
;(result as any)[key] = objects[0]
const nestedResult = {}
const parent = (result as any)[key]
await this.getLookupValue(parent, nested, nestedResult)
await this.getLookupValue(_class, parent, nested, nestedResult)
Object.assign(parent, {
$lookup: nestedResult
})
} else {
const objects = await this.findAll(value, { _id: (doc as any)[key] })
const objects = await this.findAll(value, { _id: getObjectValue(tkey, doc) })
;(result as any)[key] = objects[0]
}
}
@ -523,9 +530,9 @@ export class LiveQuery extends TxProcessor implements Client {
}
}
private async lookup<T extends Doc>(doc: T, lookup: Lookup<T>): Promise<void> {
private async lookup<T extends Doc>(_class: Ref<Class<T>>, doc: T, lookup: Lookup<T>): Promise<void> {
const result: LookupData<Doc> = {}
await this.getLookupValue(doc, lookup, result)
await this.getLookupValue(_class, doc, lookup, result)
;(doc as WithLookup<Doc>).$lookup = result
}
@ -547,7 +554,7 @@ export class LiveQuery extends TxProcessor implements Client {
}
if (q.options?.lookup !== undefined) {
await this.lookup(doc, q.options.lookup)
await this.lookup(q._class, doc, q.options.lookup)
}
// We could already have document inside results, if query is created during processing of document create transaction and not yet handled on client.
const pos = q.result.findIndex((p) => p._id === doc._id)
@ -751,14 +758,7 @@ export class LiveQuery extends TxProcessor implements Client {
return await super.tx(tx)
}
private async __updateDoc (q: Query, updatedDoc: WithLookup<Doc>, tx: TxUpdateDoc<Doc>): Promise<void> {
TxProcessor.updateDoc2Doc(updatedDoc, tx)
const ops = {
...tx.operations,
modifiedBy: tx.modifiedBy,
modifiedOn: tx.modifiedOn
} as any
private async __updateLookup (q: Query, updatedDoc: WithLookup<Doc>, ops: any): Promise<void> {
for (const key in ops) {
if (!key.startsWith('$')) {
if (q.options !== undefined) {
@ -783,7 +783,7 @@ export class LiveQuery extends TxProcessor implements Client {
}
} else {
if (key === '$push') {
const pops = tx.operations[key] ?? {}
const pops = ops[key] ?? {}
for (const pkey of Object.keys(pops)) {
if (q.options !== undefined) {
const lookup = (q.options.lookup as any)?.[pkey]
@ -794,28 +794,26 @@ export class LiveQuery extends TxProcessor implements Client {
if (pp[pkey] === undefined) {
pp[pkey] = []
}
if (Array.isArray((pops as any)[pkey])) {
if (Array.isArray(pops[pkey])) {
const pushData = await this.client.findAll(
lookupClass,
{ _id: { $in: (pops as any)[pkey] } },
{ _id: { $in: pops[pkey] } },
{ lookup: nestedLookup }
)
pp[pkey].push(...pushData)
} else {
pp[pkey].push(
await this.client.findOne(lookupClass, { _id: (pops as any)[pkey] }, { lookup: nestedLookup })
)
pp[pkey].push(await this.client.findOne(lookupClass, { _id: pops[pkey] }, { lookup: nestedLookup }))
}
}
}
}
} else if (key === '$pull') {
const pops = tx.operations[key] ?? {}
const pops = ops[key] ?? {}
for (const pkey of Object.keys(pops)) {
if (q.options !== undefined) {
const lookup = (q.options.lookup as any)?.[pkey]
if (lookup !== undefined) {
const pid = (pops as any)[pkey]
const pid = pops[pkey]
const pp = updatedDoc.$lookup as any
if (pp[pkey] === undefined) {
pp[pkey] = []
@ -833,6 +831,17 @@ export class LiveQuery extends TxProcessor implements Client {
}
}
private async __updateDoc (q: Query, updatedDoc: WithLookup<Doc>, tx: TxUpdateDoc<Doc>): Promise<void> {
TxProcessor.updateDoc2Doc(updatedDoc, tx)
const ops = {
...tx.operations,
modifiedBy: tx.modifiedBy,
modifiedOn: tx.modifiedOn
}
await this.__updateLookup(q, updatedDoc, ops)
}
private sort (q: Query, tx: TxUpdateDoc<Doc>): void {
const sort = q.options?.sort
if (sort === undefined) return

View File

@ -24,7 +24,7 @@
export let direction: TooltipAlignment | undefined = undefined
export let icon: Asset | AnySvelteComponent
export let size: 'small' | 'medium' | 'large'
export let action: (ev: Event) => Promise<void> | void = async () => {}
export let action: (ev: MouseEvent) => Promise<void> | void = async () => {}
export let invisible: boolean = false
</script>

View File

@ -0,0 +1,7 @@
module.exports = {
extends: ['./node_modules/@anticrm/platform-rig/profiles/assets/config/eslint.config.json'],
parserOptions: {
tsconfigRootDir: __dirname,
project: './tsconfig.json'
}
}

View File

@ -0,0 +1,13 @@
<!-- ALL HASHTAGs -->
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
<symbol id="structure" viewBox="0 0 24 24">
<path d="M17.2,7.8h3.6c1.1,0,2-0.9,2-2V4.2c0-1.1-0.9-2-2-2h-3.6c-1.1,0-2,0.9-2,2v0H9.8V4c0-1.5-1.2-2.8-2.8-2.8H4 C2.5,1.2,1.2,2.5,1.2,4v2c0,1.5,1.2,2.8,2.8,2.8h3c1.5,0,2.8-1.2,2.8-2.8V5.8h2V18c0,1.5,1.2,2.8,2.8,2.8h0.8v0c0,1.1,0.9,2,2,2h3.6 c1.1,0,2-0.9,2-2v-1.6c0-1.1-0.9-2-2-2h-3.6c-1.1,0-2,0.9-2,2v0h-0.8c-0.7,0-1.2-0.6-1.2-1.2v-4.8h2v0c0,1.1,0.9,2,2,2h3.6 c1.1,0,2-0.9,2-2v-1.6c0-1.1-0.9-2-2-2h-3.6c-1.1,0-2,0.9-2,2v0h-2v-6h2v0C15.2,6.9,16.1,7.8,17.2,7.8z M8.2,6 c0,0.7-0.6,1.2-1.2,1.2H4C3.3,7.2,2.8,6.7,2.8,6V4c0-0.7,0.6-1.2,1.2-1.2h3c0.7,0,1.2,0.6,1.2,1.2V6z M16.8,19.2 c0-0.2,0.2-0.5,0.5-0.5h3.6c0.2,0,0.5,0.2,0.5,0.5v1.6c0,0.2-0.2,0.5-0.5,0.5h-3.6c-0.2,0-0.5-0.2-0.5-0.5V19.2z M16.8,11.7 c0-0.2,0.2-0.5,0.5-0.5h3.6c0.2,0,0.5,0.2,0.5,0.5v1.6c0,0.2-0.2,0.5-0.5,0.5h-3.6c-0.2,0-0.5-0.2-0.5-0.5V11.7z M16.8,4.2 c0-0.2,0.2-0.5,0.5-0.5h3.6c0.2,0,0.5,0.2,0.5,0.5v1.6c0,0.2-0.2,0.5-0.5,0.5h-3.6c-0.2,0-0.5-0.2-0.5-0.5V4.2z" />
</symbol>
<symbol id="department" viewBox="0 0 24 24">
<path d="M17.2,7.8h3.6c1.1,0,2-0.9,2-2V4.2c0-1.1-0.9-2-2-2h-3.6c-1.1,0-2,0.9-2,2v0H9.8V4c0-1.5-1.2-2.8-2.8-2.8H4 C2.5,1.2,1.2,2.5,1.2,4v2c0,1.5,1.2,2.8,2.8,2.8h3c1.5,0,2.8-1.2,2.8-2.8V5.8h2V18c0,1.5,1.2,2.8,2.8,2.8h0.8v0c0,1.1,0.9,2,2,2h3.6 c1.1,0,2-0.9,2-2v-1.6c0-1.1-0.9-2-2-2h-3.6c-1.1,0-2,0.9-2,2v0h-0.8c-0.7,0-1.2-0.6-1.2-1.2v-4.8h2v0c0,1.1,0.9,2,2,2h3.6 c1.1,0,2-0.9,2-2v-1.6c0-1.1-0.9-2-2-2h-3.6c-1.1,0-2,0.9-2,2v0h-2v-6h2v0C15.2,6.9,16.1,7.8,17.2,7.8z M8.2,6 c0,0.7-0.6,1.2-1.2,1.2H4C3.3,7.2,2.8,6.7,2.8,6V4c0-0.7,0.6-1.2,1.2-1.2h3c0.7,0,1.2,0.6,1.2,1.2V6z M16.8,19.2 c0-0.2,0.2-0.5,0.5-0.5h3.6c0.2,0,0.5,0.2,0.5,0.5v1.6c0,0.2-0.2,0.5-0.5,0.5h-3.6c-0.2,0-0.5-0.2-0.5-0.5V19.2z M16.8,11.7 c0-0.2,0.2-0.5,0.5-0.5h3.6c0.2,0,0.5,0.2,0.5,0.5v1.6c0,0.2-0.2,0.5-0.5,0.5h-3.6c-0.2,0-0.5-0.2-0.5-0.5V11.7z M16.8,4.2 c0-0.2,0.2-0.5,0.5-0.5h3.6c0.2,0,0.5,0.2,0.5,0.5v1.6c0,0.2-0.2,0.5-0.5,0.5h-3.6c-0.2,0-0.5-0.2-0.5-0.5V4.2z" />
</symbol>
<symbol id="hr" viewBox="0 0 24 24">
<path d="M12,10.8c2.6,0,4.8-2.1,4.8-4.8S14.6,1.2,12,1.2C9.4,1.2,7.2,3.4,7.2,6S9.4,10.8,12,10.8z M12,2.8 c1.8,0,3.2,1.5,3.2,3.2S13.8,9.2,12,9.2S8.8,7.8,8.8,6S10.2,2.8,12,2.8z" />
<path d="M12,12.2c-5.4,0-9.8,4.4-9.8,9.8c0,0.4,0.3,0.8,0.8,0.8s0.8-0.3,0.8-0.8c0-4.2,3.1-7.7,7.2-8.2l-1.7,5.5 c-0.1,0.2,0,0.5,0.1,0.7l2,2.5c0.1,0.2,0.4,0.3,0.6,0.3s0.4-0.1,0.6-0.3l2-2.5c0.2-0.2,0.2-0.5,0.1-0.7L13,13.8 c4.1,0.5,7.2,4,7.2,8.2c0,0.4,0.3,0.8,0.8,0.8s0.8-0.3,0.8-0.8C21.8,16.6,17.4,12.2,12,12.2z M12,20.8l-1.2-1.5l1.2-3.8l1.2,3.8 L12,20.8z" />
</symbol>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -0,0 +1,5 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
"rigPackageName": "@anticrm/platform-rig",
"rigProfile": "assets"
}

View File

@ -0,0 +1,21 @@
{
"string": {
"Department": "Department",
"ParentDepartmentLabel": "Parent department",
"Structure":"Structure",
"CreateDepartment": "Create department",
"CreateDepartmentLabel": "Create department",
"DepartmentPlaceholder": "Department",
"TeamLead": "Team lead",
"UnAssignLead": "Unassign team lead",
"MemberCount": "{count, plural, =0 {no employees} =1 {1 employee} other {# employees}}",
"AssignLead": "Assign team lead",
"TeamLeadTooltip": "{value}",
"HRApplication": "Human resources",
"MoveStaff": "Employee transfer",
"MoveStaffDescr": "Do you want to transfer employee from {current} to {department}",
"Departments": "Departments",
"ShowEmployees": "Show employees",
"AddEmployee": "Add employee"
}
}

View File

@ -0,0 +1,21 @@
{
"string": {
"Department": "Департамент",
"ParentDepartmentLabel": "Родительский департамент",
"Structure":"Структура",
"CreateDepartment": "Создать департамент",
"CreateDepartmentLabel": "Создать департамент",
"DepartmentPlaceholder": "Департамент",
"TeamLead": "Менеджер",
"UnAssignLead": "Отменить назначение менеджера",
"MemberCount": "{count, plural, =0 {нет сотрудников} =1 {1 сотрудник} =2 {2 сотрудника} =3 {3 сотрудника} =4 {4 сотрудника} other {# сотрудников}}",
"AssignLead": "Назначить менеджера",
"TeamLeadTooltip": "{value}",
"HRApplication": "Human resources",
"MoveStaff": "Перевод сотрудника",
"MoveStaffDescr": "Вы действительно хотите перевести сотрудника из {current} в {department}",
"Departments": "Департаменты",
"ShowEmployees": "Просмотреть сотрудников",
"AddEmployee": "Добавить сотрудника"
}
}

View File

@ -0,0 +1,34 @@
{
"name": "@anticrm/hr-assets",
"version": "0.6.0",
"main": "lib/index.js",
"author": "Anticrm Platform Contributors",
"template": "@anticrm/assets-package",
"license": "EPL-2.0",
"scripts": {
"build": "heft build",
"build:docs": "",
"lint": "eslint src",
"lint:fix": "eslint --fix src",
"format": "prettier --write src && eslint --fix src",
"build:watch": "tsc"
},
"devDependencies": {
"@anticrm/platform-rig": "~0.6.0",
"@types/heft-jest": "^1.0.2",
"@typescript-eslint/eslint-plugin": "^5.21.0",
"@typescript-eslint/parser": "^5.4.0",
"eslint-config-standard-with-typescript": "^21.0.1",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^5.1.1",
"eslint": "^7.32.0",
"prettier": "^2.4.1",
"@rushstack/heft": "^0.45.5",
"@types/node": "~16.11.12"
},
"dependencies": {
"@anticrm/platform": "~0.6.6",
"@anticrm/hr": "~0.6.0"
}
}

View File

@ -0,0 +1,26 @@
//
// Copyright © 2022 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import { addStringsLoader, loadMetadata } from '@anticrm/platform'
import hr, { hrId } from '@anticrm/hr'
const icons = require('../assets/icons.svg') as string // eslint-disable-line
loadMetadata(hr.icon, {
HR: `${icons}#hr`,
Department: `${icons}#department`,
Structure: `${icons}#structure`
})
addStringsLoader(hrId, async (lang: string) => await import(`../lang/${lang}.json`))

View File

@ -0,0 +1,16 @@
{
"compilerOptions": {
"moduleResolution": "node",
"target": "esnext",
"module": "esnext",
"declaration": true,
"outDir": "./lib",
"strict": true,
"skipLibCheck": true,
"esModuleInterop": true,
"lib": [
"esnext",
"dom"
]
}
}

View File

@ -0,0 +1,7 @@
module.exports = {
extends: ['./node_modules/@anticrm/platform-rig/profiles/ui/config/eslint.config.json'],
parserOptions: { tsconfigRootDir: __dirname },
settings: {
'svelte3/ignore-styles': () => true
}
}

View File

@ -0,0 +1,5 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
"rigPackageName": "@anticrm/platform-rig",
"rigProfile": "ui"
}

View File

@ -0,0 +1,46 @@
{
"name": "@anticrm/hr-resources",
"version": "0.6.0",
"main": "src/index.ts",
"author": "Anticrm Platform Contributors",
"license": "EPL-2.0",
"scripts": {
"build": "tsc --noEmit",
"build:docs": "api-extractor run --local",
"lint": "svelte-check && eslint",
"lint:fix": "eslint --fix src",
"format": "prettier --write --plugin-search-dir=. src && eslint --fix src"
},
"devDependencies": {
"svelte-loader": "^3.1.2",
"sass": "^1.37.5",
"svelte-preprocess": "^4.10.5",
"@anticrm/platform-rig": "~0.6.0",
"@typescript-eslint/eslint-plugin": "^5.21.0",
"@typescript-eslint/parser": "^5.4.0",
"eslint-config-standard-with-typescript": "^21.0.1",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^5.1.1",
"eslint-plugin-svelte3": "^4.0.0",
"prettier-plugin-svelte": "^2.7.0",
"eslint": "^7.32.0",
"prettier": "^2.4.1",
"svelte-check": "^2.7.0",
"typescript": "^4.3.5"
},
"dependencies": {
"@anticrm/platform": "~0.6.6",
"svelte": "^3.47",
"@anticrm/hr": "~0.6.0",
"@anticrm/ui": "~0.6.0",
"@anticrm/presentation": "~0.6.2",
"@anticrm/core": "~0.6.16",
"@anticrm/panel": "~0.6.0",
"@anticrm/contact": "~0.6.5",
"@anticrm/view-resources": "~0.6.0",
"@anticrm/contact-resources": "~0.6.0",
"@anticrm/setting": "~0.6.1",
"@anticrm/attachment": "~0.6.1"
}
}

View File

@ -0,0 +1,5 @@
module.exports = {
plugins: [
require('autoprefixer')
]
}

View File

@ -0,0 +1,89 @@
<!--
// Copyright © 2022 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import contact, { Employee } from '@anticrm/contact'
import { Ref } from '@anticrm/core'
import { Card, getClient, SpaceSelector, UserBox } from '@anticrm/presentation'
import { Button, createFocusManager, EditBox, FocusHandler } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
import hr from '../plugin'
export let space = hr.ids.Head
const dispatch = createEventDispatcher()
let name: string = ''
let lead: Ref<Employee> | null = null
export function canClose (): boolean {
return name === ''
}
const client = getClient()
async function createDepartment () {
const id = await client.createDoc(hr.class.Department, space, {
name,
description: '',
private: false,
archived: false,
members: [],
teamLead: lead
})
dispatch('close', id)
}
const manager = createFocusManager()
</script>
<FocusHandler {manager} />
<Card
label={hr.string.CreateDepartment}
okAction={createDepartment}
canSave={!!name}
on:close={() => {
dispatch('close')
}}
>
<div class="flex-row-center clear-mins">
<div class="mr-3">
<Button focusIndex={1} icon={hr.icon.Department} size={'medium'} kind={'link-bordered'} disabled />
</div>
<EditBox
focusIndex={2}
bind:value={name}
placeholder={hr.string.DepartmentPlaceholder}
maxWidth={'37.5rem'}
kind={'large-style'}
focus
/>
</div>
<svelte:fragment slot="header">
<SpaceSelector _class={hr.class.Department} label={hr.string.ParentDepartmentLabel} bind:space />
</svelte:fragment>
<svelte:fragment slot="pool">
<UserBox
focusIndex={3}
_class={contact.class.Employee}
label={hr.string.TeamLead}
placeholder={hr.string.TeamLead}
bind:value={lead}
allowDeselect
titleDeselect={hr.string.UnAssignLead}
kind={'no-border'}
size={'small'}
/>
</svelte:fragment>
</Card>

View File

@ -0,0 +1,151 @@
<!--
// Copyright © 2022 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { Ref, WithLookup } from '@anticrm/core'
import { Department } from '@anticrm/hr'
import { Avatar, getClient, UsersPopup } from '@anticrm/presentation'
import CreateDepartment from './CreateDepartment.svelte'
import DepartmentCard from './DepartmentCard.svelte'
import hr from '../plugin'
import { IconAdd, IconMoreV, Button, eventToHTMLElement, Label, showPopup, ActionIcon } from '@anticrm/ui'
import contact, { Employee } from '@anticrm/contact'
import { EmployeePresenter } from '@anticrm/contact-resources'
import DepartmentStaff from './DepartmentStaff.svelte'
import { Menu } from '@anticrm/view-resources'
export let value: WithLookup<Department>
export let descendants: Map<Ref<Department>, WithLookup<Department>[]>
$: currentDescendants = descendants.get(value._id) ?? []
let expand = false
const client = getClient()
function toggle () {
if (currentDescendants.length === 0) return
expand = !expand
}
async function changeLead (result: Employee | null | undefined): Promise<void> {
if (result === undefined) {
return
}
const newLead = result === null ? null : result._id
if (newLead !== value.teamLead) {
await client.update(value, { teamLead: newLead })
}
}
function openLeadEditor (event: MouseEvent) {
showPopup(
UsersPopup,
{
_class: contact.class.Employee,
selected: value.$lookup?.teamLead,
allowDeselect: true,
placeholder: hr.string.TeamLead
},
eventToHTMLElement(event),
changeLead
)
}
function createChild (e: MouseEvent) {
showPopup(CreateDepartment, { space: value._id }, eventToHTMLElement(e), (res) => {
if (res && !expand) expand = true
})
}
function editMembers (e: MouseEvent) {
showPopup(DepartmentStaff, { _id: value._id }, 'float')
}
function showMenu (e: MouseEvent) {
showPopup(
Menu,
{ object: value, baseMenuClass: value._class },
{
getBoundingClientRect: () => DOMRect.fromRect({ width: 1, height: 1, x: e.clientX, y: e.clientY })
}
)
}
</script>
<div class="flex-center w-full px-4">
<div
class="w-full mt-2 mb-2 container flex"
class:cursor-pointer={currentDescendants.length}
on:click|stopPropagation={toggle}
on:contextmenu|preventDefault={showMenu}
>
{#if currentDescendants.length}
<div class="verticalDivider" />
<div class="verticalDivider" />
{/if}
<div class="flex-between pt-4 pb-4 pr-4 pl-2 w-full">
<div class="flex-center">
<Avatar size={'medium'} avatar={value.avatar} icon={hr.icon.Department} />
<div class="flex-row ml-2">
<div class="fs-title">
{value.name}
</div>
<div class="cursor-pointer" on:click|stopPropagation={editMembers}>
<Label label={hr.string.MemberCount} params={{ count: value.members.length }} />
</div>
</div>
</div>
<div class="flex-center mr-2">
<div class="mr-2">
<EmployeePresenter
value={value.$lookup?.teamLead}
avatarSize={'small'}
shouldShowAvatar
shouldShowPlaceholder
shouldShowName={false}
tooltipLabels={{
personLabel: hr.string.TeamLeadTooltip,
placeholderLabel: hr.string.AssignLead
}}
onEmployeeEdit={openLeadEditor}
/>
</div>
<Button icon={IconAdd} on:click={createChild} />
<ActionIcon icon={IconMoreV} size={'medium'} action={showMenu} />
</div>
</div>
</div>
</div>
{#if expand && currentDescendants.length}
<div class="ml-8">
{#each descendants.get(value._id) ?? [] as nested}
<DepartmentCard value={nested} {descendants} />
{/each}
</div>
{/if}
<style lang="scss">
.container {
border-radius: 0.5rem;
border: 1px solid var(--theme-zone-border);
background-color: var(--board-card-bg-color);
}
.verticalDivider {
width: 1px;
margin-left: 0.125rem;
background-color: var(--theme-zone-border);
}
</style>

View File

@ -0,0 +1,41 @@
<!--
// Copyright © 2022 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { Ref } from '@anticrm/core'
import { Department } from '@anticrm/hr'
import { IntlString } from '@anticrm/platform'
import { SpaceSelector } from '@anticrm/presentation'
import { ButtonKind, ButtonSize } from '@anticrm/ui'
import hr from '../plugin'
export let value: Ref<Department> | undefined
export let label: IntlString = hr.string.Department
export let onChange: (value: any) => void
export let kind: ButtonKind = 'no-border'
export let size: ButtonSize = 'small'
export let justify: 'left' | 'center' = 'center'
export let width: string | undefined = undefined
</script>
<SpaceSelector
_class={hr.class.Department}
label={hr.string.ParentDepartmentLabel}
{size}
{kind}
{justify}
{width}
bind:space={value}
on:change={(e) => onChange(e.detail)}
/>

View File

@ -0,0 +1,162 @@
<!--
// Copyright © 2022 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import contact, { Employee, EmployeeAccount } from '@anticrm/contact'
import { Ref, SortingOrder, WithLookup } from '@anticrm/core'
import { Department, Staff } from '@anticrm/hr'
import { Avatar, createQuery, MessageBox, getClient, UsersPopup } from '@anticrm/presentation'
import { Scroller, Panel, Button, showPopup, eventToHTMLElement } from '@anticrm/ui'
import { createEventDispatcher } from 'svelte'
import StaffPresenter from './StaffPresenter.svelte'
import hr from '../plugin'
export let _id: Ref<Department> | undefined
let value: Department | undefined
let employees: WithLookup<Staff>[] = []
let accounts: EmployeeAccount[] = []
const dispatch = createEventDispatcher()
const departmentQuery = createQuery()
const query = createQuery()
const accountsQuery = createQuery()
const client = getClient()
$: _id &&
value === undefined &&
departmentQuery.query(
hr.class.Department,
{
_id
},
(res) => ([value] = res)
)
$: value &&
accountsQuery.query(
contact.class.EmployeeAccount,
{
_id: { $in: value.members as Ref<EmployeeAccount>[] }
},
(res) => {
accounts = res
}
)
$: accounts.length &&
query.query(
hr.mixin.Staff,
{
_id: { $in: accounts.map((p) => p.employee) as Ref<Staff>[] }
},
(res) => {
employees = res
},
{
sort: {
name: SortingOrder.Descending
},
lookup: {
department: hr.class.Department
}
}
)
function add (e: MouseEvent) {
showPopup(
UsersPopup,
{
_class: contact.class.Employee,
ignoreUsers: employees.filter((p) => p.department === _id).map((p) => p._id)
},
eventToHTMLElement(e),
addMember
)
}
async function addMember (employee: Employee | undefined): Promise<void> {
if (employee === undefined || value === undefined) {
return
}
const hierarchy = client.getHierarchy()
if (!hierarchy.hasMixin(employee, hr.mixin.Staff)) {
await client.createMixin(employee._id, employee._class, employee.space, hr.mixin.Staff, {
department: value._id
})
} else {
const staff = hierarchy.as(employee, hr.mixin.Staff)
if (staff.department === value._id) return
const current = await client.findOne(hr.class.Department, {
_id: staff.department
})
if (current !== undefined) {
showPopup(
MessageBox,
{
label: hr.string.MoveStaff,
message: hr.string.MoveStaffDescr,
params: {
current: current.name,
department: value.name
}
},
undefined,
async (res?: boolean) => {
if (res === true && value !== undefined) {
await client.updateMixin(employee._id, employee._class, employee.space, hr.mixin.Staff, {
department: value._id
})
}
}
)
} else {
await client.updateMixin(employee._id, employee._class, employee.space, hr.mixin.Staff, {
department: value._id
})
}
}
}
</script>
<Panel
isHeader={true}
isAside={false}
isFullSize
on:fullsize
on:close={() => {
dispatch('close')
}}
>
<svelte:fragment slot="title">
<div class="antiTitle icon-wrapper">
{#if value}
<div class="wrapped-icon"><Avatar size={'medium'} avatar={value.avatar} icon={hr.icon.Department} /></div>
<div class="title-wrapper">
<span class="wrapped-title">{value.name}</span>
</div>
{/if}
</div>
</svelte:fragment>
<svelte:fragment slot="utils">
<Button label={hr.string.AddEmployee} kind={'primary'} on:click={add} />
</svelte:fragment>
<Scroller>
{#each employees as value}
<StaffPresenter {value} />
{/each}
</Scroller>
</Panel>

View File

@ -0,0 +1,164 @@
<!--
// Copyright © 2022 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { createQuery, EditableAvatar, getClient } from '@anticrm/presentation'
import { Panel } from '@anticrm/panel'
import { createFocusManager, EditBox, FocusHandler } from '@anticrm/ui'
import { ActionContext } from '@anticrm/view-resources'
import { createEventDispatcher } from 'svelte'
import { Department } from '@anticrm/hr'
import core, { getCurrentAccount, Ref, Space } from '@anticrm/core'
import hr from '../plugin'
import { getResource } from '@anticrm/platform'
import attachment from '@anticrm/attachment'
import { ChannelsEditor } from '@anticrm/contact-resources'
import setting, { IntegrationType } from '@anticrm/setting'
export let _id: Ref<Department>
let object: Department | undefined
const dispatch = createEventDispatcher()
const client = getClient()
const query = createQuery()
query.query(
hr.class.Department,
{ _id },
(res) => {
object = res[0]
},
{ limit: 1 }
)
async function onAvatarDone (e: any) {
if (object === undefined) return
const uploadFile = await getResource(attachment.helper.UploadFile)
const deleteFile = await getResource(attachment.helper.DeleteFile)
const { file: avatar } = e.detail
if (object.avatar != null) {
await deleteFile(object.avatar)
}
const uuid = await uploadFile(avatar)
await client.updateDoc(object._class, object.space, object._id, {
avatar: uuid
})
}
async function removeAvatar (): Promise<void> {
if (object === undefined) return
const deleteFile = await getResource(attachment.helper.DeleteFile)
if (object.avatar != null) {
await client.updateDoc(object._class, object.space, object._id, {
avatar: null
})
await deleteFile(object.avatar)
}
}
async function nameChange (): Promise<void> {
if (object === undefined) return
await client.update(object, {
name: object.name
})
}
const manager = createFocusManager()
const _update = (result: any): void => {
dispatch('update', result)
}
let integrations: Set<Ref<IntegrationType>> = new Set<Ref<IntegrationType>>()
const accountId = getCurrentAccount()._id
const settingsQuery = createQuery()
$: settingsQuery.query(
setting.class.Integration,
{ space: accountId as string as Ref<Space>, disabled: false },
(res) => {
integrations = new Set(res.map((p) => p.type))
}
)
</script>
<ActionContext
context={{
mode: 'editor'
}}
/>
<FocusHandler {manager} />
{#if object !== undefined}
<Panel
icon={hr.icon.Department}
title={object.name}
{object}
isHeader={false}
isAside={true}
on:update={(ev) => _update(ev.detail)}
on:close={() => {
dispatch('close')
}}
>
<div class="flex-row-stretch flex-grow">
<div class="mr-8">
{#key object}
<EditableAvatar
avatar={object.avatar}
size={'x-large'}
icon={hr.icon.Department}
on:done={onAvatarDone}
on:remove={removeAvatar}
/>
{/key}
</div>
<div class="flex-grow flex-col">
<div class="name">
<EditBox
placeholder={core.string.Name}
maxWidth="20rem"
bind:value={object.name}
on:change={nameChange}
focusIndex={1}
/>
</div>
<div class="separator" />
<div class="flex-row-center">
<ChannelsEditor
attachedTo={object._id}
attachedClass={object._class}
{integrations}
focusIndex={10}
on:click
/>
</div>
</div>
</div>
</Panel>
{/if}
<style lang="scss">
.name {
font-weight: 500;
font-size: 1.25rem;
color: var(--theme-caption-color);
}
.separator {
margin: 1rem 0;
height: 1px;
background-color: var(--divider-color);
}
</style>

View File

@ -0,0 +1,36 @@
<!--
// Copyright © 2022 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { formatName } from '@anticrm/contact'
import { WithLookup } from '@anticrm/core'
import { Staff } from '@anticrm/hr'
import { Avatar } from '@anticrm/presentation'
export let value: WithLookup<Staff>
</script>
<div class="flex-between w-full p-4">
<div class="flex-row-center">
<Avatar avatar={value.avatar} size={'medium'} />
<div class="fs-title ml-2">
{formatName(value.name)}
</div>
</div>
<div>
{#if value.$lookup?.department}
{value.$lookup.department.name}
{/if}
</div>
</div>

View File

@ -0,0 +1,85 @@
<!--
// Copyright © 2022 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { DocumentQuery, Ref } from '@anticrm/core'
import { Button, Icon, Label, Scroller, SearchEdit, showPopup, IconAdd, eventToHTMLElement } from '@anticrm/ui'
import type { Department } from '@anticrm/hr'
import hr from '../plugin'
import CreateDepartment from './CreateDepartment.svelte'
import DepartmentCard from './DepartmentCard.svelte'
import { createQuery } from '@anticrm/presentation'
import contact from '@anticrm/contact'
let search = ''
let resultQuery: DocumentQuery<Department> = {}
function updateResultQuery (search: string): void {
resultQuery = search === '' ? {} : { $search: search }
}
function showCreateDialog (ev: MouseEvent) {
showPopup(CreateDepartment, {}, eventToHTMLElement(ev))
}
const query = createQuery()
let descendants: Map<Ref<Department>, Department[]> = new Map<Ref<Department>, Department[]>()
let head: Department | undefined
query.query(
hr.class.Department,
resultQuery,
(res) => {
head = res.find((p) => p._id === hr.ids.Head)
descendants.clear()
for (const doc of res) {
const current = descendants.get(doc.space)
if (!current) {
descendants.set(doc.space, [doc])
} else {
current.push(doc)
descendants.set(doc.space, current)
}
}
descendants = descendants
},
{
lookup: {
teamLead: contact.class.Employee
}
}
)
</script>
<div class="ac-header full divide">
<div class="ac-header__wrap-title">
<div class="ac-header__icon"><Icon icon={hr.icon.Structure} size={'small'} /></div>
<span class="ac-header__title"><Label label={hr.string.Structure} /></span>
</div>
<SearchEdit
bind:value={search}
on:change={() => {
updateResultQuery(search)
}}
/>
<Button label={hr.string.CreateDepartmentLabel} icon={IconAdd} kind={'primary'} on:click={showCreateDialog} />
</div>
<Scroller>
{#if head}
<DepartmentCard value={head} {descendants} />
{/if}
</Scroller>

View File

@ -0,0 +1,29 @@
//
// Copyright © 2022 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import { Resources } from '@anticrm/platform'
import Structure from './components/Structure.svelte'
import DepartmentStaff from './components/DepartmentStaff.svelte'
import EditDepartment from './components/EditDepartment.svelte'
import DepartmentEditor from './components/DepartmentEditor.svelte'
export default async (): Promise<Resources> => ({
component: {
Structure,
EditDepartment,
DepartmentStaff,
DepartmentEditor
}
})

View File

@ -0,0 +1,36 @@
//
// Copyright © 2022 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import hr, { hrId } from '@anticrm/hr'
import { IntlString, mergeIds } from '@anticrm/platform'
export default mergeIds(hrId, hr, {
string: {
Department: '' as IntlString,
ParentDepartmentLabel: '' as IntlString,
Structure: '' as IntlString,
CreateDepartment: '' as IntlString,
CreateDepartmentLabel: '' as IntlString,
DepartmentPlaceholder: '' as IntlString,
TeamLead: '' as IntlString,
UnAssignLead: '' as IntlString,
MemberCount: '' as IntlString,
AssignLead: '' as IntlString,
TeamLeadTooltip: '' as IntlString,
MoveStaff: '' as IntlString,
MoveStaffDescr: '' as IntlString,
AddEmployee: '' as IntlString
}
})

View File

@ -0,0 +1,5 @@
const sveltePreprocess = require('svelte-preprocess')
module.exports = {
preprocess: sveltePreprocess()
};

View File

@ -0,0 +1,16 @@
{
"compilerOptions": {
"moduleResolution": "node",
"target": "esnext",
"module": "esnext",
"declaration": true,
"outDir": "./lib",
"strict": true,
"skipLibCheck": true,
"esModuleInterop": true,
"lib": [
"esnext",
"dom"
]
}
}

7
plugins/hr/.eslintrc.js Normal file
View File

@ -0,0 +1,7 @@
module.exports = {
extends: ['./node_modules/@anticrm/platform-rig/profiles/default/config/eslint.config.json'],
parserOptions: {
tsconfigRootDir: __dirname,
project: './tsconfig.json'
}
}

4
plugins/hr/.npmignore Normal file
View File

@ -0,0 +1,4 @@
*
!/lib/**
!CHANGELOG.md
/lib/**/__tests__/

View File

@ -0,0 +1,18 @@
// The "rig.json" file directs tools to look for their config files in an external package.
// Documentation for this system: https://www.npmjs.com/package/@rushstack/rig-package
{
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
/**
* (Required) The name of the rig package to inherit from.
* It should be an NPM package name with the "-rig" suffix.
*/
"rigPackageName": "@anticrm/platform-rig"
/**
* (Optional) Selects a config profile from the rig package. The name must consist of
* lowercase alphanumeric words separated by hyphens, for example "sample-profile".
* If omitted, then the "default" profile will be used."
*/
// "rigProfile": "your-profile-name"
}

33
plugins/hr/package.json Normal file
View File

@ -0,0 +1,33 @@
{
"name": "@anticrm/hr",
"version": "0.6.0",
"main": "lib/index.js",
"author": "Anticrm Platform Contributors",
"license": "EPL-2.0",
"scripts": {
"build": "heft build",
"build:watch": "tsc",
"lint:fix": "eslint --fix src",
"lint": "eslint src",
"format": "prettier --write src && eslint --fix src"
},
"devDependencies": {
"@anticrm/platform-rig": "~0.6.0",
"@types/heft-jest": "^1.0.2",
"@typescript-eslint/eslint-plugin": "^5.21.0",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-promise": "^5.1.1",
"eslint-plugin-node": "^11.1.0",
"eslint": "^7.32.0",
"@typescript-eslint/parser": "^5.4.0",
"eslint-config-standard-with-typescript": "^21.0.1",
"prettier": "^2.4.1",
"@rushstack/heft": "^0.45.5",
"typescript": "^4.3.5"
},
"dependencies": {
"@anticrm/contact": "~0.6.5",
"@anticrm/core": "~0.6.16",
"@anticrm/platform": "~0.6.6"
}
}

68
plugins/hr/src/index.ts Normal file
View File

@ -0,0 +1,68 @@
//
// Copyright © 2022 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import type { Employee } from '@anticrm/contact'
import type { Class, Doc, Mixin, Ref, Space } from '@anticrm/core'
import type { Asset, Plugin } from '@anticrm/platform'
import { plugin } from '@anticrm/platform'
/**
* @public
*/
export interface Department extends Space {
space: Ref<Department>
avatar?: string | null
teamLead: Ref<Employee> | null
}
/**
* @public
*/
export interface Staff extends Employee {
department: Ref<Department>
}
/**
* @public
*/
export const hrId = 'hr' as Plugin
/**
* @public
*/
const hr = plugin(hrId, {
app: {
HR: '' as Ref<Doc>
},
class: {
Department: '' as Ref<Class<Department>>
},
mixin: {
Staff: '' as Ref<Mixin<Staff>>
},
icon: {
HR: '' as Asset,
Department: '' as Asset,
Structure: '' as Asset
},
ids: {
Head: '' as Ref<Department>
}
})
/**
* @public
*/
export default hr

9
plugins/hr/tsconfig.json Normal file
View File

@ -0,0 +1,9 @@
{
"extends": "./node_modules/@anticrm/platform-rig/profiles/default/tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib",
"lib": ["esnext", "dom"]
}
}

View File

@ -1327,6 +1327,41 @@
"packageName": "@anticrm/pod-backup",
"projectFolder": "pods/backup",
"shouldPublish": false
},
{
"packageName": "@anticrm/hr",
"projectFolder": "plugins/hr",
"shouldPublish": true
},
{
"packageName": "@anticrm/hr-assets",
"projectFolder": "plugins/hr-assets",
"shouldPublish": true
},
{
"packageName": "@anticrm/hr-resources",
"projectFolder": "plugins/hr-resources",
"shouldPublish": true
},
{
"packageName": "@anticrm/model-hr",
"projectFolder": "models/hr",
"shouldPublish": true
},
{
"packageName": "@anticrm/server-hr",
"projectFolder": "server-plugins/hr",
"shouldPublish": true
},
{
"packageName": "@anticrm/model-server-hr",
"projectFolder": "models/server-hr",
"shouldPublish": true
},
{
"packageName": "@anticrm/server-hr-resources",
"projectFolder": "server-plugins/hr-resources",
"shouldPublish": true
}
]
}

View File

@ -0,0 +1,7 @@
module.exports = {
extends: ['./node_modules/@anticrm/platform-rig/profiles/default/config/eslint.config.json'],
parserOptions: {
tsconfigRootDir: __dirname,
project: './tsconfig.json'
}
}

View File

@ -0,0 +1,4 @@
*
!/lib/**
!CHANGELOG.md
/lib/**/__tests__/

View File

@ -0,0 +1,18 @@
// The "rig.json" file directs tools to look for their config files in an external package.
// Documentation for this system: https://www.npmjs.com/package/@rushstack/rig-package
{
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
/**
* (Required) The name of the rig package to inherit from.
* It should be an NPM package name with the "-rig" suffix.
*/
"rigPackageName": "@anticrm/platform-rig"
/**
* (Optional) Selects a config profile from the rig package. The name must consist of
* lowercase alphanumeric words separated by hyphens, for example "sample-profile".
* If omitted, then the "default" profile will be used."
*/
// "rigProfile": "your-profile-name"
}

View File

@ -0,0 +1,35 @@
{
"name": "@anticrm/server-hr-resources",
"version": "0.6.0",
"main": "lib/index.js",
"author": "Anticrm Platform Contributors",
"license": "EPL-2.0",
"scripts": {
"build": "heft build",
"build:watch": "tsc",
"lint:fix": "eslint --fix src",
"lint": "eslint src",
"format": "prettier --write src && eslint --fix src"
},
"devDependencies": {
"@anticrm/platform-rig": "~0.6.0",
"@types/heft-jest": "^1.0.2",
"@typescript-eslint/eslint-plugin": "^5.21.0",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-promise": "^5.1.1",
"eslint-plugin-node": "^11.1.0",
"eslint": "^7.32.0",
"@typescript-eslint/parser": "^5.4.0",
"eslint-config-standard-with-typescript": "^21.0.1",
"prettier": "^2.4.1",
"@rushstack/heft": "^0.45.5",
"typescript": "^4.3.5"
},
"dependencies": {
"@anticrm/core": "~0.6.16",
"@anticrm/platform": "~0.6.6",
"@anticrm/server-core": "~0.6.1",
"@anticrm/contact": "~0.6.5",
"@anticrm/hr": "~0.6.0"
}
}

View File

@ -0,0 +1,132 @@
//
// Copyright © 2022 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import contact, { Employee } from '@anticrm/contact'
import core, { Account, Ref, SortingOrder, Tx, TxFactory, TxMixin } from '@anticrm/core'
import hr, { Department, Staff } from '@anticrm/hr'
import { extractTx, TriggerControl } from '@anticrm/server-core'
async function getOldDepartment (
currentTx: TxMixin<Employee, Staff>,
control: TriggerControl
): Promise<Ref<Department> | undefined> {
const txes = await control.findAll<TxMixin<Employee, Staff>>(
core.class.TxMixin,
{
objectId: currentTx.objectId
},
{ sort: { modifiedOn: SortingOrder.Ascending } }
)
let lastDepartment: Ref<Department> | undefined
for (const tx of txes) {
if (tx._id === currentTx._id) continue
if (tx.attributes?.department !== undefined) {
lastDepartment = tx.attributes.department
}
}
return lastDepartment
}
async function buildHierarchy (_id: Ref<Department>, control: TriggerControl): Promise<Ref<Department>[]> {
const res: Ref<Department>[] = []
if (_id === hr.ids.Head) return [hr.ids.Head]
const department = (
await control.findAll(hr.class.Department, {
_id
})
)[0]
if (department !== undefined) {
const ancestors = await buildHierarchy(department.space, control)
return [department._id, ...ancestors]
}
return res
}
function exlude (first: Ref<Department>[], second: Ref<Department>[]): Ref<Department>[] {
const set = new Set(first)
const res: Ref<Department>[] = []
for (const department of second) {
if (!set.has(department)) {
res.push(department)
}
}
return res
}
function getTxes (
factory: TxFactory,
account: Ref<Account>,
added: Ref<Department>[],
removed?: Ref<Department>[]
): Tx[] {
const pushTxes = added.map((dep) =>
factory.createTxUpdateDoc(hr.class.Department, core.space.Space, dep, {
$push: { members: account }
})
)
if (removed === undefined) return pushTxes
const pullTxes = removed.map((dep) =>
factory.createTxUpdateDoc(hr.class.Department, core.space.Space, dep, {
$pull: { members: account }
})
)
return [...pullTxes, ...pushTxes]
}
/**
* @public
*/
export async function OnDepartmentStaff (tx: Tx, control: TriggerControl): Promise<Tx[]> {
const actualTx = extractTx(tx)
if (core.class.TxMixin !== actualTx._class) {
return []
}
const ctx = actualTx as TxMixin<Employee, Staff>
if (ctx.mixin !== hr.mixin.Staff) {
return []
}
const targetAccount = (
await control.modelDb.findAll(contact.class.EmployeeAccount, {
employee: ctx.objectId
})
)[0]
if (targetAccount === undefined) return []
if (ctx.attributes.department !== undefined) {
const lastDepartment = await getOldDepartment(ctx, control)
const departmentId = ctx.attributes.department
const push = await buildHierarchy(departmentId, control)
if (lastDepartment === undefined) {
return getTxes(control.txFactory, targetAccount._id, push)
}
let removed = await buildHierarchy(lastDepartment, control)
const added = exlude(removed, push)
removed = exlude(push, removed)
return getTxes(control.txFactory, targetAccount._id, added, removed)
}
return []
}
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export default async () => ({
trigger: {
OnDepartmentStaff
}
})

View File

@ -0,0 +1,8 @@
{
"extends": "./node_modules/@anticrm/platform-rig/profiles/default/tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib"
}
}

View File

@ -0,0 +1,7 @@
module.exports = {
extends: ['./node_modules/@anticrm/platform-rig/profiles/default/config/eslint.config.json'],
parserOptions: {
tsconfigRootDir: __dirname,
project: './tsconfig.json'
}
}

View File

@ -0,0 +1,4 @@
*
!/lib/**
!CHANGELOG.md
/lib/**/__tests__/

View File

@ -0,0 +1,18 @@
// The "rig.json" file directs tools to look for their config files in an external package.
// Documentation for this system: https://www.npmjs.com/package/@rushstack/rig-package
{
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
/**
* (Required) The name of the rig package to inherit from.
* It should be an NPM package name with the "-rig" suffix.
*/
"rigPackageName": "@anticrm/platform-rig"
/**
* (Optional) Selects a config profile from the rig package. The name must consist of
* lowercase alphanumeric words separated by hyphens, for example "sample-profile".
* If omitted, then the "default" profile will be used."
*/
// "rigProfile": "your-profile-name"
}

View File

@ -0,0 +1,34 @@
{
"name": "@anticrm/server-hr",
"version": "0.6.0",
"main": "lib/index.js",
"author": "Anticrm Platform Contributors",
"license": "EPL-2.0",
"scripts": {
"build": "heft build",
"build:watch": "tsc",
"lint:fix": "eslint --fix src",
"lint": "eslint src",
"format": "prettier --write src && eslint --fix src"
},
"devDependencies": {
"@anticrm/platform-rig": "~0.6.0",
"@types/heft-jest": "^1.0.2",
"@types/node": "~16.11.12",
"@typescript-eslint/eslint-plugin": "^5.21.0",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-promise": "^5.1.1",
"eslint-plugin-node": "^11.1.0",
"eslint": "^7.32.0",
"@typescript-eslint/parser": "^5.4.0",
"eslint-config-standard-with-typescript": "^21.0.1",
"prettier": "^2.4.1",
"@rushstack/heft": "^0.45.5",
"typescript": "^4.3.5"
},
"dependencies": {
"@anticrm/core": "~0.6.16",
"@anticrm/platform": "~0.6.6",
"@anticrm/server-core": "~0.6.1"
}
}

View File

@ -0,0 +1,32 @@
//
// Copyright © 2022 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
//
import type { Plugin, Resource } from '@anticrm/platform'
import { plugin } from '@anticrm/platform'
import type { TriggerFunc } from '@anticrm/server-core'
/**
* @public
*/
export const serverHrId = 'server-hr' as Plugin
/**
* @public
*/
export default plugin(serverHrId, {
trigger: {
OnDepartmentStaff: '' as Resource<TriggerFunc>
}
})

View File

@ -0,0 +1,9 @@
{
"extends": "./node_modules/@anticrm/platform-rig/profiles/default/tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib",
"esModuleInterop": true
}
}

View File

@ -76,6 +76,8 @@
"@anticrm/server-preference": "~0.6.0",
"@anticrm/server-telegram": "~0.6.0",
"@anticrm/server-telegram-resources": "~0.6.0",
"@anticrm/server-hr": "~0.6.0",
"@anticrm/server-hr-resources": "~0.6.0",
"@anticrm/server-token": "~0.6.0",
"@anticrm/middleware": "~0.6.0"
}

View File

@ -60,6 +60,7 @@ import { serverTagsId } from '@anticrm/server-tags'
import { serverTaskId } from '@anticrm/server-task'
import { serverTrackerId } from '@anticrm/server-tracker'
import { serverTelegramId } from '@anticrm/server-telegram'
import { serverHrId } from '@anticrm/server-hr'
import { Token } from '@anticrm/server-token'
import { BroadcastCall, ClientSession, start as startJsonRpc } from '@anticrm/server-ws'
import { Client as MinioClient } from 'minio'
@ -137,6 +138,7 @@ export function start (
addLocation(serverCalendarId, () => import('@anticrm/server-calendar-resources'))
addLocation(serverGmailId, () => import('@anticrm/server-gmail-resources'))
addLocation(serverTelegramId, () => import('@anticrm/server-telegram-resources'))
addLocation(serverHrId, () => import('@anticrm/server-hr-resources'))
const middlewares: MiddlewareCreator[] = [ModifiedMiddleware.create, PrivateMiddleware.create]