@dssp/dkpi 1.0.30 → 1.0.33

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 (24) hide show
  1. package/dist-client/pages/project-complete-tabs/pc-tab1-plan.d.ts +3 -10
  2. package/dist-client/pages/project-complete-tabs/pc-tab1-plan.js +63 -79
  3. package/dist-client/pages/project-complete-tabs/pc-tab1-plan.js.map +1 -1
  4. package/dist-client/pages/project-complete-tabs/pc-tab2-rating.d.ts +2 -0
  5. package/dist-client/pages/project-complete-tabs/pc-tab2-rating.js +28 -11
  6. package/dist-client/pages/project-complete-tabs/pc-tab2-rating.js.map +1 -1
  7. package/dist-client/pages/project-complete-tabs/pc-tab3-upload.d.ts +2 -0
  8. package/dist-client/pages/project-complete-tabs/pc-tab3-upload.js +42 -16
  9. package/dist-client/pages/project-complete-tabs/pc-tab3-upload.js.map +1 -1
  10. package/dist-client/pages/project-complete-tabs/pc-tab4-monthly.d.ts +2 -1
  11. package/dist-client/pages/project-complete-tabs/pc-tab4-monthly.js +31 -20
  12. package/dist-client/pages/project-complete-tabs/pc-tab4-monthly.js.map +1 -1
  13. package/dist-client/pages/sv-project-detail.js +34 -27
  14. package/dist-client/pages/sv-project-detail.js.map +1 -1
  15. package/dist-client/pages/sv-project-update.d.ts +2 -3
  16. package/dist-client/pages/sv-project-update.js +156 -17
  17. package/dist-client/pages/sv-project-update.js.map +1 -1
  18. package/dist-client/tsconfig.tsbuildinfo +1 -1
  19. package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.d.ts +6 -1
  20. package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.js +12 -15
  21. package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.js.map +1 -1
  22. package/dist-server/tsconfig.tsbuildinfo +1 -1
  23. package/package.json +3 -3
  24. package/schema.graphql +1932 -125
