@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
|
@@ -1,432 +0,0 @@
|
|
|
1
|
-
import { Resolver, Mutation, Arg, Ctx, Directive } from 'type-graphql'
|
|
2
|
-
import { In, Between } from 'typeorm'
|
|
3
|
-
import { getRepository } from '@things-factory/shell'
|
|
4
|
-
|
|
5
|
-
import { KpiValue } from './kpi-value'
|
|
6
|
-
import { NewKpiValue, KpiValuePatch } from './kpi-value-type'
|
|
7
|
-
import { Kpi } from '../kpi/kpi'
|
|
8
|
-
import { KpiMetric } from '../kpi-metric/kpi-metric'
|
|
9
|
-
import { KpiOrgScope } from '../kpi-org-scope/kpi-org-scope'
|
|
10
|
-
import { KpiMetricValue } from '../kpi-metric-value/kpi-metric-value'
|
|
11
|
-
import { KpiPeriodType } from '../kpi/kpi'
|
|
12
|
-
import { getDefaultValueDate } from '../utils/value-date-util'
|
|
13
|
-
import { KpiValueInputType } from './kpi-value'
|
|
14
|
-
import { KpiValueScoreService } from './kpi-value-score.service'
|
|
15
|
-
import { parseFormula } from '../../calculator/parser'
|
|
16
|
-
import { evaluateFormula } from '../../calculator/evaluator'
|
|
17
|
-
import { builtinFunctions } from '../../calculator/functions'
|
|
18
|
-
import { KpiMetricValueProvider } from '../../controllers/kpi-metric-value-provider'
|
|
19
|
-
|
|
20
|
-
@Resolver(KpiValue)
|
|
21
|
-
export class KpiValueMutation {
|
|
22
|
-
@Directive('@privilege(category: "kpi", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)')
|
|
23
|
-
@Directive('@transaction')
|
|
24
|
-
@Mutation(returns => KpiValue, { description: 'Create a new KPI value with the provided details.' })
|
|
25
|
-
async createKpiValue(
|
|
26
|
-
@Arg('kpiValue', { description: 'Input object containing details for the new KPI value.' }) kpiValue: NewKpiValue,
|
|
27
|
-
@Ctx() context: ResolverContext
|
|
28
|
-
): Promise<KpiValue> {
|
|
29
|
-
const { domain, user, tx } = context.state
|
|
30
|
-
|
|
31
|
-
let kpi = kpiValue.kpiId ? await getRepository(Kpi).findOne({ where: { id: kpiValue.kpiId } }) : undefined
|
|
32
|
-
|
|
33
|
-
let inputType: KpiValueInputType | undefined = undefined
|
|
34
|
-
if (kpiValue.inputType) {
|
|
35
|
-
if (typeof kpiValue.inputType === 'string') {
|
|
36
|
-
inputType = KpiValueInputType[kpiValue.inputType as keyof typeof KpiValueInputType]
|
|
37
|
-
} else {
|
|
38
|
-
inputType = kpiValue.inputType
|
|
39
|
-
}
|
|
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
|
-
|
|
70
|
-
const entity: Partial<KpiValue> = {
|
|
71
|
-
kpi,
|
|
72
|
-
kpiId: kpiValue.kpiId,
|
|
73
|
-
version: kpiValue.version,
|
|
74
|
-
valueDate: kpiValue.valueDate,
|
|
75
|
-
value: kpiValue.value,
|
|
76
|
-
source: kpiValue.source,
|
|
77
|
-
domain: domain,
|
|
78
|
-
creator: user,
|
|
79
|
-
updater: user
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if (kpiOrgScope) {
|
|
83
|
-
entity.kpiOrgScope = kpiOrgScope
|
|
84
|
-
entity.kpiOrgScopeId = kpiOrgScope.id
|
|
85
|
-
}
|
|
86
|
-
if (inputType) entity.inputType = inputType
|
|
87
|
-
|
|
88
|
-
return await getRepository(KpiValue, tx).save(entity)
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
@Directive('@transaction')
|
|
92
|
-
@Directive('@privilege(category: "kpi", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)')
|
|
93
|
-
@Mutation(returns => [KpiValue], { description: 'Create multiple KPI values with the provided details.' })
|
|
94
|
-
async createMultipleKpiValue(
|
|
95
|
-
@Arg('values', type => [NewKpiValue], {
|
|
96
|
-
description: 'Array of input objects containing details for the new KPI values.'
|
|
97
|
-
})
|
|
98
|
-
values: NewKpiValue[],
|
|
99
|
-
@Ctx() context: ResolverContext
|
|
100
|
-
): Promise<KpiValue[]> {
|
|
101
|
-
const { domain, user, tx } = context.state
|
|
102
|
-
const repository = getRepository(KpiValue, tx)
|
|
103
|
-
const scoreService = new KpiValueScoreService()
|
|
104
|
-
|
|
105
|
-
const entities: KpiValue[] = []
|
|
106
|
-
|
|
107
|
-
for (const kpiValue of values) {
|
|
108
|
-
let kpi = kpiValue.kpiId ? await getRepository(Kpi).findOne({ where: { id: kpiValue.kpiId } }) : undefined
|
|
109
|
-
|
|
110
|
-
let inputType: KpiValueInputType | undefined = undefined
|
|
111
|
-
if (kpiValue.inputType) {
|
|
112
|
-
if (typeof kpiValue.inputType === 'string') {
|
|
113
|
-
inputType = KpiValueInputType[kpiValue.inputType as keyof typeof KpiValueInputType]
|
|
114
|
-
} else {
|
|
115
|
-
inputType = kpiValue.inputType
|
|
116
|
-
}
|
|
117
|
-
}
|
|
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
|
-
|
|
147
|
-
const entity: Partial<KpiValue> = {
|
|
148
|
-
kpi,
|
|
149
|
-
kpiId: kpiValue.kpiId,
|
|
150
|
-
version: kpiValue.version || 1,
|
|
151
|
-
valueDate: kpiValue.valueDate,
|
|
152
|
-
value: kpiValue.value,
|
|
153
|
-
source: kpiValue.source || 'MANUAL',
|
|
154
|
-
domain: domain,
|
|
155
|
-
creator: user,
|
|
156
|
-
updater: user
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
if (kpiOrgScope) {
|
|
160
|
-
entity.kpiOrgScope = kpiOrgScope
|
|
161
|
-
entity.kpiOrgScopeId = kpiOrgScope.id
|
|
162
|
-
}
|
|
163
|
-
if (inputType) entity.inputType = inputType
|
|
164
|
-
|
|
165
|
-
const savedEntity = await repository.save(entity)
|
|
166
|
-
|
|
167
|
-
// 성과 점수 자동 계산 및 저장
|
|
168
|
-
if (kpi) {
|
|
169
|
-
await scoreService.calculateAndSaveScore(savedEntity, kpi)
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
entities.push(savedEntity)
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
return entities
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
@Directive('@transaction')
|
|
179
|
-
@Directive('@privilege(category: "kpi", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)')
|
|
180
|
-
@Mutation(returns => KpiValue, { description: 'To modify KpiValue information' })
|
|
181
|
-
async updateKpiValue(
|
|
182
|
-
@Arg('id') id: string,
|
|
183
|
-
@Arg('patch') patch: KpiValuePatch,
|
|
184
|
-
@Ctx() context: ResolverContext
|
|
185
|
-
): Promise<KpiValue> {
|
|
186
|
-
const { domain, user, tx } = context.state
|
|
187
|
-
|
|
188
|
-
const repository = getRepository(KpiValue, tx)
|
|
189
|
-
const kpiValue = await repository.findOne({
|
|
190
|
-
where: { domain: { id: domain.id }, id }
|
|
191
|
-
})
|
|
192
|
-
|
|
193
|
-
let kpi = patch.kpiId ? await getRepository(Kpi).findOne({ where: { id: patch.kpiId } }) : kpiValue.kpi
|
|
194
|
-
|
|
195
|
-
let inputType: KpiValueInputType | undefined = undefined
|
|
196
|
-
if (patch.inputType) {
|
|
197
|
-
if (typeof patch.inputType === 'string') {
|
|
198
|
-
inputType = KpiValueInputType[patch.inputType as keyof typeof KpiValueInputType]
|
|
199
|
-
} else {
|
|
200
|
-
inputType = patch.inputType
|
|
201
|
-
}
|
|
202
|
-
} else {
|
|
203
|
-
inputType = kpiValue.inputType
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
const entity: any = {
|
|
207
|
-
...kpiValue,
|
|
208
|
-
...patch,
|
|
209
|
-
kpi,
|
|
210
|
-
updater: user
|
|
211
|
-
}
|
|
212
|
-
if (inputType) entity.inputType = inputType
|
|
213
|
-
|
|
214
|
-
return await repository.save(entity)
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
@Directive('@transaction')
|
|
218
|
-
@Directive('@privilege(category: "kpi", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)')
|
|
219
|
-
@Mutation(returns => [KpiValue], { description: "To modify multiple KpiValues' information" })
|
|
220
|
-
async updateMultipleKpiValue(
|
|
221
|
-
@Arg('patches', type => [KpiValuePatch]) patches: KpiValuePatch[],
|
|
222
|
-
@Ctx() context: ResolverContext
|
|
223
|
-
): Promise<KpiValue[]> {
|
|
224
|
-
const { domain, user, tx } = context.state
|
|
225
|
-
|
|
226
|
-
let results = []
|
|
227
|
-
const _createRecords = patches.filter((patch: any) => patch.cuFlag.toUpperCase() === '+')
|
|
228
|
-
const _updateRecords = patches.filter((patch: any) => patch.cuFlag.toUpperCase() === 'M')
|
|
229
|
-
const kpiValueRepo = getRepository(KpiValue, tx)
|
|
230
|
-
|
|
231
|
-
if (_createRecords.length > 0) {
|
|
232
|
-
for (let i = 0; i < _createRecords.length; i++) {
|
|
233
|
-
const newRecord = _createRecords[i]
|
|
234
|
-
|
|
235
|
-
const kpi = await getRepository(Kpi, tx).findOne({
|
|
236
|
-
where: { domain: { id: domain.id }, id: newRecord.kpiId }
|
|
237
|
-
})
|
|
238
|
-
|
|
239
|
-
const result = await kpiValueRepo.save({
|
|
240
|
-
...newRecord,
|
|
241
|
-
kpi,
|
|
242
|
-
version: newRecord.version || kpi.version,
|
|
243
|
-
domain,
|
|
244
|
-
creator: user,
|
|
245
|
-
updater: user
|
|
246
|
-
} as any)
|
|
247
|
-
|
|
248
|
-
results.push({ ...result, cuFlag: '+' })
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
if (_updateRecords.length > 0) {
|
|
253
|
-
for (let i = 0; i < _updateRecords.length; i++) {
|
|
254
|
-
const updateRecord = _updateRecords[i]
|
|
255
|
-
const kpiValue = await kpiValueRepo.findOneBy({ id: updateRecord.id })
|
|
256
|
-
|
|
257
|
-
const kpi = await getRepository(Kpi, tx).findOne({
|
|
258
|
-
where: { domain: { id: domain.id }, id: updateRecord.kpiId }
|
|
259
|
-
})
|
|
260
|
-
|
|
261
|
-
const result = await kpiValueRepo.save({
|
|
262
|
-
...kpiValue,
|
|
263
|
-
...updateRecord,
|
|
264
|
-
kpi,
|
|
265
|
-
version: updateRecord.version || kpi.version,
|
|
266
|
-
updater: user
|
|
267
|
-
} as any)
|
|
268
|
-
|
|
269
|
-
results.push({ ...result, cuFlag: 'M' })
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
return results
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
@Directive('@transaction')
|
|
277
|
-
@Directive('@privilege(category: "kpi", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)')
|
|
278
|
-
@Mutation(returns => Boolean, { description: 'To delete KpiValue' })
|
|
279
|
-
async deleteKpiValue(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<boolean> {
|
|
280
|
-
const { domain, tx } = context.state
|
|
281
|
-
|
|
282
|
-
await getRepository(KpiValue, tx).delete({ domain: { id: domain.id }, id })
|
|
283
|
-
|
|
284
|
-
return true
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
@Directive('@transaction')
|
|
288
|
-
@Directive('@privilege(category: "kpi", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)')
|
|
289
|
-
@Mutation(returns => Boolean, { description: 'To delete multiple KpiValues' })
|
|
290
|
-
async deleteKpiValues(
|
|
291
|
-
@Arg('ids', type => [String]) ids: string[],
|
|
292
|
-
@Ctx() context: ResolverContext
|
|
293
|
-
): Promise<boolean> {
|
|
294
|
-
const { domain, tx } = context.state
|
|
295
|
-
|
|
296
|
-
await getRepository(KpiValue, tx).delete({
|
|
297
|
-
domain: { id: domain.id },
|
|
298
|
-
id: In(ids)
|
|
299
|
-
})
|
|
300
|
-
|
|
301
|
-
return true
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
@Directive('@transaction')
|
|
305
|
-
@Directive('@privilege(category: "kpi", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)')
|
|
306
|
-
@Mutation(returns => Boolean, { description: 'To import multiple KpiValues' })
|
|
307
|
-
async importKpiValues(
|
|
308
|
-
@Arg('kpiValues', type => [KpiValuePatch]) kpiValues: KpiValuePatch[],
|
|
309
|
-
@Ctx() context: ResolverContext
|
|
310
|
-
): Promise<boolean> {
|
|
311
|
-
const { domain, tx } = context.state
|
|
312
|
-
|
|
313
|
-
await Promise.all(
|
|
314
|
-
kpiValues.map(async (kpiValue: KpiValuePatch) => {
|
|
315
|
-
const createdKpiValue: KpiValue = await getRepository(KpiValue, tx).save({ domain, ...kpiValue } as any)
|
|
316
|
-
})
|
|
317
|
-
)
|
|
318
|
-
|
|
319
|
-
return true
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
@Directive('@transaction')
|
|
323
|
-
@Directive('@privilege(category: "kpi", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)')
|
|
324
|
-
@Mutation(returns => Boolean, { description: 'Recalculate scores for all KpiValues of a specific KPI' })
|
|
325
|
-
async recalculateScoresForKpi(@Arg('kpiId') kpiId: string, @Ctx() context: ResolverContext): Promise<boolean> {
|
|
326
|
-
const scoreService = new KpiValueScoreService()
|
|
327
|
-
await scoreService.recalculateScoresForKpi(kpiId, context)
|
|
328
|
-
return true
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
@Directive('@transaction')
|
|
332
|
-
@Directive('@privilege(category: "kpi", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)')
|
|
333
|
-
@Mutation(returns => KpiValue, { description: '기존 KPI Value 인스턴스를 현재 formula/metric 값으로 재계산' })
|
|
334
|
-
async recalculateKpiValue(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<KpiValue> {
|
|
335
|
-
const { domain, user, tx } = context.state
|
|
336
|
-
const kpiValueRepo = getRepository(KpiValue, tx)
|
|
337
|
-
const kpiRepo = getRepository(Kpi, tx)
|
|
338
|
-
const metricRepo = getRepository(KpiMetric, tx)
|
|
339
|
-
const metricValueRepo = getRepository(KpiMetricValue, tx)
|
|
340
|
-
|
|
341
|
-
// 1. 기존 KPI Value 인스턴스 조회
|
|
342
|
-
const kpiValue = await kpiValueRepo.findOne({
|
|
343
|
-
where: { id, domain: { id: domain.id } },
|
|
344
|
-
relations: ['kpi', 'kpiOrgScope']
|
|
345
|
-
})
|
|
346
|
-
if (!kpiValue) throw new Error('KPI Value not found')
|
|
347
|
-
// 항상 최신 버전의 KPI를 조회하여 사용
|
|
348
|
-
const kpi = await kpiRepo.findOne({
|
|
349
|
-
where: { id: kpiValue.kpiId, domain: { id: domain.id } },
|
|
350
|
-
order: { version: 'DESC' }
|
|
351
|
-
})
|
|
352
|
-
if (!kpi) throw new Error('KPI 정보 없음')
|
|
353
|
-
if (!kpi.formula) throw new Error('KPI formula 없음')
|
|
354
|
-
const periodType = kpi.periodType || KpiPeriodType.DAY
|
|
355
|
-
const valueDate = kpiValue.valueDate
|
|
356
|
-
const kpiOrgScope = kpiValue.kpiOrgScope
|
|
357
|
-
const org = kpiOrgScope?.entityName || kpiOrgScope?.org || 'unknown' // KpiOrgScope에서 org 정보 추출
|
|
358
|
-
|
|
359
|
-
// 2. formula에서 metric code 추출
|
|
360
|
-
const metricCodes = (kpi.formula.match(/[a-zA-Z_][a-zA-Z0-9_]*/g) || []).filter(
|
|
361
|
-
code => code !== 'null' && code !== 'undefined'
|
|
362
|
-
)
|
|
363
|
-
const metricMap: Record<string, KpiMetric> = {}
|
|
364
|
-
for (const code of metricCodes) {
|
|
365
|
-
const metric = await metricRepo.findOne({ where: { name: code, domain: { id: domain.id } } })
|
|
366
|
-
if (!metric) throw new Error(`KPI formula metric '${code}' not found`)
|
|
367
|
-
metricMap[code] = metric
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
// 3. metric 값 집계 (periodType 변환/집계)
|
|
371
|
-
const metricValues: Record<string, number> = {}
|
|
372
|
-
for (const code of metricCodes) {
|
|
373
|
-
const metric = metricMap[code]
|
|
374
|
-
let value = null
|
|
375
|
-
if (metric.periodType === periodType) {
|
|
376
|
-
const mv = await metricValueRepo.findOne({
|
|
377
|
-
where: {
|
|
378
|
-
metric: { id: metric.id },
|
|
379
|
-
valueDate,
|
|
380
|
-
periodType,
|
|
381
|
-
org: org ?? '',
|
|
382
|
-
domain: { id: domain.id }
|
|
383
|
-
}
|
|
384
|
-
})
|
|
385
|
-
value = mv?.value ?? null
|
|
386
|
-
} else {
|
|
387
|
-
let startDate: string, endDate: string
|
|
388
|
-
if (periodType === KpiPeriodType.MONTH && metric.periodType === KpiPeriodType.DAY) {
|
|
389
|
-
startDate = valueDate + '-01'
|
|
390
|
-
endDate = valueDate + '-31'
|
|
391
|
-
const mvs = await metricValueRepo.find({
|
|
392
|
-
where: {
|
|
393
|
-
metric: { id: metric.id },
|
|
394
|
-
valueDate: Between(startDate, endDate),
|
|
395
|
-
periodType: metric.periodType,
|
|
396
|
-
org: org ?? '',
|
|
397
|
-
domain: { id: domain.id }
|
|
398
|
-
}
|
|
399
|
-
})
|
|
400
|
-
value = mvs.reduce((sum, mv) => sum + (mv.value ?? 0), 0)
|
|
401
|
-
} else {
|
|
402
|
-
throw new Error('KPI/Metric periodType 조합 집계 미지원')
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
if (value == null) throw new Error(`Metric '${code}' 값 없음`)
|
|
406
|
-
metricValues[code] = value
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
// 4. formula 계산 (calculator 기반)
|
|
410
|
-
const ast = parseFormula(kpi.formula)
|
|
411
|
-
const provider = new KpiMetricValueProvider({
|
|
412
|
-
valueDate,
|
|
413
|
-
org,
|
|
414
|
-
domainId: domain.id,
|
|
415
|
-
tx
|
|
416
|
-
})
|
|
417
|
-
const evalContext = { functions: builtinFunctions, provider }
|
|
418
|
-
const kpiValueResult = await evaluateFormula(ast, evalContext)
|
|
419
|
-
if (kpiValueResult == null || isNaN(kpiValueResult)) throw new Error('KPI formula 결과값 없음')
|
|
420
|
-
|
|
421
|
-
// 5. KPI Value 업데이트 (최신 버전으로)
|
|
422
|
-
kpiValue.value = kpiValueResult
|
|
423
|
-
kpiValue.version = kpi.version || 1
|
|
424
|
-
kpiValue.updater = user
|
|
425
|
-
|
|
426
|
-
// 6. 성과 점수 자동 계산 및 저장
|
|
427
|
-
const scoreService = new KpiValueScoreService()
|
|
428
|
-
await scoreService.calculateAndSaveScore(kpiValue, kpi)
|
|
429
|
-
|
|
430
|
-
return await kpiValueRepo.save(kpiValue)
|
|
431
|
-
}
|
|
432
|
-
}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { Resolver, Query, FieldResolver, Root, Args, Arg, Ctx, Directive, Float } from 'type-graphql'
|
|
2
|
-
import { Domain, getQueryBuilderFromListParams, getRepository, ListParam } from '@things-factory/shell'
|
|
3
|
-
import { User } from '@things-factory/auth-base'
|
|
4
|
-
import { KpiValue } from './kpi-value'
|
|
5
|
-
import { KpiValueList } from './kpi-value-type'
|
|
6
|
-
import { Kpi } from '../kpi/kpi'
|
|
7
|
-
import { KpiValueScoreService } from './kpi-value-score.service'
|
|
8
|
-
|
|
9
|
-
@Resolver(KpiValue)
|
|
10
|
-
export class KpiValueQuery {
|
|
11
|
-
@Directive('@privilege(category: "kpi", privilege: "query", domainOwnerGranted: true, superUserGranted: true)')
|
|
12
|
-
@Query(returns => KpiValue!, { nullable: true, description: 'Fetch a single KPI value by its unique identifier.' })
|
|
13
|
-
async kpiValue(
|
|
14
|
-
@Arg('id', { description: 'Unique identifier of the KPI value to fetch.' }) id: string,
|
|
15
|
-
@Ctx() context: ResolverContext
|
|
16
|
-
): Promise<KpiValue> {
|
|
17
|
-
const { domain } = context.state
|
|
18
|
-
|
|
19
|
-
return await getRepository(KpiValue).findOne({
|
|
20
|
-
where: { domain: { id: domain.id }, id }
|
|
21
|
-
})
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
@Directive('@privilege(category: "kpi", privilege: "query", domainOwnerGranted: true, superUserGranted: true)')
|
|
25
|
-
@Query(returns => KpiValueList, { description: 'To fetch multiple KpiValues' })
|
|
26
|
-
async kpiValues(@Args(type => ListParam) params: ListParam, @Ctx() context: ResolverContext): Promise<KpiValueList> {
|
|
27
|
-
const { domain } = context.state
|
|
28
|
-
|
|
29
|
-
const queryBuilder = getQueryBuilderFromListParams({
|
|
30
|
-
domain,
|
|
31
|
-
params,
|
|
32
|
-
repository: await getRepository(KpiValue),
|
|
33
|
-
searchables: ['kpi', 'group', 'valueDate']
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
const [items, total] = await queryBuilder.getManyAndCount()
|
|
37
|
-
|
|
38
|
-
return { items, total }
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
@FieldResolver(type => Domain)
|
|
42
|
-
async domain(@Root() kpiValue: KpiValue): Promise<Domain> {
|
|
43
|
-
return kpiValue.domainId && (await getRepository(Domain).findOneBy({ id: kpiValue.domainId }))
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
@FieldResolver(type => User)
|
|
47
|
-
async updater(@Root() kpiValue: KpiValue): Promise<User> {
|
|
48
|
-
return kpiValue.updaterId && (await getRepository(User).findOneBy({ id: kpiValue.updaterId }))
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
@FieldResolver(type => User)
|
|
52
|
-
async creator(@Root() kpiValue: KpiValue): Promise<User> {
|
|
53
|
-
return kpiValue.creatorId && (await getRepository(User).findOneBy({ id: kpiValue.creatorId }))
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
@FieldResolver(type => Kpi)
|
|
57
|
-
async kpi(@Root() kpiValue: KpiValue): Promise<Kpi | null> {
|
|
58
|
-
if (!kpiValue.kpiId) return null
|
|
59
|
-
return await getRepository(Kpi).findOneBy({ id: kpiValue.kpiId })
|
|
60
|
-
}
|
|
61
|
-
}
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
import { getRepository } from '@things-factory/shell'
|
|
2
|
-
import { Kpi } from '../kpi/kpi'
|
|
3
|
-
import { KpiValue } from './kpi-value'
|
|
4
|
-
import { parseFormula } from '../../calculator/parser'
|
|
5
|
-
import { evaluateFormula } from '../../calculator/evaluator'
|
|
6
|
-
import { builtinFunctions } from '../../calculator/functions'
|
|
7
|
-
|
|
8
|
-
export interface ScoreResult {
|
|
9
|
-
score: number
|
|
10
|
-
calculationMethod?: 'formula' | 'lookup'
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export class KpiValueScoreService {
|
|
14
|
-
/**
|
|
15
|
-
* scoreFormula를 사용한 성과 점수 계산
|
|
16
|
-
*/
|
|
17
|
-
async calculateScoreFromFormula(kpi: Kpi, value: number): Promise<ScoreResult | null> {
|
|
18
|
-
if (!kpi.scoreFormula) {
|
|
19
|
-
return null
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
try {
|
|
23
|
-
const ast = parseFormula(kpi.scoreFormula)
|
|
24
|
-
const provider = {
|
|
25
|
-
get: async (name: string) => {
|
|
26
|
-
if (name === 'value') return value
|
|
27
|
-
return null
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
const evalContext = { functions: builtinFunctions, provider }
|
|
31
|
-
const result = await evaluateFormula(ast, evalContext)
|
|
32
|
-
|
|
33
|
-
if (result !== null && result !== undefined && !isNaN(result)) {
|
|
34
|
-
const performanceScore = Number(result)
|
|
35
|
-
|
|
36
|
-
return {
|
|
37
|
-
score: performanceScore,
|
|
38
|
-
calculationMethod: 'formula'
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
} catch (error) {
|
|
42
|
-
console.error(`Score formula evaluation for '${kpi.name}: ${kpi.scoreFormula}' error:`, error)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return null
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* KPI의 grades lookup table을 이용한 성과 점수 변환
|
|
50
|
-
* 복잡한 성과 점수 변환을 수식으로 표현하기 어려운 경우 사용
|
|
51
|
-
* @deprecated 향후 grades 필드 제거 예정. scoreFormula로 대체 권장
|
|
52
|
-
*/
|
|
53
|
-
calculateScoreFromLookup(kpi: Kpi, value: number): ScoreResult | null {
|
|
54
|
-
if (!kpi.grades || kpi.grades.length === 0) {
|
|
55
|
-
return null
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// grades lookup table에서 해당 값의 범위에 맞는 성과 점수 찾기
|
|
59
|
-
const grade = kpi.grades.find(g => value >= g.minValue && value <= g.maxValue)
|
|
60
|
-
|
|
61
|
-
if (!grade) {
|
|
62
|
-
return null
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return {
|
|
66
|
-
score: grade.score,
|
|
67
|
-
calculationMethod: 'lookup'
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* KpiValue 생성/수정 시 성과 점수 자동 계산 및 저장
|
|
73
|
-
*/
|
|
74
|
-
async calculateAndSaveScore(kpiValue: KpiValue, kpi: Kpi): Promise<void> {
|
|
75
|
-
// 1. scoreFormula 우선 시도
|
|
76
|
-
let scoreResult = await this.calculateScoreFromFormula(kpi, kpiValue.value)
|
|
77
|
-
|
|
78
|
-
// 2. scoreFormula가 없거나 실패하면 grades lookup table 사용 (deprecated)
|
|
79
|
-
if (!scoreResult) {
|
|
80
|
-
scoreResult = this.calculateScoreFromLookup(kpi, kpiValue.value)
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (scoreResult) {
|
|
84
|
-
// KpiValue의 score 필드에 성과 점수 저장
|
|
85
|
-
kpiValue.score = scoreResult.score
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* 기존 KpiValue들의 성과 점수 재계산 (배치 처리)
|
|
91
|
-
*/
|
|
92
|
-
async recalculateScoresForKpi(kpiId: string, context: ResolverContext): Promise<void> {
|
|
93
|
-
const kpi = await getRepository(Kpi).findOne({ where: { id: kpiId } })
|
|
94
|
-
if (!kpi) return
|
|
95
|
-
|
|
96
|
-
const kpiValues = await getRepository(KpiValue).find({
|
|
97
|
-
where: { kpiId, version: kpi.version }
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
for (const kpiValue of kpiValues) {
|
|
101
|
-
await this.calculateAndSaveScore(kpiValue, kpi)
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
await getRepository(KpiValue).save(kpiValues)
|
|
105
|
-
}
|
|
106
|
-
}
|