@things-factory/kpi 9.0.31 → 9.0.33

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.
Files changed (365) hide show
  1. package/README.md +1 -2
  2. package/client/charts/kpi-boxplot-chart.ts +182 -42
  3. package/client/charts/kpi-radar-chart.ts +9 -9
  4. package/client/pages/kpi/kpi-list-page.ts +196 -32
  5. package/client/pages/kpi/kpi-overview.ts +9 -11
  6. package/client/pages/kpi/kpi-tree-page.ts +409 -0
  7. package/client/pages/kpi/kpi-view.ts +187 -0
  8. package/client/pages/kpi-dashboard/cards/kpi-level1-card.ts +1 -1
  9. package/client/pages/kpi-dashboard/cards/kpi-level2-comparison.ts +1 -1
  10. package/client/pages/kpi-dashboard/cards/kpi-level3-comparison.ts +1 -1
  11. package/client/pages/kpi-dashboard/components/kpi-left-panel.ts +198 -160
  12. package/client/pages/kpi-dashboard/components/kpi-map-panel.ts +133 -0
  13. package/client/pages/kpi-dashboard/components/kpi-region-popup.ts +3 -2
  14. package/client/pages/kpi-dashboard/kpi-dashboard-map.ts +291 -48
  15. package/client/pages/kpi-dashboard/kpi-dashboard.ts +28 -30
  16. package/client/pages/kpi-history/kpi-history-list-page.ts +11 -11
  17. package/client/pages/kpi-metric/kpi-metric-list-page.ts +10 -2
  18. package/client/pages/kpi-metric-value/kpi-metric-value-editor-page.ts +7 -7
  19. package/client/pages/kpi-metric-value/kpi-metric-value-importer.ts +2 -2
  20. package/client/pages/kpi-metric-value/kpi-metric-value-list-page.ts +16 -8
  21. package/client/pages/kpi-metric-value/kpi-metric-value-manual-entry-form.ts +5 -5
  22. package/client/pages/kpi-statistic/kpi-statistic-editor-page.ts +1 -2
  23. package/client/pages/kpi-statistic/kpi-statistic-list-page.ts +10 -2
  24. package/client/pages/kpi-value/kpi-value-editor-page.ts +11 -7
  25. package/client/pages/kpi-value/kpi-value-list-page.ts +31 -7
  26. package/client/route.ts +2 -9
  27. package/design-entities.md +8 -12
  28. package/dist-client/charts/kpi-boxplot-chart.d.ts +2 -0
  29. package/dist-client/charts/kpi-boxplot-chart.js +168 -42
  30. package/dist-client/charts/kpi-boxplot-chart.js.map +1 -1
  31. package/dist-client/charts/kpi-radar-chart.js +9 -9
  32. package/dist-client/charts/kpi-radar-chart.js.map +1 -1
  33. package/dist-client/pages/kpi/kpi-list-page.d.ts +19 -3
  34. package/dist-client/pages/kpi/kpi-list-page.js +188 -32
  35. package/dist-client/pages/kpi/kpi-list-page.js.map +1 -1
  36. package/dist-client/pages/kpi/kpi-overview.js +9 -11
  37. package/dist-client/pages/kpi/kpi-overview.js.map +1 -1
  38. package/dist-client/pages/kpi/kpi-tree-page.d.ts +59 -0
  39. package/dist-client/pages/kpi/kpi-tree-page.js +403 -0
  40. package/dist-client/pages/kpi/kpi-tree-page.js.map +1 -0
  41. package/dist-client/pages/kpi/kpi-view.d.ts +12 -0
  42. package/dist-client/pages/kpi/kpi-view.js +191 -0
  43. package/dist-client/pages/kpi/kpi-view.js.map +1 -0
  44. package/dist-client/pages/kpi-dashboard/cards/kpi-level1-card.js +1 -1
  45. package/dist-client/pages/kpi-dashboard/cards/kpi-level1-card.js.map +1 -1
  46. package/dist-client/pages/kpi-dashboard/cards/kpi-level2-comparison.js +1 -1
  47. package/dist-client/pages/kpi-dashboard/cards/kpi-level2-comparison.js.map +1 -1
  48. package/dist-client/pages/kpi-dashboard/cards/kpi-level3-comparison.js +1 -1
  49. package/dist-client/pages/kpi-dashboard/cards/kpi-level3-comparison.js.map +1 -1
  50. package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.d.ts +3 -1
  51. package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.js +197 -161
  52. package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.js.map +1 -1
  53. package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.d.ts +5 -0
  54. package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.js +146 -0
  55. package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.js.map +1 -1
  56. package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.js +3 -2
  57. package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.js.map +1 -1
  58. package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.d.ts +3 -1
  59. package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js +268 -46
  60. package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js.map +1 -1
  61. package/dist-client/pages/kpi-dashboard/kpi-dashboard.js +28 -30
  62. package/dist-client/pages/kpi-dashboard/kpi-dashboard.js.map +1 -1
  63. package/dist-client/pages/kpi-history/kpi-history-list-page.d.ts +6 -1
  64. package/dist-client/pages/kpi-history/kpi-history-list-page.js +11 -11
  65. package/dist-client/pages/kpi-history/kpi-history-list-page.js.map +1 -1
  66. package/dist-client/pages/kpi-metric/kpi-metric-list-page.d.ts +5 -0
  67. package/dist-client/pages/kpi-metric/kpi-metric-list-page.js +10 -2
  68. package/dist-client/pages/kpi-metric/kpi-metric-list-page.js.map +1 -1
  69. package/dist-client/pages/kpi-metric-value/kpi-metric-value-editor-page.d.ts +1 -1
  70. package/dist-client/pages/kpi-metric-value/kpi-metric-value-editor-page.js +8 -8
  71. package/dist-client/pages/kpi-metric-value/kpi-metric-value-editor-page.js.map +1 -1
  72. package/dist-client/pages/kpi-metric-value/kpi-metric-value-importer.js +2 -2
  73. package/dist-client/pages/kpi-metric-value/kpi-metric-value-importer.js.map +1 -1
  74. package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.d.ts +5 -0
  75. package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.js +16 -8
  76. package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.js.map +1 -1
  77. package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-form.d.ts +1 -1
  78. package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-form.js +6 -6
  79. package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-form.js.map +1 -1
  80. package/dist-client/pages/kpi-statistic/kpi-statistic-editor-page.js +1 -2
  81. package/dist-client/pages/kpi-statistic/kpi-statistic-editor-page.js.map +1 -1
  82. package/dist-client/pages/kpi-statistic/kpi-statistic-list-page.d.ts +5 -0
  83. package/dist-client/pages/kpi-statistic/kpi-statistic-list-page.js +10 -2
  84. package/dist-client/pages/kpi-statistic/kpi-statistic-list-page.js.map +1 -1
  85. package/dist-client/pages/kpi-value/kpi-value-editor-page.d.ts +2 -1
  86. package/dist-client/pages/kpi-value/kpi-value-editor-page.js +16 -8
  87. package/dist-client/pages/kpi-value/kpi-value-editor-page.js.map +1 -1
  88. package/dist-client/pages/kpi-value/kpi-value-list-page.d.ts +5 -0
  89. package/dist-client/pages/kpi-value/kpi-value-list-page.js +31 -7
  90. package/dist-client/pages/kpi-value/kpi-value-list-page.js.map +1 -1
  91. package/dist-client/route.d.ts +1 -1
  92. package/dist-client/route.js +2 -8
  93. package/dist-client/route.js.map +1 -1
  94. package/dist-client/tsconfig.tsbuildinfo +1 -1
  95. package/dist-server/controllers/kpi-metric-value-provider.d.ts +1 -1
  96. package/dist-server/controllers/kpi-metric-value-provider.js +4 -4
  97. package/dist-server/controllers/kpi-metric-value-provider.js.map +1 -1
  98. package/dist-server/controllers/kpi-value-provider.d.ts +1 -1
  99. package/dist-server/controllers/kpi-value-provider.js +3 -3
  100. package/dist-server/controllers/kpi-value-provider.js.map +1 -1
  101. package/dist-server/migrations/1752190849680-seed-kpi-metrics.d.ts +6 -0
  102. package/dist-server/migrations/1752190849680-seed-kpi-metrics.js +101 -0
  103. package/dist-server/migrations/1752190849680-seed-kpi-metrics.js.map +1 -0
  104. package/dist-server/migrations/1752190849681-seed-kpi.d.ts +5 -0
  105. package/dist-server/migrations/1752190849681-seed-kpi.js +315 -0
  106. package/dist-server/migrations/1752190849681-seed-kpi.js.map +1 -0
  107. package/dist-server/migrations/1752192090123-add-grades-to-kpi.d.ts +7 -0
  108. package/dist-server/migrations/1752192090123-add-grades-to-kpi.js +51 -0
  109. package/dist-server/migrations/1752192090123-add-grades-to-kpi.js.map +1 -0
  110. package/dist-server/migrations/1752192090124-add-kpi-statistics.d.ts +5 -0
  111. package/dist-server/migrations/1752192090124-add-kpi-statistics.js +710 -0
  112. package/dist-server/migrations/1752192090124-add-kpi-statistics.js.map +1 -0
  113. package/dist-server/migrations/1752192090128-seed-kpi-org-scope.d.ts +6 -0
  114. package/dist-server/migrations/1752192090128-seed-kpi-org-scope.js +111 -0
  115. package/dist-server/migrations/1752192090128-seed-kpi-org-scope.js.map +1 -0
  116. package/dist-server/migrations/1752192090129-seed-kpi-values.d.ts +6 -0
  117. package/dist-server/migrations/1752192090129-seed-kpi-values.js +187 -0
  118. package/dist-server/migrations/1752192090129-seed-kpi-values.js.map +1 -0
  119. package/dist-server/migrations/grade-data/x11-performance-table.json +962 -0
  120. package/dist-server/migrations/grade-data/x12-performance-table.json +611 -0
  121. package/dist-server/migrations/grade-data/x14-performance-table.json +42 -0
  122. package/dist-server/migrations/grade-data/x21-performance-table.json +889 -0
  123. package/dist-server/migrations/grade-data/x22-performance-table.json +1064 -0
  124. package/dist-server/migrations/grade-data/x23-performance-table.json +42 -0
  125. package/dist-server/migrations/grade-data/x31-performance-table.json +644 -0
  126. package/dist-server/migrations/grade-data/x32-performance-table.json +993 -0
  127. package/dist-server/migrations/grade-data/x33-performance-table.json +195 -0
  128. package/dist-server/migrations/grade-data/x34-performance-table.json +12 -0
  129. package/dist-server/migrations/grade-data/x35-performance-table.json +42 -0
  130. package/dist-server/migrations/grade-data/x41-performance-table.json +825 -0
  131. package/dist-server/migrations/grade-data/x42-performance-table.json +786 -0
  132. package/dist-server/migrations/grade-data/x43-performance-table.json +12 -0
  133. package/dist-server/migrations/grade-data/x44-performance-table.json +42 -0
  134. package/dist-server/migrations/grade-data/x51-performance-table.json +924 -0
  135. package/dist-server/migrations/grade-data/x52-performance-table.json +42 -0
  136. package/dist-server/migrations/grade-data/x61-performance-table.json +261 -0
  137. package/dist-server/migrations/grade-data/x62-performance-table.json +42 -0
  138. package/dist-server/migrations/seed-data/kpi-metrics-seed.json +454 -0
  139. package/dist-server/migrations/seed-data/kpi-org-scope-seed.json +1676 -0
  140. package/dist-server/migrations/seed-data/kpi-scopes-seed.json +121 -0
  141. package/dist-server/migrations/seed-data/kpi-values-seed.json +402 -0
  142. package/dist-server/migrations/seed-data/kpis-seed.json +488 -0
  143. package/dist-server/migrations/seed-data/scope-definitions-seed.json +90 -0
  144. package/dist-server/service/index.d.ts +4 -7
  145. package/dist-server/service/index.js +10 -13
  146. package/dist-server/service/index.js.map +1 -1
  147. package/dist-server/service/kpi/aggregate-kpi.js +30 -13
  148. package/dist-server/service/kpi/aggregate-kpi.js.map +1 -1
  149. package/dist-server/service/kpi/kpi-formula.service.d.ts +15 -0
  150. package/dist-server/service/kpi/kpi-formula.service.js +90 -0
  151. package/dist-server/service/kpi/kpi-formula.service.js.map +1 -1
  152. package/dist-server/service/kpi/kpi-history.d.ts +0 -3
  153. package/dist-server/service/kpi/kpi-history.js +0 -10
  154. package/dist-server/service/kpi/kpi-history.js.map +1 -1
  155. package/dist-server/service/kpi/kpi-mutation.d.ts +1 -1
  156. package/dist-server/service/kpi/kpi-mutation.js +57 -20
  157. package/dist-server/service/kpi/kpi-mutation.js.map +1 -1
  158. package/dist-server/service/kpi/kpi-query.d.ts +7 -3
  159. package/dist-server/service/kpi/kpi-query.js +126 -10
  160. package/dist-server/service/kpi/kpi-query.js.map +1 -1
  161. package/dist-server/service/kpi/kpi-type.d.ts +4 -2
  162. package/dist-server/service/kpi/kpi-type.js +12 -4
  163. package/dist-server/service/kpi/kpi-type.js.map +1 -1
  164. package/dist-server/service/kpi/kpi.d.ts +4 -3
  165. package/dist-server/service/kpi/kpi.js +20 -8
  166. package/dist-server/service/kpi/kpi.js.map +1 -1
  167. package/dist-server/service/kpi-metric/aggregate-kpi-metric.js +46 -11
  168. package/dist-server/service/kpi-metric/aggregate-kpi-metric.js.map +1 -1
  169. package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.d.ts +1 -1
  170. package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.js +6 -6
  171. package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.js.map +1 -1
  172. package/dist-server/service/kpi-metric-value/kpi-metric-value-type.d.ts +2 -2
  173. package/dist-server/service/kpi-metric-value/kpi-metric-value-type.js +4 -4
  174. package/dist-server/service/kpi-metric-value/kpi-metric-value-type.js.map +1 -1
  175. package/dist-server/service/kpi-metric-value/kpi-metric-value.d.ts +1 -1
  176. package/dist-server/service/kpi-metric-value/kpi-metric-value.js +3 -3
  177. package/dist-server/service/kpi-metric-value/kpi-metric-value.js.map +1 -1
  178. package/dist-server/service/kpi-org-scope/index.d.ts +5 -0
  179. package/dist-server/service/kpi-org-scope/index.js +9 -0
  180. package/dist-server/service/kpi-org-scope/index.js.map +1 -0
  181. package/dist-server/service/kpi-org-scope/kpi-org-scope-mutation.d.ts +8 -0
  182. package/dist-server/service/kpi-org-scope/kpi-org-scope-mutation.js +170 -0
  183. package/dist-server/service/kpi-org-scope/kpi-org-scope-mutation.js.map +1 -0
  184. package/dist-server/service/kpi-org-scope/kpi-org-scope-query.d.ts +14 -0
  185. package/dist-server/service/kpi-org-scope/kpi-org-scope-query.js +152 -0
  186. package/dist-server/service/kpi-org-scope/kpi-org-scope-query.js.map +1 -0
  187. package/dist-server/service/kpi-org-scope/kpi-org-scope-type.d.ts +26 -0
  188. package/dist-server/service/kpi-org-scope/kpi-org-scope-type.js +101 -0
  189. package/dist-server/service/kpi-org-scope/kpi-org-scope-type.js.map +1 -0
  190. package/dist-server/service/kpi-org-scope/kpi-org-scope.d.ts +26 -0
  191. package/dist-server/service/kpi-org-scope/kpi-org-scope.js +135 -0
  192. package/dist-server/service/kpi-org-scope/kpi-org-scope.js.map +1 -0
  193. package/dist-server/service/kpi-scope/index.d.ts +9 -0
  194. package/dist-server/service/kpi-scope/index.js +14 -0
  195. package/dist-server/service/kpi-scope/index.js.map +1 -0
  196. package/dist-server/service/kpi-scope/kpi-scope-mutation.d.ts +9 -0
  197. package/dist-server/service/kpi-scope/kpi-scope-mutation.js +135 -0
  198. package/dist-server/service/kpi-scope/kpi-scope-mutation.js.map +1 -0
  199. package/dist-server/service/kpi-scope/kpi-scope-query.d.ts +11 -0
  200. package/dist-server/service/kpi-scope/kpi-scope-query.js +89 -0
  201. package/dist-server/service/kpi-scope/kpi-scope-query.js.map +1 -0
  202. package/dist-server/service/kpi-scope/kpi-scope-type.d.ts +35 -0
  203. package/dist-server/service/kpi-scope/kpi-scope-type.js +138 -0
  204. package/dist-server/service/kpi-scope/kpi-scope-type.js.map +1 -0
  205. package/dist-server/service/kpi-scope/kpi-scope.d.ts +38 -0
  206. package/dist-server/service/kpi-scope/kpi-scope.js +144 -0
  207. package/dist-server/service/kpi-scope/kpi-scope.js.map +1 -0
  208. package/dist-server/service/kpi-statistic/kpi-statistic-batch.service.d.ts +43 -0
  209. package/dist-server/service/kpi-statistic/kpi-statistic-batch.service.js +181 -0
  210. package/dist-server/service/kpi-statistic/kpi-statistic-batch.service.js.map +1 -0
  211. package/dist-server/service/kpi-statistic/kpi-statistic-calculation.service.d.ts +50 -0
  212. package/dist-server/service/kpi-statistic/kpi-statistic-calculation.service.js +324 -0
  213. package/dist-server/service/kpi-statistic/kpi-statistic-calculation.service.js.map +1 -0
  214. package/dist-server/service/kpi-statistic/kpi-statistic-mutation.d.ts +4 -0
  215. package/dist-server/service/kpi-statistic/kpi-statistic-mutation.js +76 -0
  216. package/dist-server/service/kpi-statistic/kpi-statistic-mutation.js.map +1 -1
  217. package/dist-server/service/kpi-statistic/kpi-statistic-query.d.ts +5 -1
  218. package/dist-server/service/kpi-statistic/kpi-statistic-query.js +92 -1
  219. package/dist-server/service/kpi-statistic/kpi-statistic-query.js.map +1 -1
  220. package/dist-server/service/kpi-statistic/kpi-statistic.d.ts +4 -0
  221. package/dist-server/service/kpi-statistic/kpi-statistic.js +33 -0
  222. package/dist-server/service/kpi-statistic/kpi-statistic.js.map +1 -1
  223. package/dist-server/service/kpi-value/kpi-value-mutation.js +71 -7
  224. package/dist-server/service/kpi-value/kpi-value-mutation.js.map +1 -1
  225. package/dist-server/service/kpi-value/kpi-value-type.d.ts +4 -2
  226. package/dist-server/service/kpi-value/kpi-value-type.js +12 -4
  227. package/dist-server/service/kpi-value/kpi-value-type.js.map +1 -1
  228. package/dist-server/service/kpi-value/kpi-value.d.ts +3 -1
  229. package/dist-server/service/kpi-value/kpi-value.js +11 -5
  230. package/dist-server/service/kpi-value/kpi-value.js.map +1 -1
  231. package/dist-server/service/utils/value-date-util.d.ts +1 -0
  232. package/dist-server/service/utils/value-date-util.js +41 -0
  233. package/dist-server/service/utils/value-date-util.js.map +1 -1
  234. package/dist-server/tsconfig.json +10 -0
  235. package/dist-server/tsconfig.tsbuildinfo +1 -1
  236. package/package.json +7 -6
  237. package/server/@types/index.d.ts +11 -0
  238. package/server/controllers/kpi-metric-value-provider.ts +5 -5
  239. package/server/controllers/kpi-value-provider.ts +4 -4
  240. package/server/migrations/1752190849680-seed-kpi-metrics.ts +124 -0
  241. package/server/migrations/1752190849681-seed-kpi.ts +356 -0
  242. package/server/migrations/1752192090123-add-grades-to-kpi.ts +67 -0
  243. package/server/migrations/1752192090124-add-kpi-statistics.ts +719 -0
  244. package/server/migrations/1752192090128-seed-kpi-org-scope.ts +132 -0
  245. package/server/migrations/1752192090129-seed-kpi-values.ts +207 -0
  246. package/server/migrations/grade-data/x11-performance-table.json +962 -0
  247. package/server/migrations/grade-data/x12-performance-table.json +611 -0
  248. package/server/migrations/grade-data/x14-performance-table.json +42 -0
  249. package/server/migrations/grade-data/x21-performance-table.json +889 -0
  250. package/server/migrations/grade-data/x22-performance-table.json +1064 -0
  251. package/server/migrations/grade-data/x23-performance-table.json +42 -0
  252. package/server/migrations/grade-data/x31-performance-table.json +644 -0
  253. package/server/migrations/grade-data/x32-performance-table.json +993 -0
  254. package/server/migrations/grade-data/x33-performance-table.json +195 -0
  255. package/server/migrations/grade-data/x34-performance-table.json +12 -0
  256. package/server/migrations/grade-data/x35-performance-table.json +42 -0
  257. package/server/migrations/grade-data/x41-performance-table.json +825 -0
  258. package/server/migrations/grade-data/x42-performance-table.json +786 -0
  259. package/server/migrations/grade-data/x43-performance-table.json +12 -0
  260. package/server/migrations/grade-data/x44-performance-table.json +42 -0
  261. package/server/migrations/grade-data/x51-performance-table.json +924 -0
  262. package/server/migrations/grade-data/x52-performance-table.json +42 -0
  263. package/server/migrations/grade-data/x61-performance-table.json +261 -0
  264. package/server/migrations/grade-data/x62-performance-table.json +42 -0
  265. package/server/migrations/seed-data/kpi-metrics-seed.json +454 -0
  266. package/server/migrations/seed-data/kpi-org-scope-seed.json +1676 -0
  267. package/server/migrations/seed-data/kpi-scopes-seed.json +121 -0
  268. package/server/migrations/seed-data/kpi-values-seed.json +402 -0
  269. package/server/migrations/seed-data/kpis-seed.json +488 -0
  270. package/server/migrations/seed-data/scope-definitions-seed.json +90 -0
  271. package/server/service/index.ts +10 -13
  272. package/server/service/kpi/aggregate-kpi.ts +31 -13
  273. package/server/service/kpi/kpi-formula.service.ts +101 -0
  274. package/server/service/kpi/kpi-history.ts +0 -8
  275. package/server/service/kpi/kpi-mutation.ts +59 -19
  276. package/server/service/kpi/kpi-query.ts +119 -8
  277. package/server/service/kpi/kpi-type.ts +10 -4
  278. package/server/service/kpi/kpi.ts +17 -7
  279. package/server/service/kpi-metric/aggregate-kpi-metric.ts +55 -11
  280. package/server/service/kpi-metric-value/kpi-metric-value-mutation.ts +6 -6
  281. package/server/service/kpi-metric-value/kpi-metric-value-type.ts +4 -4
  282. package/server/service/kpi-metric-value/kpi-metric-value.ts +3 -3
  283. package/server/service/kpi-org-scope/index.ts +6 -0
  284. package/server/service/kpi-org-scope/kpi-org-scope-mutation.ts +173 -0
  285. package/server/service/kpi-org-scope/kpi-org-scope-query.ts +127 -0
  286. package/server/service/kpi-org-scope/kpi-org-scope-type.ts +68 -0
  287. package/server/service/kpi-org-scope/kpi-org-scope.ts +123 -0
  288. package/server/service/kpi-scope/index.ts +11 -0
  289. package/server/service/kpi-scope/kpi-scope-mutation.ts +129 -0
  290. package/server/service/kpi-scope/kpi-scope-query.ts +63 -0
  291. package/server/service/kpi-scope/kpi-scope-type.ts +96 -0
  292. package/server/service/kpi-scope/kpi-scope.ts +143 -0
  293. package/server/service/kpi-statistic/kpi-statistic-batch.service.ts +231 -0
  294. package/server/service/kpi-statistic/kpi-statistic-calculation.service.ts +410 -0
  295. package/server/service/kpi-statistic/kpi-statistic-mutation.ts +97 -0
  296. package/server/service/kpi-statistic/kpi-statistic-query.ts +89 -2
  297. package/server/service/kpi-statistic/kpi-statistic.ts +32 -0
  298. package/server/service/kpi-value/kpi-value-mutation.ts +73 -7
  299. package/server/service/kpi-value/kpi-value-type.ts +10 -4
  300. package/server/service/kpi-value/kpi-value.ts +10 -5
  301. package/server/service/utils/value-date-util.ts +47 -0
  302. package/server/types/global.d.ts +8 -0
  303. package/things-factory.config.js +1 -0
  304. package/translations/en.json +15 -3
  305. package/translations/ja.json +13 -3
  306. package/translations/ko.json +15 -3
  307. package/translations/ms.json +13 -3
  308. package/translations/zh.json +13 -3
  309. package/client/pages/kpi-category/kpi-category-importer.ts +0 -90
  310. package/client/pages/kpi-category/kpi-category-list-page.ts +0 -537
  311. package/client/pages/kpi-category/kpi-category-value-calculator.ts +0 -233
  312. package/client/pages/kpi-category-value/kpi-category-value-list-page.ts +0 -404
  313. package/dist-client/pages/kpi-category/kpi-category-importer.d.ts +0 -23
  314. package/dist-client/pages/kpi-category/kpi-category-importer.js +0 -92
  315. package/dist-client/pages/kpi-category/kpi-category-importer.js.map +0 -1
  316. package/dist-client/pages/kpi-category/kpi-category-list-page.d.ts +0 -74
  317. package/dist-client/pages/kpi-category/kpi-category-list-page.js +0 -517
  318. package/dist-client/pages/kpi-category/kpi-category-list-page.js.map +0 -1
  319. package/dist-client/pages/kpi-category/kpi-category-value-calculator.d.ts +0 -13
  320. package/dist-client/pages/kpi-category/kpi-category-value-calculator.js +0 -256
  321. package/dist-client/pages/kpi-category/kpi-category-value-calculator.js.map +0 -1
  322. package/dist-client/pages/kpi-category-value/kpi-category-value-list-page.d.ts +0 -63
  323. package/dist-client/pages/kpi-category-value/kpi-category-value-list-page.js +0 -393
  324. package/dist-client/pages/kpi-category-value/kpi-category-value-list-page.js.map +0 -1
  325. package/dist-server/service/kpi-category/index.d.ts +0 -6
  326. package/dist-server/service/kpi-category/index.js +0 -10
  327. package/dist-server/service/kpi-category/index.js.map +0 -1
  328. package/dist-server/service/kpi-category/kpi-category-mutation.d.ts +0 -9
  329. package/dist-server/service/kpi-category/kpi-category-mutation.js +0 -221
  330. package/dist-server/service/kpi-category/kpi-category-mutation.js.map +0 -1
  331. package/dist-server/service/kpi-category/kpi-category-query.d.ts +0 -18
  332. package/dist-server/service/kpi-category/kpi-category-query.js +0 -115
  333. package/dist-server/service/kpi-category/kpi-category-query.js.map +0 -1
  334. package/dist-server/service/kpi-category/kpi-category-type.d.ts +0 -24
  335. package/dist-server/service/kpi-category/kpi-category-type.js +0 -100
  336. package/dist-server/service/kpi-category/kpi-category-type.js.map +0 -1
  337. package/dist-server/service/kpi-category/kpi-category.d.ts +0 -22
  338. package/dist-server/service/kpi-category/kpi-category.js +0 -106
  339. package/dist-server/service/kpi-category/kpi-category.js.map +0 -1
  340. package/dist-server/service/kpi-category-value/index.d.ts +0 -6
  341. package/dist-server/service/kpi-category-value/index.js +0 -10
  342. package/dist-server/service/kpi-category-value/index.js.map +0 -1
  343. package/dist-server/service/kpi-category-value/kpi-category-value-mutation.d.ts +0 -8
  344. package/dist-server/service/kpi-category-value/kpi-category-value-mutation.js +0 -102
  345. package/dist-server/service/kpi-category-value/kpi-category-value-mutation.js.map +0 -1
  346. package/dist-server/service/kpi-category-value/kpi-category-value-query.d.ts +0 -13
  347. package/dist-server/service/kpi-category-value/kpi-category-value-query.js +0 -91
  348. package/dist-server/service/kpi-category-value/kpi-category-value-query.js.map +0 -1
  349. package/dist-server/service/kpi-category-value/kpi-category-value-type.d.ts +0 -19
  350. package/dist-server/service/kpi-category-value/kpi-category-value-type.js +0 -73
  351. package/dist-server/service/kpi-category-value/kpi-category-value-type.js.map +0 -1
  352. package/dist-server/service/kpi-category-value/kpi-category-value.d.ts +0 -19
  353. package/dist-server/service/kpi-category-value/kpi-category-value.js +0 -91
  354. package/dist-server/service/kpi-category-value/kpi-category-value.js.map +0 -1
  355. package/helps/kpi/kpi-category.md +0 -160
  356. package/server/service/kpi-category/index.ts +0 -7
  357. package/server/service/kpi-category/kpi-category-mutation.ts +0 -217
  358. package/server/service/kpi-category/kpi-category-query.ts +0 -87
  359. package/server/service/kpi-category/kpi-category-type.ts +0 -73
  360. package/server/service/kpi-category/kpi-category.ts +0 -95
  361. package/server/service/kpi-category-value/index.ts +0 -7
  362. package/server/service/kpi-category-value/kpi-category-value-mutation.ts +0 -88
  363. package/server/service/kpi-category-value/kpi-category-value-query.ts +0 -62
  364. package/server/service/kpi-category-value/kpi-category-value-type.ts +0 -48
  365. package/server/service/kpi-category-value/kpi-category-value.ts +0 -79
