@things-factory/kpi 9.2.5 → 10.0.0-beta.10

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 (139) hide show
  1. package/client/pages/kpi/kpi-list-page.ts +339 -525
  2. package/client/pages/kpi/kpi-tree-page.ts +135 -207
  3. package/client/pages/kpi-metric/kpi-metric-list-page.ts +146 -226
  4. package/client/pages/kpi-metric-value/kpi-metric-value-editor-page.ts +187 -295
  5. package/client/pages/kpi-metric-value/kpi-metric-value-list-page.ts +123 -194
  6. package/client/pages/kpi-metric-value/kpi-metric-value-manual-entry-page.ts +57 -91
  7. package/client/pages/kpi-statistic/kpi-statistic-editor-page.ts +180 -278
  8. package/client/pages/kpi-statistic/kpi-statistic-list-page.ts +186 -286
  9. package/client/pages/kpi-value/kpi-value-editor-page.ts +189 -292
  10. package/client/pages/kpi-value/kpi-value-list-page.ts +170 -264
  11. package/dist-client/pages/kpi/kpi-list-page.d.ts +0 -6
  12. package/dist-client/pages/kpi/kpi-list-page.js +150 -282
  13. package/dist-client/pages/kpi/kpi-list-page.js.map +1 -1
  14. package/dist-client/pages/kpi/kpi-tree-page.d.ts +1 -7
  15. package/dist-client/pages/kpi/kpi-tree-page.js +76 -127
  16. package/dist-client/pages/kpi/kpi-tree-page.js.map +1 -1
  17. package/dist-client/pages/kpi-metric/kpi-metric-list-page.d.ts +0 -6
  18. package/dist-client/pages/kpi-metric/kpi-metric-list-page.js +62 -116
  19. package/dist-client/pages/kpi-metric/kpi-metric-list-page.js.map +1 -1
  20. package/dist-client/pages/kpi-metric-value/kpi-metric-value-editor-page.d.ts +1 -7
  21. package/dist-client/pages/kpi-metric-value/kpi-metric-value-editor-page.js +82 -140
  22. package/dist-client/pages/kpi-metric-value/kpi-metric-value-editor-page.js.map +1 -1
  23. package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.d.ts +0 -6
  24. package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.js +54 -98
  25. package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.js.map +1 -1
  26. package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-page.d.ts +1 -7
  27. package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-page.js +30 -57
  28. package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-page.js.map +1 -1
  29. package/dist-client/pages/kpi-statistic/kpi-statistic-editor-page.d.ts +1 -7
  30. package/dist-client/pages/kpi-statistic/kpi-statistic-editor-page.js +91 -153
  31. package/dist-client/pages/kpi-statistic/kpi-statistic-editor-page.js.map +1 -1
  32. package/dist-client/pages/kpi-statistic/kpi-statistic-list-page.d.ts +0 -6
  33. package/dist-client/pages/kpi-statistic/kpi-statistic-list-page.js +81 -155
  34. package/dist-client/pages/kpi-statistic/kpi-statistic-list-page.js.map +1 -1
  35. package/dist-client/pages/kpi-value/kpi-value-editor-page.d.ts +1 -7
  36. package/dist-client/pages/kpi-value/kpi-value-editor-page.js +80 -136
  37. package/dist-client/pages/kpi-value/kpi-value-editor-page.js.map +1 -1
  38. package/dist-client/pages/kpi-value/kpi-value-list-page.d.ts +0 -6
  39. package/dist-client/pages/kpi-value/kpi-value-list-page.js +73 -134
  40. package/dist-client/pages/kpi-value/kpi-value-list-page.js.map +1 -1
  41. package/dist-client/tsconfig.tsbuildinfo +1 -1
  42. package/dist-server/tsconfig.tsbuildinfo +1 -1
  43. package/package.json +18 -18
  44. package/client/tsconfig.json +0 -11
  45. package/dist-server/tsconfig.json +0 -10
  46. package/server/@types/index.d.ts +0 -11
  47. package/server/calculator/evaluator.ts +0 -45
  48. package/server/calculator/functions.ts +0 -67
  49. package/server/calculator/index.ts +0 -4
  50. package/server/calculator/parser.ts +0 -137
  51. package/server/calculator/provider.ts +0 -10
  52. package/server/controllers/index.ts +0 -2
  53. package/server/controllers/kpi-metric-value-provider.ts +0 -79
  54. package/server/controllers/kpi-value-provider.ts +0 -51
  55. package/server/index.ts +0 -6
  56. package/server/migrations/1752190849680-seed-kpi-metrics.ts +0 -124
  57. package/server/migrations/1752190849681-seed-kpi.ts +0 -356
  58. package/server/migrations/1752192090123-add-grades-to-kpi.ts +0 -67
  59. package/server/migrations/1752192090124-add-kpi-statistics.ts +0 -719
  60. package/server/migrations/1752192090128-seed-kpi-org-scope.ts +0 -132
  61. package/server/migrations/1752192090129-seed-kpi-values.ts +0 -207
  62. package/server/migrations/grade-data/x11-performance-table.json +0 -962
  63. package/server/migrations/grade-data/x12-performance-table.json +0 -611
  64. package/server/migrations/grade-data/x14-performance-table.json +0 -42
  65. package/server/migrations/grade-data/x21-performance-table.json +0 -889
  66. package/server/migrations/grade-data/x22-performance-table.json +0 -1064
  67. package/server/migrations/grade-data/x23-performance-table.json +0 -42
  68. package/server/migrations/grade-data/x31-performance-table.json +0 -644
  69. package/server/migrations/grade-data/x32-performance-table.json +0 -993
  70. package/server/migrations/grade-data/x33-performance-table.json +0 -195
  71. package/server/migrations/grade-data/x34-performance-table.json +0 -12
  72. package/server/migrations/grade-data/x35-performance-table.json +0 -42
  73. package/server/migrations/grade-data/x41-performance-table.json +0 -825
  74. package/server/migrations/grade-data/x42-performance-table.json +0 -786
  75. package/server/migrations/grade-data/x43-performance-table.json +0 -12
  76. package/server/migrations/grade-data/x44-performance-table.json +0 -42
  77. package/server/migrations/grade-data/x51-performance-table.json +0 -924
  78. package/server/migrations/grade-data/x52-performance-table.json +0 -42
  79. package/server/migrations/grade-data/x61-performance-table.json +0 -261
  80. package/server/migrations/grade-data/x62-performance-table.json +0 -42
  81. package/server/migrations/index.ts +0 -9
  82. package/server/migrations/seed-data/kpi-metrics-seed.json +0 -454
  83. package/server/migrations/seed-data/kpi-org-scope-seed.json +0 -1676
  84. package/server/migrations/seed-data/kpi-scopes-seed.json +0 -121
  85. package/server/migrations/seed-data/kpi-values-seed.json +0 -402
  86. package/server/migrations/seed-data/kpis-seed.json +0 -488
  87. package/server/migrations/seed-data/scope-definitions-seed.json +0 -90
  88. package/server/routes.ts +0 -81
  89. package/server/service/index.ts +0 -51
  90. package/server/service/kpi/aggregate-kpi.ts +0 -103
  91. package/server/service/kpi/event-subscriber.ts +0 -29
  92. package/server/service/kpi/index.ts +0 -9
  93. package/server/service/kpi/kpi-formula.service.ts +0 -164
  94. package/server/service/kpi/kpi-grade.types.ts +0 -28
  95. package/server/service/kpi/kpi-history.ts +0 -126
  96. package/server/service/kpi/kpi-mutation.ts +0 -553
  97. package/server/service/kpi/kpi-query.ts +0 -224
  98. package/server/service/kpi/kpi-type.ts +0 -151
  99. package/server/service/kpi/kpi.ts +0 -254
  100. package/server/service/kpi-alert/index.ts +0 -3
  101. package/server/service/kpi-alert/kpi-alert-query.ts +0 -59
  102. package/server/service/kpi-alert/kpi-alert-type.ts +0 -20
  103. package/server/service/kpi-metric/aggregate-kpi-metric.ts +0 -132
  104. package/server/service/kpi-metric/index.ts +0 -7
  105. package/server/service/kpi-metric/kpi-metric-mutation.ts +0 -309
  106. package/server/service/kpi-metric/kpi-metric-query.ts +0 -70
  107. package/server/service/kpi-metric/kpi-metric-type.ts +0 -111
  108. package/server/service/kpi-metric/kpi-metric.ts +0 -134
  109. package/server/service/kpi-metric-value/index.ts +0 -7
  110. package/server/service/kpi-metric-value/kpi-metric-value-mutation.ts +0 -270
  111. package/server/service/kpi-metric-value/kpi-metric-value-query.ts +0 -62
  112. package/server/service/kpi-metric-value/kpi-metric-value-type.ts +0 -82
  113. package/server/service/kpi-metric-value/kpi-metric-value.ts +0 -93
  114. package/server/service/kpi-org-scope/index.ts +0 -6
  115. package/server/service/kpi-org-scope/kpi-org-scope-mutation.ts +0 -173
  116. package/server/service/kpi-org-scope/kpi-org-scope-query.ts +0 -127
  117. package/server/service/kpi-org-scope/kpi-org-scope-type.ts +0 -68
  118. package/server/service/kpi-org-scope/kpi-org-scope.ts +0 -123
  119. package/server/service/kpi-scope/index.ts +0 -11
  120. package/server/service/kpi-scope/kpi-scope-mutation.ts +0 -129
  121. package/server/service/kpi-scope/kpi-scope-query.ts +0 -63
  122. package/server/service/kpi-scope/kpi-scope-type.ts +0 -96
  123. package/server/service/kpi-scope/kpi-scope.ts +0 -143
  124. package/server/service/kpi-statistic/index.ts +0 -7
  125. package/server/service/kpi-statistic/kpi-statistic-batch.service.ts +0 -231
  126. package/server/service/kpi-statistic/kpi-statistic-calculation.service.ts +0 -410
  127. package/server/service/kpi-statistic/kpi-statistic-mutation.ts +0 -291
  128. package/server/service/kpi-statistic/kpi-statistic-query.ts +0 -146
  129. package/server/service/kpi-statistic/kpi-statistic-type.ts +0 -152
  130. package/server/service/kpi-statistic/kpi-statistic.ts +0 -199
  131. package/server/service/kpi-value/index.ts +0 -7
  132. package/server/service/kpi-value/kpi-value-mutation.ts +0 -432
  133. package/server/service/kpi-value/kpi-value-query.ts +0 -61
  134. package/server/service/kpi-value/kpi-value-score.service.ts +0 -106
  135. package/server/service/kpi-value/kpi-value-type.ts +0 -122
  136. package/server/service/kpi-value/kpi-value.ts +0 -160
  137. package/server/service/utils/value-date-util.ts +0 -119
  138. package/server/tsconfig.json +0 -10
  139. package/server/types/global.d.ts +0 -8
