@things-factory/kpi 9.0.31 → 9.0.32
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/README.md +1 -2
- package/client/charts/kpi-boxplot-chart.ts +182 -42
- package/client/charts/kpi-radar-chart.ts +9 -9
- package/client/pages/kpi/kpi-list-page.ts +196 -32
- package/client/pages/kpi/kpi-overview.ts +9 -11
- package/client/pages/kpi/kpi-tree-page.ts +409 -0
- package/client/pages/kpi/kpi-view.ts +187 -0
- package/client/pages/kpi-dashboard/cards/kpi-level1-card.ts +1 -1
- package/client/pages/kpi-dashboard/cards/kpi-level2-comparison.ts +1 -1
- package/client/pages/kpi-dashboard/cards/kpi-level3-comparison.ts +1 -1
- package/client/pages/kpi-dashboard/components/kpi-left-panel.ts +198 -160
- package/client/pages/kpi-dashboard/components/kpi-map-panel.ts +133 -0
- package/client/pages/kpi-dashboard/components/kpi-region-popup.ts +3 -2
- package/client/pages/kpi-dashboard/kpi-dashboard-map.ts +4 -3
- package/client/pages/kpi-dashboard/kpi-dashboard.ts +28 -30
- package/client/pages/kpi-history/kpi-history-list-page.ts +11 -11
- package/client/pages/kpi-metric/kpi-metric-list-page.ts +10 -2
- package/client/pages/kpi-metric-value/kpi-metric-value-editor-page.ts +7 -7
- package/client/pages/kpi-metric-value/kpi-metric-value-importer.ts +2 -2
- package/client/pages/kpi-metric-value/kpi-metric-value-list-page.ts +16 -8
- package/client/pages/kpi-metric-value/kpi-metric-value-manual-entry-form.ts +5 -5
- package/client/pages/kpi-statistic/kpi-statistic-editor-page.ts +1 -2
- package/client/pages/kpi-statistic/kpi-statistic-list-page.ts +10 -2
- package/client/pages/kpi-value/kpi-value-editor-page.ts +11 -7
- package/client/pages/kpi-value/kpi-value-list-page.ts +31 -7
- package/client/route.ts +2 -9
- package/design-entities.md +8 -12
- package/dist-client/charts/kpi-boxplot-chart.d.ts +2 -0
- package/dist-client/charts/kpi-boxplot-chart.js +168 -42
- package/dist-client/charts/kpi-boxplot-chart.js.map +1 -1
- package/dist-client/charts/kpi-radar-chart.js +9 -9
- package/dist-client/charts/kpi-radar-chart.js.map +1 -1
- package/dist-client/pages/kpi/kpi-list-page.d.ts +19 -3
- package/dist-client/pages/kpi/kpi-list-page.js +188 -32
- package/dist-client/pages/kpi/kpi-list-page.js.map +1 -1
- package/dist-client/pages/kpi/kpi-overview.js +9 -11
- package/dist-client/pages/kpi/kpi-overview.js.map +1 -1
- package/dist-client/pages/kpi/kpi-tree-page.d.ts +59 -0
- package/dist-client/pages/kpi/kpi-tree-page.js +403 -0
- package/dist-client/pages/kpi/kpi-tree-page.js.map +1 -0
- package/dist-client/pages/kpi/kpi-view.d.ts +12 -0
- package/dist-client/pages/kpi/kpi-view.js +191 -0
- package/dist-client/pages/kpi/kpi-view.js.map +1 -0
- package/dist-client/pages/kpi-dashboard/cards/kpi-level1-card.js +1 -1
- package/dist-client/pages/kpi-dashboard/cards/kpi-level1-card.js.map +1 -1
- package/dist-client/pages/kpi-dashboard/cards/kpi-level2-comparison.js +1 -1
- package/dist-client/pages/kpi-dashboard/cards/kpi-level2-comparison.js.map +1 -1
- package/dist-client/pages/kpi-dashboard/cards/kpi-level3-comparison.js +1 -1
- package/dist-client/pages/kpi-dashboard/cards/kpi-level3-comparison.js.map +1 -1
- package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.d.ts +3 -1
- package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.js +197 -161
- package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.js.map +1 -1
- package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.d.ts +5 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.js +146 -0
- package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.js.map +1 -1
- package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.js +3 -2
- package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.js.map +1 -1
- package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js +4 -3
- package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js.map +1 -1
- package/dist-client/pages/kpi-dashboard/kpi-dashboard.js +28 -30
- package/dist-client/pages/kpi-dashboard/kpi-dashboard.js.map +1 -1
- package/dist-client/pages/kpi-history/kpi-history-list-page.d.ts +6 -1
- package/dist-client/pages/kpi-history/kpi-history-list-page.js +11 -11
- package/dist-client/pages/kpi-history/kpi-history-list-page.js.map +1 -1
- package/dist-client/pages/kpi-metric/kpi-metric-list-page.d.ts +5 -0
- package/dist-client/pages/kpi-metric/kpi-metric-list-page.js +10 -2
- package/dist-client/pages/kpi-metric/kpi-metric-list-page.js.map +1 -1
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-editor-page.d.ts +1 -1
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-editor-page.js +8 -8
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-editor-page.js.map +1 -1
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-importer.js +2 -2
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-importer.js.map +1 -1
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.d.ts +5 -0
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.js +16 -8
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.js.map +1 -1
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-form.d.ts +1 -1
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-form.js +6 -6
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-form.js.map +1 -1
- package/dist-client/pages/kpi-statistic/kpi-statistic-editor-page.js +1 -2
- package/dist-client/pages/kpi-statistic/kpi-statistic-editor-page.js.map +1 -1
- package/dist-client/pages/kpi-statistic/kpi-statistic-list-page.d.ts +5 -0
- package/dist-client/pages/kpi-statistic/kpi-statistic-list-page.js +10 -2
- package/dist-client/pages/kpi-statistic/kpi-statistic-list-page.js.map +1 -1
- package/dist-client/pages/kpi-value/kpi-value-editor-page.d.ts +2 -1
- package/dist-client/pages/kpi-value/kpi-value-editor-page.js +16 -8
- package/dist-client/pages/kpi-value/kpi-value-editor-page.js.map +1 -1
- package/dist-client/pages/kpi-value/kpi-value-list-page.d.ts +5 -0
- package/dist-client/pages/kpi-value/kpi-value-list-page.js +31 -7
- package/dist-client/pages/kpi-value/kpi-value-list-page.js.map +1 -1
- package/dist-client/route.d.ts +1 -1
- package/dist-client/route.js +2 -8
- package/dist-client/route.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/controllers/kpi-metric-value-provider.d.ts +1 -1
- package/dist-server/controllers/kpi-metric-value-provider.js +4 -4
- package/dist-server/controllers/kpi-metric-value-provider.js.map +1 -1
- package/dist-server/controllers/kpi-value-provider.d.ts +1 -1
- package/dist-server/controllers/kpi-value-provider.js +3 -3
- package/dist-server/controllers/kpi-value-provider.js.map +1 -1
- package/dist-server/migrations/1752190849680-seed-kpi-metrics.d.ts +6 -0
- package/dist-server/migrations/1752190849680-seed-kpi-metrics.js +101 -0
- package/dist-server/migrations/1752190849680-seed-kpi-metrics.js.map +1 -0
- package/dist-server/migrations/1752190849681-seed-kpi.d.ts +5 -0
- package/dist-server/migrations/1752190849681-seed-kpi.js +315 -0
- package/dist-server/migrations/1752190849681-seed-kpi.js.map +1 -0
- package/dist-server/migrations/1752192090123-add-grades-to-kpi.d.ts +7 -0
- package/dist-server/migrations/1752192090123-add-grades-to-kpi.js +51 -0
- package/dist-server/migrations/1752192090123-add-grades-to-kpi.js.map +1 -0
- package/dist-server/migrations/1752192090124-add-kpi-statistics.d.ts +5 -0
- package/dist-server/migrations/1752192090124-add-kpi-statistics.js +710 -0
- package/dist-server/migrations/1752192090124-add-kpi-statistics.js.map +1 -0
- package/dist-server/migrations/1752192090128-seed-kpi-org-scope.d.ts +6 -0
- package/dist-server/migrations/1752192090128-seed-kpi-org-scope.js +111 -0
- package/dist-server/migrations/1752192090128-seed-kpi-org-scope.js.map +1 -0
- package/dist-server/migrations/1752192090129-seed-kpi-values.d.ts +6 -0
- package/dist-server/migrations/1752192090129-seed-kpi-values.js +187 -0
- package/dist-server/migrations/1752192090129-seed-kpi-values.js.map +1 -0
- package/dist-server/migrations/grade-data/x11-performance-table.json +962 -0
- package/dist-server/migrations/grade-data/x12-performance-table.json +611 -0
- package/dist-server/migrations/grade-data/x14-performance-table.json +42 -0
- package/dist-server/migrations/grade-data/x21-performance-table.json +889 -0
- package/dist-server/migrations/grade-data/x22-performance-table.json +1064 -0
- package/dist-server/migrations/grade-data/x23-performance-table.json +42 -0
- package/dist-server/migrations/grade-data/x31-performance-table.json +644 -0
- package/dist-server/migrations/grade-data/x32-performance-table.json +993 -0
- package/dist-server/migrations/grade-data/x33-performance-table.json +195 -0
- package/dist-server/migrations/grade-data/x34-performance-table.json +12 -0
- package/dist-server/migrations/grade-data/x35-performance-table.json +42 -0
- package/dist-server/migrations/grade-data/x41-performance-table.json +825 -0
- package/dist-server/migrations/grade-data/x42-performance-table.json +786 -0
- package/dist-server/migrations/grade-data/x43-performance-table.json +12 -0
- package/dist-server/migrations/grade-data/x44-performance-table.json +42 -0
- package/dist-server/migrations/grade-data/x51-performance-table.json +924 -0
- package/dist-server/migrations/grade-data/x52-performance-table.json +42 -0
- package/dist-server/migrations/grade-data/x61-performance-table.json +261 -0
- package/dist-server/migrations/grade-data/x62-performance-table.json +42 -0
- package/dist-server/migrations/seed-data/kpi-metrics-seed.json +454 -0
- package/dist-server/migrations/seed-data/kpi-org-scope-seed.json +1676 -0
- package/dist-server/migrations/seed-data/kpi-values-seed.json +402 -0
- package/dist-server/migrations/seed-data/kpis-seed.json +488 -0
- package/dist-server/service/index.d.ts +3 -7
- package/dist-server/service/index.js +5 -12
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/kpi/aggregate-kpi.js +30 -13
- package/dist-server/service/kpi/aggregate-kpi.js.map +1 -1
- package/dist-server/service/kpi/kpi-formula.service.d.ts +15 -0
- package/dist-server/service/kpi/kpi-formula.service.js +90 -0
- package/dist-server/service/kpi/kpi-formula.service.js.map +1 -1
- package/dist-server/service/kpi/kpi-history.d.ts +0 -3
- package/dist-server/service/kpi/kpi-history.js +0 -10
- package/dist-server/service/kpi/kpi-history.js.map +1 -1
- package/dist-server/service/kpi/kpi-mutation.d.ts +1 -1
- package/dist-server/service/kpi/kpi-mutation.js +57 -20
- package/dist-server/service/kpi/kpi-mutation.js.map +1 -1
- package/dist-server/service/kpi/kpi-query.d.ts +7 -3
- package/dist-server/service/kpi/kpi-query.js +126 -10
- package/dist-server/service/kpi/kpi-query.js.map +1 -1
- package/dist-server/service/kpi/kpi-type.d.ts +4 -2
- package/dist-server/service/kpi/kpi-type.js +12 -4
- package/dist-server/service/kpi/kpi-type.js.map +1 -1
- package/dist-server/service/kpi/kpi.d.ts +4 -3
- package/dist-server/service/kpi/kpi.js +20 -8
- package/dist-server/service/kpi/kpi.js.map +1 -1
- package/dist-server/service/kpi-metric/aggregate-kpi-metric.js +46 -11
- package/dist-server/service/kpi-metric/aggregate-kpi-metric.js.map +1 -1
- package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.d.ts +1 -1
- package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.js +6 -6
- package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.js.map +1 -1
- package/dist-server/service/kpi-metric-value/kpi-metric-value-type.d.ts +2 -2
- package/dist-server/service/kpi-metric-value/kpi-metric-value-type.js +4 -4
- package/dist-server/service/kpi-metric-value/kpi-metric-value-type.js.map +1 -1
- package/dist-server/service/kpi-metric-value/kpi-metric-value.d.ts +1 -1
- package/dist-server/service/kpi-metric-value/kpi-metric-value.js +3 -3
- package/dist-server/service/kpi-metric-value/kpi-metric-value.js.map +1 -1
- package/dist-server/service/kpi-org-scope/index.d.ts +9 -0
- package/dist-server/service/kpi-org-scope/index.js +14 -0
- package/dist-server/service/kpi-org-scope/index.js.map +1 -0
- package/dist-server/service/kpi-org-scope/kpi-org-scope-mutation.d.ts +8 -0
- package/dist-server/service/kpi-org-scope/kpi-org-scope-mutation.js +170 -0
- package/dist-server/service/kpi-org-scope/kpi-org-scope-mutation.js.map +1 -0
- package/dist-server/service/kpi-org-scope/kpi-org-scope-query.d.ts +14 -0
- package/dist-server/service/kpi-org-scope/kpi-org-scope-query.js +152 -0
- package/dist-server/service/kpi-org-scope/kpi-org-scope-query.js.map +1 -0
- package/dist-server/service/kpi-org-scope/kpi-org-scope-type.d.ts +26 -0
- package/dist-server/service/kpi-org-scope/kpi-org-scope-type.js +101 -0
- package/dist-server/service/kpi-org-scope/kpi-org-scope-type.js.map +1 -0
- package/dist-server/service/kpi-org-scope/kpi-org-scope.d.ts +26 -0
- package/dist-server/service/kpi-org-scope/kpi-org-scope.js +135 -0
- package/dist-server/service/kpi-org-scope/kpi-org-scope.js.map +1 -0
- package/dist-server/service/kpi-statistic/kpi-statistic.d.ts +1 -0
- package/dist-server/service/kpi-statistic/kpi-statistic.js +11 -0
- package/dist-server/service/kpi-statistic/kpi-statistic.js.map +1 -1
- package/dist-server/service/kpi-value/kpi-value-mutation.js +71 -7
- package/dist-server/service/kpi-value/kpi-value-mutation.js.map +1 -1
- package/dist-server/service/kpi-value/kpi-value-type.d.ts +4 -2
- package/dist-server/service/kpi-value/kpi-value-type.js +12 -4
- package/dist-server/service/kpi-value/kpi-value-type.js.map +1 -1
- package/dist-server/service/kpi-value/kpi-value.d.ts +3 -1
- package/dist-server/service/kpi-value/kpi-value.js +11 -5
- package/dist-server/service/kpi-value/kpi-value.js.map +1 -1
- package/dist-server/service/utils/value-date-util.d.ts +1 -0
- package/dist-server/service/utils/value-date-util.js +41 -0
- package/dist-server/service/utils/value-date-util.js.map +1 -1
- package/dist-server/tsconfig.json +10 -0
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +7 -6
- package/server/@types/index.d.ts +11 -0
- package/server/controllers/kpi-metric-value-provider.ts +5 -5
- package/server/controllers/kpi-value-provider.ts +4 -4
- package/server/migrations/1752190849680-seed-kpi-metrics.ts +124 -0
- package/server/migrations/1752190849681-seed-kpi.ts +356 -0
- package/server/migrations/1752192090123-add-grades-to-kpi.ts +67 -0
- package/server/migrations/1752192090124-add-kpi-statistics.ts +719 -0
- package/server/migrations/1752192090128-seed-kpi-org-scope.ts +132 -0
- package/server/migrations/1752192090129-seed-kpi-values.ts +207 -0
- package/server/migrations/grade-data/x11-performance-table.json +962 -0
- package/server/migrations/grade-data/x12-performance-table.json +611 -0
- package/server/migrations/grade-data/x14-performance-table.json +42 -0
- package/server/migrations/grade-data/x21-performance-table.json +889 -0
- package/server/migrations/grade-data/x22-performance-table.json +1064 -0
- package/server/migrations/grade-data/x23-performance-table.json +42 -0
- package/server/migrations/grade-data/x31-performance-table.json +644 -0
- package/server/migrations/grade-data/x32-performance-table.json +993 -0
- package/server/migrations/grade-data/x33-performance-table.json +195 -0
- package/server/migrations/grade-data/x34-performance-table.json +12 -0
- package/server/migrations/grade-data/x35-performance-table.json +42 -0
- package/server/migrations/grade-data/x41-performance-table.json +825 -0
- package/server/migrations/grade-data/x42-performance-table.json +786 -0
- package/server/migrations/grade-data/x43-performance-table.json +12 -0
- package/server/migrations/grade-data/x44-performance-table.json +42 -0
- package/server/migrations/grade-data/x51-performance-table.json +924 -0
- package/server/migrations/grade-data/x52-performance-table.json +42 -0
- package/server/migrations/grade-data/x61-performance-table.json +261 -0
- package/server/migrations/grade-data/x62-performance-table.json +42 -0
- package/server/migrations/seed-data/kpi-metrics-seed.json +454 -0
- package/server/migrations/seed-data/kpi-org-scope-seed.json +1676 -0
- package/server/migrations/seed-data/kpi-values-seed.json +402 -0
- package/server/migrations/seed-data/kpis-seed.json +488 -0
- package/server/service/index.ts +5 -12
- package/server/service/kpi/aggregate-kpi.ts +31 -13
- package/server/service/kpi/kpi-formula.service.ts +101 -0
- package/server/service/kpi/kpi-history.ts +0 -8
- package/server/service/kpi/kpi-mutation.ts +59 -19
- package/server/service/kpi/kpi-query.ts +118 -8
- package/server/service/kpi/kpi-type.ts +10 -4
- package/server/service/kpi/kpi.ts +17 -7
- package/server/service/kpi-metric/aggregate-kpi-metric.ts +55 -11
- package/server/service/kpi-metric-value/kpi-metric-value-mutation.ts +6 -6
- package/server/service/kpi-metric-value/kpi-metric-value-type.ts +4 -4
- package/server/service/kpi-metric-value/kpi-metric-value.ts +3 -3
- package/server/service/kpi-org-scope/index.ts +11 -0
- package/server/service/kpi-org-scope/kpi-org-scope-mutation.ts +173 -0
- package/server/service/kpi-org-scope/kpi-org-scope-query.ts +127 -0
- package/server/service/kpi-org-scope/kpi-org-scope-type.ts +68 -0
- package/server/service/kpi-org-scope/kpi-org-scope.ts +123 -0
- package/server/service/kpi-statistic/kpi-statistic.ts +10 -0
- package/server/service/kpi-value/kpi-value-mutation.ts +73 -7
- package/server/service/kpi-value/kpi-value-type.ts +10 -4
- package/server/service/kpi-value/kpi-value.ts +10 -5
- package/server/service/utils/value-date-util.ts +47 -0
- package/server/types/global.d.ts +8 -0
- package/things-factory.config.js +1 -0
- package/translations/en.json +15 -3
- package/translations/ja.json +13 -3
- package/translations/ko.json +15 -3
- package/translations/ms.json +13 -3
- package/translations/zh.json +13 -3
- package/client/pages/kpi-category/kpi-category-importer.ts +0 -90
- package/client/pages/kpi-category/kpi-category-list-page.ts +0 -537
- package/client/pages/kpi-category/kpi-category-value-calculator.ts +0 -233
- package/client/pages/kpi-category-value/kpi-category-value-list-page.ts +0 -404
- package/dist-client/pages/kpi-category/kpi-category-importer.d.ts +0 -23
- package/dist-client/pages/kpi-category/kpi-category-importer.js +0 -92
- package/dist-client/pages/kpi-category/kpi-category-importer.js.map +0 -1
- package/dist-client/pages/kpi-category/kpi-category-list-page.d.ts +0 -74
- package/dist-client/pages/kpi-category/kpi-category-list-page.js +0 -517
- package/dist-client/pages/kpi-category/kpi-category-list-page.js.map +0 -1
- package/dist-client/pages/kpi-category/kpi-category-value-calculator.d.ts +0 -13
- package/dist-client/pages/kpi-category/kpi-category-value-calculator.js +0 -256
- package/dist-client/pages/kpi-category/kpi-category-value-calculator.js.map +0 -1
- package/dist-client/pages/kpi-category-value/kpi-category-value-list-page.d.ts +0 -63
- package/dist-client/pages/kpi-category-value/kpi-category-value-list-page.js +0 -393
- package/dist-client/pages/kpi-category-value/kpi-category-value-list-page.js.map +0 -1
- package/dist-server/service/kpi-category/index.d.ts +0 -6
- package/dist-server/service/kpi-category/index.js +0 -10
- package/dist-server/service/kpi-category/index.js.map +0 -1
- package/dist-server/service/kpi-category/kpi-category-mutation.d.ts +0 -9
- package/dist-server/service/kpi-category/kpi-category-mutation.js +0 -221
- package/dist-server/service/kpi-category/kpi-category-mutation.js.map +0 -1
- package/dist-server/service/kpi-category/kpi-category-query.d.ts +0 -18
- package/dist-server/service/kpi-category/kpi-category-query.js +0 -115
- package/dist-server/service/kpi-category/kpi-category-query.js.map +0 -1
- package/dist-server/service/kpi-category/kpi-category-type.d.ts +0 -24
- package/dist-server/service/kpi-category/kpi-category-type.js +0 -100
- package/dist-server/service/kpi-category/kpi-category-type.js.map +0 -1
- package/dist-server/service/kpi-category/kpi-category.d.ts +0 -22
- package/dist-server/service/kpi-category/kpi-category.js +0 -106
- package/dist-server/service/kpi-category/kpi-category.js.map +0 -1
- package/dist-server/service/kpi-category-value/index.d.ts +0 -6
- package/dist-server/service/kpi-category-value/index.js +0 -10
- package/dist-server/service/kpi-category-value/index.js.map +0 -1
- package/dist-server/service/kpi-category-value/kpi-category-value-mutation.d.ts +0 -8
- package/dist-server/service/kpi-category-value/kpi-category-value-mutation.js +0 -102
- package/dist-server/service/kpi-category-value/kpi-category-value-mutation.js.map +0 -1
- package/dist-server/service/kpi-category-value/kpi-category-value-query.d.ts +0 -13
- package/dist-server/service/kpi-category-value/kpi-category-value-query.js +0 -91
- package/dist-server/service/kpi-category-value/kpi-category-value-query.js.map +0 -1
- package/dist-server/service/kpi-category-value/kpi-category-value-type.d.ts +0 -19
- package/dist-server/service/kpi-category-value/kpi-category-value-type.js +0 -73
- package/dist-server/service/kpi-category-value/kpi-category-value-type.js.map +0 -1
- package/dist-server/service/kpi-category-value/kpi-category-value.d.ts +0 -19
- package/dist-server/service/kpi-category-value/kpi-category-value.js +0 -91
- package/dist-server/service/kpi-category-value/kpi-category-value.js.map +0 -1
- package/helps/kpi/kpi-category.md +0 -160
- package/server/service/kpi-category/index.ts +0 -7
- package/server/service/kpi-category/kpi-category-mutation.ts +0 -217
- package/server/service/kpi-category/kpi-category-query.ts +0 -87
- package/server/service/kpi-category/kpi-category-type.ts +0 -73
- package/server/service/kpi-category/kpi-category.ts +0 -95
- package/server/service/kpi-category-value/index.ts +0 -7
- package/server/service/kpi-category-value/kpi-category-value-mutation.ts +0 -88
- package/server/service/kpi-category-value/kpi-category-value-query.ts +0 -62
- package/server/service/kpi-category-value/kpi-category-value-type.ts +0 -48
- package/server/service/kpi-category-value/kpi-category-value.ts +0 -79
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { getRepository } from '@things-factory/shell'
|
|
2
2
|
import { KpiMetric } from '../kpi-metric/kpi-metric'
|
|
3
|
+
import { Kpi } from './kpi'
|
|
4
|
+
import { KpiValue } from '../kpi-value/kpi-value'
|
|
5
|
+
import { KpiOrgScope } from '../kpi-org-scope/kpi-org-scope'
|
|
3
6
|
|
|
4
7
|
/**
|
|
5
8
|
* KPI formula 파싱/검증/매핑 유효성 검사 서비스
|
|
@@ -60,4 +63,102 @@ export class KpiFormulaService {
|
|
|
60
63
|
// ...
|
|
61
64
|
return { valid: errors.length === 0, errors }
|
|
62
65
|
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 계층 구조 KPI의 값을 자식 KPI들로부터 집계 계산
|
|
69
|
+
* @param kpiId 부모 KPI ID
|
|
70
|
+
* @param valueDate 계산 대상 날짜
|
|
71
|
+
* @param org 조직 단위
|
|
72
|
+
* @returns Promise<number | null> 집계된 값
|
|
73
|
+
*/
|
|
74
|
+
async calculateHierarchicalValue(kpiId: string, valueDate: string, org?: string): Promise<number | null> {
|
|
75
|
+
const kpiRepo = getRepository(Kpi)
|
|
76
|
+
const kpi = await kpiRepo.findOne({
|
|
77
|
+
where: { id: kpiId },
|
|
78
|
+
relations: ['children']
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
if (!kpi || !kpi.children || kpi.children.length === 0) {
|
|
82
|
+
return null
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const childValues: number[] = []
|
|
86
|
+
const childWeights: number[] = []
|
|
87
|
+
|
|
88
|
+
for (const child of kpi.children) {
|
|
89
|
+
let childValue: number | null = null
|
|
90
|
+
|
|
91
|
+
if (child.isLeaf) {
|
|
92
|
+
// Leaf KPI인 경우 KpiValue에서 실제 값을 가져옴
|
|
93
|
+
const kpiValueRepo = getRepository(KpiValue)
|
|
94
|
+
let kpiValue: KpiValue | null = null
|
|
95
|
+
if (org) {
|
|
96
|
+
const kpiOrgScope = await getRepository(KpiOrgScope).findOne({
|
|
97
|
+
where: { org: org, domain: { id: child.domain?.id } }
|
|
98
|
+
})
|
|
99
|
+
if (kpiOrgScope) {
|
|
100
|
+
kpiValue = await kpiValueRepo.findOne({
|
|
101
|
+
where: {
|
|
102
|
+
kpi: { id: child.id },
|
|
103
|
+
valueDate,
|
|
104
|
+
kpiOrgScope: { id: kpiOrgScope.id }
|
|
105
|
+
}
|
|
106
|
+
})
|
|
107
|
+
}
|
|
108
|
+
} else {
|
|
109
|
+
// org가 없는 경우 kpiOrgScope 없이 조회
|
|
110
|
+
kpiValue = await kpiValueRepo.findOne({
|
|
111
|
+
where: {
|
|
112
|
+
kpi: { id: child.id },
|
|
113
|
+
valueDate,
|
|
114
|
+
kpiOrgScope: null
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
}
|
|
118
|
+
childValue = kpiValue?.value || null
|
|
119
|
+
} else {
|
|
120
|
+
// 중간 노드인 경우 재귀적으로 계산
|
|
121
|
+
childValue = await this.calculateHierarchicalValue(child.id, valueDate, org)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (childValue !== null) {
|
|
125
|
+
childValues.push(childValue)
|
|
126
|
+
childWeights.push(child.weight || 1)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (childValues.length === 0) {
|
|
131
|
+
return null
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// 가중 평균 계산
|
|
135
|
+
const totalWeight = childWeights.reduce((sum, weight) => sum + weight, 0)
|
|
136
|
+
const weightedSum = childValues.reduce((sum, value, index) => sum + value * childWeights[index], 0)
|
|
137
|
+
|
|
138
|
+
return totalWeight > 0 ? weightedSum / totalWeight : null
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* KPI가 순환 참조를 생성하는지 확인
|
|
143
|
+
* @param kpiId KPI ID
|
|
144
|
+
* @param parentId 설정하려는 부모 KPI ID
|
|
145
|
+
* @returns Promise<boolean> 순환 참조 여부
|
|
146
|
+
*/
|
|
147
|
+
async hasCircularReference(kpiId: string, parentId: string): Promise<boolean> {
|
|
148
|
+
if (kpiId === parentId) {
|
|
149
|
+
return true
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const kpiRepo = getRepository(Kpi)
|
|
153
|
+
const parent = await kpiRepo.findOne({
|
|
154
|
+
where: { id: parentId },
|
|
155
|
+
relations: ['parent']
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
if (!parent || !parent.parent) {
|
|
159
|
+
return false
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return await this.hasCircularReference(kpiId, parent.parent.id)
|
|
163
|
+
}
|
|
63
164
|
}
|
|
@@ -12,7 +12,6 @@ import { config } from '@things-factory/env'
|
|
|
12
12
|
import { Domain, ScalarObject } from '@things-factory/shell'
|
|
13
13
|
|
|
14
14
|
import { Kpi, KpiStatus, KpiPeriodType } from './kpi'
|
|
15
|
-
import { KpiCategory } from '../kpi-category/kpi-category'
|
|
16
15
|
import { KpiScores } from './kpi-grade.types'
|
|
17
16
|
|
|
18
17
|
const ORMCONFIG = config.get('ormconfig', {})
|
|
@@ -48,13 +47,6 @@ export class KpiHistory implements HistoryEntityInterface<Kpi> {
|
|
|
48
47
|
@Field({ nullable: true })
|
|
49
48
|
description?: string
|
|
50
49
|
|
|
51
|
-
@ManyToOne(() => KpiCategory, { nullable: true })
|
|
52
|
-
@Field(type => KpiCategory, { nullable: true, description: 'Category to which this KPI belongs.' })
|
|
53
|
-
category?: KpiCategory
|
|
54
|
-
|
|
55
|
-
@RelationId((kpi: KpiHistory) => kpi.category)
|
|
56
|
-
categoryId?: string
|
|
57
|
-
|
|
58
50
|
@Column({ nullable: true })
|
|
59
51
|
@Field({ nullable: true, description: 'Calculation formula for the KPI.' })
|
|
60
52
|
formula?: string
|
|
@@ -5,11 +5,11 @@ import { getRepository } from '@things-factory/shell'
|
|
|
5
5
|
import { createAttachment, deleteAttachmentsByRef } from '@things-factory/attachment-base'
|
|
6
6
|
import { Kpi } from './kpi'
|
|
7
7
|
import { NewKpi, KpiPatch } from './kpi-type'
|
|
8
|
-
import { KpiCategory } from '../kpi-category/kpi-category'
|
|
9
8
|
import { KpiHistory } from './kpi-history'
|
|
10
9
|
import { KpiFormulaService } from './kpi-formula.service'
|
|
11
10
|
import { Application, CallbackBase, registerSchedule, unregisterSchedule } from '@things-factory/scheduler-client'
|
|
12
11
|
import { KpiValue } from '../kpi-value/kpi-value'
|
|
12
|
+
import { KpiOrgScope } from '../kpi-org-scope/kpi-org-scope'
|
|
13
13
|
import { Between } from 'typeorm'
|
|
14
14
|
import { KpiMetric } from '../kpi-metric/kpi-metric'
|
|
15
15
|
import { KpiMetricValue } from '../kpi-metric-value/kpi-metric-value'
|
|
@@ -29,13 +29,22 @@ export class KpiMutation {
|
|
|
29
29
|
): Promise<Kpi> {
|
|
30
30
|
const { domain, user, tx } = context.state
|
|
31
31
|
|
|
32
|
-
let
|
|
33
|
-
? await getRepository(
|
|
32
|
+
let parent = kpi.parent
|
|
33
|
+
? await getRepository(Kpi).findOne({ where: { id: kpi.parent.id, domain: { id: domain.id } } })
|
|
34
34
|
: undefined
|
|
35
35
|
|
|
36
|
+
// 순환 참조 검사
|
|
37
|
+
if (parent) {
|
|
38
|
+
const formulaService = new KpiFormulaService()
|
|
39
|
+
const hasCircular = await formulaService.hasCircularReference(parent.id, parent.id)
|
|
40
|
+
if (hasCircular) {
|
|
41
|
+
throw new Error('Circular reference detected in KPI hierarchy')
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
36
45
|
const result = await getRepository(Kpi, tx).save({
|
|
37
46
|
...kpi,
|
|
38
|
-
|
|
47
|
+
parent,
|
|
39
48
|
domain,
|
|
40
49
|
creator: user,
|
|
41
50
|
updater: user
|
|
@@ -114,14 +123,23 @@ export class KpiMutation {
|
|
|
114
123
|
where: { domain: { id: domain.id }, id }
|
|
115
124
|
})
|
|
116
125
|
|
|
117
|
-
let
|
|
118
|
-
? await getRepository(
|
|
119
|
-
: kpi.
|
|
126
|
+
let parent = patch.parent
|
|
127
|
+
? await getRepository(Kpi).findOne({ where: { id: patch.parent.id, domain: { id: domain.id } } })
|
|
128
|
+
: patch.parent === null ? null : kpi.parent
|
|
129
|
+
|
|
130
|
+
// 순환 참조 검사 (parent가 변경되는 경우)
|
|
131
|
+
if (parent && parent.id !== kpi.parentId) {
|
|
132
|
+
const formulaService = new KpiFormulaService()
|
|
133
|
+
const hasCircular = await formulaService.hasCircularReference(kpi.id, parent.id)
|
|
134
|
+
if (hasCircular) {
|
|
135
|
+
throw new Error('Circular reference detected in KPI hierarchy')
|
|
136
|
+
}
|
|
137
|
+
}
|
|
120
138
|
|
|
121
139
|
const result = await repository.save({
|
|
122
140
|
...kpi,
|
|
123
141
|
...patch,
|
|
124
|
-
|
|
142
|
+
parent,
|
|
125
143
|
updater: user
|
|
126
144
|
})
|
|
127
145
|
|
|
@@ -383,8 +401,6 @@ export class KpiMutation {
|
|
|
383
401
|
...kpi,
|
|
384
402
|
name: kpiHistory.name,
|
|
385
403
|
description: kpiHistory.description,
|
|
386
|
-
category: kpiHistory.category,
|
|
387
|
-
categoryId: kpiHistory.categoryId,
|
|
388
404
|
formula: kpiHistory.formula,
|
|
389
405
|
active: kpiHistory.active,
|
|
390
406
|
state: 'DRAFT',
|
|
@@ -400,7 +416,7 @@ export class KpiMutation {
|
|
|
400
416
|
async calculateKpiValue(
|
|
401
417
|
@Arg('kpiId') kpiId: string,
|
|
402
418
|
@Arg('valueDate', { nullable: true }) valueDate: string,
|
|
403
|
-
@Arg('
|
|
419
|
+
@Arg('org', { nullable: true }) org: string,
|
|
404
420
|
@Ctx() context: ResolverContext
|
|
405
421
|
): Promise<KpiValue> {
|
|
406
422
|
const { domain, user, tx } = context.state
|
|
@@ -443,7 +459,7 @@ export class KpiMutation {
|
|
|
443
459
|
metric: { id: metric.id },
|
|
444
460
|
valueDate: valueDateToUse,
|
|
445
461
|
periodType,
|
|
446
|
-
|
|
462
|
+
org: org ?? '',
|
|
447
463
|
domain: { id: domain.id }
|
|
448
464
|
}
|
|
449
465
|
})
|
|
@@ -458,7 +474,7 @@ export class KpiMutation {
|
|
|
458
474
|
metric: { id: metric.id },
|
|
459
475
|
valueDate: Between(startDate, endDate),
|
|
460
476
|
periodType: metric.periodType,
|
|
461
|
-
|
|
477
|
+
org: org ?? '',
|
|
462
478
|
domain: { id: domain.id }
|
|
463
479
|
}
|
|
464
480
|
})
|
|
@@ -475,19 +491,39 @@ export class KpiMutation {
|
|
|
475
491
|
const ast = parseFormula(kpi.formula)
|
|
476
492
|
const provider = new KpiMetricValueProvider({
|
|
477
493
|
valueDate: valueDateToUse,
|
|
478
|
-
|
|
494
|
+
org,
|
|
479
495
|
domainId: domain.id,
|
|
480
496
|
tx
|
|
481
497
|
})
|
|
482
498
|
const evalContext = { functions: builtinFunctions, provider }
|
|
483
499
|
const kpiValueResult = await evaluateFormula(ast, evalContext)
|
|
484
500
|
|
|
485
|
-
// 5.
|
|
501
|
+
// 5. KpiOrgScope 처리
|
|
502
|
+
let kpiOrgScope: KpiOrgScope | null = null
|
|
503
|
+
if (org) {
|
|
504
|
+
kpiOrgScope = await getRepository(KpiOrgScope, tx).findOne({
|
|
505
|
+
where: { org: org, domain: { id: domain.id } }
|
|
506
|
+
})
|
|
507
|
+
if (!kpiOrgScope) {
|
|
508
|
+
// 새 KpiOrgScope 생성
|
|
509
|
+
kpiOrgScope = await getRepository(KpiOrgScope, tx).save({
|
|
510
|
+
entityType: 'KpiCalculation',
|
|
511
|
+
entityId: `calc-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
512
|
+
entityName: org,
|
|
513
|
+
org: org,
|
|
514
|
+
domain,
|
|
515
|
+
creator: user,
|
|
516
|
+
updater: user
|
|
517
|
+
})
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// 6. KPI Value upsert
|
|
486
522
|
let kpiValue = await kpiValueRepo.findOne({
|
|
487
523
|
where: {
|
|
488
524
|
kpi: { id: kpi.id },
|
|
489
525
|
valueDate: valueDateToUse,
|
|
490
|
-
|
|
526
|
+
kpiOrgScope: kpiOrgScope ? { id: kpiOrgScope.id } : null,
|
|
491
527
|
version: kpi.version,
|
|
492
528
|
domain: { id: domain.id }
|
|
493
529
|
}
|
|
@@ -496,17 +532,21 @@ export class KpiMutation {
|
|
|
496
532
|
kpiValue.value = kpiValueResult
|
|
497
533
|
kpiValue.updater = user
|
|
498
534
|
} else {
|
|
499
|
-
|
|
535
|
+
const createData: any = {
|
|
500
536
|
kpi,
|
|
501
537
|
kpiId: kpi.id,
|
|
502
538
|
version: kpi.version,
|
|
503
539
|
valueDate: valueDateToUse,
|
|
504
540
|
value: kpiValueResult,
|
|
505
|
-
group: group ?? '',
|
|
506
541
|
domain,
|
|
507
542
|
creator: user,
|
|
508
543
|
updater: user
|
|
509
|
-
}
|
|
544
|
+
}
|
|
545
|
+
if (kpiOrgScope) {
|
|
546
|
+
createData.kpiOrgScope = kpiOrgScope
|
|
547
|
+
createData.kpiOrgScopeId = kpiOrgScope.id
|
|
548
|
+
}
|
|
549
|
+
kpiValue = Object.assign(new KpiValue(), createData)
|
|
510
550
|
}
|
|
511
551
|
return await kpiValueRepo.save(kpiValue)
|
|
512
552
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { IsNull } from 'typeorm'
|
|
1
2
|
import { Resolver, Query, FieldResolver, Root, Args, Arg, Ctx, Directive } from 'type-graphql'
|
|
2
3
|
import { Attachment } from '@things-factory/attachment-base'
|
|
3
4
|
import { Domain, getQueryBuilderFromListParams, getRepository, ListParam } from '@things-factory/shell'
|
|
@@ -7,10 +8,67 @@ import { KpiList } from './kpi-type'
|
|
|
7
8
|
import { KpiValue } from '../kpi-value/kpi-value'
|
|
8
9
|
import { KpiHistory } from './kpi-history'
|
|
9
10
|
import { Int } from 'type-graphql'
|
|
10
|
-
import { KpiCategory } from '../kpi-category/kpi-category'
|
|
11
11
|
|
|
12
12
|
@Resolver(Kpi)
|
|
13
13
|
export class KpiQuery {
|
|
14
|
+
@Directive('@privilege(category: "kpi", privilege: "query", domainOwnerGranted: true, superUserGranted: true)')
|
|
15
|
+
@Query(returns => [Kpi], { description: 'Fetch root KPIs (KPIs without parent)' })
|
|
16
|
+
async kpisLevel0(@Ctx() context: ResolverContext): Promise<Kpi[]> {
|
|
17
|
+
const { domain } = context.state
|
|
18
|
+
|
|
19
|
+
return await getRepository(Kpi).find({
|
|
20
|
+
where: { domain: { id: domain.id }, parent: null },
|
|
21
|
+
order: { name: 'ASC' }
|
|
22
|
+
})
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@Directive('@privilege(category: "kpi", privilege: "query", domainOwnerGranted: true, superUserGranted: true)')
|
|
26
|
+
@Query(returns => [Kpi], {
|
|
27
|
+
description:
|
|
28
|
+
'Fetch first level child KPIs. If rootId is provided, get children of that parent. If not, get all level 1 KPIs (children of root KPIs)'
|
|
29
|
+
})
|
|
30
|
+
async kpisLevel1(
|
|
31
|
+
@Arg('rootId', {
|
|
32
|
+
nullable: true,
|
|
33
|
+
description: 'ID of the parent KPI to get level 1 children. If not provided, returns all level 1 KPIs'
|
|
34
|
+
})
|
|
35
|
+
rootId: string,
|
|
36
|
+
@Ctx() context: ResolverContext
|
|
37
|
+
): Promise<Kpi[]> {
|
|
38
|
+
const { domain } = context.state
|
|
39
|
+
|
|
40
|
+
let whereCondition: any = { domain: { id: domain.id } }
|
|
41
|
+
|
|
42
|
+
if (rootId) {
|
|
43
|
+
// 특정 부모의 자식들
|
|
44
|
+
whereCondition.parent = { id: rootId }
|
|
45
|
+
} else {
|
|
46
|
+
// 모든 level 1 KPI들 (root KPI들의 직계 자식들)
|
|
47
|
+
// 부모가 있으면서 부모의 부모가 null인 KPI들
|
|
48
|
+
const kpis = await getRepository(Kpi)
|
|
49
|
+
.createQueryBuilder('kpi')
|
|
50
|
+
.leftJoinAndSelect('kpi.parent', 'parent')
|
|
51
|
+
.leftJoinAndSelect('kpi.children', 'children')
|
|
52
|
+
.leftJoinAndSelect('children.children', 'grandchildren')
|
|
53
|
+
.where('kpi.domain = :domainId', { domainId: domain.id })
|
|
54
|
+
.andWhere('parent.id IS NOT NULL')
|
|
55
|
+
.andWhere('parent.parent IS NULL')
|
|
56
|
+
.orderBy('kpi.name', 'ASC')
|
|
57
|
+
.getMany()
|
|
58
|
+
|
|
59
|
+
return kpis
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// rootId 제공된 경우
|
|
63
|
+
const kpis = await getRepository(Kpi).find({
|
|
64
|
+
where: whereCondition,
|
|
65
|
+
relations: ['children', 'children.children'],
|
|
66
|
+
order: { name: 'ASC' }
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
return kpis
|
|
70
|
+
}
|
|
71
|
+
|
|
14
72
|
@Directive('@privilege(category: "kpi", privilege: "query", domainOwnerGranted: true, superUserGranted: true)')
|
|
15
73
|
@Query(returns => Kpi!, { nullable: true, description: 'Fetch a single KPI by its unique identifier.' })
|
|
16
74
|
async kpi(
|
|
@@ -33,7 +91,10 @@ export class KpiQuery {
|
|
|
33
91
|
domain,
|
|
34
92
|
params,
|
|
35
93
|
repository: await getRepository(Kpi),
|
|
36
|
-
searchables: ['name', 'description']
|
|
94
|
+
searchables: ['name', 'description'],
|
|
95
|
+
filtersMap: {
|
|
96
|
+
parent: { columnName: 'id', relationColumn: 'parent' }
|
|
97
|
+
}
|
|
37
98
|
})
|
|
38
99
|
|
|
39
100
|
const [items, total] = await queryBuilder.getManyAndCount()
|
|
@@ -41,6 +102,13 @@ export class KpiQuery {
|
|
|
41
102
|
return { items, total }
|
|
42
103
|
}
|
|
43
104
|
|
|
105
|
+
@FieldResolver(type => [Kpi])
|
|
106
|
+
children(@Root() kpi: Kpi): Promise<Kpi[]> {
|
|
107
|
+
return getRepository(Kpi).find({
|
|
108
|
+
where: { parent: { id: kpi.id } }
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
|
|
44
112
|
@FieldResolver(type => String)
|
|
45
113
|
async thumbnail(@Root() kpi: Kpi): Promise<string | undefined> {
|
|
46
114
|
const attachment: Attachment = await getRepository(Attachment).findOne({
|
|
@@ -70,10 +138,26 @@ export class KpiQuery {
|
|
|
70
138
|
}
|
|
71
139
|
|
|
72
140
|
@FieldResolver(type => KpiValue, { nullable: true })
|
|
73
|
-
async value(
|
|
141
|
+
async value(
|
|
142
|
+
@Root() kpi: Kpi,
|
|
143
|
+
@Ctx() context,
|
|
144
|
+
@Arg('orgScope', { nullable: true, description: 'Organization scope filter for value' }) orgScope?: string
|
|
145
|
+
): Promise<KpiValue | null> {
|
|
74
146
|
const { domain } = context.state
|
|
147
|
+
|
|
148
|
+
let whereCondition: any = {
|
|
149
|
+
domain: { id: domain.id },
|
|
150
|
+
kpi: { id: kpi.id }
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// orgScope 필터가 있으면 KpiOrgScope로 필터링
|
|
154
|
+
if (orgScope) {
|
|
155
|
+
whereCondition.kpiOrgScope = { org: orgScope }
|
|
156
|
+
}
|
|
157
|
+
|
|
75
158
|
return await getRepository(KpiValue).findOne({
|
|
76
|
-
where:
|
|
159
|
+
where: whereCondition,
|
|
160
|
+
relations: ['kpiOrgScope'],
|
|
77
161
|
order: { valueDate: 'DESC' }
|
|
78
162
|
})
|
|
79
163
|
}
|
|
@@ -88,10 +172,10 @@ export class KpiQuery {
|
|
|
88
172
|
return kpi.vizMeta?.unit
|
|
89
173
|
}
|
|
90
174
|
|
|
91
|
-
@FieldResolver(type =>
|
|
92
|
-
async
|
|
93
|
-
if (!kpi.
|
|
94
|
-
return await getRepository(
|
|
175
|
+
@FieldResolver(type => Kpi, { nullable: true })
|
|
176
|
+
async parent(@Root() kpi: Kpi): Promise<Kpi | null> {
|
|
177
|
+
if (!kpi.parentId) return null
|
|
178
|
+
return await getRepository(Kpi).findOneBy({ id: kpi.parentId })
|
|
95
179
|
}
|
|
96
180
|
|
|
97
181
|
@FieldResolver(type => [KpiHistory], { nullable: true })
|
|
@@ -110,4 +194,30 @@ export class KpiQuery {
|
|
|
110
194
|
else qb.limit(1)
|
|
111
195
|
return await qb.getMany()
|
|
112
196
|
}
|
|
197
|
+
|
|
198
|
+
@Directive('@privilege(category: "kpi", privilege: "query", domainOwnerGranted: true, superUserGranted: true)')
|
|
199
|
+
@Query(returns => [Kpi], { description: 'Fetch KPIs in hierarchical tree structure' })
|
|
200
|
+
async kpiTree(@Ctx() context: ResolverContext): Promise<Kpi[]> {
|
|
201
|
+
const { domain } = context.state
|
|
202
|
+
|
|
203
|
+
return await getRepository(Kpi).find({
|
|
204
|
+
where: { domain: { id: domain.id }, parent: IsNull() },
|
|
205
|
+
relations: ['children', 'children.children', 'children.children.children'],
|
|
206
|
+
order: { name: 'ASC' }
|
|
207
|
+
})
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
@Directive('@privilege(category: "kpi", privilege: "query", domainOwnerGranted: true, superUserGranted: true)')
|
|
211
|
+
@Query(returns => [Kpi], { description: 'Fetch child KPIs of a given parent KPI' })
|
|
212
|
+
async kpiChildren(
|
|
213
|
+
@Arg('parentId', { description: 'ID of the parent KPI' }) parentId: string,
|
|
214
|
+
@Ctx() context: ResolverContext
|
|
215
|
+
): Promise<Kpi[]> {
|
|
216
|
+
const { domain } = context.state
|
|
217
|
+
|
|
218
|
+
return await getRepository(Kpi).find({
|
|
219
|
+
where: { domain: { id: domain.id }, parent: { id: parentId } },
|
|
220
|
+
order: { name: 'ASC' }
|
|
221
|
+
})
|
|
222
|
+
}
|
|
113
223
|
}
|
|
@@ -15,8 +15,11 @@ export class NewKpi {
|
|
|
15
15
|
@Field({ nullable: true, description: 'Detailed description of the KPI.' })
|
|
16
16
|
description?: string
|
|
17
17
|
|
|
18
|
-
@Field(type => ObjectRef, { nullable: true, description: 'Reference to the
|
|
19
|
-
|
|
18
|
+
@Field(type => ObjectRef, { nullable: true, description: 'Reference to the parent KPI in hierarchical structure.' })
|
|
19
|
+
parent?: ObjectRef
|
|
20
|
+
|
|
21
|
+
@Field({ nullable: true, description: 'Indicates whether this KPI is a leaf node (has actual measured values).' })
|
|
22
|
+
isLeaf?: boolean
|
|
20
23
|
|
|
21
24
|
@Field({ nullable: true, description: 'Calculation formula for the KPI, using metric codes and operators.' })
|
|
22
25
|
formula?: string
|
|
@@ -79,8 +82,11 @@ export class KpiPatch {
|
|
|
79
82
|
@Field({ nullable: true, description: 'Detailed description of the KPI.' })
|
|
80
83
|
description?: string
|
|
81
84
|
|
|
82
|
-
@Field(type => ObjectRef, { nullable: true, description: 'Reference to the
|
|
83
|
-
|
|
85
|
+
@Field(type => ObjectRef, { nullable: true, description: 'Reference to the parent KPI in hierarchical structure.' })
|
|
86
|
+
parent?: ObjectRef
|
|
87
|
+
|
|
88
|
+
@Field({ nullable: true, description: 'Indicates whether this KPI is a leaf node (has actual measured values).' })
|
|
89
|
+
isLeaf?: boolean
|
|
84
90
|
|
|
85
91
|
@Field({ nullable: true, description: 'Calculation formula for the KPI, using metric codes and operators.' })
|
|
86
92
|
formula?: string
|
|
@@ -15,7 +15,6 @@ import { ObjectType, Field, Int, ID, registerEnumType } from 'type-graphql'
|
|
|
15
15
|
|
|
16
16
|
import { Domain } from '@things-factory/shell'
|
|
17
17
|
import { User } from '@things-factory/auth-base'
|
|
18
|
-
import { KpiCategory } from '../kpi-category/kpi-category'
|
|
19
18
|
import { config } from '@things-factory/env'
|
|
20
19
|
import { ScalarObject } from '@things-factory/shell'
|
|
21
20
|
|
|
@@ -78,6 +77,9 @@ registerEnumType(KpiPeriodType, {
|
|
|
78
77
|
where: '"deleted_at" IS NULL',
|
|
79
78
|
unique: true
|
|
80
79
|
})
|
|
80
|
+
@Index('ix_kpi_hierarchy', (kpi: Kpi) => [kpi.domain, kpi.parent], {
|
|
81
|
+
where: '"deleted_at" IS NULL'
|
|
82
|
+
})
|
|
81
83
|
@ObjectType({
|
|
82
84
|
description:
|
|
83
85
|
'KPI entity. Represents a key performance indicator with calculation formula, target, category, and other attributes.'
|
|
@@ -111,13 +113,21 @@ export class Kpi {
|
|
|
111
113
|
@Field({ nullable: true, description: 'Detailed description of the KPI.' })
|
|
112
114
|
description?: string
|
|
113
115
|
|
|
114
|
-
@ManyToOne(() =>
|
|
115
|
-
@Field(type =>
|
|
116
|
-
|
|
116
|
+
@ManyToOne(() => Kpi, { nullable: true })
|
|
117
|
+
@Field(type => Kpi, { nullable: true, description: 'Parent KPI in hierarchical structure.' })
|
|
118
|
+
parent?: Kpi
|
|
119
|
+
|
|
120
|
+
@RelationId((kpi: Kpi) => kpi.parent)
|
|
121
|
+
@Field({ nullable: true, description: 'ID of the parent KPI.' })
|
|
122
|
+
parentId?: string
|
|
123
|
+
|
|
124
|
+
@OneToMany(() => Kpi, kpi => kpi.parent)
|
|
125
|
+
@Field(type => [Kpi], { nullable: true, description: 'Child KPIs in hierarchical structure.' })
|
|
126
|
+
children?: Kpi[]
|
|
117
127
|
|
|
118
|
-
@
|
|
119
|
-
@Field({ nullable: true, description: '
|
|
120
|
-
|
|
128
|
+
@Column({ nullable: false, default: true })
|
|
129
|
+
@Field({ nullable: true, description: 'Indicates whether this KPI is a leaf node (has actual measured values).' })
|
|
130
|
+
isLeaf?: boolean
|
|
121
131
|
|
|
122
132
|
@Column({
|
|
123
133
|
nullable: true,
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { getRepository } from '@things-factory/shell'
|
|
2
|
+
import type ResolverContext from '@things-factory/auth-base'
|
|
2
3
|
import { DataSummary } from '@things-factory/dataset'
|
|
3
4
|
import { finalizeLatestDataCollection } from '@things-factory/dataset/dist-server/controllers/finalize-data-collection'
|
|
4
5
|
import { KpiMetric } from './kpi-metric'
|
|
5
6
|
import { KpiValue, KpiValueInputType } from '../kpi-value/kpi-value'
|
|
6
7
|
import { Kpi } from '../kpi/kpi'
|
|
8
|
+
import { KpiOrgScope } from '../kpi-org-scope/kpi-org-scope'
|
|
7
9
|
import { KpiFormulaService } from '../kpi/kpi-formula.service'
|
|
8
10
|
|
|
9
11
|
/**
|
|
@@ -38,7 +40,7 @@ export async function aggregateKpiMetricValue(metricId: string, domainId: string
|
|
|
38
40
|
date: summary.date,
|
|
39
41
|
period: summary.period,
|
|
40
42
|
value: summary.summary[metric.fieldName],
|
|
41
|
-
|
|
43
|
+
org: {
|
|
42
44
|
key01: summary.key01,
|
|
43
45
|
key02: summary.key02,
|
|
44
46
|
key03: summary.key03,
|
|
@@ -56,32 +58,74 @@ export async function aggregateKpiMetricValue(metricId: string, domainId: string
|
|
|
56
58
|
const kpi = await getRepository(Kpi).findOne({ where: { name: metric.name, domain: { id: domainId } } })
|
|
57
59
|
if (!kpi) throw new Error('KPI 정보 없음 (metric.name과 동일한 KPI name 기준, 실제 연동 구조에 맞게 보완 필요)')
|
|
58
60
|
|
|
59
|
-
// KPI Value version, valueDate,
|
|
61
|
+
// KPI Value version, valueDate, org 등 매핑
|
|
60
62
|
const savedValues = []
|
|
63
|
+
const kpiOrgScopeRepo = getRepository(KpiOrgScope, context.state?.tx)
|
|
64
|
+
const kpiValueRepo = getRepository(KpiValue, context.state?.tx)
|
|
65
|
+
|
|
61
66
|
for (const v of values) {
|
|
62
67
|
const valueDate = v.date
|
|
63
|
-
const
|
|
68
|
+
const orgData = v.org
|
|
64
69
|
const version = kpi.version || 1
|
|
65
70
|
const value = v.value
|
|
66
71
|
if (value == null || isNaN(value)) continue
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
72
|
+
|
|
73
|
+
// KpiOrgScope 찾기 또는 생성
|
|
74
|
+
let kpiOrgScope: KpiOrgScope | null = null
|
|
75
|
+
|
|
76
|
+
// key들로 조합된 org 식별자 생성
|
|
77
|
+
const orgIdentifier =
|
|
78
|
+
[orgData.key01, orgData.key02, orgData.key03, orgData.key04, orgData.key05].filter(Boolean).join('-') ||
|
|
79
|
+
'unknown-org'
|
|
80
|
+
|
|
81
|
+
// 기존 KpiOrgScope 조회 (entityName 기준)
|
|
82
|
+
kpiOrgScope = await kpiOrgScopeRepo.findOne({
|
|
83
|
+
where: { entityName: orgIdentifier, domain: { id: domainId } }
|
|
71
84
|
})
|
|
72
|
-
|
|
85
|
+
|
|
86
|
+
// 없으면 새로 생성
|
|
87
|
+
if (!kpiOrgScope) {
|
|
88
|
+
kpiOrgScope = await kpiOrgScopeRepo.save({
|
|
89
|
+
entityType: 'DataSummary', // DataSummary에서 온 데이터임을 표시
|
|
90
|
+
entityId: `datasummary-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
91
|
+
entityName: orgIdentifier,
|
|
92
|
+
org: orgIdentifier, // legacy field
|
|
93
|
+
scope01: orgData.key01,
|
|
94
|
+
scope02: orgData.key02,
|
|
95
|
+
scope03: orgData.key03,
|
|
96
|
+
scope04: orgData.key04,
|
|
97
|
+
scope05: orgData.key05,
|
|
98
|
+
domain: kpi.domain,
|
|
99
|
+
creator: context.state?.user,
|
|
100
|
+
updater: context.state?.user
|
|
101
|
+
})
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// upsert KpiValue (동일 KPI, valueDate, kpiOrgScope, version 기준)
|
|
105
|
+
const existing = await kpiValueRepo.findOne({
|
|
106
|
+
where: {
|
|
107
|
+
kpi: { id: kpi.id },
|
|
108
|
+
valueDate,
|
|
109
|
+
kpiOrgScope: { id: kpiOrgScope.id },
|
|
110
|
+
version,
|
|
111
|
+
domain: { id: domainId }
|
|
112
|
+
}
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
let entity = existing || kpiValueRepo.create()
|
|
73
116
|
entity.kpi = kpi
|
|
74
117
|
entity.kpiId = kpi.id
|
|
75
118
|
entity.version = version
|
|
76
119
|
entity.valueDate = valueDate
|
|
77
120
|
entity.value = value
|
|
78
|
-
entity.
|
|
121
|
+
entity.kpiOrgScope = kpiOrgScope
|
|
122
|
+
entity.kpiOrgScopeId = kpiOrgScope.id
|
|
79
123
|
entity.inputType = KpiValueInputType.AUTO
|
|
80
|
-
entity.source = 'AUTO'
|
|
124
|
+
entity.source = 'AUTO-AGGREGATE'
|
|
81
125
|
entity.domain = kpi.domain
|
|
82
126
|
entity.creator = context.state?.user
|
|
83
127
|
entity.updater = context.state?.user
|
|
84
|
-
entity = await
|
|
128
|
+
entity = await kpiValueRepo.save(entity)
|
|
85
129
|
savedValues.push(entity)
|
|
86
130
|
}
|
|
87
131
|
return savedValues
|