@@ -1,9 +1,12 @@
1
1
  import { Resolver, Mutation, Arg, Ctx, Directive } from 'type-graphql'
2
2
  import { In } from 'typeorm'
3
3
  import { getRepository } from '@things-factory/shell'
4
+ import type ResolverContext from '@things-factory/auth-base'
4
5
 
5
6
  import { KpiStatistic } from './kpi-statistic.js'
6
7
  import { NewKpiStatistic, KpiStatisticPatch } from './kpi-statistic-type.js'
8
+ import { KpiStatisticCalculationService } from './kpi-statistic-calculation.service.js'
9
+ import { KpiPeriodType } from '../kpi/kpi.js'
7
10
 
8
11
  @Resolver(KpiStatistic)
9
12
  export class KpiStatisticMutation {
@@ -191,4 +194,98 @@ export class KpiStatisticMutation {
191
194
 
192
195
  return true
193
196
  }
197
+
198
+ @Directive('@transaction')
199
+ @Directive('@privilege(category: "kpi", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)')
200
+ @Mutation(returns => [KpiStatistic], {
201
+ description: 'Calculate statistics for a specific KPI and period from KpiValue data'
202
+ })
203
+ async calculateKpiStatistics(
204
+ @Arg('kpiId') kpiId: string,
205
+ @Arg('periodType', type => KpiPeriodType) periodType: KpiPeriodType,
206
+ @Arg('valueDate') valueDate: string,
207
+ @Ctx() context: ResolverContext
208
+ ): Promise<KpiStatistic[]> {
209
+ const overallStats = await KpiStatisticCalculationService.calculateKpiStatistics(
210
+ kpiId,
211
+ periodType,
212
+ valueDate,
213
+ null, // 전체 통계
214
+ context
215
+ )
216
+
217
+ const scopedStats = await KpiStatisticCalculationService.calculateScopedStatistics(
218
+ kpiId,
219
+ periodType,
220
+ valueDate,
221
+ context
222
+ )
223
+
224
+ return [overallStats, ...scopedStats].filter(Boolean)
225
+ }
226
+
227
+ @Directive('@transaction')
228
+ @Directive('@privilege(category: "kpi", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)')
229
+ @Mutation(returns => [KpiStatistic], {
230
+ description: 'Calculate statistics for all KPIs in a specific period from KpiValue data'
231
+ })
232
+ async calculateAllKpiStatistics(
233
+ @Arg('periodType', type => KpiPeriodType) periodType: KpiPeriodType,
234
+ @Arg('valueDate') valueDate: string,
235
+ @Ctx() context: ResolverContext
236
+ ): Promise<KpiStatistic[]> {
237
+ return await KpiStatisticCalculationService.calculateAllStatistics(
238
+ periodType,
239
+ valueDate,
240
+ context
241
+ )
242
+ }
243
+
244
+ @Directive('@transaction')
245
+ @Directive('@privilege(category: "kpi", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)')
246
+ @Mutation(returns => [KpiStatistic], {
247
+ description: 'Recalculate statistics for dashboard regions (scope02-based statistics)'
248
+ })
249
+ async calculateRegionalStatistics(
250
+ @Arg('periodType', type => KpiPeriodType, { defaultValue: KpiPeriodType.MONTH }) periodType: KpiPeriodType,
251
+ @Arg('valueDate') valueDate: string,
252
+ @Ctx() context: ResolverContext
253
+ ): Promise<KpiStatistic[]> {
254
+ const { domain, user, tx } = context.state
255
+
256
+ // 지역별(scope02) 통계만 계산
257
+ const kpiStatistics = await getRepository(KpiStatistic, tx)
258
+ .createQueryBuilder('stat')
259
+ .leftJoinAndSelect('stat.kpiOrgScope', 'orgScope')
260
+ .leftJoinAndSelect('stat.kpi', 'kpi')
261
+ .where('stat.domain = :domainId', { domainId: domain.id })
262
+ .andWhere('stat.periodType = :periodType', { periodType })
263
+ .andWhere('stat.valueDate = :valueDate', { valueDate })
264
+ .andWhere('orgScope.scope02 IS NOT NULL') // 지역 정보가 있는 것만
265
+ .getMany()
266
+
267
+ // 계산 결과가 없으면 새로 계산
268
+ if (kpiStatistics.length === 0) {
269
+ return await KpiStatisticCalculationService.calculateAllStatistics(
270
+ periodType,
271
+ valueDate,
272
+ context
273
+ )
274
+ }
275
+
276
+ // 기존 통계 재계산
277
+ const results: KpiStatistic[] = []
278
+ for (const stat of kpiStatistics) {
279
+ const recalculated = await KpiStatisticCalculationService.calculateKpiStatistics(
280
+ stat.kpiId,
281
+ periodType,
282
+ valueDate,
283
+ stat.kpiOrgScope,
284
+ context
285
+ )
286
+ if (recalculated) results.push(recalculated)
287
+ }
288
+
289
+ return results
290
+ }
194
291
  }
