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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/client/pages/kpi/kpi-list-page.ts +339 -525
  2. package/client/pages/kpi/kpi-tree-page.ts +135 -207
  3. package/client/pages/kpi-metric/kpi-metric-list-page.ts +146 -226
  4. package/client/pages/kpi-metric-value/kpi-metric-value-editor-page.ts +187 -295
  5. package/client/pages/kpi-metric-value/kpi-metric-value-list-page.ts +123 -194
  6. package/client/pages/kpi-metric-value/kpi-metric-value-manual-entry-page.ts +57 -91
  7. package/client/pages/kpi-statistic/kpi-statistic-editor-page.ts +180 -278
  8. package/client/pages/kpi-statistic/kpi-statistic-list-page.ts +186 -286
  9. package/client/pages/kpi-value/kpi-value-editor-page.ts +189 -292
  10. package/client/pages/kpi-value/kpi-value-list-page.ts +170 -264
  11. package/dist-client/pages/kpi/kpi-list-page.d.ts +0 -6
  12. package/dist-client/pages/kpi/kpi-list-page.js +150 -282
  13. package/dist-client/pages/kpi/kpi-list-page.js.map +1 -1
  14. package/dist-client/pages/kpi/kpi-tree-page.d.ts +1 -7
  15. package/dist-client/pages/kpi/kpi-tree-page.js +76 -127
  16. package/dist-client/pages/kpi/kpi-tree-page.js.map +1 -1
  17. package/dist-client/pages/kpi-metric/kpi-metric-list-page.d.ts +0 -6
  18. package/dist-client/pages/kpi-metric/kpi-metric-list-page.js +62 -116
  19. package/dist-client/pages/kpi-metric/kpi-metric-list-page.js.map +1 -1
  20. package/dist-client/pages/kpi-metric-value/kpi-metric-value-editor-page.d.ts +1 -7
  21. package/dist-client/pages/kpi-metric-value/kpi-metric-value-editor-page.js +82 -140
  22. package/dist-client/pages/kpi-metric-value/kpi-metric-value-editor-page.js.map +1 -1
  23. package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.d.ts +0 -6
  24. package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.js +54 -98
  25. package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.js.map +1 -1
  26. package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-page.d.ts +1 -7
  27. package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-page.js +30 -57
  28. package/dist-client/pages/kpi-metric-value/kpi-metric-value-manual-entry-page.js.map +1 -1
  29. package/dist-client/pages/kpi-statistic/kpi-statistic-editor-page.d.ts +1 -7
  30. package/dist-client/pages/kpi-statistic/kpi-statistic-editor-page.js +91 -153
  31. package/dist-client/pages/kpi-statistic/kpi-statistic-editor-page.js.map +1 -1
  32. package/dist-client/pages/kpi-statistic/kpi-statistic-list-page.d.ts +0 -6
  33. package/dist-client/pages/kpi-statistic/kpi-statistic-list-page.js +81 -155
  34. package/dist-client/pages/kpi-statistic/kpi-statistic-list-page.js.map +1 -1
  35. package/dist-client/pages/kpi-value/kpi-value-editor-page.d.ts +1 -7
  36. package/dist-client/pages/kpi-value/kpi-value-editor-page.js +80 -136
  37. package/dist-client/pages/kpi-value/kpi-value-editor-page.js.map +1 -1
  38. package/dist-client/pages/kpi-value/kpi-value-list-page.d.ts +0 -6
  39. package/dist-client/pages/kpi-value/kpi-value-list-page.js +73 -134
  40. package/dist-client/pages/kpi-value/kpi-value-list-page.js.map +1 -1
  41. package/dist-client/tsconfig.tsbuildinfo +1 -1
  42. package/dist-server/tsconfig.tsbuildinfo +1 -1
  43. package/package.json +18 -18
  44. package/client/tsconfig.json +0 -11
  45. package/dist-server/tsconfig.json +0 -10
  46. package/server/@types/index.d.ts +0 -11
  47. package/server/calculator/evaluator.ts +0 -45
  48. package/server/calculator/functions.ts +0 -67
  49. package/server/calculator/index.ts +0 -4
  50. package/server/calculator/parser.ts +0 -137
  51. package/server/calculator/provider.ts +0 -10
  52. package/server/controllers/index.ts +0 -2
  53. package/server/controllers/kpi-metric-value-provider.ts +0 -79
  54. package/server/controllers/kpi-value-provider.ts +0 -51
  55. package/server/index.ts +0 -6
  56. package/server/migrations/1752190849680-seed-kpi-metrics.ts +0 -124
  57. package/server/migrations/1752190849681-seed-kpi.ts +0 -356
  58. package/server/migrations/1752192090123-add-grades-to-kpi.ts +0 -67
  59. package/server/migrations/1752192090124-add-kpi-statistics.ts +0 -719
  60. package/server/migrations/1752192090128-seed-kpi-org-scope.ts +0 -132
  61. package/server/migrations/1752192090129-seed-kpi-values.ts +0 -207
  62. package/server/migrations/grade-data/x11-performance-table.json +0 -962
  63. package/server/migrations/grade-data/x12-performance-table.json +0 -611
  64. package/server/migrations/grade-data/x14-performance-table.json +0 -42
  65. package/server/migrations/grade-data/x21-performance-table.json +0 -889
  66. package/server/migrations/grade-data/x22-performance-table.json +0 -1064
  67. package/server/migrations/grade-data/x23-performance-table.json +0 -42
  68. package/server/migrations/grade-data/x31-performance-table.json +0 -644
  69. package/server/migrations/grade-data/x32-performance-table.json +0 -993
  70. package/server/migrations/grade-data/x33-performance-table.json +0 -195
  71. package/server/migrations/grade-data/x34-performance-table.json +0 -12
  72. package/server/migrations/grade-data/x35-performance-table.json +0 -42
  73. package/server/migrations/grade-data/x41-performance-table.json +0 -825
  74. package/server/migrations/grade-data/x42-performance-table.json +0 -786
  75. package/server/migrations/grade-data/x43-performance-table.json +0 -12
  76. package/server/migrations/grade-data/x44-performance-table.json +0 -42
  77. package/server/migrations/grade-data/x51-performance-table.json +0 -924
  78. package/server/migrations/grade-data/x52-performance-table.json +0 -42
  79. package/server/migrations/grade-data/x61-performance-table.json +0 -261
  80. package/server/migrations/grade-data/x62-performance-table.json +0 -42
  81. package/server/migrations/index.ts +0 -9
  82. package/server/migrations/seed-data/kpi-metrics-seed.json +0 -454
  83. package/server/migrations/seed-data/kpi-org-scope-seed.json +0 -1676
  84. package/server/migrations/seed-data/kpi-scopes-seed.json +0 -121
  85. package/server/migrations/seed-data/kpi-values-seed.json +0 -402
  86. package/server/migrations/seed-data/kpis-seed.json +0 -488
  87. package/server/migrations/seed-data/scope-definitions-seed.json +0 -90
  88. package/server/routes.ts +0 -81
  89. package/server/service/index.ts +0 -51
  90. package/server/service/kpi/aggregate-kpi.ts +0 -103
  91. package/server/service/kpi/event-subscriber.ts +0 -29
  92. package/server/service/kpi/index.ts +0 -9
  93. package/server/service/kpi/kpi-formula.service.ts +0 -164
  94. package/server/service/kpi/kpi-grade.types.ts +0 -28
  95. package/server/service/kpi/kpi-history.ts +0 -126
  96. package/server/service/kpi/kpi-mutation.ts +0 -553
  97. package/server/service/kpi/kpi-query.ts +0 -224
  98. package/server/service/kpi/kpi-type.ts +0 -151
  99. package/server/service/kpi/kpi.ts +0 -254
  100. package/server/service/kpi-alert/index.ts +0 -3
  101. package/server/service/kpi-alert/kpi-alert-query.ts +0 -59
  102. package/server/service/kpi-alert/kpi-alert-type.ts +0 -20
  103. package/server/service/kpi-metric/aggregate-kpi-metric.ts +0 -132
  104. package/server/service/kpi-metric/index.ts +0 -7
  105. package/server/service/kpi-metric/kpi-metric-mutation.ts +0 -309
  106. package/server/service/kpi-metric/kpi-metric-query.ts +0 -70
  107. package/server/service/kpi-metric/kpi-metric-type.ts +0 -111
  108. package/server/service/kpi-metric/kpi-metric.ts +0 -134
  109. package/server/service/kpi-metric-value/index.ts +0 -7
  110. package/server/service/kpi-metric-value/kpi-metric-value-mutation.ts +0 -270
  111. package/server/service/kpi-metric-value/kpi-metric-value-query.ts +0 -62
  112. package/server/service/kpi-metric-value/kpi-metric-value-type.ts +0 -82
  113. package/server/service/kpi-metric-value/kpi-metric-value.ts +0 -93
  114. package/server/service/kpi-org-scope/index.ts +0 -6
  115. package/server/service/kpi-org-scope/kpi-org-scope-mutation.ts +0 -173
  116. package/server/service/kpi-org-scope/kpi-org-scope-query.ts +0 -127
  117. package/server/service/kpi-org-scope/kpi-org-scope-type.ts +0 -68
  118. package/server/service/kpi-org-scope/kpi-org-scope.ts +0 -123
  119. package/server/service/kpi-scope/index.ts +0 -11
  120. package/server/service/kpi-scope/kpi-scope-mutation.ts +0 -129
  121. package/server/service/kpi-scope/kpi-scope-query.ts +0 -63
  122. package/server/service/kpi-scope/kpi-scope-type.ts +0 -96
  123. package/server/service/kpi-scope/kpi-scope.ts +0 -143
  124. package/server/service/kpi-statistic/index.ts +0 -7
  125. package/server/service/kpi-statistic/kpi-statistic-batch.service.ts +0 -231
  126. package/server/service/kpi-statistic/kpi-statistic-calculation.service.ts +0 -410
  127. package/server/service/kpi-statistic/kpi-statistic-mutation.ts +0 -291
  128. package/server/service/kpi-statistic/kpi-statistic-query.ts +0 -146
  129. package/server/service/kpi-statistic/kpi-statistic-type.ts +0 -152
  130. package/server/service/kpi-statistic/kpi-statistic.ts +0 -199
  131. package/server/service/kpi-value/index.ts +0 -7
  132. package/server/service/kpi-value/kpi-value-mutation.ts +0 -432
  133. package/server/service/kpi-value/kpi-value-query.ts +0 -61
  134. package/server/service/kpi-value/kpi-value-score.service.ts +0 -106
  135. package/server/service/kpi-value/kpi-value-type.ts +0 -122
  136. package/server/service/kpi-value/kpi-value.ts +0 -160
  137. package/server/service/utils/value-date-util.ts +0 -119
  138. package/server/tsconfig.json +0 -10
  139. package/server/types/global.d.ts +0 -8
