@things-factory/compliance 0.0.0-stage → 10.1.255
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-client/bootstrap.d.ts +2 -0
- package/dist-client/bootstrap.js +28 -0
- package/dist-client/bootstrap.js.map +1 -0
- package/dist-client/components/compliance-case-form.d.ts +42 -0
- package/dist-client/components/compliance-case-form.js +313 -0
- package/dist-client/components/compliance-case-form.js.map +1 -0
- package/dist-client/components/compliance-case-sheet.d.ts +35 -0
- package/dist-client/components/compliance-case-sheet.js +335 -0
- package/dist-client/components/compliance-case-sheet.js.map +1 -0
- package/dist-client/components/compliance-my-inventions.d.ts +17 -0
- package/dist-client/components/compliance-my-inventions.js +133 -0
- package/dist-client/components/compliance-my-inventions.js.map +1 -0
- package/dist-client/components/compliance-my-privacy-requests.d.ts +34 -0
- package/dist-client/components/compliance-my-privacy-requests.js +232 -0
- package/dist-client/components/compliance-my-privacy-requests.js.map +1 -0
- package/dist-client/components/compliance-my-reports.d.ts +19 -0
- package/dist-client/components/compliance-my-reports.js +137 -0
- package/dist-client/components/compliance-my-reports.js.map +1 -0
- package/dist-client/components/guide-link.d.ts +13 -0
- package/dist-client/components/guide-link.js +55 -0
- package/dist-client/components/guide-link.js.map +1 -0
- package/dist-client/components/invention-report-form.d.ts +30 -0
- package/dist-client/components/invention-report-form.js +188 -0
- package/dist-client/components/invention-report-form.js.map +1 -0
- package/dist-client/components/legal-todo.d.ts +10 -0
- package/dist-client/components/legal-todo.js +43 -0
- package/dist-client/components/legal-todo.js.map +1 -0
- package/dist-client/components/my-trainings.d.ts +18 -0
- package/dist-client/components/my-trainings.js +149 -0
- package/dist-client/components/my-trainings.js.map +1 -0
- package/dist-client/components/regulation-form.d.ts +26 -0
- package/dist-client/components/regulation-form.js +161 -0
- package/dist-client/components/regulation-form.js.map +1 -0
- package/dist-client/components/seal-use-form.d.ts +47 -0
- package/dist-client/components/seal-use-form.js +300 -0
- package/dist-client/components/seal-use-form.js.map +1 -0
- package/dist-client/components/training-attendance-form.d.ts +37 -0
- package/dist-client/components/training-attendance-form.js +186 -0
- package/dist-client/components/training-attendance-form.js.map +1 -0
- package/dist-client/components/training-certificate-form.d.ts +41 -0
- package/dist-client/components/training-certificate-form.js +198 -0
- package/dist-client/components/training-certificate-form.js.map +1 -0
- package/dist-client/components/training-session-form.d.ts +39 -0
- package/dist-client/components/training-session-form.js +270 -0
- package/dist-client/components/training-session-form.js.map +1 -0
- package/dist-client/components/training-setting-form.d.ts +45 -0
- package/dist-client/components/training-setting-form.js +175 -0
- package/dist-client/components/training-setting-form.js.map +1 -0
- package/dist-client/index.d.ts +4 -0
- package/dist-client/index.js +8 -0
- package/dist-client/index.js.map +1 -0
- package/dist-client/legal-record/dispute-calendar-source.d.ts +1 -0
- package/dist-client/legal-record/dispute-calendar-source.js +51 -0
- package/dist-client/legal-record/dispute-calendar-source.js.map +1 -0
- package/dist-client/legal-record/legal-child-grid.d.ts +31 -0
- package/dist-client/legal-record/legal-child-grid.js +76 -0
- package/dist-client/legal-record/legal-child-grid.js.map +1 -0
- package/dist-client/legal-record/legal-codes.d.ts +3 -0
- package/dist-client/legal-record/legal-codes.js +5 -0
- package/dist-client/legal-record/legal-codes.js.map +1 -0
- package/dist-client/legal-record/legal-grid.d.ts +59 -0
- package/dist-client/legal-record/legal-grid.js +68 -0
- package/dist-client/legal-record/legal-grid.js.map +1 -0
- package/dist-client/legal-record/legal-record-files.d.ts +26 -0
- package/dist-client/legal-record/legal-record-files.js +162 -0
- package/dist-client/legal-record/legal-record-files.js.map +1 -0
- package/dist-client/pages/compliance-case-list.d.ts +35 -0
- package/dist-client/pages/compliance-case-list.js +112 -0
- package/dist-client/pages/compliance-case-list.js.map +1 -0
- package/dist-client/pages/dispute-list.d.ts +57 -0
- package/dist-client/pages/dispute-list.js +156 -0
- package/dist-client/pages/dispute-list.js.map +1 -0
- package/dist-client/pages/invention-list.d.ts +57 -0
- package/dist-client/pages/invention-list.js +166 -0
- package/dist-client/pages/invention-list.js.map +1 -0
- package/dist-client/pages/legal-registration-list.d.ts +63 -0
- package/dist-client/pages/legal-registration-list.js +206 -0
- package/dist-client/pages/legal-registration-list.js.map +1 -0
- package/dist-client/pages/privacy-record-list.d.ts +52 -0
- package/dist-client/pages/privacy-record-list.js +225 -0
- package/dist-client/pages/privacy-record-list.js.map +1 -0
- package/dist-client/pages/regulation-detail.d.ts +43 -0
- package/dist-client/pages/regulation-detail.js +370 -0
- package/dist-client/pages/regulation-detail.js.map +1 -0
- package/dist-client/pages/regulation-list.d.ts +52 -0
- package/dist-client/pages/regulation-list.js +144 -0
- package/dist-client/pages/regulation-list.js.map +1 -0
- package/dist-client/pages/regulations.d.ts +49 -0
- package/dist-client/pages/regulations.js +412 -0
- package/dist-client/pages/regulations.js.map +1 -0
- package/dist-client/pages/seal-use-list.d.ts +57 -0
- package/dist-client/pages/seal-use-list.js +168 -0
- package/dist-client/pages/seal-use-list.js.map +1 -0
- package/dist-client/pages/training-list.d.ts +51 -0
- package/dist-client/pages/training-list.js +333 -0
- package/dist-client/pages/training-list.js.map +1 -0
- package/dist-client/regulation/regulation-fragments.d.ts +1 -0
- package/dist-client/regulation/regulation-fragments.js +37 -0
- package/dist-client/regulation/regulation-fragments.js.map +1 -0
- package/dist-client/regulation/regulation-guide.d.ts +19 -0
- package/dist-client/regulation/regulation-guide.js +48 -0
- package/dist-client/regulation/regulation-guide.js.map +1 -0
- package/dist-client/route.d.ts +1 -0
- package/dist-client/route.js +36 -0
- package/dist-client/route.js.map +1 -0
- package/dist-client/training-words.d.ts +11 -0
- package/dist-client/training-words.js +11 -0
- package/dist-client/training-words.js.map +1 -0
- package/dist-client/tsconfig.tsbuildinfo +1 -0
- package/dist-client/words.d.ts +15 -0
- package/dist-client/words.js +20 -0
- package/dist-client/words.js.map +1 -0
- package/dist-server/activities/index.d.ts +6 -0
- package/dist-server/activities/index.js +37 -0
- package/dist-server/activities/index.js.map +1 -0
- package/dist-server/activities/legal-todo-activity-names.d.ts +1 -0
- package/dist-server/activities/legal-todo-activity-names.js +5 -0
- package/dist-server/activities/legal-todo-activity-names.js.map +1 -0
- package/dist-server/activities/legal-todo-activity.d.ts +37 -0
- package/dist-server/activities/legal-todo-activity.js +41 -0
- package/dist-server/activities/legal-todo-activity.js.map +1 -0
- package/dist-server/activities/regulation-activity-names.d.ts +1 -0
- package/dist-server/activities/regulation-activity-names.js +5 -0
- package/dist-server/activities/regulation-activity-names.js.map +1 -0
- package/dist-server/activities/regulation-activity.d.ts +48 -0
- package/dist-server/activities/regulation-activity.js +47 -0
- package/dist-server/activities/regulation-activity.js.map +1 -0
- package/dist-server/activities/seal-use-activity-names.d.ts +2 -0
- package/dist-server/activities/seal-use-activity-names.js +6 -0
- package/dist-server/activities/seal-use-activity-names.js.map +1 -0
- package/dist-server/activities/seal-use-activity.d.ts +48 -0
- package/dist-server/activities/seal-use-activity.js +47 -0
- package/dist-server/activities/seal-use-activity.js.map +1 -0
- package/dist-server/index.d.ts +2 -0
- package/dist-server/index.js +21 -0
- package/dist-server/index.js.map +1 -0
- package/dist-server/role-tasks.d.ts +2 -0
- package/dist-server/role-tasks.js +28 -0
- package/dist-server/role-tasks.js.map +1 -0
- package/dist-server/service/common/correction.d.ts +32 -0
- package/dist-server/service/common/correction.js +98 -0
- package/dist-server/service/common/correction.js.map +1 -0
- package/dist-server/service/common/employee-reader.d.ts +11 -0
- package/dist-server/service/common/employee-reader.js +46 -0
- package/dist-server/service/common/employee-reader.js.map +1 -0
- package/dist-server/service/common/legal-todo.d.ts +19 -0
- package/dist-server/service/common/legal-todo.js +37 -0
- package/dist-server/service/common/legal-todo.js.map +1 -0
- package/dist-server/service/compliance-case/compliance-case-mutation.d.ts +9 -0
- package/dist-server/service/compliance-case/compliance-case-mutation.js +73 -0
- package/dist-server/service/compliance-case/compliance-case-mutation.js.map +1 -0
- package/dist-server/service/compliance-case/compliance-case-query.d.ts +28 -0
- package/dist-server/service/compliance-case/compliance-case-query.js +212 -0
- package/dist-server/service/compliance-case/compliance-case-query.js.map +1 -0
- package/dist-server/service/compliance-case/compliance-case-rules.d.ts +71 -0
- package/dist-server/service/compliance-case/compliance-case-rules.js +111 -0
- package/dist-server/service/compliance-case/compliance-case-rules.js.map +1 -0
- package/dist-server/service/compliance-case/compliance-case-service.d.ts +76 -0
- package/dist-server/service/compliance-case/compliance-case-service.js +425 -0
- package/dist-server/service/compliance-case/compliance-case-service.js.map +1 -0
- package/dist-server/service/compliance-case/compliance-case-type.d.ts +56 -0
- package/dist-server/service/compliance-case/compliance-case-type.js +200 -0
- package/dist-server/service/compliance-case/compliance-case-type.js.map +1 -0
- package/dist-server/service/compliance-case/compliance-case.d.ts +76 -0
- package/dist-server/service/compliance-case/compliance-case.js +260 -0
- package/dist-server/service/compliance-case/compliance-case.js.map +1 -0
- package/dist-server/service/compliance-case/index.d.ts +7 -0
- package/dist-server/service/compliance-case/index.js +17 -0
- package/dist-server/service/compliance-case/index.js.map +1 -0
- package/dist-server/service/dispute/dispute-mutation.d.ts +9 -0
- package/dist-server/service/dispute/dispute-mutation.js +64 -0
- package/dist-server/service/dispute/dispute-mutation.js.map +1 -0
- package/dist-server/service/dispute/dispute-query.d.ts +16 -0
- package/dist-server/service/dispute/dispute-query.js +124 -0
- package/dist-server/service/dispute/dispute-query.js.map +1 -0
- package/dist-server/service/dispute/dispute-rules.d.ts +20 -0
- package/dist-server/service/dispute/dispute-rules.js +42 -0
- package/dist-server/service/dispute/dispute-rules.js.map +1 -0
- package/dist-server/service/dispute/dispute-service.d.ts +17 -0
- package/dist-server/service/dispute/dispute-service.js +96 -0
- package/dist-server/service/dispute/dispute-service.js.map +1 -0
- package/dist-server/service/dispute/dispute-type.d.ts +39 -0
- package/dist-server/service/dispute/dispute-type.js +149 -0
- package/dist-server/service/dispute/dispute-type.js.map +1 -0
- package/dist-server/service/dispute/dispute.d.ts +48 -0
- package/dist-server/service/dispute/dispute.js +198 -0
- package/dist-server/service/dispute/dispute.js.map +1 -0
- package/dist-server/service/dispute/index.d.ts +5 -0
- package/dist-server/service/dispute/index.js +9 -0
- package/dist-server/service/dispute/index.js.map +1 -0
- package/dist-server/service/index.d.ts +46 -0
- package/dist-server/service/index.js +112 -0
- package/dist-server/service/index.js.map +1 -0
- package/dist-server/service/invention/index.d.ts +5 -0
- package/dist-server/service/invention/index.js +9 -0
- package/dist-server/service/invention/index.js.map +1 -0
- package/dist-server/service/invention/invention-mutation.d.ts +10 -0
- package/dist-server/service/invention/invention-mutation.js +79 -0
- package/dist-server/service/invention/invention-mutation.js.map +1 -0
- package/dist-server/service/invention/invention-query.d.ts +18 -0
- package/dist-server/service/invention/invention-query.js +141 -0
- package/dist-server/service/invention/invention-query.js.map +1 -0
- package/dist-server/service/invention/invention-rules.d.ts +28 -0
- package/dist-server/service/invention/invention-rules.js +54 -0
- package/dist-server/service/invention/invention-rules.js.map +1 -0
- package/dist-server/service/invention/invention-service.d.ts +25 -0
- package/dist-server/service/invention/invention-service.js +176 -0
- package/dist-server/service/invention/invention-service.js.map +1 -0
- package/dist-server/service/invention/invention-type.d.ts +34 -0
- package/dist-server/service/invention/invention-type.js +129 -0
- package/dist-server/service/invention/invention-type.js.map +1 -0
- package/dist-server/service/invention/invention.d.ts +55 -0
- package/dist-server/service/invention/invention.js +214 -0
- package/dist-server/service/invention/invention.js.map +1 -0
- package/dist-server/service/legal-record/attachment-readers.d.ts +1 -0
- package/dist-server/service/legal-record/attachment-readers.js +27 -0
- package/dist-server/service/legal-record/attachment-readers.js.map +1 -0
- package/dist-server/service/legal-record/days.d.ts +7 -0
- package/dist-server/service/legal-record/days.js +26 -0
- package/dist-server/service/legal-record/days.js.map +1 -0
- package/dist-server/service/legal-record/legal-record-support.d.ts +24 -0
- package/dist-server/service/legal-record/legal-record-support.js +51 -0
- package/dist-server/service/legal-record/legal-record-support.js.map +1 -0
- package/dist-server/service/legal-registration/centre-change-subscriber.d.ts +9 -0
- package/dist-server/service/legal-registration/centre-change-subscriber.js +50 -0
- package/dist-server/service/legal-registration/centre-change-subscriber.js.map +1 -0
- package/dist-server/service/legal-registration/index.d.ts +7 -0
- package/dist-server/service/legal-registration/index.js +11 -0
- package/dist-server/service/legal-registration/index.js.map +1 -0
- package/dist-server/service/legal-registration/legal-registration-mutation.d.ts +10 -0
- package/dist-server/service/legal-registration/legal-registration-mutation.js +77 -0
- package/dist-server/service/legal-registration/legal-registration-mutation.js.map +1 -0
- package/dist-server/service/legal-registration/legal-registration-query.d.ts +26 -0
- package/dist-server/service/legal-registration/legal-registration-query.js +199 -0
- package/dist-server/service/legal-registration/legal-registration-query.js.map +1 -0
- package/dist-server/service/legal-registration/legal-registration-rules.d.ts +80 -0
- package/dist-server/service/legal-registration/legal-registration-rules.js +106 -0
- package/dist-server/service/legal-registration/legal-registration-rules.js.map +1 -0
- package/dist-server/service/legal-registration/legal-registration-service.d.ts +37 -0
- package/dist-server/service/legal-registration/legal-registration-service.js +248 -0
- package/dist-server/service/legal-registration/legal-registration-service.js.map +1 -0
- package/dist-server/service/legal-registration/legal-registration-type.d.ts +55 -0
- package/dist-server/service/legal-registration/legal-registration-type.js +207 -0
- package/dist-server/service/legal-registration/legal-registration-type.js.map +1 -0
- package/dist-server/service/legal-registration/legal-registration.d.ts +76 -0
- package/dist-server/service/legal-registration/legal-registration.js +311 -0
- package/dist-server/service/legal-registration/legal-registration.js.map +1 -0
- package/dist-server/service/privacy-record/index.d.ts +5 -0
- package/dist-server/service/privacy-record/index.js +9 -0
- package/dist-server/service/privacy-record/index.js.map +1 -0
- package/dist-server/service/privacy-record/privacy-record-mutation.d.ts +7 -0
- package/dist-server/service/privacy-record/privacy-record-mutation.js +56 -0
- package/dist-server/service/privacy-record/privacy-record-mutation.js.map +1 -0
- package/dist-server/service/privacy-record/privacy-record-query.d.ts +18 -0
- package/dist-server/service/privacy-record/privacy-record-query.js +131 -0
- package/dist-server/service/privacy-record/privacy-record-query.js.map +1 -0
- package/dist-server/service/privacy-record/privacy-record-rules.d.ts +52 -0
- package/dist-server/service/privacy-record/privacy-record-rules.js +91 -0
- package/dist-server/service/privacy-record/privacy-record-rules.js.map +1 -0
- package/dist-server/service/privacy-record/privacy-record-service.d.ts +33 -0
- package/dist-server/service/privacy-record/privacy-record-service.js +133 -0
- package/dist-server/service/privacy-record/privacy-record-service.js.map +1 -0
- package/dist-server/service/privacy-record/privacy-record-type.d.ts +40 -0
- package/dist-server/service/privacy-record/privacy-record-type.js +153 -0
- package/dist-server/service/privacy-record/privacy-record-type.js.map +1 -0
- package/dist-server/service/privacy-record/privacy-record.d.ts +69 -0
- package/dist-server/service/privacy-record/privacy-record.js +245 -0
- package/dist-server/service/privacy-record/privacy-record.js.map +1 -0
- package/dist-server/service/regulation/index.d.ts +5 -0
- package/dist-server/service/regulation/index.js +9 -0
- package/dist-server/service/regulation/index.js.map +1 -0
- package/dist-server/service/regulation/regulation-mutation.d.ts +15 -0
- package/dist-server/service/regulation/regulation-mutation.js +152 -0
- package/dist-server/service/regulation/regulation-mutation.js.map +1 -0
- package/dist-server/service/regulation/regulation-query.d.ts +10 -0
- package/dist-server/service/regulation/regulation-query.js +109 -0
- package/dist-server/service/regulation/regulation-query.js.map +1 -0
- package/dist-server/service/regulation/regulation-read.d.ts +21 -0
- package/dist-server/service/regulation/regulation-read.js +111 -0
- package/dist-server/service/regulation/regulation-read.js.map +1 -0
- package/dist-server/service/regulation/regulation-rules.d.ts +83 -0
- package/dist-server/service/regulation/regulation-rules.js +170 -0
- package/dist-server/service/regulation/regulation-rules.js.map +1 -0
- package/dist-server/service/regulation/regulation-service.d.ts +63 -0
- package/dist-server/service/regulation/regulation-service.js +365 -0
- package/dist-server/service/regulation/regulation-service.js.map +1 -0
- package/dist-server/service/regulation/regulation-type.d.ts +61 -0
- package/dist-server/service/regulation/regulation-type.js +226 -0
- package/dist-server/service/regulation/regulation-type.js.map +1 -0
- package/dist-server/service/regulation/regulation.d.ts +113 -0
- package/dist-server/service/regulation/regulation.js +382 -0
- package/dist-server/service/regulation/regulation.js.map +1 -0
- package/dist-server/service/seal-use/index.d.ts +5 -0
- package/dist-server/service/seal-use/index.js +9 -0
- package/dist-server/service/seal-use/index.js.map +1 -0
- package/dist-server/service/seal-use/seal-use-mutation.d.ts +8 -0
- package/dist-server/service/seal-use/seal-use-mutation.js +69 -0
- package/dist-server/service/seal-use/seal-use-mutation.js.map +1 -0
- package/dist-server/service/seal-use/seal-use-query.d.ts +14 -0
- package/dist-server/service/seal-use/seal-use-query.js +100 -0
- package/dist-server/service/seal-use/seal-use-query.js.map +1 -0
- package/dist-server/service/seal-use/seal-use-rules.d.ts +35 -0
- package/dist-server/service/seal-use/seal-use-rules.js +64 -0
- package/dist-server/service/seal-use/seal-use-rules.js.map +1 -0
- package/dist-server/service/seal-use/seal-use-service.d.ts +20 -0
- package/dist-server/service/seal-use/seal-use-service.js +168 -0
- package/dist-server/service/seal-use/seal-use-service.js.map +1 -0
- package/dist-server/service/seal-use/seal-use-type.d.ts +24 -0
- package/dist-server/service/seal-use/seal-use-type.js +93 -0
- package/dist-server/service/seal-use/seal-use-type.js.map +1 -0
- package/dist-server/service/seal-use/seal-use.d.ts +57 -0
- package/dist-server/service/seal-use/seal-use.js +181 -0
- package/dist-server/service/seal-use/seal-use.js.map +1 -0
- package/dist-server/service/training/index.d.ts +5 -0
- package/dist-server/service/training/index.js +9 -0
- package/dist-server/service/training/index.js.map +1 -0
- package/dist-server/service/training/training-checks.d.ts +20 -0
- package/dist-server/service/training/training-checks.js +56 -0
- package/dist-server/service/training/training-checks.js.map +1 -0
- package/dist-server/service/training/training-kinds.d.ts +24 -0
- package/dist-server/service/training/training-kinds.js +42 -0
- package/dist-server/service/training/training-kinds.js.map +1 -0
- package/dist-server/service/training/training-mutation.d.ts +14 -0
- package/dist-server/service/training/training-mutation.js +140 -0
- package/dist-server/service/training/training-mutation.js.map +1 -0
- package/dist-server/service/training/training-query.d.ts +21 -0
- package/dist-server/service/training/training-query.js +196 -0
- package/dist-server/service/training/training-query.js.map +1 -0
- package/dist-server/service/training/training-read.d.ts +5 -0
- package/dist-server/service/training/training-read.js +35 -0
- package/dist-server/service/training/training-read.js.map +1 -0
- package/dist-server/service/training/training-register.d.ts +1 -0
- package/dist-server/service/training/training-register.js +27 -0
- package/dist-server/service/training/training-register.js.map +1 -0
- package/dist-server/service/training/training-rules.d.ts +72 -0
- package/dist-server/service/training/training-rules.js +143 -0
- package/dist-server/service/training/training-rules.js.map +1 -0
- package/dist-server/service/training/training-service.d.ts +98 -0
- package/dist-server/service/training/training-service.js +455 -0
- package/dist-server/service/training/training-service.js.map +1 -0
- package/dist-server/service/training/training-type.d.ts +94 -0
- package/dist-server/service/training/training-type.js +352 -0
- package/dist-server/service/training/training-type.js.map +1 -0
- package/dist-server/service/training/training.d.ts +127 -0
- package/dist-server/service/training/training.js +448 -0
- package/dist-server/service/training/training.js.map +1 -0
- package/dist-server/tsconfig.tsbuildinfo +1 -0
- package/package.json +61 -4
- package/things-factory.config.js +19 -0
- package/translations/en.json +612 -0
- package/translations/es.json +612 -0
- package/translations/ja.json +612 -0
- package/translations/ko.json +612 -0
- package/translations/ms.json +612 -0
- package/translations/zh.json +612 -0
- package/README.md +0 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"training-rules.js","sourceRoot":"","sources":["../../../server/service/training/training-rules.ts"],"names":[],"mappings":";;AAsCA,sDAYC;AAGD,0CAIC;AAGD,4CAEC;AAGD,sCAcC;AAUD,wDAKC;AAYD,wDASC;AAaD,gEAkBC;AAGD,0DAIC;AAGD,0DAEC;AAKD,0CAIC;AAGD,wDAGC;AA7KD,iDAA+C;AAE/C,qDAA+F;AAE/F;;;GAGG;AAEH,MAAM,GAAG,GAAG,qBAAqB,CAAA;AACjC,MAAM,OAAO,GAAG,yCAAyC,CAAA;AACzD,MAAM,OAAO,GAAG,CAAC,YAAY,EAAE,QAAQ,EAAE,YAAY,EAAE,QAAQ,CAAC,CAAA;AAChE,MAAM,MAAM,GAAG,CAAC,QAAQ,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;AA0BvD,SAAgB,qBAAqB,CAAC,IAAmC;IACvE,OAAO,IAAA,8BAAa,GAAE,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE;QAChC,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,CAAC,CAAA;QACpD,OAAO;YACL,GAAG,IAAI;YACP,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS;YAC7C,MAAM,EAAE,GAAG,EAAE,MAAM,IAAI,gCAAe;YACtC,SAAS,EAAE,GAAG,EAAE,SAAS,IAAI,EAAE;YAC/B,IAAI,EAAE,GAAG,EAAE,IAAI,IAAI,IAAI;YACvB,GAAG,EAAE,CAAC,CAAC,GAAG;SACX,CAAA;IACH,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,wFAAwF;AACxF,SAAgB,eAAe,CAAC,IAAY,EAAE,MAAc;IAC1D,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC,GAAG,CAAC,MAAM,IAAI,gCAAe,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;IACvE,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,UAAU,EAAE,CAAA;IAC5D,OAAO,GAAG,IAAI,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAA;AACpG,CAAC;AAED,8EAA8E;AAC9E,SAAgB,gBAAgB,CAAC,KAAa,EAAE,GAAW;IACzD,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,YAAY,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,KAAK,YAAY,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAA;AACnG,CAAC;AAED,gHAAgH;AAChH,SAAgB,aAAa,CAAC,GAAW,EAAE,QAAwB;IACjE,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,YAAY,CAAC,CAAA;IAC1C,IAAI,CAAC,QAAQ;QAAE,OAAO,IAAI,IAAI,CAAC,GAAG,CAAC,CAAA;IACnC,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,CAAC,WAAW,CAC9B,IAAI,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;aACjL,aAAa,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC;aAC5B,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CACxC,CAAA;QACD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,GAAG,IAAI,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,CAAA;QACpH,OAAO,IAAI,IAAI,CAAC,GAAG,GAAG,CAAC,KAAK,GAAG,GAAG,CAAC,CAAC,CAAA;IACtC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,IAAI,CAAC,GAAG,CAAC,CAAA;IACtB,CAAC;AACH,CAAC;AASD,uEAAuE;AACvE,SAAgB,sBAAsB,CAAC,KAA2B;IAChE,IAAI,CAAC,IAAA,+BAAc,EAAC,KAAK,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,eAAO,CAAC,uBAAuB,EAAE,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,EAAE,EAAE,EAAE,iCAAiC,KAAK,CAAC,IAAI,IAAI,EAAE,GAAG,CAAC,CAAA;IAC9J,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC;QAAE,OAAO,IAAI,eAAO,CAAC,0BAA0B,EAAE,EAAE,EAAE,8CAA8C,CAAC,CAAA;IACnJ,IAAI,CAAC,KAAK,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC;QAAE,OAAO,IAAI,eAAO,CAAC,2BAA2B,EAAE,EAAE,EAAE,0DAA0D,CAAC,CAAA;IACvM,OAAO,IAAI,CAAA;AACb,CAAC;AAWD,mGAAmG;AACnG,SAAgB,sBAAsB,CAAC,KAA4B,EAAE,IAAiC;IACpG,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,eAAO,CAAC,uBAAuB,EAAE,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,EAAE,EAAE,EAAE,iCAAiC,KAAK,CAAC,IAAI,IAAI,EAAE,GAAG,CAAC,CAAA;IACxI,IAAI,CAAC,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,eAAO,CAAC,mBAAmB,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,EAAE,gBAAgB,IAAI,CAAC,IAAI,2CAA2C,CAAC,CAAA;IACrJ,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,IAAK,KAAK,CAAC,IAAe,GAAG,IAAI,IAAK,KAAK,CAAC,IAAe,GAAG,IAAI;QAAE,OAAO,IAAI,eAAO,CAAC,uBAAuB,EAAE,EAAE,EAAE,wCAAwC,CAAC,CAAA;IAC9L,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE;QAAE,OAAO,IAAI,eAAO,CAAC,gCAAgC,EAAE,EAAE,EAAE,yBAAyB,CAAC,CAAA;IAC5G,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,IAAI,EAAE,CAAC;QAAE,OAAO,IAAI,eAAO,CAAC,0BAA0B,EAAE,EAAE,EAAE,sBAAsB,CAAC,CAAA;IACrH,IAAI,CAAC,KAAK,CAAC,QAAQ,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC;QAAE,OAAO,IAAI,eAAO,CAAC,6BAA6B,EAAE,EAAE,EAAE,sCAAsC,CAAC,CAAA;IAC/I,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,MAAM,GAAG,KAAK,CAAC,QAAQ,CAAC;QAAE,OAAO,IAAI,eAAO,CAAC,4BAA4B,EAAE,EAAE,EAAE,mDAAmD,CAAC,CAAA;IACzL,OAAO,IAAI,CAAA;AACb,CAAC;AASD;;;GAGG;AACH,SAAgB,0BAA0B,CACxC,KAAgC,EAChC,IAAiC,EACjC,KAAa,EACb,KAAa,EACb,IAAmC;IAEnC,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,eAAO,CAAC,uBAAuB,EAAE,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,EAAE,EAAE,EAAE,iCAAiC,KAAK,CAAC,IAAI,IAAI,EAAE,GAAG,CAAC,CAAA;IACxI,IAAI,CAAC,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,eAAO,CAAC,mBAAmB,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,EAAE,gBAAgB,IAAI,CAAC,IAAI,wBAAwB,CAAC,CAAA;IAClI,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,eAAO,CAAC,uBAAuB,EAAE,EAAE,EAAE,wCAAwC,CAAC,CAAA;IAC5H,IAAI,CAAC,KAAK,CAAC,WAAW,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC;QAAE,OAAO,IAAI,eAAO,CAAC,gCAAgC,EAAE,EAAE,EAAE,2CAA2C,CAAC,CAAA;IAC7J,IAAI,KAAK,CAAC,WAAW,GAAG,KAAK;QAAE,OAAO,IAAI,eAAO,CAAC,8BAA8B,EAAE,EAAE,KAAK,EAAE,EAAE,4CAA4C,KAAK,EAAE,CAAC,CAAA;IACjJ,IAAI,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,IAAI;QAAE,OAAO,IAAI,eAAO,CAAC,iCAAiC,EAAE,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,EAAE,8BAA8B,KAAK,CAAC,IAAI,0BAA0B,CAAC,CAAA;IACzM,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,IAAI,EAAE,CAAC;QAAE,OAAO,IAAI,eAAO,CAAC,0BAA0B,EAAE,EAAE,EAAE,sBAAsB,CAAC,CAAA;IACrH,IAAI,KAAK,GAAG,CAAC;QAAE,OAAO,IAAI,eAAO,CAAC,+BAA+B,EAAE,EAAE,EAAE,2CAA2C,CAAC,CAAA;IACnH,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,MAAM,KAAK,WAAW,CAAC;QAAE,OAAO,IAAI,eAAO,CAAC,uBAAuB,EAAE,EAAE,EAAE,iDAAiD,CAAC,CAAA;IACpJ,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,MAAM,KAAK,WAAW,CAAC;QAAE,OAAO,IAAI,eAAO,CAAC,4BAA4B,EAAE,EAAE,EAAE,wDAAwD,CAAC,CAAA;IAChK,OAAO,IAAI,CAAA;AACb,CAAC;AAED,wEAAwE;AACxE,SAAgB,uBAAuB,CAAC,MAAc,EAAE,QAAkC,EAAE,MAAsB;IAChH,IAAI,MAAM,KAAK,WAAW;QAAE,OAAO,IAAI,eAAO,CAAC,wBAAwB,EAAE,EAAE,EAAE,qEAAqE,CAAC,CAAA;IACnJ,IAAI,QAAQ,KAAK,UAAU,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE;QAAE,OAAO,IAAI,eAAO,CAAC,iCAAiC,EAAE,EAAE,EAAE,wBAAwB,CAAC,CAAA;IACnI,OAAO,IAAI,CAAA;AACb,CAAC;AAED,0GAA0G;AAC1G,SAAgB,uBAAuB;IACrC,OAAO,IAAI,eAAO,CAAC,sBAAsB,EAAE,EAAE,EAAE,8FAA8F,CAAC,CAAA;AAChJ,CAAC;AAKD,SAAgB,eAAe,CAAC,WAA0C;IACxE,IAAI,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,MAAM,KAAK,WAAW,CAAC;QAAE,OAAO,MAAM,CAAA;IACtE,IAAI,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,MAAM,KAAK,WAAW,CAAC;QAAE,OAAO,SAAS,CAAA;IACzE,OAAO,UAAU,CAAA;AACnB,CAAC;AAED,gFAAgF;AAChF,SAAgB,sBAAsB,CAAC,MAAgC;IACrE,IAAI,CAAC,MAAM,CAAC,MAAM;QAAE,OAAO,IAAI,CAAA;IAC/B,OAAO,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,MAAM,CAAC,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAA;AACpE,CAAC","sourcesContent":["import { Refusal } from '@things-factory/shell'\n\nimport { DEFAULT_DUE_DAY, TrainingKind, trainingKindOf, trainingKinds } from './training-kinds'\n\n/*\n * What the mandatory trainings hold to (ADR-0187 decisions 15-19). Pure - the service reads the rows and the people,\n * these say yes or no, and work out where each person stands.\n */\n\nconst DAY = /^\\d{4}-\\d{2}-\\d{2}$/\nconst DUE_DAY = /^(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$/\nconst METHODS = ['COLLECTIVE', 'ONLINE', 'OUTSOURCED', 'POSTED']\nconst SCOPES = ['DOMAIN', 'DEPARTMENT', 'ROLE', 'USER']\n\nexport interface TrainingTarget {\n scope: string\n targetId: string\n targetName?: string | null\n}\n\nexport interface TrainingSettingRow {\n kind: string\n enabled?: boolean | null\n dueDay?: string | null\n audiences?: TrainingTarget[] | null\n memo?: string | null\n}\n\n/** A kind as a domain has it: the declaration with the domain's own word over it. */\nexport interface TrainingKindSet extends TrainingKind {\n enabled: boolean\n dueDay: string\n audiences: TrainingTarget[]\n memo?: string | null\n /** Whether the domain has said anything - a kind with no row reads as declared. */\n set: boolean\n}\n\nexport function trainingKindsInDomain(rows: readonly TrainingSettingRow[]): TrainingKindSet[] {\n return trainingKinds().map(kind => {\n const row = rows.find(one => one.kind === kind.code)\n return {\n ...kind,\n enabled: row ? !!row.enabled : kind.defaultOn,\n dueDay: row?.dueDay || DEFAULT_DUE_DAY,\n audiences: row?.audiences ?? [],\n memo: row?.memo ?? null,\n set: !!row\n }\n })\n}\n\n/** The day a kind is due in a year - Feb 29 falls to Feb 28 in a year that has none. */\nexport function trainingDueOnOf(year: number, dueDay: string): string {\n const [month, day] = (dueDay || DEFAULT_DUE_DAY).split('-').map(Number)\n const last = new Date(Date.UTC(year, month, 0)).getUTCDate()\n return `${year}-${String(month).padStart(2, '0')}-${String(Math.min(day, last)).padStart(2, '0')}`\n}\n\n/** Days from `today` to `day`, both YYYY-MM-DD - negative when it is past. */\nexport function trainingDaysLeft(today: string, day: string): number {\n return Math.round((Date.parse(`${day}T00:00:00Z`) - Date.parse(`${today}T00:00:00Z`)) / 86400000)\n}\n\n/** The instant a calendar day ends in a time zone - a to-do due on a day is due until its last second there. */\nexport function trainingDueAt(day: string, timezone?: string | null): Date {\n const utc = Date.parse(`${day}T23:59:59Z`)\n if (!timezone) return new Date(utc)\n try {\n const parts = Object.fromEntries(\n new Intl.DateTimeFormat('en-US', { timeZone: timezone, hourCycle: 'h23', year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' })\n .formatToParts(new Date(utc))\n .map(part => [part.type, part.value])\n )\n const shown = Date.parse(`${parts.year}-${parts.month}-${parts.day}T${parts.hour}:${parts.minute}:${parts.second}Z`)\n return new Date(utc - (shown - utc))\n } catch {\n return new Date(utc)\n }\n}\n\nexport interface TrainingSettingInput {\n kind?: string | null\n enabled?: boolean | null\n dueDay?: string | null\n audiences?: TrainingTarget[] | null\n}\n\n/** What a kind's setting asks for before it is written, or nothing. */\nexport function trainingSettingRefusal(input: TrainingSettingInput): Refusal | null {\n if (!trainingKindOf(input.kind)) return new Refusal('training-kind-unknown', { kind: input.kind ?? '' }, `there is no kind of training '${input.kind ?? ''}'`)\n if (input.dueDay && !DUE_DAY.test(input.dueDay)) return new Refusal('training-due-day-invalid', {}, 'give the due day of the year as MM-DD, 12-31')\n if ((input.audiences ?? []).some(one => !SCOPES.includes(one?.scope) || !one.targetId)) return new Refusal('training-audience-invalid', {}, 'a target is the domain, a department, a role or a person')\n return null\n}\n\nexport interface TrainingSessionFields {\n kind?: string | null\n year?: number | null\n name?: string | null\n method?: string | null\n startsOn?: string | null\n endsOn?: string | null\n}\n\n/** What a session asks for before it is opened, or nothing - the kind must be on in the domain. */\nexport function trainingSessionRefusal(input: TrainingSessionFields, kind: TrainingKindSet | undefined): Refusal | null {\n if (!kind) return new Refusal('training-kind-unknown', { kind: input.kind ?? '' }, `there is no kind of training '${input.kind ?? ''}'`)\n if (!kind.enabled) return new Refusal('training-kind-off', { kind: kind.code }, `the training ${kind.code} is off in this domain - turn it on first`)\n if (!Number.isInteger(input.year) || (input.year as number) < 2000 || (input.year as number) > 2999) return new Refusal('training-year-invalid', {}, 'give the year it counts for, 2000-2999')\n if (!input.name?.trim()) return new Refusal('training-session-name-required', {}, 'give the session a name')\n if (!METHODS.includes(input.method ?? '')) return new Refusal('training-method-required', {}, 'pick how it is given')\n if (!input.startsOn || !DAY.test(input.startsOn)) return new Refusal('training-starts-on-required', {}, 'give the day it is given, YYYY-MM-DD')\n if (input.endsOn && (!DAY.test(input.endsOn) || input.endsOn < input.startsOn)) return new Refusal('training-ends-before-start', {}, 'the last day is on or after the first, YYYY-MM-DD')\n return null\n}\n\nexport interface TrainingCertificateFields {\n kind?: string | null\n year?: number | null\n completedOn?: string | null\n method?: string | null\n}\n\n/**\n * What a certificate a person sends asks for, or nothing: a kind on in the domain, the day completed within the year it\n * counts for and not after today, a file, and no completion of it already done or waiting.\n */\nexport function trainingCertificateRefusal(\n input: TrainingCertificateFields,\n kind: TrainingKindSet | undefined,\n today: string,\n files: number,\n mine: readonly { status: string }[]\n): Refusal | null {\n if (!kind) return new Refusal('training-kind-unknown', { kind: input.kind ?? '' }, `there is no kind of training '${input.kind ?? ''}'`)\n if (!kind.enabled) return new Refusal('training-kind-off', { kind: kind.code }, `the training ${kind.code} is off in this domain`)\n if (!Number.isInteger(input.year)) return new Refusal('training-year-invalid', {}, 'give the year it counts for, 2000-2999')\n if (!input.completedOn || !DAY.test(input.completedOn)) return new Refusal('training-completed-on-required', {}, 'give the day it was completed, YYYY-MM-DD')\n if (input.completedOn > today) return new Refusal('training-completed-on-future', { today }, `the day completed is on or before today, ${today}`)\n if (Number(input.completedOn.slice(0, 4)) !== input.year) return new Refusal('training-completed-outside-year', { year: input.year }, `the day completed falls in ${input.year}, the year it counts for`)\n if (!METHODS.includes(input.method ?? '')) return new Refusal('training-method-required', {}, 'pick how it is given')\n if (files < 1) return new Refusal('training-certificate-required', {}, 'attach the certificate, one file at least')\n if (mine.some(one => one.status === 'CONFIRMED')) return new Refusal('training-already-done', {}, 'this training is completed for the year already')\n if (mine.some(one => one.status === 'SUBMITTED')) return new Refusal('training-already-submitted', {}, 'a certificate of it is waiting for the manager already')\n return null\n}\n\n/** Only a certificate sent and not decided is confirmed or returned. */\nexport function trainingDecisionRefusal(status: string, decision: 'CONFIRMED' | 'RETURNED', reason?: string | null): Refusal | null {\n if (status !== 'SUBMITTED') return new Refusal('training-not-submitted', {}, 'only a certificate waiting for the manager is confirmed or returned')\n if (decision === 'RETURNED' && !reason?.trim()) return new Refusal('training-return-reason-required', {}, 'say why it is returned')\n return null\n}\n\n/** Completions, sessions and their files are kept (decision 17) - the refusal says what to do instead. */\nexport function trainingDeletionRefusal(): Refusal {\n return new Refusal('training-record-kept', {}, 'training records are kept - return a certificate, or record a new completion, to correct one')\n}\n\n/** Where a person stands on a kind in a year. Only a confirmed completion is done; a certificate sent waits. */\nexport type TrainingState = 'DONE' | 'WAITING' | 'NOT_DONE'\n\nexport function trainingStateOf(completions: readonly { status: string }[]): TrainingState {\n if (completions.some(one => one.status === 'CONFIRMED')) return 'DONE'\n if (completions.some(one => one.status === 'SUBMITTED')) return 'WAITING'\n return 'NOT_DONE'\n}\n\n/** The share of the people who are done, 0-1; null when nobody must take it. */\nexport function trainingCompletionRate(states: readonly TrainingState[]): number | null {\n if (!states.length) return null\n return states.filter(one => one === 'DONE').length / states.length\n}\n"]}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { EntityManager } from 'typeorm';
|
|
2
|
+
import { Employee } from '@things-factory/organization';
|
|
3
|
+
import { TrainingCompletion, TrainingSession, TrainingSetting } from './training';
|
|
4
|
+
import { TrainingTarget, TrainingKindSet, TrainingState } from './training-rules';
|
|
5
|
+
import { NewTrainingSession, TrainingCertificateInput, TrainingSessionPatch, TrainingSettingPatch } from './training-type';
|
|
6
|
+
export declare const TRAINING_SESSION_REF_TYPE = "TrainingSession";
|
|
7
|
+
export declare const TRAINING_COMPLETION_REF_TYPE = "TrainingCompletion";
|
|
8
|
+
export declare const TRAINING_SCHEDULED_TRIGGER = "compliance.training-scheduled";
|
|
9
|
+
/** The employee's screen of their trainings - where a to-do and an announcement lead. */
|
|
10
|
+
export declare const MY_TRAININGS_ADDRESS = "regulations?tab=trainings";
|
|
11
|
+
/** The request's transaction, or the data source's manager for a query that runs without one. */
|
|
12
|
+
export declare function trainingManagerOf(context: any): EntityManager;
|
|
13
|
+
/** The kinds as the domain has them. */
|
|
14
|
+
export declare function trainingKindsOf(tx: EntityManager, domain: {
|
|
15
|
+
id: string;
|
|
16
|
+
}): Promise<TrainingKindSet[]>;
|
|
17
|
+
/** Whom a kind or a session is for - the whole domain when nobody is named. */
|
|
18
|
+
export declare function trainingAudiencesOf(kind: TrainingKindSet, domain: {
|
|
19
|
+
id: string;
|
|
20
|
+
name?: string;
|
|
21
|
+
}, own?: TrainingTarget[] | null): TrainingTarget[];
|
|
22
|
+
/**
|
|
23
|
+
* The people who must take a kind: the employees still working here whose account the kind's audience reaches. One
|
|
24
|
+
* with no account is reached by no audience, as an announcement reaches nobody without one.
|
|
25
|
+
*/
|
|
26
|
+
export declare function trainingPeopleOf(tx: EntityManager, domain: any, audiences: TrainingTarget[], today: string): Promise<Employee[]>;
|
|
27
|
+
export interface TrainingReader {
|
|
28
|
+
/** Holds `training` query - reads every completion and its certificates. */
|
|
29
|
+
all: boolean;
|
|
30
|
+
employee: Employee | null;
|
|
31
|
+
/** The departments the reader heads, with those under them - read for whether their people are done, nothing more. */
|
|
32
|
+
headed: string[];
|
|
33
|
+
}
|
|
34
|
+
/** Who the request is to the trainings. A department head is the employee a department names as its manager. */
|
|
35
|
+
export declare function trainingReaderOf(context: any): Promise<TrainingReader>;
|
|
36
|
+
/** A kind's own word - written over the declaration, one row per kind. */
|
|
37
|
+
export declare function saveTrainingSettings(patches: TrainingSettingPatch[], context: any): Promise<TrainingSetting[]>;
|
|
38
|
+
/** Opens a session with its material, and tells its audience - in the same write. */
|
|
39
|
+
export declare function openTrainingSession(input: NewTrainingSession, attach: any[] | undefined, context: any): Promise<TrainingSession>;
|
|
40
|
+
/** What a session says may be corrected - its kind and year stay, the completions of it count for them. */
|
|
41
|
+
export declare function updateTrainingSessions(patches: TrainingSessionPatch[], context: any): Promise<TrainingSession[]>;
|
|
42
|
+
/**
|
|
43
|
+
* The attendance of a session, recorded by the manager - confirmed at once. Someone already done for the kind and year
|
|
44
|
+
* is left as they are.
|
|
45
|
+
*/
|
|
46
|
+
export declare function recordTrainingAttendance(sessionId: string, employeeIds: string[], completedOn: string | null | undefined, context: any): Promise<TrainingCompletion[]>;
|
|
47
|
+
/** A certificate the person sends for themself - waiting until the manager confirms it. */
|
|
48
|
+
export declare function submitTrainingCertificate(input: TrainingCertificateInput, attach: any[] | undefined, context: any): Promise<TrainingCompletion>;
|
|
49
|
+
/** The manager confirms certificates sent - each closes the person's to-do. */
|
|
50
|
+
export declare function confirmTrainingCompletions(ids: string[], context: any): Promise<TrainingCompletion[]>;
|
|
51
|
+
/** The manager returns a certificate, saying why - the person sends another. */
|
|
52
|
+
export declare function returnTrainingCompletion(id: string, reason: string, context: any): Promise<TrainingCompletion>;
|
|
53
|
+
/** Training records are kept (decision 17) - every delete is refused with what to do instead. */
|
|
54
|
+
export declare function deleteTrainingRecords(): Promise<never>;
|
|
55
|
+
export interface TrainingYearOpening {
|
|
56
|
+
people: number;
|
|
57
|
+
done: number;
|
|
58
|
+
handedOut: number;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Opens a kind's year: a worklist to-do 「<교육> 이수」 with the due day for everyone of its audience not done - at most
|
|
62
|
+
* one per person, kind and year, so opening it again hands out only to those it missed (a person who joined since, a
|
|
63
|
+
* to-do the domain could not hand out before).
|
|
64
|
+
*/
|
|
65
|
+
export declare function openTrainingYear(kindCode: string, year: number, context: any): Promise<TrainingYearOpening>;
|
|
66
|
+
export interface TrainingStandingRow {
|
|
67
|
+
id: string;
|
|
68
|
+
kind: string;
|
|
69
|
+
year: number;
|
|
70
|
+
dueOn: string;
|
|
71
|
+
state: TrainingState;
|
|
72
|
+
completedOn?: string | null;
|
|
73
|
+
employee: Employee;
|
|
74
|
+
departmentName?: string | null;
|
|
75
|
+
}
|
|
76
|
+
/** Where everyone of every kind on stands in a year - the people of each kind's audience, by name. */
|
|
77
|
+
export declare function standingsOf(tx: EntityManager, domain: any, year: number, today: string): Promise<TrainingStandingRow[]>;
|
|
78
|
+
/**
|
|
79
|
+
* The standings the reader may read: a holder all of them; an employee their own; a department head also whether each
|
|
80
|
+
* of their people is done - no day, no waiting certificate, nothing of its content.
|
|
81
|
+
*/
|
|
82
|
+
export declare function readableStandings(year: number, context: any): Promise<TrainingStandingRow[]>;
|
|
83
|
+
export interface MyTrainingRow {
|
|
84
|
+
kind: string;
|
|
85
|
+
year: number;
|
|
86
|
+
dueOn: string;
|
|
87
|
+
state: TrainingState;
|
|
88
|
+
completion?: TrainingCompletion | null;
|
|
89
|
+
sessions: TrainingSession[];
|
|
90
|
+
}
|
|
91
|
+
/** The request's employee's trainings of a year: each kind on whose audience they are in, with its sessions. */
|
|
92
|
+
export declare function myTrainings(year: number, context: any): Promise<MyTrainingRow[]>;
|
|
93
|
+
/** Whether the request may read a completion - a holder, or the one it is of. A department head may not. */
|
|
94
|
+
export declare function mayReadCompletion(row: {
|
|
95
|
+
employeeId?: string | null;
|
|
96
|
+
} | null, context: any): Promise<boolean>;
|
|
97
|
+
/** Whether the request may read a session's material - a holder, or an employee it is for. */
|
|
98
|
+
export declare function mayReadSession(row: TrainingSession | null, context: any): Promise<boolean>;
|
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MY_TRAININGS_ADDRESS = exports.TRAINING_SCHEDULED_TRIGGER = exports.TRAINING_COMPLETION_REF_TYPE = exports.TRAINING_SESSION_REF_TYPE = void 0;
|
|
4
|
+
exports.trainingManagerOf = trainingManagerOf;
|
|
5
|
+
exports.trainingKindsOf = trainingKindsOf;
|
|
6
|
+
exports.trainingAudiencesOf = trainingAudiencesOf;
|
|
7
|
+
exports.trainingPeopleOf = trainingPeopleOf;
|
|
8
|
+
exports.trainingReaderOf = trainingReaderOf;
|
|
9
|
+
exports.saveTrainingSettings = saveTrainingSettings;
|
|
10
|
+
exports.openTrainingSession = openTrainingSession;
|
|
11
|
+
exports.updateTrainingSessions = updateTrainingSessions;
|
|
12
|
+
exports.recordTrainingAttendance = recordTrainingAttendance;
|
|
13
|
+
exports.submitTrainingCertificate = submitTrainingCertificate;
|
|
14
|
+
exports.confirmTrainingCompletions = confirmTrainingCompletions;
|
|
15
|
+
exports.returnTrainingCompletion = returnTrainingCompletion;
|
|
16
|
+
exports.deleteTrainingRecords = deleteTrainingRecords;
|
|
17
|
+
exports.openTrainingYear = openTrainingYear;
|
|
18
|
+
exports.standingsOf = standingsOf;
|
|
19
|
+
exports.readableStandings = readableStandings;
|
|
20
|
+
exports.myTrainings = myTrainings;
|
|
21
|
+
exports.mayReadCompletion = mayReadCompletion;
|
|
22
|
+
exports.mayReadSession = mayReadSession;
|
|
23
|
+
const typeorm_1 = require("typeorm");
|
|
24
|
+
const announcement_1 = require("@things-factory/announcement");
|
|
25
|
+
const attachment_base_1 = require("@things-factory/attachment-base");
|
|
26
|
+
const translator_1 = require("@things-factory/i18n-base/translator");
|
|
27
|
+
const organization_1 = require("@things-factory/organization");
|
|
28
|
+
const employee_profile_rules_js_1 = require("@things-factory/organization/dist-server/service/employee-profile/employee-profile-rules.js");
|
|
29
|
+
const shell_1 = require("@things-factory/shell");
|
|
30
|
+
const employee_reader_1 = require("../common/employee-reader");
|
|
31
|
+
const legal_todo_1 = require("../common/legal-todo");
|
|
32
|
+
const training_1 = require("./training");
|
|
33
|
+
const training_rules_1 = require("./training-rules");
|
|
34
|
+
/*
|
|
35
|
+
* The mandatory trainings of a domain (ADR-0187 decisions 15-19): a kind's setting, a session opened and told, the
|
|
36
|
+
* attendance and certificates that complete it, the to-dos of a year, and who reads where each person stands. Nothing
|
|
37
|
+
* here deletes a training record.
|
|
38
|
+
*/
|
|
39
|
+
exports.TRAINING_SESSION_REF_TYPE = 'TrainingSession';
|
|
40
|
+
exports.TRAINING_COMPLETION_REF_TYPE = 'TrainingCompletion';
|
|
41
|
+
exports.TRAINING_SCHEDULED_TRIGGER = 'compliance.training-scheduled';
|
|
42
|
+
/** The employee's screen of their trainings - where a to-do and an announcement lead. */
|
|
43
|
+
exports.MY_TRAININGS_ADDRESS = 'regulations?tab=trainings';
|
|
44
|
+
const text = (value) => (typeof value === 'string' && value.trim() ? value.trim() : null);
|
|
45
|
+
const kindWord = (t, code) => t(`text.training-kind.${code}`);
|
|
46
|
+
/* ---------------------------------------------------------------- kinds and people */
|
|
47
|
+
/** The request's transaction, or the data source's manager for a query that runs without one. */
|
|
48
|
+
function trainingManagerOf(context) {
|
|
49
|
+
return context?.state?.tx ?? (0, shell_1.getRepository)(training_1.TrainingSetting).manager;
|
|
50
|
+
}
|
|
51
|
+
/** The kinds as the domain has them. */
|
|
52
|
+
async function trainingKindsOf(tx, domain) {
|
|
53
|
+
return (0, training_rules_1.trainingKindsInDomain)(await tx.getRepository(training_1.TrainingSetting).find({ where: { domain: { id: domain.id } } }));
|
|
54
|
+
}
|
|
55
|
+
/** Whom a kind or a session is for - the whole domain when nobody is named. */
|
|
56
|
+
function trainingAudiencesOf(kind, domain, own) {
|
|
57
|
+
if (own?.length)
|
|
58
|
+
return own;
|
|
59
|
+
if (kind.audiences.length)
|
|
60
|
+
return kind.audiences;
|
|
61
|
+
return [{ scope: 'DOMAIN', targetId: domain.id, targetName: domain.name }];
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* The people who must take a kind: the employees still working here whose account the kind's audience reaches. One
|
|
65
|
+
* with no account is reached by no audience, as an announcement reaches nobody without one.
|
|
66
|
+
*/
|
|
67
|
+
async function trainingPeopleOf(tx, domain, audiences, today) {
|
|
68
|
+
const userIds = await (0, organization_1.audienceUserIds)(domain, audiences, tx);
|
|
69
|
+
if (!userIds.length)
|
|
70
|
+
return [];
|
|
71
|
+
const rows = await tx.getRepository(organization_1.Employee).find({ where: { domain: { id: domain.id }, user: { id: (0, typeorm_1.In)(userIds) } }, relations: ['user', 'department'], order: { name: 'ASC' } });
|
|
72
|
+
return rows.filter(one => (0, employee_profile_rules_js_1.isActiveEmployee)(one, today));
|
|
73
|
+
}
|
|
74
|
+
/** Why the targets of an audience may not be given, or nothing - each names one of this domain's own. */
|
|
75
|
+
async function audiencesRefusal(tx, domain, audiences) {
|
|
76
|
+
if (!audiences?.length)
|
|
77
|
+
return null;
|
|
78
|
+
const departmentIds = [...(await (0, organization_1.departmentTreeOf)(domain, tx)).keys()];
|
|
79
|
+
const roleIds = (await (0, organization_1.domainRolesOf)(domain, undefined, tx)).map(one => one.id);
|
|
80
|
+
for (const target of audiences) {
|
|
81
|
+
const why = (0, organization_1.audienceTargetRefusal)(target, { domainId: domain.id, departmentIds, roleIds });
|
|
82
|
+
if (why)
|
|
83
|
+
return new shell_1.Refusal('training-audience-invalid', { target: target.targetName ?? target.targetId }, `${target.targetName ?? target.targetId} is not of this domain (${why})`);
|
|
84
|
+
}
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
const audienceRows = (audiences) => (audiences ?? []).map(one => ({ scope: one.scope, targetId: one.targetId, ...(one.targetName ? { targetName: String(one.targetName).slice(0, 100) } : {}) }));
|
|
88
|
+
/** Who the request is to the trainings. A department head is the employee a department names as its manager. */
|
|
89
|
+
async function trainingReaderOf(context) {
|
|
90
|
+
const { domain } = context.state;
|
|
91
|
+
const tx = trainingManagerOf(context);
|
|
92
|
+
const all = await (0, employee_reader_1.holds)(context, 'training', 'query');
|
|
93
|
+
const employee = await (0, employee_reader_1.workingEmployeeOf)(context);
|
|
94
|
+
let headed = [];
|
|
95
|
+
if (employee) {
|
|
96
|
+
const managed = await tx.getRepository(organization_1.Department).find({ where: { domain: { id: domain.id }, manager: { id: employee.id } } });
|
|
97
|
+
if (managed.length)
|
|
98
|
+
headed = (0, organization_1.withDescendants)(managed.map(one => one.id), await (0, organization_1.departmentTreeOf)(domain, tx));
|
|
99
|
+
}
|
|
100
|
+
return { all, employee, headed };
|
|
101
|
+
}
|
|
102
|
+
/** The reader may read the trainings at all - a holder or an employee; a partner is refused. */
|
|
103
|
+
function requireReader(reader) {
|
|
104
|
+
if (!reader.all && !reader.employee)
|
|
105
|
+
throw new shell_1.Refusal('compliance-employees-only', {}, 'only an employee of this company can do this');
|
|
106
|
+
}
|
|
107
|
+
/* ---------------------------------------------------------------- settings */
|
|
108
|
+
/** A kind's own word - written over the declaration, one row per kind. */
|
|
109
|
+
async function saveTrainingSettings(patches, context) {
|
|
110
|
+
const { domain, user, tx } = context.state;
|
|
111
|
+
const repository = tx.getRepository(training_1.TrainingSetting);
|
|
112
|
+
const kinds = await trainingKindsOf(tx, domain);
|
|
113
|
+
const saved = [];
|
|
114
|
+
for (const patch of patches) {
|
|
115
|
+
const refusal = (0, training_rules_1.trainingSettingRefusal)(patch) ?? (await audiencesRefusal(tx, domain, patch.audiences));
|
|
116
|
+
if (refusal)
|
|
117
|
+
throw refusal;
|
|
118
|
+
const kind = kinds.find(one => one.code === patch.kind);
|
|
119
|
+
const row = (await repository.findOne({ where: { domain: { id: domain.id }, kind: patch.kind } })) ?? repository.create({ domain, kind: patch.kind, enabled: kind.enabled, creator: user });
|
|
120
|
+
if (patch.enabled !== undefined && patch.enabled !== null)
|
|
121
|
+
row.enabled = !!patch.enabled;
|
|
122
|
+
if (patch.dueDay !== undefined)
|
|
123
|
+
row.dueDay = text(patch.dueDay);
|
|
124
|
+
if (patch.audiences !== undefined)
|
|
125
|
+
row.audiences = audienceRows(patch.audiences);
|
|
126
|
+
if (patch.memo !== undefined)
|
|
127
|
+
row.memo = text(patch.memo);
|
|
128
|
+
row.updater = user;
|
|
129
|
+
saved.push(await repository.save(row));
|
|
130
|
+
}
|
|
131
|
+
return saved;
|
|
132
|
+
}
|
|
133
|
+
/* ---------------------------------------------------------------- sessions */
|
|
134
|
+
/** Opens a session with its material, and tells its audience - in the same write. */
|
|
135
|
+
async function openTrainingSession(input, attach, context) {
|
|
136
|
+
const { domain, user, tx } = context.state;
|
|
137
|
+
const kind = (await trainingKindsOf(tx, domain)).find(one => one.code === input.kind);
|
|
138
|
+
const refusal = (0, training_rules_1.trainingSessionRefusal)(input, kind) ?? (await audiencesRefusal(tx, domain, input.audiences));
|
|
139
|
+
if (refusal)
|
|
140
|
+
throw refusal;
|
|
141
|
+
const saved = await tx.getRepository(training_1.TrainingSession).save({
|
|
142
|
+
domain,
|
|
143
|
+
kind: input.kind,
|
|
144
|
+
year: input.year,
|
|
145
|
+
name: input.name.trim(),
|
|
146
|
+
method: input.method,
|
|
147
|
+
startsOn: input.startsOn,
|
|
148
|
+
endsOn: text(input.endsOn),
|
|
149
|
+
instructor: text(input.instructor),
|
|
150
|
+
organisation: text(input.organisation),
|
|
151
|
+
audiences: audienceRows(input.audiences),
|
|
152
|
+
notes: text(input.notes),
|
|
153
|
+
creator: user,
|
|
154
|
+
updater: user
|
|
155
|
+
});
|
|
156
|
+
for (const file of attach ?? [])
|
|
157
|
+
await (0, attachment_base_1.createAttachment)(null, { attachment: { file, refType: exports.TRAINING_SESSION_REF_TYPE, refBy: saved.id } }, context);
|
|
158
|
+
const t = context.t ?? ((key) => key);
|
|
159
|
+
const when = saved.endsOn && saved.endsOn !== saved.startsOn ? `${saved.startsOn} ~ ${saved.endsOn}` : saved.startsOn;
|
|
160
|
+
await (0, announcement_1.announce)(context, {
|
|
161
|
+
trigger: exports.TRAINING_SCHEDULED_TRIGGER,
|
|
162
|
+
sourceKind: exports.TRAINING_SESSION_REF_TYPE,
|
|
163
|
+
sourceId: saved.id,
|
|
164
|
+
title: `${kindWord(t, saved.kind)} · ${saved.name}`,
|
|
165
|
+
body: [
|
|
166
|
+
`${t('field.training-session-when')}: ${when}`,
|
|
167
|
+
`${t('field.training-method')}: ${t(`text.training-method.${saved.method}`)}`,
|
|
168
|
+
...(saved.instructor ? [`${t('field.training-instructor')}: ${saved.instructor}`] : []),
|
|
169
|
+
...(saved.organisation ? [`${t('field.training-organisation')}: ${saved.organisation}`] : [])
|
|
170
|
+
].join('\n'),
|
|
171
|
+
links: [{ label: t('title.training-my-trainings'), url: exports.MY_TRAININGS_ADDRESS }],
|
|
172
|
+
audiences: trainingAudiencesOf(kind, domain, saved.audiences)
|
|
173
|
+
});
|
|
174
|
+
return saved;
|
|
175
|
+
}
|
|
176
|
+
/** What a session says may be corrected - its kind and year stay, the completions of it count for them. */
|
|
177
|
+
async function updateTrainingSessions(patches, context) {
|
|
178
|
+
const { domain, user, tx } = context.state;
|
|
179
|
+
const repository = tx.getRepository(training_1.TrainingSession);
|
|
180
|
+
const kinds = await trainingKindsOf(tx, domain);
|
|
181
|
+
const saved = [];
|
|
182
|
+
for (const patch of patches) {
|
|
183
|
+
const row = await repository.findOne({ where: { id: patch.id, domain: { id: domain.id } } });
|
|
184
|
+
if (!row)
|
|
185
|
+
throw new shell_1.Refusal('training-not-found', {}, 'the record was not found. reload and try again.');
|
|
186
|
+
for (const field of ['name', 'method', 'startsOn', 'endsOn', 'instructor', 'organisation', 'notes'])
|
|
187
|
+
if (patch[field] !== undefined)
|
|
188
|
+
row[field] = field === 'name' || field === 'method' || field === 'startsOn' ? (patch[field] ?? '').trim() : text(patch[field]);
|
|
189
|
+
/* An earlier session of a kind turned off since is still corrected - only what it says is asked. */
|
|
190
|
+
const refusal = (0, training_rules_1.trainingSessionRefusal)(row, { ...kinds.find(one => one.code === row.kind), enabled: true });
|
|
191
|
+
if (refusal)
|
|
192
|
+
throw refusal;
|
|
193
|
+
row.updater = user;
|
|
194
|
+
saved.push(await repository.save(row));
|
|
195
|
+
}
|
|
196
|
+
return saved;
|
|
197
|
+
}
|
|
198
|
+
/* ---------------------------------------------------------------- completions */
|
|
199
|
+
async function completionsOf(tx, domain, employeeId, kind, year) {
|
|
200
|
+
return await tx.getRepository(training_1.TrainingCompletion).find({ where: { domain: { id: domain.id }, employee: { id: employeeId }, kind, year } });
|
|
201
|
+
}
|
|
202
|
+
/** A confirmed completion closes the person's to-do of the kind and year, once. */
|
|
203
|
+
async function closeTodoOf(tx, domain, employeeId, kind, year, by) {
|
|
204
|
+
const repository = tx.getRepository(training_1.TrainingAssignment);
|
|
205
|
+
const assignment = await repository.findOne({ where: { domain: { id: domain.id }, employee: { id: employeeId }, kind, year } });
|
|
206
|
+
if (!assignment?.todoInstanceId || assignment.closedAt)
|
|
207
|
+
return;
|
|
208
|
+
await (0, legal_todo_1.closeLegalTodo)(tx, domain, assignment.todoInstanceId, by, '이수 확인');
|
|
209
|
+
assignment.closedAt = new Date();
|
|
210
|
+
assignment.updater = by;
|
|
211
|
+
await repository.save(assignment);
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* The attendance of a session, recorded by the manager - confirmed at once. Someone already done for the kind and year
|
|
215
|
+
* is left as they are.
|
|
216
|
+
*/
|
|
217
|
+
async function recordTrainingAttendance(sessionId, employeeIds, completedOn, context) {
|
|
218
|
+
const { domain, user, tx } = context.state;
|
|
219
|
+
const session = await tx.getRepository(training_1.TrainingSession).findOne({ where: { id: sessionId, domain: { id: domain.id } } });
|
|
220
|
+
if (!session)
|
|
221
|
+
throw new shell_1.Refusal('training-not-found', {}, 'the record was not found. reload and try again.');
|
|
222
|
+
const day = text(completedOn) ?? session.endsOn ?? session.startsOn;
|
|
223
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(day) || day < session.startsOn)
|
|
224
|
+
throw new shell_1.Refusal('training-attendance-day-invalid', { startsOn: session.startsOn }, `the day attended is on or after ${session.startsOn}, YYYY-MM-DD`);
|
|
225
|
+
if (!employeeIds.length)
|
|
226
|
+
throw new shell_1.Refusal('training-attendees-required', {}, 'pick the people who attended');
|
|
227
|
+
const today = (0, employee_reader_1.todayIn)(domain);
|
|
228
|
+
const employees = await tx.getRepository(organization_1.Employee).find({ where: { domain: { id: domain.id }, id: (0, typeorm_1.In)(employeeIds) } });
|
|
229
|
+
const repository = tx.getRepository(training_1.TrainingCompletion);
|
|
230
|
+
const written = [];
|
|
231
|
+
for (const id of new Set(employeeIds)) {
|
|
232
|
+
const employee = employees.find(one => one.id === id);
|
|
233
|
+
if (!employee || !(0, employee_profile_rules_js_1.isActiveEmployee)(employee, today))
|
|
234
|
+
throw new shell_1.Refusal('training-employee-unknown', {}, 'an attendee must be an employee working in this domain');
|
|
235
|
+
if ((0, training_rules_1.trainingStateOf)(await completionsOf(tx, domain, id, session.kind, session.year)) === 'DONE')
|
|
236
|
+
continue;
|
|
237
|
+
written.push(await repository.save({
|
|
238
|
+
domain,
|
|
239
|
+
employee,
|
|
240
|
+
kind: session.kind,
|
|
241
|
+
year: session.year,
|
|
242
|
+
session,
|
|
243
|
+
completedOn: day,
|
|
244
|
+
method: session.method,
|
|
245
|
+
organisation: session.organisation,
|
|
246
|
+
status: training_1.TrainingCompletionStatus.CONFIRMED,
|
|
247
|
+
decidedAt: new Date(),
|
|
248
|
+
decidedBy: user,
|
|
249
|
+
creator: user,
|
|
250
|
+
updater: user
|
|
251
|
+
}));
|
|
252
|
+
await closeTodoOf(tx, domain, id, session.kind, session.year, user);
|
|
253
|
+
}
|
|
254
|
+
return written;
|
|
255
|
+
}
|
|
256
|
+
/** A certificate the person sends for themself - waiting until the manager confirms it. */
|
|
257
|
+
async function submitTrainingCertificate(input, attach, context) {
|
|
258
|
+
const { domain, user, tx } = context.state;
|
|
259
|
+
const employee = await (0, employee_reader_1.workingEmployeeOf)(context);
|
|
260
|
+
if (!employee)
|
|
261
|
+
throw new shell_1.Refusal('compliance-employees-only', {}, 'only an employee of this company can do this');
|
|
262
|
+
const kind = (await trainingKindsOf(tx, domain)).find(one => one.code === input.kind);
|
|
263
|
+
const refusal = (0, training_rules_1.trainingCertificateRefusal)(input, kind, (0, employee_reader_1.todayIn)(domain), attach?.length ?? 0, await completionsOf(tx, domain, employee.id, input.kind, input.year));
|
|
264
|
+
if (refusal)
|
|
265
|
+
throw refusal;
|
|
266
|
+
const session = input.sessionId ? await tx.getRepository(training_1.TrainingSession).findOne({ where: { id: input.sessionId, domain: { id: domain.id }, kind: input.kind, year: input.year } }) : null;
|
|
267
|
+
if (input.sessionId && !session)
|
|
268
|
+
throw new shell_1.Refusal('training-session-unknown', {}, 'the session must be one of this kind and year');
|
|
269
|
+
const saved = await tx.getRepository(training_1.TrainingCompletion).save({
|
|
270
|
+
domain,
|
|
271
|
+
employee,
|
|
272
|
+
kind: input.kind,
|
|
273
|
+
year: input.year,
|
|
274
|
+
session,
|
|
275
|
+
completedOn: input.completedOn,
|
|
276
|
+
method: input.method,
|
|
277
|
+
organisation: text(input.organisation),
|
|
278
|
+
status: training_1.TrainingCompletionStatus.SUBMITTED,
|
|
279
|
+
creator: user,
|
|
280
|
+
updater: user
|
|
281
|
+
});
|
|
282
|
+
for (const file of attach ?? [])
|
|
283
|
+
await (0, attachment_base_1.createAttachment)(null, { attachment: { file, refType: exports.TRAINING_COMPLETION_REF_TYPE, refBy: saved.id } }, context);
|
|
284
|
+
return saved;
|
|
285
|
+
}
|
|
286
|
+
/** The manager confirms certificates sent - each closes the person's to-do. */
|
|
287
|
+
async function confirmTrainingCompletions(ids, context) {
|
|
288
|
+
return await decide(ids, training_1.TrainingCompletionStatus.CONFIRMED, null, context);
|
|
289
|
+
}
|
|
290
|
+
/** The manager returns a certificate, saying why - the person sends another. */
|
|
291
|
+
async function returnTrainingCompletion(id, reason, context) {
|
|
292
|
+
return (await decide([id], training_1.TrainingCompletionStatus.RETURNED, reason, context))[0];
|
|
293
|
+
}
|
|
294
|
+
async function decide(ids, decision, reason, context) {
|
|
295
|
+
const { domain, user, tx } = context.state;
|
|
296
|
+
const repository = tx.getRepository(training_1.TrainingCompletion);
|
|
297
|
+
const saved = [];
|
|
298
|
+
for (const id of ids) {
|
|
299
|
+
const row = await repository.findOne({ where: { id, domain: { id: domain.id } } });
|
|
300
|
+
if (!row)
|
|
301
|
+
throw new shell_1.Refusal('training-not-found', {}, 'the record was not found. reload and try again.');
|
|
302
|
+
const refusal = (0, training_rules_1.trainingDecisionRefusal)(row.status, decision, reason);
|
|
303
|
+
if (refusal)
|
|
304
|
+
throw refusal;
|
|
305
|
+
row.status = decision;
|
|
306
|
+
row.returnReason = decision === training_1.TrainingCompletionStatus.RETURNED ? reason.trim() : null;
|
|
307
|
+
row.decidedAt = new Date();
|
|
308
|
+
row.decidedBy = user;
|
|
309
|
+
row.updater = user;
|
|
310
|
+
saved.push(await repository.save(row));
|
|
311
|
+
if (decision === training_1.TrainingCompletionStatus.CONFIRMED)
|
|
312
|
+
await closeTodoOf(tx, domain, row.employeeId, row.kind, row.year, user);
|
|
313
|
+
}
|
|
314
|
+
return saved;
|
|
315
|
+
}
|
|
316
|
+
/** Training records are kept (decision 17) - every delete is refused with what to do instead. */
|
|
317
|
+
async function deleteTrainingRecords() {
|
|
318
|
+
throw (0, training_rules_1.trainingDeletionRefusal)();
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Opens a kind's year: a worklist to-do 「<교육> 이수」 with the due day for everyone of its audience not done - at most
|
|
322
|
+
* one per person, kind and year, so opening it again hands out only to those it missed (a person who joined since, a
|
|
323
|
+
* to-do the domain could not hand out before).
|
|
324
|
+
*/
|
|
325
|
+
async function openTrainingYear(kindCode, year, context) {
|
|
326
|
+
const { domain, user, tx } = context.state;
|
|
327
|
+
const kind = (await trainingKindsOf(tx, domain)).find(one => one.code === kindCode);
|
|
328
|
+
const refusal = (0, training_rules_1.trainingSessionRefusal)({ kind: kindCode, year, name: '-', method: 'POSTED', startsOn: `${year}-01-01` }, kind);
|
|
329
|
+
if (refusal)
|
|
330
|
+
throw refusal;
|
|
331
|
+
const today = (0, employee_reader_1.todayIn)(domain);
|
|
332
|
+
const dueOn = (0, training_rules_1.trainingDueOnOf)(year, kind.dueDay);
|
|
333
|
+
const people = await trainingPeopleOf(tx, domain, trainingAudiencesOf(kind, domain), today);
|
|
334
|
+
const repository = tx.getRepository(training_1.TrainingAssignment);
|
|
335
|
+
const done = new Set((await tx.getRepository(training_1.TrainingCompletion).find({ where: { domain: { id: domain.id }, kind: kindCode, year, status: training_1.TrainingCompletionStatus.CONFIRMED } })).map(one => one.employeeId));
|
|
336
|
+
let handedOut = 0;
|
|
337
|
+
for (const person of people) {
|
|
338
|
+
if (done.has(person.id) || !person.user)
|
|
339
|
+
continue;
|
|
340
|
+
const assignment = (await repository.findOne({ where: { domain: { id: domain.id }, employee: { id: person.id }, kind: kindCode, year } })) ??
|
|
341
|
+
(await repository.save({ domain, employee: person, kind: kindCode, year, dueOn, creator: user, updater: user }));
|
|
342
|
+
if (assignment.todoInstanceId)
|
|
343
|
+
continue;
|
|
344
|
+
const t = (0, translator_1.translatorFor)(person.user.locale || 'ko');
|
|
345
|
+
const id = await (0, legal_todo_1.issueLegalTodo)(tx, domain, {
|
|
346
|
+
assignee: person.user,
|
|
347
|
+
name: t('text.training-todo-name', { kind: kindWord(t, kindCode), year }),
|
|
348
|
+
headline: t('text.training-todo-headline', { dueOn }),
|
|
349
|
+
address: exports.MY_TRAININGS_ADDRESS,
|
|
350
|
+
subjectKind: 'TrainingAssignment',
|
|
351
|
+
subjectId: assignment.id,
|
|
352
|
+
dueAt: (0, training_rules_1.trainingDueAt)(dueOn, domain.timezone)
|
|
353
|
+
});
|
|
354
|
+
if (!id)
|
|
355
|
+
continue;
|
|
356
|
+
assignment.todoInstanceId = id;
|
|
357
|
+
assignment.updater = user;
|
|
358
|
+
await repository.save(assignment);
|
|
359
|
+
handedOut++;
|
|
360
|
+
}
|
|
361
|
+
return { people: people.length, done: people.filter(one => done.has(one.id)).length, handedOut };
|
|
362
|
+
}
|
|
363
|
+
/** Where everyone of every kind on stands in a year - the people of each kind's audience, by name. */
|
|
364
|
+
async function standingsOf(tx, domain, year, today) {
|
|
365
|
+
const kinds = (await trainingKindsOf(tx, domain)).filter(one => one.enabled);
|
|
366
|
+
const completions = await tx.getRepository(training_1.TrainingCompletion).find({ where: { domain: { id: domain.id }, year } });
|
|
367
|
+
const rows = [];
|
|
368
|
+
for (const kind of kinds) {
|
|
369
|
+
const dueOn = (0, training_rules_1.trainingDueOnOf)(year, kind.dueDay);
|
|
370
|
+
for (const employee of await trainingPeopleOf(tx, domain, trainingAudiencesOf(kind, domain), today)) {
|
|
371
|
+
const mine = completions.filter(one => one.kind === kind.code && one.employeeId === employee.id);
|
|
372
|
+
const state = (0, training_rules_1.trainingStateOf)(mine);
|
|
373
|
+
rows.push({
|
|
374
|
+
id: `${kind.code}:${year}:${employee.id}`,
|
|
375
|
+
kind: kind.code,
|
|
376
|
+
year,
|
|
377
|
+
dueOn,
|
|
378
|
+
state,
|
|
379
|
+
completedOn: state === 'DONE' ? mine.filter(one => one.status === 'CONFIRMED').map(one => one.completedOn).sort()[0] : null,
|
|
380
|
+
employee,
|
|
381
|
+
departmentName: employee.department?.name ?? null
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
return rows;
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* The standings the reader may read: a holder all of them; an employee their own; a department head also whether each
|
|
389
|
+
* of their people is done - no day, no waiting certificate, nothing of its content.
|
|
390
|
+
*/
|
|
391
|
+
async function readableStandings(year, context) {
|
|
392
|
+
const { domain } = context.state;
|
|
393
|
+
const tx = trainingManagerOf(context);
|
|
394
|
+
const reader = await trainingReaderOf(context);
|
|
395
|
+
requireReader(reader);
|
|
396
|
+
const rows = await standingsOf(tx, domain, year, (0, employee_reader_1.todayIn)(domain));
|
|
397
|
+
if (reader.all)
|
|
398
|
+
return rows;
|
|
399
|
+
return rows
|
|
400
|
+
.filter(row => row.employee.id === reader.employee.id || (row.employee.departmentId && reader.headed.includes(row.employee.departmentId)))
|
|
401
|
+
.map(row => (row.employee.id === reader.employee.id ? row : { ...row, state: row.state === 'DONE' ? 'DONE' : 'NOT_DONE', completedOn: null }));
|
|
402
|
+
}
|
|
403
|
+
/** The request's employee's trainings of a year: each kind on whose audience they are in, with its sessions. */
|
|
404
|
+
async function myTrainings(year, context) {
|
|
405
|
+
const { domain } = context.state;
|
|
406
|
+
const tx = trainingManagerOf(context);
|
|
407
|
+
const employee = await (0, employee_reader_1.workingEmployeeOf)(context);
|
|
408
|
+
if (!employee)
|
|
409
|
+
throw new shell_1.Refusal('compliance-employees-only', {}, 'only an employee of this company can do this');
|
|
410
|
+
const today = (0, employee_reader_1.todayIn)(domain);
|
|
411
|
+
const kinds = (await trainingKindsOf(tx, domain)).filter(one => one.enabled);
|
|
412
|
+
const sessions = await tx.getRepository(training_1.TrainingSession).find({ where: { domain: { id: domain.id }, year }, order: { startsOn: 'ASC' } });
|
|
413
|
+
const out = [];
|
|
414
|
+
for (const kind of kinds) {
|
|
415
|
+
if (!(await trainingPeopleOf(tx, domain, trainingAudiencesOf(kind, domain), today)).some(one => one.id === employee.id))
|
|
416
|
+
continue;
|
|
417
|
+
const mine = (await completionsOf(tx, domain, employee.id, kind.code, year)).sort((a, b) => +new Date(b.createdAt) - +new Date(a.createdAt));
|
|
418
|
+
const state = (0, training_rules_1.trainingStateOf)(mine);
|
|
419
|
+
out.push({
|
|
420
|
+
kind: kind.code,
|
|
421
|
+
year,
|
|
422
|
+
dueOn: (0, training_rules_1.trainingDueOnOf)(year, kind.dueDay),
|
|
423
|
+
state,
|
|
424
|
+
completion: (state === 'DONE' ? mine.find(one => one.status === 'CONFIRMED') : mine[0]) ?? null,
|
|
425
|
+
sessions: sessions.filter(one => one.kind === kind.code)
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
return out;
|
|
429
|
+
}
|
|
430
|
+
/** Whether the request may read a completion - a holder, or the one it is of. A department head may not. */
|
|
431
|
+
async function mayReadCompletion(row, context) {
|
|
432
|
+
if (!row)
|
|
433
|
+
return false;
|
|
434
|
+
if (await (0, employee_reader_1.holds)(context, 'training', 'query'))
|
|
435
|
+
return true;
|
|
436
|
+
const employee = await (0, employee_reader_1.workingEmployeeOf)(context);
|
|
437
|
+
return !!employee && employee.id === row.employeeId;
|
|
438
|
+
}
|
|
439
|
+
/** Whether the request may read a session's material - a holder, or an employee it is for. */
|
|
440
|
+
async function mayReadSession(row, context) {
|
|
441
|
+
if (!row)
|
|
442
|
+
return false;
|
|
443
|
+
if (await (0, employee_reader_1.holds)(context, 'training', 'query'))
|
|
444
|
+
return true;
|
|
445
|
+
const { domain } = context.state;
|
|
446
|
+
const employee = await (0, employee_reader_1.workingEmployeeOf)(context);
|
|
447
|
+
if (!employee)
|
|
448
|
+
return false;
|
|
449
|
+
const manager = trainingManagerOf(context);
|
|
450
|
+
const kind = (await trainingKindsOf(manager, domain)).find(one => one.code === row.kind);
|
|
451
|
+
if (!kind)
|
|
452
|
+
return false;
|
|
453
|
+
return (await trainingPeopleOf(manager, domain, trainingAudiencesOf(kind, domain, row.audiences), (0, employee_reader_1.todayIn)(domain))).some(one => one.id === employee.id);
|
|
454
|
+
}
|
|
455
|
+
//# sourceMappingURL=training-service.js.map
|