@@ -1,9 +1,11 @@
1
1
  import { Resolver, Query, FieldResolver, Root, Args, Arg, Ctx, Directive } from 'type-graphql'
2
2
  import { Domain, getQueryBuilderFromListParams, getRepository, ListParam } from '@things-factory/shell'
3
3
  import { User } from '@things-factory/auth-base'
4
+ import type ResolverContext from '@things-factory/auth-base'
4
5
  import { KpiStatistic } from './kpi-statistic.js'
5
6
  import { KpiStatisticList } from './kpi-statistic-type.js'
6
- import { Kpi } from '../kpi/kpi.js'
7
+ import { Kpi, KpiPeriodType } from '../kpi/kpi.js'
8
+ import { KpiOrgScope } from '../kpi-org-scope/kpi-org-scope.js'
7
9
 
8
10
  @Resolver(KpiStatistic)
9
11
  export class KpiStatisticQuery {
@@ -29,7 +31,17 @@ export class KpiStatisticQuery {
29
31
  domain,
30
32
  params,
31
33
  repository: await getRepository(KpiStatistic),
32
- searchables: ['kpi', 'period']
34
+ searchables: ['kpi', 'valueDate'],
35
+ filtersMap: {
36
+ kpi: { columnName: 'id', relationColumn: 'kpi' },
37
+ kpiOrgScope: { columnName: 'id', relationColumn: 'kpiOrgScope' },
38
+ periodType: { columnName: 'periodType' },
39
+ valueDate: { columnName: 'valueDate' },
40
+ scope02: {
41
+ columnName: 'scope02',
42
+ relationColumn: 'kpiOrgScope'
43
+ }
44
+ }
33
45
  })
