@things-factory/kpi 9.0.13 → 9.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client/pages/kpi/kpi-grade-editor.ts +294 -0
- package/client/pages/kpi/kpi-list-page.ts +69 -1
- package/client/pages/kpi/kpi-overview.ts +5 -11
- package/client/pages/kpi-dashboard/kpi-dashboard.ts +43 -15
- package/client/pages/kpi-dashboard/kpi-performance-summary.ts +17 -10
- package/client/pages/kpi-value/kpi-value-manual-entry-form.ts +1 -0
- package/client/route.ts +0 -4
- package/dist-client/pages/kpi/kpi-grade-editor.d.ts +35 -0
- package/dist-client/pages/kpi/kpi-grade-editor.js +278 -0
- package/dist-client/pages/kpi/kpi-grade-editor.js.map +1 -0
- package/dist-client/pages/kpi/kpi-list-page.d.ts +4 -0
- package/dist-client/pages/kpi/kpi-list-page.js +64 -1
- package/dist-client/pages/kpi/kpi-list-page.js.map +1 -1
- package/dist-client/pages/kpi/kpi-overview.js +3 -11
- package/dist-client/pages/kpi/kpi-overview.js.map +1 -1
- package/dist-client/pages/kpi-dashboard/kpi-dashboard.js +44 -12
- package/dist-client/pages/kpi-dashboard/kpi-dashboard.js.map +1 -1
- package/dist-client/pages/kpi-dashboard/kpi-performance-summary.js +17 -10
- package/dist-client/pages/kpi-dashboard/kpi-performance-summary.js.map +1 -1
- package/dist-client/pages/kpi-value/kpi-value-manual-entry-form.js +1 -0
- package/dist-client/pages/kpi-value/kpi-value-manual-entry-form.js.map +1 -1
- package/dist-client/route.d.ts +1 -1
- package/dist-client/route.js +0 -3
- package/dist-client/route.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/index.d.ts +0 -1
- package/dist-server/index.js +0 -1
- package/dist-server/index.js.map +1 -1
- package/dist-server/service/index.d.ts +2 -4
- package/dist-server/service/index.js +0 -5
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/kpi/aggregate-kpi.js +4 -0
- package/dist-server/service/kpi/aggregate-kpi.js.map +1 -1
- package/dist-server/service/kpi/kpi-grade.types.d.ts +21 -0
- package/dist-server/service/kpi/kpi-grade.types.js +3 -0
- package/dist-server/service/kpi/kpi-grade.types.js.map +1 -0
- package/dist-server/service/kpi/kpi-history.d.ts +2 -0
- package/dist-server/service/kpi/kpi-history.js +8 -0
- package/dist-server/service/kpi/kpi-history.js.map +1 -1
- package/dist-server/service/kpi/kpi-query.d.ts +0 -2
- package/dist-server/service/kpi/kpi-query.js +0 -11
- package/dist-server/service/kpi/kpi-query.js.map +1 -1
- package/dist-server/service/kpi/kpi-type.d.ts +3 -0
- package/dist-server/service/kpi/kpi-type.js +14 -0
- package/dist-server/service/kpi/kpi-type.js.map +1 -1
- package/dist-server/service/kpi/kpi.d.ts +2 -2
- package/dist-server/service/kpi/kpi.js +5 -3
- package/dist-server/service/kpi/kpi.js.map +1 -1
- package/dist-server/service/kpi-alert/kpi-alert-query.js +13 -13
- package/dist-server/service/kpi-alert/kpi-alert-query.js.map +1 -1
- package/dist-server/service/kpi-value/kpi-value-grade.service.d.ts +34 -0
- package/dist-server/service/kpi-value/kpi-value-grade.service.js +117 -0
- package/dist-server/service/kpi-value/kpi-value-grade.service.js.map +1 -0
- package/dist-server/service/kpi-value/kpi-value-mutation.d.ts +1 -0
- package/dist-server/service/kpi-value/kpi-value-mutation.js +15 -0
- package/dist-server/service/kpi-value/kpi-value-mutation.js.map +1 -1
- package/dist-server/service/kpi-value/kpi-value-query.d.ts +2 -0
- package/dist-server/service/kpi-value/kpi-value-query.js +12 -0
- package/dist-server/service/kpi-value/kpi-value-query.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/server/index.ts +0 -1
- package/server/service/index.ts +0 -5
- package/server/service/kpi/aggregate-kpi.ts +6 -0
- package/server/service/kpi/kpi-grade.types.ts +27 -0
- package/server/service/kpi/kpi-history.ts +9 -1
- package/server/service/kpi/kpi-query.ts +0 -6
- package/server/service/kpi/kpi-type.ts +13 -0
- package/server/service/kpi/kpi.ts +8 -4
- package/server/service/kpi-alert/kpi-alert-query.ts +13 -14
- package/server/service/kpi-value/kpi-value-grade.service.ts +127 -0
- package/server/service/kpi-value/kpi-value-mutation.ts +9 -0
- package/server/service/kpi-value/kpi-value-query.ts +7 -0
- package/things-factory.config.js +0 -1
- package/client/pages/kpi-grade/kpi-grade-importer.ts +0 -90
- package/client/pages/kpi-grade/kpi-grade-list-page.ts +0 -405
- package/dist-client/pages/kpi-grade/kpi-grade-importer.d.ts +0 -23
- package/dist-client/pages/kpi-grade/kpi-grade-importer.js +0 -92
- package/dist-client/pages/kpi-grade/kpi-grade-importer.js.map +0 -1
- package/dist-client/pages/kpi-grade/kpi-grade-list-page.d.ts +0 -66
- package/dist-client/pages/kpi-grade/kpi-grade-list-page.js +0 -387
- package/dist-client/pages/kpi-grade/kpi-grade-list-page.js.map +0 -1
- package/dist-server/migrations/1752188906708-SeedKpiCategory.d.ts +0 -5
- package/dist-server/migrations/1752188906708-SeedKpiCategory.js +0 -56
- package/dist-server/migrations/1752188906708-SeedKpiCategory.js.map +0 -1
- package/dist-server/migrations/1752190849681-SeedKpi.d.ts +0 -5
- package/dist-server/migrations/1752190849681-SeedKpi.js +0 -107
- package/dist-server/migrations/1752190849681-SeedKpi.js.map +0 -1
- package/dist-server/migrations/1752191090459-SeedKpiGrade.d.ts +0 -5
- package/dist-server/migrations/1752191090459-SeedKpiGrade.js +0 -271
- package/dist-server/migrations/1752191090459-SeedKpiGrade.js.map +0 -1
- package/dist-server/migrations/index.d.ts +0 -1
- package/dist-server/migrations/index.js +0 -12
- package/dist-server/migrations/index.js.map +0 -1
- package/dist-server/service/kpi-grade/index.d.ts +0 -6
- package/dist-server/service/kpi-grade/index.js +0 -10
- package/dist-server/service/kpi-grade/index.js.map +0 -1
- package/dist-server/service/kpi-grade/kpi-grade-mutation.d.ts +0 -10
- package/dist-server/service/kpi-grade/kpi-grade-mutation.js +0 -151
- package/dist-server/service/kpi-grade/kpi-grade-mutation.js.map +0 -1
- package/dist-server/service/kpi-grade/kpi-grade-query.d.ts +0 -13
- package/dist-server/service/kpi-grade/kpi-grade-query.js +0 -92
- package/dist-server/service/kpi-grade/kpi-grade-query.js.map +0 -1
- package/dist-server/service/kpi-grade/kpi-grade-type.d.ts +0 -29
- package/dist-server/service/kpi-grade/kpi-grade-type.js +0 -113
- package/dist-server/service/kpi-grade/kpi-grade-type.js.map +0 -1
- package/dist-server/service/kpi-grade/kpi-grade.d.ts +0 -24
- package/dist-server/service/kpi-grade/kpi-grade.js +0 -117
- package/dist-server/service/kpi-grade/kpi-grade.js.map +0 -1
- package/server/migrations/1752188906708-SeedKpiCategory.ts +0 -61
- package/server/migrations/1752190849681-SeedKpi.ts +0 -112
- package/server/migrations/1752191090459-SeedKpiGrade.ts +0 -270
- package/server/migrations/index.ts +0 -9
- package/server/service/kpi-grade/index.ts +0 -7
- package/server/service/kpi-grade/kpi-grade-mutation.ts +0 -146
- package/server/service/kpi-grade/kpi-grade-query.ts +0 -58
- package/server/service/kpi-grade/kpi-grade-type.ts +0 -82
- package/server/service/kpi-grade/kpi-grade.ts +0 -101
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@things-factory/kpi",
|
|
3
|
-
"version": "9.0.
|
|
3
|
+
"version": "9.0.14",
|
|
4
4
|
"main": "dist-server/index.js",
|
|
5
5
|
"browser": "dist-client/index.js",
|
|
6
6
|
"things-factory": true,
|
|
@@ -41,8 +41,8 @@
|
|
|
41
41
|
"@operato/styles": "^9.0.0",
|
|
42
42
|
"@operato/utils": "^9.0.0",
|
|
43
43
|
"@things-factory/auth-base": "^9.0.0",
|
|
44
|
-
"@things-factory/dataset": "^9.0.
|
|
44
|
+
"@things-factory/dataset": "^9.0.14",
|
|
45
45
|
"@things-factory/shell": "^9.0.0"
|
|
46
46
|
},
|
|
47
|
-
"gitHead": "
|
|
47
|
+
"gitHead": "b092ace8cad6c994b41ce6c06b929085d886b134"
|
|
48
48
|
}
|
package/server/index.ts
CHANGED
package/server/service/index.ts
CHANGED
|
@@ -3,8 +3,6 @@ export * from './kpi/kpi'
|
|
|
3
3
|
export * from './kpi/kpi-type'
|
|
4
4
|
export * from './kpi-category/kpi-category'
|
|
5
5
|
export * from './kpi-category/kpi-category-type'
|
|
6
|
-
export * from './kpi-grade/kpi-grade'
|
|
7
|
-
export * from './kpi-grade/kpi-grade-type'
|
|
8
6
|
export * from './kpi-value/kpi-value'
|
|
9
7
|
export * from './kpi-value/kpi-value-type'
|
|
10
8
|
export * from './kpi-metric/kpi-metric'
|
|
@@ -14,7 +12,6 @@ export * from './kpi-alert'
|
|
|
14
12
|
/* IMPORT ENTITIES AND RESOLVERS */
|
|
15
13
|
import { entities as KpiEntities, resolvers as KpiResolvers } from './kpi'
|
|
16
14
|
import { entities as KpiCategoryEntities, resolvers as KpiCategoryResolvers } from './kpi-category'
|
|
17
|
-
import { entities as KpiGradeEntities, resolvers as KpiGradeResolvers } from './kpi-grade'
|
|
18
15
|
import { entities as KpiValueEntities, resolvers as KpiValueResolvers } from './kpi-value'
|
|
19
16
|
import { entities as KpiMetricEntities, resolvers as KpiMetricResolvers } from './kpi-metric'
|
|
20
17
|
import { resolvers as KpiAlertResolvers } from './kpi-alert'
|
|
@@ -23,7 +20,6 @@ export const entities = [
|
|
|
23
20
|
/* ENTITIES */
|
|
24
21
|
...KpiEntities,
|
|
25
22
|
...KpiCategoryEntities,
|
|
26
|
-
...KpiGradeEntities,
|
|
27
23
|
...KpiValueEntities,
|
|
28
24
|
...KpiMetricEntities
|
|
29
25
|
]
|
|
@@ -33,7 +29,6 @@ export const schema = {
|
|
|
33
29
|
/* RESOLVER CLASSES */
|
|
34
30
|
...KpiResolvers,
|
|
35
31
|
...KpiCategoryResolvers,
|
|
36
|
-
...KpiGradeResolvers,
|
|
37
32
|
...KpiValueResolvers,
|
|
38
33
|
...KpiMetricResolvers,
|
|
39
34
|
...KpiAlertResolvers
|
|
@@ -4,6 +4,7 @@ import { KpiMetric } from '../kpi-metric/kpi-metric'
|
|
|
4
4
|
import { KpiValue, KpiValueInputType } from '../kpi-value/kpi-value'
|
|
5
5
|
import { KpiFormulaService } from './kpi-formula.service'
|
|
6
6
|
import { aggregateKpiMetricValue } from '../kpi-metric/aggregate-kpi-metric'
|
|
7
|
+
import { KpiValueGradeService } from '../kpi-value/kpi-value-grade.service'
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* KPI 단위 집계/산식 자동화 함수
|
|
@@ -14,6 +15,7 @@ import { aggregateKpiMetricValue } from '../kpi-metric/aggregate-kpi-metric'
|
|
|
14
15
|
*/
|
|
15
16
|
export async function aggregateKpiValue(kpiId: string, domainId: string, context: ResolverContext) {
|
|
16
17
|
const tx = context.state?.tx || getRepository(Kpi).manager
|
|
18
|
+
const gradeService = new KpiValueGradeService()
|
|
17
19
|
|
|
18
20
|
// 1. KPI 정보 조회
|
|
19
21
|
const kpi = await getRepository(Kpi).findOne({ where: { id: kpiId, domain: { id: domainId } } })
|
|
@@ -75,6 +77,10 @@ export async function aggregateKpiValue(kpiId: string, domainId: string, context
|
|
|
75
77
|
entity.domain = kpi.domain
|
|
76
78
|
entity.creator = context.state?.user
|
|
77
79
|
entity.updater = context.state?.user
|
|
80
|
+
|
|
81
|
+
// 등급 자동 계산 및 저장
|
|
82
|
+
await gradeService.calculateAndSaveGrade(entity, kpi)
|
|
83
|
+
|
|
78
84
|
entity = await repo.save(entity)
|
|
79
85
|
savedValues.push(entity)
|
|
80
86
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KPI 등급 설정 타입 정의
|
|
3
|
+
*/
|
|
4
|
+
export interface KpiGrade {
|
|
5
|
+
/** 등급명 (예: A, B, C, 우수, 양호 등) */
|
|
6
|
+
name: string
|
|
7
|
+
|
|
8
|
+
/** 등급의 최소값 */
|
|
9
|
+
minValue: number
|
|
10
|
+
|
|
11
|
+
/** 등급의 최대값 */
|
|
12
|
+
maxValue: number
|
|
13
|
+
|
|
14
|
+
/** 등급 점수 (선택사항) */
|
|
15
|
+
score?: number
|
|
16
|
+
|
|
17
|
+
/** 등급 색상 (선택사항) */
|
|
18
|
+
color?: string
|
|
19
|
+
|
|
20
|
+
/** 등급 설명 (선택사항) */
|
|
21
|
+
description?: string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* KPI 등급 배열 타입
|
|
26
|
+
*/
|
|
27
|
+
export type KpiGrades = KpiGrade[]
|
|
@@ -9,10 +9,11 @@ import {
|
|
|
9
9
|
} from '@operato/typeorm-history'
|
|
10
10
|
import { Role, User } from '@things-factory/auth-base'
|
|
11
11
|
import { config } from '@things-factory/env'
|
|
12
|
-
import { Domain } from '@things-factory/shell'
|
|
12
|
+
import { Domain, ScalarObject } from '@things-factory/shell'
|
|
13
13
|
|
|
14
14
|
import { Kpi, KpiStatus } from './kpi'
|
|
15
15
|
import { KpiCategory } from '../kpi-category/kpi-category'
|
|
16
|
+
import { KpiGrades } from './kpi-grade.types'
|
|
16
17
|
|
|
17
18
|
const ORMCONFIG = config.get('ormconfig', {})
|
|
18
19
|
const DATABASE_TYPE = ORMCONFIG.type
|
|
@@ -69,6 +70,13 @@ export class KpiHistory implements HistoryEntityInterface<Kpi> {
|
|
|
69
70
|
@Field(type => String, { nullable: true, description: 'Thumbnail image or file path for this KPI.' })
|
|
70
71
|
thumbnail?: string
|
|
71
72
|
|
|
73
|
+
@Column({ type: 'simple-json', nullable: true })
|
|
74
|
+
@Field(type => ScalarObject, {
|
|
75
|
+
nullable: true,
|
|
76
|
+
description: 'Grade configuration for this KPI version'
|
|
77
|
+
})
|
|
78
|
+
grades?: KpiGrades
|
|
79
|
+
|
|
72
80
|
@Column({ nullable: true })
|
|
73
81
|
@Field({ nullable: true })
|
|
74
82
|
createdAt?: Date
|
|
@@ -4,7 +4,6 @@ import { Domain, getQueryBuilderFromListParams, getRepository, ListParam } from
|
|
|
4
4
|
import { User } from '@things-factory/auth-base'
|
|
5
5
|
import { Kpi } from './kpi'
|
|
6
6
|
import { KpiList } from './kpi-type'
|
|
7
|
-
import { KpiGrade } from '../kpi-grade/kpi-grade'
|
|
8
7
|
import { KpiValue } from '../kpi-value/kpi-value'
|
|
9
8
|
import { KpiHistory } from './kpi-history'
|
|
10
9
|
import { Int } from 'type-graphql'
|
|
@@ -53,11 +52,6 @@ export class KpiQuery {
|
|
|
53
52
|
return attachment?.fullpath
|
|
54
53
|
}
|
|
55
54
|
|
|
56
|
-
@FieldResolver(type => [KpiGrade])
|
|
57
|
-
async grades(@Root() kpi: Kpi): Promise<KpiGrade[]> {
|
|
58
|
-
return await getRepository(KpiGrade).find({ where: { domain: { id: kpi.domainId }, kpi: { id: kpi.id } } })
|
|
59
|
-
}
|
|
60
|
-
|
|
61
55
|
@FieldResolver(type => Domain)
|
|
62
56
|
async domain(@Root() kpi: Kpi): Promise<Domain> {
|
|
63
57
|
return kpi.domainId && (await getRepository(Domain).findOneBy({ id: kpi.domainId }))
|
|
@@ -5,6 +5,7 @@ import { ObjectType, Field, InputType, Int, ID, registerEnumType } from 'type-gr
|
|
|
5
5
|
import { ObjectRef, ScalarObject } from '@things-factory/shell'
|
|
6
6
|
|
|
7
7
|
import { Kpi, KpiStatus } from './kpi'
|
|
8
|
+
import { KpiGrades } from './kpi-grade.types'
|
|
8
9
|
|
|
9
10
|
@InputType({ description: 'Input type for creating a new KPI. Used in mutations to provide KPI details.' })
|
|
10
11
|
export class NewKpi {
|
|
@@ -50,6 +51,12 @@ export class NewKpi {
|
|
|
50
51
|
|
|
51
52
|
@Field({ nullable: true, description: 'Timezone for the KPI schedule.' })
|
|
52
53
|
timezone?: string
|
|
54
|
+
|
|
55
|
+
@Field(type => ScalarObject, {
|
|
56
|
+
nullable: true,
|
|
57
|
+
description: 'Grade configuration for this KPI version'
|
|
58
|
+
})
|
|
59
|
+
grades?: KpiGrades
|
|
53
60
|
}
|
|
54
61
|
|
|
55
62
|
@InputType({ description: 'Input type for updating an existing KPI. Used in mutations to patch KPI details.' })
|
|
@@ -102,6 +109,12 @@ export class KpiPatch {
|
|
|
102
109
|
|
|
103
110
|
@Field({ nullable: true, description: 'Timezone for the KPI schedule.' })
|
|
104
111
|
timezone?: string
|
|
112
|
+
|
|
113
|
+
@Field(type => ScalarObject, {
|
|
114
|
+
nullable: true,
|
|
115
|
+
description: 'Grade configuration for this KPI version'
|
|
116
|
+
})
|
|
117
|
+
grades?: KpiGrades
|
|
105
118
|
}
|
|
106
119
|
|
|
107
120
|
@ObjectType()
|
|
@@ -16,10 +16,11 @@ import { ObjectType, Field, Int, ID, registerEnumType } from 'type-graphql'
|
|
|
16
16
|
import { Domain } from '@things-factory/shell'
|
|
17
17
|
import { User } from '@things-factory/auth-base'
|
|
18
18
|
import { KpiCategory } from '../kpi-category/kpi-category'
|
|
19
|
-
import { KpiGrade } from '../kpi-grade/kpi-grade'
|
|
20
19
|
import { config } from '@things-factory/env'
|
|
21
20
|
import { ScalarObject } from '@things-factory/shell'
|
|
22
21
|
|
|
22
|
+
import { KpiGrades } from './kpi-grade.types'
|
|
23
|
+
|
|
23
24
|
const ORMCONFIG = config.get('ormconfig', {})
|
|
24
25
|
const DATABASE_TYPE = ORMCONFIG.type
|
|
25
26
|
|
|
@@ -168,7 +169,10 @@ export class Kpi {
|
|
|
168
169
|
@Field(type => String, { nullable: true, description: 'Thumbnail image or file path for this KPI.' })
|
|
169
170
|
thumbnail?: string
|
|
170
171
|
|
|
171
|
-
@
|
|
172
|
-
@Field(type =>
|
|
173
|
-
|
|
172
|
+
@Column({ type: 'simple-json', nullable: true })
|
|
173
|
+
@Field(type => ScalarObject, {
|
|
174
|
+
nullable: true,
|
|
175
|
+
description: 'Grade configuration for this KPI version'
|
|
176
|
+
})
|
|
177
|
+
grades?: KpiGrades
|
|
174
178
|
}
|
|
@@ -2,7 +2,6 @@ import { Resolver, Query, Ctx } from 'type-graphql'
|
|
|
2
2
|
import { getRepository } from '@things-factory/shell'
|
|
3
3
|
import { Kpi } from '../kpi/kpi'
|
|
4
4
|
import { KpiValue } from '../kpi-value/kpi-value'
|
|
5
|
-
import { KpiGrade } from '../kpi-grade/kpi-grade'
|
|
6
5
|
import { KpiAlert } from './kpi-alert-type'
|
|
7
6
|
|
|
8
7
|
@Resolver()
|
|
@@ -11,7 +10,7 @@ export class KpiAlertQuery {
|
|
|
11
10
|
async kpiAlerts(@Ctx() context): Promise<KpiAlert[]> {
|
|
12
11
|
const { domain } = context.state
|
|
13
12
|
// 1. KPI 전체 조회
|
|
14
|
-
const kpis = await getRepository(Kpi).find({ where: { domain: { id: domain.id } }
|
|
13
|
+
const kpis = await getRepository(Kpi).find({ where: { domain: { id: domain.id } } })
|
|
15
14
|
// 2. KPI별 최신 실적값 조회
|
|
16
15
|
const alerts: KpiAlert[] = []
|
|
17
16
|
for (const kpi of kpis) {
|
|
@@ -41,18 +40,18 @@ export class KpiAlertQuery {
|
|
|
41
40
|
})
|
|
42
41
|
}
|
|
43
42
|
// 등급 기준 경고(예: 최하위 등급)
|
|
44
|
-
if (kpi.grades?.length) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
43
|
+
// if (kpi.grades?.length) {
|
|
44
|
+
// const grade = kpi.grades.find(g => value.value >= g.minValue && value.value <= g.maxValue)
|
|
45
|
+
// if (grade && grade.score !== undefined && grade.score <= 2) {
|
|
46
|
+
// alerts.push({
|
|
47
|
+
// id: `${kpi.id}-grade-low`,
|
|
48
|
+
// kpi,
|
|
49
|
+
// message: `${kpi.name} 등급(${grade.name})(${grade.score}점)`,
|
|
50
|
+
// level: 'critical',
|
|
51
|
+
// createdAt: new Date()
|
|
52
|
+
// })
|
|
53
|
+
// }
|
|
54
|
+
// }
|
|
56
55
|
}
|
|
57
56
|
return alerts
|
|
58
57
|
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { getRepository } from '@things-factory/shell'
|
|
2
|
+
import { ObjectType, Field } from 'type-graphql'
|
|
3
|
+
import { Kpi } from '../kpi/kpi'
|
|
4
|
+
import { KpiValue } from './kpi-value'
|
|
5
|
+
import { KpiGrades } from '../kpi/kpi-grade.types'
|
|
6
|
+
|
|
7
|
+
export interface GradeResult {
|
|
8
|
+
name: string
|
|
9
|
+
score?: number
|
|
10
|
+
color?: string
|
|
11
|
+
description?: string
|
|
12
|
+
minValue: number
|
|
13
|
+
maxValue: number
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@ObjectType({ description: 'Grade information for KPI value' })
|
|
17
|
+
export class GradeInfo {
|
|
18
|
+
@Field({ description: 'Grade name (e.g., A, B, C, 우수, 양호)' })
|
|
19
|
+
name: string
|
|
20
|
+
|
|
21
|
+
@Field({ nullable: true, description: 'Grade score or performance value' })
|
|
22
|
+
score?: number
|
|
23
|
+
|
|
24
|
+
@Field({ nullable: true, description: 'Color code for visualization' })
|
|
25
|
+
color?: string
|
|
26
|
+
|
|
27
|
+
@Field({ nullable: true, description: 'Grade description' })
|
|
28
|
+
description?: string
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export class KpiValueGradeService {
|
|
32
|
+
/**
|
|
33
|
+
* KPI의 grades 정보를 이용하여 KpiValue의 등급 계산
|
|
34
|
+
*/
|
|
35
|
+
calculateGradeFromKpiGrades(kpi: Kpi, value: number): GradeResult | null {
|
|
36
|
+
if (!kpi.grades || kpi.grades.length === 0) {
|
|
37
|
+
return null
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// grades 배열에서 해당 값의 범위에 맞는 등급 찾기
|
|
41
|
+
const grade = kpi.grades.find(g => value >= g.minValue && value <= g.maxValue)
|
|
42
|
+
|
|
43
|
+
if (!grade) {
|
|
44
|
+
return null
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
name: grade.name,
|
|
49
|
+
score: grade.score,
|
|
50
|
+
color: grade.color,
|
|
51
|
+
description: grade.description,
|
|
52
|
+
minValue: grade.minValue,
|
|
53
|
+
maxValue: grade.maxValue
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* KpiValue 생성/수정 시 등급 자동 계산 및 저장
|
|
59
|
+
*/
|
|
60
|
+
async calculateAndSaveGrade(kpiValue: KpiValue, kpi: Kpi): Promise<void> {
|
|
61
|
+
const gradeResult = this.calculateGradeFromKpiGrades(kpi, kpiValue.value)
|
|
62
|
+
|
|
63
|
+
if (gradeResult) {
|
|
64
|
+
// KpiValue의 meta 필드에 등급 정보 저장
|
|
65
|
+
kpiValue.meta = {
|
|
66
|
+
...kpiValue.meta,
|
|
67
|
+
grade: {
|
|
68
|
+
name: gradeResult.name,
|
|
69
|
+
score: gradeResult.score,
|
|
70
|
+
color: gradeResult.color,
|
|
71
|
+
description: gradeResult.description,
|
|
72
|
+
calculatedAt: new Date().toISOString()
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* 기존 KpiValue들의 등급 재계산 (배치 처리)
|
|
80
|
+
*/
|
|
81
|
+
async recalculateGradesForKpi(kpiId: string, context: ResolverContext): Promise<void> {
|
|
82
|
+
const kpi = await getRepository(Kpi).findOne({ where: { id: kpiId } })
|
|
83
|
+
if (!kpi) return
|
|
84
|
+
|
|
85
|
+
const kpiValues = await getRepository(KpiValue).find({
|
|
86
|
+
where: { kpiId, version: kpi.version }
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
for (const kpiValue of kpiValues) {
|
|
90
|
+
await this.calculateAndSaveGrade(kpiValue, kpi)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
await getRepository(KpiValue).save(kpiValues)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* 특정 KpiValue의 등급 정보 조회
|
|
98
|
+
*/
|
|
99
|
+
async getGradeForKpiValue(kpiValue: KpiValue): Promise<GradeInfo | null> {
|
|
100
|
+
// meta 필드에서 등급 정보 조회
|
|
101
|
+
if (kpiValue.meta?.grade) {
|
|
102
|
+
return {
|
|
103
|
+
name: kpiValue.meta.grade.name,
|
|
104
|
+
score: kpiValue.meta.grade.score,
|
|
105
|
+
color: kpiValue.meta.grade.color,
|
|
106
|
+
description: kpiValue.meta.grade.description
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// 실시간 계산 (필요시)
|
|
111
|
+
const kpi = await getRepository(Kpi).findOne({ where: { id: kpiValue.kpiId } })
|
|
112
|
+
if (kpi) {
|
|
113
|
+
const gradeResult = this.calculateGradeFromKpiGrades(kpi, kpiValue.value)
|
|
114
|
+
|
|
115
|
+
if (gradeResult) {
|
|
116
|
+
return {
|
|
117
|
+
name: gradeResult.name,
|
|
118
|
+
score: gradeResult.score,
|
|
119
|
+
color: gradeResult.color,
|
|
120
|
+
description: gradeResult.description
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return null
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -6,6 +6,7 @@ import { KpiValue } from './kpi-value'
|
|
|
6
6
|
import { NewKpiValue, KpiValuePatch } from './kpi-value-type'
|
|
7
7
|
import { Kpi } from '../kpi/kpi'
|
|
8
8
|
import { KpiValueInputType } from './kpi-value'
|
|
9
|
+
import { KpiValueGradeService } from './kpi-value-grade.service'
|
|
9
10
|
|
|
10
11
|
@Resolver(KpiValue)
|
|
11
12
|
export class KpiValueMutation {
|
|
@@ -172,4 +173,12 @@ export class KpiValueMutation {
|
|
|
172
173
|
|
|
173
174
|
return true
|
|
174
175
|
}
|
|
176
|
+
|
|
177
|
+
@Directive('@transaction')
|
|
178
|
+
@Mutation(returns => Boolean, { description: 'Recalculate grades for all KpiValues of a specific KPI' })
|
|
179
|
+
async recalculateGradesForKpi(@Arg('kpiId') kpiId: string, @Ctx() context: ResolverContext): Promise<boolean> {
|
|
180
|
+
const gradeService = new KpiValueGradeService()
|
|
181
|
+
await gradeService.recalculateGradesForKpi(kpiId, context)
|
|
182
|
+
return true
|
|
183
|
+
}
|
|
175
184
|
}
|
|
@@ -4,6 +4,7 @@ import { User } from '@things-factory/auth-base'
|
|
|
4
4
|
import { KpiValue } from './kpi-value'
|
|
5
5
|
import { KpiValueList } from './kpi-value-type'
|
|
6
6
|
import { Kpi } from '../kpi/kpi'
|
|
7
|
+
import { KpiValueGradeService, GradeInfo } from './kpi-value-grade.service'
|
|
7
8
|
|
|
8
9
|
@Resolver(KpiValue)
|
|
9
10
|
export class KpiValueQuery {
|
|
@@ -55,4 +56,10 @@ export class KpiValueQuery {
|
|
|
55
56
|
if (!kpiValue.kpiId) return null
|
|
56
57
|
return await getRepository(Kpi).findOneBy({ id: kpiValue.kpiId })
|
|
57
58
|
}
|
|
59
|
+
|
|
60
|
+
@FieldResolver(type => GradeInfo, { nullable: true })
|
|
61
|
+
async grade(@Root() kpiValue: KpiValue): Promise<GradeInfo | null> {
|
|
62
|
+
const gradeService = new KpiValueGradeService()
|
|
63
|
+
return await gradeService.getGradeForKpiValue(kpiValue)
|
|
64
|
+
}
|
|
58
65
|
}
|
package/things-factory.config.js
CHANGED
|
@@ -11,7 +11,6 @@ export default {
|
|
|
11
11
|
{ tagname: 'kpi-metric-list-page', page: 'kpi-metric-list' },
|
|
12
12
|
{ tagname: 'kpi-value-list-page', page: 'kpi-value-list' },
|
|
13
13
|
{ tagname: 'kpi-value-manual-entry-page', page: 'kpi-value-manual-entry' },
|
|
14
|
-
{ tagname: 'kpi-grade-list-page', page: 'kpi-grade-list' },
|
|
15
14
|
{ tagname: 'kpi-history-list-page', page: 'kpi-history-list' }
|
|
16
15
|
]
|
|
17
16
|
// bootstrap
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import '@material/web/icon/icon.js'
|
|
2
|
-
import '@operato/data-grist'
|
|
3
|
-
|
|
4
|
-
import gql from 'graphql-tag'
|
|
5
|
-
import { css, html, LitElement } from 'lit'
|
|
6
|
-
import { property } from 'lit/decorators.js'
|
|
7
|
-
|
|
8
|
-
import { client } from '@operato/graphql'
|
|
9
|
-
import { i18next } from '@operato/i18n'
|
|
10
|
-
import { isMobileDevice } from '@operato/utils'
|
|
11
|
-
import { ButtonContainerStyles } from '@operato/styles'
|
|
12
|
-
|
|
13
|
-
export class KpiGradeImporter extends LitElement {
|
|
14
|
-
static styles = [
|
|
15
|
-
ButtonContainerStyles,
|
|
16
|
-
css`
|
|
17
|
-
:host {
|
|
18
|
-
display: flex;
|
|
19
|
-
flex-direction: column;
|
|
20
|
-
|
|
21
|
-
background-color: #fff;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
ox-grist {
|
|
25
|
-
flex: 1;
|
|
26
|
-
}
|
|
27
|
-
`
|
|
28
|
-
]
|
|
29
|
-
|
|
30
|
-
@property({ type: Array }) kpiGrades: any[] = []
|
|
31
|
-
@property({ type: Object }) columns = {
|
|
32
|
-
list: { fields: ['name', 'description'] },
|
|
33
|
-
pagination: { infinite: true },
|
|
34
|
-
columns: [
|
|
35
|
-
{
|
|
36
|
-
type: 'string',
|
|
37
|
-
name: 'name',
|
|
38
|
-
header: i18next.t('field.name'),
|
|
39
|
-
width: 150
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
type: 'string',
|
|
43
|
-
name: 'description',
|
|
44
|
-
header: i18next.t('field.description'),
|
|
45
|
-
width: 200
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
type: 'checkbox',
|
|
49
|
-
name: 'active',
|
|
50
|
-
header: i18next.t('field.active'),
|
|
51
|
-
width: 60
|
|
52
|
-
}
|
|
53
|
-
]
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
render() {
|
|
58
|
-
return html`
|
|
59
|
-
<ox-grist
|
|
60
|
-
.mode=${isMobileDevice() ? 'LIST' : 'GRID'}
|
|
61
|
-
.config=${this.columns}
|
|
62
|
-
.data=${
|
|
63
|
-
{
|
|
64
|
-
records: this.kpiGrades
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
></ox-grist>
|
|
68
|
-
|
|
69
|
-
<div class="button-container">
|
|
70
|
-
<button @click="${this.save.bind(this)}"><md-icon>save</md-icon>${i18next.t('button.save')}</button>
|
|
71
|
-
</div>
|
|
72
|
-
`
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
async save() {
|
|
76
|
-
const response = await client.mutate({
|
|
77
|
-
mutation: gql`
|
|
78
|
-
mutation importKpiGrades($kpiGrades: [KpiGradePatch!]!) {
|
|
79
|
-
importKpiGrades(kpiGrades: $kpiGrades)
|
|
80
|
-
}
|
|
81
|
-
`,
|
|
82
|
-
variables: { kpiGrades: this.kpiGrades }
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
if (response.errors?.length) return
|
|
86
|
-
|
|
87
|
-
this.dispatchEvent(new CustomEvent('imported'))
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|