@@ -1,51 +0,0 @@
1
- /* EXPORT ENTITY TYPES */
2
- export * from './kpi-statistic/kpi-statistic'
3
- export * from './kpi/kpi'
4
- export * from './kpi/kpi-type'
5
- export * from './kpi-value/kpi-value'
6
- export * from './kpi-value/kpi-value-type'
7
- export * from './kpi-metric/kpi-metric'
8
- export * from './kpi-metric/kpi-metric-type'
9
- export * from './kpi-metric-value/kpi-metric-value'
10
- export * from './kpi-metric-value/kpi-metric-value-type'
11
- export * from './kpi-scope/kpi-scope'
12
- export * from './kpi-org-scope/kpi-org-scope'
13
-
14
- /* IMPORT ENTITIES AND RESOLVERS */
15
- import {
16
- entities as KpiStatisticEntities,
17
- resolvers as KpiStatisticResolvers,
18
- subscribers as KpiStatisticSubscribers
19
- } from './kpi-statistic'
20
- import { entities as KpiEntities, resolvers as KpiResolvers } from './kpi'
21
- import { entities as KpiValueEntities, resolvers as KpiValueResolvers } from './kpi-value'
22
- import { entities as KpiMetricEntities, resolvers as KpiMetricResolvers } from './kpi-metric'
23
- import { entities as KpiMetricValueEntities, resolvers as KpiMetricValueResolvers } from './kpi-metric-value'
24
- import { entities as KpiOrgScopeEntities, resolvers as KpiOrgScopeResolvers } from './kpi-org-scope'
25
- import { resolvers as KpiAlertResolvers } from './kpi-alert'
26
- import { entities as KpiScopeEntities, resolvers as KpiScopeResolvers } from './kpi-scope'
27
-
28
- export const entities = [
29
- /* ENTITIES */
30
- ...KpiStatisticEntities,
31
- ...KpiEntities,
32
- ...KpiValueEntities,
33
- ...KpiMetricEntities,
34
- ...KpiMetricValueEntities,
35
- ...KpiOrgScopeEntities,
36
- ...KpiScopeEntities
37
- ]
38
-
39
- export const schema = {
40
- resolverClasses: [
41
- /* RESOLVER CLASSES */
42
- ...KpiStatisticResolvers,
43
- ...KpiResolvers,
44
- ...KpiValueResolvers,
45
- ...KpiMetricResolvers,
46
- ...KpiMetricValueResolvers,
47
- ...KpiOrgScopeResolvers,
48
- ...KpiAlertResolvers,
49
- ...KpiScopeResolvers
50
- ]
51
- }
@@ -1,103 +0,0 @@
1
- import { getRepository } from '@things-factory/shell'
2
- import { Kpi } from './kpi'
3
- import { KpiMetric } from '../kpi-metric/kpi-metric'
4
- import { KpiValue, KpiValueInputType } from '../kpi-value/kpi-value'
5
- import { KpiOrgScope } from '../kpi-org-scope/kpi-org-scope'
6
- import { KpiFormulaService } from './kpi-formula.service'
7
- import { aggregateKpiMetricValue } from '../kpi-metric/aggregate-kpi-metric'
8
- import { KpiValueScoreService } from '../kpi-value/kpi-value-score.service'
9
-
10
- /**
11
- * KPI 단위 집계/산식 자동화 함수
12
- * @param kpiId KPI ID
13
- * @param domainId 도메인 ID
14
- * @param context ResolverContext
15
- * @returns 저장된 KPI Value 배열
16
- */
17
- export async function aggregateKpiValue(kpiId: string, domainId: string, context: ResolverContext) {
18
- const tx = context.state?.tx || getRepository(Kpi).manager
19
- const scoreService = new KpiValueScoreService()
20
-
21
- // 1. KPI 정보 조회
22
- const kpi = await getRepository(Kpi).findOne({ where: { id: kpiId, domain: { id: domainId } } })
23
- if (!kpi) throw new Error('KPI 정보 없음')
24
- if (!kpi.active) throw new Error('비활성화된 KPI')
25
- if (!kpi.formula) throw new Error('KPI formula 없음')
26
-
27
- // 2. formula 파싱 및 metric code별 값 집계
28
- const formulaService = new KpiFormulaService()
29
- const metricCodes = formulaService.extractMetricCodes(kpi.formula)
30
- const codeValueMap: Record<string, any[]> = {}
31
- for (const code of metricCodes) {
32
- // code로 metric 찾기
33
- const metric = await getRepository(KpiMetric).findOne({ where: { name: code, domain: { id: domainId } } })
34
- if (!metric) throw new Error(`KPI formula metric '${code}' not found`)
35
- codeValueMap[code] = await aggregateKpiMetricValue(metric.id, domainId, context)
36
- }
37
- // org/date/period별로 값 매핑(가장 최근 기준, org key 조합)
38
- const orgKey = v => [v.date, v.period, v.org].join('|')
39
- const orgMap: Record<string, any> = {}
40
- for (const code of metricCodes) {
41
- for (const v of codeValueMap[code]) {
42
- const key = orgKey(v)
43
- orgMap[key] = orgMap[key] || { ...v, _values: {} }
44
- orgMap[key]._values[code] = v.value
45
- }
46
- }
47
- // formula 계산 (js eval)
48
- const savedValues = []
49
- for (const key in orgMap) {
50
- const ctx = orgMap[key]._values
51
- let value = null
52
- try {
53
- value = Function(...Object.keys(ctx), `return (${kpi.formula})`)(...Object.values(ctx))
54
- } catch (e) {
55
- value = null
56
- }
57
- const valueDate = orgMap[key].date
58
- const org = orgMap[key].org
59
- const version = kpi.version || 1
60
- if (value == null || isNaN(value)) continue
61
- // KpiOrgScope 처리 (org 문자열로 찾기 또는 생성)
62
- const kpiOrgScopeRepo = getRepository(KpiOrgScope, context.state?.tx)
63
- let kpiOrgScope = await kpiOrgScopeRepo.findOne({
64
- where: { org: org, domain: { id: domainId } }
65
- })
66
- if (!kpiOrgScope) {
67
- // 새 KpiOrgScope 생성
68
- kpiOrgScope = await kpiOrgScopeRepo.save({
69
- entityType: 'AutoAggregate',
70
- entityId: `auto-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
71
- entityName: org,
72
- org: org,
73
- domain: kpi.domain
74
- })
75
- }
76
-
77
- // upsert(동일 KPI, valueDate, kpiOrgScope, version) 기준으로 저장
78
- const repo = getRepository(KpiValue, context.state?.tx)
79
- const existing = await repo.findOne({
80
- where: { kpi: { id: kpi.id }, valueDate, kpiOrgScope: { id: kpiOrgScope.id }, version, domain: { id: domainId } }
81
- })
82
- let entity = existing || repo.create()
83
- entity.kpi = kpi
84
- entity.kpiId = kpi.id
85
- entity.version = version
86
- entity.valueDate = valueDate
87
- entity.value = value
88
- entity.kpiOrgScope = kpiOrgScope
89
- entity.kpiOrgScopeId = kpiOrgScope.id
90
- entity.inputType = KpiValueInputType.AUTO
91
- entity.source = 'AUTO'
92
- entity.domain = kpi.domain
93
- entity.creator = context.state?.user
94
- entity.updater = context.state?.user
95
-
96
- // 성과 점수 자동 계산 및 저장
97
- await scoreService.calculateAndSaveScore(entity, kpi)
98
-
99
- entity = await repo.save(entity)
100
- savedValues.push(entity)
101
- }
102
- return savedValues
103
- }
@@ -1,29 +0,0 @@
1
- import { EventSubscriber } from 'typeorm'
2
-
3
- import { HistoryEntitySubscriber } from '@operato/typeorm-history'
4
-
5
- import { Kpi } from './kpi'
6
- import { KpiHistory } from './kpi-history'
7
-
8
- @EventSubscriber()
9
- export class KpiHistoryEntitySubscriber extends HistoryEntitySubscriber<Kpi, KpiHistory> {
10
- public get entity() {
11
- return Kpi
12
- }
13
-
14
- public get historyEntity() {
15
- return KpiHistory
16
- }
17
-
18
- public async afterInsert(event): Promise<void> {
19
- if (event.entity.state == 'RELEASE') {
20
- await super.afterInsert(event as any)
21
- }
22
- }
23
-
24
- public async afterUpdate(event): Promise<void> {
25
- if (event.entity.state == 'RELEASE') {
26
- await super.afterUpdate(event as any)
27
- }
28
- }
29
- }
@@ -1,9 +0,0 @@
1
- import { Kpi } from './kpi'
2
- import { KpiHistory } from './kpi-history'
3
- import { KpiHistoryEntitySubscriber } from './event-subscriber'
4
- import { KpiQuery } from './kpi-query'
5
- import { KpiMutation } from './kpi-mutation'
6
-
7
- export const entities = [Kpi, KpiHistory]
8
- export const resolvers = [KpiQuery, KpiMutation]
9
- export const subscribers = [KpiHistoryEntitySubscriber]
@@ -1,164 +0,0 @@
1
- import { getRepository } from '@things-factory/shell'
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'
6
-
7
- /**
8
- * KPI formula 파싱/검증/매핑 유효성 검사 서비스
9
- * - formula에서 Metric 코드(변수명) 추출
10
- * - 각 Metric의 존재/매핑 유효성 검사
11
- * - 구문 오류, 미정의 Metric, 매핑 오류 등 에러 리포팅
12
- */
13
- export class KpiFormulaService {
14
- /**
15
- * formula 문자열에서 Metric 코드(변수명) 추출
16
- * @param formula KPI formula (예: 'defect_count / total_count * 100')
17
- * @returns string[] 추출된 Metric 코드 리스트
18
- */
19
- extractMetricCodes(formula: string): string[] {
20
- // 변수명: 알파벳/언더스코어로 시작, 숫자/알파벳/언더스코어 포함
21
- const regex = /[a-zA-Z_][a-zA-Z0-9_]*/g
22
- // 숫자/예약어/연산자 제외(후처리 필요)
23
- const reserved = new Set(['if', 'else', 'return', 'true', 'false'])
24
- return Array.from(new Set((formula.match(regex) || []).filter(code => isNaN(Number(code)) && !reserved.has(code))))
25
- }
26
-
27
- /**
28
- * formula 내 Metric 코드의 존재/매핑 유효성 검사 + 순환참조 검출
29
- * @param formula KPI formula
30
- * @param visited 순환참조 검출용 Set (재귀 호출 시 사용)
31
- * @returns { valid: boolean, errors: string[] }
32
- */
33
- async validateFormula(
34
- formula: string,
35
- visited: Set<string> = new Set()
36
- ): Promise<{ valid: boolean; errors: string[] }> {
37
- const errors: string[] = []
38
- const metricCodes = this.extractMetricCodes(formula)
39
- const metricRepo = getRepository(KpiMetric)
40
- for (const code of metricCodes) {
41
- if (visited.has(code)) {
42
- errors.push(`순환참조 감지: ${Array.from(visited).join(' → ')} → ${code}`)
43
- continue
44
- }
45
- visited.add(code)
46
- const metric = await metricRepo.findOne({ where: { name: code } })
47
- if (!metric) {
48
- errors.push(`Metric '${code}' is not defined.`)
49
- visited.delete(code)
50
- continue
51
- }
52
- if (!metric.dataSetId || !metric.fieldName) {
53
- errors.push(`Metric '${code}' is not mapped to a dataset field.`)
54
- }
55
- // metric이 formula를 가지고 있다면 재귀적으로 순환참조 검증
56
- if ((metric as any).formula) {
57
- const subResult = await this.validateFormula((metric as any).formula, new Set(visited))
58
- if (!subResult.valid) errors.push(...subResult.errors)
59
- }
60
- visited.delete(code)
61
- }
62
- // (선택) formula 구문 오류, 괄호 불일치 등 추가 검사 가능
63
- // ...
64
- return { valid: errors.length === 0, errors }
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
- }
164
- }
@@ -1,28 +0,0 @@
1
- /**
2
- * KPI 성과 점수 lookup table 타입 정의
3
- * 복잡한 성과 점수 변환을 수식으로 표현하기 어려운 경우 사용
4
- */
5
- export interface KpiScore {
6
- /** 성과 지수명 (예: A, B, C, 우수, 양호 등) */
7
- name: string
8
-
9
- /** 성과 점수 변환 기준 최소값 */
10
- minValue: number
11
-
12
- /** 성과 점수 변환 기준 최대값 */
13
- maxValue: number
14
-
15
- /** 성과 지수 점수 (선택사항) */
16
- score?: number
17
-
18
- /** 성과 지수 색상 (선택사항) */
19
- color?: string
20
-
21
- /** 성과 지수 설명 (선택사항) */
22
- description?: string
23
- }
24
-
25
- /**
26
- * KPI 성과 점수 lookup table 배열 타입
27
- */
28
- export type KpiScores = KpiScore[]
@@ -1,126 +0,0 @@
1
- import { Field, ID, ObjectType } from 'type-graphql'
2
- import { Column, Entity, Index, ManyToOne, PrimaryGeneratedColumn, RelationId, JoinColumn } from 'typeorm'
3
-
4
- import {
5
- HistoryActionColumn,
6
- HistoryActionType,
7
- HistoryEntityInterface,
8
- HistoryOriginalIdColumn
9
- } from '@operato/typeorm-history'
10
- import { Role, User } from '@things-factory/auth-base'
11
- import { config } from '@things-factory/env'
12
- import { Domain, ScalarObject } from '@things-factory/shell'
13
-
14
- import { Kpi, KpiStatus, KpiPeriodType } from './kpi'
15
- import { KpiScores } from './kpi-grade.types'
16
-
17
- const ORMCONFIG = config.get('ormconfig', {})
18
- const DATABASE_TYPE = ORMCONFIG.type
19
-
20
- @Entity()
21
- @Index('ix_kpi_history_0', (kpiHistory: KpiHistory) => [kpiHistory.originalId, kpiHistory.version], { unique: true })
22
- @Index('ix_kpi_history_1', (kpiHistory: KpiHistory) => [kpiHistory.domain, kpiHistory.originalId, kpiHistory.version], {
23
- unique: true
24
- })
25
- @ObjectType({ description: 'History Entity of Kpi' })
26
- export class KpiHistory implements HistoryEntityInterface<Kpi> {
27
- @PrimaryGeneratedColumn('uuid')
28
- @Field(type => ID)
29
- readonly id: string
30
-
31
- @Column({ nullable: true, default: 1 })
32
- @Field({ nullable: true })
33
- version?: number = 1
34
-
35
- @ManyToOne(type => Domain)
36
- @Field({ nullable: true })
37
- domain?: Domain
38
-
39
- @RelationId((kpi: Kpi) => kpi.domain)
40
- domainId?: string
41
-
42
- @Column()
43
- @Field()
44
- name: string
45
-
46
- @Column({ nullable: true })
47
- @Field({ nullable: true })
48
- description?: string
49
-
50
- @Column({ nullable: true })
51
- @Field({ nullable: true, description: 'Calculation formula for the KPI.' })
52
- formula?: string
53
-
54
- @Column({ nullable: false, default: false })
55
- @Field({ nullable: true, description: 'Whether this KPI is active (usable) or not.' })
56
- active?: boolean
57
-
58
- @Column({ nullable: true })
59
- @Field({ nullable: true, description: 'Current state of the KPI (DRAFT, RELEASED, ARCHIVED).' })
60
- state?: KpiStatus
61
-
62
- @Field(type => String, { nullable: true, description: 'Thumbnail image or file path for this KPI.' })
63
- thumbnail?: string
64
-
65
- @Column({ type: 'simple-json', nullable: true })
66
- @Field(type => ScalarObject, {
67
- nullable: true,
68
- description: 'Grade configuration for this KPI version'
69
- })
70
- grades?: KpiScores
71
-
72
- @Column({ type: 'float', nullable: true, default: 1 })
73
- @Field({ nullable: true, description: 'Weight for aggregation in parent category.' })
74
- weight?: number
75
-
76
- @Column({ default: 'DAY' })
77
- @Field(type => KpiPeriodType, { nullable: true })
78
- periodType: KpiPeriodType
79
-
80
- @Column({ nullable: true })
81
- @Field({ nullable: true })
82
- createdAt?: Date
83
-
84
- @Column({ nullable: true })
85
- @Field({ nullable: true })
86
- updatedAt?: Date
87
-
88
- @Column({ nullable: true })
89
- @Field({ nullable: true })
90
- deletedAt?: Date
91
-
92
- @ManyToOne(type => User, { nullable: true })
93
- @Field(type => User, { nullable: true })
94
- creator?: User
95
-
96
- @RelationId((kpi: Kpi) => kpi.creator)
97
- creatorId?: string
98
-
99
- @ManyToOne(type => User, { nullable: true })
100
- @Field(type => User, { nullable: true })
101
- updater?: User
102
-
103
- @RelationId((kpi: Kpi) => kpi.updater)
104
- updaterId?: string
105
-
106
- @HistoryOriginalIdColumn()
107
- public originalId!: string
108
-
109
- @HistoryActionColumn({
110
- nullable: false,
111
- type:
112
- DATABASE_TYPE == 'postgres' || DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb'
113
- ? 'enum'
114
- : DATABASE_TYPE == 'oracle'
115
- ? 'varchar2'
116
- : DATABASE_TYPE == 'mssql'
117
- ? 'nvarchar'
118
- : 'varchar',
119
- enum:
120
- DATABASE_TYPE == 'postgres' || DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb'
121
- ? HistoryActionType
122
- : undefined,
123
- length: DATABASE_TYPE == 'postgres' || DATABASE_TYPE == 'mysql' || DATABASE_TYPE == 'mariadb' ? undefined : 32
124
- })
125
- public action!: HistoryActionType
126
- }