34
46
 
35
47
  const [items, total] = await queryBuilder.getManyAndCount()
@@ -56,4 +68,79 @@ export class KpiStatisticQuery {
56
68
  async creator(@Root() kpiStatistic: KpiStatistic): Promise<User> {
57
69
  return kpiStatistic.creatorId && (await getRepository(User).findOneBy({ id: kpiStatistic.creatorId }))
58
70
  }
71
+
72
+ @FieldResolver(type => KpiOrgScope)
73
+ async kpiOrgScope(@Root() kpiStatistic: KpiStatistic): Promise<KpiOrgScope> {
74
+ return kpiStatistic.kpiOrgScopeId && (await getRepository(KpiOrgScope).findOneBy({ id: kpiStatistic.kpiOrgScopeId }))
75
+ }
76
+
77
+ @Directive('@privilege(category: "kpi", privilege: "query", domainOwnerGranted: true, superUserGranted: true)')
78
+ @Query(returns => [KpiStatistic], {
79
+ description: 'Get regional statistics for dashboard map visualization'
80
+ })
81
+ async regionalKpiStatistics(
82
+ @Arg('valueDate') valueDate: string,
83
+ @Arg('periodType', type => KpiPeriodType, { defaultValue: KpiPeriodType.MONTH }) periodType: KpiPeriodType,
84
+ @Arg('regions', type => [String], { nullable: true }) regions: string[] | undefined,
85
+ @Ctx() context: ResolverContext
86
+ ): Promise<KpiStatistic[]> {
87
+ const { domain } = context.state
88
+
89
+ const queryBuilder = getRepository(KpiStatistic)
90
+ .createQueryBuilder('stat')
91
+ .leftJoinAndSelect('stat.kpi', 'kpi')
92
+ .leftJoinAndSelect('stat.kpiOrgScope', 'orgScope')
93
+ .where('stat.domain = :domainId', { domainId: domain.id })
94
+ .andWhere('stat.periodType = :periodType', { periodType })
95
+ .andWhere('stat.valueDate = :valueDate', { valueDate })
96
+ .andWhere('orgScope.scope02 IS NOT NULL') // 지역 정보가 있는 것만
97
+
98
+ if (regions && regions.length > 0) {
99
+ queryBuilder.andWhere('orgScope.scope02 IN (:...regions)', { regions })
100
+ }
101
+
102
+ queryBuilder.orderBy('orgScope.scope02', 'ASC')
103
+ .addOrderBy('kpi.name', 'ASC')
104
+
105
+ return await queryBuilder.getMany()
106
+ }
107
+
108
+ @Directive('@privilege(category: "kpi", privilege: "query", domainOwnerGranted: true, superUserGranted: true)')
109
+ @Query(returns => [KpiStatistic], {
110
+ description: 'Get aggregated statistics by scope02 (regional level) for dashboard'
111
+ })
112
+ async dashboardRegionalStatistics(
113
+ @Arg('valueDate') valueDate: string,
114
+ @Arg('periodType', type => KpiPeriodType, { defaultValue: KpiPeriodType.MONTH }) periodType: KpiPeriodType,
115
+ @Ctx() context: ResolverContext
116
+ ): Promise<KpiStatistic[]> {
117
+ const { domain } = context.state
118
+
119
+ // 지역별로 평균을 내서 하나의 통계로 합침
120
+ const query = `
121
+ SELECT
122
+ 'regional-aggregate-' || orgScope.scope02 as id,
123
+ kpi.id as kpiId,
124
+ kpi.name as kpiName,
125
+ orgScope.scope02 as region,
126
+ AVG(stat.mean) as avgMean,
127
+ AVG(stat.median) as avgMedian,
128
+ MIN(stat.minimum) as overallMin,
129
+ MAX(stat.maximum) as overallMax,
130
+ AVG(stat.standardDeviation) as avgStdDev,
131
+ SUM(stat.count) as totalCount,
132
+ COUNT(*) as orgCount
133
+ FROM kpi_statistic stat
134
+ LEFT JOIN kpi ON stat.kpiId = kpi.id
135
+ LEFT JOIN kpi_org_scope orgScope ON stat.kpiOrgScopeId = orgScope.id
136
+ WHERE stat.domainId = ?
137
+ AND stat.periodType = ?
138
+ AND stat.valueDate = ?
139
+ AND orgScope.scope02 IS NOT NULL
140
+ GROUP BY kpi.id, orgScope.scope02
141
+ ORDER BY orgScope.scope02, kpi.name
142
+ `
143
+
144
+ return await getRepository(KpiStatistic).query(query, [domain.id, periodType, valueDate])
145
+ }
59
146
  }
