@dssp/dkpi 1.0.0-alpha.80 → 1.0.0-alpha.82
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/dist-client/bootstrap.d.ts +1 -0
- package/dist-client/bootstrap.js +11 -0
- package/dist-client/bootstrap.js.map +1 -1
- package/dist-client/components/kpi-single-boxplot-chart.d.ts +3 -2
- package/dist-client/components/kpi-single-boxplot-chart.js +30 -23
- package/dist-client/components/kpi-single-boxplot-chart.js.map +1 -1
- package/dist-client/pages/component/project-update-header.d.ts +1 -0
- package/dist-client/pages/component/project-update-header.js +127 -0
- package/dist-client/pages/component/project-update-header.js.map +1 -0
- package/dist-client/pages/kpi-admin/kpi-system-guide.d.ts +1 -1
- package/dist-client/pages/kpi-admin/kpi-system-guide.js +29 -21
- package/dist-client/pages/kpi-admin/kpi-system-guide.js.map +1 -1
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.d.ts +1 -1
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.js +1 -1
- package/dist-client/pages/kpi-metric-value/kpi-metric-value-list-page.js.map +1 -1
- package/dist-client/pages/kpi-value/kpi-value-list-page.js +1 -1
- package/dist-client/pages/kpi-value/kpi-value-list-page.js.map +1 -1
- package/dist-client/pages/project-complete-tabs/pc-tab1-plan.d.ts +21 -2
- package/dist-client/pages/project-complete-tabs/pc-tab1-plan.js +166 -134
- package/dist-client/pages/project-complete-tabs/pc-tab1-plan.js.map +1 -1
- package/dist-client/pages/project-complete-tabs/pc-tab2-rating.d.ts +4 -2
- package/dist-client/pages/project-complete-tabs/pc-tab2-rating.js +109 -44
- package/dist-client/pages/project-complete-tabs/pc-tab2-rating.js.map +1 -1
- package/dist-client/pages/project-complete-tabs/pc-tab3-upload.d.ts +3 -0
- package/dist-client/pages/project-complete-tabs/pc-tab3-upload.js +32 -4
- package/dist-client/pages/project-complete-tabs/pc-tab3-upload.js.map +1 -1
- package/dist-client/pages/project-complete-tabs/pc-tab4-monthly.d.ts +24 -0
- package/dist-client/pages/project-complete-tabs/pc-tab4-monthly.js +365 -157
- package/dist-client/pages/project-complete-tabs/pc-tab4-monthly.js.map +1 -1
- package/dist-client/pages/sv-project-complete.d.ts +4 -1
- package/dist-client/pages/sv-project-complete.js +43 -12
- package/dist-client/pages/sv-project-complete.js.map +1 -1
- package/dist-client/pages/sv-project-completed-list.js +3 -3
- package/dist-client/pages/sv-project-completed-list.js.map +1 -1
- package/dist-client/pages/sv-project-detail.d.ts +11 -0
- package/dist-client/pages/sv-project-detail.js +188 -46
- package/dist-client/pages/sv-project-detail.js.map +1 -1
- package/dist-client/pages/sv-project-list.d.ts +10 -0
- package/dist-client/pages/sv-project-list.js +96 -6
- package/dist-client/pages/sv-project-list.js.map +1 -1
- package/dist-client/pages/sv-project-update.d.ts +86 -0
- package/dist-client/pages/sv-project-update.js +1121 -0
- package/dist-client/pages/sv-project-update.js.map +1 -0
- package/dist-client/route.d.ts +1 -1
- package/dist-client/route.js +3 -0
- package/dist-client/route.js.map +1 -1
- package/dist-client/shared/complete-api.d.ts +10 -9
- package/dist-client/shared/complete-api.js +47 -19
- package/dist-client/shared/complete-api.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-client/viewparts/menu-tools.js +47 -54
- package/dist-client/viewparts/menu-tools.js.map +1 -1
- package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.d.ts +23 -0
- package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.js +72 -28
- package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.js.map +1 -1
- package/dist-server/service/kpi-metric-value/kpi-metric-value-query.js +9 -2
- package/dist-server/service/kpi-metric-value/kpi-metric-value-query.js.map +1 -1
- package/dist-server/service/kpi-stat/kpi-stat-query.js +19 -18
- package/dist-server/service/kpi-stat/kpi-stat-query.js.map +1 -1
- package/dist-server/service/kpi-value/kpi-value-query.js +2 -2
- 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/schema.graphql +13 -1
- package/things-factory.config.js +1 -0
- package/dist-client/shared/domain-context.d.ts +0 -7
- package/dist-client/shared/domain-context.js +0 -13
- package/dist-client/shared/domain-context.js.map +0 -1
|
@@ -7,6 +7,7 @@ import { getKpiMetricValues, getKpiMetrics, updateProjectCompleteStep1, collectP
|
|
|
7
7
|
import { INTEGRATION_SOURCES } from '../../shared/integration-fetch';
|
|
8
8
|
import moment from 'moment-timezone';
|
|
9
9
|
import { notify } from '@operato/layout';
|
|
10
|
+
import { hasPrivilege } from '@things-factory/auth-base/dist-client';
|
|
10
11
|
const KPI_METRIC_KEY_MAPPING = [
|
|
11
12
|
{ label: '공사기간', projectKey: 'constructionPeriod' },
|
|
12
13
|
{ label: '총 근로자수', projectKey: 'totalWorkerCount' },
|
|
@@ -24,12 +25,6 @@ const KPI_METRIC_KEY_MAPPING = [
|
|
|
24
25
|
];
|
|
25
26
|
// 계획값이 없는 것들 (실제값으로 표시할 필드)
|
|
26
27
|
const NO_PLAN_FIELDS = ['workerCount', 'area', 'floorAreaRatio', 'designChangeCount', 'upperFloorCount', 'lowerFloorCount'];
|
|
27
|
-
const EXCLUDE_FIELDS = [
|
|
28
|
-
{ id: 'b7a583c0-9de9-4c12-af49-dde65198dfce', name: '계획공사비' },
|
|
29
|
-
{ id: '9767747a-d2ec-4e36-96c4-4a78bba35b98', name: '실제공사비' },
|
|
30
|
-
{ id: '036d6e1c-193e-46bd-8d6e-93f248af8124', name: '계획공사기간' },
|
|
31
|
-
{ id: '5df77618-db44-4da3-a22d-43c067cb5e86', name: '실제공사기간' }
|
|
32
|
-
];
|
|
33
28
|
let SvProjectCompleteTab1Plan = SvProjectCompleteTab1Plan_1 = class SvProjectCompleteTab1Plan extends LitElement {
|
|
34
29
|
constructor() {
|
|
35
30
|
super(...arguments);
|
|
@@ -50,8 +45,14 @@ let SvProjectCompleteTab1Plan = SvProjectCompleteTab1Plan_1 = class SvProjectCom
|
|
|
50
45
|
/** 방금 채워진 셀 강조용 (metricId 또는 plan:projectKey) */
|
|
51
46
|
this.justFilled = {};
|
|
52
47
|
this.collecting = false;
|
|
48
|
+
/** kpi:input — Step1 계획값 입력/저장 권한 */
|
|
49
|
+
this.canSave = false;
|
|
50
|
+
/** kpi:auto-collect — 외부 시스템 자동 수집 권한 */
|
|
51
|
+
this.canAutoCollect = false;
|
|
53
52
|
}
|
|
54
53
|
render() {
|
|
54
|
+
// 전월 (last month) YYYY-MM. 월별 metric 의 "전월 데이터 입력" 검사 기준.
|
|
55
|
+
const lastMonth = moment().tz('Asia/Seoul').subtract(1, 'month').format('YYYY-MM');
|
|
55
56
|
return html `
|
|
56
57
|
<div class="title">
|
|
57
58
|
<div>
|
|
@@ -73,12 +74,18 @@ let SvProjectCompleteTab1Plan = SvProjectCompleteTab1Plan_1 = class SvProjectCom
|
|
|
73
74
|
|
|
74
75
|
${this.kpiMetrics.map(metric => {
|
|
75
76
|
var _a, _b, _c, _d, _e;
|
|
76
|
-
// 제외 필드는 표시하지 않음
|
|
77
|
-
if (EXCLUDE_FIELDS.some(item => item.id === metric.id))
|
|
78
|
-
return null;
|
|
79
77
|
// 상수로 정의된 매핑 정보에서 metric.name으로 projectKey를 찾음
|
|
80
78
|
const projectKey = ((_a = KPI_METRIC_KEY_MAPPING.find(item => item.label === metric.name)) === null || _a === void 0 ? void 0 : _a.projectKey) || '';
|
|
81
|
-
const
|
|
79
|
+
const isMonthly = metric.periodType === 'MONTH';
|
|
80
|
+
// 현재값 lookup:
|
|
81
|
+
// MONTH → 전월 row (단일 진실원)
|
|
82
|
+
// ALLTIME → 단일 ALLTIME row
|
|
83
|
+
// 그 외 → metricId+periodType 매칭
|
|
84
|
+
const kpiMetricValue = isMonthly
|
|
85
|
+
? this.kpiMetricValues.find((item) => item.metricId === metric.id &&
|
|
86
|
+
item.periodType === 'MONTH' &&
|
|
87
|
+
(item.valueDate || '').startsWith(lastMonth)) || {}
|
|
88
|
+
: this.kpiMetricValues.find((item) => item.metricId === metric.id && item.periodType === metric.periodType) || {};
|
|
82
89
|
const isDisplayInput = projectKey === 'constructionPeriod' || projectKey === 'constructionCost'; // 유효한 계획 필드
|
|
83
90
|
// planValue는 project에서 찾고 없으면 buildingComplex의 값에서 찾음
|
|
84
91
|
const basePlanValue = this.project[projectKey] || ((_c = (_b = this.project) === null || _b === void 0 ? void 0 : _b.buildingComplex) === null || _c === void 0 ? void 0 : _c[projectKey]) || 0;
|
|
@@ -106,29 +113,48 @@ let SvProjectCompleteTab1Plan = SvProjectCompleteTab1Plan_1 = class SvProjectCom
|
|
|
106
113
|
const effDiff = calcDiff(shownPlan, actualValue);
|
|
107
114
|
const effDiffClass = effDiff === 0 ? '' : effDiff > 0 ? 'plus' : 'minus';
|
|
108
115
|
const effDiffSign = effDiff === 0 ? '' : effDiff > 0 ? '+' : '-';
|
|
116
|
+
// periodType 무관하게 값 없으면 pending. 메시지는 MONTH 만 "전월 데이터 입력 필요".
|
|
117
|
+
const isPending = kpiMetricValue.value === undefined || kpiMetricValue.value === null;
|
|
109
118
|
return html `<div class="row">
|
|
110
|
-
<div class="label
|
|
119
|
+
<div class="label ${isPending ? 'pending' : ''}"
|
|
120
|
+
title=${isPending
|
|
121
|
+
? isMonthly
|
|
122
|
+
? '전월 데이터 입력 필요'
|
|
123
|
+
: '아직 입력되지 않은 항목'
|
|
124
|
+
: ''}>• ${metric.name}</div>
|
|
111
125
|
<div class="cell ${planFilled ? 'just-filled' : ''}">
|
|
112
126
|
${isDisplayInput
|
|
113
|
-
? html `<input
|
|
127
|
+
? html `<input
|
|
128
|
+
numeric
|
|
129
|
+
.value=${shownPlan !== null && shownPlan !== void 0 ? shownPlan : 0}
|
|
130
|
+
@input=${(e) => this._onPlanInputChange(e, metric, projectKey)}
|
|
131
|
+
/>
|
|
132
|
+
${unit}
|
|
114
133
|
${planSrc ? html `<span class="src-chip">🔗 ${planSrc}</span>` : ''}`
|
|
115
134
|
: html `-`}
|
|
116
135
|
</div>
|
|
117
|
-
<div class="cell ${this.justFilled[metric.id] ? 'just-filled' : ''}">
|
|
136
|
+
<div class="cell ${this.justFilled[metric.id] ? 'just-filled' : ''} ${isPending ? 'pending' : ''}">
|
|
118
137
|
<input numeric .value=${actualValue !== null && actualValue !== void 0 ? actualValue : 0} @input=${(e) => this._onInputChange(e, metric)} />
|
|
119
138
|
${unit}
|
|
120
139
|
${src ? html `<span class="src-chip">🔗 ${src}</span>` : ''}
|
|
121
140
|
</div>
|
|
122
|
-
<div class="unit ${isDisplayInput ? effDiffClass :
|
|
123
|
-
${isDisplayInput
|
|
124
|
-
|
|
141
|
+
<div class="unit ${isDisplayInput ? effDiffClass : ''}">
|
|
142
|
+
${isDisplayInput
|
|
143
|
+
? html `${effDiffSign} ${Math.abs(effDiff).toLocaleString()} ${unit}`
|
|
144
|
+
: ''}
|
|
125
145
|
</div>
|
|
126
146
|
</div>`;
|
|
127
147
|
})}
|
|
128
148
|
|
|
129
149
|
<div class="button-line">
|
|
130
150
|
<div class="ghost-btn" @click=${this._reset}>초기화</div>
|
|
131
|
-
<div
|
|
151
|
+
<div
|
|
152
|
+
class="ghost-btn secondary ${this.canSave ? '' : 'disabled'}"
|
|
153
|
+
title=${this.canSave ? '' : 'kpi:input 권한 필요'}
|
|
154
|
+
@click=${() => this.canSave && this._save()}
|
|
155
|
+
>
|
|
156
|
+
저장
|
|
157
|
+
</div>
|
|
132
158
|
</div>
|
|
133
159
|
</div>
|
|
134
160
|
`;
|
|
@@ -139,9 +165,10 @@ let SvProjectCompleteTab1Plan = SvProjectCompleteTab1Plan_1 = class SvProjectCom
|
|
|
139
165
|
<div class="collect-head">
|
|
140
166
|
<div class="ttl">시스템 연동 자동 수집 <small>외부 시스템에서 기본정보를 가져옵니다</small></div>
|
|
141
167
|
<div
|
|
142
|
-
class="collect-btn"
|
|
168
|
+
class="collect-btn ${this.canAutoCollect ? '' : 'disabled'}"
|
|
143
169
|
?disabled=${this.collecting}
|
|
144
|
-
|
|
170
|
+
title=${this.canAutoCollect ? '' : 'kpi:auto-collect 권한 필요'}
|
|
171
|
+
@click=${() => this.canAutoCollect && !this.collecting ? this._autoCollect() : null}
|
|
145
172
|
>
|
|
146
173
|
${this.collecting ? '수집 중…' : '⟳ 자동 수집'}
|
|
147
174
|
</div>
|
|
@@ -226,9 +253,17 @@ let SvProjectCompleteTab1Plan = SvProjectCompleteTab1Plan_1 = class SvProjectCom
|
|
|
226
253
|
// KPI_METRIC_KEY_MAPPING 매칭 키 → form 채움. 미매칭 키 (siteType,
|
|
227
254
|
// structureType 등 프로젝트/BuildingComplex 기본 속성) → 카드 요약만.
|
|
228
255
|
const summary = [];
|
|
256
|
+
// KPI_METRIC_KEY_MAPPING 에 없는 키들의 한글 라벨. BuildingComplex/Project 직접 속성용.
|
|
229
257
|
const NON_KPI_LABEL = {
|
|
230
258
|
siteType: '건축현장유형',
|
|
231
|
-
structureType: '구조형태'
|
|
259
|
+
structureType: '구조형태',
|
|
260
|
+
coverageRatio: '건폐율',
|
|
261
|
+
householdCount: '세대수',
|
|
262
|
+
buildingCount: '동수',
|
|
263
|
+
startDate: '착공일',
|
|
264
|
+
endDate: '준공일',
|
|
265
|
+
permitDate: '건축허가일',
|
|
266
|
+
bldNm: '건물명'
|
|
232
267
|
};
|
|
233
268
|
for (const [projectKey, rawValue] of Object.entries(r.data || {})) {
|
|
234
269
|
if (rawValue === null || rawValue === undefined || rawValue === '')
|
|
@@ -252,10 +287,14 @@ let SvProjectCompleteTab1Plan = SvProjectCompleteTab1Plan_1 = class SvProjectCom
|
|
|
252
287
|
if (!mapping)
|
|
253
288
|
continue;
|
|
254
289
|
if (isPlan) {
|
|
255
|
-
// 계획값 (키스콘) — 계획
|
|
290
|
+
// 계획값 (키스콘 제공) — 계획 칼럼 표시 + KpiMetric value 에도 upsert
|
|
291
|
+
// 하여 _save 시 patches 에 포함. 사용자가 계획 input 직접 편집해도 같은 흐름.
|
|
256
292
|
this.collectedPlan = Object.assign(Object.assign({}, this.collectedPlan), { [projectKey]: numericValue });
|
|
257
293
|
this.planSources = Object.assign(Object.assign({}, this.planSources), { [projectKey]: r.label });
|
|
258
294
|
this.justFilled = Object.assign(Object.assign({}, this.justFilled), { [`plan:${projectKey}`]: true });
|
|
295
|
+
const planMetric = this.kpiMetrics.find((m) => { var _a; return ((_a = KPI_METRIC_KEY_MAPPING.find(x => x.label === m.name)) === null || _a === void 0 ? void 0 : _a.projectKey) === projectKey; });
|
|
296
|
+
if (planMetric)
|
|
297
|
+
this._setMetricValue(planMetric, numericValue);
|
|
259
298
|
}
|
|
260
299
|
else {
|
|
261
300
|
// 실제값 (세움터/올바로/기타) — 매칭되는 metric 의 실제 칼럼에 반영
|
|
@@ -305,26 +344,20 @@ let SvProjectCompleteTab1Plan = SvProjectCompleteTab1Plan_1 = class SvProjectCom
|
|
|
305
344
|
this.collecting = false;
|
|
306
345
|
}
|
|
307
346
|
}
|
|
308
|
-
/**
|
|
347
|
+
/**
|
|
348
|
+
* 메트릭의 실제값 set/add — _onInputChange 와 동일하게 periodType 별 row upsert.
|
|
349
|
+
*/
|
|
309
350
|
_setMetricValue(metric, value) {
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
metricId: metric.id,
|
|
321
|
-
unit: metric.unit || '',
|
|
322
|
-
org: this.project.id,
|
|
323
|
-
periodType: metric.periodType,
|
|
324
|
-
valueDate: moment().tz('Asia/Seoul').format('YYYY-MM-DD')
|
|
325
|
-
}
|
|
326
|
-
];
|
|
327
|
-
}
|
|
351
|
+
this.kpiMetricValues = this._upsertValueForMetric(this.kpiMetricValues, metric, value);
|
|
352
|
+
}
|
|
353
|
+
async connectedCallback() {
|
|
354
|
+
super.connectedCallback();
|
|
355
|
+
const [canSave, canAutoCollect] = await Promise.all([
|
|
356
|
+
hasPrivilege({ category: 'kpi', privilege: 'input', domainOwnerGranted: true, superUserGranted: true }),
|
|
357
|
+
hasPrivilege({ category: 'kpi', privilege: 'auto-collect', domainOwnerGranted: true, superUserGranted: true })
|
|
358
|
+
]);
|
|
359
|
+
this.canSave = canSave;
|
|
360
|
+
this.canAutoCollect = canAutoCollect;
|
|
328
361
|
}
|
|
329
362
|
willUpdate(changedProperties) {
|
|
330
363
|
var _a;
|
|
@@ -335,9 +368,22 @@ let SvProjectCompleteTab1Plan = SvProjectCompleteTab1Plan_1 = class SvProjectCom
|
|
|
335
368
|
}
|
|
336
369
|
}
|
|
337
370
|
async _getInitData() {
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
371
|
+
var _a, _b;
|
|
372
|
+
// getKpiMetrics 가 이미 active=true 만 반환. 여기선 평가 (Step2) 만 추가 제외.
|
|
373
|
+
// 비활성화는 KPI 관리 admin 에서 active=false 처리.
|
|
374
|
+
const kpiMetrics = await getKpiMetrics((_a = this.project) === null || _a === void 0 ? void 0 : _a.code);
|
|
375
|
+
this.kpiMetrics = kpiMetrics.filter(item => !item.name.includes('평가')) || [];
|
|
376
|
+
this.kpiMetricValues = await getKpiMetricValues(this.project.id, (_b = this.project) === null || _b === void 0 ? void 0 : _b.code);
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* 공사비/공사기간 의 "계획" 컬럼 input 편집 핸들러 — collectedPlan 표시값 동기화 +
|
|
380
|
+
* 해당 KpiMetric 의 value 도 upsert 하여 _save 시 patches 에 포함되도록.
|
|
381
|
+
*/
|
|
382
|
+
_onPlanInputChange(event, metric, projectKey) {
|
|
383
|
+
const target = event.target;
|
|
384
|
+
const inputVal = Number(target.value.replace(/[^\d.]/g, ''));
|
|
385
|
+
this.collectedPlan = Object.assign(Object.assign({}, this.collectedPlan), { [projectKey]: inputVal });
|
|
386
|
+
this.kpiMetricValues = this._upsertValueForMetric(this.kpiMetricValues, metric, inputVal);
|
|
341
387
|
}
|
|
342
388
|
// Input 요소의 값이 변경될 때 호출되는 콜백 함수
|
|
343
389
|
_onInputChange(event, metric) {
|
|
@@ -347,105 +393,68 @@ let SvProjectCompleteTab1Plan = SvProjectCompleteTab1Plan_1 = class SvProjectCom
|
|
|
347
393
|
if (target.hasAttribute('numeric')) {
|
|
348
394
|
inputVal = Number(inputVal.replace(/[^\d.]/g, ''));
|
|
349
395
|
}
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
396
|
+
this.kpiMetricValues = this._upsertValueForMetric(this.kpiMetricValues, metric, inputVal);
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* metric.periodType 에 따라 적절한 row 를 upsert.
|
|
400
|
+
* - MONTH : 전월 row (YYYY-MM-01). "오늘 입력 = 지난달 데이터" 운영 원칙.
|
|
401
|
+
* - ALLTIME : 단일 row (valueDate sentinel 무관).
|
|
402
|
+
* - 그 외 (DAY/WEEK/QUARTER/YEAR/RANGE) : metric.periodType 그대로, valueDate=today.
|
|
403
|
+
* 매칭은 (metricId, periodType) 기준 — MONTH 만 추가로 valueDate prefix.
|
|
404
|
+
*/
|
|
405
|
+
_upsertValueForMetric(values, metric, value) {
|
|
406
|
+
const today = moment().tz('Asia/Seoul');
|
|
407
|
+
const todayYmd = today.format('YYYY-MM-DD');
|
|
408
|
+
const periodType = metric.periodType;
|
|
409
|
+
let updated = [...values];
|
|
410
|
+
if (periodType === 'MONTH') {
|
|
411
|
+
const lastMonth = today.clone().subtract(1, 'month');
|
|
412
|
+
const lastMonth1 = lastMonth.format('YYYY-MM-01');
|
|
413
|
+
const lastMonthYm = lastMonth.format('YYYY-MM');
|
|
414
|
+
const idx = updated.findIndex((i) => i.metricId === metric.id &&
|
|
415
|
+
i.periodType === 'MONTH' &&
|
|
416
|
+
(i.valueDate || '').startsWith(lastMonthYm));
|
|
417
|
+
if (idx !== -1) {
|
|
418
|
+
updated[idx] = Object.assign(Object.assign({}, updated[idx]), { value });
|
|
419
|
+
}
|
|
420
|
+
else {
|
|
421
|
+
updated.push({
|
|
361
422
|
id: crypto.randomUUID(),
|
|
362
|
-
value
|
|
423
|
+
value,
|
|
363
424
|
metricId: metric.id,
|
|
364
425
|
unit: metric.unit || '',
|
|
365
|
-
org: this.project.id,
|
|
366
|
-
periodType:
|
|
367
|
-
valueDate:
|
|
368
|
-
}
|
|
369
|
-
];
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
_generateExcludeFieldsData() {
|
|
373
|
-
var _a, _b;
|
|
374
|
-
const excludeData = [];
|
|
375
|
-
const constructionCostMetric = this.kpiMetrics.find(metric => { var _a; return ((_a = KPI_METRIC_KEY_MAPPING.find(item => item.label === metric.name)) === null || _a === void 0 ? void 0 : _a.projectKey) === 'constructionCost'; });
|
|
376
|
-
// 공사기간 관련 메트릭을 찾음
|
|
377
|
-
const constructionPeriodMetric = this.kpiMetrics.find(metric => { var _a; return ((_a = KPI_METRIC_KEY_MAPPING.find(item => item.label === metric.name)) === null || _a === void 0 ? void 0 : _a.projectKey) === 'constructionPeriod'; });
|
|
378
|
-
// 실적공사비 값 (사용자가 입력한 constructionCost 값)
|
|
379
|
-
const actualConstructionCostValue = constructionCostMetric
|
|
380
|
-
? ((_a = this.kpiMetricValues.find((item) => item.metricId === constructionCostMetric.id)) === null || _a === void 0 ? void 0 : _a.value) || 0
|
|
381
|
-
: 0;
|
|
382
|
-
// 실제공사기간 값 (사용자가 입력한 constructionPeriod 값)
|
|
383
|
-
const actualConstructionPeriodValue = constructionPeriodMetric
|
|
384
|
-
? ((_b = this.kpiMetricValues.find((item) => item.metricId === constructionPeriodMetric.id)) === null || _b === void 0 ? void 0 : _b.value) || 0
|
|
385
|
-
: 0;
|
|
386
|
-
EXCLUDE_FIELDS.forEach(excludeField => {
|
|
387
|
-
var _a, _b, _c, _d, _e;
|
|
388
|
-
// 해당 excludeField.id에 매칭되는 메트릭 찾기
|
|
389
|
-
const metric = this.kpiMetrics.find(m => m.id === excludeField.id);
|
|
390
|
-
// 기존에 저장된 데이터가 있는지 확인
|
|
391
|
-
const existingValue = this.kpiMetricValues.find((item) => item.metricId === excludeField.id);
|
|
392
|
-
let value = 0;
|
|
393
|
-
if (excludeField.name == '계획공사비') {
|
|
394
|
-
// 키스콘 수집 계획값 우선, 없으면 buildingComplex
|
|
395
|
-
value = (_d = (_a = this.collectedPlan['constructionCost']) !== null && _a !== void 0 ? _a : (_c = (_b = this.project) === null || _b === void 0 ? void 0 : _b.buildingComplex) === null || _c === void 0 ? void 0 : _c.constructionCost) !== null && _d !== void 0 ? _d : 0;
|
|
396
|
-
}
|
|
397
|
-
else if (excludeField.name == '실제공사비') {
|
|
398
|
-
value = actualConstructionCostValue;
|
|
426
|
+
org: this.project.id,
|
|
427
|
+
periodType: 'MONTH',
|
|
428
|
+
valueDate: lastMonth1
|
|
429
|
+
});
|
|
399
430
|
}
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
431
|
+
}
|
|
432
|
+
else {
|
|
433
|
+
// ALLTIME / DAY / WEEK / QUARTER / YEAR / RANGE — metric.periodType 그대로 단일 row upsert.
|
|
434
|
+
const idx = updated.findIndex((i) => i.metricId === metric.id && i.periodType === periodType);
|
|
435
|
+
if (idx !== -1) {
|
|
436
|
+
updated[idx] = Object.assign(Object.assign({}, updated[idx]), { value });
|
|
404
437
|
}
|
|
405
|
-
else
|
|
406
|
-
|
|
438
|
+
else {
|
|
439
|
+
updated.push({
|
|
440
|
+
id: crypto.randomUUID(),
|
|
441
|
+
value,
|
|
442
|
+
metricId: metric.id,
|
|
443
|
+
unit: metric.unit || '',
|
|
444
|
+
org: this.project.id,
|
|
445
|
+
periodType,
|
|
446
|
+
valueDate: todayYmd
|
|
447
|
+
});
|
|
407
448
|
}
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
value,
|
|
411
|
-
metricId: excludeField.id,
|
|
412
|
-
unit: (metric === null || metric === void 0 ? void 0 : metric.unit) || '',
|
|
413
|
-
org: this.project.id,
|
|
414
|
-
periodType: metric === null || metric === void 0 ? void 0 : metric.periodType,
|
|
415
|
-
valueDate: moment().tz('Asia/Seoul').format('YYYY-MM-DD')
|
|
416
|
-
});
|
|
417
|
-
});
|
|
418
|
-
return excludeData;
|
|
449
|
+
}
|
|
450
|
+
return updated;
|
|
419
451
|
}
|
|
420
452
|
async _save() {
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
//
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
const filteredKpiMetricValues = this.kpiMetricValues.filter((item) => !excludeMetricIds.includes(item.metricId));
|
|
427
|
-
// 기본 실제값 엔트리 생성 (사용자 입력이 없는 메트릭에 대해 기본값 채움)
|
|
428
|
-
const existingMetricIds = new Set(filteredKpiMetricValues.map((item) => item.metricId));
|
|
429
|
-
const defaultActualData = this.kpiMetrics
|
|
430
|
-
.filter(metric => !EXCLUDE_FIELDS.some(field => field.id === metric.id) && !existingMetricIds.has(metric.id))
|
|
431
|
-
.map(metric => {
|
|
432
|
-
var _a, _b, _c;
|
|
433
|
-
const projectKey = ((_a = KPI_METRIC_KEY_MAPPING.find(item => item.label === metric.name)) === null || _a === void 0 ? void 0 : _a.projectKey) || '';
|
|
434
|
-
const basePlanValue = this.project[projectKey] || ((_c = (_b = this.project) === null || _b === void 0 ? void 0 : _b.buildingComplex) === null || _c === void 0 ? void 0 : _c[projectKey]) || 0;
|
|
435
|
-
const value = NO_PLAN_FIELDS.includes(projectKey) ? basePlanValue : 0;
|
|
436
|
-
return {
|
|
437
|
-
id: crypto.randomUUID(),
|
|
438
|
-
value,
|
|
439
|
-
metricId: metric.id,
|
|
440
|
-
unit: metric.unit || '',
|
|
441
|
-
org: this.project.id,
|
|
442
|
-
periodType: metric.periodType,
|
|
443
|
-
valueDate: moment().tz('Asia/Seoul').format('YYYY-MM-DD')
|
|
444
|
-
};
|
|
445
|
-
});
|
|
446
|
-
// 필터링된 데이터와 exclude 데이터, 기본 실제값 데이터를 합침
|
|
447
|
-
const allKpiMetricValues = [...excludeData, ...filteredKpiMetricValues, ...defaultActualData];
|
|
448
|
-
const response = await updateProjectCompleteStep1(allKpiMetricValues);
|
|
453
|
+
var _a;
|
|
454
|
+
// 사용자가 실제 편집한 row 만 저장. 자동 산정 metric (계획/실제 공사비·공기) 는
|
|
455
|
+
// 별도의 데이터 출처(project 정보, 키스콘 수집, 다른 metric 의 전월 row 등)에서
|
|
456
|
+
// 도출돼야 하므로 이 화면에서 하드코딩 derive 하지 않음.
|
|
457
|
+
const response = await updateProjectCompleteStep1(this.kpiMetricValues, (_a = this.project) === null || _a === void 0 ? void 0 : _a.code);
|
|
449
458
|
if (!response.errors) {
|
|
450
459
|
notify({ message: '저장되었습니다.' });
|
|
451
460
|
}
|
|
@@ -550,6 +559,11 @@ SvProjectCompleteTab1Plan.styles = [
|
|
|
550
559
|
.ghost-btn.secondary {
|
|
551
560
|
background: #24be7b;
|
|
552
561
|
}
|
|
562
|
+
.ghost-btn.disabled,
|
|
563
|
+
.collect-btn.disabled {
|
|
564
|
+
opacity: 0.45;
|
|
565
|
+
cursor: not-allowed;
|
|
566
|
+
}
|
|
553
567
|
|
|
554
568
|
/* ── 자동 수집 패널 ── */
|
|
555
569
|
.collect-panel {
|
|
@@ -693,6 +707,16 @@ SvProjectCompleteTab1Plan.styles = [
|
|
|
693
707
|
.cell.just-filled input {
|
|
694
708
|
animation: flash 1.1s ease;
|
|
695
709
|
}
|
|
710
|
+
/* 미입력 — kpiMetricValue 가 비어있는 metric 행 */
|
|
711
|
+
.label.pending::before {
|
|
712
|
+
content: '⚠';
|
|
713
|
+
color: #e74c3c;
|
|
714
|
+
margin-right: 4px;
|
|
715
|
+
}
|
|
716
|
+
.cell.pending input {
|
|
717
|
+
border-color: #e74c3c;
|
|
718
|
+
background-color: #fff5f5;
|
|
719
|
+
}
|
|
696
720
|
@keyframes flash {
|
|
697
721
|
0% {
|
|
698
722
|
background: #fff7cc;
|
|
@@ -749,6 +773,14 @@ __decorate([
|
|
|
749
773
|
state(),
|
|
750
774
|
__metadata("design:type", Object)
|
|
751
775
|
], SvProjectCompleteTab1Plan.prototype, "collecting", void 0);
|
|
776
|
+
__decorate([
|
|
777
|
+
state(),
|
|
778
|
+
__metadata("design:type", Object)
|
|
779
|
+
], SvProjectCompleteTab1Plan.prototype, "canSave", void 0);
|
|
780
|
+
__decorate([
|
|
781
|
+
state(),
|
|
782
|
+
__metadata("design:type", Object)
|
|
783
|
+
], SvProjectCompleteTab1Plan.prototype, "canAutoCollect", void 0);
|
|
752
784
|
SvProjectCompleteTab1Plan = SvProjectCompleteTab1Plan_1 = __decorate([
|
|
753
785
|
customElement('sv-pc-tab1-plan')
|
|
754
786
|
], SvProjectCompleteTab1Plan);
|