@@ -9,24 +9,22 @@ export declare class SvProjectCompleteTab1Plan extends LitElement {
9
9
  collectError: Record<string, string>;
10
10
  /** 자동 수집한 실제값의 출처: metricId → 시스템 라벨 */
11
11
  valueSources: Record<string, string>;
12
- /** 자동 수집한 계획값: projectKey → 값 (키스콘이 제공하는 계획공사기간/계획공사비) */
13
- collectedPlan: Record<string, number>;
14
12
  /** 계획값 출처: projectKey → 시스템 라벨 */
15
- planSources: Record<string, string>;
16
13
  /** 시스템별 수집 요약 (카드 표시용): 라벨 → [{label, text}] */
17
14
  collectedSummary: Record<string, {
18
15
  label: string;
19
16
  text: string;
20
17
  }[]>;
21
- /** 방금 채워진 셀 강조용 (metricId 또는 plan:projectKey) */
18
+ /** 방금 채워진 셀 강조용 (metricId) */
22
19
  justFilled: Record<string, boolean>;
23
20
  collecting: boolean;
24
21
  /** kpi:input — Step1 계획값 입력/저장 권한 */
25
22
  canSave: boolean;
26
23
  /** kpi:auto-collect — 외부 시스템 자동 수집 권한 */
27
24
  canAutoCollect: boolean;
25
+ /** 완료(COMPLETED) 프로젝트 — 입력된 값은 보이지만 입력·수집·저장은 하지 않는다. */
26
+ private get isCompleted();
28
27
  /** 계획 칼럼을 갖는 항목 (키스콘이 계획값을 제공) */
29
- private static readonly PLAN_KEYS;
30
28
  render(): import("lit-html").TemplateResult<1>;
31
29
  /** '기본정보' / '월별 데이터' 섹션 렌더. 해당 그룹에 metric 이 없으면 섹션 자체를 생략. */
32
30
  private _renderSection;
@@ -68,11 +66,6 @@ export declare class SvProjectCompleteTab1Plan extends LitElement {
68
66
  connectedCallback(): Promise<void>;
69
67
  willUpdate(changedProperties: Map<string, any>): void;
70
68
  private _getInitData;
71
- /**
72
- * 계획공사비/계획공사기간 의 "계획" 컬럼 input 편집 핸들러 — collectedPlan 표시값 동기화 +
73
- * 해당 KpiMetric 의 value 도 upsert 하여 _save 시 patches 에 포함되도록.
74
- */
75
- private _onPlanInputChange;
76
69
  private _onInputChange;
77
70
  /**
78
71
  * metric.periodType 에 따라 적절한 row 를 upsert.
@@ -1,19 +1,18 @@
1
- var SvProjectCompleteTab1Plan_1;
2
1
  import { __decorate, __metadata } from "tslib";
3
2
  import { css, html, LitElement } from 'lit';
4
3
  import { customElement, property, state } from 'lit/decorators.js';
5
- import { calcDateDiff } from '../../shared/func';
6
4
  import { getKpiMetricValues, getKpiMetrics, updateProjectCompleteStep1, collectProjectExternalData, collectProjectSupervisionData } from '../../shared/complete-api';
7
5
  import { INTEGRATION_SOURCES } from '../../shared/integration-fetch';
8
6
  import { syncKpiToProjectInfo } from '../../shared/kpi-project-sync';
9
7
  import moment from 'moment-timezone';
10
8
  import { notify } from '@operato/layout';
9
+ import { ProjectState } from '../sv-project-list';
11
10
  import { hasPrivilege } from '@things-factory/auth-base/dist-client';
12
11
  const KPI_METRIC_KEY_MAPPING = [
13
- { label: '공사기간', projectKey: 'constructionPeriod' },
12
+ { label: '계획공사기간', projectKey: 'constructionPeriod' },
14
13
  { label: '총 근로자수', projectKey: 'totalWorkerCount' },
15
14
  { label: '연간 근로자 수', projectKey: 'annualWorkerCount' },
16
- { label: '총 투입인력(M/M)', projectKey: 'workerCount' },
15
+ { label: '총 투입인력', projectKey: 'workerCount' },
17
16
  { label: '재해 건수', projectKey: 'accidentCount' },
18
17
  { label: '일정 이탈 수준', projectKey: 'scheduleDeviationLevel' },
19
18
  { label: '총 건설 폐기물 발생량', projectKey: 'totalConstructionWasteAmount' },
@@ -33,7 +32,7 @@ const KPI_METRIC_KEY_MAPPING = [
33
32
  ];
34
33
  // 계획값이 없는 것들 (실제값으로 표시할 필드)
35
34
  const NO_PLAN_FIELDS = ['workerCount', 'area', 'floorAreaRatio', 'designChangeCount', 'upperFloorCount', 'lowerFloorCount'];
36
- let SvProjectCompleteTab1Plan = SvProjectCompleteTab1Plan_1 = class SvProjectCompleteTab1Plan extends LitElement {
35
+ let SvProjectCompleteTab1Plan = class SvProjectCompleteTab1Plan extends LitElement {
37
36
  constructor() {
38
37
  super(...arguments);
39
38
  this.kpiMetricValues = [];
@@ -44,13 +43,10 @@ let SvProjectCompleteTab1Plan = SvProjectCompleteTab1Plan_1 = class SvProjectCom
44
43
  this.collectError = {};
45
44
  /** 자동 수집한 실제값의 출처: metricId → 시스템 라벨 */
46
45
  this.valueSources = {};
47
- /** 자동 수집한 계획값: projectKey → 값 (키스콘이 제공하는 계획공사기간/계획공사비) */
48
- this.collectedPlan = {};
49
46
  /** 계획값 출처: projectKey → 시스템 라벨 */
50
- this.planSources = {};
51
47
  /** 시스템별 수집 요약 (카드 표시용): 라벨 → [{label, text}] */
52
48
  this.collectedSummary = {};
53
- /** 방금 채워진 셀 강조용 (metricId 또는 plan:projectKey) */
49
+ /** 방금 채워진 셀 강조용 (metricId) */
54
50
  this.justFilled = {};
55
51
  this.collecting = false;
56
52
  /** kpi:input — Step1 계획값 입력/저장 권한 */
@@ -58,6 +54,12 @@ let SvProjectCompleteTab1Plan = SvProjectCompleteTab1Plan_1 = class SvProjectCom
58
54
  /** kpi:auto-collect — 외부 시스템 자동 수집 권한 */
59
55
  this.canAutoCollect = false;
60
56
  }
57
+ /** 완료(COMPLETED) 프로젝트 — 입력된 값은 보이지만 입력·수집·저장은 하지 않는다. */
58
+ get isCompleted() {
59
+ var _a;
60
+ return ((_a = this.project) === null || _a === void 0 ? void 0 : _a.state) === ProjectState.COMPLETED;
61
+ }
62
+ /** 계획 칼럼을 갖는 항목 (키스콘이 계획값을 제공) */
61
63
  render() {
62
64
  // 전월 (last month) YYYY-MM. 월별 metric 의 "전월 데이터 입력" 검사 기준.
63
65
  const lastMonth = moment().tz('Asia/Seoul').subtract(1, 'month').format('YYYY-MM');
@@ -70,25 +72,27 @@ let SvProjectCompleteTab1Plan = SvProjectCompleteTab1Plan_1 = class SvProjectCom
70
72
  </div>
71
73
  </div>
72
74
 
73
- ${this._renderCollectPanel()}
75
+ ${this.isCompleted ? '' : this._renderCollectPanel()}
74
76
 
75
77
  <div class="rows">
76
78
  ${this._renderSection('기본정보', this.kpiMetrics.filter((m) => m.periodType !== 'MONTH'), lastMonth)}
77
79
  ${
78
- // '월별 데이터' 섹션 당분간 숨김. 복원하려면 아래 주석을 해제하고 이 '' 를 제거.
79
- // this._renderSection('월별 데이터', this.kpiMetrics.filter((m: any) => m.periodType === 'MONTH'), lastMonth, true)
80
- ''}
80
+ // 월 단위 metric 은 전월 row(YYYY-MM-01)로 읽고 쓴다 — '월별 데이터' 탭이 같은 row 를 읽는다.
81
+ // 올바로가 가져오는 총 건설 폐기물 발생량도 여기서 저장된다.
82
+ this._renderSection('월별 데이터', this.kpiMetrics.filter((m) => m.periodType === 'MONTH'), lastMonth, true)}
81
83
 
82
- <div class="button-line">
83
- <div class="ghost-btn" @click=${this._reset}>초기화</div>
84
- <div
85
- class="ghost-btn secondary ${this.canSave ? '' : 'disabled'}"
86
- title=${this.canSave ? '' : 'kpi:input 권한 필요'}
87
- @click=${() => this.canSave && this._save()}
88
- >
89
- 저장
90
- </div>
91
- </div>
84
+ ${this.isCompleted
85
+ ? html `<div class="locked-note">🔒 완료된 프로젝트 — 입력된 값만 볼 수 있습니다.</div>`
86
+ : html `<div class="button-line">
87
+ <div class="ghost-btn" @click=${this._reset}>초기화</div>
88
+ <div
89
+ class="ghost-btn secondary ${this.canSave ? '' : 'disabled'}"
90
+ title=${this.canSave ? '' : 'kpi:input 권한 필요'}
91
+ @click=${() => this.canSave && this._save()}
92
+ >
93
+ 저장
94
+ </div>
95
+ </div>`}
92
96
  </div>
93
97
  `;
94
98
  }
@@ -117,20 +121,10 @@ let SvProjectCompleteTab1Plan = SvProjectCompleteTab1Plan_1 = class SvProjectCom
117
121
  const kpiMetricValue = isMonthly
118
122
  ? this.kpiMetricValues.find((item) => item.metricId === metric.id && item.periodType === 'MONTH' && (item.valueDate || '').startsWith(lastMonth)) || {}
119
123
  : this.kpiMetricValues.find((item) => item.metricId === metric.id && item.periodType === metric.periodType) || {};
120
- const isDisplayInput = projectKey === 'constructionPeriod' || projectKey === 'constructionCost'; // 유효한 계획 필드
121
- // planValue는 project에서 찾고 없으면 buildingComplex의 값에서 찾음
124
+ // 기본값은 project 에서 찾고 없으면 buildingComplex 에서 찾음
122
125
  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;
123
- let planValue = basePlanValue;
124
126
  const unit = kpiMetricValue.unit || metric.unit || '';
125
- // 2. 계획 값 처리
126
- // 공사기간은 기간을 일 단위로 계산
127
- if (projectKey === 'constructionPeriod') {
128
- planValue = Math.ceil(calcDateDiff(this.project.startDate, this.project.endDate) / 30);
129
- }
130
- else if (!isDisplayInput) {
131
- planValue = 0;
132
- }
133
- // 1. 실제 값 처리 kpi값을 찾고, 없으면 (NO_PLAN_FIELDS에 해당하는 필드는 원래 계획값 사용)
127
+ // 값 처리 kpi값을 찾고, 없으면 (NO_PLAN_FIELDS에 해당하는 필드는 원래 계획값 사용)
134
128
  const actualValue = (_d = kpiMetricValue.value) !== null && _d !== void 0 ? _d : (NO_PLAN_FIELDS.includes(projectKey) ? basePlanValue : 0);
135
129
  const src = this.valueSources[metric.id];
136
130
  // periodType 무관하게 값 없으면 pending. 메시지는 MONTH 만 "전월 데이터 입력 필요".
@@ -143,7 +137,12 @@ let SvProjectCompleteTab1Plan = SvProjectCompleteTab1Plan_1 = class SvProjectCom
143
137
  ${metric.name}
144
138
  </div>
145
139
  <div class="cell ${this.justFilled[metric.id] ? 'just-filled' : ''} ${isPending ? 'pending' : ''}">
146
- <input numeric .value=${actualValue !== null && actualValue !== void 0 ? actualValue : 0} @input=${(e) => this._onInputChange(e, metric)} />
140
+ <input
141
+ numeric
142
+ .value=${actualValue !== null && actualValue !== void 0 ? actualValue : 0}
143
+ ?disabled=${this.isCompleted}
144
+ @input=${(e) => this._onInputChange(e, metric)}
145
+ />
147
146
  ${unit} ${src ? html `<span class="src-chip">🔗 ${src}</span>` : ''}
148
147
  </div>
149
148
  <div class="desc">${metric.description || ''}</div>
@@ -216,6 +215,8 @@ let SvProjectCompleteTab1Plan = SvProjectCompleteTab1Plan_1 = class SvProjectCom
216
215
  */
217
216
  async _autoCollect() {
218
217
  var _a;
218
+ if (this.isCompleted)
219
+ return;
219
220
  if (!((_a = this.project) === null || _a === void 0 ? void 0 : _a.id)) {
220
221
  notify({ message: '프로젝트 정보가 없습니다.' });
221
222
  return;
@@ -224,8 +225,6 @@ let SvProjectCompleteTab1Plan = SvProjectCompleteTab1Plan_1 = class SvProjectCom
224
225
  this.collectStatus = {};
225
226
  this.collectError = {};
226
227
  this.collectedSummary = {};
227
- this.collectedPlan = {};
228
- this.planSources = {};
229
228
  this.valueSources = {};
230
229
  // 모든 카드를 collecting 상태로 표시
231
230
  const collecting = {};
@@ -298,6 +297,8 @@ let SvProjectCompleteTab1Plan = SvProjectCompleteTab1Plan_1 = class SvProjectCom
298
297
  */
299
298
  async _collectSupervision() {
300
299
  var _a, _b;
300
+ if (this.isCompleted)
301
+ return;
301
302
  if (!((_a = this.project) === null || _a === void 0 ? void 0 : _a.id)) {
302
303
  notify({ message: '프로젝트 정보가 없습니다.' });
303
304
  return;
@@ -358,7 +359,6 @@ let SvProjectCompleteTab1Plan = SvProjectCompleteTab1Plan_1 = class SvProjectCom
358
359
  }
359
360
  const mapping = KPI_METRIC_KEY_MAPPING.find(x => x.projectKey === projectKey);
360
361
  const label = (mapping === null || mapping === void 0 ? void 0 : mapping.label) || NON_KPI_LABEL[projectKey] || projectKey;
361
- const isPlan = SvProjectCompleteTab1Plan_1.PLAN_KEYS.includes(projectKey);
362
362
  // summary 카드 표시용 — string 값은 그대로, number 는 toLocaleString
363
363
  const numericValue = typeof rawValue === 'number' ? rawValue : Number(rawValue);
364
364
  const isNumeric = Number.isFinite(numericValue) && typeof rawValue !== 'string';
@@ -367,25 +367,20 @@ let SvProjectCompleteTab1Plan = SvProjectCompleteTab1Plan_1 = class SvProjectCom
367
367
  // KPI 매핑이 없는 키 (siteType/structureType 등) 는 form 채움 skip — 단지 노출만.
368
368
  if (!mapping)
369
369
  continue;
370
- if (isPlan) {
371
- // 계획값 (키스콘 제공) — 계획 칼럼 표시 + KpiMetric value 에도 upsert
372
- // 하여 _save 시 patches 에 포함. 사용자가 계획 input 직접 편집해도 같은 흐름.
373
- this.collectedPlan = Object.assign(Object.assign({}, this.collectedPlan), { [projectKey]: numericValue });
374
- this.planSources = Object.assign(Object.assign({}, this.planSources), { [projectKey]: r.label });
375
- this.justFilled = Object.assign(Object.assign({}, this.justFilled), { [`plan:${projectKey}`]: true });
376
- 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; });
377
- if (planMetric)
378
- this._setMetricValue(planMetric, numericValue);
379
- }
380
- else {
381
- // 실제값 (세움터/올바로/기타) — 매칭되는 metric 의 실제 칼럼에 반영
382
- const metric = 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; });
383
- if (!metric)
384
- continue;
385
- this._setMetricValue(metric, numericValue);
386
- this.valueSources = Object.assign(Object.assign({}, this.valueSources), { [metric.id]: r.label });
387
- this.justFilled = Object.assign(Object.assign({}, this.justFilled), { [metric.id]: true });
388
- }
370
+ /*
371
+ * 계획값/실제값을 가르지 않는다.
372
+ *
373
+ * 예전에는 계획 칼럼이 따로 있어 계획공사비·계획공사기간만 collectedPlan/planSources
374
+ * 로 빠졌다. 그 칼럼이 없어진 뒤로도 분기가 남아 — 두 항목은 값만 채워지고 출처
375
+ * 뱃지(🔗)가 안 붙었다. 뱃지는 valueSources[metric.id] 를 읽기 때문이다.
376
+ * 지금은 한 줄에 값 하나뿐이므로 경로도 하나다.
377
+ */
378
+ const metric = 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; });
379
+ if (!metric)
380
+ continue;
381
+ this._setMetricValue(metric, numericValue);
382
+ this.valueSources = Object.assign(Object.assign({}, this.valueSources), { [metric.id]: r.label });
383
+ this.justFilled = Object.assign(Object.assign({}, this.justFilled), { [metric.id]: true });
389
384
  }
390
385
  this.collectedSummary = Object.assign(Object.assign({}, this.collectedSummary), { [r.label]: summary });
391
386
  }
@@ -418,16 +413,6 @@ let SvProjectCompleteTab1Plan = SvProjectCompleteTab1Plan_1 = class SvProjectCom
418
413
  this.kpiMetrics = kpiMetrics.filter(item => !item.name.includes('평가')) || [];
419
414
  this.kpiMetricValues = await getKpiMetricValues(this.project.id, (_b = this.project) === null || _b === void 0 ? void 0 : _b.code);
420
415
  }
421
- /**
422
- * 계획공사비/계획공사기간 의 "계획" 컬럼 input 편집 핸들러 — collectedPlan 표시값 동기화 +
423
- * 해당 KpiMetric 의 value 도 upsert 하여 _save 시 patches 에 포함되도록.
424
- */
425
- _onPlanInputChange(event, metric, projectKey) {
426
- const target = event.target;
427
- const inputVal = Number(target.value.replace(/[^\d.-]/g, ''));
428
- this.collectedPlan = Object.assign(Object.assign({}, this.collectedPlan), { [projectKey]: inputVal });
429
- this.kpiMetricValues = this._upsertValueForMetric(this.kpiMetricValues, metric, inputVal);
430
- }
431
416
  // Input 요소의 값이 변경될 때 호출되는 콜백 함수
432
417
  _onInputChange(event, metric) {
433
418
  const target = event.target;
@@ -492,6 +477,8 @@ let SvProjectCompleteTab1Plan = SvProjectCompleteTab1Plan_1 = class SvProjectCom
492
477
  }
493
478
  async _save() {
494
479
  var _a, _b;
480
+ if (this.isCompleted)
481
+ return;
495
482
  // 사용자가 실제 편집한 row 만 저장. 자동 산정 metric (계획/실제 공사비·공기) 는
496
483
  // 별도의 데이터 출처(project 정보, 키스콘 수집, 다른 metric 의 전월 row 등)에서
497
484
  // 도출돼야 하므로 이 화면에서 하드코딩 derive 하지 않음.
@@ -801,10 +788,15 @@ SvProjectCompleteTab1Plan.styles = [
801
788
  background: #ffffff;
802
789
  }
803
790
  }
791
+
792
+ .locked-note {
793
+ margin-top: 16px;
794
+ text-align: right;
795
+ font-size: 13px;
796
+ color: #6c757d;
797
+ }
804
798
  `
805
799
  ];