@@ -1,20 +0,0 @@
1
- import { ObjectType, Field, ID } from 'type-graphql'
2
- import { Kpi } from '../kpi/kpi'
3
-
4
- @ObjectType({ description: 'KPI 경고/알림 엔티티. KPI 실적, 등급, 목표 미달 등 경고/알림 정보를 제공.' })
5
- export class KpiAlert {
6
- @Field(type => ID)
7
- id: string
8
-
9
- @Field(type => Kpi, { nullable: true, description: '경고/알림이 발생한 KPI.' })
10
- kpi?: Kpi
11
-
12
- @Field({ description: '경고/알림 메시지.' })
13
- message: string
14
-
15
- @Field({ description: '경고 레벨(예: warning, critical, info 등).' })
16
- level: string
17
-
18
- @Field({ description: '경고/알림 발생 시각.' })
19
- createdAt: Date
20
- }
@@ -1,132 +0,0 @@
1
- import { getRepository } from '@things-factory/shell'
2
- import type ResolverContext from '@things-factory/auth-base'
3
- import { DataSummary } from '@things-factory/dataset'
4
- import { finalizeLatestDataCollection } from '@things-factory/dataset/dist-server/controllers/finalize-data-collection'
5
- import { KpiMetric } from './kpi-metric'
6
- import { KpiValue, KpiValueInputType } from '../kpi-value/kpi-value'
7
- import { Kpi } from '../kpi/kpi'
8
- import { KpiOrgScope } from '../kpi-org-scope/kpi-org-scope'
9
- import { KpiFormulaService } from '../kpi/kpi-formula.service'
10
-
11
- /**
12
- * KPI-Metric 집계/자동화 서비스 함수 (formula 지원, KPI Value 저장)
13
- * @param metricId KPI-Metric ID
14
- * @param domainId 도메인 ID
15
- * @param context ResolverContext (state에 domain, user, tx 등 포함)
16
- * @returns 저장된 KPI Value 배열
17
- */
18
- export async function aggregateKpiMetricValue(metricId: string, domainId: string, context: ResolverContext) {
19
- const tx = context.state?.tx || getRepository(KpiMetric).manager
20
-
21
- // 1. metric 정보 조회
22
- const metric = await getRepository(KpiMetric).findOne({
23
- where: { id: metricId, domain: { id: domainId } }
24
- })
25
- if (!metric) throw new Error('Metric 정보 없음')
26
- if (!metric.active) throw new Error('비활성화된 Metric')
27
-
28
- // formula 분기 제거: metric은 항상 dataset 집계만 수행
29
- let values: any[] = []
30
- if (!metric.dataSetId || !metric.fieldName) throw new Error('Metric 매핑 정보 누락')
31
- await finalizeLatestDataCollection(metric.dataSetId, context)
32
- const summaries = await getRepository(DataSummary).find({
33
- where: { dataSet: { id: metric.dataSetId }, domain: { id: domainId } },
34
- order: { date: 'DESC', period: 'DESC' }
35
- })
36
- values = summaries
37
- .map(summary => {
38
- if (summary.summary && metric.fieldName in summary.summary) {
39
- return {
40
- date: summary.date,
41
- period: summary.period,
42
- value: summary.summary[metric.fieldName],
43
- org: {
44
- key01: summary.key01,
45
- key02: summary.key02,
46
- key03: summary.key03,
47
- key04: summary.key04,
48
- key05: summary.key05
49
- }
50
- }
51
- }
52
- return null
53
- })
54
- .filter(Boolean)
55
-
56
- // 5. KPI Value로 저장 (metric 단위, KPI 단위 formula는 후속)
57
- // metric이 속한 KPI 정보 조회 (여기서는 metric.name == kpi.name인 KPI를 임시로 매핑, 실제 연동 구조에 맞게 보완 필요)
58
- const kpi = await getRepository(Kpi).findOne({ where: { name: metric.name, domain: { id: domainId } } })
59
- if (!kpi) throw new Error('KPI 정보 없음 (metric.name과 동일한 KPI name 기준, 실제 연동 구조에 맞게 보완 필요)')
60
-
61
- // KPI Value version, valueDate, org 등 매핑
62
- const savedValues = []
63
- const kpiOrgScopeRepo = getRepository(KpiOrgScope, context.state?.tx)
64
- const kpiValueRepo = getRepository(KpiValue, context.state?.tx)
65
-
66
- for (const v of values) {
67
- const valueDate = v.date
68
- const orgData = v.org
69
- const version = kpi.version || 1
70
- const value = v.value
71
- if (value == null || isNaN(value)) continue
72
-
73
- // KpiOrgScope 찾기 또는 생성
74
- let kpiOrgScope: KpiOrgScope | null = null
75
-
76
- // key들로 조합된 org 식별자 생성
77
- const orgIdentifier =
78
- [orgData.key01, orgData.key02, orgData.key03, orgData.key04, orgData.key05].filter(Boolean).join('-') ||
79
- 'unknown-org'
80
-
81
- // 기존 KpiOrgScope 조회 (entityName 기준)
82
- kpiOrgScope = await kpiOrgScopeRepo.findOne({
83
- where: { entityName: orgIdentifier, domain: { id: domainId } }
84
- })
85
-
86
- // 없으면 새로 생성
87
- if (!kpiOrgScope) {
88
- kpiOrgScope = await kpiOrgScopeRepo.save({
89
- entityType: 'DataSummary', // DataSummary에서 온 데이터임을 표시
90
- entityId: `datasummary-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
91
- entityName: orgIdentifier,
92
- org: orgIdentifier, // legacy field
93
- scope01: orgData.key01,
94
- scope02: orgData.key02,
95
- scope03: orgData.key03,
96
- scope04: orgData.key04,
97
- scope05: orgData.key05,
98
- domain: kpi.domain,
99
- creator: context.state?.user,
100
- updater: context.state?.user
101
- })
102
- }
103
-
104
- // upsert KpiValue (동일 KPI, valueDate, kpiOrgScope, version 기준)
105
- const existing = await kpiValueRepo.findOne({
106
- where: {
107
- kpi: { id: kpi.id },
108
- valueDate,
109
- kpiOrgScope: { id: kpiOrgScope.id },
110
- version,
111
- domain: { id: domainId }
112
- }
113
- })
114
-
115
- let entity = existing || kpiValueRepo.create()
116
- entity.kpi = kpi
117
- entity.kpiId = kpi.id
118
- entity.version = version
119
- entity.valueDate = valueDate
120
- entity.value = value
121
- entity.kpiOrgScope = kpiOrgScope
122
- entity.kpiOrgScopeId = kpiOrgScope.id
123
- entity.inputType = KpiValueInputType.AUTO
124
- entity.source = 'AUTO-AGGREGATE'
125
- entity.domain = kpi.domain
126
- entity.creator = context.state?.user
127
- entity.updater = context.state?.user
128
- entity = await kpiValueRepo.save(entity)
129
- savedValues.push(entity)
130
- }
131
- return savedValues
132
- }
@@ -1,7 +0,0 @@
1
- import { KpiMetric } from './kpi-metric'
2
- import { KpiMetricQuery } from './kpi-metric-query'
3
- import { KpiMetricMutation } from './kpi-metric-mutation'
4
-
5
- export const entities = [KpiMetric]
6
- export const resolvers = [KpiMetricQuery, KpiMetricMutation]
7
- export const subscribers = []
@@ -1,309 +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
- import { DataSet } from '@things-factory/dataset'
5
-
6
- import { createAttachment, deleteAttachmentsByRef } from '@things-factory/attachment-base'
7
- import { KpiMetric } from './kpi-metric'
8
- import { NewKpiMetric, KpiMetricPatch } from './kpi-metric-type'
9
- import { KpiFormulaService } from '../kpi/kpi-formula.service'
10
- import { registerSchedule, unregisterSchedule, CallbackBase, Application } from '@things-factory/scheduler-client'
11
-
12
- @Resolver(KpiMetric)
13
- export class KpiMetricMutation {
14
- @Directive('@transaction')
15
- @Directive('@privilege(category: "kpi", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)')
16
- @Mutation(returns => KpiMetric, { description: 'Create a new KPI metric with the provided details.' })
17
- async createKpiMetric(
18
- @Arg('kpiMetric', { description: 'Input object containing details for the new KPI metric.' })
19
- kpiMetric: NewKpiMetric,
20
- @Ctx() context: ResolverContext
21
- ): Promise<KpiMetric> {
22
- const { domain, user, tx } = context.state
23
-
24
- let dataSet = kpiMetric.dataSetId
25
- ? await getRepository(DataSet).findOne({ where: { id: kpiMetric.dataSetId } })
26
- : undefined
27
-
28
- // formula 관련 로직 완전 제거
29
-
30
- const result = await getRepository(KpiMetric, tx).save({
31
- ...kpiMetric,
32
- dataSet,
33
- domain,
34
- creator: user,
35
- updater: user
36
- })
37
-
38
- // 스케줄러 등록
39
- if (kpiMetric.schedule) {
40
- const handle = await registerSchedule({
41
- name: kpiMetric.name,
42
- client: {
43
- application: Application,
44
- group: `${domain.id}`,
45
- type: 'kpi-metric',
46
- key: result.id,
47
- operation: 'aggregate'
48
- },
49
- type: 'cron',
50
- schedule: kpiMetric.schedule,
51
- timezone: kpiMetric.timezone,
52
- task: {
53
- type: 'rest',
54
- connection: {
55
- host: `${CallbackBase}/callback-schedule-for-kpi-metric`,
56
- headers: {
57
- 'Content-Type': 'application/json',
58
- accept: '*/*'
59
- }
60
- },
61
- data: {
62
- domainId: domain.id,
63
- metricId: result.id
64
- },
65
- history_check: true,
66
- failed_policy: 'retry_dlq',
67
- max_retry_count: 3,
68
- retry_period: 60
69
- }
70
- })
71
- result.scheduleId = handle
72
- result.timezone = kpiMetric.timezone
73
- await getRepository(KpiMetric, tx).save(result)
74
- }
75
-
76
- if (kpiMetric.thumbnail) {
77
- await createAttachment(
78
- null,
79
- {
80
- attachment: {
81
- file: kpiMetric.thumbnail,
82
- refType: KpiMetric.name,
83
- refBy: result.id
84
- }
85
- },
86
- context
87
- )
88
- }
89
-
90
- return result
91
- }
92
-
93
- @Directive('@transaction')
94
- @Directive('@privilege(category: "kpi", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)')
95
- @Mutation(returns => KpiMetric, { description: 'To modify KpiMetric information' })
96
- async updateKpiMetric(
97
- @Arg('id') id: string,
98
- @Arg('patch') patch: KpiMetricPatch,
99
- @Ctx() context: ResolverContext
100
- ): Promise<KpiMetric> {
101
- const { domain, user, tx } = context.state
102
-
103
- const repository = getRepository(KpiMetric, tx)
104
- const kpiMetric = await repository.findOne({
105
- where: { domain: { id: domain.id }, id }
106
- })
107
-
108
- let dataSet = patch.dataSetId
109
- ? await getRepository(DataSet).findOne({ where: { id: patch.dataSetId } })
110
- : kpiMetric.dataSet
111
-
112
- // formula 관련 로직 완전 제거
113
-
114
- const result = await repository.save({
115
- ...kpiMetric,
116
- ...patch,
117
- dataSet,
118
- updater: user
119
- })
120
-
121
- // 스케줄러 해제/등록 (변경 시)
122
- if (kpiMetric.scheduleId && (patch.scheduleId !== kpiMetric.scheduleId || !patch.scheduleId)) {
123
- await unregisterSchedule(kpiMetric.scheduleId)
124
- result.scheduleId = null
125
- }
126
- if (patch.schedule) {
127
- const handle = await registerSchedule({
128
- name: patch.name || kpiMetric.name,
129
- client: {
130
- application: Application,
131
- group: `${domain.id}`,
132
- type: 'kpi-metric',
133
- key: result.id,
134
- operation: 'aggregate'
135
- },
136
- type: 'cron',
137
- schedule: patch.schedule,
138
- timezone: patch.timezone,
139
- task: {
140
- type: 'rest',
141
- connection: {
142
- host: `${CallbackBase}/callback-schedule-for-kpi-metric`,
143
- headers: {
144
- 'Content-Type': 'application/json',
145
- accept: '*/*'
146
- }
147
- },
148
- data: {
149
- domainId: domain.id,
150
- metricId: result.id
151
- },
152
- history_check: true,
153
- failed_policy: 'retry_dlq',
154
- max_retry_count: 3,
155
- retry_period: 60
156
- }
157
- })
158
- result.scheduleId = handle
159
- result.timezone = patch.timezone
160
- await repository.save(result)
161
- }
162
-
163
- if (patch.thumbnail) {
164
- await deleteAttachmentsByRef(null, { refBys: [result.id] }, context)
165
- await createAttachment(
166
- null,
167
- {
168
- attachment: {
169
- file: patch.thumbnail,
170
- refType: KpiMetric.name,
171
- refBy: result.id
172
- }
173
- },
174
- context
175
- )
176
- }
177
-
178
- return result
179
- }
180
-
181
- @Directive('@transaction')
182
- @Directive('@privilege(category: "kpi", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)')
183
- @Mutation(returns => [KpiMetric], { description: "To modify multiple KpiMetrics' information" })
184
- async updateMultipleKpiMetric(
185
- @Arg('patches', type => [KpiMetricPatch]) patches: KpiMetricPatch[],
186
- @Ctx() context: ResolverContext
187
- ): Promise<KpiMetric[]> {
188
- const { domain, user, tx } = context.state
189
-
190
- let results = []
191
- const _createRecords = patches.filter((patch: any) => patch.cuFlag.toUpperCase() === '+')
192
- const _updateRecords = patches.filter((patch: any) => patch.cuFlag.toUpperCase() === 'M')
193
- const kpiMetricRepo = getRepository(KpiMetric, tx)
194
-
195
- if (_createRecords.length > 0) {
196
- for (let i = 0; i < _createRecords.length; i++) {
197
- const newRecord = _createRecords[i]
198
-
199
- const result = await kpiMetricRepo.save({
200
- ...newRecord,
201
- domain,
202
- creator: user,
203
- updater: user
204
- })
205
-
206
- if (newRecord.thumbnail) {
207
- await createAttachment(
208
- null,
209
- {
210
- attachment: {
211
- file: newRecord.thumbnail,
212
- refType: KpiMetric.name,
213
- refBy: result.id
214
- }
215
- },
216
- context
217
- )
218
- }
219
-
220
- results.push({ ...result, cuFlag: '+' })
221
- }
222
- }
223
-
224
- if (_updateRecords.length > 0) {
225
- for (let i = 0; i < _updateRecords.length; i++) {
226
- const updateRecord = _updateRecords[i]
227
- const kpiMetric = await kpiMetricRepo.findOneBy({ id: updateRecord.id })
228
-
229
- const result = await kpiMetricRepo.save({
230
- ...kpiMetric,
231
- ...updateRecord,
232
- updater: user
233
- })
234
-
235
- if (updateRecord.thumbnail) {
236
- await deleteAttachmentsByRef(null, { refBys: [result.id] }, context)
237
- await createAttachment(
238
- null,
239
- {
240
- attachment: {
241
- file: updateRecord.thumbnail,
242
- refType: KpiMetric.name,
243
- refBy: result.id
244
- }
245
- },
246
- context
247
- )
248
- }
249
-
250
- results.push({ ...result, cuFlag: 'M' })
251
- }
252
- }
253
-
254
- return results
255
- }
256
-
257
- @Directive('@transaction')
258
- @Directive('@privilege(category: "kpi", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)')
259
- @Mutation(returns => Boolean, { description: 'To delete KpiMetric' })
260
- async deleteKpiMetric(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<boolean> {
261
- const { domain, tx } = context.state
262
- const repo = getRepository(KpiMetric, tx)
263
- const metric = await repo.findOne({ where: { domain: { id: domain.id }, id } })
264
- if (metric?.scheduleId) {
265
- await unregisterSchedule(metric.scheduleId)
266
- }
267
- await repo.delete({ domain: { id: domain.id }, id })
268
- await deleteAttachmentsByRef(null, { refBys: [id] }, context)
269
-
270
- return true
271
- }
272
-
273
- @Directive('@transaction')
274
- @Directive('@privilege(category: "kpi", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)')
275
- @Mutation(returns => Boolean, { description: 'To delete multiple KpiMetrics' })
276
- async deleteKpiMetrics(
277
- @Arg('ids', type => [String]) ids: string[],
278
- @Ctx() context: ResolverContext
279
- ): Promise<boolean> {
280
- const { domain, tx } = context.state
281
-
282
- await getRepository(KpiMetric, tx).delete({
283
- domain: { id: domain.id },
284
- id: In(ids)
285
- })
286
-
287
- await deleteAttachmentsByRef(null, { refBys: ids }, context)
288
-
289
- return true
290
- }
291
-
292
- @Directive('@transaction')
293
- @Directive('@privilege(category: "kpi", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)')
294
- @Mutation(returns => Boolean, { description: 'To import multiple KpiMetrics' })
295
- async importKpiMetrics(
296
- @Arg('kpiMetrics', type => [KpiMetricPatch]) kpiMetrics: KpiMetricPatch[],
297
- @Ctx() context: ResolverContext
298
- ): Promise<boolean> {
299
- const { domain, tx } = context.state
300
-
301
- await Promise.all(
302
- kpiMetrics.map(async (kpiMetric: KpiMetricPatch) => {
303
- const createdKpiMetric: KpiMetric = await getRepository(KpiMetric, tx).save({ domain, ...kpiMetric })
304
- })
305
- )
306
-
307
- return true
308
- }
309
- }
@@ -1,70 +0,0 @@
1
- import { Resolver, Query, FieldResolver, Root, Args, Arg, Ctx, Directive } from 'type-graphql'
2
- import { Attachment } from '@things-factory/attachment-base'
3
- import { Domain, getQueryBuilderFromListParams, getRepository, ListParam } from '@things-factory/shell'
4
- import { User } from '@things-factory/auth-base'
5
- import { KpiMetric } from './kpi-metric'
6
- import { KpiMetricList } from './kpi-metric-type'
7
-
8
- @Resolver(KpiMetric)
9
- export class KpiMetricQuery {
10
- @Directive('@privilege(category: "kpi", privilege: "query", domainOwnerGranted: true, superUserGranted: true)')
11
- @Query(returns => KpiMetric!, { nullable: true, description: 'Fetch a single KPI metric by its unique identifier.' })
12
- async kpiMetric(
13
- @Arg('id', { description: 'Unique identifier of the KPI metric to fetch.' }) id: string,
14
- @Ctx() context: ResolverContext
15
- ): Promise<KpiMetric> {
16
- const { domain } = context.state
17
-
18
- return await getRepository(KpiMetric).findOne({
19
- where: { domain: { id: domain.id }, id }
20
- })
21
- }
22
-
23
- @Directive('@privilege(category: "kpi", privilege: "query", domainOwnerGranted: true, superUserGranted: true)')
24
- @Query(returns => KpiMetricList, { description: 'To fetch multiple KpiMetrics' })
25
- async kpiMetrics(
26
- @Args(type => ListParam) params: ListParam,
27
- @Ctx() context: ResolverContext
28
- ): Promise<KpiMetricList> {
29
- const { domain } = context.state
30
-
31
- const queryBuilder = getQueryBuilderFromListParams({
32
- domain,
33
- params,
34
- repository: await getRepository(KpiMetric),
35
- searchables: ['name', 'description']
36
- })
37
-
38
- const [items, total] = await queryBuilder.getManyAndCount()
39
-
40
- return { items, total }
41
- }
42
-
43
- @FieldResolver(type => String)
44
- async thumbnail(@Root() kpiMetric: KpiMetric): Promise<string | undefined> {
45
- const attachment: Attachment = await getRepository(Attachment).findOne({
46
- where: {
47
- domain: { id: kpiMetric.domainId },
48
- refType: KpiMetric.name,
49
- refBy: kpiMetric.id
50
- }
51
- })
52
-
53
- return attachment?.fullpath
54
- }
55
-
56
- @FieldResolver(type => Domain)
57
- async domain(@Root() kpiMetric: KpiMetric): Promise<Domain> {
58
- return kpiMetric.domainId && (await getRepository(Domain).findOneBy({ id: kpiMetric.domainId }))
59
- }
60
-
61
- @FieldResolver(type => User)
62
- async updater(@Root() kpiMetric: KpiMetric): Promise<User> {
63
- return kpiMetric.updaterId && (await getRepository(User).findOneBy({ id: kpiMetric.updaterId }))
64
- }
65
-
66
- @FieldResolver(type => User)
67
- async creator(@Root() kpiMetric: KpiMetric): Promise<User> {
68
- return kpiMetric.creatorId && (await getRepository(User).findOneBy({ id: kpiMetric.creatorId }))
69
- }
70
- }
@@ -1,111 +0,0 @@
1
- import type { FileUpload } from 'graphql-upload/GraphQLUpload.js'
2
- import GraphQLUpload from 'graphql-upload/GraphQLUpload.js'
3
- import { ObjectType, Field, InputType, Int, ID, registerEnumType } from 'type-graphql'
4
-
5
- import { KpiPeriodType } from '../kpi/kpi'
6
- import { KpiMetricCollectType } from './kpi-metric'
7
- import { KpiMetric } from './kpi-metric'
8
-
9
- @InputType({ description: 'Input type for creating a new KPI metric. Used in mutations to provide metric details.' })
10
- export class NewKpiMetric {
11
- @Field({ description: 'Metric code, unique within the domain, used in KPI formulas.' })
12
- name: string
13
-
14
- @Field({ nullable: true, description: 'User-friendly name or description of the metric.' })
15
- description?: string
16
-
17
- @Field({ nullable: true, description: 'Unit of measurement for this metric (e.g., %, count, hours).' })
18
- unit?: string
19
-
20
- @Field({ nullable: true, description: 'Source of the metric data (e.g., system, method, dataset).' })
21
- source?: string
22
-
23
- @Field(type => ID, { nullable: true, description: 'ID of the source dataset for this metric.' })
24
- dataSetId?: string
25
-
26
- @Field({ nullable: true, description: 'Name of the field in the dataset this metric maps to.' })
27
- fieldName?: string
28
-
29
- @Field({ nullable: true, description: 'Indicates whether this metric is active and usable.' })
30
- active?: boolean
31
-
32
- @Field({
33
- nullable: true,
34
- description: 'Cron schedule string for periodic KPI value aggregation (e.g., "0 0 * * *" for daily).'
35
- })
36
- schedule?: string
37
-
38
- @Field(type => GraphQLUpload, { nullable: true, description: 'Thumbnail image or file for this metric.' })
39
- thumbnail?: FileUpload
40
-
41
- @Field({ nullable: true, description: 'Timezone for the schedule.' })
42
- timezone?: string
43
- @Field({ nullable: true, description: 'Schedule ID for the registered cron job.' })
44
- scheduleId?: string
45
-
46
- @Field(type => KpiPeriodType, { nullable: true, description: 'Aggregation period type for this metric.' })
47
- periodType?: KpiPeriodType
48
-
49
- @Field(type => KpiMetricCollectType, { nullable: true, description: '데이터 수집 방식' })
50
- collectType?: KpiMetricCollectType
51
- }
52
-
53
- @InputType()
54
- export class KpiMetricPatch {
55
- @Field(type => ID, { nullable: true })
56
- id?: string
57
-
58
- @Field({ nullable: true })
59
- name?: string
60
-
61
- @Field({ nullable: true })
62
- description?: string
63
-
64
- @Field({ nullable: true })
65
- unit?: string
66
-
67
- @Field({ nullable: true })
68
- source?: string
69
-
70
- @Field(type => ID, { nullable: true })
71
- dataSetId?: string
72
-
73
- @Field({ nullable: true })
74
- fieldName?: string
75
-
76
- @Field({ nullable: true })
77
- active?: boolean
78
-
79
- @Field({
80
- nullable: true,
81
- description: 'Cron schedule string for periodic KPI value aggregation (e.g., "0 0 * * *" for daily).'
82
- })
83
- schedule?: string
84
-
85
- @Field(type => GraphQLUpload, { nullable: true })
86
- thumbnail?: FileUpload
87
-
88
- @Field({ nullable: true, description: 'Timezone for the schedule.' })
89
- timezone?: string
90
-
91
- @Field({ nullable: true, description: 'Schedule ID for the registered cron job.' })
92
- scheduleId?: string
93
-
94
- @Field(type => KpiPeriodType, { nullable: true, description: 'Aggregation period type for this metric.' })
95
- periodType?: KpiPeriodType
96
-
97
- @Field({ nullable: true })
98
- cuFlag?: string
99
-
100
- @Field(type => KpiMetricCollectType, { nullable: true, description: '데이터 수집 방식' })
101
- collectType?: KpiMetricCollectType
102
- }
103
-
104
- @ObjectType()
105
- export class KpiMetricList {
106
- @Field(type => [KpiMetric])
107
- items: KpiMetric[]
108
-
109
- @Field(type => Int)
110
- total: number
111
- }