@@ -13,6 +13,7 @@ import { ObjectType, Field, Int, ID } from 'type-graphql'
13
13
  import { Domain, ScalarObject } from '@things-factory/shell'
14
14
  import { User } from '@things-factory/auth-base'
15
15
  import { Kpi, KpiPeriodType } from '../kpi/kpi'
16
+ import { KpiOrgScope } from '../kpi-org-scope/kpi-org-scope'
16
17
 
17
18
  @Entity()
18
19
  @Index('ix_kpi_statistic_target', (kpiStatistic: KpiStatistic) => [kpiStatistic.domain, kpiStatistic.kpi])
@@ -21,6 +22,18 @@ import { Kpi, KpiPeriodType } from '../kpi/kpi'
21
22
  kpiStatistic.valueDate,
22
23
  kpiStatistic.periodType
23
24
  ])
25
+ @Index('ix_kpi_statistic_scope', (kpiStatistic: KpiStatistic) => [
26
+ kpiStatistic.domain,
27
+ kpiStatistic.kpi,
28
+ kpiStatistic.kpiOrgScope,
29
+ kpiStatistic.valueDate
30
+ ])
31
+ @Index('ix_kpi_statistic_legacy_scope', (kpiStatistic: KpiStatistic) => [
32
+ kpiStatistic.domain,
33
+ kpiStatistic.kpi,
34
+ kpiStatistic.scope,
35
+ kpiStatistic.valueDate
36
+ ])
24
37
  @ObjectType({
25
38
  description:
26
39
  'KPI Statistics Entity - Stores comprehensive statistical information for KPIs and Categories including central tendency measures (mean, median), dispersion metrics (standard deviation, variance), range indicators (min, max), and percentile distributions (25th, 75th percentiles, IQR). Supports both KPI and Category targets with flexible period-based aggregation (daily, weekly, monthly, yearly). Includes extensible JSON fields for additional metrics and metadata for calculation tracking.'
@@ -54,6 +67,25 @@ export class KpiStatistic {
54
67
  @Field(type => KpiPeriodType, { description: 'Aggregation period type for this statistic.' })
55
68
  periodType: KpiPeriodType
56
69
 
70
+ // === 스코프 정보 ===
71
+
72
+ @ManyToOne(() => KpiOrgScope, { nullable: true })
73
+ @Field(type => KpiOrgScope, {
74
+ nullable: true,
75
+ description: 'Organization scope for scoped statistics. Null for overall statistics.'
76
+ })
77
+ kpiOrgScope?: KpiOrgScope
78
+
79
+ @RelationId((kpiStatistic: KpiStatistic) => kpiStatistic.kpiOrgScope)
80
+ kpiOrgScopeId?: string
81
+
82
+ @Column({ nullable: true })
83
+ @Field({
84
+ nullable: true,
85
+ description: 'Legacy scope field - use kpiOrgScope instead. Statistical scope - null for overall statistics, category value for scoped statistics (e.g., "서울", "부장", "대규모")'
86
+ })
87
+ scope?: string
88
+
57
89
  // === 핵심 통계 필드 (14개) ===
58
90
 
59
91
  // 1. 기본 정보 (3개)
@@ -6,6 +6,7 @@ import { KpiValue } from './kpi-value'
6
6
  import { NewKpiValue, KpiValuePatch } from './kpi-value-type'
7
7
  import { Kpi } from '../kpi/kpi'
8
8
  import { KpiMetric } from '../kpi-metric/kpi-metric'
9
+ import { KpiOrgScope } from '../kpi-org-scope/kpi-org-scope'
9
10
  import { KpiMetricValue } from '../kpi-metric-value/kpi-metric-value'
10
11
  import { KpiPeriodType } from '../kpi/kpi'
11
12
  import { getDefaultValueDate } from '../utils/value-date-util'
@@ -38,18 +39,50 @@ export class KpiValueMutation {
38
39
  }
39
40
  }
40
41
 
42
+ // KpiOrgScope 처리
43
+ let kpiOrgScope: KpiOrgScope | undefined
44
+ if (kpiValue.kpiOrgScopeId) {
45
+ kpiOrgScope = await getRepository(KpiOrgScope, context.state?.tx).findOne({
46
+ where: { id: kpiValue.kpiOrgScopeId, domain: { id: domain.id } }
47
+ })
48
+ if (!kpiOrgScope) {
49
+ throw new Error(`KpiOrgScope not found: ${kpiValue.kpiOrgScopeId}`)
50
+ }
51
+ } else if (kpiValue.org) {
52
+ // Legacy org 처리 - org 문자열로 KpiOrgScope 찾기 또는 생성
53
+ kpiOrgScope = await getRepository(KpiOrgScope, context.state?.tx).findOne({
54
+ where: { org: kpiValue.org, domain: { id: domain.id } }
55
+ })
56
+ if (!kpiOrgScope) {
57
+ // 새 KpiOrgScope 생성
58
+ kpiOrgScope = await getRepository(KpiOrgScope, context.state?.tx).save({
59
+ entityType: 'Manual',
60
+ entityId: `manual-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
61
+ entityName: kpiValue.org,
62
+ org: kpiValue.org,
63
+ domain,
64
+ creator: user,
65
+ updater: user
66
+ })
67
+ }
68
+ }
69
+
41
70
  const entity: Partial<KpiValue> = {
42
71
  kpi,
43
72
  kpiId: kpiValue.kpiId,
44
73
  version: kpiValue.version,
45
74
  valueDate: kpiValue.valueDate,
46
75
  value: kpiValue.value,
47
- group: kpiValue.group,
48
76
  source: kpiValue.source,
49
77
  domain: domain,
50
78
  creator: user,
51
79
  updater: user
52
80
  }
81
+
82
+ if (kpiOrgScope) {
83
+ entity.kpiOrgScope = kpiOrgScope
84
+ entity.kpiOrgScopeId = kpiOrgScope.id
85
+ }
53
86
  if (inputType) entity.inputType = inputType
54
87
 
55
88
  return await getRepository(KpiValue, tx).save(entity)
@@ -83,18 +116,50 @@ export class KpiValueMutation {
83
116
  }
84
117
  }
85
118
 
119
+ // KpiOrgScope 처리
120
+ let kpiOrgScope: KpiOrgScope | undefined
121
+ if (kpiValue.kpiOrgScopeId) {
122
+ kpiOrgScope = await getRepository(KpiOrgScope, context.state?.tx).findOne({
123
+ where: { id: kpiValue.kpiOrgScopeId, domain: { id: domain.id } }
124
+ })
125
+ if (!kpiOrgScope) {
126
+ throw new Error(`KpiOrgScope not found: ${kpiValue.kpiOrgScopeId}`)
127
+ }
128
+ } else if (kpiValue.org) {
129
+ // Legacy org 처리
130
+ kpiOrgScope = await getRepository(KpiOrgScope, context.state?.tx).findOne({
131
+ where: { org: kpiValue.org, domain: { id: domain.id } }
132
+ })
133
+ if (!kpiOrgScope) {
134
+ // 새 KpiOrgScope 생성
135
+ kpiOrgScope = await getRepository(KpiOrgScope, context.state?.tx).save({
136
+ entityType: 'Manual',
137
+ entityId: `manual-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
138
+ entityName: kpiValue.org,
139
+ org: kpiValue.org,
140
+ domain,
141
+ creator: user,
142
+ updater: user
143
+ })
144
+ }
145
+ }
146
+
86
147
  const entity: Partial<KpiValue> = {
87
148
  kpi,
88
149
  kpiId: kpiValue.kpiId,
89
150
  version: kpiValue.version || 1,
90
151
  valueDate: kpiValue.valueDate,
91
152
  value: kpiValue.value,
92
- group: kpiValue.group || '',
93
153
  source: kpiValue.source || 'MANUAL',
94
154
  domain: domain,
95
155
  creator: user,
96
156
  updater: user
97
157
  }
158
+
159
+ if (kpiOrgScope) {
160
+ entity.kpiOrgScope = kpiOrgScope
161
+ entity.kpiOrgScopeId = kpiOrgScope.id
162
+ }
98
163
  if (inputType) entity.inputType = inputType
99
164
 
100
165
  const savedEntity = await repository.save(entity)
@@ -276,7 +341,7 @@ export class KpiValueMutation {
276
341
  // 1. 기존 KPI Value 인스턴스 조회
277
342
  const kpiValue = await kpiValueRepo.findOne({
278
343
  where: { id, domain: { id: domain.id } },
279
- relations: ['kpi']
344
+ relations: ['kpi', 'kpiOrgScope']
280
345
  })
281
346
  if (!kpiValue) throw new Error('KPI Value not found')
282
347
  const kpi = kpiValue.kpi || (await kpiRepo.findOne({ where: { id: kpiValue.kpiId, domain: { id: domain.id } } }))
@@ -284,7 +349,8 @@ export class KpiValueMutation {
284
349
  if (!kpi.formula) throw new Error('KPI formula 없음')
285
350
  const periodType = kpi.periodType || KpiPeriodType.DAY
286
351
  const valueDate = kpiValue.valueDate
287
- const group = kpiValue.group
352
+ const kpiOrgScope = kpiValue.kpiOrgScope
353
+ const org = kpiOrgScope?.entityName || kpiOrgScope?.org || 'unknown' // KpiOrgScope에서 org 정보 추출
288
354
  const version = kpiValue.version
289
355
 
290
356
  // 2. formula에서 metric code 추출
@@ -309,7 +375,7 @@ export class KpiValueMutation {
309
375
  metric: { id: metric.id },
310
376
  valueDate,
311
377
  periodType,
312
- group: group ?? '',
378
+ org: org ?? '',
313
379
  domain: { id: domain.id }
314
380
  }
315
381
  })
@@ -324,7 +390,7 @@ export class KpiValueMutation {
324
390
  metric: { id: metric.id },
325
391
  valueDate: Between(startDate, endDate),
326
392
  periodType: metric.periodType,
327
- group: group ?? '',
393
+ org: org ?? '',
328
394
  domain: { id: domain.id }
329
395
  }
330
396
  })