806
- /** 계획 칼럼을 갖는 항목 (키스콘이 계획값을 제공) */
807
- SvProjectCompleteTab1Plan.PLAN_KEYS = ['constructionPeriod', 'constructionCost'];
808
800
  __decorate([
809
801
  state(),
810
802
  __metadata("design:type", Object)
@@ -829,14 +821,6 @@ __decorate([
829
821
  state(),
830
822
  __metadata("design:type", Object)
831
823
  ], SvProjectCompleteTab1Plan.prototype, "valueSources", void 0);
832
- __decorate([
833
- state(),
834
- __metadata("design:type", Object)
835
- ], SvProjectCompleteTab1Plan.prototype, "collectedPlan", void 0);
836
- __decorate([
837
- state(),
838
- __metadata("design:type", Object)
839
- ], SvProjectCompleteTab1Plan.prototype, "planSources", void 0);
840
824
  __decorate([
841
825
  state(),
842
826
  __metadata("design:type", Object)
@@ -857,7 +841,7 @@ __decorate([
857
841
  state(),
858
842
  __metadata("design:type", Object)
859
843
  ], SvProjectCompleteTab1Plan.prototype, "canAutoCollect", void 0);
860
- SvProjectCompleteTab1Plan = SvProjectCompleteTab1Plan_1 = __decorate([
844
+ SvProjectCompleteTab1Plan = __decorate([
861
845
  customElement('sv-pc-tab1-plan')
862
846
  ], SvProjectCompleteTab1Plan);
863
847
  export { SvProjectCompleteTab1Plan };
@@ -1 +1 @@
1
- {"version":3,"file":"pc-tab1-plan.js","sourceRoot":"","sources":["../../../client/pages/project-complete-tabs/pc-tab1-plan.ts"],"names":[],"mappings":"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","sourcesContent":["import { css, html, LitElement } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { calcDateDiff } from '../../shared/func'\nimport {\n getKpiMetricValues,\n getKpiMetrics,\n updateProjectCompleteStep1,\n collectProjectExternalData,\n collectProjectSupervisionData\n} from '../../shared/complete-api'\nimport { INTEGRATION_SOURCES } from '../../shared/integration-fetch'\nimport { syncKpiToProjectInfo } from '../../shared/kpi-project-sync'\nimport moment from 'moment-timezone'\nimport { notify } from '@operato/layout'\nimport { hasPrivilege } from '@things-factory/auth-base/dist-client'\n\nconst KPI_METRIC_KEY_MAPPING = [\n { label: '공사기간', projectKey: 'constructionPeriod' },\n { label: '총 근로자수', projectKey: 'totalWorkerCount' },\n { label: '연간 근로자 수', projectKey: 'annualWorkerCount' },\n { label: '총 투입인력(M/M)', projectKey: 'workerCount' },\n { label: '재해 건수', projectKey: 'accidentCount' },\n { label: '일정 이탈 수준', projectKey: 'scheduleDeviationLevel' },\n { label: '총 건설 폐기물 발생량', projectKey: 'totalConstructionWasteAmount' },\n { label: '용적률', projectKey: 'floorAreaRatio' },\n { label: '총 설계 변경 건', projectKey: 'designChangeCount' },\n { label: '계획공사비', projectKey: 'constructionCost' },\n { label: '연면적', projectKey: 'area' },\n { label: '지상층수', projectKey: 'upperFloorCount' },\n { label: '지하층수', projectKey: 'lowerFloorCount' },\n // 시공감리 플랫폼(DCSP)에서 가져오는 항목 — 2026-08-07 회의\n { label: '실제공사기간', projectKey: 'actualConstructionPeriod' },\n { label: '실제공사비', projectKey: 'actualConstructionCost' },\n { label: '재해율', projectKey: 'accidentRate' },\n { label: '재해강도율', projectKey: 'accidentSeverityRate' },\n { label: '품질검측 건수', projectKey: 'inspectionCount' },\n { label: '품질검측 불합격 건수', projectKey: 'inspectionFailCount' }\n]\n// 계획값이 없는 것들 (실제값으로 표시할 필드)\nconst NO_PLAN_FIELDS = ['workerCount', 'area', 'floorAreaRatio', 'designChangeCount', 'upperFloorCount', 'lowerFloorCount']\n\n@customElement('sv-pc-tab1-plan')\nexport class SvProjectCompleteTab1Plan extends LitElement {\n static styles = [\n css`\n :host {\n display: block;\n }\n .title {\n color: #212529;\n font-size: 13px;\n font-weight: 400;\n line-height: 24px;\n text-align: center;\n }\n\n .rows {\n display: flex;\n flex-direction: column;\n padding: 8px 6px;\n }\n .row.header {\n min-height: 35px;\n background: #f3f3fa;\n border-top: 2px #0c4da2 solid;\n grid-template-columns: 220px 320px 1fr;\n padding: 0px 25px;\n\n .header-label {\n color: #212529;\n text-align: center;\n }\n }\n /* '기본정보' / '월별 데이터' 섹션 사이 간격 */\n .row.header.section-gap {\n margin-top: 24px;\n }\n .row {\n display: grid;\n grid-template-columns: 220px 320px 1fr;\n gap: 6px 10px;\n align-items: center;\n padding: 8px 25px;\n border-bottom: 1px rgba(0, 0, 0, 0.1) solid;\n\n .cell {\n display: flex;\n align-items: center;\n justify-content: flex-start;\n flex-wrap: nowrap;\n white-space: nowrap;\n gap: 6px;\n padding-right: 12px;\n }\n }\n .label {\n color: #35618e;\n font-size: 16px;\n letter-spacing: -0.05em;\n white-space: nowrap;\n display: flex;\n justify-content: flex-end;\n padding-right: 12px;\n }\n input {\n padding: 6px 8px;\n border: 1px solid rgba(0, 0, 0, 0.1);\n border-radius: 5px;\n background: #ffffff;\n color: #212529;\n font-size: 16px;\n margin-right: 5px;\n text-align: right;\n\n &:disabled {\n background: #f6f6f6;\n }\n }\n .unit {\n text-align: center;\n color: #212529;\n }\n .desc {\n color: #5a6168;\n font-size: 12px;\n line-height: 1.4;\n padding-left: 8px;\n }\n .plus {\n color: #e13232;\n font-weight: 700;\n }\n .minus {\n color: #1e88e5;\n font-weight: 700;\n }\n .button-line {\n display: flex;\n justify-content: center;\n gap: 10px;\n margin-top: 16px;\n }\n .ghost-btn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 6px 10px;\n background: #35618e;\n color: #ffffff;\n border-radius: 5px;\n cursor: pointer;\n }\n .ghost-btn.secondary {\n background: #24be7b;\n }\n .ghost-btn.disabled,\n .collect-btn.disabled {\n opacity: 0.45;\n cursor: not-allowed;\n }\n\n /* ── 자동 수집 패널 ── */\n .collect-panel {\n margin: 4px 6px 14px;\n border: 1px solid #d7e3f2;\n border-radius: 8px;\n background: linear-gradient(180deg, #f7fafe 0%, #ffffff 100%);\n overflow: hidden;\n }\n .collect-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 10px 16px;\n background: #eaf2fb;\n border-bottom: 1px solid #d7e3f2;\n }\n .collect-head .ttl {\n color: #0c4da2;\n font-weight: 700;\n font-size: 14px;\n letter-spacing: -0.03em;\n }\n .collect-head .ttl small {\n color: #5a7da6;\n font-weight: 400;\n margin-left: 8px;\n }\n .collect-btn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 7px 14px;\n background: #0c4da2;\n color: #fff;\n border-radius: 6px;\n cursor: pointer;\n font-size: 13px;\n font-weight: 600;\n }\n .collect-actions {\n display: flex;\n align-items: center;\n gap: 8px;\n }\n /* 외부 연동이 아니라 같은 DB 에서 가져오는 것이라 색을 달리해 구분한다. */\n .collect-btn.supervision {\n background: #c53030;\n }\n .collect-btn[disabled] {\n background: #9bb4d2;\n cursor: default;\n }\n .source-list {\n display: flex;\n gap: 10px;\n padding: 12px 16px;\n flex-wrap: wrap;\n }\n .source-card {\n flex: 1 1 200px;\n border: 1px solid #e3e9f0;\n border-radius: 7px;\n padding: 10px 12px;\n background: #fff;\n transition:\n border-color 0.2s,\n box-shadow 0.2s;\n }\n .source-card.collecting {\n border-color: #2e79be;\n box-shadow: 0 0 0 3px rgba(46, 121, 190, 0.12);\n }\n .source-card.done {\n border-color: #24be7b;\n }\n .source-card .sc-head {\n display: flex;\n align-items: center;\n gap: 8px;\n font-weight: 600;\n color: #212529;\n font-size: 13px;\n }\n .source-card .sc-head .sys-icon {\n font-size: 16px;\n }\n .source-card .sc-status {\n margin-left: auto;\n font-size: 12px;\n }\n .sc-status.idle {\n color: #aab4c0;\n }\n .sc-status.collecting {\n color: #2e79be;\n }\n .sc-status.done {\n color: #24be7b;\n font-weight: 700;\n }\n .sc-status.error {\n color: #8a8a8a;\n font-weight: 600;\n }\n .source-card.error {\n background: #fafafa;\n border-color: #e0e0e0;\n }\n .source-card .sc-error-msg {\n margin-top: 6px;\n padding: 6px 8px;\n border-radius: 4px;\n background: #f3f4f6;\n color: #6b7280;\n font-size: 12px;\n line-height: 1.4;\n word-break: break-word;\n }\n .source-card .sc-fields {\n margin-top: 8px;\n display: flex;\n flex-direction: column;\n gap: 3px;\n }\n .sc-field {\n font-size: 12px;\n color: #5a6b7b;\n display: flex;\n justify-content: space-between;\n }\n .sc-field b {\n color: #0c4da2;\n }\n .sc-desc {\n font-size: 11px;\n color: #9aa7b4;\n margin-top: 2px;\n }\n /* 가져온 값 출처 칩 */\n .src-chip {\n display: inline-flex;\n align-items: center;\n margin-left: 6px;\n padding: 1px 7px;\n font-size: 11px;\n border-radius: 10px;\n background: #e7f3ec;\n color: #1d9c63;\n white-space: nowrap;\n }\n .cell.just-filled input {\n animation: flash 1.1s ease;\n }\n /* 미입력 — kpiMetricValue 가 비어있는 metric 행 */\n .label.pending::before {\n content: '⚠';\n color: #e74c3c;\n margin-right: 4px;\n }\n .cell.pending input {\n border-color: #e74c3c;\n background-color: #fff5f5;\n }\n @keyframes flash {\n 0% {\n background: #fff7cc;\n }\n 100% {\n background: #ffffff;\n }\n }\n `\n ]\n\n @state() kpiMetricValues: any = []\n @state() kpiMetrics: any = []\n @property({ type: Object }) project: any = {}\n\n /** 연동 진행 상태: source → 'idle' | 'collecting' | 'done' */\n @state() collectStatus: Record<string, 'idle' | 'collecting' | 'done' | 'error'> = {}\n @state() collectError: Record<string, string> = {}\n /** 자동 수집한 실제값의 출처: metricId → 시스템 라벨 */\n @state() valueSources: Record<string, string> = {}\n /** 자동 수집한 계획값: projectKey → 값 (키스콘이 제공하는 계획공사기간/계획공사비) */\n @state() collectedPlan: Record<string, number> = {}\n /** 계획값 출처: projectKey → 시스템 라벨 */\n @state() planSources: Record<string, string> = {}\n /** 시스템별 수집 요약 (카드 표시용): 라벨 → [{label, text}] */\n @state() collectedSummary: Record<string, { label: string; text: string }[]> = {}\n /** 방금 채워진 셀 강조용 (metricId 또는 plan:projectKey) */\n @state() justFilled: Record<string, boolean> = {}\n @state() collecting = false\n\n /** kpi:input — Step1 계획값 입력/저장 권한 */\n @state() canSave = false\n /** kpi:auto-collect — 외부 시스템 자동 수집 권한 */\n @state() canAutoCollect = false\n\n /** 계획 칼럼을 갖는 항목 (키스콘이 계획값을 제공) */\n private static readonly PLAN_KEYS = ['constructionPeriod', 'constructionCost']\n\n render() {\n // 전월 (last month) YYYY-MM. 월별 metric 의 \"전월 데이터 입력\" 검사 기준.\n const lastMonth = moment().tz('Asia/Seoul').subtract(1, 'month').format('YYYY-MM')\n\n return html`\n <div class=\"title\">\n <div>\n 당초 계획 대비 실제 진행 과정에서 변동된 공사비, 공기(공사기간), 면적, 기타 주요 항목을 현실에 맞게 수정·입력합니다.\n <br />이 정보는 성과 분석, KPI 평가, 통계 산출 등에 기준값으로 사용되므로, 가능한 한 실제 값 기준으로 정확히\n 입력해주시기 바랍니다.\n </div>\n </div>\n\n ${this._renderCollectPanel()}\n\n <div class=\"rows\">\n ${this._renderSection(\n '기본정보',\n this.kpiMetrics.filter((m: any) => m.periodType !== 'MONTH'),\n lastMonth\n )}\n ${\n // '월별 데이터' 섹션 당분간 숨김. 복원하려면 아래 주석을 해제하고 이 '' 를 제거.\n // this._renderSection('월별 데이터', this.kpiMetrics.filter((m: any) => m.periodType === 'MONTH'), lastMonth, true)\n ''\n }\n\n <div class=\"button-line\">\n <div class=\"ghost-btn\" @click=${this._reset}>초기화</div>\n <div\n class=\"ghost-btn secondary ${this.canSave ? '' : 'disabled'}\"\n title=${this.canSave ? '' : 'kpi:input 권한 필요'}\n @click=${() => this.canSave && this._save()}\n >\n 저장\n </div>\n </div>\n </div>\n `\n }\n\n /** '기본정보' / '월별 데이터' 섹션 렌더. 해당 그룹에 metric 이 없으면 섹션 자체를 생략. */\n private _renderSection(title: string, metrics: any[], lastMonth: string, topGap = false) {\n if (!metrics.length) return ''\n return html`\n <div class=\"row header ${topGap ? 'section-gap' : ''}\">\n <div class=\"header-label\">${title}</div>\n <div class=\"header-label\">값</div>\n <div class=\"header-label\">설명</div>\n </div>\n ${metrics.map(metric => this._renderMetricRow(metric, lastMonth))}\n `\n }\n\n private _renderMetricRow(metric: any, lastMonth: string) {\n // 상수로 정의된 매핑 정보에서 metric.name으로 projectKey를 찾음\n const projectKey = KPI_METRIC_KEY_MAPPING.find(item => item.label === metric.name)?.projectKey || ''\n const isMonthly = metric.periodType === 'MONTH'\n // 현재값 lookup:\n // MONTH → 전월 row (단일 진실원)\n // ALLTIME → 단일 ALLTIME row\n // 그 외 → metricId+periodType 매칭\n const kpiMetricValue = isMonthly\n ? this.kpiMetricValues.find(\n (item: any) =>\n item.metricId === metric.id && item.periodType === 'MONTH' && (item.valueDate || '').startsWith(lastMonth)\n ) || {}\n : this.kpiMetricValues.find((item: any) => item.metricId === metric.id && item.periodType === metric.periodType) || {}\n const isDisplayInput = projectKey === 'constructionPeriod' || projectKey === 'constructionCost' // 유효한 계획 필드\n\n // planValue는 project에서 찾고 없으면 buildingComplex의 값에서 찾음\n const basePlanValue = this.project[projectKey] || this.project?.buildingComplex?.[projectKey] || 0\n let planValue = basePlanValue\n const unit = kpiMetricValue.unit || metric.unit || ''\n\n // 2. 계획 값 처리\n // 공사기간은 기간을 일 단위로 계산\n if (projectKey === 'constructionPeriod') {\n planValue = Math.ceil(calcDateDiff(this.project.startDate, this.project.endDate) / 30)\n } else if (!isDisplayInput) {\n planValue = 0\n }\n\n // 1. 실제 값 처리 kpi값을 찾고, 없으면 (NO_PLAN_FIELDS에 해당하는 필드는 원래 계획값 사용)\n const actualValue = kpiMetricValue.value ?? (NO_PLAN_FIELDS.includes(projectKey) ? basePlanValue : 0)\n\n const src = this.valueSources[metric.id]\n\n // periodType 무관하게 값 없으면 pending. 메시지는 MONTH 만 \"전월 데이터 입력 필요\".\n const isPending = kpiMetricValue.value === undefined || kpiMetricValue.value === null\n\n return html`<div class=\"row\">\n <div\n class=\"label ${isPending ? 'pending' : ''}\"\n title=${isPending ? (isMonthly ? '전월 데이터 입력 필요' : '아직 입력되지 않은 항목') : ''}\n >\n ${metric.name}\n </div>\n <div class=\"cell ${this.justFilled[metric.id] ? 'just-filled' : ''} ${isPending ? 'pending' : ''}\">\n <input numeric .value=${actualValue ?? 0} @input=${(e: InputEvent) => this._onInputChange(e, metric)} />\n ${unit} ${src ? html`<span class=\"src-chip\">🔗 ${src}</span>` : ''}\n </div>\n <div class=\"desc\">${metric.description || ''}</div>\n </div>`\n }\n\n private _renderCollectPanel() {\n return html`\n <div class=\"collect-panel\">\n <div class=\"collect-head\">\n <div class=\"ttl\">시스템 연동 자동 수집 <small>외부 시스템에서 기본정보를 가져옵니다</small></div>\n <div class=\"collect-actions\">\n <div\n class=\"collect-btn supervision ${this.canAutoCollect ? '' : 'disabled'}\"\n ?disabled=${this.collecting}\n title=${this.canAutoCollect ? '시공감리 플랫폼에 입력된 정보를 그대로 가져옵니다' : 'kpi:auto-collect 권한 필요'}\n @click=${() => (this.canAutoCollect && !this.collecting ? this._collectSupervision() : null)}\n >\n ${this.collecting ? '수집 중…' : '시공감리 플랫폼 자동 수집'}\n </div>\n <div\n class=\"collect-btn ${this.canAutoCollect ? '' : 'disabled'}\"\n ?disabled=${this.collecting}\n title=${this.canAutoCollect ? '' : 'kpi:auto-collect 권한 필요'}\n @click=${() => (this.canAutoCollect && !this.collecting ? this._autoCollect() : null)}\n >\n ${this.collecting ? '수집 중…' : '⟳ 자동 수집'}\n </div>\n </div>\n </div>\n <div class=\"source-list\">\n ${INTEGRATION_SOURCES.map(meta => {\n const status = this.collectStatus[meta.source] || 'idle'\n const errMsg = this.collectError[meta.source]\n // 카드 안의 수집 필드 summary — 실제/계획 무관 collectedSummary 기준으로 통일.\n // (valueSources 만 보면 키스콘처럼 계획값만 채우는 시스템은 표시 안 됨)\n const collectedFields = this._collectedFieldsOf(meta.label)\n const statusText =\n status === 'collecting' ? '연동 중…' : status === 'done' ? '완료 ✓' : status === 'error' ? '정보 없음 ⓘ' : '대기'\n return html`\n <div class=\"source-card ${status}\">\n <div class=\"sc-head\">\n <span class=\"sys-icon\">${meta.icon}</span>\n <span>${meta.label}</span>\n <span class=\"sc-status ${status}\">${statusText}</span>\n </div>\n <div class=\"sc-desc\">${meta.desc}</div>\n ${status === 'error' && errMsg ? html`<div class=\"sc-error-msg\">${errMsg}</div>` : ''}\n ${\n status === 'done' && collectedFields.length\n ? html`<div class=\"sc-fields\">\n ${collectedFields.map(f => html`<div class=\"sc-field\"><span>${f.label}</span><b>${f.text}</b></div>`)}\n </div>`\n : ''\n }\n </div>\n `\n })}\n </div>\n </div>\n `\n }\n\n /** 패널 카드에 표시할, 해당 시스템에서 수집한 값 요약 */\n private _collectedFieldsOf(sourceLabel: string): { label: string; text: string }[] {\n return this.collectedSummary[sourceLabel] || []\n }\n\n /** 자동 수집 — 백엔드의 collectProjectExternalData mutation 1번 호출.\n * 서버가 시나리오 3종 (세움터/올바로/키스콘) 을 순차 실행하고 시스템별 ok/메시지를 반환.\n * 성공/실패와 무관하게 시나리오 step 이 KPI Value 를 DB 에 적재하므로 호출 후\n * KPI Metric Values 를 재조회해 form 을 자동 갱신.\n *\n * 운영 현실: 키스콘·올바로는 시공사 자격증명 미확보가 많아 오류 빈도 높음.\n * 세움터는 공공데이터 서비스키만으로 동작 가능. 시스템별로 카드에 독립 표시.\n */\n private async _autoCollect() {\n if (!this.project?.id) {\n notify({ message: '프로젝트 정보가 없습니다.' })\n return\n }\n this.collecting = true\n this.collectStatus = {}\n this.collectError = {}\n this.collectedSummary = {}\n this.collectedPlan = {}\n this.planSources = {}\n this.valueSources = {}\n\n // 모든 카드를 collecting 상태로 표시\n const collecting: Record<string, 'collecting'> = {}\n for (const meta of INTEGRATION_SOURCES) collecting[meta.source] = 'collecting'\n this.collectStatus = collecting\n\n try {\n const results = await collectProjectExternalData(this.project.id)\n\n // 시스템별 status / error 메시지 업데이트 + result data 를 form 에 매핑\n const status: Record<string, 'done' | 'error'> = {}\n const errors: Record<string, string> = {}\n for (const r of results) {\n status[r.source] = r.ok ? 'done' : 'error'\n if (!r.ok) {\n errors[r.source] = r.message\n continue\n }\n // 시나리오 result 객체 = { projectKey: value, ... }.\n // KPI_METRIC_KEY_MAPPING 매칭 키 → form 채움. 미매칭 키 (siteType,\n // structureType 등 프로젝트/BuildingComplex 기본 속성) → 카드 요약만.\n this._applyCollectionResult(r)\n }\n this.collectStatus = status\n this.collectError = errors\n\n // 재조회 안 함 — 시나리오는 fetch + result 반환만 책임 (DB 적재 X). 직전에\n // _setMetricValue 가 memory 에 채운 값들을 DB 의 옛 값으로 덮어쓰면 자동수집\n // 결과가 폼에 반영되지 않음. 사용자가 [저장] 버튼 누르면 그제서야 DB 반영.\n this.requestUpdate()\n setTimeout(() => (this.justFilled = {}), 1300)\n\n const okCount = results.filter(r => r.ok).length\n if (okCount === results.length) {\n notify({ message: `외부 시스템 ${okCount}건 모두 수집 완료. 검토 후 [저장]을 눌러주세요.` })\n } else if (okCount === 0) {\n notify({ message: '외부 시스템에서 가져올 정보가 없습니다.' })\n } else {\n notify({ message: `외부 시스템 수집 — ${okCount}건 완료, 나머지는 정보 없음.` })\n }\n } catch (e) {\n // mutation 자체 실패 (네트워크/권한 등) — 모든 카드를 정보 없음 으로\n const msg = e instanceof Error ? e.message : String(e)\n console.warn('[자동수집] 전체 호출 실패', e)\n const status: Record<string, 'error'> = {}\n const errors: Record<string, string> = {}\n for (const meta of INTEGRATION_SOURCES) {\n status[meta.source] = 'error'\n errors[meta.source] = '업데이트할 정보가 없습니다'\n }\n this.collectStatus = status\n this.collectError = errors\n notify({ message: '외부 시스템 연동이 일시적으로 안 됩니다.' })\n } finally {\n this.collecting = false\n }\n }\n\n /**\n * 메트릭의 실제값 set/add — _onInputChange 와 동일하게 periodType 별 row upsert.\n */\n\n /**\n * 시공감리 플랫폼(DCSP) 에서 기본정보를 가져온다.\n *\n * 외부 연동 3종과 버튼을 나눈 이유: 이건 네트워크 호출이 아니라 같은 DB 의 같은 프로젝트를\n * 읽는 것이고, 실패 사유도 다르다 (자격증명 문제가 아니라 \"시공감리 쪽에 아직 입력이\n * 없다\"). 한 버튼에 묶으면 사용자가 무엇을 고쳐야 할지 알 수 없다.\n *\n * 단위 변환은 서버가 한다 — 재해율 %→비율, 공사기간 날짜→개월.\n */\n private async _collectSupervision() {\n if (!this.project?.id) {\n notify({ message: '프로젝트 정보가 없습니다.' })\n return\n }\n\n this.collecting = true\n this.collectStatus = { ...this.collectStatus, supervision: 'collecting' }\n this.collectError = { ...this.collectError, supervision: '' }\n\n try {\n const result = await collectProjectSupervisionData(this.project.id, this.project?.code)\n\n this.collectStatus = { ...this.collectStatus, supervision: result.ok ? 'done' : 'error' }\n\n if (!result.ok) {\n this.collectError = { ...this.collectError, supervision: result.message }\n notify({ message: result.message })\n return\n }\n\n this._applyCollectionResult(result)\n notify({ message: '시공감리 플랫폼에서 가져왔습니다. 확인 후 저장해 주세요.' })\n } catch (e: any) {\n this.collectStatus = { ...this.collectStatus, supervision: 'error' }\n this.collectError = { ...this.collectError, supervision: e?.message || '수집에 실패했습니다' }\n notify({ message: e?.message || '수집에 실패했습니다' })\n } finally {\n this.collecting = false\n }\n }\n\n /**\n * 수집 결과 한 건을 폼에 반영한다.\n *\n * 외부 시스템 수집(자동 수집)과 시공감리 플랫폼 복사가 같은 결과 모양\n * ({ source, label, ok, message, data })을 쓰므로 반영 규칙도 하나로 둔다. 둘이\n * 갈라지면 같은 값이 어느 버튼을 눌렀느냐에 따라 다르게 들어간다.\n */\n private _applyCollectionResult(r: any) {\n const summary: { label: string; text: string }[] = []\n // KPI_METRIC_KEY_MAPPING 에 없는 키들의 한글 라벨. BuildingComplex/Project 직접 속성용.\n const NON_KPI_LABEL: Record<string, string> = {\n siteType: '건축프로젝트유형',\n structureType: '구조형태',\n coverageRatio: '건폐율',\n householdCount: '세대수',\n buildingCount: '동수',\n startDate: '착공일',\n endDate: '준공일',\n permitDate: '건축허가일',\n bldNm: '건물명'\n }\n for (const [projectKey, rawValue] of Object.entries(r.data || {})) {\n if (rawValue === null || rawValue === undefined || rawValue === '') continue\n // 비-primitive (객체/배열) 값은 시나리오 측 return 형태 오류 — form 매핑/표시\n // 모두 skip 하고 console 에 진단 로그만 남김. 흔한 원인은 시나리오 마지막 step 이\n // `return { result: { ... } }` 처럼 wrap 한 케이스.\n if (typeof rawValue === 'object') {\n console.warn(`[자동수집] '${r.label}'.${projectKey} 가 object — 시나리오 return 형태 점검`, rawValue)\n continue\n }\n const mapping = KPI_METRIC_KEY_MAPPING.find(x => x.projectKey === projectKey)\n const label = mapping?.label || NON_KPI_LABEL[projectKey] || projectKey\n const isPlan = SvProjectCompleteTab1Plan.PLAN_KEYS.includes(projectKey)\n\n // summary 카드 표시용 — string 값은 그대로, number 는 toLocaleString\n const numericValue = typeof rawValue === 'number' ? rawValue : Number(rawValue)\n const isNumeric = Number.isFinite(numericValue) && typeof rawValue !== 'string'\n const valueText = isNumeric ? numericValue.toLocaleString() : String(rawValue)\n summary.push({ label, text: valueText })\n\n // KPI 매핑이 없는 키 (siteType/structureType 등) 는 form 채움 skip — 단지 노출만.\n if (!mapping) continue\n\n if (isPlan) {\n // 계획값 (키스콘 제공) — 계획 칼럼 표시 + KpiMetric value 에도 upsert\n // 하여 _save 시 patches 에 포함. 사용자가 계획 input 직접 편집해도 같은 흐름.\n this.collectedPlan = { ...this.collectedPlan, [projectKey]: numericValue }\n this.planSources = { ...this.planSources, [projectKey]: r.label }\n this.justFilled = { ...this.justFilled, [`plan:${projectKey}`]: true }\n const planMetric = this.kpiMetrics.find(\n (m: any) => KPI_METRIC_KEY_MAPPING.find(x => x.label === m.name)?.projectKey === projectKey\n )\n if (planMetric) this._setMetricValue(planMetric, numericValue)\n } else {\n // 실제값 (세움터/올바로/기타) — 매칭되는 metric 의 실제 칼럼에 반영\n const metric = this.kpiMetrics.find(\n (m: any) => KPI_METRIC_KEY_MAPPING.find(x => x.label === m.name)?.projectKey === projectKey\n )\n if (!metric) continue\n this._setMetricValue(metric, numericValue)\n this.valueSources = { ...this.valueSources, [metric.id]: r.label }\n this.justFilled = { ...this.justFilled, [metric.id]: true }\n }\n }\n this.collectedSummary = { ...this.collectedSummary, [r.label]: summary }\n }\n\n private _setMetricValue(metric: any, value: number | string) {\n this.kpiMetricValues = this._upsertValueForMetric(this.kpiMetricValues, metric, value)\n }\n\n async connectedCallback() {\n super.connectedCallback()\n const [canSave, canAutoCollect] = await Promise.all([\n hasPrivilege({ category: 'kpi', privilege: 'input', domainOwnerGranted: true, superUserGranted: true }),\n hasPrivilege({ category: 'kpi', privilege: 'auto-collect', domainOwnerGranted: true, superUserGranted: true })\n ])\n this.canSave = canSave\n this.canAutoCollect = canAutoCollect\n }\n\n willUpdate(changedProperties: Map<string, any>) {\n super.willUpdate(changedProperties)\n\n // project가 변경되고, project.id가 존재하면 데이터 로드\n if (changedProperties.has('project') && this.project?.id) {\n this._getInitData()\n }\n }\n\n private async _getInitData() {\n // getKpiMetrics 가 이미 active=true 만 반환. 여기선 평가 (Step2) 만 추가 제외.\n // 비활성화는 KPI 관리 admin 에서 active=false 처리.\n const kpiMetrics = await getKpiMetrics(this.project?.code)\n // 평가(Step2) 만 제외. 나머지는 기본정보 탭에서 '기본정보'/'월별 데이터' 두 섹션으로 구분 렌더.\n this.kpiMetrics = kpiMetrics.filter(item => !item.name.includes('평가')) || []\n this.kpiMetricValues = await getKpiMetricValues(this.project.id, this.project?.code)\n }\n\n /**\n * 계획공사비/계획공사기간 의 \"계획\" 컬럼 input 편집 핸들러 — collectedPlan 표시값 동기화 +\n * 해당 KpiMetric 의 value 도 upsert 하여 _save 시 patches 에 포함되도록.\n */\n private _onPlanInputChange(event: InputEvent, metric: any, projectKey: string) {\n const target = event.target as HTMLInputElement\n const inputVal = Number(target.value.replace(/[^\\d.-]/g, ''))\n this.collectedPlan = { ...this.collectedPlan, [projectKey]: inputVal }\n this.kpiMetricValues = this._upsertValueForMetric(this.kpiMetricValues, metric, inputVal)\n }\n\n // Input 요소의 값이 변경될 때 호출되는 콜백 함수\n private _onInputChange(event: InputEvent, metric: any) {\n const target = event.target as HTMLInputElement\n let inputVal: any = target.value\n\n // 숫자 타입은 다른 문자 입력 제거\n if (target.hasAttribute('numeric')) {\n inputVal = Number(inputVal.replace(/[^\\d.-]/g, ''))\n }\n\n this.kpiMetricValues = this._upsertValueForMetric(this.kpiMetricValues, metric, inputVal)\n }\n\n /**\n * metric.periodType 에 따라 적절한 row 를 upsert.\n * - MONTH : 전월 row (YYYY-MM-01). \"오늘 입력 = 지난달 데이터\" 운영 원칙.\n * - ALLTIME : 단일 row (valueDate sentinel 무관).\n * - 그 외 (DAY/WEEK/QUARTER/YEAR/RANGE) : metric.periodType 그대로, valueDate=today.\n * 매칭은 (metricId, periodType) 기준 — MONTH 만 추가로 valueDate prefix.\n */\n private _upsertValueForMetric(values: any[], metric: any, value: any): any[] {\n const today = moment().tz('Asia/Seoul')\n const todayYmd = today.format('YYYY-MM-DD')\n const periodType = metric.periodType\n let updated = [...values]\n\n if (periodType === 'MONTH') {\n const lastMonth = today.clone().subtract(1, 'month')\n const lastMonth1 = lastMonth.format('YYYY-MM-01')\n const lastMonthYm = lastMonth.format('YYYY-MM')\n const idx = updated.findIndex(\n (i: any) => i.metricId === metric.id && i.periodType === 'MONTH' && (i.valueDate || '').startsWith(lastMonthYm)\n )\n if (idx !== -1) {\n updated[idx] = { ...updated[idx], value }\n } else {\n updated.push({\n id: crypto.randomUUID(),\n value,\n metricId: metric.id,\n unit: metric.unit || '',\n org: this.project.id,\n periodType: 'MONTH',\n valueDate: lastMonth1\n })\n }\n } else {\n // ALLTIME / DAY / WEEK / QUARTER / YEAR / RANGE — metric.periodType 그대로 단일 row upsert.\n const idx = updated.findIndex((i: any) => i.metricId === metric.id && i.periodType === periodType)\n if (idx !== -1) {\n updated[idx] = { ...updated[idx], value }\n } else {\n updated.push({\n id: crypto.randomUUID(),\n value,\n metricId: metric.id,\n unit: metric.unit || '',\n org: this.project.id,\n periodType,\n valueDate: todayYmd\n })\n }\n }\n\n return updated\n }\n\n private async _save() {\n // 사용자가 실제 편집한 row 만 저장. 자동 산정 metric (계획/실제 공사비·공기) 는\n // 별도의 데이터 출처(project 정보, 키스콘 수집, 다른 metric 의 전월 row 등)에서\n // 도출돼야 하므로 이 화면에서 하드코딩 derive 하지 않음.\n const response = await updateProjectCompleteStep1(this.kpiMetricValues, this.project?.code)\n if (!response.errors) {\n // 양방향 동기 — 연면적/용적률/투입인력/설계변경건 값을 BuildingComplex(프로젝트 정보)에도 반영.\n // 보조 동기 실패가 저장 성공 UX 를 막지 않도록 warn 만.\n try {\n await syncKpiToProjectInfo(this.project, this.kpiMetrics, this.kpiMetricValues, this.project?.code)\n } catch (e) {\n console.warn('[KPI→프로젝트정보] BuildingComplex 동기 실패', e)\n }\n notify({ message: '저장되었습니다.' })\n }\n }\n\n private _reset() {\n this._getInitData()\n }\n}\n"]}
1
+ {"version":3,"file":"pc-tab1-plan.js","sourceRoot":"","sources":["../../../client/pages/project-complete-tabs/pc-tab1-plan.ts"],"names":[],"mappings":"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","sourcesContent":["import { css, html, LitElement } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport {\n getKpiMetricValues,\n getKpiMetrics,\n updateProjectCompleteStep1,\n collectProjectExternalData,\n collectProjectSupervisionData\n} from '../../shared/complete-api'\nimport { INTEGRATION_SOURCES } from '../../shared/integration-fetch'\nimport { syncKpiToProjectInfo } from '../../shared/kpi-project-sync'\nimport moment from 'moment-timezone'\nimport { notify } from '@operato/layout'\nimport { ProjectState } from '../sv-project-list'\nimport { hasPrivilege } from '@things-factory/auth-base/dist-client'\n\nconst KPI_METRIC_KEY_MAPPING = [\n { label: '계획공사기간', projectKey: 'constructionPeriod' },\n { label: '총 근로자수', projectKey: 'totalWorkerCount' },\n { label: '연간 근로자 수', projectKey: 'annualWorkerCount' },\n { label: '총 투입인력', projectKey: 'workerCount' },\n { label: '재해 건수', projectKey: 'accidentCount' },\n { label: '일정 이탈 수준', projectKey: 'scheduleDeviationLevel' },\n { label: '총 건설 폐기물 발생량', projectKey: 'totalConstructionWasteAmount' },\n { label: '용적률', projectKey: 'floorAreaRatio' },\n { label: '총 설계 변경 건', projectKey: 'designChangeCount' },\n { label: '계획공사비', projectKey: 'constructionCost' },\n { label: '연면적', projectKey: 'area' },\n { label: '지상층수', projectKey: 'upperFloorCount' },\n { label: '지하층수', projectKey: 'lowerFloorCount' },\n // 시공감리 플랫폼(DCSP)에서 가져오는 항목 — 2026-08-07 회의\n { label: '실제공사기간', projectKey: 'actualConstructionPeriod' },\n { label: '실제공사비', projectKey: 'actualConstructionCost' },\n { label: '재해율', projectKey: 'accidentRate' },\n { label: '재해강도율', projectKey: 'accidentSeverityRate' },\n { label: '품질검측 건수', projectKey: 'inspectionCount' },\n { label: '품질검측 불합격 건수', projectKey: 'inspectionFailCount' }\n]\n// 계획값이 없는 것들 (실제값으로 표시할 필드)\nconst NO_PLAN_FIELDS = ['workerCount', 'area', 'floorAreaRatio', 'designChangeCount', 'upperFloorCount', 'lowerFloorCount']\n\n@customElement('sv-pc-tab1-plan')\nexport class SvProjectCompleteTab1Plan extends LitElement {\n static styles = [\n css`\n :host {\n display: block;\n }\n .title {\n color: #212529;\n font-size: 13px;\n font-weight: 400;\n line-height: 24px;\n text-align: center;\n }\n\n .rows {\n display: flex;\n flex-direction: column;\n padding: 8px 6px;\n }\n .row.header {\n min-height: 35px;\n background: #f3f3fa;\n border-top: 2px #0c4da2 solid;\n grid-template-columns: 220px 320px 1fr;\n padding: 0px 25px;\n\n .header-label {\n color: #212529;\n text-align: center;\n }\n }\n /* '기본정보' / '월별 데이터' 섹션 사이 간격 */\n .row.header.section-gap {\n margin-top: 24px;\n }\n .row {\n display: grid;\n grid-template-columns: 220px 320px 1fr;\n gap: 6px 10px;\n align-items: center;\n padding: 8px 25px;\n border-bottom: 1px rgba(0, 0, 0, 0.1) solid;\n\n .cell {\n display: flex;\n align-items: center;\n justify-content: flex-start;\n flex-wrap: nowrap;\n white-space: nowrap;\n gap: 6px;\n padding-right: 12px;\n }\n }\n .label {\n color: #35618e;\n font-size: 16px;\n letter-spacing: -0.05em;\n white-space: nowrap;\n display: flex;\n justify-content: flex-end;\n padding-right: 12px;\n }\n input {\n padding: 6px 8px;\n border: 1px solid rgba(0, 0, 0, 0.1);\n border-radius: 5px;\n background: #ffffff;\n color: #212529;\n font-size: 16px;\n margin-right: 5px;\n text-align: right;\n\n &:disabled {\n background: #f6f6f6;\n }\n }\n .unit {\n text-align: center;\n color: #212529;\n }\n .desc {\n color: #5a6168;\n font-size: 12px;\n line-height: 1.4;\n padding-left: 8px;\n }\n .plus {\n color: #e13232;\n font-weight: 700;\n }\n .minus {\n color: #1e88e5;\n font-weight: 700;\n }\n .button-line {\n display: flex;\n justify-content: center;\n gap: 10px;\n margin-top: 16px;\n }\n .ghost-btn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 6px 10px;\n background: #35618e;\n color: #ffffff;\n border-radius: 5px;\n cursor: pointer;\n }\n .ghost-btn.secondary {\n background: #24be7b;\n }\n .ghost-btn.disabled,\n .collect-btn.disabled {\n opacity: 0.45;\n cursor: not-allowed;\n }\n\n /* ── 자동 수집 패널 ── */\n .collect-panel {\n margin: 4px 6px 14px;\n border: 1px solid #d7e3f2;\n border-radius: 8px;\n background: linear-gradient(180deg, #f7fafe 0%, #ffffff 100%);\n overflow: hidden;\n }\n .collect-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 10px 16px;\n background: #eaf2fb;\n border-bottom: 1px solid #d7e3f2;\n }\n .collect-head .ttl {\n color: #0c4da2;\n font-weight: 700;\n font-size: 14px;\n letter-spacing: -0.03em;\n }\n .collect-head .ttl small {\n color: #5a7da6;\n font-weight: 400;\n margin-left: 8px;\n }\n .collect-btn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 7px 14px;\n background: #0c4da2;\n color: #fff;\n border-radius: 6px;\n cursor: pointer;\n font-size: 13px;\n font-weight: 600;\n }\n .collect-actions {\n display: flex;\n align-items: center;\n gap: 8px;\n }\n /* 외부 연동이 아니라 같은 DB 에서 가져오는 것이라 색을 달리해 구분한다. */\n .collect-btn.supervision {\n background: #c53030;\n }\n .collect-btn[disabled] {\n background: #9bb4d2;\n cursor: default;\n }\n .source-list {\n display: flex;\n gap: 10px;\n padding: 12px 16px;\n flex-wrap: wrap;\n }\n .source-card {\n flex: 1 1 200px;\n border: 1px solid #e3e9f0;\n border-radius: 7px;\n padding: 10px 12px;\n background: #fff;\n transition:\n border-color 0.2s,\n box-shadow 0.2s;\n }\n .source-card.collecting {\n border-color: #2e79be;\n box-shadow: 0 0 0 3px rgba(46, 121, 190, 0.12);\n }\n .source-card.done {\n border-color: #24be7b;\n }\n .source-card .sc-head {\n display: flex;\n align-items: center;\n gap: 8px;\n font-weight: 600;\n color: #212529;\n font-size: 13px;\n }\n .source-card .sc-head .sys-icon {\n font-size: 16px;\n }\n .source-card .sc-status {\n margin-left: auto;\n font-size: 12px;\n }\n .sc-status.idle {\n color: #aab4c0;\n }\n .sc-status.collecting {\n color: #2e79be;\n }\n .sc-status.done {\n color: #24be7b;\n font-weight: 700;\n }\n .sc-status.error {\n color: #8a8a8a;\n font-weight: 600;\n }\n .source-card.error {\n background: #fafafa;\n border-color: #e0e0e0;\n }\n .source-card .sc-error-msg {\n margin-top: 6px;\n padding: 6px 8px;\n border-radius: 4px;\n background: #f3f4f6;\n color: #6b7280;\n font-size: 12px;\n line-height: 1.4;\n word-break: break-word;\n }\n .source-card .sc-fields {\n margin-top: 8px;\n display: flex;\n flex-direction: column;\n gap: 3px;\n }\n .sc-field {\n font-size: 12px;\n color: #5a6b7b;\n display: flex;\n justify-content: space-between;\n }\n .sc-field b {\n color: #0c4da2;\n }\n .sc-desc {\n font-size: 11px;\n color: #9aa7b4;\n margin-top: 2px;\n }\n /* 가져온 값 출처 칩 */\n .src-chip {\n display: inline-flex;\n align-items: center;\n margin-left: 6px;\n padding: 1px 7px;\n font-size: 11px;\n border-radius: 10px;\n background: #e7f3ec;\n color: #1d9c63;\n white-space: nowrap;\n }\n .cell.just-filled input {\n animation: flash 1.1s ease;\n }\n /* 미입력 — kpiMetricValue 가 비어있는 metric 행 */\n .label.pending::before {\n content: '⚠';\n color: #e74c3c;\n margin-right: 4px;\n }\n .cell.pending input {\n border-color: #e74c3c;\n background-color: #fff5f5;\n }\n @keyframes flash {\n 0% {\n background: #fff7cc;\n }\n 100% {\n background: #ffffff;\n }\n }\n\n .locked-note {\n margin-top: 16px;\n text-align: right;\n font-size: 13px;\n color: #6c757d;\n }\n `\n ]\n\n @state() kpiMetricValues: any = []\n @state() kpiMetrics: any = []\n @property({ type: Object }) project: any = {}\n\n /** 연동 진행 상태: source → 'idle' | 'collecting' | 'done' */\n @state() collectStatus: Record<string, 'idle' | 'collecting' | 'done' | 'error'> = {}\n @state() collectError: Record<string, string> = {}\n /** 자동 수집한 실제값의 출처: metricId → 시스템 라벨 */\n @state() valueSources: Record<string, string> = {}\n /** 계획값 출처: projectKey → 시스템 라벨 */\n /** 시스템별 수집 요약 (카드 표시용): 라벨 → [{label, text}] */\n @state() collectedSummary: Record<string, { label: string; text: string }[]> = {}\n /** 방금 채워진 셀 강조용 (metricId) */\n @state() justFilled: Record<string, boolean> = {}\n @state() collecting = false\n\n /** kpi:input — Step1 계획값 입력/저장 권한 */\n @state() canSave = false\n /** kpi:auto-collect — 외부 시스템 자동 수집 권한 */\n @state() canAutoCollect = false\n\n /** 완료(COMPLETED) 프로젝트 — 입력된 값은 보이지만 입력·수집·저장은 하지 않는다. */\n private get isCompleted(): boolean {\n return this.project?.state === ProjectState.COMPLETED\n }\n\n /** 계획 칼럼을 갖는 항목 (키스콘이 계획값을 제공) */\n render() {\n // 전월 (last month) YYYY-MM. 월별 metric 의 \"전월 데이터 입력\" 검사 기준.\n const lastMonth = moment().tz('Asia/Seoul').subtract(1, 'month').format('YYYY-MM')\n\n return html`\n <div class=\"title\">\n <div>\n 당초 계획 대비 실제 진행 과정에서 변동된 공사비, 공기(공사기간), 면적, 기타 주요 항목을 현실에 맞게 수정·입력합니다.\n <br />이 정보는 성과 분석, KPI 평가, 통계 산출 등에 기준값으로 사용되므로, 가능한 한 실제 값 기준으로 정확히\n 입력해주시기 바랍니다.\n </div>\n </div>\n\n ${this.isCompleted ? '' : this._renderCollectPanel()}\n\n <div class=\"rows\">\n ${this._renderSection(\n '기본정보',\n this.kpiMetrics.filter((m: any) => m.periodType !== 'MONTH'),\n lastMonth\n )}\n ${\n // 월 단위 metric 은 전월 row(YYYY-MM-01)로 읽고 쓴다 — '월별 데이터' 탭이 같은 row 를 읽는다.\n // 올바로가 가져오는 총 건설 폐기물 발생량도 여기서 저장된다.\n this._renderSection(\n '월별 데이터',\n this.kpiMetrics.filter((m: any) => m.periodType === 'MONTH'),\n lastMonth,\n true\n )\n }\n\n ${this.isCompleted\n ? html`<div class=\"locked-note\">🔒 완료된 프로젝트 — 입력된 값만 볼 수 있습니다.</div>`\n : html`<div class=\"button-line\">\n <div class=\"ghost-btn\" @click=${this._reset}>초기화</div>\n <div\n class=\"ghost-btn secondary ${this.canSave ? '' : 'disabled'}\"\n title=${this.canSave ? '' : 'kpi:input 권한 필요'}\n @click=${() => this.canSave && this._save()}\n >\n 저장\n </div>\n </div>`}\n </div>\n `\n }\n\n /** '기본정보' / '월별 데이터' 섹션 렌더. 해당 그룹에 metric 이 없으면 섹션 자체를 생략. */\n private _renderSection(title: string, metrics: any[], lastMonth: string, topGap = false) {\n if (!metrics.length) return ''\n return html`\n <div class=\"row header ${topGap ? 'section-gap' : ''}\">\n <div class=\"header-label\">${title}</div>\n <div class=\"header-label\">값</div>\n <div class=\"header-label\">설명</div>\n </div>\n ${metrics.map(metric => this._renderMetricRow(metric, lastMonth))}\n `\n }\n\n private _renderMetricRow(metric: any, lastMonth: string) {\n // 상수로 정의된 매핑 정보에서 metric.name으로 projectKey를 찾음\n const projectKey = KPI_METRIC_KEY_MAPPING.find(item => item.label === metric.name)?.projectKey || ''\n const isMonthly = metric.periodType === 'MONTH'\n // 현재값 lookup:\n // MONTH → 전월 row (단일 진실원)\n // ALLTIME → 단일 ALLTIME row\n // 그 외 → metricId+periodType 매칭\n const kpiMetricValue = isMonthly\n ? this.kpiMetricValues.find(\n (item: any) =>\n item.metricId === metric.id && item.periodType === 'MONTH' && (item.valueDate || '').startsWith(lastMonth)\n ) || {}\n : this.kpiMetricValues.find((item: any) => item.metricId === metric.id && item.periodType === metric.periodType) || {}\n\n // 기본값은 project 에서 찾고 없으면 buildingComplex 에서 찾음\n const basePlanValue = this.project[projectKey] || this.project?.buildingComplex?.[projectKey] || 0\n const unit = kpiMetricValue.unit || metric.unit || ''\n\n // 값 처리 kpi값을 찾고, 없으면 (NO_PLAN_FIELDS에 해당하는 필드는 원래 계획값 사용)\n const actualValue = kpiMetricValue.value ?? (NO_PLAN_FIELDS.includes(projectKey) ? basePlanValue : 0)\n\n const src = this.valueSources[metric.id]\n\n // periodType 무관하게 값 없으면 pending. 메시지는 MONTH 만 \"전월 데이터 입력 필요\".\n const isPending = kpiMetricValue.value === undefined || kpiMetricValue.value === null\n\n return html`<div class=\"row\">\n <div\n class=\"label ${isPending ? 'pending' : ''}\"\n title=${isPending ? (isMonthly ? '전월 데이터 입력 필요' : '아직 입력되지 않은 항목') : ''}\n >\n ${metric.name}\n </div>\n <div class=\"cell ${this.justFilled[metric.id] ? 'just-filled' : ''} ${isPending ? 'pending' : ''}\">\n <input\n numeric\n .value=${actualValue ?? 0}\n ?disabled=${this.isCompleted}\n @input=${(e: InputEvent) => this._onInputChange(e, metric)}\n />\n ${unit} ${src ? html`<span class=\"src-chip\">🔗 ${src}</span>` : ''}\n </div>\n <div class=\"desc\">${metric.description || ''}</div>\n </div>`\n }\n\n private _renderCollectPanel() {\n return html`\n <div class=\"collect-panel\">\n <div class=\"collect-head\">\n <div class=\"ttl\">시스템 연동 자동 수집 <small>외부 시스템에서 기본정보를 가져옵니다</small></div>\n <div class=\"collect-actions\">\n <div\n class=\"collect-btn supervision ${this.canAutoCollect ? '' : 'disabled'}\"\n ?disabled=${this.collecting}\n title=${this.canAutoCollect ? '시공감리 플랫폼에 입력된 정보를 그대로 가져옵니다' : 'kpi:auto-collect 권한 필요'}\n @click=${() => (this.canAutoCollect && !this.collecting ? this._collectSupervision() : null)}\n >\n ${this.collecting ? '수집 중…' : '시공감리 플랫폼 자동 수집'}\n </div>\n <div\n class=\"collect-btn ${this.canAutoCollect ? '' : 'disabled'}\"\n ?disabled=${this.collecting}\n title=${this.canAutoCollect ? '' : 'kpi:auto-collect 권한 필요'}\n @click=${() => (this.canAutoCollect && !this.collecting ? this._autoCollect() : null)}\n >\n ${this.collecting ? '수집 중…' : '⟳ 자동 수집'}\n </div>\n </div>\n </div>\n <div class=\"source-list\">\n ${INTEGRATION_SOURCES.map(meta => {\n const status = this.collectStatus[meta.source] || 'idle'\n const errMsg = this.collectError[meta.source]\n // 카드 안의 수집 필드 summary — 실제/계획 무관 collectedSummary 기준으로 통일.\n // (valueSources 만 보면 키스콘처럼 계획값만 채우는 시스템은 표시 안 됨)\n const collectedFields = this._collectedFieldsOf(meta.label)\n const statusText =\n status === 'collecting' ? '연동 중…' : status === 'done' ? '완료 ✓' : status === 'error' ? '정보 없음 ⓘ' : '대기'\n return html`\n <div class=\"source-card ${status}\">\n <div class=\"sc-head\">\n <span class=\"sys-icon\">${meta.icon}</span>\n <span>${meta.label}</span>\n <span class=\"sc-status ${status}\">${statusText}</span>\n </div>\n <div class=\"sc-desc\">${meta.desc}</div>\n ${status === 'error' && errMsg ? html`<div class=\"sc-error-msg\">${errMsg}</div>` : ''}\n ${\n status === 'done' && collectedFields.length\n ? html`<div class=\"sc-fields\">\n ${collectedFields.map(f => html`<div class=\"sc-field\"><span>${f.label}</span><b>${f.text}</b></div>`)}\n </div>`\n : ''\n }\n </div>\n `\n })}\n </div>\n </div>\n `\n }\n\n /** 패널 카드에 표시할, 해당 시스템에서 수집한 값 요약 */\n private _collectedFieldsOf(sourceLabel: string): { label: string; text: string }[] {\n return this.collectedSummary[sourceLabel] || []\n }\n\n /** 자동 수집 — 백엔드의 collectProjectExternalData mutation 1번 호출.\n * 서버가 시나리오 3종 (세움터/올바로/키스콘) 을 순차 실행하고 시스템별 ok/메시지를 반환.\n * 성공/실패와 무관하게 시나리오 step 이 KPI Value 를 DB 에 적재하므로 호출 후\n * KPI Metric Values 를 재조회해 form 을 자동 갱신.\n *\n * 운영 현실: 키스콘·올바로는 시공사 자격증명 미확보가 많아 오류 빈도 높음.\n * 세움터는 공공데이터 서비스키만으로 동작 가능. 시스템별로 카드에 독립 표시.\n */\n private async _autoCollect() {\n if (this.isCompleted) return\n if (!this.project?.id) {\n notify({ message: '프로젝트 정보가 없습니다.' })\n return\n }\n this.collecting = true\n this.collectStatus = {}\n this.collectError = {}\n this.collectedSummary = {}\n this.valueSources = {}\n\n // 모든 카드를 collecting 상태로 표시\n const collecting: Record<string, 'collecting'> = {}\n for (const meta of INTEGRATION_SOURCES) collecting[meta.source] = 'collecting'\n this.collectStatus = collecting\n\n try {\n const results = await collectProjectExternalData(this.project.id)\n\n // 시스템별 status / error 메시지 업데이트 + result data 를 form 에 매핑\n const status: Record<string, 'done' | 'error'> = {}\n const errors: Record<string, string> = {}\n for (const r of results) {\n status[r.source] = r.ok ? 'done' : 'error'\n if (!r.ok) {\n errors[r.source] = r.message\n continue\n }\n // 시나리오 result 객체 = { projectKey: value, ... }.\n // KPI_METRIC_KEY_MAPPING 매칭 키 → form 채움. 미매칭 키 (siteType,\n // structureType 등 프로젝트/BuildingComplex 기본 속성) → 카드 요약만.\n this._applyCollectionResult(r)\n }\n this.collectStatus = status\n this.collectError = errors\n\n // 재조회 안 함 — 시나리오는 fetch + result 반환만 책임 (DB 적재 X). 직전에\n // _setMetricValue 가 memory 에 채운 값들을 DB 의 옛 값으로 덮어쓰면 자동수집\n // 결과가 폼에 반영되지 않음. 사용자가 [저장] 버튼 누르면 그제서야 DB 반영.\n this.requestUpdate()\n setTimeout(() => (this.justFilled = {}), 1300)\n\n const okCount = results.filter(r => r.ok).length\n if (okCount === results.length) {\n notify({ message: `외부 시스템 ${okCount}건 모두 수집 완료. 검토 후 [저장]을 눌러주세요.` })\n } else if (okCount === 0) {\n notify({ message: '외부 시스템에서 가져올 정보가 없습니다.' })\n } else {\n notify({ message: `외부 시스템 수집 — ${okCount}건 완료, 나머지는 정보 없음.` })\n }\n } catch (e) {\n // mutation 자체 실패 (네트워크/권한 등) — 모든 카드를 정보 없음 으로\n const msg = e instanceof Error ? e.message : String(e)\n console.warn('[자동수집] 전체 호출 실패', e)\n const status: Record<string, 'error'> = {}\n const errors: Record<string, string> = {}\n for (const meta of INTEGRATION_SOURCES) {\n status[meta.source] = 'error'\n errors[meta.source] = '업데이트할 정보가 없습니다'\n }\n this.collectStatus = status\n this.collectError = errors\n notify({ message: '외부 시스템 연동이 일시적으로 안 됩니다.' })\n } finally {\n this.collecting = false\n }\n }\n\n /**\n * 메트릭의 실제값 set/add — _onInputChange 와 동일하게 periodType 별 row upsert.\n */\n\n /**\n * 시공감리 플랫폼(DCSP) 에서 기본정보를 가져온다.\n *\n * 외부 연동 3종과 버튼을 나눈 이유: 이건 네트워크 호출이 아니라 같은 DB 의 같은 프로젝트를\n * 읽는 것이고, 실패 사유도 다르다 (자격증명 문제가 아니라 \"시공감리 쪽에 아직 입력이\n * 없다\"). 한 버튼에 묶으면 사용자가 무엇을 고쳐야 할지 알 수 없다.\n *\n * 단위 변환은 서버가 한다 — 재해율 %→비율, 공사기간 날짜→개월.\n */\n private async _collectSupervision() {\n if (this.isCompleted) return\n if (!this.project?.id) {\n notify({ message: '프로젝트 정보가 없습니다.' })\n return\n }\n\n this.collecting = true\n this.collectStatus = { ...this.collectStatus, supervision: 'collecting' }\n this.collectError = { ...this.collectError, supervision: '' }\n\n try {\n const result = await collectProjectSupervisionData(this.project.id, this.project?.code)\n\n this.collectStatus = { ...this.collectStatus, supervision: result.ok ? 'done' : 'error' }\n\n if (!result.ok) {\n this.collectError = { ...this.collectError, supervision: result.message }\n notify({ message: result.message })\n return\n }\n\n this._applyCollectionResult(result)\n notify({ message: '시공감리 플랫폼에서 가져왔습니다. 확인 후 저장해 주세요.' })\n } catch (e: any) {\n this.collectStatus = { ...this.collectStatus, supervision: 'error' }\n this.collectError = { ...this.collectError, supervision: e?.message || '수집에 실패했습니다' }\n notify({ message: e?.message || '수집에 실패했습니다' })\n } finally {\n this.collecting = false\n }\n }\n\n /**\n * 수집 결과 한 건을 폼에 반영한다.\n *\n * 외부 시스템 수집(자동 수집)과 시공감리 플랫폼 복사가 같은 결과 모양\n * ({ source, label, ok, message, data })을 쓰므로 반영 규칙도 하나로 둔다. 둘이\n * 갈라지면 같은 값이 어느 버튼을 눌렀느냐에 따라 다르게 들어간다.\n */\n private _applyCollectionResult(r: any) {\n const summary: { label: string; text: string }[] = []\n // KPI_METRIC_KEY_MAPPING 에 없는 키들의 한글 라벨. BuildingComplex/Project 직접 속성용.\n const NON_KPI_LABEL: Record<string, string> = {\n siteType: '건축프로젝트유형',\n structureType: '구조형태',\n coverageRatio: '건폐율',\n householdCount: '세대수',\n buildingCount: '동수',\n startDate: '착공일',\n endDate: '준공일',\n permitDate: '건축허가일',\n bldNm: '건물명'\n }\n for (const [projectKey, rawValue] of Object.entries(r.data || {})) {\n if (rawValue === null || rawValue === undefined || rawValue === '') continue\n // 비-primitive (객체/배열) 값은 시나리오 측 return 형태 오류 — form 매핑/표시\n // 모두 skip 하고 console 에 진단 로그만 남김. 흔한 원인은 시나리오 마지막 step 이\n // `return { result: { ... } }` 처럼 wrap 한 케이스.\n if (typeof rawValue === 'object') {\n console.warn(`[자동수집] '${r.label}'.${projectKey} 가 object — 시나리오 return 형태 점검`, rawValue)\n continue\n }\n const mapping = KPI_METRIC_KEY_MAPPING.find(x => x.projectKey === projectKey)\n const label = mapping?.label || NON_KPI_LABEL[projectKey] || projectKey\n\n // summary 카드 표시용 — string 값은 그대로, number 는 toLocaleString\n const numericValue = typeof rawValue === 'number' ? rawValue : Number(rawValue)\n const isNumeric = Number.isFinite(numericValue) && typeof rawValue !== 'string'\n const valueText = isNumeric ? numericValue.toLocaleString() : String(rawValue)\n summary.push({ label, text: valueText })\n\n // KPI 매핑이 없는 키 (siteType/structureType 등) 는 form 채움 skip — 단지 노출만.\n if (!mapping) continue\n\n /*\n * 계획값/실제값을 가르지 않는다.\n *\n * 예전에는 계획 칼럼이 따로 있어 계획공사비·계획공사기간만 collectedPlan/planSources\n * 로 빠졌다. 그 칼럼이 없어진 뒤로도 분기가 남아 — 두 항목은 값만 채워지고 출처\n * 뱃지(🔗)가 안 붙었다. 뱃지는 valueSources[metric.id] 를 읽기 때문이다.\n * 지금은 한 줄에 값 하나뿐이므로 경로도 하나다.\n */\n const metric = this.kpiMetrics.find(\n (m: any) => KPI_METRIC_KEY_MAPPING.find(x => x.label === m.name)?.projectKey === projectKey\n )\n if (!metric) continue\n this._setMetricValue(metric, numericValue)\n this.valueSources = { ...this.valueSources, [metric.id]: r.label }\n this.justFilled = { ...this.justFilled, [metric.id]: true }\n }\n this.collectedSummary = { ...this.collectedSummary, [r.label]: summary }\n }\n\n private _setMetricValue(metric: any, value: number | string) {\n this.kpiMetricValues = this._upsertValueForMetric(this.kpiMetricValues, metric, value)\n }\n\n async connectedCallback() {\n super.connectedCallback()\n const [canSave, canAutoCollect] = await Promise.all([\n hasPrivilege({ category: 'kpi', privilege: 'input', domainOwnerGranted: true, superUserGranted: true }),\n hasPrivilege({ category: 'kpi', privilege: 'auto-collect', domainOwnerGranted: true, superUserGranted: true })\n ])\n this.canSave = canSave\n this.canAutoCollect = canAutoCollect\n }\n\n willUpdate(changedProperties: Map<string, any>) {\n super.willUpdate(changedProperties)\n\n // project가 변경되고, project.id가 존재하면 데이터 로드\n if (changedProperties.has('project') && this.project?.id) {\n this._getInitData()\n }\n }\n\n private async _getInitData() {\n // getKpiMetrics 가 이미 active=true 만 반환. 여기선 평가 (Step2) 만 추가 제외.\n // 비활성화는 KPI 관리 admin 에서 active=false 처리.\n const kpiMetrics = await getKpiMetrics(this.project?.code)\n // 평가(Step2) 만 제외. 나머지는 기본정보 탭에서 '기본정보'/'월별 데이터' 두 섹션으로 구분 렌더.\n this.kpiMetrics = kpiMetrics.filter(item => !item.name.includes('평가')) || []\n this.kpiMetricValues = await getKpiMetricValues(this.project.id, this.project?.code)\n }\n\n // Input 요소의 값이 변경될 때 호출되는 콜백 함수\n private _onInputChange(event: InputEvent, metric: any) {\n const target = event.target as HTMLInputElement\n let inputVal: any = target.value\n\n // 숫자 타입은 다른 문자 입력 제거\n if (target.hasAttribute('numeric')) {\n inputVal = Number(inputVal.replace(/[^\\d.-]/g, ''))\n }\n\n this.kpiMetricValues = this._upsertValueForMetric(this.kpiMetricValues, metric, inputVal)\n }\n\n /**\n * metric.periodType 에 따라 적절한 row 를 upsert.\n * - MONTH : 전월 row (YYYY-MM-01). \"오늘 입력 = 지난달 데이터\" 운영 원칙.\n * - ALLTIME : 단일 row (valueDate sentinel 무관).\n * - 그 외 (DAY/WEEK/QUARTER/YEAR/RANGE) : metric.periodType 그대로, valueDate=today.\n * 매칭은 (metricId, periodType) 기준 — MONTH 만 추가로 valueDate prefix.\n */\n private _upsertValueForMetric(values: any[], metric: any, value: any): any[] {\n const today = moment().tz('Asia/Seoul')\n const todayYmd = today.format('YYYY-MM-DD')\n const periodType = metric.periodType\n let updated = [...values]\n\n if (periodType === 'MONTH') {\n const lastMonth = today.clone().subtract(1, 'month')\n const lastMonth1 = lastMonth.format('YYYY-MM-01')\n const lastMonthYm = lastMonth.format('YYYY-MM')\n const idx = updated.findIndex(\n (i: any) => i.metricId === metric.id && i.periodType === 'MONTH' && (i.valueDate || '').startsWith(lastMonthYm)\n )\n if (idx !== -1) {\n updated[idx] = { ...updated[idx], value }\n } else {\n updated.push({\n id: crypto.randomUUID(),\n value,\n metricId: metric.id,\n unit: metric.unit || '',\n org: this.project.id,\n periodType: 'MONTH',\n valueDate: lastMonth1\n })\n }\n } else {\n // ALLTIME / DAY / WEEK / QUARTER / YEAR / RANGE — metric.periodType 그대로 단일 row upsert.\n const idx = updated.findIndex((i: any) => i.metricId === metric.id && i.periodType === periodType)\n if (idx !== -1) {\n updated[idx] = { ...updated[idx], value }\n } else {\n updated.push({\n id: crypto.randomUUID(),\n value,\n metricId: metric.id,\n unit: metric.unit || '',\n org: this.project.id,\n periodType,\n valueDate: todayYmd\n })\n }\n }\n\n return updated\n }\n\n private async _save() {\n if (this.isCompleted) return\n // 사용자가 실제 편집한 row 만 저장. 자동 산정 metric (계획/실제 공사비·공기) 는\n // 별도의 데이터 출처(project 정보, 키스콘 수집, 다른 metric 의 전월 row 등)에서\n // 도출돼야 하므로 이 화면에서 하드코딩 derive 하지 않음.\n const response = await updateProjectCompleteStep1(this.kpiMetricValues, this.project?.code)\n if (!response.errors) {\n // 양방향 동기 — 연면적/용적률/투입인력/설계변경건 값을 BuildingComplex(프로젝트 정보)에도 반영.\n // 보조 동기 실패가 저장 성공 UX 를 막지 않도록 warn 만.\n try {\n await syncKpiToProjectInfo(this.project, this.kpiMetrics, this.kpiMetricValues, this.project?.code)\n } catch (e) {\n console.warn('[KPI→프로젝트정보] BuildingComplex 동기 실패', e)\n }\n notify({ message: '저장되었습니다.' })\n }\n }\n\n private _reset() {\n this._getInitData()\n }\n}\n"]}
@@ -7,6 +7,8 @@ export declare class SvProjectCompleteTab2Rating extends LitElement {
7
7
  kpiMetrics: any;
8
8
  /** kpi:assessment — Step2 평가 저장 권한 */
9
9
  canSave: boolean;
10
+ /** 완료(COMPLETED) 프로젝트 — 입력된 값은 보이지만 입력·저장은 하지 않는다. */
11
+ private get isCompleted();
10
12
  render(): import("lit-html").TemplateResult<1>;
11
13
  connectedCallback(): Promise<void>;
12
14
  willUpdate(changedProperties: Map<string, any>): void;