@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,553 +0,0 @@
|
|
|
1
|
-
import { Resolver, Mutation, Arg, Ctx, Directive } from 'type-graphql'
|
|
2
|
-
import { In } from 'typeorm'
|
|
3
|
-
import { getRepository } from '@things-factory/shell'
|
|
4
|
-
|
|
5
|
-
import { createAttachment, deleteAttachmentsByRef } from '@things-factory/attachment-base'
|
|
6
|
-
import { Kpi } from './kpi'
|
|
7
|
-
import { NewKpi, KpiPatch } from './kpi-type'
|
|
8
|
-
import { KpiHistory } from './kpi-history'
|
|
9
|
-
import { KpiFormulaService } from './kpi-formula.service'
|
|
10
|
-
import { Application, CallbackBase, registerSchedule, unregisterSchedule } from '@things-factory/scheduler-client'
|
|
11
|
-
import { KpiValue } from '../kpi-value/kpi-value'
|
|
12
|
-
import { KpiOrgScope } from '../kpi-org-scope/kpi-org-scope'
|
|
13
|
-
import { Between } from 'typeorm'
|
|
14
|
-
import { KpiMetric } from '../kpi-metric/kpi-metric'
|
|
15
|
-
import { KpiMetricValue } from '../kpi-metric-value/kpi-metric-value'
|
|
16
|
-
import { parseFormula } from '../../calculator/parser'
|
|
17
|
-
import { evaluateFormula } from '../../calculator/evaluator'
|
|
18
|
-
import { builtinFunctions } from '../../calculator/functions'
|
|
19
|
-
import { KpiMetricValueProvider } from '../../controllers/kpi-metric-value-provider'
|
|
20
|
-
|
|
21
|
-
@Resolver(Kpi)
|
|
22
|
-
export class KpiMutation {
|
|
23
|
-
@Directive('@transaction')
|
|
24
|
-
@Directive('@privilege(category: "kpi", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)')
|
|
25
|
-
@Mutation(returns => Kpi, { description: 'Create a new KPI with the provided details.' })
|
|
26
|
-
async createKpi(
|
|
27
|
-
@Arg('kpi', { description: 'Input object containing details for the new KPI.' }) kpi: NewKpi,
|
|
28
|
-
@Ctx() context: ResolverContext
|
|
29
|
-
): Promise<Kpi> {
|
|
30
|
-
const { domain, user, tx } = context.state
|
|
31
|
-
|
|
32
|
-
let parent = kpi.parent
|
|
33
|
-
? await getRepository(Kpi).findOne({ where: { id: kpi.parent.id, domain: { id: domain.id } } })
|
|
34
|
-
: undefined
|
|
35
|
-
|
|
36
|
-
// 순환 참조 검사
|
|
37
|
-
if (parent) {
|
|
38
|
-
const formulaService = new KpiFormulaService()
|
|
39
|
-
const hasCircular = await formulaService.hasCircularReference(parent.id, parent.id)
|
|
40
|
-
if (hasCircular) {
|
|
41
|
-
throw new Error('Circular reference detected in KPI hierarchy')
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const result = await getRepository(Kpi, tx).save({
|
|
46
|
-
...kpi,
|
|
47
|
-
parent,
|
|
48
|
-
domain,
|
|
49
|
-
creator: user,
|
|
50
|
-
updater: user
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
if (kpi.thumbnail) {
|
|
54
|
-
await createAttachment(
|
|
55
|
-
null,
|
|
56
|
-
{
|
|
57
|
-
attachment: {
|
|
58
|
-
file: kpi.thumbnail,
|
|
59
|
-
refType: Kpi.name,
|
|
60
|
-
refBy: result.id
|
|
61
|
-
}
|
|
62
|
-
},
|
|
63
|
-
context
|
|
64
|
-
)
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
if (kpi.formula) {
|
|
68
|
-
const formulaService = new KpiFormulaService()
|
|
69
|
-
const result = await formulaService.validateFormula(kpi.formula)
|
|
70
|
-
if (!result.valid) {
|
|
71
|
-
throw new Error(result.errors.join('\n'))
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// KPI 스케줄러 등록
|
|
76
|
-
if (kpi.schedule) {
|
|
77
|
-
const handle = await registerSchedule({
|
|
78
|
-
name: result.name,
|
|
79
|
-
client: {
|
|
80
|
-
application: Application,
|
|
81
|
-
group: `${domain.id}`,
|
|
82
|
-
type: 'kpi',
|
|
83
|
-
key: result.id,
|
|
84
|
-
operation: 'schedule'
|
|
85
|
-
},
|
|
86
|
-
type: 'cron',
|
|
87
|
-
schedule: kpi.schedule,
|
|
88
|
-
timezone: kpi.timezone,
|
|
89
|
-
task: {
|
|
90
|
-
type: 'rest',
|
|
91
|
-
connection: {
|
|
92
|
-
host: `${CallbackBase}/callback-schedule-for-kpi`,
|
|
93
|
-
headers: {
|
|
94
|
-
'Content-Type': 'application/json',
|
|
95
|
-
accept: '*/*'
|
|
96
|
-
}
|
|
97
|
-
},
|
|
98
|
-
data: {
|
|
99
|
-
domainId: domain.id,
|
|
100
|
-
kpiId: result.id
|
|
101
|
-
},
|
|
102
|
-
history_check: true,
|
|
103
|
-
failed_policy: 'retry_dlq',
|
|
104
|
-
max_retry_count: 3,
|
|
105
|
-
retry_period: 60
|
|
106
|
-
}
|
|
107
|
-
})
|
|
108
|
-
result.scheduleId = handle
|
|
109
|
-
await getRepository(Kpi, tx).save(result)
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
return result
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
@Directive('@transaction')
|
|
116
|
-
@Directive('@privilege(category: "kpi", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)')
|
|
117
|
-
@Mutation(returns => Kpi, { description: 'To modify Kpi information' })
|
|
118
|
-
async updateKpi(@Arg('id') id: string, @Arg('patch') patch: KpiPatch, @Ctx() context: ResolverContext): Promise<Kpi> {
|
|
119
|
-
const { domain, user, tx } = context.state
|
|
120
|
-
|
|
121
|
-
const repository = getRepository(Kpi, tx)
|
|
122
|
-
const kpi = await repository.findOne({
|
|
123
|
-
where: { domain: { id: domain.id }, id }
|
|
124
|
-
})
|
|
125
|
-
|
|
126
|
-
let parent = patch.parent
|
|
127
|
-
? await getRepository(Kpi).findOne({ where: { id: patch.parent.id, domain: { id: domain.id } } })
|
|
128
|
-
: patch.parent === null ? null : kpi.parent
|
|
129
|
-
|
|
130
|
-
// 순환 참조 검사 (parent가 변경되는 경우)
|
|
131
|
-
if (parent && parent.id !== kpi.parentId) {
|
|
132
|
-
const formulaService = new KpiFormulaService()
|
|
133
|
-
const hasCircular = await formulaService.hasCircularReference(kpi.id, parent.id)
|
|
134
|
-
if (hasCircular) {
|
|
135
|
-
throw new Error('Circular reference detected in KPI hierarchy')
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const result = await repository.save({
|
|
140
|
-
...kpi,
|
|
141
|
-
...patch,
|
|
142
|
-
parent,
|
|
143
|
-
updater: user
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
if (patch.thumbnail) {
|
|
147
|
-
await deleteAttachmentsByRef(null, { refBys: [result.id] }, context)
|
|
148
|
-
await createAttachment(
|
|
149
|
-
null,
|
|
150
|
-
{
|
|
151
|
-
attachment: {
|
|
152
|
-
file: patch.thumbnail,
|
|
153
|
-
refType: Kpi.name,
|
|
154
|
-
refBy: result.id
|
|
155
|
-
}
|
|
156
|
-
},
|
|
157
|
-
context
|
|
158
|
-
)
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
if (patch.formula) {
|
|
162
|
-
const formulaService = new KpiFormulaService()
|
|
163
|
-
const result = await formulaService.validateFormula(patch.formula)
|
|
164
|
-
if (!result.valid) {
|
|
165
|
-
throw new Error(result.errors.join('\n'))
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// KPI 스케줄러 등록/해제 갱신
|
|
170
|
-
if (patch.schedule) {
|
|
171
|
-
// 기존 스케줄러 해제
|
|
172
|
-
if (kpi.scheduleId) {
|
|
173
|
-
await unregisterSchedule(kpi.scheduleId)
|
|
174
|
-
}
|
|
175
|
-
// 새 스케줄러 등록
|
|
176
|
-
const handle = await registerSchedule({
|
|
177
|
-
name: result.name,
|
|
178
|
-
client: {
|
|
179
|
-
application: Application,
|
|
180
|
-
group: `${domain.id}`,
|
|
181
|
-
type: 'kpi',
|
|
182
|
-
key: result.id,
|
|
183
|
-
operation: 'schedule'
|
|
184
|
-
},
|
|
185
|
-
type: 'cron',
|
|
186
|
-
schedule: patch.schedule,
|
|
187
|
-
timezone: patch.timezone,
|
|
188
|
-
task: {
|
|
189
|
-
type: 'rest',
|
|
190
|
-
connection: {
|
|
191
|
-
host: `${CallbackBase}/callback-schedule-for-kpi`,
|
|
192
|
-
headers: {
|
|
193
|
-
'Content-Type': 'application/json',
|
|
194
|
-
accept: '*/*'
|
|
195
|
-
}
|
|
196
|
-
},
|
|
197
|
-
data: {
|
|
198
|
-
domainId: domain.id,
|
|
199
|
-
kpiId: result.id
|
|
200
|
-
},
|
|
201
|
-
history_check: true,
|
|
202
|
-
failed_policy: 'retry_dlq',
|
|
203
|
-
max_retry_count: 3,
|
|
204
|
-
retry_period: 60
|
|
205
|
-
}
|
|
206
|
-
})
|
|
207
|
-
result.scheduleId = handle
|
|
208
|
-
await repository.save(result)
|
|
209
|
-
} else if (patch.schedule === null && kpi.scheduleId) {
|
|
210
|
-
// 스케줄 해제 요청
|
|
211
|
-
await unregisterSchedule(kpi.scheduleId)
|
|
212
|
-
result.scheduleId = null
|
|
213
|
-
await repository.save(result)
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
return result
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
@Directive('@transaction')
|
|
220
|
-
@Directive('@privilege(category: "kpi", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)')
|
|
221
|
-
@Mutation(returns => [Kpi], { description: "To modify multiple Kpis' information" })
|
|
222
|
-
async updateMultipleKpi(
|
|
223
|
-
@Arg('patches', type => [KpiPatch]) patches: KpiPatch[],
|
|
224
|
-
@Ctx() context: ResolverContext
|
|
225
|
-
): Promise<Kpi[]> {
|
|
226
|
-
const { domain, user, tx } = context.state
|
|
227
|
-
|
|
228
|
-
let results = []
|
|
229
|
-
const _createRecords = patches.filter((patch: any) => patch.cuFlag.toUpperCase() === '+')
|
|
230
|
-
const _updateRecords = patches.filter((patch: any) => patch.cuFlag.toUpperCase() === 'M')
|
|
231
|
-
const kpiRepo = getRepository(Kpi, tx)
|
|
232
|
-
|
|
233
|
-
if (_createRecords.length > 0) {
|
|
234
|
-
for (let i = 0; i < _createRecords.length; i++) {
|
|
235
|
-
const newRecord = _createRecords[i]
|
|
236
|
-
|
|
237
|
-
const result = await kpiRepo.save({
|
|
238
|
-
...newRecord,
|
|
239
|
-
domain,
|
|
240
|
-
creator: user,
|
|
241
|
-
updater: user
|
|
242
|
-
})
|
|
243
|
-
|
|
244
|
-
if (newRecord.thumbnail) {
|
|
245
|
-
await createAttachment(
|
|
246
|
-
null,
|
|
247
|
-
{
|
|
248
|
-
attachment: {
|
|
249
|
-
file: newRecord.thumbnail,
|
|
250
|
-
refType: Kpi.name,
|
|
251
|
-
refBy: result.id
|
|
252
|
-
}
|
|
253
|
-
},
|
|
254
|
-
context
|
|
255
|
-
)
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
results.push({ ...result, cuFlag: '+' })
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
if (_updateRecords.length > 0) {
|
|
263
|
-
for (let i = 0; i < _updateRecords.length; i++) {
|
|
264
|
-
const updateRecord = _updateRecords[i]
|
|
265
|
-
const kpi = await kpiRepo.findOneBy({ id: updateRecord.id })
|
|
266
|
-
|
|
267
|
-
const result = await kpiRepo.save({
|
|
268
|
-
...kpi,
|
|
269
|
-
...updateRecord,
|
|
270
|
-
updater: user
|
|
271
|
-
})
|
|
272
|
-
|
|
273
|
-
if (updateRecord.thumbnail) {
|
|
274
|
-
await deleteAttachmentsByRef(null, { refBys: [result.id] }, context)
|
|
275
|
-
await createAttachment(
|
|
276
|
-
null,
|
|
277
|
-
{
|
|
278
|
-
attachment: {
|
|
279
|
-
file: updateRecord.thumbnail,
|
|
280
|
-
refType: Kpi.name,
|
|
281
|
-
refBy: result.id
|
|
282
|
-
}
|
|
283
|
-
},
|
|
284
|
-
context
|
|
285
|
-
)
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
results.push({ ...result, cuFlag: 'M' })
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
return results
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
@Directive('@transaction')
|
|
296
|
-
@Directive('@privilege(category: "kpi", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)')
|
|
297
|
-
@Mutation(returns => Boolean, { description: 'To delete Kpi' })
|
|
298
|
-
async deleteKpi(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<boolean> {
|
|
299
|
-
const { domain, tx } = context.state
|
|
300
|
-
|
|
301
|
-
const kpi = await getRepository(Kpi, tx).findOne({ where: { domain: { id: domain.id }, id } })
|
|
302
|
-
if (kpi && kpi.scheduleId) {
|
|
303
|
-
await unregisterSchedule(kpi.scheduleId)
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
await getRepository(Kpi, tx).delete({ domain: { id: domain.id }, id })
|
|
307
|
-
await deleteAttachmentsByRef(null, { refBys: [id] }, context)
|
|
308
|
-
|
|
309
|
-
return true
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
@Directive('@transaction')
|
|
313
|
-
@Directive('@privilege(category: "kpi", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)')
|
|
314
|
-
@Mutation(returns => Boolean, { description: 'To delete multiple Kpis' })
|
|
315
|
-
async deleteKpis(@Arg('ids', type => [String]) ids: string[], @Ctx() context: ResolverContext): Promise<boolean> {
|
|
316
|
-
const { domain, tx } = context.state
|
|
317
|
-
|
|
318
|
-
await getRepository(Kpi, tx).delete({
|
|
319
|
-
domain: { id: domain.id },
|
|
320
|
-
id: In(ids)
|
|
321
|
-
})
|
|
322
|
-
|
|
323
|
-
await deleteAttachmentsByRef(null, { refBys: ids }, context)
|
|
324
|
-
|
|
325
|
-
return true
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
@Directive('@transaction')
|
|
329
|
-
@Directive('@privilege(category: "kpi", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)')
|
|
330
|
-
@Mutation(returns => Boolean, { description: 'To import multiple Kpis' })
|
|
331
|
-
async importKpis(
|
|
332
|
-
@Arg('kpis', type => [KpiPatch]) kpis: KpiPatch[],
|
|
333
|
-
@Ctx() context: ResolverContext
|
|
334
|
-
): Promise<boolean> {
|
|
335
|
-
const { domain, tx } = context.state
|
|
336
|
-
|
|
337
|
-
await Promise.all(
|
|
338
|
-
kpis.map(async (kpi: KpiPatch) => {
|
|
339
|
-
const createdKpi: Kpi = await getRepository(Kpi, tx).save({ domain, ...kpi })
|
|
340
|
-
})
|
|
341
|
-
)
|
|
342
|
-
|
|
343
|
-
return true
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
@Directive('@transaction')
|
|
347
|
-
@Directive('@privilege(category: "kpi", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)')
|
|
348
|
-
@Mutation(returns => Kpi, { description: 'Release a KPI and create a version history.' })
|
|
349
|
-
async releaseKpi(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<Kpi> {
|
|
350
|
-
const { domain, user, tx } = context.state
|
|
351
|
-
const repository = getRepository(Kpi, tx)
|
|
352
|
-
const historyRepository = getRepository(KpiHistory, tx)
|
|
353
|
-
|
|
354
|
-
const kpi = await repository.findOne({
|
|
355
|
-
where: { domain: { id: domain.id }, id }
|
|
356
|
-
})
|
|
357
|
-
if (!kpi) throw `KPI given id(${id}) is not found`
|
|
358
|
-
if (kpi.state == 'RELEASE') throw `KPI given id(${id}) is already released`
|
|
359
|
-
|
|
360
|
-
// 히스토리에서 max version 가져오기
|
|
361
|
-
const maxHistory = await historyRepository
|
|
362
|
-
.createQueryBuilder('history')
|
|
363
|
-
.select('MAX(history.version)', 'max')
|
|
364
|
-
.where('history.originalId = :id', { id: kpi.id })
|
|
365
|
-
.getRawOne()
|
|
366
|
-
const nextVersion = maxHistory?.max ? Number(maxHistory.max) + 1 : 1
|
|
367
|
-
|
|
368
|
-
const updated = await repository.save({
|
|
369
|
-
...kpi,
|
|
370
|
-
version: nextVersion,
|
|
371
|
-
state: 'RELEASE',
|
|
372
|
-
updater: user
|
|
373
|
-
} as any)
|
|
374
|
-
return updated
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
@Directive('@transaction')
|
|
378
|
-
@Directive('@privilege(category: "kpi", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)')
|
|
379
|
-
@Mutation(returns => Kpi, { description: 'Revert a KPI to a specific historical version.' })
|
|
380
|
-
async revertKpiVersion(
|
|
381
|
-
@Arg('id') id: string,
|
|
382
|
-
@Arg('version') version: number,
|
|
383
|
-
@Ctx() context: ResolverContext
|
|
384
|
-
): Promise<Kpi> {
|
|
385
|
-
const { domain, user, tx } = context.state
|
|
386
|
-
const repository = getRepository(Kpi, tx)
|
|
387
|
-
const historyRepository = getRepository(KpiHistory, tx)
|
|
388
|
-
|
|
389
|
-
const kpi = await repository.findOne({
|
|
390
|
-
where: { domain: { id: domain.id }, id }
|
|
391
|
-
})
|
|
392
|
-
if (!kpi) throw `KPI with id(${id}) is not found`
|
|
393
|
-
|
|
394
|
-
const kpiHistory = await historyRepository.findOne({
|
|
395
|
-
where: { domain: { id: domain.id }, originalId: id, version },
|
|
396
|
-
order: { version: 'DESC' }
|
|
397
|
-
})
|
|
398
|
-
if (!kpiHistory) throw `KPI with id:version(${id}:${version}) is not found`
|
|
399
|
-
|
|
400
|
-
const updated = await repository.save({
|
|
401
|
-
...kpi,
|
|
402
|
-
name: kpiHistory.name,
|
|
403
|
-
description: kpiHistory.description,
|
|
404
|
-
formula: kpiHistory.formula,
|
|
405
|
-
active: kpiHistory.active,
|
|
406
|
-
state: 'DRAFT',
|
|
407
|
-
thumbnail: kpiHistory.thumbnail,
|
|
408
|
-
updater: user
|
|
409
|
-
} as any)
|
|
410
|
-
return updated
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
@Directive('@transaction')
|
|
414
|
-
@Directive('@privilege(category: "kpi", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)')
|
|
415
|
-
@Mutation(returns => KpiValue, { description: 'KPI 기준으로 formula 계산 및 KPI Value upsert' })
|
|
416
|
-
async calculateKpiValue(
|
|
417
|
-
@Arg('kpiId') kpiId: string,
|
|
418
|
-
@Arg('valueDate', { nullable: true }) valueDate: string,
|
|
419
|
-
@Arg('org', { nullable: true }) org: string,
|
|
420
|
-
@Ctx() context: ResolverContext
|
|
421
|
-
): Promise<KpiValue> {
|
|
422
|
-
const { domain, user, tx } = context.state
|
|
423
|
-
const kpiRepo = getRepository(Kpi, tx)
|
|
424
|
-
const metricRepo = getRepository<KpiMetric>(require('../kpi-metric/kpi-metric').KpiMetric, tx)
|
|
425
|
-
const metricValueRepo = getRepository<KpiMetricValue>(
|
|
426
|
-
require('../kpi-metric-value/kpi-metric-value').KpiMetricValue,
|
|
427
|
-
tx
|
|
428
|
-
)
|
|
429
|
-
const kpiValueRepo = getRepository<KpiValue>(KpiValue, tx)
|
|
430
|
-
const { KpiPeriodType } = require('./kpi')
|
|
431
|
-
const { getDefaultValueDate } = require('../utils/value-date-util')
|
|
432
|
-
|
|
433
|
-
// 1. KPI 정보 조회
|
|
434
|
-
const kpi = await kpiRepo.findOne({ where: { id: kpiId, domain: { id: domain.id } } })
|
|
435
|
-
if (!kpi) throw new Error('KPI 정보 없음')
|
|
436
|
-
if (!kpi.formula) throw new Error('KPI formula 없음')
|
|
437
|
-
const periodType = kpi.periodType || KpiPeriodType.DAY
|
|
438
|
-
const valueDateToUse = valueDate || getDefaultValueDate(periodType, 'last')
|
|
439
|
-
|
|
440
|
-
// 2. formula에서 metric code 추출
|
|
441
|
-
const metricCodes = (kpi.formula.match(/[a-zA-Z_][a-zA-Z0-9_]*/g) || []).filter(
|
|
442
|
-
code => code !== 'null' && code !== 'undefined'
|
|
443
|
-
)
|
|
444
|
-
const metricMap: Record<string, any> = {}
|
|
445
|
-
for (const code of metricCodes) {
|
|
446
|
-
const metric = await metricRepo.findOne({ where: { name: code, domain: { id: domain.id } } })
|
|
447
|
-
if (!metric) throw new Error(`KPI formula metric '${code}' not found`)
|
|
448
|
-
metricMap[code] = metric
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
// 3. metric 값 집계 (periodType 변환/집계)
|
|
452
|
-
const metricValues: Record<string, number> = {}
|
|
453
|
-
for (const code of metricCodes) {
|
|
454
|
-
const metric = metricMap[code]
|
|
455
|
-
let value = null
|
|
456
|
-
if (metric.periodType === periodType) {
|
|
457
|
-
const mv = await metricValueRepo.findOne({
|
|
458
|
-
where: {
|
|
459
|
-
metric: { id: metric.id },
|
|
460
|
-
valueDate: valueDateToUse,
|
|
461
|
-
periodType,
|
|
462
|
-
org: org ?? '',
|
|
463
|
-
domain: { id: domain.id }
|
|
464
|
-
}
|
|
465
|
-
})
|
|
466
|
-
value = mv?.value ?? null
|
|
467
|
-
} else {
|
|
468
|
-
let startDate: string, endDate: string
|
|
469
|
-
if (periodType === KpiPeriodType.MONTH && metric.periodType === KpiPeriodType.DAY) {
|
|
470
|
-
startDate = valueDateToUse + '-01'
|
|
471
|
-
endDate = valueDateToUse + '-31'
|
|
472
|
-
const mvs = await metricValueRepo.find({
|
|
473
|
-
where: {
|
|
474
|
-
metric: { id: metric.id },
|
|
475
|
-
valueDate: Between(startDate, endDate),
|
|
476
|
-
periodType: metric.periodType,
|
|
477
|
-
org: org ?? '',
|
|
478
|
-
domain: { id: domain.id }
|
|
479
|
-
}
|
|
480
|
-
})
|
|
481
|
-
value = mvs.reduce((sum, mv) => sum + (mv.value ?? 0), 0)
|
|
482
|
-
} else {
|
|
483
|
-
throw new Error('KPI/Metric periodType 조합 집계 미지원')
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
if (value == null) throw new Error(`Metric '${code}' 값 없음`)
|
|
487
|
-
metricValues[code] = value
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
// 4. formula 계산 (calculator 기반)
|
|
491
|
-
const ast = parseFormula(kpi.formula)
|
|
492
|
-
const provider = new KpiMetricValueProvider({
|
|
493
|
-
valueDate: valueDateToUse,
|
|
494
|
-
org,
|
|
495
|
-
domainId: domain.id,
|
|
496
|
-
tx
|
|
497
|
-
})
|
|
498
|
-
const evalContext = { functions: builtinFunctions, provider }
|
|
499
|
-
const kpiValueResult = await evaluateFormula(ast, evalContext)
|
|
500
|
-
|
|
501
|
-
// 5. KpiOrgScope 처리
|
|
502
|
-
let kpiOrgScope: KpiOrgScope | null = null
|
|
503
|
-
if (org) {
|
|
504
|
-
kpiOrgScope = await getRepository(KpiOrgScope, tx).findOne({
|
|
505
|
-
where: { org: org, domain: { id: domain.id } }
|
|
506
|
-
})
|
|
507
|
-
if (!kpiOrgScope) {
|
|
508
|
-
// 새 KpiOrgScope 생성
|
|
509
|
-
kpiOrgScope = await getRepository(KpiOrgScope, tx).save({
|
|
510
|
-
entityType: 'KpiCalculation',
|
|
511
|
-
entityId: `calc-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
512
|
-
entityName: org,
|
|
513
|
-
org: org,
|
|
514
|
-
domain,
|
|
515
|
-
creator: user,
|
|
516
|
-
updater: user
|
|
517
|
-
})
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
// 6. KPI Value upsert
|
|
522
|
-
let kpiValue = await kpiValueRepo.findOne({
|
|
523
|
-
where: {
|
|
524
|
-
kpi: { id: kpi.id },
|
|
525
|
-
valueDate: valueDateToUse,
|
|
526
|
-
kpiOrgScope: kpiOrgScope ? { id: kpiOrgScope.id } : null,
|
|
527
|
-
version: kpi.version,
|
|
528
|
-
domain: { id: domain.id }
|
|
529
|
-
}
|
|
530
|
-
})
|
|
531
|
-
if (kpiValue) {
|
|
532
|
-
kpiValue.value = kpiValueResult
|
|
533
|
-
kpiValue.updater = user
|
|
534
|
-
} else {
|
|
535
|
-
const createData: any = {
|
|
536
|
-
kpi,
|
|
537
|
-
kpiId: kpi.id,
|
|
538
|
-
version: kpi.version,
|
|
539
|
-
valueDate: valueDateToUse,
|
|
540
|
-
value: kpiValueResult,
|
|
541
|
-
domain,
|
|
542
|
-
creator: user,
|
|
543
|
-
updater: user
|
|
544
|
-
}
|
|
545
|
-
if (kpiOrgScope) {
|
|
546
|
-
createData.kpiOrgScope = kpiOrgScope
|
|
547
|
-
createData.kpiOrgScopeId = kpiOrgScope.id
|
|
548
|
-
}
|
|
549
|
-
kpiValue = Object.assign(new KpiValue(), createData)
|
|
550
|
-
}
|
|
551
|
-
return await kpiValueRepo.save(kpiValue)
|
|
552
|
-
}
|
|
553
|
-
}
|