@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,134 +0,0 @@
1
- import {
2
- CreateDateColumn,
3
- UpdateDateColumn,
4
- DeleteDateColumn,
5
- Entity,
6
- Index,
7
- Column,
8
- RelationId,
9
- ManyToOne,
10
- PrimaryGeneratedColumn
11
- } from 'typeorm'
12
- import { ObjectType, Field, Int, ID, registerEnumType } from 'type-graphql'
13
-
14
- import { Domain } from '@things-factory/shell'
15
- import { User } from '@things-factory/auth-base'
16
- import { DataSet } from '@things-factory/dataset'
17
- import { KpiPeriodType } from '../kpi/kpi'
18
-
19
- export enum KpiMetricCollectType {
20
- AUTO = 'AUTO', // 데이터셋 등 자동 수집
21
- MANUAL = 'MANUAL', // 수동 입력
22
- IMPORT = 'IMPORT', // 외부 파일 등 임포트
23
- EXTERNAL = 'EXTERNAL' // 외부 API 등
24
- }
25
- registerEnumType(KpiMetricCollectType, {
26
- name: 'KpiMetricCollectType',
27
- description: '방식: AUTO(자동), MANUAL(수동), IMPORT(임포트), EXTERNAL(외부API)'
28
- })
29
-
30
- @Entity()
31
- @Index('ix_kpi_metric_0', (kpiMetric: KpiMetric) => [kpiMetric.domain, kpiMetric.name], {
32
- where: '"deleted_at" IS NULL',
33
- unique: true
34
- })
35
- @ObjectType({ description: 'KPI metric entity. Represents a source data item used in KPI formulas.' })
36
- export class KpiMetric {
37
- @PrimaryGeneratedColumn('uuid')
38
- @Field(type => ID, { description: 'Unique identifier for this KPI metric.' })
39
- readonly id: string
40
-
41
- @ManyToOne(type => Domain)
42
- @Field({ nullable: true, description: 'Domain (tenant) to which this KPI metric belongs.' })
43
- domain?: Domain
44
-
45
- @RelationId((metric: KpiMetric) => metric.domain)
46
- @Field({ nullable: true, description: 'ID of the domain (tenant) for this KPI metric.' })
47
- domainId?: string
48
-
49
- @Column()
50
- @Field({ description: 'Metric code, unique within the domain, used in KPI formulas.' })
51
- name: string
52
-
53
- @Column({ nullable: true })
54
- @Field({ nullable: true, description: 'User-friendly name or description of the metric.' })
55
- description?: string
56
-
57
- @Column({ nullable: true })
58
- @Field({ nullable: true, description: 'Unit of measurement for this metric (e.g., %, count, hours).' })
59
- unit?: string
60
-
61
- @Column({ nullable: true })
62
- @Field({ nullable: true, description: 'Source of the metric data (e.g., system, method, dataset).' })
63
- source?: string
64
-
65
- @ManyToOne(type => DataSet, { nullable: true })
66
- @Field(type => DataSet, { nullable: true, description: 'Reference to the source dataset for this metric.' })
67
- dataSet?: DataSet
68
-
69
- @RelationId((metric: KpiMetric) => metric.dataSet)
70
- @Field({ nullable: true, description: 'ID of the referenced source dataset.' })
71
- dataSetId?: string
72
-
73
- @Column({ nullable: true })
74
- @Field({ nullable: true, description: 'Name of the field in the dataset this metric maps to.' })
75
- fieldName?: string
76
-
77
- @Column({ nullable: false, default: false })
78
- @Field({ nullable: true, description: 'Indicates whether this metric is active and usable.' })
79
- active?: boolean
80
-
81
- @Column({ nullable: true })
82
- @Field({
83
- nullable: true,
84
- description: 'Cron schedule string for periodic KPI value aggregation (e.g., "0 0 * * *" for daily).'
85
- })
86
- schedule?: string
87
-
88
- @Column({ nullable: true })
89
- @Field({ nullable: true, description: 'Schedule ID for the registered cron job.' })
90
- scheduleId?: string
91
-
92
- @Column({ nullable: true })
93
- @Field({ nullable: true, description: 'Timezone for the schedule.' })
94
- timezone?: string
95
-
96
- @Column({ type: 'enum', enum: KpiMetricCollectType, default: KpiMetricCollectType.AUTO })
97
- @Field(type => KpiMetricCollectType, { description: '데이터 수집 방식' })
98
- collectType: KpiMetricCollectType
99
-
100
- @Column({ default: 'DAY' })
101
- @Field(type => KpiPeriodType, { description: 'Aggregation period type for this metric.' })
102
- periodType: KpiPeriodType
103
-
104
- @CreateDateColumn()
105
- @Field({ nullable: true, description: 'Timestamp when this KPI metric was created.' })
106
- createdAt?: Date
107
-
108
- @UpdateDateColumn()
109
- @Field({ nullable: true, description: 'Timestamp when this KPI metric was last updated.' })
110
- updatedAt?: Date
111
-
112
- @DeleteDateColumn()
113
- @Field({ nullable: true, description: 'Timestamp when this KPI metric was deleted (soft delete).' })
114
- deletedAt?: Date
115
-
116
- @ManyToOne(type => User, { nullable: true })
117
- @Field(type => User, { nullable: true, description: 'User who created this KPI metric.' })
118
- creator?: User
119
-
120
- @RelationId((metric: KpiMetric) => metric.creator)
121
- @Field({ nullable: true, description: 'ID of the user who created this KPI metric.' })
122
- creatorId?: string
123
-
124
- @ManyToOne(type => User, { nullable: true })
125
- @Field(type => User, { nullable: true, description: 'User who last updated this KPI metric.' })
126
- updater?: User
127
-
128
- @RelationId((metric: KpiMetric) => metric.updater)
129
- @Field({ nullable: true, description: 'ID of the user who last updated this KPI metric.' })
130
- updaterId?: string
131
-
132
- @Field(type => String, { nullable: true, description: 'Thumbnail image or file path for this KPI metric.' })
133
- thumbnail?: string
134
- }
@@ -1,7 +0,0 @@
1
- import { KpiMetricValue } from './kpi-metric-value'
2
- import { KpiMetricValueQuery } from './kpi-metric-value-query'
3
- import { KpiMetricValueMutation } from './kpi-metric-value-mutation'
4
-
5
- export const entities = [KpiMetricValue]
6
- export const resolvers = [KpiMetricValueQuery, KpiMetricValueMutation]
7
- export const subscribers = []
@@ -1,270 +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 { Float } from 'type-graphql'
5
- import { KpiPeriodType } from '../kpi/kpi'
6
-
7
- import { KpiMetricValue } from './kpi-metric-value'
8
- import { NewKpiMetricValue, KpiMetricValuePatch } from './kpi-metric-value-type'
9
- import { KpiMetric } from '../kpi-metric/kpi-metric'
10
- import { ScalarObject } from '@things-factory/shell'
11
- import { getDefaultValueDate } from '../utils/value-date-util'
12
-
13
- @Resolver(KpiMetricValue)
14
- export class KpiMetricValueMutation {
15
- @Directive('@transaction')
16
- @Directive('@privilege(category: "kpi", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)')
17
- @Mutation(returns => KpiMetricValue, { description: 'Create a new metric value with the provided details.' })
18
- async createKpiMetricValue(
19
- @Arg('metricValue', { description: 'Input object containing details for the new metric value.' })
20
- metricValue: NewKpiMetricValue,
21
- @Ctx() context: ResolverContext
22
- ): Promise<KpiMetricValue> {
23
- const { domain, user, tx } = context.state
24
-
25
- let metric = metricValue.metricId
26
- ? await getRepository(KpiMetric).findOne({ where: { id: metricValue.metricId } })
27
- : undefined
28
-
29
- // valueDate 자동 생성/보정 로직 추가
30
- let periodType = metricValue.periodType
31
- if (!periodType && metric) {
32
- periodType = metric.periodType
33
- }
34
- if (!periodType) {
35
- periodType = KpiPeriodType.DAY
36
- }
37
- // periodType을 string으로 변환 후 KpiPeriodType으로 강제 변환
38
- periodType = KpiPeriodType[String(periodType) as keyof typeof KpiPeriodType]
39
- let valueDate = metricValue.valueDate
40
- if (!valueDate || !isValueDateValidForPeriodType(valueDate, periodType)) {
41
- valueDate = getDefaultValueDate(periodType)
42
- }
43
-
44
- const entity: Partial<KpiMetricValue> = {
45
- metric,
46
- metricId: metricValue.metricId,
47
- valueDate,
48
- periodType,
49
- value: metricValue.value,
50
- org: metricValue.org,
51
- unit: metricValue.unit,
52
- meta: metricValue.meta,
53
- domain: domain,
54
- creator: user,
55
- updater: user
56
- }
57
-
58
- return await getRepository(KpiMetricValue, tx).save(entity)
59
- }
60
-
61
- @Directive('@transaction')
62
- @Directive('@privilege(category: "kpi", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)')
63
- @Mutation(returns => KpiMetricValue, { description: 'Record a metric value by metric name, value, meta, and org.' })
64
- async recordKpiMetricValue(
65
- @Arg('metricName', { description: 'Metric code/name.' }) metricName: string,
66
- @Arg('value', type => Float, { nullable: true, description: 'Metric value (number).' }) value: number | null,
67
- @Arg('meta', type => ScalarObject, { nullable: true, description: 'Extended or non-numeric information (JSON).' })
68
- meta: any,
69
- @Arg('org', { nullable: true, description: 'Organizational unit for this value (project, department, company, employee, etc.)' })
70
- org: string | null,
71
- @Ctx() context: ResolverContext
72
- ): Promise<KpiMetricValue> {
73
- const { domain, user, tx } = context.state
74
- const metric = await getRepository(KpiMetric).findOne({ where: { name: metricName, domain: { id: domain.id } } })
75
- if (!metric) throw new Error(`Metric not found: ${metricName}`)
76
- const periodType = metric.periodType || KpiPeriodType.DAY
77
- const valueDate = getDefaultValueDate(periodType)
78
- if (value == null && meta == null) {
79
- throw new Error('Either value or meta must be provided.')
80
- }
81
- const repo = getRepository(KpiMetricValue, tx)
82
- let entity = await repo.findOne({
83
- where: {
84
- metric: { id: metric.id },
85
- valueDate,
86
- periodType,
87
- org,
88
- domain: { id: domain.id }
89
- }
90
- })
91
- if (entity) {
92
- entity.value = value
93
- entity.meta = meta
94
- entity.updater = user
95
- } else {
96
- entity = repo.create({
97
- metric,
98
- metricId: metric.id,
99
- value,
100
- meta,
101
- valueDate,
102
- periodType,
103
- org,
104
- domain,
105
- creator: user,
106
- updater: user
107
- })
108
- }
109
- return await repo.save(entity)
110
- }
111
-
112
- @Directive('@transaction')
113
- @Directive('@privilege(category: "kpi", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)')
114
- @Mutation(returns => KpiMetricValue, { description: 'To modify KpiMetricValue information' })
115
- async updateKpiMetricValue(
116
- @Arg('id') id: string,
117
- @Arg('patch') patch: KpiMetricValuePatch,
118
- @Ctx() context: ResolverContext
119
- ): Promise<KpiMetricValue> {
120
- const { domain, user, tx } = context.state
121
-
122
- const repository = getRepository(KpiMetricValue, tx)
123
- const metricValue = await repository.findOne({
124
- where: { domain: { id: domain.id }, id }
125
- })
126
-
127
- let metric = patch.metricId
128
- ? await getRepository(KpiMetric).findOne({ where: { id: patch.metricId } })
129
- : metricValue.metric
130
-
131
- const entity: any = {
132
- ...metricValue,
133
- ...patch,
134
- metric,
135
- updater: user
136
- }
137
-
138
- return await repository.save(entity)
139
- }
140
-
141
- @Directive('@transaction')
142
- @Directive('@privilege(category: "kpi", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)')
143
- @Mutation(returns => [KpiMetricValue], { description: "To modify multiple KpiMetricValues' information" })
144
- async updateMultipleKpiMetricValue(
145
- @Arg('patches', type => [KpiMetricValuePatch]) patches: KpiMetricValuePatch[],
146
- @Ctx() context: ResolverContext
147
- ): Promise<KpiMetricValue[]> {
148
- const { domain, user, tx } = context.state
149
-
150
- let results = []
151
- const _createRecords = patches.filter((patch: any) => patch.cuFlag && patch.cuFlag.toUpperCase() === '+')
152
- const _updateRecords = patches.filter((patch: any) => patch.cuFlag && patch.cuFlag.toUpperCase() === 'M')
153
- const metricValueRepo = getRepository(KpiMetricValue, tx)
154
-
155
- if (_createRecords.length > 0) {
156
- for (let i = 0; i < _createRecords.length; i++) {
157
- const newRecord = _createRecords[i]
158
-
159
- if (!newRecord.metricId) {
160
- throw new Error('metricId is required for creating new metric value')
161
- }
162
-
163
- const metric = await getRepository(KpiMetric).findOne({
164
- where: { domain: { id: domain.id }, id: newRecord.metricId }
165
- })
166
-
167
- if (!metric) {
168
- throw new Error(`Metric not found: ${newRecord.metricId}`)
169
- }
170
-
171
- const result = await metricValueRepo.save({
172
- ...newRecord,
173
- metric,
174
- domain,
175
- creator: user,
176
- updater: user
177
- } as any)
178
- results.push({ ...result, cuFlag: '+' })
179
- }
180
- }
181
-
182
- if (_updateRecords.length > 0) {
183
- for (let i = 0; i < _updateRecords.length; i++) {
184
- const updateRecord = _updateRecords[i]
185
- const metricValue = await metricValueRepo.findOneBy({ id: updateRecord.id })
186
-
187
- if (!updateRecord.metricId) {
188
- throw new Error('metricId is required for updating value')
189
- }
190
-
191
- const metric = await getRepository(KpiMetric).findOne({
192
- where: { domain: { id: domain.id }, id: updateRecord.metricId }
193
- })
194
-
195
- if (!metric) {
196
- throw new Error(`Metric not found: ${updateRecord.metricId}`)
197
- }
198
-
199
- const result = await metricValueRepo.save({
200
- ...metricValue,
201
- ...updateRecord,
202
- metric,
203
- updater: user
204
- } as any)
205
- results.push({ ...result, cuFlag: 'M' })
206
- }
207
- }
208
-
209
- return results
210
- }
211
-
212
- @Directive('@transaction')
213
- @Directive('@privilege(category: "kpi", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)')
214
- @Mutation(returns => Boolean, { description: 'To delete KpiMetricValue' })
215
- async deleteKpiMetricValue(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<boolean> {
216
- const { domain, tx } = context.state
217
- await getRepository(KpiMetricValue, tx).delete({ domain: { id: domain.id }, id })
218
- return true
219
- }
220
-
221
- @Directive('@transaction')
222
- @Directive('@privilege(category: "kpi", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)')
223
- @Mutation(returns => Boolean, { description: 'To delete multiple KpiMetricValues' })
224
- async deleteKpiMetricValues(
225
- @Arg('ids', type => [String]) ids: string[],
226
- @Ctx() context: ResolverContext
227
- ): Promise<boolean> {
228
- const { domain, tx } = context.state
229
- await getRepository(KpiMetricValue, tx).delete({
230
- domain: { id: domain.id },
231
- id: In(ids)
232
- })
233
- return true
234
- }
235
-
236
- @Directive('@transaction')
237
- @Directive('@privilege(category: "kpi", privilege: "mutation", domainOwnerGranted: true, superUserGranted: true)')
238
- @Mutation(returns => Boolean, { description: 'To import multiple KpiMetricValues' })
239
- async importKpiMetricValues(
240
- @Arg('metricValues', type => [KpiMetricValuePatch]) metricValues: KpiMetricValuePatch[],
241
- @Ctx() context: ResolverContext
242
- ): Promise<boolean> {
243
- const { domain, tx } = context.state
244
- await Promise.all(
245
- metricValues.map(async (metricValue: KpiMetricValuePatch) => {
246
- await getRepository(KpiMetricValue, tx).save({ domain, ...metricValue } as any)
247
- })
248
- )
249
- return true
250
- }
251
- }
252
-
253
- // valueDate와 periodType의 일치 여부를 검사하는 유틸리티 함수(임시, 아래에 추가)
254
- function isValueDateValidForPeriodType(valueDate: string, periodType: KpiPeriodType): boolean {
255
- if (!valueDate) return false
256
- switch (periodType) {
257
- case KpiPeriodType.DAY:
258
- return /^\d{4}-\d{2}-\d{2}$/.test(valueDate)
259
- case KpiPeriodType.MONTH:
260
- return /^\d{4}-\d{2}$/.test(valueDate)
261
- case KpiPeriodType.QUARTER:
262
- return /^\d{4}-Q[1-4]$/.test(valueDate)
263
- case KpiPeriodType.WEEK:
264
- return /^\d{4}-W\d{1,2}$/.test(valueDate)
265
- case KpiPeriodType.ALLTIME:
266
- return valueDate === 'ALLTIME'
267
- default:
268
- return true
269
- }
270
- }
@@ -1,62 +0,0 @@
1
- import { Directive, Resolver, Query, Args, Arg, Ctx, FieldResolver, Root } from 'type-graphql'
2
- import { Domain, getQueryBuilderFromListParams, getRepository, ListParam } from '@things-factory/shell'
3
- import { User } from '@things-factory/auth-base'
4
- import { KpiMetricValue } from './kpi-metric-value'
5
- import { KpiMetricValueList } from './kpi-metric-value-type'
6
- import { KpiMetric } from '../kpi-metric/kpi-metric'
7
-
8
- @Resolver(KpiMetricValue)
9
- export class KpiMetricValueQuery {
10
- @Directive('@privilege(category: "kpi", privilege: "query", domainOwnerGranted: true, superUserGranted: true)')
11
- @Query(returns => KpiMetricValueList, { description: 'To fetch multiple KpiMetricValues' })
12
- async kpiMetricValues(
13
- @Args(type => ListParam) params: ListParam,
14
- @Ctx() context: ResolverContext
15
- ): Promise<KpiMetricValueList> {
16
- const { domain } = context.state
17
- const queryBuilder = getQueryBuilderFromListParams({
18
- domain,
19
- params,
20
- repository: await getRepository(KpiMetricValue),
21
- searchables: ['metricId', 'group', 'valueDate']
22
- })
23
- const [items, total] = await queryBuilder.getManyAndCount()
24
- return { items, total }
25
- }
26
-
27
- @Directive('@privilege(category: "kpi", privilege: "query", domainOwnerGranted: true, superUserGranted: true)')
28
- @Query(returns => KpiMetricValue, {
29
- nullable: true,
30
- description: 'Fetch a single metric value by its unique identifier.'
31
- })
32
- async kpiMetricValue(
33
- @Arg('id', { description: 'Unique identifier of the metric value to fetch.' }) id: string,
34
- @Ctx() context: ResolverContext
35
- ): Promise<KpiMetricValue | null> {
36
- const { domain } = context.state
37
- return await getRepository(KpiMetricValue).findOne({
38
- where: { domain: { id: domain.id }, id }
39
- })
40
- }
41
-
42
- @FieldResolver(type => Domain)
43
- async domain(@Root() metricValue: KpiMetricValue): Promise<Domain> {
44
- return metricValue.domainId && (await getRepository(Domain).findOneBy({ id: metricValue.domainId }))
45
- }
46
-
47
- @FieldResolver(type => User)
48
- async updater(@Root() metricValue: KpiMetricValue): Promise<User> {
49
- return metricValue.updaterId && (await getRepository(User).findOneBy({ id: metricValue.updaterId }))
50
- }
51
-
52
- @FieldResolver(type => User)
53
- async creator(@Root() metricValue: KpiMetricValue): Promise<User> {
54
- return metricValue.creatorId && (await getRepository(User).findOneBy({ id: metricValue.creatorId }))
55
- }
56
-
57
- @FieldResolver(type => KpiMetric)
58
- async metric(@Root() metricValue: KpiMetricValue): Promise<KpiMetric | null> {
59
- if (!metricValue.metricId) return null
60
- return await getRepository(KpiMetric).findOneBy({ id: metricValue.metricId })
61
- }
62
- }
@@ -1,82 +0,0 @@
1
- import { ObjectType, Field, ID, Float, InputType, Int } from 'type-graphql'
2
- import { ScalarObject } from '@things-factory/shell'
3
- import { KpiMetricValue } from './kpi-metric-value'
4
- import { KpiPeriodType } from '../kpi/kpi'
5
-
6
- @InputType({
7
- description: 'Input type for creating a new metric value. Used in mutations to provide metric value details.'
8
- })
9
- export class NewKpiMetricValue {
10
- @Field(type => ID, { description: 'ID of the metric to which this value belongs.' })
11
- metricId: string
12
-
13
- @Field({ description: 'Date or period for which this metric value is recorded (e.g., day, month, quarter, range).' })
14
- valueDate: string
15
-
16
- @Field(type => KpiPeriodType, { description: 'Aggregation period type for this metric value.' })
17
- periodType: KpiPeriodType
18
-
19
- @Field(type => Float, { description: 'The value for this metric and period.' })
20
- value: number
21
-
22
- @Field({ nullable: true, description: 'Unit of measurement for this metric value.' })
23
- unit?: string
24
-
25
- @Field(type => ScalarObject, {
26
- nullable: true,
27
- description:
28
- 'Extended or non-numeric information related to this metric value, stored as JSON. Can include status, comments, or other metadata.'
29
- })
30
- meta?: any
31
-
32
- @Field({ nullable: true, description: 'Organizational unit for this value (project, department, company, employee, etc.)' })
33
- org?: string
34
- }
35
-
36
- @InputType({
37
- description: 'Input type for updating an existing metric value. Used in mutations to patch metric value details.'
38
- })
39
- export class KpiMetricValuePatch {
40
- @Field(type => ID, { nullable: true, description: 'ID of the metric value to update.' })
41
- id?: string
42
-
43
- @Field(type => ID, { nullable: true, description: 'ID of the metric to which this value belongs.' })
44
- metricId?: string
45
-
46
- @Field({
47
- nullable: true,
48
- description: 'Date or period for which this metric value is recorded (e.g., day, month, quarter, range).'
49
- })
50
- valueDate?: string
51
-
52
- @Field(type => KpiPeriodType, { nullable: true, description: 'Aggregation period type for this metric value.' })
53
- periodType?: KpiPeriodType
54
-
55
- @Field(type => Float, { nullable: true, description: 'The value for this metric and period.' })
56
- value?: number
57
-
58
- @Field({ nullable: true, description: 'Unit of measurement for this metric value.' })
59
- unit?: string
60
-
61
- @Field(type => ScalarObject, {
62
- nullable: true,
63
- description:
64
- 'Extended or non-numeric information related to this metric value, stored as JSON. Can include status, comments, or other metadata.'
65
- })
66
- meta?: any
67
-
68
- @Field({ nullable: true, description: 'Organizational unit for this value (project, department, company, employee, etc.)' })
69
- org?: string
70
-
71
- @Field({ nullable: true, description: 'Custom flag for update operations (internal use).' })
72
- cuFlag?: string
73
- }
74
-
75
- @ObjectType()
76
- export class KpiMetricValueList {
77
- @Field(type => [KpiMetricValue])
78
- items: KpiMetricValue[]
79
-
80
- @Field(type => Int)
81
- total: number
82
- }
@@ -1,93 +0,0 @@
1
- import {
2
- Entity,
3
- PrimaryGeneratedColumn,
4
- Column,
5
- ManyToOne,
6
- Index,
7
- JoinColumn,
8
- CreateDateColumn,
9
- UpdateDateColumn,
10
- RelationId
11
- } from 'typeorm'
12
- import { ObjectType, Field, ID, Float } from 'type-graphql'
13
- import { Domain, ScalarObject } from '@things-factory/shell'
14
- import { KpiMetric } from '../kpi-metric/kpi-metric'
15
- import { KpiPeriodType } from '../kpi/kpi'
16
- import { User } from '@things-factory/auth-base'
17
-
18
- @Entity()
19
- @Index('ix_kpi_metric_value_latest', ['domain', 'metric', 'valueDate', 'org'], { unique: true })
20
- @ObjectType({ description: 'Current value for each KPI metric (can be used for both state and history).' })
21
- export class KpiMetricValue {
22
- @PrimaryGeneratedColumn('uuid')
23
- @Field(type => ID)
24
- id: string
25
-
26
- @ManyToOne(() => Domain)
27
- @Field(type => Domain, { nullable: true })
28
- domain?: Domain
29
-
30
- @RelationId((entity: KpiMetricValue) => entity.domain)
31
- @Field({ nullable: true })
32
- domainId?: string
33
-
34
- @ManyToOne(() => KpiMetric, { nullable: false, onDelete: 'CASCADE' })
35
- @JoinColumn({ name: 'metricId' })
36
- @Field(type => KpiMetric)
37
- metric: KpiMetric
38
-
39
- @RelationId((entity: KpiMetricValue) => entity.metric)
40
- @Field()
41
- metricId: string
42
-
43
- @Column('float')
44
- @Field(type => Float)
45
- value: number
46
-
47
- @Column({ nullable: true })
48
- @Field({ nullable: true })
49
- unit?: string
50
-
51
- @Column()
52
- @Field({
53
- description:
54
- 'Date or period for which this metric value is recorded (e.g., day: YYYY-MM-DD, month: YYYY-MM, range: YYYY-MM-DD~YYYY-MM-DD).'
55
- })
56
- valueDate: string
57
-
58
- @Column({ default: 'DAY' })
59
- @Field(type => KpiPeriodType)
60
- periodType: KpiPeriodType
61
-
62
- @Column({ default: '' })
63
- @Field({ nullable: true, description: 'Organizational unit for this value (project, department, company, employee, etc.)' })
64
- org?: string
65
-
66
- @Column({ type: 'simple-json', nullable: true })
67
- @Field(type => ScalarObject, { nullable: true })
68
- meta?: any
69
-
70
- @CreateDateColumn()
71
- @Field({ nullable: true })
72
- createdAt?: Date
73
-
74
- @UpdateDateColumn()
75
- @Field({ nullable: true })
76
- updatedAt?: Date
77
-
78
- @ManyToOne(() => User, { nullable: true })
79
- @Field(type => User, { nullable: true })
80
- creator?: User
81
-
82
- @RelationId((entity: KpiMetricValue) => entity.creator)
83
- @Field({ nullable: true })
84
- creatorId?: string
85
-
86
- @ManyToOne(() => User, { nullable: true })
87
- @Field(type => User, { nullable: true })
88
- updater?: User
89
-
90
- @RelationId((entity: KpiMetricValue) => entity.updater)
91
- @Field({ nullable: true })
92
- updaterId?: string
93
- }
@@ -1,6 +0,0 @@
1
- import { KpiOrgScope } from './kpi-org-scope'
2
- import { KpiOrgScopeQuery } from './kpi-org-scope-query'
3
- import { KpiOrgScopeMutation } from './kpi-org-scope-mutation'
4
-
5
- export const entities = [KpiOrgScope]
6
- export const resolvers = [KpiOrgScopeQuery, KpiOrgScopeMutation]