@dssp/dkpi 1.0.0-alpha.97 → 1.0.0-alpha.99

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 (29) hide show
  1. package/dist-client/bootstrap.js +5 -1
  2. package/dist-client/bootstrap.js.map +1 -1
  3. package/dist-client/pages/project-complete-tabs/pc-tab1-plan.d.ts +1 -1
  4. package/dist-client/pages/project-complete-tabs/pc-tab1-plan.js +6 -6
  5. package/dist-client/pages/project-complete-tabs/pc-tab1-plan.js.map +1 -1
  6. package/dist-client/pages/project-complete-tabs/pc-tab2-rating.d.ts +3 -0
  7. package/dist-client/pages/project-complete-tabs/pc-tab2-rating.js +17 -24
  8. package/dist-client/pages/project-complete-tabs/pc-tab2-rating.js.map +1 -1
  9. package/dist-client/pages/project-complete-tabs/pc-tab4-monthly.d.ts +8 -1
  10. package/dist-client/pages/project-complete-tabs/pc-tab4-monthly.js +39 -30
  11. package/dist-client/pages/project-complete-tabs/pc-tab4-monthly.js.map +1 -1
  12. package/dist-client/pages/sv-project-completed-list.js +2 -2
  13. package/dist-client/pages/sv-project-completed-list.js.map +1 -1
  14. package/dist-client/pages/sv-project-detail.js +61 -71
  15. package/dist-client/pages/sv-project-detail.js.map +1 -1
  16. package/dist-client/pages/sv-project-list.d.ts +2 -2
  17. package/dist-client/pages/sv-project-list.js +1 -1
  18. package/dist-client/pages/sv-project-list.js.map +1 -1
  19. package/dist-client/pages/sv-project-update.js +16 -27
  20. package/dist-client/pages/sv-project-update.js.map +1 -1
  21. package/dist-client/shared/integration-fetch.js +1 -1
  22. package/dist-client/shared/integration-fetch.js.map +1 -1
  23. package/dist-client/shared/kpi-project-sync.d.ts +3 -2
  24. package/dist-client/shared/kpi-project-sync.js +22 -20
  25. package/dist-client/shared/kpi-project-sync.js.map +1 -1
  26. package/dist-client/tsconfig.tsbuildinfo +1 -1
  27. package/dist-server/tsconfig.tsbuildinfo +1 -1
  28. package/package.json +3 -3
  29. package/schema.graphql +9 -0