@@ -341,7 +407,7 @@ export class KpiValueMutation {
341
407
  const ast = parseFormula(kpi.formula)
342
408
  const provider = new KpiMetricValueProvider({
343
409
  valueDate,
344
- group,
410
+ org,
345
411
  domainId: domain.id,
346
412
  tx
347
413
  })
@@ -33,8 +33,11 @@ export class NewKpiValue {
33
33
  })
34
34
  meta?: any
35
35
 
36
- @Field({ nullable: true, description: 'Group key for this value (organization, line, user, etc.)' })
37
- group?: string
36
+ @Field(type => ID, { nullable: true, description: 'ID of the KPI organization scope for this value.' })
37
+ kpiOrgScopeId?: string
38
+
39
+ @Field({ nullable: true, description: 'Legacy organizational unit identifier (for backward compatibility)' })
40
+ org?: string
38
41
 
39
42
  @Field(type => KpiValueInputType, {
40
43
  nullable: true,
@@ -84,8 +87,11 @@ export class KpiValuePatch {
84
87
  })
85
88
  meta?: any
86
89
 
87
- @Field({ nullable: true, description: 'Group key for this value (organization, line, user, etc.)' })
88
- group?: string
90
+ @Field(type => ID, { nullable: true, description: 'ID of the KPI organization scope for this value.' })
91
+ kpiOrgScopeId?: string
92
+
93
+ @Field({ nullable: true, description: 'Legacy organizational unit identifier (for backward compatibility)' })
94
+ org?: string
89
95
 
90
96
  @Field(type => KpiValueInputType, {
91
97
  nullable: true,
@@ -14,6 +14,7 @@ import { User } from '@things-factory/auth-base'
14
14
  import { Domain, ScalarObject, json5Transformer } from '@things-factory/shell'
15
15
  import { config } from '@things-factory/env'
16
16
  import { Kpi, KpiPeriodType } from '../kpi/kpi'
17
+ import { KpiOrgScope } from '../kpi-org-scope/kpi-org-scope'
17
18
 
18
19
  const ORMCONFIG = config.get('ormconfig', {})
19
20
  const DATABASE_TYPE = ORMCONFIG.type
@@ -29,8 +30,8 @@ registerEnumType(KpiValueInputType, {
29
30
  })
30
31
 
31
32
  @Entity()
32
- @Index('ix_kpi_value_latest', ['domain', 'kpi', 'valueDate', 'group', 'version'], { unique: true })
33
- @Index('ix_kpi_value_latest_for_query', ['domain', 'kpi', 'valueDate', 'group'], { unique: false })
33
+ @Index('ix_kpi_value_latest', ['domain', 'kpi', 'valueDate', 'kpiOrgScope', 'version'], { unique: true })
34
+ @Index('ix_kpi_value_latest_for_query', ['domain', 'kpi', 'valueDate', 'kpiOrgScope'], { unique: false })
34
35
  @ObjectType({ description: 'Entity for KpiValue' })
35
36
  export class KpiValue {
36
37
  @PrimaryGeneratedColumn('uuid')
@@ -78,9 +79,13 @@ export class KpiValue {
78
79
  })
79
80
  score?: number
80
81
 
81
- @Column({ default: '' })
82
- @Field({ nullable: true, description: 'Group key for this value (organization, line, user, etc.)' })
83
- group?: string
82
+ @ManyToOne(() => KpiOrgScope, { nullable: true })
83
+ @Field(type => KpiOrgScope, { nullable: true, description: 'Reference to the organization scope mapping for this KPI value.' })
84
+ kpiOrgScope?: KpiOrgScope
85
+
86
+ @RelationId((kpiValue: KpiValue) => kpiValue.kpiOrgScope)
87
+ @Field({ nullable: true, description: 'ID of the referenced KPI organization scope.' })
88
+ kpiOrgScopeId?: string
84
89
 
85
90
  @Column({ nullable: true })
86
91
  @Field({
@@ -9,6 +9,53 @@ export function getISOWeek(date: Date): number {
9
9
  return weekNo
10
10
  }
11
11
 
12
+ function getMonday(year: number, week: number): Date {
13
+ const simple = new Date(year, 0, 1 + (week - 1) * 7)
14
+ const dow = simple.getDay()
15
+ const ISOweekStart = simple
16
+ if (dow <= 4) ISOweekStart.setDate(simple.getDate() - simple.getDay() + 1)
17
+ else ISOweekStart.setDate(simple.getDate() + 8 - simple.getDay())
18
+ return ISOweekStart
19
+ }
20
+
21
+ export function normalizePeriodDate(valueDate: string, periodType: KpiPeriodType): Date {
22
+ switch (periodType) {
23
+ case KpiPeriodType.DAY:
24
+ return new Date(valueDate + 'T00:00:00.000Z')
25
+
26
+ case KpiPeriodType.WEEK: {
27
+ // "2024-W03" → 해당 주의 월요일
28
+ const [year, week] = valueDate.split('-W')
29
+ return getMonday(parseInt(year), parseInt(week))
30
+ }
31
+
32
+ case KpiPeriodType.MONTH:
33
+ return new Date(valueDate + '-01T00:00:00.000Z')
34
+
35
+ case KpiPeriodType.QUARTER: {
36
+ // "2024-Q1" → 해당 분기 시작일
37
+ const [year, quarter] = valueDate.split('-Q')
38
+ const month = (parseInt(quarter) - 1) * 3 + 1
39
+ return new Date(`${year}-${month.toString().padStart(2, '0')}-01T00:00:00.000Z`)
40
+ }
41
+
42
+ case KpiPeriodType.YEAR:
43
+ return new Date(valueDate + '-01-01T00:00:00.000Z')
44
+
45
+ case KpiPeriodType.RANGE: {
46
+ // "2024-01-01~2024-01-31" → 시작일 사용
47
+ const startDate = valueDate.split('~')[0]
48
+ return new Date(startDate + 'T00:00:00.000Z')
49
+ }
50
+
51
+ case KpiPeriodType.ALLTIME:
52
+ return new Date('1900-01-01T00:00:00.000Z') // 고정 기준점
53
+
54
+ default:
55
+ return new Date(valueDate + 'T00:00:00.000Z')
56
+ }
57
+ }
58
+
12
59
  export function getDefaultValueDate(periodType: KpiPeriodType, mode: 'current' | 'last' = 'current'): string {
13
60
  const now = new Date()
14
61
  if (mode === 'last') {
@@ -0,0 +1,8 @@
1
+ /// <reference types="@things-factory/auth-base/server/types" />
2
+
3
+ // auth-base의 global 타입들을 여기서도 사용할 수 있도록 재선언
4
+ declare global {
5
+ // ResolverContext는 이미 auth-base에서 global로 선언되어 있음
6
+ }
7
+
8
+ export {}
@@ -7,6 +7,7 @@ export default {
7
7
  { tagname: 'kpi-overview', page: 'kpi-overview' },
8
8
  { tagname: 'kpi-dashboard', page: 'kpi-dashboard' },
9
9
  { tagname: 'kpi-list-page', page: 'kpi-list' },
10
+ { tagname: 'kpi-tree-page', page: 'kpi-tree' },
10
11
  { tagname: 'kpi-category-list-page', page: 'kpi-category-list' },
11
12
  { tagname: 'kpi-category-value-list-page', page: 'kpi-category-value-list' },
12
13
  { tagname: 'kpi-metric-list-page', page: 'kpi-metric-list' },
@@ -4,8 +4,7 @@
4
4
  "label.operators": "operators",
5
5
  "label.functions": "functions",
6
6
  "title.kpi list": "KPI List",
7
- "title.kpi category list": "KPI Catogory List",
8
- "title.kpi category value list": "KPI Category Value List",
7
+ "title.kpi tree": "KPI Tree",
9
8
  "title.kpi dashboard": "KPI Dashboard",
10
9
  "title.kpi value editor": "KPI Value Editor",
11
10
  "title.kpi grade list": "KPI Grade List",
@@ -41,5 +40,18 @@
41
40
  "field.percentile75": "75th Percentile",
42
41
  "field.iqr": "IQR",
43
42
  "field.lower_fence": "Lower Fence",
44
- "field.upper_fence": "Upper Fence"
43
+ "field.upper_fence": "Upper Fence",
44
+ "label.basic_information": "Basic Information",
45
+ "label.is_leaf_node": "Is Leaf Node",
46
+ "label.formula_calculation": "Formula & Calculation",
47
+ "label.kpi_formula": "KPI Formula",
48
+ "label.score_formula": "Score Formula",
49
+ "placeholder.enter_formula": "Enter formula using metric codes (e.g., defect_count / total_count * 100)",
50
+ "placeholder.enter_score_formula": "Enter score calculation formula (e.g., if(value >= 90, 100, value * 0.8))",
51
+ "placeholder.cron_schedule": "0 0 * * * (daily at midnight)",
52
+ "placeholder.timezone": "Asia/Seoul",
53
+ "message.kpi_not_selected": "KPI not selected",
54
+ "message.select_kpi_first": "Please select KPI first.",
55
+ "message.kpis_with_children_cannot_be_deleted": "KPIs with children cannot be deleted.",
56
+ "message.kpi_having_children_cannot_be_deleted": "KPI having children cannot be deleted."
45
57
  }
@@ -4,8 +4,7 @@
4
4
  "label.operators": "演算子",
5
5
  "label.functions": "関数",
6
6
  "title.kpi list": "KPIリスト",
7
- "title.kpi category list": "KPIカテゴリリスト",
8
- "title.kpi category value list": "KPIカテゴリ値リスト",
7
+ "title.kpi tree": "KPIツリー",
9
8
  "title.kpi dashboard": "KPIダッシュボード",
10
9
  "title.kpi value editor": "KPI値エディタ",
11
10
  "title.kpi grade list": "KPIグレードリスト",
@@ -41,5 +40,16 @@
41
40
  "field.percentile75": "75パーセンタイル",
42
41
  "field.iqr": "四分位範囲",
43
42
  "field.lower_fence": "下限フェンス",
44
- "field.upper_fence": "上限フェンス"
43
+ "field.upper_fence": "上限フェンス",
44
+ "label.basic_information": "基本情報",
45
+ "label.is_leaf_node": "リーフノード",
46
+ "label.formula_calculation": "数式と計算",
47
+ "label.kpi_formula": "KPI数式",
48
+ "label.score_formula": "スコア数式",
49
+ "placeholder.enter_formula": "メトリックコードを使用して数式を入力してください(例:defect_count / total_count * 100)",
50
+ "placeholder.enter_score_formula": "スコア計算数式を入力してください(例:if(value >= 90, 100, value * 0.8))",
51
+ "placeholder.cron_schedule": "0 0 * * *(毎日午前0時)",
52
+ "placeholder.timezone": "Asia/Tokyo",
53
+ "message.kpis_with_children_cannot_be_deleted": "子KPIを持つKPIは削除できません。",
54
+ "message.kpi_having_children_cannot_be_deleted": "子KPIを持つKPIは削除できません。"
45
55
  }
@@ -4,8 +4,7 @@
4
4
  "label.operators": "연산자",
5
5
  "label.functions": "함수",
6
6
  "title.kpi list": "KPI 목록",
7
- "title.kpi category list": "KPI 카테고리 목록",
8
- "title.kpi category value list": "KPI 카테고리 값 목록",
7
+ "title.kpi tree": "KPI 트리",
9
8
  "title.kpi dashboard": "KPI 대시보드",
10
9
  "title.kpi value editor": "KPI 값 편집기",
11
10
  "title.kpi grade list": "KPI 등급 목록",
@@ -41,5 +40,18 @@
41
40
  "field.percentile75": "75분위수",
42
41
  "field.iqr": "사분위수 범위",
43
42
  "field.lower_fence": "하위 울타리",
44
- "field.upper_fence": "상위 울타리"
43
+ "field.upper_fence": "상위 울타리",
44
+ "label.basic_information": "기본 정보",
45
+ "label.is_leaf_node": "리프 노드",
46
+ "label.formula_calculation": "수식 및 계산",
47
+ "label.kpi_formula": "KPI 수식",
48
+ "label.score_formula": "점수 수식",
49
+ "placeholder.enter_formula": "지표 코드를 사용하여 수식을 입력하세요 (예: defect_count / total_count * 100)",
50
+ "placeholder.enter_score_formula": "점수 계산 수식을 입력하세요 (예: if(value >= 90, 100, value * 0.8))",
51
+ "placeholder.cron_schedule": "0 0 * * * (매일 자정)",
52
+ "placeholder.timezone": "Asia/Seoul",
53
+ "message.kpi_not_selected": "KPI가 선택되지 않음",
54
+ "message.select_kpi_first": "먼저 KPI를 선택하세요.",
55
+ "message.kpis_with_children_cannot_be_deleted": "하위 KPI가 있는 KPI는 삭제할 수 없습니다.",
56
+ "message.kpi_having_children_cannot_be_deleted": "하위 KPI가 있는 KPI는 삭제할 수 없습니다."
45
57
  }