@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.
Files changed (140) 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/service/index.d.ts +1 -1
  43. package/dist-server/tsconfig.tsbuildinfo +1 -1
  44. package/package.json +18 -18
  45. package/client/tsconfig.json +0 -11
  46. package/dist-server/tsconfig.json +0 -10
  47. package/server/@types/index.d.ts +0 -11
  48. package/server/calculator/evaluator.ts +0 -45
  49. package/server/calculator/functions.ts +0 -67
  50. package/server/calculator/index.ts +0 -4
  51. package/server/calculator/parser.ts +0 -137
  52. package/server/calculator/provider.ts +0 -10
  53. package/server/controllers/index.ts +0 -2
  54. package/server/controllers/kpi-metric-value-provider.ts +0 -79
  55. package/server/controllers/kpi-value-provider.ts +0 -51
  56. package/server/index.ts +0 -6
  57. package/server/migrations/1752190849680-seed-kpi-metrics.ts +0 -124
  58. package/server/migrations/1752190849681-seed-kpi.ts +0 -356
  59. package/server/migrations/1752192090123-add-grades-to-kpi.ts +0 -67
  60. package/server/migrations/1752192090124-add-kpi-statistics.ts +0 -719
  61. package/server/migrations/1752192090128-seed-kpi-org-scope.ts +0 -132
  62. package/server/migrations/1752192090129-seed-kpi-values.ts +0 -207
  63. package/server/migrations/grade-data/x11-performance-table.json +0 -962
  64. package/server/migrations/grade-data/x12-performance-table.json +0 -611
  65. package/server/migrations/grade-data/x14-performance-table.json +0 -42
  66. package/server/migrations/grade-data/x21-performance-table.json +0 -889
  67. package/server/migrations/grade-data/x22-performance-table.json +0 -1064
  68. package/server/migrations/grade-data/x23-performance-table.json +0 -42
  69. package/server/migrations/grade-data/x31-performance-table.json +0 -644
  70. package/server/migrations/grade-data/x32-performance-table.json +0 -993
  71. package/server/migrations/grade-data/x33-performance-table.json +0 -195
  72. package/server/migrations/grade-data/x34-performance-table.json +0 -12
  73. package/server/migrations/grade-data/x35-performance-table.json +0 -42
  74. package/server/migrations/grade-data/x41-performance-table.json +0 -825
  75. package/server/migrations/grade-data/x42-performance-table.json +0 -786
  76. package/server/migrations/grade-data/x43-performance-table.json +0 -12
  77. package/server/migrations/grade-data/x44-performance-table.json +0 -42
  78. package/server/migrations/grade-data/x51-performance-table.json +0 -924
  79. package/server/migrations/grade-data/x52-performance-table.json +0 -42
  80. package/server/migrations/grade-data/x61-performance-table.json +0 -261
  81. package/server/migrations/grade-data/x62-performance-table.json +0 -42
  82. package/server/migrations/index.ts +0 -9
  83. package/server/migrations/seed-data/kpi-metrics-seed.json +0 -454
  84. package/server/migrations/seed-data/kpi-org-scope-seed.json +0 -1676
  85. package/server/migrations/seed-data/kpi-scopes-seed.json +0 -121
  86. package/server/migrations/seed-data/kpi-values-seed.json +0 -402
  87. package/server/migrations/seed-data/kpis-seed.json +0 -488
  88. package/server/migrations/seed-data/scope-definitions-seed.json +0 -90
  89. package/server/routes.ts +0 -81
  90. package/server/service/index.ts +0 -51
  91. package/server/service/kpi/aggregate-kpi.ts +0 -103
  92. package/server/service/kpi/event-subscriber.ts +0 -29
  93. package/server/service/kpi/index.ts +0 -9
  94. package/server/service/kpi/kpi-formula.service.ts +0 -164
  95. package/server/service/kpi/kpi-grade.types.ts +0 -28
  96. package/server/service/kpi/kpi-history.ts +0 -126
  97. package/server/service/kpi/kpi-mutation.ts +0 -553
  98. package/server/service/kpi/kpi-query.ts +0 -224
  99. package/server/service/kpi/kpi-type.ts +0 -151
  100. package/server/service/kpi/kpi.ts +0 -254
  101. package/server/service/kpi-alert/index.ts +0 -3
  102. package/server/service/kpi-alert/kpi-alert-query.ts +0 -59
  103. package/server/service/kpi-alert/kpi-alert-type.ts +0 -20
  104. package/server/service/kpi-metric/aggregate-kpi-metric.ts +0 -132
  105. package/server/service/kpi-metric/index.ts +0 -7
  106. package/server/service/kpi-metric/kpi-metric-mutation.ts +0 -309
  107. package/server/service/kpi-metric/kpi-metric-query.ts +0 -70
  108. package/server/service/kpi-metric/kpi-metric-type.ts +0 -111
  109. package/server/service/kpi-metric/kpi-metric.ts +0 -134
  110. package/server/service/kpi-metric-value/index.ts +0 -7
  111. package/server/service/kpi-metric-value/kpi-metric-value-mutation.ts +0 -270
  112. package/server/service/kpi-metric-value/kpi-metric-value-query.ts +0 -62
  113. package/server/service/kpi-metric-value/kpi-metric-value-type.ts +0 -82
  114. package/server/service/kpi-metric-value/kpi-metric-value.ts +0 -93
  115. package/server/service/kpi-org-scope/index.ts +0 -6
  116. package/server/service/kpi-org-scope/kpi-org-scope-mutation.ts +0 -173
  117. package/server/service/kpi-org-scope/kpi-org-scope-query.ts +0 -127
  118. package/server/service/kpi-org-scope/kpi-org-scope-type.ts +0 -68
  119. package/server/service/kpi-org-scope/kpi-org-scope.ts +0 -123
  120. package/server/service/kpi-scope/index.ts +0 -11
  121. package/server/service/kpi-scope/kpi-scope-mutation.ts +0 -129
  122. package/server/service/kpi-scope/kpi-scope-query.ts +0 -63
  123. package/server/service/kpi-scope/kpi-scope-type.ts +0 -96
  124. package/server/service/kpi-scope/kpi-scope.ts +0 -143
  125. package/server/service/kpi-statistic/index.ts +0 -7
  126. package/server/service/kpi-statistic/kpi-statistic-batch.service.ts +0 -231
  127. package/server/service/kpi-statistic/kpi-statistic-calculation.service.ts +0 -410
  128. package/server/service/kpi-statistic/kpi-statistic-mutation.ts +0 -291
  129. package/server/service/kpi-statistic/kpi-statistic-query.ts +0 -146
  130. package/server/service/kpi-statistic/kpi-statistic-type.ts +0 -152
  131. package/server/service/kpi-statistic/kpi-statistic.ts +0 -199
  132. package/server/service/kpi-value/index.ts +0 -7
  133. package/server/service/kpi-value/kpi-value-mutation.ts +0 -432
  134. package/server/service/kpi-value/kpi-value-query.ts +0 -61
  135. package/server/service/kpi-value/kpi-value-score.service.ts +0 -106
  136. package/server/service/kpi-value/kpi-value-type.ts +0 -122
  137. package/server/service/kpi-value/kpi-value.ts +0 -160
  138. package/server/service/utils/value-date-util.ts +0 -119
  139. package/server/tsconfig.json +0 -10
  140. 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
- }