@@ -77,9 +77,7 @@ let SvProjectUpdate = class SvProjectUpdate extends ScopedElementsMixin(PageView
77
77
  get context() {
78
78
  var _a, _b;
79
79
  return {
80
- title: !isProjectTypeDomain() && ((_a = this.project) === null || _a === void 0 ? void 0 : _a.name)
81
- ? `프로젝트 정보 관리 - ${(_b = this.project) === null || _b === void 0 ? void 0 : _b.name}`
82
- : '프로젝트 정보 관리'
80
+ title: !isProjectTypeDomain() && ((_a = this.project) === null || _a === void 0 ? void 0 : _a.name) ? `프로젝트 정보 관리 - ${(_b = this.project) === null || _b === void 0 ? void 0 : _b.name}` : '프로젝트 정보 관리'
83
81
  };
84
82
  }
85
83
  render() {
@@ -141,7 +139,7 @@ let SvProjectUpdate = class SvProjectUpdate extends ScopedElementsMixin(PageView
141
139
  </span>
142
140
  </div>
143
141
  <div row>
144
- <span>연 면적</span>
142
+ <span>연면적</span>
145
143
  <span align-end
146
144
  ><md-outlined-text-field
147
145
  type="text"
@@ -171,7 +169,7 @@ let SvProjectUpdate = class SvProjectUpdate extends ScopedElementsMixin(PageView
171
169
  </div>
172
170
 
173
171
  <div row>
174
- <span>착공일정 ~ 준공일정</span>
172
+ <span>착공일정 ~ 준공일정(계획)</span>
175
173
  <span
176
174
  ><input
177
175
  type="date"
@@ -216,16 +214,10 @@ let SvProjectUpdate = class SvProjectUpdate extends ScopedElementsMixin(PageView
216
214
  <md-select-option ?selected=${!this.project.buildingUsage} value="">
217
215
  <div slot="headline">선택</div>
218
216
  </md-select-option>
219
- <md-select-option
220
- ?selected=${this.project.buildingUsage === 'RESIDENTIAL'}
221
- value="RESIDENTIAL"
222
- >
217
+ <md-select-option ?selected=${this.project.buildingUsage === 'RESIDENTIAL'} value="RESIDENTIAL">
223
218
  <div slot="headline">주거</div>
224
219
  </md-select-option>
225
- <md-select-option
226
- ?selected=${this.project.buildingUsage === 'NON_RESIDENTIAL'}
227
- value="NON_RESIDENTIAL"
228
- >
220
+ <md-select-option ?selected=${this.project.buildingUsage === 'NON_RESIDENTIAL'} value="NON_RESIDENTIAL">
229
221
  <div slot="headline">비주거</div>
230
222
  </md-select-option>
231
223
  </md-filled-select>
@@ -292,7 +284,7 @@ let SvProjectUpdate = class SvProjectUpdate extends ScopedElementsMixin(PageView
292
284
  </span>
293
285
  </div>
294
286
  <div row>
295
- <span>공사금액</span>
287
+ <span>계획공사비</span>
296
288
  <span
297
289
  ><md-outlined-text-field
298
290
  type="text"
@@ -306,7 +298,7 @@ let SvProjectUpdate = class SvProjectUpdate extends ScopedElementsMixin(PageView
306
298
  </span>
307
299
  </div>
308
300
  <div row>
309
- <span>투입 인력</span>
301
+ <span>총 투입인력(M/M)</span>
310
302
  <span
311
303
  ><md-outlined-text-field
312
304
  type="text"
@@ -315,7 +307,7 @@ let SvProjectUpdate = class SvProjectUpdate extends ScopedElementsMixin(PageView
315
307
  building-complex
316
308
  .value=${((_z = (_y = (_x = this.project) === null || _x === void 0 ? void 0 : _x.buildingComplex) === null || _y === void 0 ? void 0 : _y.workerCount) === null || _z === void 0 ? void 0 : _z.toString()) || ''}
317
309
  @input=${this._onInputChange}
318
- suffix-text=""
310
+ suffix-text="(M/M)"
319
311
  ></md-outlined-text-field>
320
312
  </span>
321
313
  </div>
@@ -329,8 +321,8 @@ let SvProjectUpdate = class SvProjectUpdate extends ScopedElementsMixin(PageView
329
321
  <span>
330
322
  ${this.project.tenantDomain
331
323
  ? html `<a href=${`/project/${this.project.code}/`} target="_blank" title="테넌트로 이동"
332
- >${this.project.code} <md-icon style="font-size:14px;vertical-align:middle">open_in_new</md-icon></a
333
- >`
324
+ >${this.project.code} <md-icon style="font-size:14px;vertical-align:middle">open_in_new</md-icon></a
325
+ >`
334
326
  : this.project.code
335
327
  ? html `${this.project.code}`
336
328
  : html `<span style="color:#999">미발번 (승격 시 자동 부여)</span>`}
@@ -351,16 +343,14 @@ let SvProjectUpdate = class SvProjectUpdate extends ScopedElementsMixin(PageView
351
343
  <span tenant-actions>
352
344
  ${this.project.tenantDomain
353
345
  ? html `<md-elevated-button @click=${this._demoteTenant} ?disabled=${!this.project.id}>
354
- <md-icon slot="icon">link_off</md-icon>테넌트 강등
355
- </md-elevated-button>`
346
+ <md-icon slot="icon">link_off</md-icon>테넌트 강등
347
+ </md-elevated-button>`
356
348
  : html `<md-elevated-button green @click=${this._promoteTenant} ?disabled=${!this.project.id}>
357
- <md-icon slot="icon">verified</md-icon>테넌트 승격
358
- </md-elevated-button>`}
349
+ <md-icon slot="icon">verified</md-icon>테넌트 승격
350
+ </md-elevated-button>`}
359
351
  </span>
360
352
  </div>
361
353
  </div>
362
-
363
-
364
354
  </div>
365
355
  </div>
366
356
  `;
@@ -542,8 +532,7 @@ let SvProjectUpdate = class SvProjectUpdate extends ScopedElementsMixin(PageView
542
532
  // buildingComplex.siteType 가 SiteType enum 외 값 ('BUILDING' 같은 옛 잔재) 이면
543
533
  // mutation validation 실패. enum 에 없으면 null 로 정정.
544
534
  const validSiteTypes = new Set(Object.values(SiteType));
545
- if (((_a = this.project.buildingComplex) === null || _a === void 0 ? void 0 : _a.siteType) &&
546
- !validSiteTypes.has(this.project.buildingComplex.siteType)) {
535
+ if (((_a = this.project.buildingComplex) === null || _a === void 0 ? void 0 : _a.siteType) && !validSiteTypes.has(this.project.buildingComplex.siteType)) {
547
536
  this.project.buildingComplex.siteType = null;
548
537
  }
549
538
  const response = await client.mutate({
@@ -563,7 +552,7 @@ let SvProjectUpdate = class SvProjectUpdate extends ScopedElementsMixin(PageView
563
552
  }
564
553
  });
565
554
  if (!response.errors) {
566
- // 양방향 동기 — 연면적/용적율/투입인력/설계변경건을 KPI 기준정보(KpiMetricValue)에 반영하고,
555
+ // 양방향 동기 — 연면적/용적률/투입인력/설계변경건을 KPI 기준정보(KpiMetricValue)에 반영하고,
567
556
  // 공사일정(startDate/endDate)은 KPI "계획기간"(constructionPeriod)으로 단방향 반영.
568
557
  // 보조 동기 실패가 저장 성공 UX 를 막지 않도록 warn 만.
569
558
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"sv-project-update.js","sourceRoot":"","sources":["../../client/pages/sv-project-update.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\nimport '@material/web/button/elevated-button.js'\nimport '@material/web/textfield/outlined-text-field.js'\n\nimport { PageView } from '@operato/shell'\nimport { PageLifecycle } from '@operato/shell/dist/src/app/pages/page-view'\nimport { css, html } from 'lit'\nimport { customElement, state } from 'lit/decorators.js'\nimport { ScopedElementsMixin } from '@open-wc/scoped-elements'\nimport { client } from '@operato/graphql'\nimport { notify } from '@operato/layout'\nimport { OxPrompt } from '@operato/popup'\nimport gql from 'graphql-tag'\nimport { Project, SiteType, SITE_TYPE_DISPLAY } from '@dssp/project/dist-client/pages/project/project-list'\nimport '@dssp/project/dist-client/pages/lib/select2-component'\nimport './component/project-update-header'\nimport { isProjectTypeDomain, tenantHeaders } from '@dssp/project/dist-client/shared/domain-context'\nimport { inferGeoGroupFromPostalCode } from '../shared/geo-group-mapping'\nimport { syncProjectInfoToKpi } from '../shared/kpi-project-sync'\n\n@customElement('sv-project-update')\nexport class SvProjectUpdate extends ScopedElementsMixin(PageView) {\n static styles = [\n css`\n :host {\n display: grid;\n grid-template-rows: 55px auto;\n color: #4e5055;\n\n width: 100%;\n background-color: var(--md-sys-color-background, #f6f6f6);\n overflow-y: auto;\n\n --grid-record-emphasized-background-color: red;\n --grid-record-emphasized-color: yellow;\n }\n\n md-outlined-text-field {\n width: 100%;\n\n --md-outlined-text-field-container-shape: 5px;\n --md-outlined-text-field-outline-color: rgba(51, 51, 51, 0.2);\n --md-outlined-text-field-focus-outline-color: #1f7fd9;\n --md-outlined-text-field-focus-outline-width: 1px;\n --md-sys-color-primary: #586878;\n --md-outlined-text-field-input-text-size: 14px;\n --md-outlined-field-bottom-space: 3px;\n --md-outlined-field-top-space: 3px;\n --md-outlined-field-leading-space: var(--spacing-medium, 8px);\n --md-outlined-field-trailing-space: var(--spacing-medium, 8px);\n }\n select2-component {\n width: 100%;\n }\n md-filled-select {\n width: 100%;\n --md-filled-select-text-field-container-color: transparent;\n --md-filled-select-text-field-active-indicator-color: #999;\n --md-filled-select-text-field-input-text-size: 14px;\n --md-filled-select-text-field-input-text-line-height: 6px;\n }\n input[type='date'] {\n border: 1px solid rgba(51, 51, 51, 0.2);\n padding: var(--spacing-small, 4px) var(--spacing-medium, 8px);\n border-radius: 5px;\n }\n md-outlined-text-field[type='textarea'] {\n width: 100%;\n height: 120px;\n resize: none;\n }\n\n ox-input-image {\n width: 120px;\n height: 90px;\n }\n ox-input-file {\n width: 120px;\n height: 90px;\n padding: 0;\n }\n\n div[body] {\n display: flex;\n margin: var(--spacing-large, 12px);\n margin-top: 0;\n gap: var(--spacing-medium, 8px);\n\n & > div {\n display: flex;\n height: fit-content;\n flex-direction: column;\n padding: var(--spacing-large, 12px);\n background-color: var(--md-sys-color-on-primary);\n border: 1px solid #cccccc80;\n border-radius: 5px;\n gap: var(--spacing-medium, 8px);\n\n h3 {\n color: #2e79be;\n font-size: 16px;\n margin: 0px;\n }\n\n div[row] {\n display: flex;\n align-items: center;\n\n span:first-child {\n flex: 0.3;\n text-align: right;\n font-size: 14px;\n }\n\n span:last-child {\n flex: 0.7;\n display: flex;\n gap: var(--spacing-medium, 8px);\n margin-left: var(--spacing-medium, 8px);\n align-items: center;\n\n &.address-row {\n md-outlined-text-field {\n flex: 1;\n }\n .info-link-btn {\n flex: 0 0 auto;\n --md-elevated-button-container-color: #2e79be;\n --md-elevated-button-label-text-color: #fff;\n --md-elevated-button-container-height: 36px;\n }\n }\n\n &[align-end] {\n align-items: end;\n }\n }\n }\n }\n\n div[project-info] {\n flex: 0.55;\n }\n\n div[document-preview] {\n width: 100%;\n font-size: 12px;\n color: #2e79be;\n font-weight: 500;\n background-color: #f8f9fa;\n padding: 8px 12px;\n border-radius: 4px;\n border-left: 3px solid #2e79be;\n margin-top: 4px;\n }\n\n div[detail-info] {\n flex: 0.45;\n padding: 0px;\n gap: 10px;\n background-color: transparent;\n border: none;\n\n & > div {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-medium, 8px);\n border: 1px solid #cccccc80;\n border-radius: 5px;\n padding: var(--spacing-large, 12px);\n background-color: var(--md-sys-color-on-primary);\n\n md-outlined-text-field[type='text'] {\n width: 60%;\n }\n\n md-elevated-button {\n --md-elevated-button-container-height: 30px;\n --md-elevated-button-container-color: var(--md-sys-color-on-primary);\n --md-elevated-button-label-text-size: 16px;\n }\n hr {\n border: 1px rgba(51, 51, 51, 0.1) dashed;\n width: 100%;\n margin-bottom: var(--spacing-tiny, 2px);\n }\n div[warn] {\n font-size: 12px;\n color: red;\n margin-left: var(--spacing-small, 4px);\n margin-bottom: var(--spacing-small, 4px);\n }\n\n md-outlined-button {\n min-height: 30px;\n padding: 0 var(--spacing-large, 12px);\n box-shadow: 1px 1px 1px #0000001a;\n --md-outlined-button-label-text-color: #586878;\n --md-outlined-button-label-text-weight: bold;\n --md-sys-color-outline: rgba(51, 51, 51, 0.2);\n }\n\n div[row] {\n span:first-child {\n flex: 0.2;\n }\n span:last-child {\n flex: 0.8;\n }\n }\n\n div[separate-container] {\n display: grid;\n grid-template-columns: repeat(2, 1fr);\n gap: 8px 30px;\n margin-left: 15px;\n\n md-outlined-text-field {\n width: 80px;\n }\n\n & > div {\n display: flex;\n gap: 15px;\n\n span[floor-row] {\n display: inline-flex;\n align-items: center;\n margin-left: 5px;\n }\n\n span[floor-title] {\n min-width: 33px;\n margin-right: var(--spacing-small, 4px);\n }\n span:first-child {\n flex: 0.4;\n justify-content: flex-start;\n display: flex;\n }\n span:last-child {\n flex: 0.6;\n display: flex;\n align-items: center;\n padding-left: var(--spacing-medium, 8px);\n }\n }\n\n div[basement-container] {\n display: flex;\n align-items: center;\n gap: 4px;\n\n div[basement-checkbox] {\n display: flex;\n align-items: center;\n cursor: pointer;\n\n md-icon {\n --md-icon-size: 25px;\n color: #808080;\n transition: opacity 0.2s ease;\n }\n\n &[disabled] {\n cursor: not-allowed;\n opacity: 0.4;\n\n md-icon {\n color: #cccccc;\n }\n }\n\n &[checked] md-icon {\n background-color: #2e79be;\n color: white;\n border-radius: 3px;\n }\n }\n\n md-outlined-text-field[basement-floor] {\n width: 70px;\n margin-left: var(--spacing-small, 4px);\n opacity: 1;\n transition: opacity 0.2s ease;\n\n &[disabled] {\n opacity: 0.4;\n pointer-events: none;\n }\n }\n }\n }\n\n &[project] {\n div[separate-container] {\n margin-left: 15px;\n\n & > div {\n align-items: center;\n\n span:first-child {\n flex: 0.4;\n min-width: 100px;\n }\n span:last-child {\n flex: 0.4;\n margin-left: 0;\n }\n }\n }\n }\n }\n }\n }\n `\n ]\n\n get context() {\n return {\n title:\n !isProjectTypeDomain() && this.project?.name\n ? `프로젝트 정보 관리 - ${this.project?.name}`\n : '프로젝트 정보 관리'\n }\n }\n\n private defaultProject = {\n name: '',\n documentNaming: '',\n buildingComplex: {\n address: '',\n area: 0,\n constructionCompany: '',\n clientCompany: '',\n supervisoryCompany: '',\n designCompany: '',\n constructionType: '',\n buildings: []\n }\n }\n @state() projectId: string = ''\n @state() project: Project = { ...this.defaultProject }\n @state() selectedValues: string[] = []\n @state() overallConstructorList: Array<{ name: string; value: string }> = []\n @state() taskConstructorList: Array<{ name: string; value: string }> = []\n @state() taskSupervisoryList: Array<{ name: string; value: string }> = []\n @state() overallSupervisoryList: Array<{ name: string; value: string }> = []\n @state() collectingBasicInfo: boolean = false\n\n render() {\n return html`\n <dkpi-project-update-header\n .projectId=${this.project.id || ''}\n .projectState=${(this.project as any).state || ''}\n title=\"프로젝트 정보 관리\"\n @custom-click=${this._saveProject}\n @unfinalize=${this._onUnfinalize}\n ></dkpi-project-update-header>\n\n <div body>\n <div project-info>\n <h3>기본 정보</h3>\n <div row>\n <span>프로젝트명</span>\n <span\n ><md-outlined-text-field\n type=\"text\"\n name=\"name\"\n project\n .value=${this.project.name || ''}\n @input=${this._onInputChange}\n ></md-outlined-text-field>\n </span>\n </div>\n <div row>\n <span>프로젝트 주소</span>\n <span class=\"address-row\">\n <md-outlined-text-field\n type=\"text\"\n name=\"address\"\n building-complex\n .value=${this.project?.buildingComplex?.address || ''}\n @input=${this._onInputChange}\n ></md-outlined-text-field>\n <md-elevated-button\n class=\"info-link-btn\"\n ?disabled=${this.collectingBasicInfo}\n @click=${() => this._collectBasicInfo()}\n title=\"입력된 주소로 세움터 건축물대장 정보를 가져와 폼을 채웁니다\"\n >\n ${this.collectingBasicInfo ? '연동 중…' : '🔗 정보 연동'}\n </md-elevated-button>\n </span>\n </div>\n <div row>\n <span>우편번호</span>\n <span>\n <md-outlined-text-field\n type=\"text\"\n name=\"postalCode\"\n project\n .value=${(this.project as any).postalCode || ''}\n @input=${this._onPostalCodeChange}\n ></md-outlined-text-field>\n </span>\n </div>\n <div row>\n <span>연 면적</span>\n <span align-end\n ><md-outlined-text-field\n type=\"text\"\n name=\"area\"\n numeric\n building-complex\n .value=${this.project?.buildingComplex?.area?.toString() || ''}\n @input=${this._onInputChange}\n suffix-text=\"㎡\"\n ></md-outlined-text-field>\n </span>\n </div>\n\n <div row>\n <span>용적률</span>\n <span align-end>\n <md-outlined-text-field\n type=\"text\"\n name=\"floorAreaRatio\"\n numeric\n building-complex\n .value=${this.project?.buildingComplex?.floorAreaRatio?.toString() || ''}\n @input=${this._onInputChange}\n suffix-text=\"%\"\n ></md-outlined-text-field>\n </span>\n </div>\n\n <div row>\n <span>착공일정 ~ 준공일정</span>\n <span\n ><input\n type=\"date\"\n name=\"startDate\"\n project\n .value=${this.project.startDate || ''}\n @input=${this._onInputChange}\n max=\"9999-12-31\"\n />\n ~\n <input\n type=\"date\"\n name=\"endDate\"\n project\n .value=${this.project.endDate || ''}\n @input=${this._onInputChange}\n max=\"9999-12-31\"\n />\n </span>\n </div>\n\n <div row>\n <span>유형</span>\n <span\n ><md-filled-select name=\"sectorType\" project @change=${this._onInputChange}>\n <md-select-option ?selected=${!(this.project as any).sectorType} value=\"\">\n <div slot=\"headline\">선택</div>\n </md-select-option>\n <md-select-option ?selected=${(this.project as any).sectorType === 'PUBLIC'} value=\"PUBLIC\">\n <div slot=\"headline\">공공</div>\n </md-select-option>\n <md-select-option ?selected=${(this.project as any).sectorType === 'PRIVATE'} value=\"PRIVATE\">\n <div slot=\"headline\">민간</div>\n </md-select-option>\n </md-filled-select>\n </span>\n </div>\n <div row>\n <span>용도</span>\n <span\n ><md-filled-select name=\"buildingUsage\" project @change=${this._onInputChange}>\n <md-select-option ?selected=${!(this.project as any).buildingUsage} value=\"\">\n <div slot=\"headline\">선택</div>\n </md-select-option>\n <md-select-option\n ?selected=${(this.project as any).buildingUsage === 'RESIDENTIAL'}\n value=\"RESIDENTIAL\"\n >\n <div slot=\"headline\">주거</div>\n </md-select-option>\n <md-select-option\n ?selected=${(this.project as any).buildingUsage === 'NON_RESIDENTIAL'}\n value=\"NON_RESIDENTIAL\"\n >\n <div slot=\"headline\">비주거</div>\n </md-select-option>\n </md-filled-select>\n </span>\n </div>\n\n <div row>\n <span>발주처</span>\n <span\n ><md-outlined-text-field\n type=\"text\"\n name=\"clientCompany\"\n building-complex\n .value=${this.project?.buildingComplex?.clientCompany || ''}\n @input=${this._onInputChange}\n ></md-outlined-text-field>\n </span>\n </div>\n <div row>\n <span>건설사</span>\n <span\n ><md-outlined-text-field\n type=\"text\"\n name=\"constructionCompany\"\n building-complex\n .value=${this.project?.buildingComplex?.constructionCompany || ''}\n @input=${this._onInputChange}\n ></md-outlined-text-field>\n </span>\n </div>\n <div row>\n <span>설계사</span>\n <span\n ><md-outlined-text-field\n type=\"text\"\n name=\"designCompany\"\n building-complex\n .value=${this.project?.buildingComplex?.designCompany || ''}\n @input=${this._onInputChange}\n ></md-outlined-text-field>\n </span>\n </div>\n <div row>\n <span>감리사</span>\n <span\n ><md-outlined-text-field\n type=\"text\"\n name=\"supervisoryCompany\"\n building-complex\n .value=${this.project?.buildingComplex?.supervisoryCompany || ''}\n @input=${this._onInputChange}\n ></md-outlined-text-field>\n </span>\n </div>\n\n <div row>\n <span>대표사진 업로드</span>\n <span>\n <ox-input-image\n name=\"mainPhoto\"\n value=${this.project?.mainPhoto?.fullpath || ''}\n @change=${this.onCreateAttachment.bind(this)}\n ></ox-input-image>\n </span>\n </div>\n <div row>\n <span>공사금액</span>\n <span\n ><md-outlined-text-field\n type=\"text\"\n name=\"constructionCost\"\n numeric\n building-complex\n .value=${this.project?.buildingComplex?.constructionCost?.toString() || ''}\n @input=${this._onInputChange}\n suffix-text=\"억원\"\n ></md-outlined-text-field>\n </span>\n </div>\n <div row>\n <span>투입 인력</span>\n <span\n ><md-outlined-text-field\n type=\"text\"\n name=\"workerCount\"\n numeric\n building-complex\n .value=${this.project?.buildingComplex?.workerCount?.toString() || ''}\n @input=${this._onInputChange}\n suffix-text=\"명\"\n ></md-outlined-text-field>\n </span>\n </div>\n </div>\n\n <div detail-info>\n <div>\n <h3>테넌트 관리</h3>\n <div row>\n <span>관리번호</span>\n <span>\n ${this.project.tenantDomain\n ? html`<a href=${`/project/${this.project.code}/`} target=\"_blank\" title=\"테넌트로 이동\"\n >${this.project.code} <md-icon style=\"font-size:14px;vertical-align:middle\">open_in_new</md-icon></a\n >`\n : this.project.code\n ? html`${this.project.code}`\n : html`<span style=\"color:#999\">미발번 (승격 시 자동 부여)</span>`}\n </span>\n </div>\n <div row>\n <span>현재 상태</span>\n <span>\n ${this.project.tenantDomain\n ? html`<b style=\"color:#42b382\">활성 테넌트</b>`\n : this.project.code\n ? html`<b style=\"color:#aa6633\">강등됨</b> (재승격 시 ${this.project.code} 재사용)`\n : html`<b style=\"color:#999\">미승격</b>`}\n </span>\n </div>\n <div row>\n <span></span>\n <span tenant-actions>\n ${this.project.tenantDomain\n ? html`<md-elevated-button @click=${this._demoteTenant} ?disabled=${!this.project.id}>\n <md-icon slot=\"icon\">link_off</md-icon>테넌트 강등\n </md-elevated-button>`\n : html`<md-elevated-button green @click=${this._promoteTenant} ?disabled=${!this.project.id}>\n <md-icon slot=\"icon\">verified</md-icon>테넌트 승격\n </md-elevated-button>`}\n </span>\n </div>\n </div>\n\n\n </div>\n </div>\n `\n }\n\n async pageInitialized(lifecycle: PageLifecycle) {}\n\n async pageUpdated(changes: any, lifecycle: PageLifecycle) {\n if (!this.active) return\n\n this.projectId = await this._resolveProjectId(lifecycle.resourceId || '')\n await this.initProject(this.projectId)\n }\n\n /**\n * resourceId 를 정상 Project.id (UUID) 로 변환한다.\n * - UUID 형식이면 그대로 사용\n * - 그 외 (code 또는 비어있음) 에는 현재 테넌트 컨텍스트의 프로젝트 조회\n */\n private async _resolveProjectId(resourceId: string): Promise<string> {\n if (resourceId && resourceId.length === 36) return resourceId\n\n const response = await client.query({\n query: gql`\n query CurrentProject {\n currentProject {\n id\n }\n }\n `\n })\n\n return response.data?.currentProject?.id || ''\n }\n\n async initProject(projectId: string = '') {\n const response = await client.query({\n query: gql`\n query Project($id: String!, $filters: [Filter!]) {\n project(id: $id) {\n id\n code\n state\n tenantDomain {\n id\n subdomain\n name\n }\n name\n documentNaming\n sectorType\n buildingUsage\n postalCode\n geoGroup\n startDate\n endDate\n mainPhoto {\n fullpath\n }\n totalProgress\n weeklyProgress\n kpi\n inspPassRate\n robotProgressRate\n structuralSafetyRate\n robotCount\n buildingComplex {\n id\n address\n siteType\n latitude\n longitude\n area\n coverageRatio\n floorAreaRatio\n structureType\n workerCount\n designChangeCount\n plannedProgress\n actualProgress\n clientCompany\n constructionCompany\n supervisoryCompany\n designCompany\n drawing {\n id\n name\n }\n constructionType\n constructionCost\n etc\n notice\n householdCount\n buildingCount\n overallConstructorEmails\n taskConstructorEmails\n overallSupervisoryEmails\n taskSupervisoryEmails\n virtualTourLink\n buildings {\n id\n name\n floorCount\n hasBasement\n basementFloorCount\n }\n }\n }\n\n employees(filters: $filters) {\n items {\n id\n name\n jobResponsibility\n active\n user {\n id\n name\n email\n }\n }\n }\n }\n `,\n variables: {\n id: projectId,\n filters: [\n {\n name: 'active',\n operator: 'eq',\n value: true\n },\n {\n name: 'userId',\n operator: 'is_not_null',\n value: ''\n }\n ]\n },\n context: { headers: tenantHeaders((this.project as any)?.code) }\n })\n\n this.project = response.data?.project || { ...this.defaultProject }\n\n const items = response.data?.employees?.items || []\n this.overallConstructorList = this._filterUserByPermission(items, 'OVERALL_CONSTRUCTOR')\n this.taskConstructorList = this._filterUserByPermission(items, 'TASK_CONSTRUCTOR')\n this.overallSupervisoryList = this._filterUserByPermission(items, 'OVERALL_SUPERVISORY')\n this.taskSupervisoryList = this._filterUserByPermission(items, 'TASK_SUPERVISORY')\n\n this.updateContext()\n }\n\n private _filterUserByPermission(userList, permission: string) {\n return userList\n .filter(v => v.jobResponsibility == permission || v.jobResponsibility == 'ADMIN')\n .map(v => ({ name: v.name, value: v.user.email }))\n }\n\n private _stripTypename(obj: any): any {\n if (Array.isArray(obj)) {\n return obj.map(item => this._stripTypename(item))\n }\n if (obj && typeof obj === 'object' && !(obj instanceof File)) {\n const cleaned: any = {}\n for (const [key, value] of Object.entries(obj)) {\n if (key === '__typename') continue\n cleaned[key] = this._stripTypename(value)\n }\n return cleaned\n }\n return obj\n }\n\n private async _saveProject() {\n // 첨부 파일 필드 제거 (첨부 파일은 {filename}Upload 로 전송)\n delete this.project.mainPhoto\n delete this.project.buildingComplex.drawing\n // ProjectPatch 입력 타입에 없는 표시 전용 필드 제거 (테넌트 승격 UI 표시용)\n const projectCode = (this.project as any).code // 도메인 헤더 용으로 미리 보존\n delete (this.project as any).code\n delete (this.project as any).tenantDomain\n // state 는 raw 값('10'/'20')으로 로드되지만 ProjectPatch 는 enum 키 기대 — 일반\n // save 에서는 state 변경 의도 없으므로 patch 에서 제외. (state 변경은 _onUnfinalize 별도)\n delete (this.project as any).state\n\n // buildingComplex.siteType 가 SiteType enum 외 값 ('BUILDING' 같은 옛 잔재) 이면\n // mutation validation 실패. enum 에 없으면 null 로 정정.\n const validSiteTypes = new Set(Object.values(SiteType))\n if (\n this.project.buildingComplex?.siteType &&\n !validSiteTypes.has(this.project.buildingComplex.siteType as any)\n ) {\n this.project.buildingComplex.siteType = null as any\n }\n\n const response = await client.mutate({\n mutation: gql`\n mutation UpdateProject($project: ProjectPatch!) {\n updateProject(project: $project) {\n id\n }\n }\n `,\n variables: {\n project: this._stripTypename(this.project)\n },\n context: {\n hasUpload: true,\n headers: tenantHeaders(projectCode)\n }\n })\n\n if (!response.errors) {\n // 양방향 동기 — 연면적/용적율/투입인력/설계변경건을 KPI 기준정보(KpiMetricValue)에 반영하고,\n // 공사일정(startDate/endDate)은 KPI \"계획기간\"(constructionPeriod)으로 단방향 반영.\n // 보조 동기 실패가 저장 성공 UX 를 막지 않도록 warn 만.\n try {\n await syncProjectInfoToKpi(this.project, projectCode)\n } catch (e) {\n console.warn('[프로젝트정보→KPI] KpiMetricValue 동기 실패', e)\n }\n notify({ message: '저장에 성공하였습니다.' })\n }\n }\n\n /**\n * 완료(COMPLETED) 상태 프로젝트를 진행중(ONGOING) 으로 되돌림.\n * ProjectPatch 의 state 필드만 갱신 — 다른 폼 필드는 건드리지 않음.\n */\n private _onUnfinalize = async () => {\n if (!this.project?.id) return\n const ok = await OxPrompt.open({\n title: '완료 상태 해제',\n confirmButton: { text: '확인' },\n cancelButton: { text: '취소' }\n })\n if (!ok) return\n\n const response = await client.mutate({\n mutation: gql`\n mutation UpdateProject($project: ProjectPatch!) {\n updateProject(project: $project) {\n id\n state\n }\n }\n `,\n variables: { project: { id: this.project.id, state: 'ONGOING' } },\n context: { headers: tenantHeaders((this.project as any)?.code) }\n })\n\n if (!response.errors) {\n notify({ message: '진행중 상태로 변경되었습니다.' })\n await this.initProject(this.project.id)\n }\n }\n\n /**\n * 주소 옆 \"정보 연동\" 버튼 handler.\n * - 현재 폼의 주소를 backend 로 보냄 → backend 가 EnvVar 갱신 + 세움터 시나리오 호출\n * - 결과 (한글 raw + 숫자) 를 받아 BuildingComplex 폼 필드들에 매핑\n * · siteType (한글) → SiteType enum 변환 (BC.siteType, enum 컬럼)\n * · siteType (한글) → BC.constructionType 그대로 (자유 텍스트 컬럼)\n * · area / floorAreaRatio / coverageRatio / householdCount / buildingCount /\n * structureType → BC 동명 컬럼에 직접 대입\n * · upperFloorCount / lowerFloorCount / bldNm / useAprDay / pmsDay → BC 컬럼\n * 없음, 매핑 skip (참고용 콘솔 로그만)\n */\n private async _collectBasicInfo() {\n const address = (this.project?.buildingComplex?.address || '').trim()\n if (!address) {\n notify({ message: '먼저 프로젝트 주소를 입력해 주세요.' })\n return\n }\n this.collectingBasicInfo = true\n try {\n const response = await client.mutate({\n mutation: gql`\n mutation CollectProjectBasicInfo($projectId: String!, $address: String) {\n collectProjectBasicInfo(projectId: $projectId, address: $address) {\n source\n label\n ok\n message\n data\n }\n }\n `,\n variables: { projectId: this.project.id, address },\n context: { headers: tenantHeaders((this.project as any)?.code) }\n })\n if (response.errors) {\n throw new Error(response.errors.map((e: any) => e.message).join('\\n'))\n }\n const result = response.data?.collectProjectBasicInfo\n if (!result?.ok) {\n notify({ message: result?.message || '업데이트할 정보가 없습니다.' })\n return\n }\n const data = result.data || {}\n this._applyBasicInfoToForm(data)\n this.requestUpdate()\n notify({ message: '외부 시스템에서 기본 정보를 가져왔습니다. 검토 후 [저장]을 눌러주세요.' })\n } catch (err) {\n console.warn('[정보 연동] 실패', err)\n notify({ message: '외부 시스템 연동이 일시적으로 안 됩니다.' })\n } finally {\n this.collectingBasicInfo = false\n }\n }\n\n /**\n * 한글 주용도명 → SiteType enum.\n * - NFC normalize + 공백 제거로 보이지 않는 문자/표기 차이 대응\n * - 매핑 미존재시 null\n */\n private _siteTypeFromKorean(korean: string | null | undefined): string | null {\n if (!korean) return null\n const KEYS: Record<string, string> = {\n 공동주택: 'APARTMENT_COMPLEX',\n 단독주택: 'DETACHED_HOUSE',\n 제1종근린생활시설: 'NEIGHBORHOOD_FACILITY',\n 제2종근린생활시설: 'NEIGHBORHOOD_FACILITY',\n 근린생활시설: 'NEIGHBORHOOD_FACILITY',\n 업무시설: 'OFFICE',\n 판매시설: 'COMMERCIAL',\n 교육연구시설: 'EDUCATION',\n 의료시설: 'MEDICAL',\n 문화및집회시설: 'CULTURAL',\n 종교시설: 'RELIGIOUS',\n 공장: 'INDUSTRIAL',\n 창고시설: 'INDUSTRIAL',\n 운수시설: 'TRANSPORT'\n }\n const key = korean.normalize('NFC').replace(/\\s+/g, '').trim()\n return KEYS[key] || null\n }\n\n /**\n * 시나리오 result data 를 폼에 매핑 — BuildingComplex 와 Project 양쪽.\n * 빈 값(null/undefined/'')은 기존 값 유지 — 자동 수집이 사용자 입력을 무리하게 덮지 않도록.\n */\n private _applyBasicInfoToForm(data: Record<string, any>) {\n const bc = { ...this.project.buildingComplex }\n const setBC = (key: string, value: any) => {\n if (value === null || value === undefined || value === '') return\n ;(bc as any)[key] = value\n }\n setBC('area', this._toNum(data.area))\n setBC('floorAreaRatio', this._toNum(data.floorAreaRatio))\n setBC('coverageRatio', this._toNum(data.coverageRatio))\n setBC('householdCount', this._toNum(data.householdCount))\n setBC('buildingCount', this._toNum(data.buildingCount))\n setBC('structureType', data.structureType)\n // siteType: 한글 → SiteType enum 매핑. 매핑 안 되면 BC.siteType 빈 채로.\n if (data.siteType) {\n const enumVal = this._siteTypeFromKorean(data.siteType)\n if (enumVal) (bc as any).siteType = enumVal\n }\n\n // Project 엔티티 컬럼 — 착공일/준공일.\n // 세움터 useAprDay (사용승인일) = 준공으로 매핑. stcnsDay = 착공.\n const project: any = { ...this.project, buildingComplex: bc }\n if (data.startDate) project.startDate = data.startDate\n if (data.endDate) project.endDate = data.endDate\n this.project = project\n\n // 참고 메타 — BC/Project 직접 컬럼 없음, 콘솔로만 (permitDate=건축허가일, bldNm 등)\n console.log(\n '[정보 연동] meta:',\n JSON.stringify({\n upperFloorCount: data.upperFloorCount,\n lowerFloorCount: data.lowerFloorCount,\n bldNm: data.bldNm,\n permitDate: data.permitDate\n })\n )\n }\n\n private _toNum(v: any): number | null {\n if (v === null || v === undefined || v === '') return null\n const n = Number(v)\n return Number.isFinite(n) ? n : null\n }\n\n private async _promoteTenant() {\n if (!this.project.id) return\n\n if (!confirm('이 프로젝트를 테넌트로 승격하시겠습니까?\\n승격 시 관리번호(YYYY-NNNNN)가 자동 발번되며 변경할 수 없습니다.')) {\n return\n }\n\n const response = await client.mutate({\n mutation: gql`\n mutation PromoteProjectToTenant($projectId: String!) {\n promoteProjectToTenant(projectId: $projectId) {\n id\n code\n }\n }\n `,\n variables: { projectId: this.project.id }\n })\n\n if (!response.errors) {\n notify({ message: `테넌트로 승격되었습니다. 관리번호: ${response.data.promoteProjectToTenant.code}` })\n await this.initProject(this.projectId)\n }\n }\n\n private async _demoteTenant() {\n if (!this.project.id) return\n\n if (!confirm('이 프로젝트의 테넌트를 강등하시겠습니까?\\n관리번호는 보존되며 재승격 시 같은 번호가 재사용됩니다.')) {\n return\n }\n\n const response = await client.mutate({\n mutation: gql`\n mutation DemoteProjectTenant($projectId: String!) {\n demoteProjectTenant(projectId: $projectId)\n }\n `,\n variables: { projectId: this.project.id }\n })\n\n if (!response.errors) {\n notify({ message: '테넌트가 강등되었습니다.' })\n await this.initProject(this.projectId)\n }\n }\n\n // 동 적용 버튼을 누르면 입력한 수 만큼 해당 단지에 동 데이터 생성\n private _setBuilding() {\n const buildingCount: number = this.project?.buildingComplex?.buildingCount || 0\n const buildingInitData = { name: undefined, floorCount: undefined }\n\n // 빌딩 데이터가 없으면 빈 배열 넣어줌\n if (!this.project?.buildingComplex?.buildings?.length) {\n this.project.buildingComplex.buildings = []\n }\n\n if (this.project.buildingComplex.buildings.length >= buildingCount) {\n // 동 수가 더 작게 들어오면 기존 배열을 필요한 크기만큼 잘라내기\n this.project.buildingComplex.buildings = [...this.project.buildingComplex.buildings!.slice(0, buildingCount)]\n } else {\n // 동수가 더 크게 들어오면 기존 배열 + 빈 값을 채움\n const additionalCount = buildingCount - this.project.buildingComplex.buildings.length\n const additionalBuildings = Array.from({ length: additionalCount }, () => ({ ...buildingInitData }))\n this.project.buildingComplex.buildings = [...this.project.buildingComplex.buildings, ...additionalBuildings]\n }\n\n // 리렌더링\n this.project = { ...this.project }\n }\n\n // Input 요소의 값이 변경될 때 호출되는 콜백 함수\n /**\n * 우편번호 입력 핸들러 — postalCode 갱신과 동시에 앞 2자리 prefix 로 geoGroup\n * best-guess 자동 매핑. 매핑 안 되면 기존 값 유지.\n */\n private _onPostalCodeChange(event: InputEvent) {\n const target = event.target as HTMLInputElement\n const value = target.value\n ;(this.project as any).postalCode = value\n const inferred = inferGeoGroupFromPostalCode(value)\n if (inferred) (this.project as any).geoGroup = inferred\n }\n\n private _onInputChange(event: InputEvent, idx: number) {\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 if (target.hasAttribute('project')) {\n this.project[target.name] = inputVal\n\n // documentNaming 필드가 변경되면 실시간으로 미리보기 업데이트\n if (target.name === 'documentNaming') {\n this.requestUpdate()\n }\n } else if (target.hasAttribute('building-complex')) {\n this.project.buildingComplex![target.name] = inputVal\n } else if (target.hasAttribute('building')) {\n this.project.buildingComplex.buildings![idx][target.name] = inputVal\n }\n }\n\n // 이미지 업로드\n async onCreateAttachment(e: CustomEvent) {\n const target = e.target as HTMLInputElement\n const file = (target.name === 'mainPhoto' ? e.detail : e.detail[0]) || null\n\n if (target.name === 'mainPhoto') {\n this.project.mainPhotoUpload = file\n } else {\n this.project.buildingComplex.drawingUpload = file\n }\n }\n\n private _handleSelectionChange(e) {\n const name = e.target.getAttribute('name')\n const selectedValues = e.detail.selectedValues\n\n this.project.buildingComplex[name] = selectedValues\n }\n\n private _toggleBasement(idx: number) {\n const building = this.project.buildingComplex.buildings![idx]\n building.hasBasement = !building.hasBasement\n\n // 층 수가 0이거나 체크 해제시 지하층수 초기화 (플레이스 홀더 나오게 하기 위함)\n if (building.basementFloorCount == 0 || !building.hasBasement) {\n building.basementFloorCount = undefined\n }\n\n this.project.buildingComplex.buildings![idx]['hasBasement'] = building.hasBasement\n this.requestUpdate()\n }\n}\n"]}
1
+ {"version":3,"file":"sv-project-update.js","sourceRoot":"","sources":["../../client/pages/sv-project-update.ts"],"names":[],"mappings":";AAAA,OAAO,4BAA4B,CAAA;AACnC,OAAO,yCAAyC,CAAA;AAChD,OAAO,gDAAgD,CAAA;AAEvD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAEzC,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,CAAA;AAC/B,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AACxD,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAC9D,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AACzC,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AACxC,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AACzC,OAAO,GAAG,MAAM,aAAa,CAAA;AAC7B,OAAO,EAAW,QAAQ,EAAqB,MAAM,sDAAsD,CAAA;AAC3G,OAAO,uDAAuD,CAAA;AAC9D,OAAO,mCAAmC,CAAA;AAC1C,OAAO,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,iDAAiD,CAAA;AACpG,OAAO,EAAE,2BAA2B,EAAE,MAAM,6BAA6B,CAAA;AACzE,OAAO,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAA;AAG1D,IAAM,eAAe,GAArB,MAAM,eAAgB,SAAQ,mBAAmB,CAAC,QAAQ,CAAC;IAA3D;;QA+SG,mBAAc,GAAG;YACvB,IAAI,EAAE,EAAE;YACR,cAAc,EAAE,EAAE;YAClB,eAAe,EAAE;gBACf,OAAO,EAAE,EAAE;gBACX,IAAI,EAAE,CAAC;gBACP,mBAAmB,EAAE,EAAE;gBACvB,aAAa,EAAE,EAAE;gBACjB,kBAAkB,EAAE,EAAE;gBACtB,aAAa,EAAE,EAAE;gBACjB,gBAAgB,EAAE,EAAE;gBACpB,SAAS,EAAE,EAAE;aACd;SACF,CAAA;QACQ,cAAS,GAAW,EAAE,CAAA;QACtB,YAAO,qBAAiB,IAAI,CAAC,cAAc,EAAE;QAC7C,mBAAc,GAAa,EAAE,CAAA;QAC7B,2BAAsB,GAA2C,EAAE,CAAA;QACnE,wBAAmB,GAA2C,EAAE,CAAA;QAChE,wBAAmB,GAA2C,EAAE,CAAA;QAChE,2BAAsB,GAA2C,EAAE,CAAA;QACnE,wBAAmB,GAAY,KAAK,CAAA;QAqf7C;;;WAGG;QACK,kBAAa,GAAG,KAAK,IAAI,EAAE;;YACjC,IAAI,CAAC,CAAA,MAAA,IAAI,CAAC,OAAO,0CAAE,EAAE,CAAA;gBAAE,OAAM;YAC7B,MAAM,EAAE,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC;gBAC7B,KAAK,EAAE,UAAU;gBACjB,aAAa,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE;gBAC7B,YAAY,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE;aAC7B,CAAC,CAAA;YACF,IAAI,CAAC,EAAE;gBAAE,OAAM;YAEf,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC;gBACnC,QAAQ,EAAE,GAAG,CAAA;;;;;;;OAOZ;gBACD,SAAS,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE;gBACjE,OAAO,EAAE,EAAE,OAAO,EAAE,aAAa,CAAC,MAAC,IAAI,CAAC,OAAe,0CAAE,IAAI,CAAC,EAAE;aACjE,CAAC,CAAA;YAEF,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;gBACrB,MAAM,CAAC,EAAE,OAAO,EAAE,kBAAkB,EAAE,CAAC,CAAA;gBACvC,MAAM,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;YACzC,CAAC;QACH,CAAC,CAAA;IA4QH,CAAC;IA1zBC,IAAI,OAAO;;QACT,OAAO;YACL,KAAK,EAAE,CAAC,mBAAmB,EAAE,KAAI,MAAA,IAAI,CAAC,OAAO,0CAAE,IAAI,CAAA,CAAC,CAAC,CAAC,gBAAgB,MAAA,IAAI,CAAC,OAAO,0CAAE,IAAI,EAAE,CAAC,CAAC,CAAC,YAAY;SAC1G,CAAA;IACH,CAAC;IAyBD,MAAM;;QACJ,OAAO,IAAI,CAAA;;qBAEM,IAAI,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE;wBACjB,IAAI,CAAC,OAAe,CAAC,KAAK,IAAI,EAAE;;wBAEjC,IAAI,CAAC,YAAY;sBACnB,IAAI,CAAC,aAAa;;;;;;;;;;;;;yBAaf,IAAI,CAAC,OAAO,CAAC,IAAI,IAAI,EAAE;yBACvB,IAAI,CAAC,cAAc;;;;;;;;;;;yBAWnB,CAAA,MAAA,MAAA,IAAI,CAAC,OAAO,0CAAE,eAAe,0CAAE,OAAO,KAAI,EAAE;yBAC5C,IAAI,CAAC,cAAc;;;;4BAIhB,IAAI,CAAC,mBAAmB;yBAC3B,GAAG,EAAE,CAAC,IAAI,CAAC,iBAAiB,EAAE;;;kBAGrC,IAAI,CAAC,mBAAmB,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU;;;;;;;;;;;yBAWvC,IAAI,CAAC,OAAe,CAAC,UAAU,IAAI,EAAE;yBACtC,IAAI,CAAC,mBAAmB;;;;;;;;;;;;yBAYxB,CAAA,MAAA,MAAA,MAAA,IAAI,CAAC,OAAO,0CAAE,eAAe,0CAAE,IAAI,0CAAE,QAAQ,EAAE,KAAI,EAAE;yBACrD,IAAI,CAAC,cAAc;;;;;;;;;;;;;;yBAcnB,CAAA,MAAA,MAAA,MAAA,IAAI,CAAC,OAAO,0CAAE,eAAe,0CAAE,cAAc,0CAAE,QAAQ,EAAE,KAAI,EAAE;yBAC/D,IAAI,CAAC,cAAc;;;;;;;;;;;;;yBAanB,IAAI,CAAC,OAAO,CAAC,SAAS,IAAI,EAAE;yBAC5B,IAAI,CAAC,cAAc;;;;;;;;yBAQnB,IAAI,CAAC,OAAO,CAAC,OAAO,IAAI,EAAE;yBAC1B,IAAI,CAAC,cAAc;;;;;;;;;qEASyB,IAAI,CAAC,cAAc;8CAC1C,CAAE,IAAI,CAAC,OAAe,CAAC,UAAU;;;8CAGhC,IAAI,CAAC,OAAe,CAAC,UAAU,KAAK,QAAQ;;;8CAG5C,IAAI,CAAC,OAAe,CAAC,UAAU,KAAK,SAAS;;;;;;;;;wEASpB,IAAI,CAAC,cAAc;8CAC7C,CAAE,IAAI,CAAC,OAAe,CAAC,aAAa;;;8CAGnC,IAAI,CAAC,OAAe,CAAC,aAAa,KAAK,aAAa;;;8CAGpD,IAAI,CAAC,OAAe,CAAC,aAAa,KAAK,iBAAiB;;;;;;;;;;;;;;yBAc9E,CAAA,MAAA,MAAA,IAAI,CAAC,OAAO,0CAAE,eAAe,0CAAE,aAAa,KAAI,EAAE;yBAClD,IAAI,CAAC,cAAc;;;;;;;;;;;yBAWnB,CAAA,MAAA,MAAA,IAAI,CAAC,OAAO,0CAAE,eAAe,0CAAE,mBAAmB,KAAI,EAAE;yBACxD,IAAI,CAAC,cAAc;;;;;;;;;;;yBAWnB,CAAA,MAAA,MAAA,IAAI,CAAC,OAAO,0CAAE,eAAe,0CAAE,aAAa,KAAI,EAAE;yBAClD,IAAI,CAAC,cAAc;;;;;;;;;;;yBAWnB,CAAA,MAAA,MAAA,IAAI,CAAC,OAAO,0CAAE,eAAe,0CAAE,kBAAkB,KAAI,EAAE;yBACvD,IAAI,CAAC,cAAc;;;;;;;;;;wBAUpB,CAAA,MAAA,MAAA,IAAI,CAAC,OAAO,0CAAE,SAAS,0CAAE,QAAQ,KAAI,EAAE;0BACrC,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC;;;;;;;;;;;;yBAYnC,CAAA,MAAA,MAAA,MAAA,IAAI,CAAC,OAAO,0CAAE,eAAe,0CAAE,gBAAgB,0CAAE,QAAQ,EAAE,KAAI,EAAE;yBACjE,IAAI,CAAC,cAAc;;;;;;;;;;;;;yBAanB,CAAA,MAAA,MAAA,MAAA,IAAI,CAAC,OAAO,0CAAE,eAAe,0CAAE,WAAW,0CAAE,QAAQ,EAAE,KAAI,EAAE;yBAC5D,IAAI,CAAC,cAAc;;;;;;;;;;;;;kBAc1B,IAAI,CAAC,OAAO,CAAC,YAAY;YACvB,CAAC,CAAC,IAAI,CAAA,WAAW,YAAY,IAAI,CAAC,OAAO,CAAC,IAAI,GAAG;2BAC1C,IAAI,CAAC,OAAO,CAAC,IAAI;wBACpB;YACJ,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI;gBACjB,CAAC,CAAC,IAAI,CAAA,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE;gBAC5B,CAAC,CAAC,IAAI,CAAA,kDACZ;;;;;;kBAOE,IAAI,CAAC,OAAO,CAAC,YAAY;YACvB,CAAC,CAAC,IAAI,CAAA,qCAAqC;YAC3C,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI;gBACjB,CAAC,CAAC,IAAI,CAAA,2CAA2C,IAAI,CAAC,OAAO,CAAC,IAAI,OAAO;gBACzE,CAAC,CAAC,IAAI,CAAA,+BACZ;;;;;;kBAOE,IAAI,CAAC,OAAO,CAAC,YAAY;YACvB,CAAC,CAAC,IAAI,CAAA,8BAA8B,IAAI,CAAC,aAAa,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE;;4CAE5D;YACxB,CAAC,CAAC,IAAI,CAAA,oCAAoC,IAAI,CAAC,cAAc,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE;;4CAG/F;;;;;;KAMX,CAAA;IACH,CAAC;IAED,KAAK,CAAC,eAAe,CAAC,SAAwB,IAAG,CAAC;IAElD,KAAK,CAAC,WAAW,CAAC,OAAY,EAAE,SAAwB;QACtD,IAAI,CAAC,IAAI,CAAC,MAAM;YAAE,OAAM;QAExB,IAAI,CAAC,SAAS,GAAG,MAAM,IAAI,CAAC,iBAAiB,CAAC,SAAS,CAAC,UAAU,IAAI,EAAE,CAAC,CAAA;QACzE,MAAM,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;IACxC,CAAC;IAED;;;;OAIG;IACK,KAAK,CAAC,iBAAiB,CAAC,UAAkB;;QAChD,IAAI,UAAU,IAAI,UAAU,CAAC,MAAM,KAAK,EAAE;YAAE,OAAO,UAAU,CAAA;QAE7D,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC;YAClC,KAAK,EAAE,GAAG,CAAA;;;;;;OAMT;SACF,CAAC,CAAA;QAEF,OAAO,CAAA,MAAA,MAAA,QAAQ,CAAC,IAAI,0CAAE,cAAc,0CAAE,EAAE,KAAI,EAAE,CAAA;IAChD,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,YAAoB,EAAE;;QACtC,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC;YAClC,KAAK,EAAE,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAsFT;YACD,SAAS,EAAE;gBACT,EAAE,EAAE,SAAS;gBACb,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,QAAQ;wBACd,QAAQ,EAAE,IAAI;wBACd,KAAK,EAAE,IAAI;qBACZ;oBACD;wBACE,IAAI,EAAE,QAAQ;wBACd,QAAQ,EAAE,aAAa;wBACvB,KAAK,EAAE,EAAE;qBACV;iBACF;aACF;YACD,OAAO,EAAE,EAAE,OAAO,EAAE,aAAa,CAAC,MAAC,IAAI,CAAC,OAAe,0CAAE,IAAI,CAAC,EAAE;SACjE,CAAC,CAAA;QAEF,IAAI,CAAC,OAAO,GAAG,CAAA,MAAA,QAAQ,CAAC,IAAI,0CAAE,OAAO,uBAAS,IAAI,CAAC,cAAc,CAAE,CAAA;QAEnE,MAAM,KAAK,GAAG,CAAA,MAAA,MAAA,QAAQ,CAAC,IAAI,0CAAE,SAAS,0CAAE,KAAK,KAAI,EAAE,CAAA;QACnD,IAAI,CAAC,sBAAsB,GAAG,IAAI,CAAC,uBAAuB,CAAC,KAAK,EAAE,qBAAqB,CAAC,CAAA;QACxF,IAAI,CAAC,mBAAmB,GAAG,IAAI,CAAC,uBAAuB,CAAC,KAAK,EAAE,kBAAkB,CAAC,CAAA;QAClF,IAAI,CAAC,sBAAsB,GAAG,IAAI,CAAC,uBAAuB,CAAC,KAAK,EAAE,qBAAqB,CAAC,CAAA;QACxF,IAAI,CAAC,mBAAmB,GAAG,IAAI,CAAC,uBAAuB,CAAC,KAAK,EAAE,kBAAkB,CAAC,CAAA;QAElF,IAAI,CAAC,aAAa,EAAE,CAAA;IACtB,CAAC;IAEO,uBAAuB,CAAC,QAAQ,EAAE,UAAkB;QAC1D,OAAO,QAAQ;aACZ,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,iBAAiB,IAAI,UAAU,IAAI,CAAC,CAAC,iBAAiB,IAAI,OAAO,CAAC;aAChF,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAA;IACtD,CAAC;IAEO,cAAc,CAAC,GAAQ;QAC7B,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;YACvB,OAAO,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAA;QACnD,CAAC;QACD,IAAI,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,CAAC,CAAC,GAAG,YAAY,IAAI,CAAC,EAAE,CAAC;YAC7D,MAAM,OAAO,GAAQ,EAAE,CAAA;YACvB,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC/C,IAAI,GAAG,KAAK,YAAY;oBAAE,SAAQ;gBAClC,OAAO,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,CAAA;YAC3C,CAAC;YACD,OAAO,OAAO,CAAA;QAChB,CAAC;QACD,OAAO,GAAG,CAAA;IACZ,CAAC;IAEO,KAAK,CAAC,YAAY;;QACxB,6CAA6C;QAC7C,OAAO,IAAI,CAAC,OAAO,CAAC,SAAS,CAAA;QAC7B,OAAO,IAAI,CAAC,OAAO,CAAC,eAAe,CAAC,OAAO,CAAA;QAC3C,qDAAqD;QACrD,MAAM,WAAW,GAAI,IAAI,CAAC,OAAe,CAAC,IAAI,CAAA,CAAC,mBAAmB;QAClE,OAAQ,IAAI,CAAC,OAAe,CAAC,IAAI,CAAA;QACjC,OAAQ,IAAI,CAAC,OAAe,CAAC,YAAY,CAAA;QACzC,iEAAiE;QACjE,sEAAsE;QACtE,OAAQ,IAAI,CAAC,OAAe,CAAC,KAAK,CAAA;QAElC,uEAAuE;QACvE,gDAAgD;QAChD,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAA;QACvD,IAAI,CAAA,MAAA,IAAI,CAAC,OAAO,CAAC,eAAe,0CAAE,QAAQ,KAAI,CAAC,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,eAAe,CAAC,QAAe,CAAC,EAAE,CAAC;YAChH,IAAI,CAAC,OAAO,CAAC,eAAe,CAAC,QAAQ,GAAG,IAAW,CAAA;QACrD,CAAC;QAED,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC;YACnC,QAAQ,EAAE,GAAG,CAAA;;;;;;OAMZ;YACD,SAAS,EAAE;gBACT,OAAO,EAAE,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC;aAC3C;YACD,OAAO,EAAE;gBACP,SAAS,EAAE,IAAI;gBACf,OAAO,EAAE,aAAa,CAAC,WAAW,CAAC;aACpC;SACF,CAAC,CAAA;QAEF,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;YACrB,+DAA+D;YAC/D,oEAAoE;YACpE,sCAAsC;YACtC,IAAI,CAAC;gBACH,MAAM,oBAAoB,CAAC,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC,CAAA;YACvD,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,OAAO,CAAC,IAAI,CAAC,mCAAmC,EAAE,CAAC,CAAC,CAAA;YACtD,CAAC;YACD,MAAM,CAAC,EAAE,OAAO,EAAE,cAAc,EAAE,CAAC,CAAA;QACrC,CAAC;IACH,CAAC;IAkCD;;;;;;;;;;OAUG;IACK,KAAK,CAAC,iBAAiB;;QAC7B,MAAM,OAAO,GAAG,CAAC,CAAA,MAAA,MAAA,IAAI,CAAC,OAAO,0CAAE,eAAe,0CAAE,OAAO,KAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;QACrE,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,CAAC,EAAE,OAAO,EAAE,sBAAsB,EAAE,CAAC,CAAA;YAC3C,OAAM;QACR,CAAC;QACD,IAAI,CAAC,mBAAmB,GAAG,IAAI,CAAA;QAC/B,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC;gBACnC,QAAQ,EAAE,GAAG,CAAA;;;;;;;;;;SAUZ;gBACD,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE,OAAO,EAAE;gBAClD,OAAO,EAAE,EAAE,OAAO,EAAE,aAAa,CAAC,MAAC,IAAI,CAAC,OAAe,0CAAE,IAAI,CAAC,EAAE;aACjE,CAAC,CAAA;YACF,IAAI,QAAQ,CAAC,MAAM,EAAE,CAAC;gBACpB,MAAM,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAA;YACxE,CAAC;YACD,MAAM,MAAM,GAAG,MAAA,QAAQ,CAAC,IAAI,0CAAE,uBAAuB,CAAA;YACrD,IAAI,CAAC,CAAA,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,EAAE,CAAA,EAAE,CAAC;gBAChB,MAAM,CAAC,EAAE,OAAO,EAAE,CAAA,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAE,OAAO,KAAI,iBAAiB,EAAE,CAAC,CAAA;gBACzD,OAAM;YACR,CAAC;YACD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,IAAI,EAAE,CAAA;YAC9B,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,CAAA;YAChC,IAAI,CAAC,aAAa,EAAE,CAAA;YACpB,MAAM,CAAC,EAAE,OAAO,EAAE,2CAA2C,EAAE,CAAC,CAAA;QAClE,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,IAAI,CAAC,YAAY,EAAE,GAAG,CAAC,CAAA;YAC/B,MAAM,CAAC,EAAE,OAAO,EAAE,yBAAyB,EAAE,CAAC,CAAA;QAChD,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,mBAAmB,GAAG,KAAK,CAAA;QAClC,CAAC;IACH,CAAC;IAED;;;;OAIG;IACK,mBAAmB,CAAC,MAAiC;QAC3D,IAAI,CAAC,MAAM;YAAE,OAAO,IAAI,CAAA;QACxB,MAAM,IAAI,GAA2B;YACnC,IAAI,EAAE,mBAAmB;YACzB,IAAI,EAAE,gBAAgB;YACtB,SAAS,EAAE,uBAAuB;YAClC,SAAS,EAAE,uBAAuB;YAClC,MAAM,EAAE,uBAAuB;YAC/B,IAAI,EAAE,QAAQ;YACd,IAAI,EAAE,YAAY;YAClB,MAAM,EAAE,WAAW;YACnB,IAAI,EAAE,SAAS;YACf,OAAO,EAAE,UAAU;YACnB,IAAI,EAAE,WAAW;YACjB,EAAE,EAAE,YAAY;YAChB,IAAI,EAAE,YAAY;YAClB,IAAI,EAAE,WAAW;SAClB,CAAA;QACD,MAAM,GAAG,GAAG,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAA;QAC9D,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,IAAI,CAAA;IAC1B,CAAC;IAED;;;OAGG;IACK,qBAAqB,CAAC,IAAyB;QACrD,MAAM,EAAE,qBAAQ,IAAI,CAAC,OAAO,CAAC,eAAe,CAAE,CAAA;QAC9C,MAAM,KAAK,GAAG,CAAC,GAAW,EAAE,KAAU,EAAE,EAAE;YACxC,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,EAAE;gBAAE,OAC1D;YAAC,EAAU,CAAC,GAAG,CAAC,GAAG,KAAK,CAAA;QAC3B,CAAC,CAAA;QACD,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAA;QACrC,KAAK,CAAC,gBAAgB,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,CAAA;QACzD,KAAK,CAAC,eAAe,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAA;QACvD,KAAK,CAAC,gBAAgB,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,CAAA;QACzD,KAAK,CAAC,eAAe,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAA;QACvD,KAAK,CAAC,eAAe,EAAE,IAAI,CAAC,aAAa,CAAC,CAAA;QAC1C,6DAA6D;QAC7D,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClB,MAAM,OAAO,GAAG,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;YACvD,IAAI,OAAO;gBAAG,EAAU,CAAC,QAAQ,GAAG,OAAO,CAAA;QAC7C,CAAC;QAED,4BAA4B;QAC5B,kDAAkD;QAClD,MAAM,OAAO,mCAAa,IAAI,CAAC,OAAO,KAAE,eAAe,EAAE,EAAE,GAAE,CAAA;QAC7D,IAAI,IAAI,CAAC,SAAS;YAAE,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,CAAA;QACtD,IAAI,IAAI,CAAC,OAAO;YAAE,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAA;QAChD,IAAI,CAAC,OAAO,GAAG,OAAO,CAAA;QAEtB,gEAAgE;QAChE,OAAO,CAAC,GAAG,CACT,eAAe,EACf,IAAI,CAAC,SAAS,CAAC;YACb,eAAe,EAAE,IAAI,CAAC,eAAe;YACrC,eAAe,EAAE,IAAI,CAAC,eAAe;YACrC,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,UAAU,EAAE,IAAI,CAAC,UAAU;SAC5B,CAAC,CACH,CAAA;IACH,CAAC;IAEO,MAAM,CAAC,CAAM;QACnB,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK,EAAE;YAAE,OAAO,IAAI,CAAA;QAC1D,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAA;QACnB,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;IACtC,CAAC;IAEO,KAAK,CAAC,cAAc;QAC1B,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE;YAAE,OAAM;QAE5B,IAAI,CAAC,OAAO,CAAC,oEAAoE,CAAC,EAAE,CAAC;YACnF,OAAM;QACR,CAAC;QAED,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC;YACnC,QAAQ,EAAE,GAAG,CAAA;;;;;;;OAOZ;YACD,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE;SAC1C,CAAC,CAAA;QAEF,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;YACrB,MAAM,CAAC,EAAE,OAAO,EAAE,uBAAuB,QAAQ,CAAC,IAAI,CAAC,sBAAsB,CAAC,IAAI,EAAE,EAAE,CAAC,CAAA;YACvF,MAAM,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;QACxC,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,aAAa;QACzB,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE;YAAE,OAAM;QAE5B,IAAI,CAAC,OAAO,CAAC,yDAAyD,CAAC,EAAE,CAAC;YACxE,OAAM;QACR,CAAC;QAED,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC;YACnC,QAAQ,EAAE,GAAG,CAAA;;;;OAIZ;YACD,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE;SAC1C,CAAC,CAAA;QAEF,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;YACrB,MAAM,CAAC,EAAE,OAAO,EAAE,eAAe,EAAE,CAAC,CAAA;YACpC,MAAM,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;QACxC,CAAC;IACH,CAAC;IAED,wCAAwC;IAChC,YAAY;;QAClB,MAAM,aAAa,GAAW,CAAA,MAAA,MAAA,IAAI,CAAC,OAAO,0CAAE,eAAe,0CAAE,aAAa,KAAI,CAAC,CAAA;QAC/E,MAAM,gBAAgB,GAAG,EAAE,IAAI,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,CAAA;QAEnE,uBAAuB;QACvB,IAAI,CAAC,CAAA,MAAA,MAAA,MAAA,IAAI,CAAC,OAAO,0CAAE,eAAe,0CAAE,SAAS,0CAAE,MAAM,CAAA,EAAE,CAAC;YACtD,IAAI,CAAC,OAAO,CAAC,eAAe,CAAC,SAAS,GAAG,EAAE,CAAA;QAC7C,CAAC;QAED,IAAI,IAAI,CAAC,OAAO,CAAC,eAAe,CAAC,SAAS,CAAC,MAAM,IAAI,aAAa,EAAE,CAAC;YACnE,sCAAsC;YACtC,IAAI,CAAC,OAAO,CAAC,eAAe,CAAC,SAAS,GAAG,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,eAAe,CAAC,SAAU,CAAC,KAAK,CAAC,CAAC,EAAE,aAAa,CAAC,CAAC,CAAA;QAC/G,CAAC;aAAM,CAAC;YACN,gCAAgC;YAChC,MAAM,eAAe,GAAG,aAAa,GAAG,IAAI,CAAC,OAAO,CAAC,eAAe,CAAC,SAAS,CAAC,MAAM,CAAA;YACrF,MAAM,mBAAmB,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,eAAe,EAAE,EAAE,GAAG,EAAE,CAAC,mBAAM,gBAAgB,EAAG,CAAC,CAAA;YACpG,IAAI,CAAC,OAAO,CAAC,eAAe,CAAC,SAAS,GAAG,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,eAAe,CAAC,SAAS,EAAE,GAAG,mBAAmB,CAAC,CAAA;QAC9G,CAAC;QAED,OAAO;QACP,IAAI,CAAC,OAAO,qBAAQ,IAAI,CAAC,OAAO,CAAE,CAAA;IACpC,CAAC;IAED,gCAAgC;IAChC;;;OAGG;IACK,mBAAmB,CAAC,KAAiB;QAC3C,MAAM,MAAM,GAAG,KAAK,CAAC,MAA0B,CAAA;QAC/C,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CACzB;QAAC,IAAI,CAAC,OAAe,CAAC,UAAU,GAAG,KAAK,CAAA;QACzC,MAAM,QAAQ,GAAG,2BAA2B,CAAC,KAAK,CAAC,CAAA;QACnD,IAAI,QAAQ;YAAG,IAAI,CAAC,OAAe,CAAC,QAAQ,GAAG,QAAQ,CAAA;IACzD,CAAC;IAEO,cAAc,CAAC,KAAiB,EAAE,GAAW;QACnD,MAAM,MAAM,GAAG,KAAK,CAAC,MAA0B,CAAA;QAC/C,IAAI,QAAQ,GAAQ,MAAM,CAAC,KAAK,CAAA;QAEhC,qBAAqB;QACrB,IAAI,MAAM,CAAC,YAAY,CAAC,SAAS,CAAC,EAAE,CAAC;YACnC,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,CAAA;QACpD,CAAC;QAED,IAAI,MAAM,CAAC,YAAY,CAAC,SAAS,CAAC,EAAE,CAAC;YACnC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAA;YAEpC,0CAA0C;YAC1C,IAAI,MAAM,CAAC,IAAI,KAAK,gBAAgB,EAAE,CAAC;gBACrC,IAAI,CAAC,aAAa,EAAE,CAAA;YACtB,CAAC;QACH,CAAC;aAAM,IAAI,MAAM,CAAC,YAAY,CAAC,kBAAkB,CAAC,EAAE,CAAC;YACnD,IAAI,CAAC,OAAO,CAAC,eAAgB,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAA;QACvD,CAAC;aAAM,IAAI,MAAM,CAAC,YAAY,CAAC,UAAU,CAAC,EAAE,CAAC;YAC3C,IAAI,CAAC,OAAO,CAAC,eAAe,CAAC,SAAU,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAA;QACtE,CAAC;IACH,CAAC;IAED,UAAU;IACV,KAAK,CAAC,kBAAkB,CAAC,CAAc;QACrC,MAAM,MAAM,GAAG,CAAC,CAAC,MAA0B,CAAA;QAC3C,MAAM,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,KAAK,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAA;QAE3E,IAAI,MAAM,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;YAChC,IAAI,CAAC,OAAO,CAAC,eAAe,GAAG,IAAI,CAAA;QACrC,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,OAAO,CAAC,eAAe,CAAC,aAAa,GAAG,IAAI,CAAA;QACnD,CAAC;IACH,CAAC;IAEO,sBAAsB,CAAC,CAAC;QAC9B,MAAM,IAAI,GAAG,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,CAAA;QAC1C,MAAM,cAAc,GAAG,CAAC,CAAC,MAAM,CAAC,cAAc,CAAA;QAE9C,IAAI,CAAC,OAAO,CAAC,eAAe,CAAC,IAAI,CAAC,GAAG,cAAc,CAAA;IACrD,CAAC;IAEO,eAAe,CAAC,GAAW;QACjC,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,eAAe,CAAC,SAAU,CAAC,GAAG,CAAC,CAAA;QAC7D,QAAQ,CAAC,WAAW,GAAG,CAAC,QAAQ,CAAC,WAAW,CAAA;QAE5C,gDAAgD;QAChD,IAAI,QAAQ,CAAC,kBAAkB,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC;YAC9D,QAAQ,CAAC,kBAAkB,GAAG,SAAS,CAAA;QACzC,CAAC;QAED,IAAI,CAAC,OAAO,CAAC,eAAe,CAAC,SAAU,CAAC,GAAG,CAAC,CAAC,aAAa,CAAC,GAAG,QAAQ,CAAC,WAAW,CAAA;QAClF,IAAI,CAAC,aAAa,EAAE,CAAA;IACtB,CAAC;;AAjmCM,sBAAM,GAAG;IACd,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAoSF;CACF,AAtSY,CAsSZ;AAsBQ;IAAR,KAAK,EAAE;;kDAAuB;AACtB;IAAR,KAAK,EAAE;;gDAA8C;AAC7C;IAAR,KAAK,EAAE;;uDAA8B;AAC7B;IAAR,KAAK,EAAE;8BAAyB,KAAK;+DAAsC;AACnE;IAAR,KAAK,EAAE;8BAAsB,KAAK;4DAAsC;AAChE;IAAR,KAAK,EAAE;8BAAsB,KAAK;4DAAsC;AAChE;IAAR,KAAK,EAAE;8BAAyB,KAAK;+DAAsC;AACnE;IAAR,KAAK,EAAE;;4DAAqC;AApUlC,eAAe;IAD3B,aAAa,CAAC,mBAAmB,CAAC;GACtB,eAAe,CAmmC3B","sourcesContent":["import '@material/web/icon/icon.js'\nimport '@material/web/button/elevated-button.js'\nimport '@material/web/textfield/outlined-text-field.js'\n\nimport { PageView } from '@operato/shell'\nimport { PageLifecycle } from '@operato/shell/dist/src/app/pages/page-view'\nimport { css, html } from 'lit'\nimport { customElement, state } from 'lit/decorators.js'\nimport { ScopedElementsMixin } from '@open-wc/scoped-elements'\nimport { client } from '@operato/graphql'\nimport { notify } from '@operato/layout'\nimport { OxPrompt } from '@operato/popup'\nimport gql from 'graphql-tag'\nimport { Project, SiteType, SITE_TYPE_DISPLAY } from '@dssp/project/dist-client/pages/project/project-list'\nimport '@dssp/project/dist-client/pages/lib/select2-component'\nimport './component/project-update-header'\nimport { isProjectTypeDomain, tenantHeaders } from '@dssp/project/dist-client/shared/domain-context'\nimport { inferGeoGroupFromPostalCode } from '../shared/geo-group-mapping'\nimport { syncProjectInfoToKpi } from '../shared/kpi-project-sync'\n\n@customElement('sv-project-update')\nexport class SvProjectUpdate extends ScopedElementsMixin(PageView) {\n static styles = [\n css`\n :host {\n display: grid;\n grid-template-rows: 55px auto;\n color: #4e5055;\n\n width: 100%;\n background-color: var(--md-sys-color-background, #f6f6f6);\n overflow-y: auto;\n\n --grid-record-emphasized-background-color: red;\n --grid-record-emphasized-color: yellow;\n }\n\n md-outlined-text-field {\n width: 100%;\n\n --md-outlined-text-field-container-shape: 5px;\n --md-outlined-text-field-outline-color: rgba(51, 51, 51, 0.2);\n --md-outlined-text-field-focus-outline-color: #1f7fd9;\n --md-outlined-text-field-focus-outline-width: 1px;\n --md-sys-color-primary: #586878;\n --md-outlined-text-field-input-text-size: 14px;\n --md-outlined-field-bottom-space: 3px;\n --md-outlined-field-top-space: 3px;\n --md-outlined-field-leading-space: var(--spacing-medium, 8px);\n --md-outlined-field-trailing-space: var(--spacing-medium, 8px);\n }\n select2-component {\n width: 100%;\n }\n md-filled-select {\n width: 100%;\n --md-filled-select-text-field-container-color: transparent;\n --md-filled-select-text-field-active-indicator-color: #999;\n --md-filled-select-text-field-input-text-size: 14px;\n --md-filled-select-text-field-input-text-line-height: 6px;\n }\n input[type='date'] {\n border: 1px solid rgba(51, 51, 51, 0.2);\n padding: var(--spacing-small, 4px) var(--spacing-medium, 8px);\n border-radius: 5px;\n }\n md-outlined-text-field[type='textarea'] {\n width: 100%;\n height: 120px;\n resize: none;\n }\n\n ox-input-image {\n width: 120px;\n height: 90px;\n }\n ox-input-file {\n width: 120px;\n height: 90px;\n padding: 0;\n }\n\n div[body] {\n display: flex;\n margin: var(--spacing-large, 12px);\n margin-top: 0;\n gap: var(--spacing-medium, 8px);\n\n & > div {\n display: flex;\n height: fit-content;\n flex-direction: column;\n padding: var(--spacing-large, 12px);\n background-color: var(--md-sys-color-on-primary);\n border: 1px solid #cccccc80;\n border-radius: 5px;\n gap: var(--spacing-medium, 8px);\n\n h3 {\n color: #2e79be;\n font-size: 16px;\n margin: 0px;\n }\n\n div[row] {\n display: flex;\n align-items: center;\n\n span:first-child {\n flex: 0.3;\n text-align: right;\n font-size: 14px;\n }\n\n span:last-child {\n flex: 0.7;\n display: flex;\n gap: var(--spacing-medium, 8px);\n margin-left: var(--spacing-medium, 8px);\n align-items: center;\n\n &.address-row {\n md-outlined-text-field {\n flex: 1;\n }\n .info-link-btn {\n flex: 0 0 auto;\n --md-elevated-button-container-color: #2e79be;\n --md-elevated-button-label-text-color: #fff;\n --md-elevated-button-container-height: 36px;\n }\n }\n\n &[align-end] {\n align-items: end;\n }\n }\n }\n }\n\n div[project-info] {\n flex: 0.55;\n }\n\n div[document-preview] {\n width: 100%;\n font-size: 12px;\n color: #2e79be;\n font-weight: 500;\n background-color: #f8f9fa;\n padding: 8px 12px;\n border-radius: 4px;\n border-left: 3px solid #2e79be;\n margin-top: 4px;\n }\n\n div[detail-info] {\n flex: 0.45;\n padding: 0px;\n gap: 10px;\n background-color: transparent;\n border: none;\n\n & > div {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-medium, 8px);\n border: 1px solid #cccccc80;\n border-radius: 5px;\n padding: var(--spacing-large, 12px);\n background-color: var(--md-sys-color-on-primary);\n\n md-outlined-text-field[type='text'] {\n width: 60%;\n }\n\n md-elevated-button {\n --md-elevated-button-container-height: 30px;\n --md-elevated-button-container-color: var(--md-sys-color-on-primary);\n --md-elevated-button-label-text-size: 16px;\n }\n hr {\n border: 1px rgba(51, 51, 51, 0.1) dashed;\n width: 100%;\n margin-bottom: var(--spacing-tiny, 2px);\n }\n div[warn] {\n font-size: 12px;\n color: red;\n margin-left: var(--spacing-small, 4px);\n margin-bottom: var(--spacing-small, 4px);\n }\n\n md-outlined-button {\n min-height: 30px;\n padding: 0 var(--spacing-large, 12px);\n box-shadow: 1px 1px 1px #0000001a;\n --md-outlined-button-label-text-color: #586878;\n --md-outlined-button-label-text-weight: bold;\n --md-sys-color-outline: rgba(51, 51, 51, 0.2);\n }\n\n div[row] {\n span:first-child {\n flex: 0.2;\n }\n span:last-child {\n flex: 0.8;\n }\n }\n\n div[separate-container] {\n display: grid;\n grid-template-columns: repeat(2, 1fr);\n gap: 8px 30px;\n margin-left: 15px;\n\n md-outlined-text-field {\n width: 80px;\n }\n\n & > div {\n display: flex;\n gap: 15px;\n\n span[floor-row] {\n display: inline-flex;\n align-items: center;\n margin-left: 5px;\n }\n\n span[floor-title] {\n min-width: 33px;\n margin-right: var(--spacing-small, 4px);\n }\n span:first-child {\n flex: 0.4;\n justify-content: flex-start;\n display: flex;\n }\n span:last-child {\n flex: 0.6;\n display: flex;\n align-items: center;\n padding-left: var(--spacing-medium, 8px);\n }\n }\n\n div[basement-container] {\n display: flex;\n align-items: center;\n gap: 4px;\n\n div[basement-checkbox] {\n display: flex;\n align-items: center;\n cursor: pointer;\n\n md-icon {\n --md-icon-size: 25px;\n color: #808080;\n transition: opacity 0.2s ease;\n }\n\n &[disabled] {\n cursor: not-allowed;\n opacity: 0.4;\n\n md-icon {\n color: #cccccc;\n }\n }\n\n &[checked] md-icon {\n background-color: #2e79be;\n color: white;\n border-radius: 3px;\n }\n }\n\n md-outlined-text-field[basement-floor] {\n width: 70px;\n margin-left: var(--spacing-small, 4px);\n opacity: 1;\n transition: opacity 0.2s ease;\n\n &[disabled] {\n opacity: 0.4;\n pointer-events: none;\n }\n }\n }\n }\n\n &[project] {\n div[separate-container] {\n margin-left: 15px;\n\n & > div {\n align-items: center;\n\n span:first-child {\n flex: 0.4;\n min-width: 100px;\n }\n span:last-child {\n flex: 0.4;\n margin-left: 0;\n }\n }\n }\n }\n }\n }\n }\n `\n ]\n\n get context() {\n return {\n title: !isProjectTypeDomain() && this.project?.name ? `프로젝트 정보 관리 - ${this.project?.name}` : '프로젝트 정보 관리'\n }\n }\n\n private defaultProject = {\n name: '',\n documentNaming: '',\n buildingComplex: {\n address: '',\n area: 0,\n constructionCompany: '',\n clientCompany: '',\n supervisoryCompany: '',\n designCompany: '',\n constructionType: '',\n buildings: []\n }\n }\n @state() projectId: string = ''\n @state() project: Project = { ...this.defaultProject }\n @state() selectedValues: string[] = []\n @state() overallConstructorList: Array<{ name: string; value: string }> = []\n @state() taskConstructorList: Array<{ name: string; value: string }> = []\n @state() taskSupervisoryList: Array<{ name: string; value: string }> = []\n @state() overallSupervisoryList: Array<{ name: string; value: string }> = []\n @state() collectingBasicInfo: boolean = false\n\n render() {\n return html`\n <dkpi-project-update-header\n .projectId=${this.project.id || ''}\n .projectState=${(this.project as any).state || ''}\n title=\"프로젝트 정보 관리\"\n @custom-click=${this._saveProject}\n @unfinalize=${this._onUnfinalize}\n ></dkpi-project-update-header>\n\n <div body>\n <div project-info>\n <h3>기본 정보</h3>\n <div row>\n <span>프로젝트명</span>\n <span\n ><md-outlined-text-field\n type=\"text\"\n name=\"name\"\n project\n .value=${this.project.name || ''}\n @input=${this._onInputChange}\n ></md-outlined-text-field>\n </span>\n </div>\n <div row>\n <span>프로젝트 주소</span>\n <span class=\"address-row\">\n <md-outlined-text-field\n type=\"text\"\n name=\"address\"\n building-complex\n .value=${this.project?.buildingComplex?.address || ''}\n @input=${this._onInputChange}\n ></md-outlined-text-field>\n <md-elevated-button\n class=\"info-link-btn\"\n ?disabled=${this.collectingBasicInfo}\n @click=${() => this._collectBasicInfo()}\n title=\"입력된 주소로 세움터 건축물대장 정보를 가져와 폼을 채웁니다\"\n >\n ${this.collectingBasicInfo ? '연동 중…' : '🔗 정보 연동'}\n </md-elevated-button>\n </span>\n </div>\n <div row>\n <span>우편번호</span>\n <span>\n <md-outlined-text-field\n type=\"text\"\n name=\"postalCode\"\n project\n .value=${(this.project as any).postalCode || ''}\n @input=${this._onPostalCodeChange}\n ></md-outlined-text-field>\n </span>\n </div>\n <div row>\n <span>연면적</span>\n <span align-end\n ><md-outlined-text-field\n type=\"text\"\n name=\"area\"\n numeric\n building-complex\n .value=${this.project?.buildingComplex?.area?.toString() || ''}\n @input=${this._onInputChange}\n suffix-text=\"㎡\"\n ></md-outlined-text-field>\n </span>\n </div>\n\n <div row>\n <span>용적률</span>\n <span align-end>\n <md-outlined-text-field\n type=\"text\"\n name=\"floorAreaRatio\"\n numeric\n building-complex\n .value=${this.project?.buildingComplex?.floorAreaRatio?.toString() || ''}\n @input=${this._onInputChange}\n suffix-text=\"%\"\n ></md-outlined-text-field>\n </span>\n </div>\n\n <div row>\n <span>착공일정 ~ 준공일정(계획)</span>\n <span\n ><input\n type=\"date\"\n name=\"startDate\"\n project\n .value=${this.project.startDate || ''}\n @input=${this._onInputChange}\n max=\"9999-12-31\"\n />\n ~\n <input\n type=\"date\"\n name=\"endDate\"\n project\n .value=${this.project.endDate || ''}\n @input=${this._onInputChange}\n max=\"9999-12-31\"\n />\n </span>\n </div>\n\n <div row>\n <span>유형</span>\n <span\n ><md-filled-select name=\"sectorType\" project @change=${this._onInputChange}>\n <md-select-option ?selected=${!(this.project as any).sectorType} value=\"\">\n <div slot=\"headline\">선택</div>\n </md-select-option>\n <md-select-option ?selected=${(this.project as any).sectorType === 'PUBLIC'} value=\"PUBLIC\">\n <div slot=\"headline\">공공</div>\n </md-select-option>\n <md-select-option ?selected=${(this.project as any).sectorType === 'PRIVATE'} value=\"PRIVATE\">\n <div slot=\"headline\">민간</div>\n </md-select-option>\n </md-filled-select>\n </span>\n </div>\n <div row>\n <span>용도</span>\n <span\n ><md-filled-select name=\"buildingUsage\" project @change=${this._onInputChange}>\n <md-select-option ?selected=${!(this.project as any).buildingUsage} value=\"\">\n <div slot=\"headline\">선택</div>\n </md-select-option>\n <md-select-option ?selected=${(this.project as any).buildingUsage === 'RESIDENTIAL'} value=\"RESIDENTIAL\">\n <div slot=\"headline\">주거</div>\n </md-select-option>\n <md-select-option ?selected=${(this.project as any).buildingUsage === 'NON_RESIDENTIAL'} value=\"NON_RESIDENTIAL\">\n <div slot=\"headline\">비주거</div>\n </md-select-option>\n </md-filled-select>\n </span>\n </div>\n\n <div row>\n <span>발주처</span>\n <span\n ><md-outlined-text-field\n type=\"text\"\n name=\"clientCompany\"\n building-complex\n .value=${this.project?.buildingComplex?.clientCompany || ''}\n @input=${this._onInputChange}\n ></md-outlined-text-field>\n </span>\n </div>\n <div row>\n <span>건설사</span>\n <span\n ><md-outlined-text-field\n type=\"text\"\n name=\"constructionCompany\"\n building-complex\n .value=${this.project?.buildingComplex?.constructionCompany || ''}\n @input=${this._onInputChange}\n ></md-outlined-text-field>\n </span>\n </div>\n <div row>\n <span>설계사</span>\n <span\n ><md-outlined-text-field\n type=\"text\"\n name=\"designCompany\"\n building-complex\n .value=${this.project?.buildingComplex?.designCompany || ''}\n @input=${this._onInputChange}\n ></md-outlined-text-field>\n </span>\n </div>\n <div row>\n <span>감리사</span>\n <span\n ><md-outlined-text-field\n type=\"text\"\n name=\"supervisoryCompany\"\n building-complex\n .value=${this.project?.buildingComplex?.supervisoryCompany || ''}\n @input=${this._onInputChange}\n ></md-outlined-text-field>\n </span>\n </div>\n\n <div row>\n <span>대표사진 업로드</span>\n <span>\n <ox-input-image\n name=\"mainPhoto\"\n value=${this.project?.mainPhoto?.fullpath || ''}\n @change=${this.onCreateAttachment.bind(this)}\n ></ox-input-image>\n </span>\n </div>\n <div row>\n <span>계획공사비</span>\n <span\n ><md-outlined-text-field\n type=\"text\"\n name=\"constructionCost\"\n numeric\n building-complex\n .value=${this.project?.buildingComplex?.constructionCost?.toString() || ''}\n @input=${this._onInputChange}\n suffix-text=\"억원\"\n ></md-outlined-text-field>\n </span>\n </div>\n <div row>\n <span>총 투입인력(M/M)</span>\n <span\n ><md-outlined-text-field\n type=\"text\"\n name=\"workerCount\"\n numeric\n building-complex\n .value=${this.project?.buildingComplex?.workerCount?.toString() || ''}\n @input=${this._onInputChange}\n suffix-text=\"(M/M)\"\n ></md-outlined-text-field>\n </span>\n </div>\n </div>\n\n <div detail-info>\n <div>\n <h3>테넌트 관리</h3>\n <div row>\n <span>관리번호</span>\n <span>\n ${\n this.project.tenantDomain\n ? html`<a href=${`/project/${this.project.code}/`} target=\"_blank\" title=\"테넌트로 이동\"\n >${this.project.code} <md-icon style=\"font-size:14px;vertical-align:middle\">open_in_new</md-icon></a\n >`\n : this.project.code\n ? html`${this.project.code}`\n : html`<span style=\"color:#999\">미발번 (승격 시 자동 부여)</span>`\n }\n </span>\n </div>\n <div row>\n <span>현재 상태</span>\n <span>\n ${\n this.project.tenantDomain\n ? html`<b style=\"color:#42b382\">활성 테넌트</b>`\n : this.project.code\n ? html`<b style=\"color:#aa6633\">강등됨</b> (재승격 시 ${this.project.code} 재사용)`\n : html`<b style=\"color:#999\">미승격</b>`\n }\n </span>\n </div>\n <div row>\n <span></span>\n <span tenant-actions>\n ${\n this.project.tenantDomain\n ? html`<md-elevated-button @click=${this._demoteTenant} ?disabled=${!this.project.id}>\n <md-icon slot=\"icon\">link_off</md-icon>테넌트 강등\n </md-elevated-button>`\n : html`<md-elevated-button green @click=${this._promoteTenant} ?disabled=${!this.project.id}>\n <md-icon slot=\"icon\">verified</md-icon>테넌트 승격\n </md-elevated-button>`\n }\n </span>\n </div>\n </div>\n </div>\n </div>\n `\n }\n\n async pageInitialized(lifecycle: PageLifecycle) {}\n\n async pageUpdated(changes: any, lifecycle: PageLifecycle) {\n if (!this.active) return\n\n this.projectId = await this._resolveProjectId(lifecycle.resourceId || '')\n await this.initProject(this.projectId)\n }\n\n /**\n * resourceId 를 정상 Project.id (UUID) 로 변환한다.\n * - UUID 형식이면 그대로 사용\n * - 그 외 (code 또는 비어있음) 에는 현재 테넌트 컨텍스트의 프로젝트 조회\n */\n private async _resolveProjectId(resourceId: string): Promise<string> {\n if (resourceId && resourceId.length === 36) return resourceId\n\n const response = await client.query({\n query: gql`\n query CurrentProject {\n currentProject {\n id\n }\n }\n `\n })\n\n return response.data?.currentProject?.id || ''\n }\n\n async initProject(projectId: string = '') {\n const response = await client.query({\n query: gql`\n query Project($id: String!, $filters: [Filter!]) {\n project(id: $id) {\n id\n code\n state\n tenantDomain {\n id\n subdomain\n name\n }\n name\n documentNaming\n sectorType\n buildingUsage\n postalCode\n geoGroup\n startDate\n endDate\n mainPhoto {\n fullpath\n }\n totalProgress\n weeklyProgress\n kpi\n inspPassRate\n robotProgressRate\n structuralSafetyRate\n robotCount\n buildingComplex {\n id\n address\n siteType\n latitude\n longitude\n area\n coverageRatio\n floorAreaRatio\n structureType\n workerCount\n designChangeCount\n plannedProgress\n actualProgress\n clientCompany\n constructionCompany\n supervisoryCompany\n designCompany\n drawing {\n id\n name\n }\n constructionType\n constructionCost\n etc\n notice\n householdCount\n buildingCount\n overallConstructorEmails\n taskConstructorEmails\n overallSupervisoryEmails\n taskSupervisoryEmails\n virtualTourLink\n buildings {\n id\n name\n floorCount\n hasBasement\n basementFloorCount\n }\n }\n }\n\n employees(filters: $filters) {\n items {\n id\n name\n jobResponsibility\n active\n user {\n id\n name\n email\n }\n }\n }\n }\n `,\n variables: {\n id: projectId,\n filters: [\n {\n name: 'active',\n operator: 'eq',\n value: true\n },\n {\n name: 'userId',\n operator: 'is_not_null',\n value: ''\n }\n ]\n },\n context: { headers: tenantHeaders((this.project as any)?.code) }\n })\n\n this.project = response.data?.project || { ...this.defaultProject }\n\n const items = response.data?.employees?.items || []\n this.overallConstructorList = this._filterUserByPermission(items, 'OVERALL_CONSTRUCTOR')\n this.taskConstructorList = this._filterUserByPermission(items, 'TASK_CONSTRUCTOR')\n this.overallSupervisoryList = this._filterUserByPermission(items, 'OVERALL_SUPERVISORY')\n this.taskSupervisoryList = this._filterUserByPermission(items, 'TASK_SUPERVISORY')\n\n this.updateContext()\n }\n\n private _filterUserByPermission(userList, permission: string) {\n return userList\n .filter(v => v.jobResponsibility == permission || v.jobResponsibility == 'ADMIN')\n .map(v => ({ name: v.name, value: v.user.email }))\n }\n\n private _stripTypename(obj: any): any {\n if (Array.isArray(obj)) {\n return obj.map(item => this._stripTypename(item))\n }\n if (obj && typeof obj === 'object' && !(obj instanceof File)) {\n const cleaned: any = {}\n for (const [key, value] of Object.entries(obj)) {\n if (key === '__typename') continue\n cleaned[key] = this._stripTypename(value)\n }\n return cleaned\n }\n return obj\n }\n\n private async _saveProject() {\n // 첨부 파일 필드 제거 (첨부 파일은 {filename}Upload 로 전송)\n delete this.project.mainPhoto\n delete this.project.buildingComplex.drawing\n // ProjectPatch 입력 타입에 없는 표시 전용 필드 제거 (테넌트 승격 UI 표시용)\n const projectCode = (this.project as any).code // 도메인 헤더 용으로 미리 보존\n delete (this.project as any).code\n delete (this.project as any).tenantDomain\n // state 는 raw 값('10'/'20')으로 로드되지만 ProjectPatch 는 enum 키 기대 — 일반\n // save 에서는 state 변경 의도 없으므로 patch 에서 제외. (state 변경은 _onUnfinalize 별도)\n delete (this.project as any).state\n\n // buildingComplex.siteType 가 SiteType enum 외 값 ('BUILDING' 같은 옛 잔재) 이면\n // mutation validation 실패. enum 에 없으면 null 로 정정.\n const validSiteTypes = new Set(Object.values(SiteType))\n if (this.project.buildingComplex?.siteType && !validSiteTypes.has(this.project.buildingComplex.siteType as any)) {\n this.project.buildingComplex.siteType = null as any\n }\n\n const response = await client.mutate({\n mutation: gql`\n mutation UpdateProject($project: ProjectPatch!) {\n updateProject(project: $project) {\n id\n }\n }\n `,\n variables: {\n project: this._stripTypename(this.project)\n },\n context: {\n hasUpload: true,\n headers: tenantHeaders(projectCode)\n }\n })\n\n if (!response.errors) {\n // 양방향 동기 — 연면적/용적률/투입인력/설계변경건을 KPI 기준정보(KpiMetricValue)에 반영하고,\n // 공사일정(startDate/endDate)은 KPI \"계획기간\"(constructionPeriod)으로 단방향 반영.\n // 보조 동기 실패가 저장 성공 UX 를 막지 않도록 warn 만.\n try {\n await syncProjectInfoToKpi(this.project, projectCode)\n } catch (e) {\n console.warn('[프로젝트정보→KPI] KpiMetricValue 동기 실패', e)\n }\n notify({ message: '저장에 성공하였습니다.' })\n }\n }\n\n /**\n * 완료(COMPLETED) 상태 프로젝트를 진행중(ONGOING) 으로 되돌림.\n * ProjectPatch 의 state 필드만 갱신 — 다른 폼 필드는 건드리지 않음.\n */\n private _onUnfinalize = async () => {\n if (!this.project?.id) return\n const ok = await OxPrompt.open({\n title: '완료 상태 해제',\n confirmButton: { text: '확인' },\n cancelButton: { text: '취소' }\n })\n if (!ok) return\n\n const response = await client.mutate({\n mutation: gql`\n mutation UpdateProject($project: ProjectPatch!) {\n updateProject(project: $project) {\n id\n state\n }\n }\n `,\n variables: { project: { id: this.project.id, state: 'ONGOING' } },\n context: { headers: tenantHeaders((this.project as any)?.code) }\n })\n\n if (!response.errors) {\n notify({ message: '진행중 상태로 변경되었습니다.' })\n await this.initProject(this.project.id)\n }\n }\n\n /**\n * 주소 옆 \"정보 연동\" 버튼 handler.\n * - 현재 폼의 주소를 backend 로 보냄 → backend 가 EnvVar 갱신 + 세움터 시나리오 호출\n * - 결과 (한글 raw + 숫자) 를 받아 BuildingComplex 폼 필드들에 매핑\n * · siteType (한글) → SiteType enum 변환 (BC.siteType, enum 컬럼)\n * · siteType (한글) → BC.constructionType 그대로 (자유 텍스트 컬럼)\n * · area / floorAreaRatio / coverageRatio / householdCount / buildingCount /\n * structureType → BC 동명 컬럼에 직접 대입\n * · upperFloorCount / lowerFloorCount / bldNm / useAprDay / pmsDay → BC 컬럼\n * 없음, 매핑 skip (참고용 콘솔 로그만)\n */\n private async _collectBasicInfo() {\n const address = (this.project?.buildingComplex?.address || '').trim()\n if (!address) {\n notify({ message: '먼저 프로젝트 주소를 입력해 주세요.' })\n return\n }\n this.collectingBasicInfo = true\n try {\n const response = await client.mutate({\n mutation: gql`\n mutation CollectProjectBasicInfo($projectId: String!, $address: String) {\n collectProjectBasicInfo(projectId: $projectId, address: $address) {\n source\n label\n ok\n message\n data\n }\n }\n `,\n variables: { projectId: this.project.id, address },\n context: { headers: tenantHeaders((this.project as any)?.code) }\n })\n if (response.errors) {\n throw new Error(response.errors.map((e: any) => e.message).join('\\n'))\n }\n const result = response.data?.collectProjectBasicInfo\n if (!result?.ok) {\n notify({ message: result?.message || '업데이트할 정보가 없습니다.' })\n return\n }\n const data = result.data || {}\n this._applyBasicInfoToForm(data)\n this.requestUpdate()\n notify({ message: '외부 시스템에서 기본 정보를 가져왔습니다. 검토 후 [저장]을 눌러주세요.' })\n } catch (err) {\n console.warn('[정보 연동] 실패', err)\n notify({ message: '외부 시스템 연동이 일시적으로 안 됩니다.' })\n } finally {\n this.collectingBasicInfo = false\n }\n }\n\n /**\n * 한글 주용도명 → SiteType enum.\n * - NFC normalize + 공백 제거로 보이지 않는 문자/표기 차이 대응\n * - 매핑 미존재시 null\n */\n private _siteTypeFromKorean(korean: string | null | undefined): string | null {\n if (!korean) return null\n const KEYS: Record<string, string> = {\n 공동주택: 'APARTMENT_COMPLEX',\n 단독주택: 'DETACHED_HOUSE',\n 제1종근린생활시설: 'NEIGHBORHOOD_FACILITY',\n 제2종근린생활시설: 'NEIGHBORHOOD_FACILITY',\n 근린생활시설: 'NEIGHBORHOOD_FACILITY',\n 업무시설: 'OFFICE',\n 판매시설: 'COMMERCIAL',\n 교육연구시설: 'EDUCATION',\n 의료시설: 'MEDICAL',\n 문화및집회시설: 'CULTURAL',\n 종교시설: 'RELIGIOUS',\n 공장: 'INDUSTRIAL',\n 창고시설: 'INDUSTRIAL',\n 운수시설: 'TRANSPORT'\n }\n const key = korean.normalize('NFC').replace(/\\s+/g, '').trim()\n return KEYS[key] || null\n }\n\n /**\n * 시나리오 result data 를 폼에 매핑 — BuildingComplex 와 Project 양쪽.\n * 빈 값(null/undefined/'')은 기존 값 유지 — 자동 수집이 사용자 입력을 무리하게 덮지 않도록.\n */\n private _applyBasicInfoToForm(data: Record<string, any>) {\n const bc = { ...this.project.buildingComplex }\n const setBC = (key: string, value: any) => {\n if (value === null || value === undefined || value === '') return\n ;(bc as any)[key] = value\n }\n setBC('area', this._toNum(data.area))\n setBC('floorAreaRatio', this._toNum(data.floorAreaRatio))\n setBC('coverageRatio', this._toNum(data.coverageRatio))\n setBC('householdCount', this._toNum(data.householdCount))\n setBC('buildingCount', this._toNum(data.buildingCount))\n setBC('structureType', data.structureType)\n // siteType: 한글 → SiteType enum 매핑. 매핑 안 되면 BC.siteType 빈 채로.\n if (data.siteType) {\n const enumVal = this._siteTypeFromKorean(data.siteType)\n if (enumVal) (bc as any).siteType = enumVal\n }\n\n // Project 엔티티 컬럼 — 착공일/준공일.\n // 세움터 useAprDay (사용승인일) = 준공으로 매핑. stcnsDay = 착공.\n const project: any = { ...this.project, buildingComplex: bc }\n if (data.startDate) project.startDate = data.startDate\n if (data.endDate) project.endDate = data.endDate\n this.project = project\n\n // 참고 메타 — BC/Project 직접 컬럼 없음, 콘솔로만 (permitDate=건축허가일, bldNm 등)\n console.log(\n '[정보 연동] meta:',\n JSON.stringify({\n upperFloorCount: data.upperFloorCount,\n lowerFloorCount: data.lowerFloorCount,\n bldNm: data.bldNm,\n permitDate: data.permitDate\n })\n )\n }\n\n private _toNum(v: any): number | null {\n if (v === null || v === undefined || v === '') return null\n const n = Number(v)\n return Number.isFinite(n) ? n : null\n }\n\n private async _promoteTenant() {\n if (!this.project.id) return\n\n if (!confirm('이 프로젝트를 테넌트로 승격하시겠습니까?\\n승격 시 관리번호(YYYY-NNNNN)가 자동 발번되며 변경할 수 없습니다.')) {\n return\n }\n\n const response = await client.mutate({\n mutation: gql`\n mutation PromoteProjectToTenant($projectId: String!) {\n promoteProjectToTenant(projectId: $projectId) {\n id\n code\n }\n }\n `,\n variables: { projectId: this.project.id }\n })\n\n if (!response.errors) {\n notify({ message: `테넌트로 승격되었습니다. 관리번호: ${response.data.promoteProjectToTenant.code}` })\n await this.initProject(this.projectId)\n }\n }\n\n private async _demoteTenant() {\n if (!this.project.id) return\n\n if (!confirm('이 프로젝트의 테넌트를 강등하시겠습니까?\\n관리번호는 보존되며 재승격 시 같은 번호가 재사용됩니다.')) {\n return\n }\n\n const response = await client.mutate({\n mutation: gql`\n mutation DemoteProjectTenant($projectId: String!) {\n demoteProjectTenant(projectId: $projectId)\n }\n `,\n variables: { projectId: this.project.id }\n })\n\n if (!response.errors) {\n notify({ message: '테넌트가 강등되었습니다.' })\n await this.initProject(this.projectId)\n }\n }\n\n // 동 적용 버튼을 누르면 입력한 수 만큼 해당 단지에 동 데이터 생성\n private _setBuilding() {\n const buildingCount: number = this.project?.buildingComplex?.buildingCount || 0\n const buildingInitData = { name: undefined, floorCount: undefined }\n\n // 빌딩 데이터가 없으면 빈 배열 넣어줌\n if (!this.project?.buildingComplex?.buildings?.length) {\n this.project.buildingComplex.buildings = []\n }\n\n if (this.project.buildingComplex.buildings.length >= buildingCount) {\n // 동 수가 더 작게 들어오면 기존 배열을 필요한 크기만큼 잘라내기\n this.project.buildingComplex.buildings = [...this.project.buildingComplex.buildings!.slice(0, buildingCount)]\n } else {\n // 동수가 더 크게 들어오면 기존 배열 + 빈 값을 채움\n const additionalCount = buildingCount - this.project.buildingComplex.buildings.length\n const additionalBuildings = Array.from({ length: additionalCount }, () => ({ ...buildingInitData }))\n this.project.buildingComplex.buildings = [...this.project.buildingComplex.buildings, ...additionalBuildings]\n }\n\n // 리렌더링\n this.project = { ...this.project }\n }\n\n // Input 요소의 값이 변경될 때 호출되는 콜백 함수\n /**\n * 우편번호 입력 핸들러 — postalCode 갱신과 동시에 앞 2자리 prefix 로 geoGroup\n * best-guess 자동 매핑. 매핑 안 되면 기존 값 유지.\n */\n private _onPostalCodeChange(event: InputEvent) {\n const target = event.target as HTMLInputElement\n const value = target.value\n ;(this.project as any).postalCode = value\n const inferred = inferGeoGroupFromPostalCode(value)\n if (inferred) (this.project as any).geoGroup = inferred\n }\n\n private _onInputChange(event: InputEvent, idx: number) {\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 if (target.hasAttribute('project')) {\n this.project[target.name] = inputVal\n\n // documentNaming 필드가 변경되면 실시간으로 미리보기 업데이트\n if (target.name === 'documentNaming') {\n this.requestUpdate()\n }\n } else if (target.hasAttribute('building-complex')) {\n this.project.buildingComplex![target.name] = inputVal\n } else if (target.hasAttribute('building')) {\n this.project.buildingComplex.buildings![idx][target.name] = inputVal\n }\n }\n\n // 이미지 업로드\n async onCreateAttachment(e: CustomEvent) {\n const target = e.target as HTMLInputElement\n const file = (target.name === 'mainPhoto' ? e.detail : e.detail[0]) || null\n\n if (target.name === 'mainPhoto') {\n this.project.mainPhotoUpload = file\n } else {\n this.project.buildingComplex.drawingUpload = file\n }\n }\n\n private _handleSelectionChange(e) {\n const name = e.target.getAttribute('name')\n const selectedValues = e.detail.selectedValues\n\n this.project.buildingComplex[name] = selectedValues\n }\n\n private _toggleBasement(idx: number) {\n const building = this.project.buildingComplex.buildings![idx]\n building.hasBasement = !building.hasBasement\n\n // 층 수가 0이거나 체크 해제시 지하층수 초기화 (플레이스 홀더 나오게 하기 위함)\n if (building.basementFloorCount == 0 || !building.hasBasement) {\n building.basementFloorCount = undefined\n }\n\n this.project.buildingComplex.buildings![idx]['hasBasement'] = building.hasBasement\n this.requestUpdate()\n }\n}\n"]}
@@ -37,7 +37,7 @@ export async function collectFromSource(source, project) {
37
37
  label: '세움터',
38
38
  fields: [
39
39
  { projectKey: 'area', label: '연면적', value: bc.area || 114000, unit: '㎡' },
40
- { projectKey: 'floorAreaRatio', label: '용적율', value: bc.floorAreaRatio || 250, unit: '%' },
40
+ { projectKey: 'floorAreaRatio', label: '용적률', value: bc.floorAreaRatio || 250, unit: '%' },
41
41
  { projectKey: 'upperFloorCount', label: '지상층수', value: 25, unit: '층' },
42
42
  { projectKey: 'lowerFloorCount', label: '지하층수', value: 3, unit: '층' }
43
43
  ]
@@ -1 +1 @@
1
- {"version":3,"file":"integration-fetch.js","sourceRoot":"","sources":["../../client/shared/integration-fetch.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAWH,iCAAiC;AACjC,MAAM,CAAC,MAAM,mBAAmB,GAA4B;IAC1D,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE;IACjE,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,eAAe,EAAE;IACvE,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,YAAY,EAAE;CACnE,CAAA;AAgBD;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,MAAyB,EAAE,OAAY;IAC7E,sCAAsC;IACtC,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAA;IAE1C,MAAM,EAAE,GAAG,CAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,eAAe,KAAI,EAAE,CAAA;IAEzC,QAAQ,MAAM,EAAE,CAAC;QACf,KAAK,QAAQ;YACX,OAAO;gBACL,MAAM;gBACN,KAAK,EAAE,KAAK;gBACZ,MAAM,EAAE;oBACN,EAAE,UAAU,EAAE,oBAAoB,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE;oBAC5E,EAAE,UAAU,EAAE,kBAAkB,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,CAAC,gBAAgB,IAAI,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE;iBACnG;aACF,CAAA;QAEH,KAAK,UAAU;YACb,OAAO;gBACL,MAAM;gBACN,KAAK,EAAE,KAAK;gBACZ,MAAM,EAAE;oBACN,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,IAAI,IAAI,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE;oBACzE,EAAE,UAAU,EAAE,gBAAgB,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,cAAc,IAAI,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE;oBAC1F,EAAE,UAAU,EAAE,iBAAiB,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE;oBACtE,EAAE,UAAU,EAAE,iBAAiB,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE;iBACtE;aACF,CAAA;QAEH,KAAK,SAAS;YACZ,OAAO;gBACL,MAAM;gBACN,KAAK,EAAE,KAAK;gBACZ,MAAM,EAAE,CAAC,EAAE,UAAU,EAAE,8BAA8B,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;aACnG,CAAA;IACL,CAAC;AACH,CAAC","sourcesContent":["/**\n * 완공 입력 화면의 \"자동 수집\" 워크플로용 외부 시스템 연동 수집 함수.\n *\n * 데모 목적: 키스콘·세움터·올바로에서 \"연동을 통해 값을 가져온다\"는 워크플로를\n * 보여주기 위한 것. 실제 스크래핑/API 연동의 깊이는 이 함수 내부에 캡슐화되어\n * 있으므로, 추후 실연동(integration-kiscon / data-go-kr / integration-allbaro)\n * 으로 교체할 때 이 본문만 바꾸면 화면/저장 흐름은 그대로 유지된다.\n */\n\nexport type IntegrationSource = 'kiscon' | 'saeumteo' | 'allbaro'\n\nexport interface IntegrationSourceMeta {\n source: IntegrationSource\n label: string\n icon: string\n desc: string\n}\n\n/** 화면에 표시할 연동 소스 목록 (수집 순서대로) */\nexport const INTEGRATION_SOURCES: IntegrationSourceMeta[] = [\n { source: 'kiscon', label: '키스콘', icon: '🏗', desc: '건설공사대장 통보' },\n { source: 'saeumteo', label: '세움터', icon: '🏛', desc: '건축물대장 / 건축HUB' },\n { source: 'allbaro', label: '올바로', icon: '♻', desc: '건설폐기물 처리실적' }\n]\n\nexport interface CollectedField {\n /** 폼의 projectKey (KPI_METRIC_KEY_MAPPING 의 projectKey 와 일치) */\n projectKey: string\n label: string\n value: number | string\n unit?: string\n}\n\nexport interface CollectionResult {\n source: IntegrationSource\n label: string\n fields: CollectedField[]\n}\n\n/**\n * 한 외부 시스템에서 연동으로 값을 수집한다.\n * 데모에서는 연동 수집을 흉내내기 위해 짧은 지연 후 대표값을 반환한다\n * (프로젝트에 이미 있는 값이 있으면 우선 사용).\n */\nexport async function collectFromSource(source: IntegrationSource, project: any): Promise<CollectionResult> {\n // 연동 수집이 \"진행되는\" 느낌을 주기 위한 짧은 지연 (데모용)\n await new Promise(r => setTimeout(r, 700))\n\n const bc = project?.buildingComplex || {}\n\n switch (source) {\n case 'kiscon':\n return {\n source,\n label: '키스콘',\n fields: [\n { projectKey: 'constructionPeriod', label: '계획공사기간', value: 30, unit: '개월' },\n { projectKey: 'constructionCost', label: '계획공사비', value: bc.constructionCost || 2500, unit: '억원' }\n ]\n }\n\n case 'saeumteo':\n return {\n source,\n label: '세움터',\n fields: [\n { projectKey: 'area', label: '연면적', value: bc.area || 114000, unit: '㎡' },\n { projectKey: 'floorAreaRatio', label: '용적율', value: bc.floorAreaRatio || 250, unit: '%' },\n { projectKey: 'upperFloorCount', label: '지상층수', value: 25, unit: '층' },\n { projectKey: 'lowerFloorCount', label: '지하층수', value: 3, unit: '층' }\n ]\n }\n\n case 'allbaro':\n return {\n source,\n label: '올바로',\n fields: [{ projectKey: 'totalConstructionWasteAmount', label: '총 건설폐기물', value: 15, unit: 'ton' }]\n }\n }\n}\n"]}
1
+ {"version":3,"file":"integration-fetch.js","sourceRoot":"","sources":["../../client/shared/integration-fetch.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAWH,iCAAiC;AACjC,MAAM,CAAC,MAAM,mBAAmB,GAA4B;IAC1D,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE;IACjE,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,eAAe,EAAE;IACvE,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,YAAY,EAAE;CACnE,CAAA;AAgBD;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,MAAyB,EAAE,OAAY;IAC7E,sCAAsC;IACtC,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAA;IAE1C,MAAM,EAAE,GAAG,CAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,eAAe,KAAI,EAAE,CAAA;IAEzC,QAAQ,MAAM,EAAE,CAAC;QACf,KAAK,QAAQ;YACX,OAAO;gBACL,MAAM;gBACN,KAAK,EAAE,KAAK;gBACZ,MAAM,EAAE;oBACN,EAAE,UAAU,EAAE,oBAAoB,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE;oBAC5E,EAAE,UAAU,EAAE,kBAAkB,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,CAAC,gBAAgB,IAAI,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE;iBACnG;aACF,CAAA;QAEH,KAAK,UAAU;YACb,OAAO;gBACL,MAAM;gBACN,KAAK,EAAE,KAAK;gBACZ,MAAM,EAAE;oBACN,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,IAAI,IAAI,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE;oBACzE,EAAE,UAAU,EAAE,gBAAgB,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,cAAc,IAAI,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE;oBAC1F,EAAE,UAAU,EAAE,iBAAiB,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE;oBACtE,EAAE,UAAU,EAAE,iBAAiB,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE;iBACtE;aACF,CAAA;QAEH,KAAK,SAAS;YACZ,OAAO;gBACL,MAAM;gBACN,KAAK,EAAE,KAAK;gBACZ,MAAM,EAAE,CAAC,EAAE,UAAU,EAAE,8BAA8B,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;aACnG,CAAA;IACL,CAAC;AACH,CAAC","sourcesContent":["/**\n * 완공 입력 화면의 \"자동 수집\" 워크플로용 외부 시스템 연동 수집 함수.\n *\n * 데모 목적: 키스콘·세움터·올바로에서 \"연동을 통해 값을 가져온다\"는 워크플로를\n * 보여주기 위한 것. 실제 스크래핑/API 연동의 깊이는 이 함수 내부에 캡슐화되어\n * 있으므로, 추후 실연동(integration-kiscon / data-go-kr / integration-allbaro)\n * 으로 교체할 때 이 본문만 바꾸면 화면/저장 흐름은 그대로 유지된다.\n */\n\nexport type IntegrationSource = 'kiscon' | 'saeumteo' | 'allbaro'\n\nexport interface IntegrationSourceMeta {\n source: IntegrationSource\n label: string\n icon: string\n desc: string\n}\n\n/** 화면에 표시할 연동 소스 목록 (수집 순서대로) */\nexport const INTEGRATION_SOURCES: IntegrationSourceMeta[] = [\n { source: 'kiscon', label: '키스콘', icon: '🏗', desc: '건설공사대장 통보' },\n { source: 'saeumteo', label: '세움터', icon: '🏛', desc: '건축물대장 / 건축HUB' },\n { source: 'allbaro', label: '올바로', icon: '♻', desc: '건설폐기물 처리실적' }\n]\n\nexport interface CollectedField {\n /** 폼의 projectKey (KPI_METRIC_KEY_MAPPING 의 projectKey 와 일치) */\n projectKey: string\n label: string\n value: number | string\n unit?: string\n}\n\nexport interface CollectionResult {\n source: IntegrationSource\n label: string\n fields: CollectedField[]\n}\n\n/**\n * 한 외부 시스템에서 연동으로 값을 수집한다.\n * 데모에서는 연동 수집을 흉내내기 위해 짧은 지연 후 대표값을 반환한다\n * (프로젝트에 이미 있는 값이 있으면 우선 사용).\n */\nexport async function collectFromSource(source: IntegrationSource, project: any): Promise<CollectionResult> {\n // 연동 수집이 \"진행되는\" 느낌을 주기 위한 짧은 지연 (데모용)\n await new Promise(r => setTimeout(r, 700))\n\n const bc = project?.buildingComplex || {}\n\n switch (source) {\n case 'kiscon':\n return {\n source,\n label: '키스콘',\n fields: [\n { projectKey: 'constructionPeriod', label: '계획공사기간', value: 30, unit: '개월' },\n { projectKey: 'constructionCost', label: '계획공사비', value: bc.constructionCost || 2500, unit: '억원' }\n ]\n }\n\n case 'saeumteo':\n return {\n source,\n label: '세움터',\n fields: [\n { projectKey: 'area', label: '연면적', value: bc.area || 114000, unit: '㎡' },\n { projectKey: 'floorAreaRatio', label: '용적률', value: bc.floorAreaRatio || 250, unit: '%' },\n { projectKey: 'upperFloorCount', label: '지상층수', value: 25, unit: '층' },\n { projectKey: 'lowerFloorCount', label: '지하층수', value: 3, unit: '층' }\n ]\n }\n\n case 'allbaro':\n return {\n source,\n label: '올바로',\n fields: [{ projectKey: 'totalConstructionWasteAmount', label: '총 건설폐기물', value: 15, unit: 'ton' }]\n }\n }\n}\n"]}
@@ -1,5 +1,6 @@
1
- /** BuildingComplex 실컬럼 보유 → 프로젝트정보 ↔ KPI 양방향 동기 대상. */
2
- export declare const BIDIRECTIONAL_BC_KEYS: readonly ["workerCount", "area", "floorAreaRatio", "designChangeCount"];
1
+ /** BuildingComplex 실컬럼 보유 → 프로젝트정보 ↔ KPI 양방향 동기 대상.
2
+ * (constructionCost 억원 소수 가능 정수화 대상 아님) */
3
+ export declare const BIDIRECTIONAL_BC_KEYS: readonly ["workerCount", "area", "floorAreaRatio", "designChangeCount", "constructionCost"];
3
4
  /**
4
5
  * 프로젝트 정보관리 저장 → KPI 기준정보 반영.
5
6
  * - 4개 양방향 필드: BuildingComplex 값 → KpiMetricValue
@@ -1,27 +1,29 @@
1
1
  import moment from 'moment-timezone';
2
- import { calcDateDiff } from './func';
3
2
  import { getKpiMetrics, getKpiMetricValues, updateProjectCompleteStep1, updateBuildingComplexFields } from './complete-api';
4
3
  /**
5
4
  * 프로젝트 정보관리 ↔ KPI 정보입력 상호 동기.
6
5
  *
7
- * - 양방향(BIDIRECTIONAL_BC_KEYS): BuildingComplex 실컬럼을 가진 4필드는 어느 화면에서
8
- * 저장하든 반대편도 갱신 (last-write-wins). 두 저장 위치(BuildingComplex 컬럼 /
9
- * KpiMetricValue row)는 그대로 두고 값만 맞춘다.
6
+ * - 양방향(BIDIRECTIONAL_BC_KEYS): BuildingComplex 실컬럼을 가진 5필드(투입인력/연면적/
7
+ * 용적률/설계변경건/계획공사비)는 어느 화면에서 저장하든 반대편도 갱신 (last-write-wins).
8
+ * 두 저장 위치(BuildingComplex 컬럼 / KpiMetricValue row)는 그대로 두고 값만 맞춘다.
10
9
  * - 일방향(constructionPeriod): 공사일정(startDate/endDate)은 KPI "계획기간"에 해당하므로
11
10
  * 프로젝트 → KPI 단방향으로만 반영한다. (KPI 에서 일정을 역수정하지 않음)
12
11
  *
13
12
  * metric ↔ project 필드 매칭은 metric.name(라벨) 기준 — KPI_METRIC_KEY_MAPPING 과 동일.
14
13
  */
15
- /** metric 라벨 ↔ 프로젝트/BuildingComplex 필드 키. (pc-tab1-plan 매핑과 일치) */
14
+ /** metric 라벨 ↔ 프로젝트/BuildingComplex 필드 키. 라벨은 실제 DB KpiMetric.name 과 일치해야 함.
15
+ * (DB 확인: 계획공사비/계획공사기간/총 투입인력/용적률/총 설계 변경 건/연면적 존재) */
16
16
  const LABEL_BY_KEY = {
17
- constructionPeriod: '공사기간',
18
- workerCount: '총 투입인력(M/M)',
19
- floorAreaRatio: '용적율',
17
+ constructionPeriod: '계획공사기간',
18
+ constructionCost: '계획공사비',
19
+ workerCount: '총 투입인력',
20
+ floorAreaRatio: '용적률',
20
21
  designChangeCount: '총 설계 변경 건',
21
22
  area: '연면적'
22
23
  };
23
- /** BuildingComplex 실컬럼 보유 → 프로젝트정보 ↔ KPI 양방향 동기 대상. */
24
- export const BIDIRECTIONAL_BC_KEYS = ['workerCount', 'area', 'floorAreaRatio', 'designChangeCount'];
24
+ /** BuildingComplex 실컬럼 보유 → 프로젝트정보 ↔ KPI 양방향 동기 대상.
25
+ * (constructionCost 억원 소수 가능 정수화 대상 아님) */
26
+ export const BIDIRECTIONAL_BC_KEYS = ['workerCount', 'area', 'floorAreaRatio', 'designChangeCount', 'constructionCost'];
25
27
  /** BuildingComplexPatch 에서 Int 로 정의된 컬럼 — write 시 정수화 필요. */
26
28
  const BC_INT_KEYS = new Set(['workerCount', 'designChangeCount']);
27
29
  function metricByProjectKey(metrics, projectKey) {
@@ -50,9 +52,7 @@ function findExistingRow(values, metric) {
50
52
  function buildPatch(metric, value, existing, projectId) {
51
53
  var _a, _b, _c, _d;
52
54
  const today = moment().tz('Asia/Seoul');
53
- const valueDate = (_a = existing === null || existing === void 0 ? void 0 : existing.valueDate) !== null && _a !== void 0 ? _a : (metric.periodType === 'MONTH'
54
- ? today.clone().subtract(1, 'month').format('YYYY-MM-01')
55
- : today.format('YYYY-MM-DD'));
55
+ const valueDate = (_a = existing === null || existing === void 0 ? void 0 : existing.valueDate) !== null && _a !== void 0 ? _a : (metric.periodType === 'MONTH' ? today.clone().subtract(1, 'month').format('YYYY-MM-01') : today.format('YYYY-MM-DD'));
56
56
  return {
57
57
  id: (_b = existing === null || existing === void 0 ? void 0 : existing.id) !== null && _b !== void 0 ? _b : crypto.randomUUID(),
58
58
  metricId: metric.id,
@@ -72,10 +72,7 @@ function buildPatch(metric, value, existing, projectId) {
72
72
  export async function syncProjectInfoToKpi(project, projectCode) {
73
73
  if (!(project === null || project === void 0 ? void 0 : project.id))
74
74
  return;
75
- const [metrics, existing] = await Promise.all([
76
- getKpiMetrics(projectCode),
77
- getKpiMetricValues(project.id, projectCode)
78
- ]);
75
+ const [metrics, existing] = await Promise.all([getKpiMetrics(projectCode), getKpiMetricValues(project.id, projectCode)]);
79
76
  const bc = project.buildingComplex || {};
80
77
  const patches = [];
81
78
  for (const key of BIDIRECTIONAL_BC_KEYS) {
@@ -87,11 +84,16 @@ export async function syncProjectInfoToKpi(project, projectCode) {
87
84
  continue;
88
85
  patches.push(buildPatch(metric, Number(raw), findExistingRow(existing, metric), project.id));
89
86
  }
90
- // 공사기간 계획기간 — 프로젝트 일정 → KPI 단방향
87
+ // 계획공사기간 — 프로젝트 착공~준공 일정 → KPI 단방향.
88
+ // calcDateDiff 는 strict 'YYYY-MM-DD' 파싱이라 DB 가 ISO datetime 을 주면 0 이 되므로
89
+ // 여기서는 lenient moment 파싱으로 일수를 구해 개월(올림)로 환산한다.
91
90
  if (project.startDate && project.endDate) {
91
+ const start = moment(project.startDate);
92
+ const end = moment(project.endDate);
92
93
  const metric = metricByProjectKey(metrics, 'constructionPeriod');
93
- if (metric) {
94
- const months = Math.ceil(calcDateDiff(project.startDate, project.endDate) / 30);
94
+ if (metric && start.isValid() && end.isValid()) {
95
+ // 개월 = 일수/30, 소수점 한자리 반올림 (예: 373일 → 12.4개월)
96
+ const months = Math.max(0, Math.round((end.diff(start, 'days') / 30) * 10) / 10);
95
97
  patches.push(buildPatch(metric, months, findExistingRow(existing, metric), project.id));
96
98
  }
97
99
  }
@@ -1 +1 @@
1
- {"version":3,"file":"kpi-project-sync.js","sourceRoot":"","sources":["../../client/shared/kpi-project-sync.ts"],"names":[],"mappings":"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","sourcesContent":["import moment from 'moment-timezone'\n\nimport { calcDateDiff } from './func'\nimport {\n getKpiMetrics,\n getKpiMetricValues,\n updateProjectCompleteStep1,\n updateBuildingComplexFields\n} from './complete-api'\n\n/**\n * 프로젝트 정보관리 ↔ KPI 정보입력 상호 동기.\n *\n * - 양방향(BIDIRECTIONAL_BC_KEYS): BuildingComplex 실컬럼을 가진 4필드는 어느 화면에서\n * 저장하든 반대편도 갱신 (last-write-wins). 두 저장 위치(BuildingComplex 컬럼 /\n * KpiMetricValue row)는 그대로 두고 값만 맞춘다.\n * - 일방향(constructionPeriod): 공사일정(startDate/endDate)은 KPI \"계획기간\"에 해당하므로\n * 프로젝트 → KPI 단방향으로만 반영한다. (KPI 에서 일정을 역수정하지 않음)\n *\n * metric ↔ project 필드 매칭은 metric.name(라벨) 기준 — KPI_METRIC_KEY_MAPPING 과 동일.\n */\n\n/** metric 라벨 ↔ 프로젝트/BuildingComplex 필드 키. (pc-tab1-plan 의 매핑과 일치) */\nconst LABEL_BY_KEY: Record<string, string> = {\n constructionPeriod: '공사기간',\n workerCount: '총 투입인력(M/M)',\n floorAreaRatio: '용적율',\n designChangeCount: '총 설계 변경 건',\n area: '연면적'\n}\n\n/** BuildingComplex 실컬럼 보유 → 프로젝트정보 ↔ KPI 양방향 동기 대상. */\nexport const BIDIRECTIONAL_BC_KEYS = ['workerCount', 'area', 'floorAreaRatio', 'designChangeCount'] as const\n\n/** BuildingComplexPatch 에서 Int 로 정의된 컬럼 — write 시 정수화 필요. */\nconst BC_INT_KEYS = new Set(['workerCount', 'designChangeCount'])\n\nfunction metricByProjectKey(metrics: any[], projectKey: string): any | undefined {\n const label = LABEL_BY_KEY[projectKey]\n if (!label) return undefined\n return metrics.find((m: any) => m.name === label)\n}\n\n/**\n * metric 의 기존 KpiMetricValue row 를 찾는다. (pc-tab1-plan 렌더/upsert 규칙과 동일)\n * - MONTH : 전월(YYYY-MM) prefix row\n * - 그 외 : (metricId, periodType) 단일 row\n */\nfunction findExistingRow(values: any[], metric: any): any | undefined {\n if (metric.periodType === 'MONTH') {\n const lastMonthYm = moment().tz('Asia/Seoul').subtract(1, 'month').format('YYYY-MM')\n return values.find(\n (v: any) => v.metricId === metric.id && v.periodType === 'MONTH' && (v.valueDate || '').startsWith(lastMonthYm)\n )\n }\n return values.find((v: any) => v.metricId === metric.id && v.periodType === metric.periodType)\n}\n\n/**\n * metric + value → KpiMetricValuePatch. 기존 row 가 있으면 그 id/valueDate 를 재사용해\n * 서버의 (domain, metric, valueDate, org) upsert 가 동일 row 를 update 하도록 한다.\n * org 는 projectId — 서버가 프로젝트 테넌트 도메인으로 스위치하는 데 필요.\n */\nfunction buildPatch(metric: any, value: number, existing: any, projectId: string): any {\n const today = moment().tz('Asia/Seoul')\n const valueDate =\n existing?.valueDate ??\n (metric.periodType === 'MONTH'\n ? today.clone().subtract(1, 'month').format('YYYY-MM-01')\n : today.format('YYYY-MM-DD'))\n\n return {\n id: existing?.id ?? crypto.randomUUID(),\n metricId: metric.id,\n value,\n valueDate,\n periodType: metric.periodType,\n unit: existing?.unit ?? metric.unit ?? '',\n org: projectId\n }\n}\n\n/**\n * 프로젝트 정보관리 저장 → KPI 기준정보 반영.\n * - 4개 양방향 필드: BuildingComplex 값 → KpiMetricValue\n * - constructionPeriod: startDate/endDate → ceil(기간/30) 개월 (일방향)\n * 대상 metric 이 kpi-admin 에서 비활성/삭제/개명됐으면 매칭 실패 → 조용히 skip.\n */\nexport async function syncProjectInfoToKpi(project: any, projectCode?: string): Promise<void> {\n if (!project?.id) return\n\n const [metrics, existing] = await Promise.all([\n getKpiMetrics(projectCode),\n getKpiMetricValues(project.id, projectCode)\n ])\n\n const bc = project.buildingComplex || {}\n const patches: any[] = []\n\n for (const key of BIDIRECTIONAL_BC_KEYS) {\n const raw = bc[key]\n if (raw === null || raw === undefined || raw === '') continue\n const metric = metricByProjectKey(metrics, key)\n if (!metric) continue\n patches.push(buildPatch(metric, Number(raw), findExistingRow(existing, metric), project.id))\n }\n\n // 공사기간 계획기간 — 프로젝트 일정 → KPI 단방향\n if (project.startDate && project.endDate) {\n const metric = metricByProjectKey(metrics, 'constructionPeriod')\n if (metric) {\n const months = Math.ceil(calcDateDiff(project.startDate, project.endDate) / 30)\n patches.push(buildPatch(metric, months, findExistingRow(existing, metric), project.id))\n }\n }\n\n if (patches.length) {\n await updateProjectCompleteStep1(patches, projectCode)\n }\n}\n\n/**\n * KPI 정보입력 저장 → 프로젝트 정보(BuildingComplex) 반영. (양방향 4필드만)\n * constructionPeriod 는 일방향이므로 write-back 대상에서 제외.\n * kpiMetrics / kpiMetricValues 는 KPI 화면이 이미 로드한 것을 그대로 사용.\n */\nexport async function syncKpiToProjectInfo(\n project: any,\n kpiMetrics: any[],\n kpiMetricValues: any[],\n projectCode?: string\n): Promise<void> {\n const bcId = project?.buildingComplex?.id\n if (!bcId) return\n\n const patch: Record<string, number> = {}\n for (const key of BIDIRECTIONAL_BC_KEYS) {\n const metric = metricByProjectKey(kpiMetrics, key)\n if (!metric) continue\n const row = findExistingRow(kpiMetricValues, metric)\n if (!row || row.value === undefined || row.value === null) continue\n const num = Number(row.value)\n patch[key] = BC_INT_KEYS.has(key) ? Math.round(num) : num\n }\n\n if (Object.keys(patch).length) {\n await updateBuildingComplexFields(bcId, patch, projectCode)\n }\n}\n"]}
1
+ {"version":3,"file":"kpi-project-sync.js","sourceRoot":"","sources":["../../client/shared/kpi-project-sync.ts"],"names":[],"mappings":"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","sourcesContent":["import moment from 'moment-timezone'\n\nimport { getKpiMetrics, getKpiMetricValues, updateProjectCompleteStep1, updateBuildingComplexFields } from './complete-api'\n\n/**\n * 프로젝트 정보관리 ↔ KPI 정보입력 상호 동기.\n *\n * - 양방향(BIDIRECTIONAL_BC_KEYS): BuildingComplex 실컬럼을 가진 5필드(투입인력/연면적/\n * 용적률/설계변경건/계획공사비)는 어느 화면에서 저장하든 반대편도 갱신 (last-write-wins).\n * 두 저장 위치(BuildingComplex 컬럼 / KpiMetricValue row)는 그대로 두고 값만 맞춘다.\n * - 일방향(constructionPeriod): 공사일정(startDate/endDate)은 KPI \"계획기간\"에 해당하므로\n * 프로젝트 → KPI 단방향으로만 반영한다. (KPI 에서 일정을 역수정하지 않음)\n *\n * metric ↔ project 필드 매칭은 metric.name(라벨) 기준 — KPI_METRIC_KEY_MAPPING 과 동일.\n */\n\n/** metric 라벨 ↔ 프로젝트/BuildingComplex 필드 키. 라벨은 실제 DB KpiMetric.name 과 일치해야 함.\n * (DB 확인: 계획공사비/계획공사기간/총 투입인력/용적률/총 설계 변경 건/연면적 존재) */\nconst LABEL_BY_KEY: Record<string, string> = {\n constructionPeriod: '계획공사기간',\n constructionCost: '계획공사비',\n workerCount: '총 투입인력',\n floorAreaRatio: '용적률',\n designChangeCount: '총 설계 변경 건',\n area: '연면적'\n}\n\n/** BuildingComplex 실컬럼 보유 → 프로젝트정보 ↔ KPI 양방향 동기 대상.\n * (constructionCost 는 억원 소수 가능 → 정수화 대상 아님) */\nexport const BIDIRECTIONAL_BC_KEYS = ['workerCount', 'area', 'floorAreaRatio', 'designChangeCount', 'constructionCost'] as const\n\n/** BuildingComplexPatch 에서 Int 로 정의된 컬럼 — write 시 정수화 필요. */\nconst BC_INT_KEYS = new Set(['workerCount', 'designChangeCount'])\n\nfunction metricByProjectKey(metrics: any[], projectKey: string): any | undefined {\n const label = LABEL_BY_KEY[projectKey]\n if (!label) return undefined\n return metrics.find((m: any) => m.name === label)\n}\n\n/**\n * metric 의 기존 KpiMetricValue row 를 찾는다. (pc-tab1-plan 렌더/upsert 규칙과 동일)\n * - MONTH : 전월(YYYY-MM) prefix row\n * - 그 외 : (metricId, periodType) 단일 row\n */\nfunction findExistingRow(values: any[], metric: any): any | undefined {\n if (metric.periodType === 'MONTH') {\n const lastMonthYm = moment().tz('Asia/Seoul').subtract(1, 'month').format('YYYY-MM')\n return values.find(\n (v: any) => v.metricId === metric.id && v.periodType === 'MONTH' && (v.valueDate || '').startsWith(lastMonthYm)\n )\n }\n return values.find((v: any) => v.metricId === metric.id && v.periodType === metric.periodType)\n}\n\n/**\n * metric + value → KpiMetricValuePatch. 기존 row 가 있으면 그 id/valueDate 를 재사용해\n * 서버의 (domain, metric, valueDate, org) upsert 가 동일 row 를 update 하도록 한다.\n * org 는 projectId — 서버가 프로젝트 테넌트 도메인으로 스위치하는 데 필요.\n */\nfunction buildPatch(metric: any, value: number, existing: any, projectId: string): any {\n const today = moment().tz('Asia/Seoul')\n const valueDate =\n existing?.valueDate ??\n (metric.periodType === 'MONTH' ? today.clone().subtract(1, 'month').format('YYYY-MM-01') : today.format('YYYY-MM-DD'))\n\n return {\n id: existing?.id ?? crypto.randomUUID(),\n metricId: metric.id,\n value,\n valueDate,\n periodType: metric.periodType,\n unit: existing?.unit ?? metric.unit ?? '',\n org: projectId\n }\n}\n\n/**\n * 프로젝트 정보관리 저장 → KPI 기준정보 반영.\n * - 4개 양방향 필드: BuildingComplex 값 → KpiMetricValue\n * - constructionPeriod: startDate/endDate → ceil(기간/30) 개월 (일방향)\n * 대상 metric 이 kpi-admin 에서 비활성/삭제/개명됐으면 매칭 실패 → 조용히 skip.\n */\nexport async function syncProjectInfoToKpi(project: any, projectCode?: string): Promise<void> {\n if (!project?.id) return\n\n const [metrics, existing] = await Promise.all([getKpiMetrics(projectCode), getKpiMetricValues(project.id, projectCode)])\n\n const bc = project.buildingComplex || {}\n const patches: any[] = []\n\n for (const key of BIDIRECTIONAL_BC_KEYS) {\n const raw = bc[key]\n if (raw === null || raw === undefined || raw === '') continue\n const metric = metricByProjectKey(metrics, key)\n if (!metric) continue\n patches.push(buildPatch(metric, Number(raw), findExistingRow(existing, metric), project.id))\n }\n\n // 계획공사기간 — 프로젝트 착공~준공 일정 → KPI 단방향.\n // calcDateDiff 는 strict 'YYYY-MM-DD' 파싱이라 DB 가 ISO datetime 을 주면 0 이 되므로\n // 여기서는 lenient moment 파싱으로 일수를 구해 개월(올림)로 환산한다.\n if (project.startDate && project.endDate) {\n const start = moment(project.startDate)\n const end = moment(project.endDate)\n const metric = metricByProjectKey(metrics, 'constructionPeriod')\n if (metric && start.isValid() && end.isValid()) {\n // 개월 = 일수/30, 소수점 한자리 반올림 (예: 373일 → 12.4개월)\n const months = Math.max(0, Math.round((end.diff(start, 'days') / 30) * 10) / 10)\n patches.push(buildPatch(metric, months, findExistingRow(existing, metric), project.id))\n }\n }\n\n if (patches.length) {\n await updateProjectCompleteStep1(patches, projectCode)\n }\n}\n\n/**\n * KPI 정보입력 저장 → 프로젝트 정보(BuildingComplex) 반영. (양방향 4필드만)\n * constructionPeriod 는 일방향이므로 write-back 대상에서 제외.\n * kpiMetrics / kpiMetricValues 는 KPI 화면이 이미 로드한 것을 그대로 사용.\n */\nexport async function syncKpiToProjectInfo(\n project: any,\n kpiMetrics: any[],\n kpiMetricValues: any[],\n projectCode?: string\n): Promise<void> {\n const bcId = project?.buildingComplex?.id\n if (!bcId) return\n\n const patch: Record<string, number> = {}\n for (const key of BIDIRECTIONAL_BC_KEYS) {\n const metric = metricByProjectKey(kpiMetrics, key)\n if (!metric) continue\n const row = findExistingRow(kpiMetricValues, metric)\n if (!row || row.value === undefined || row.value === null) continue\n const num = Number(row.value)\n patch[key] = BC_INT_KEYS.has(key) ? Math.round(num) : num\n }\n\n if (Object.keys(patch).length) {\n await updateBuildingComplexFields(bcId, patch, projectCode)\n }\n}\n"]}