@things-factory/kpi 9.1.19 → 10.0.0-beta.2
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/client/pages/kpi/kpi-list-page.ts +339 -525
- package/client/pages/kpi/kpi-tree-page.ts +135 -207
- package/client/pages/kpi-metric/kpi-metric-list-page.ts +146 -226
- package/client/pages/kpi-metric-value/kpi-metric-value-editor-page.ts +187 -295
- package/client/pages/kpi-metric-value/kpi-metric-value-list-page.ts +123 -194
- package/client/pages/kpi-metric-value/kpi-metric-value-manual-entry-page.ts +57 -91
- package/client/pages/kpi-statistic/kpi-statistic-editor-page.ts +180 -278
- package/client/pages/kpi-statistic/kpi-statistic-list-page.ts +186 -286
- package/client/pages/kpi-value/kpi-value-editor-page.ts +189 -292
- package/client/pages/kpi-value/kpi-value-list-page.ts +170 -264
- package/dist-client/pages/kpi/kpi-list-page.d.ts +0 -6
- package/dist-client/pages/kpi/kpi-list-page.js +150 -282
- package/dist-client/pages/kpi/kpi-list-page.js.map +1 -1
- package/dist-client/pages/kpi/kpi-tree-page.d.ts +1 -7
- package/dist-client/pages/kpi/kpi-tree-page.js +76 -127
- package/dist-client/pages/kpi/kpi-tree-page.js.map +1 -1
- package/dist-client/pages/kpi-metric/kpi-metric-list-page.d.ts +0 -6
- package/dist-client/pages/kpi-metric/kpi-metric-list-page.js +62 -116
- 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 -7
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-editor-page.js +82 -140
- 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-list-page.d.ts +0 -6
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.js +54 -98
- 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-page.d.ts +1 -7
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-page.js +30 -57
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-page.js.map +1 -1
- package/dist-client/pages/kpi-statistic/kpi-statistic-editor-page.d.ts +1 -7
- package/dist-client/pages/kpi-statistic/kpi-statistic-editor-page.js +91 -153
- 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 +0 -6
- package/dist-client/pages/kpi-statistic/kpi-statistic-list-page.js +81 -155
- 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 +1 -7
- package/dist-client/pages/kpi-value/kpi-value-editor-page.js +80 -136
- 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 +0 -6
- package/dist-client/pages/kpi-value/kpi-value-list-page.js +73 -134
- package/dist-client/pages/kpi-value/kpi-value-list-page.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/service/index.d.ts +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +18 -18
- package/client/tsconfig.json +0 -11
- package/dist-server/tsconfig.json +0 -10
- package/server/@types/index.d.ts +0 -11
- package/server/calculator/evaluator.ts +0 -45
- package/server/calculator/functions.ts +0 -67
- package/server/calculator/index.ts +0 -4
- package/server/calculator/parser.ts +0 -137
- package/server/calculator/provider.ts +0 -10
- package/server/controllers/index.ts +0 -2
- package/server/controllers/kpi-metric-value-provider.ts +0 -79
- package/server/controllers/kpi-value-provider.ts +0 -51
- package/server/index.ts +0 -6
- package/server/migrations/1752190849680-seed-kpi-metrics.ts +0 -124
- package/server/migrations/1752190849681-seed-kpi.ts +0 -356
- package/server/migrations/1752192090123-add-grades-to-kpi.ts +0 -67
- package/server/migrations/1752192090124-add-kpi-statistics.ts +0 -719
- package/server/migrations/1752192090128-seed-kpi-org-scope.ts +0 -132
- package/server/migrations/1752192090129-seed-kpi-values.ts +0 -207
- package/server/migrations/grade-data/x11-performance-table.json +0 -962
- package/server/migrations/grade-data/x12-performance-table.json +0 -611
- package/server/migrations/grade-data/x14-performance-table.json +0 -42
- package/server/migrations/grade-data/x21-performance-table.json +0 -889
- package/server/migrations/grade-data/x22-performance-table.json +0 -1064
- package/server/migrations/grade-data/x23-performance-table.json +0 -42
- package/server/migrations/grade-data/x31-performance-table.json +0 -644
- package/server/migrations/grade-data/x32-performance-table.json +0 -993
- package/server/migrations/grade-data/x33-performance-table.json +0 -195
- package/server/migrations/grade-data/x34-performance-table.json +0 -12
- package/server/migrations/grade-data/x35-performance-table.json +0 -42
- package/server/migrations/grade-data/x41-performance-table.json +0 -825
- package/server/migrations/grade-data/x42-performance-table.json +0 -786
- package/server/migrations/grade-data/x43-performance-table.json +0 -12
- package/server/migrations/grade-data/x44-performance-table.json +0 -42
- package/server/migrations/grade-data/x51-performance-table.json +0 -924
- package/server/migrations/grade-data/x52-performance-table.json +0 -42
- package/server/migrations/grade-data/x61-performance-table.json +0 -261
- package/server/migrations/grade-data/x62-performance-table.json +0 -42
- package/server/migrations/index.ts +0 -9
- package/server/migrations/seed-data/kpi-metrics-seed.json +0 -454
- package/server/migrations/seed-data/kpi-org-scope-seed.json +0 -1676
- package/server/migrations/seed-data/kpi-scopes-seed.json +0 -121
- package/server/migrations/seed-data/kpi-values-seed.json +0 -402
- package/server/migrations/seed-data/kpis-seed.json +0 -488
- package/server/migrations/seed-data/scope-definitions-seed.json +0 -90
- package/server/routes.ts +0 -81
- package/server/service/index.ts +0 -51
- package/server/service/kpi/aggregate-kpi.ts +0 -103
- package/server/service/kpi/event-subscriber.ts +0 -29
- package/server/service/kpi/index.ts +0 -9
- package/server/service/kpi/kpi-formula.service.ts +0 -164
- package/server/service/kpi/kpi-grade.types.ts +0 -28
- package/server/service/kpi/kpi-history.ts +0 -126
- package/server/service/kpi/kpi-mutation.ts +0 -553
- package/server/service/kpi/kpi-query.ts +0 -224
- package/server/service/kpi/kpi-type.ts +0 -151
- package/server/service/kpi/kpi.ts +0 -254
- package/server/service/kpi-alert/index.ts +0 -3
- package/server/service/kpi-alert/kpi-alert-query.ts +0 -59
- package/server/service/kpi-alert/kpi-alert-type.ts +0 -20
- package/server/service/kpi-metric/aggregate-kpi-metric.ts +0 -132
- package/server/service/kpi-metric/index.ts +0 -7
- package/server/service/kpi-metric/kpi-metric-mutation.ts +0 -309
- package/server/service/kpi-metric/kpi-metric-query.ts +0 -70
- package/server/service/kpi-metric/kpi-metric-type.ts +0 -111
- package/server/service/kpi-metric/kpi-metric.ts +0 -134
- package/server/service/kpi-metric-value/index.ts +0 -7
- package/server/service/kpi-metric-value/kpi-metric-value-mutation.ts +0 -270
- package/server/service/kpi-metric-value/kpi-metric-value-query.ts +0 -62
- package/server/service/kpi-metric-value/kpi-metric-value-type.ts +0 -82
- package/server/service/kpi-metric-value/kpi-metric-value.ts +0 -93
- package/server/service/kpi-org-scope/index.ts +0 -6
- package/server/service/kpi-org-scope/kpi-org-scope-mutation.ts +0 -173
- package/server/service/kpi-org-scope/kpi-org-scope-query.ts +0 -127
- package/server/service/kpi-org-scope/kpi-org-scope-type.ts +0 -68
- package/server/service/kpi-org-scope/kpi-org-scope.ts +0 -123
- package/server/service/kpi-scope/index.ts +0 -11
- package/server/service/kpi-scope/kpi-scope-mutation.ts +0 -129
- package/server/service/kpi-scope/kpi-scope-query.ts +0 -63
- package/server/service/kpi-scope/kpi-scope-type.ts +0 -96
- package/server/service/kpi-scope/kpi-scope.ts +0 -143
- package/server/service/kpi-statistic/index.ts +0 -7
- package/server/service/kpi-statistic/kpi-statistic-batch.service.ts +0 -231
- package/server/service/kpi-statistic/kpi-statistic-calculation.service.ts +0 -410
- package/server/service/kpi-statistic/kpi-statistic-mutation.ts +0 -291
- package/server/service/kpi-statistic/kpi-statistic-query.ts +0 -146
- package/server/service/kpi-statistic/kpi-statistic-type.ts +0 -152
- package/server/service/kpi-statistic/kpi-statistic.ts +0 -199
- package/server/service/kpi-value/index.ts +0 -7
- package/server/service/kpi-value/kpi-value-mutation.ts +0 -432
- package/server/service/kpi-value/kpi-value-query.ts +0 -61
- package/server/service/kpi-value/kpi-value-score.service.ts +0 -106
- package/server/service/kpi-value/kpi-value-type.ts +0 -122
- package/server/service/kpi-value/kpi-value.ts +0 -160
- package/server/service/utils/value-date-util.ts +0 -119
- package/server/tsconfig.json +0 -10
- package/server/types/global.d.ts +0 -8
package/server/service/index.ts
DELETED
|
@@ -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
|
-
}
|