diff --git a/apps/as/docs/pwrk-cnsltn/development-guide.md b/apps/as/docs/pwrk-cnsltn/development-guide.md index 6812c3db1..08193cd83 100644 --- a/apps/as/docs/pwrk-cnsltn/development-guide.md +++ b/apps/as/docs/pwrk-cnsltn/development-guide.md @@ -10,7 +10,7 @@ > > - 프로세스·상태·버튼: `03.재원협의_실무협의_프로세스_통합_v완결.md` > - REST 설계: `05.재원협의_상태별_endpoint_설계.md` -> - 공통코드: `FNRSSTTS` (`SV, ST, LE, FV, ER, SL, AC, TE, TP, RM, RB, RC, RA, AV, RK, RR, EP, TA`) +> - 공통코드: `FNRSSTTS` (`SV, ST, LE, FV, ER, SL, AC, TE, TP, RM, RB, RC, RA, AV, EV, RK, RR, EP, TA`) > > **백엔드 서비스**: > @@ -40,7 +40,7 @@ v1.0은 공단 접수자의 **자료검증 → 초안생성 → 웹기안기** | [ref-screen-buttons.md](./ref-screen-buttons.md) | 38행 화면·역할·버튼 매트릭스, 버튼 가시성 | `button-config.ts` 개편 | | [ref-endpoint.md](./ref-endpoint.md) | 버튼→API 총괄, 단계별 입출력, FE 호출 금지 API, Gap Analysis | 엔티티·service 매핑 | | [ref-frontend-entities.md](./ref-frontend-entities.md) | ENDPOINT, 슬라이스, Service 시그니처, 폴링, 웹기안기, DTO | 엔티티 레이어 구현 | -| [ref-webhwp-api.md](./ref-webhwp-api.md) | 웹한글기안기 v3 `HwpCtrl` Properties/Methods, Open/SaveAs | 기안기 위젯 연동 | +| [ref-webhwp-api.md](./ref-webhwp-api.md) | 웹한글기안기 v3 `HwpCtrl` Properties/Methods, Open/SaveAs | 기안기 위젯 연동 | --- @@ -98,7 +98,7 @@ v1.0은 공단 접수자의 **자료검증 → 초안생성 → 웹기안기** 정방향: ``` -SV → ST → LE → FV → ER → AC → TE → TP → RM → RB → RC → RA → AV → RK → RR → EP → TA +SV → ST → LE → FV → ER → AC → TE → TP → RM → RB → RC → RA → AV → EV → RK → RR → EP → TA ``` 자체종결: `LE → EP → TA` @@ -119,7 +119,8 @@ SV → ST → LE → FV → ER → AC → TE → TP → RM → RB → RC → RA | `RB` | 지자체회신 | 기초 실무협의서 관리 / 조치완료 | | `RC` | 조치결과서 제출 | 광역 → 환경청 `RA` | | `RA` | 조치결과서제출(환경청) | 환경청 2차 의뢰 `AV` | -| `AV` | 금액검증 | AI 진행 표시. **30분** 후 `RK` Fallback | +| `AV` | 금액검증 | AI 진행 표시. **30분** 후 `EV` Fallback | +| `EV` | 조치결과검토의뢰 | 공단 접수자: 2차 결재선 검토자지정 → `RK` | | `RK` | 조치결과서검토 | 웹기안기·금액검증 재실행·보완. 결재선 2차 | | `RR` | 조치결과서검토 회신 | 환경청 → 광역 `EP` | | `EP` | 검토결과 회신 | 광역 → 기초 `TA` | @@ -144,7 +145,7 @@ SV → ST → LE → FV → ER → AC → TE → TP → RM → RB → RC → RA 6. **진행상태만 바꾸는 버튼은** `POST /fnrs-cnsltn-mng/doc/updatePrgrsStts`**를 재사용한다.** (`ST→LE`, `RM→RB`, `RC→RA`, `RR→EP`, `EP→TA`) 7. **보완요청/보완완료는 공통 API** `reqsupplement` / `submitsupplement`. 복귀 상태(`ER`/`TE`/`RK`)는 서버가 이력에서 결정한다. 프론트는 직전 상태를 추측해 보내지 않는다. 8. **결재 승인/회신은** `PUT /elapr-prgrs-stts/{id}/process` **하나**. 1차 최종승인자 → `TP`, 2차 → `RR`은 서버가 Feign으로 처리한다. -9. **2차 결재선 복사는 백엔드**. `POST /elapr-apln/copy-line`은 `AV→RK` 콜백에서 호출한다. 프론트는 `RK`에서 2차 결재선을 **조회·표시**만 한다. +9. **2차 결재선은 EV에서 수동 검토자지정**. `POST /elapr-dmnd` (`elaprCycl: 2`, `docPrgrsSttsComCd: RK`). 1차 결재선 자동 copy-line은 사용하지 않는다. 10. **웹기안기는 `webhwp/open` 응답을 axios `blob`으로 조회한 뒤 `File`로 감싸 `HwpCtrl.Open`에 전달한다.** `blob:` Object URL과 `orgnlOtptDocCn`은 쓰지 않는다. --- @@ -189,6 +190,7 @@ BEGIN ON [파일보완] (ER, ATRZ01) → GET file-vrfc-results → 팝업 → reqsupplement ON [파일검증] (ER, ATRZ01) → dcrag-to transfer (상태 유지) ON [검토자지정] (ER, ATRZ01) → POST elapr-dmnd (docPrgrsSttsComCd: AC) → START POLL AC + ON [검토자지정] (EV, ATRZ01) → POST elapr-dmnd (elaprCycl: 2, docPrgrsSttsComCd: RK) ON [실무협의서관리] (TE|RK) → GET webhwp/open (blob) → HwpCtrl.Open(File) ON [실무협의서생성] (TE, 실패/타임아웃) → dcrag-to draft/generate ON [검토결과조회] (TE) → pwrk-cnsltn-mng findById | findByUpDocNo @@ -212,36 +214,37 @@ END 프론트가 치는 것만. 콜백·스케줄러는 제외. -| 주체 | 버튼 | From → To | Method & URI | -| --------- | -------------------------- | ----------------------------- | --------------------------------------------------------------- | --- | ------------------------------------------------ | -| 기초 | 저장 / 신청완료 | →`SV` / `SV→ST` | `POST /fnrs-cnsltn-mng/save-apply` | -| 기초 | 삭제 | `SV` | `DELETE /fnrs-cnsltn-mng/{docNo}` | -| 광역 | 환경청제출 | `ST→LE` | `POST /fnrs-cnsltn-mng/doc/updatePrgrsStts` | -| 환경청 | 결과회신(자체종결) | `LE→EP` | `POST /fnrs-cnsltn-mng/{docNo}/tech-review` | -| 환경청 | 기술검토 의뢰 | `LE→FV` | **신규** `POST /fnrs-cnsltn-mng/{docNo}/request-examine` | -| 공단 접수 | 파일보완 팝업 | `ER` | `GET /fnrs-cnsltn-mng/{docNo}/file-vrfc-results` | -| 공단 접수 | 파일보완요청 | `ER→SL` | `POST /fnrs-cnsltn-mng/{docNo}/reqsupplement` | -| 공단 접수 | 파일검증 재실행 | `ER` 유지 | `POST /dcrag-to/documents/transfer` | -| 공단 접수 | 검토자지정 | `ER→AC` | `POST /elapr-dmnd` | -| 공단 검토 | 실무협의서관리 | `TE`/`RK` | `GET .../pwrk-cnsltn-doc-crt-rslt/{uuid}/webhwp/open` | -| 공단 검토 | 실무협의서생성 | `TE` 유지 | `POST /dcrag-to/documents/draft/generate` | -| 공단 검토 | 저장 | `TE`/`RK` | `POST /pwrk-cnsltn-mng/save-apply` | -| 공단 | 검토완료 / 결재(승인·회신) | `TE` 유지 / `TE→TP` / `RK→RR` | `PUT /elapr-prgrs-stts/{id}/process` | -| 공단 검토 | 보완요청 | `TE→SL` / `RK→SL` | `POST /fnrs-cnsltn-mng/{docNo}/reqsupplement` | -| 기초 | 파일보완완료 / 보완완료 | `SL→ER | TE | RK` | `POST /fnrs-cnsltn-mng/{docNo}/submitsupplement` | -| 환경청 | 재원협의 결과 회신 | `TP→RM` | `POST /fnrs-cnsltn-mng/{docNo}/tech-review` | -| 광역 | 지자체회신 | `RM→RB` | `updatePrgrsStts` | -| 기초 | 실무협의서 관리 | `RB` 유지 | `GET .../pwrk-cnsltn-doc-crt-rslt/{uuid}/webhwp/open` | +| 주체 | 버튼 | From → To | Method & URI | +| --------- | -------------------------- | ----------------------------- | ---------------------------------------------------------------------------------- | +| 기초 | 저장 / 신청완료 | →`SV` / `SV→ST` | `POST /fnrs-cnsltn-mng/save-apply` | +| 기초 | 삭제 | `SV` | `DELETE /fnrs-cnsltn-mng/{docNo}` | +| 광역 | 환경청제출 | `ST→LE` | `POST /fnrs-cnsltn-mng/doc/updatePrgrsStts` | +| 환경청 | 결과회신(자체종결) | `LE→EP` | `POST /fnrs-cnsltn-mng/{docNo}/tech-review` | +| 환경청 | 기술검토 의뢰 | `LE→FV` | **신규** `POST /fnrs-cnsltn-mng/{docNo}/request-examine` | +| 공단 접수 | 파일보완 팝업 | `ER` | `GET /fnrs-cnsltn-mng/{docNo}/file-vrfc-results` | +| 공단 접수 | 파일보완요청 | `ER→SL` | `POST /fnrs-cnsltn-mng/{docNo}/reqsupplement` | +| 공단 접수 | 파일검증 재실행 | `ER` 유지 | `POST /dcrag-to/documents/transfer` | +| 공단 접수 | 검토자지정 | `ER→AC` | `POST /elapr-dmnd` (`elaprCycl: 1`) | +| 공단 접수 | 검토자지정 | `EV→RK` | `POST /elapr-dmnd` (`elaprCycl: 2`) | +| 공단 검토 | 실무협의서관리 | `TE`/`RK` | `GET .../pwrk-cnsltn-doc-crt-rslt/{uuid}/webhwp/open` | +| 공단 검토 | 실무협의서생성 | `TE` 유지 | `POST /dcrag-to/documents/draft/generate` | +| 공단 검토 | 저장 | `TE`/`RK` | `POST /pwrk-cnsltn-mng/save-apply` | +| 공단 | 검토완료 / 결재(승인·회신) | `TE` 유지 / `TE→TP` / `RK→RR` | `PUT /elapr-prgrs-stts/{id}/process` | +| 공단 검토 | 보완요청 | `TE→SL` / `RK→SL` | `POST /fnrs-cnsltn-mng/{docNo}/reqsupplement` | +| 기초 | 파일보완완료 / 보완완료 | `SL→ER` / `TE` / `RK` | `POST /fnrs-cnsltn-mng/{docNo}/submitsupplement` | +| 환경청 | 재원협의 결과 회신 | `TP→RM` | `POST /fnrs-cnsltn-mng/{docNo}/tech-review` | +| 광역 | 지자체회신 | `RM→RB` | `updatePrgrsStts` | +| 기초 | 실무협의서 관리 | `RB` 유지 | `GET .../pwrk-cnsltn-doc-crt-rslt/{uuid}/webhwp/open` | | 기초 | 조치완료 | `RB→RA` | **신규** `POST /pwrk-cnsltn-mng/{pwrkDocNo}/submit-actn-result` (`isSubmit: true`) | -| 광역 | 조치결과서제출 | `RC→RA` | `updatePrgrsStts` | -| 환경청 | 실무협의서 관리 | `RA` 유지 (조회) | `GET .../pwrk-cnsltn-doc-crt-rslt/{uuid}/webhwp/open` | -| 환경청 | 조치결과 검토의뢰 | `RA→AV` | **신규** `POST /fnrs-cnsltn-mng/{docNo}/request-actn-review` | -| 공단 검토 | 금액검증 재실행 | `RK` 유지 | `POST /dcrag-to/documents/cost/verify` | -| 환경청 | 결과회신 | `RR→EP` | `updatePrgrsStts` | -| 광역 | 결과회신 | `EP→TA` | `updatePrgrsStts` | -| 웹기안기 | 저장 | 버전 +1 | `POST /pwrk-cnsltn-doc-crt-rslt` multipart | -| 공통 | 다운로드 | — | `GET /pwrk-cnsltn-doc-crt-rslt/{uuid}/download` | +| 광역 | 조치결과서제출 | `RC→RA` | `updatePrgrsStts` | +| 환경청 | 실무협의서 관리 | `RA` 유지 (조회) | `GET .../pwrk-cnsltn-doc-crt-rslt/{uuid}/webhwp/open` | +| 환경청 | 조치결과 검토의뢰 | `RA→AV` | **신규** `POST /fnrs-cnsltn-mng/{docNo}/request-actn-review` | +| 공단 검토 | 금액검증 재실행 | `RK` 유지 | `POST /dcrag-to/documents/cost/verify` | +| 환경청 | 결과회신 | `RR→EP` | `updatePrgrsStts` | +| 광역 | 결과회신 | `EP→TA` | `updatePrgrsStts` | +| 웹기안기 | 저장 | 버전 +1 | `POST /pwrk-cnsltn-doc-crt-rslt` multipart | +| 공통 | 다운로드 | — | `GET /pwrk-cnsltn-doc-crt-rslt/{uuid}/download` | -신규 4개: `request-examine`, `submit-actn-result`, `request-actn-review`, (백엔드) `elapr-apln/copy-line`. +신규 4개: `request-examine`, `submit-actn-result`, `request-actn-review`, EV 2차 `elapr-dmnd`. --- diff --git a/apps/as/docs/pwrk-cnsltn/ref-endpoint.md b/apps/as/docs/pwrk-cnsltn/ref-endpoint.md index d63d2d4de..712ae57d6 100644 --- a/apps/as/docs/pwrk-cnsltn/ref-endpoint.md +++ b/apps/as/docs/pwrk-cnsltn/ref-endpoint.md @@ -43,7 +43,8 @@ | 27 | 조치완료 | `RB→RA` | as | `POST /api/v1/pwrk-cnsltn-mng/{pwrkDocNo}/submit-actn-result` (`isSubmit=true`) | **신규** | | 28 | 조치결과서제출 | `RC→RA` | as | `updatePrgrsStts` | 기존 | | 29 | 조치결과 검토의뢰 | `RA→AV` | as | `POST /api/v1/fnrs-cnsltn-mng/{docNo}/request-actn-review` | **신규** | -| 30 | 금액검증 콜백 | `AV→RK` | as | `POST /api/v1/dcrag/cost/verify-result` | 서버 | +| 30 | 금액검증 콜백 | `AV→EV` | as | `POST /api/v1/dcrag/cost/verify-result` | 서버 | +| 30a | 검토자지정(2차) | `EV→RK` | elapr | `POST /api/v1/elapr-dmnd` (`elaprCycl=2`) | 기존 | | 31 | 실무협의서관리 | `RK` | as | `GET /pwrk-cnsltn-mng/{pwrkDocNo}` | 기존 | | 32 | 금액검증 재실행 | `RK` | as | `POST /api/v1/dcrag-to/documents/cost/verify` | 기존 | | 33 | 보완요청(금액) | `RK→SL` | as | `reqsupplement` | 기존 | @@ -212,7 +213,7 @@ v1.0 가이드의 “이 API를 프론트가 치면 안 된다”는 **폐기**. - `isSubmit: true` → `RB→RA` (조치완료) 응답: `string` (pwrkDocNo). DTO명 `PwrkActnResultSubmitDto`. -### 3-6. `RA → AV → RK` +### 3-6. `RA → AV → EV → RK` **2차 의뢰 (신규)** — `POST /fnrs-cnsltn-mng/{docNo}/request-actn-review` ```json @@ -223,7 +224,8 @@ v1.0 가이드의 “이 API를 프론트가 치면 안 된다”는 **폐기**. `RA→AV`. 응답: `string` (docNo). DTO명 `EnvoActnReviewReqDto`. `specialRqstCn`만 선택. -금액검증 콜백은 서버. 콜백 후 서버가 `copy-line` 호출. +금액검증 콜백은 서버 → `AV→EV`. +**2차 검토자지정** — `POST /elapr-dmnd` (`elaprCycl: 2`, `docPrgrsSttsComCd: RK`). 1차 결재선(`findDocPrgrsByDocNo`, cycl=1)을 시드로 편집. **금액검증 재실행** — `POST /dcrag-to/documents/cost/verify` @@ -292,7 +294,7 @@ multipart `Content-Type` 헤더를 수동 지정하지 않는다 (boundary). JSO } ``` -`RK`에서 2차 결재선은 기존 `findDocPrgrsByDocNo` / `elaprCycl=2` 조회로 표시. +`RK`에서 결재현황은 `findDocPrgrsByDocNo`(전체) 조회 후 `elaprCycl`로 1차/2차 탭 표시. --- diff --git a/apps/as/docs/pwrk-cnsltn/ref-frontend-entities.md b/apps/as/docs/pwrk-cnsltn/ref-frontend-entities.md index e63531842..fdb817e08 100644 --- a/apps/as/docs/pwrk-cnsltn/ref-frontend-entities.md +++ b/apps/as/docs/pwrk-cnsltn/ref-frontend-entities.md @@ -343,5 +343,8 @@ docPrgrsSttsComCd: data.detDto?.prgrsSttsComCd, // ER To-Be: -- 검토자지정은 **`ER`에서만**. `elaprCycl: 1`, `docPrgrsSttsComCd: "AC"` -- 2차 결재선은 서버 `copy-line`. `RK`에서 검토자지정 팝업을 다시 열지 않음 +- 검토자지정은 **`ER`(1차)** 과 **`EV`(2차)** 에서. +- `ER`: `elaprCycl: 1`, `docPrgrsSttsComCd: "AC"`, 서식 선택 다이얼로그 +- `EV`: `elaprCycl: 2`, `docPrgrsSttsComCd: "RK"`, 1차 결재선 시드 다이얼로그(서식 없음) + +`RK` / ATRZ02 FAB: `findDocPrgrsByDocNo` 전체 조회 후 pending은 `elaprCycl === 2`만 사용. 검토완료 후 `ElaprApprovalDialog`에 `elaprCycl={2}`. 보완요청은 TE와 동일 `SplmntDmndDialog`. diff --git a/apps/as/docs/pwrk-cnsltn/ref-process-status.md b/apps/as/docs/pwrk-cnsltn/ref-process-status.md index e5da682d3..d5d55171b 100644 --- a/apps/as/docs/pwrk-cnsltn/ref-process-status.md +++ b/apps/as/docs/pwrk-cnsltn/ref-process-status.md @@ -16,7 +16,7 @@ 2-Pass: - **Pass 1** 기술 타당성: `SV→ST→LE→FV→ER→AC→TE→TP→RM→RB` -- **Pass 2** 조치결과·금액: `RB→RA→AV→RK→RR→EP→TA` +- **Pass 2** 조치결과·금액: `RB→RA→AV→EV→RK→RR→EP→TA` 자체종결(기술검토 생략): `LE→EP→TA` @@ -80,9 +80,9 @@ sequenceDiagram rect rgb(240, 255, 255) 광역->>환경청: [조치결과서제출] RC → RA 환경청->>AI: [조치결과 검토의뢰] RA → AV - AI-->>공단: 금액검증 콜백 AV → RK - Note over 공단: 30분 미응답 시 RK + 수동 [금액검증] - Note over 공단: AV→RK 시 1차 결재선 → 2차 복사 (서버) + AI-->>공단: 금액검증 콜백 AV → EV + 공단->>공단: [검토자지정] EV → RK + Note over 공단: EV에서 1차 결재선 시드로 2차 결재선 수동 배정 end rect rgb(255, 255, 230) @@ -125,7 +125,8 @@ sequenceDiagram | 9 | 검토결과 회신 | 환경청·광역 | `TP→RM→RB` | 1차 회신. `EP`와 혼동 금지 | | 10 | 조치결과 작성 | 기초 | `RB→RA` | `submit-actn-result` `isSubmit:true` (조치완료) | | 11 | 조치 의뢰 | 환경청 | `RA→AV` | `request-actn-review` | -| 12 | AI 금액검증 | AI·공단 | `AV→RK` | 결재선 복사(서버) | +| 12 | AI 금액검증 | AI·공단 | `AV→EV` | 콜백/Fallback 후 접수자 대기 | +| 12a | 2차 검토자지정 | 공단 접수 | `EV→RK` | `elapr-dmnd` cycl=2 | | 13 | 2차 검토·결재 | 공단 | `RK→RR` / `SL` | 2차 결재선 | | 14 | 최종 회신 | 환경청·광역·기초 | `RR→EP→TA` | 종결 | @@ -151,7 +152,8 @@ sequenceDiagram | `RC` | 조치결과서 제출 | Result Complete | 기초→광역 | `RB→RC` | | `RA` | 조치결과서 제출(환경청) | Result Ask to Basin | 기초→환경청 | `RB→RA` | | `AV` | 금액검증 | Amount Verification | AI | `RA→AV` | -| `RK` | 조치결과서검토 | Result Check | 공단 2차 결재 | `AV→RK`, `SL→RK` | +| `EV` | 조치결과검토의뢰 | Examine Verify | 공단 접수자 | `AV→EV` | +| `RK` | 조치결과서검토 | Result Check | 공단 2차 결재 | `EV→RK`, `SL→RK` | | `RR` | 조치결과서검토 회신 | Result Return to Basin | 공단→환경청 | `RK→RR` | | `EP` | 검토결과 회신 | Examine Pass to Province | 환경청→광역 | `LE→EP`(자체), `RR→EP`(최종) | | `TA` | 검토결과 회신(종료) | Technical Approval | 광역→기초 | `EP→TA` | @@ -200,7 +202,7 @@ stateDiagram-v2 | --- | --- | --- | --- | --- | --- | | AI-1 | 파일 무결성 | `FV→ER` | 환경청 [기술검토 의뢰] | 30분 → `ER` + [파일검증] | `POST /dcrag-to/documents/transfer` | | AI-2 | 초안 생성 | `AC→TE` | 공단 [검토자지정] | 30분 → `TE` + [실무협의서생성] | `POST /dcrag-to/documents/draft/generate` | -| AI-3 | 금액 검증 | `AV→RK` | 환경청 [조치결과 검토의뢰] | 30분 → `RK` + [금액검증] | `POST /dcrag-to/documents/cost/verify` | +| AI-3 | 금액 검증 | `AV→EV` | 환경청 [조치결과 검토의뢰] | 30분 → `EV` + 수동 [금액검증] | `POST /dcrag-to/documents/cost/verify` | - 예상 소요 안내는 **30분** (v1.0의 10분은 폐기). - 콜백 수신·스케줄러 전이는 서버. FE는 상세 refetch / draft status 폴링만. @@ -221,14 +223,12 @@ stateDiagram-v2 --- -## 7. 결재선 복사 - -규칙: `"금액검증후: 2차결재선 1차결재선 복사생성"` +## 7. 2차 결재선 (수동 검토자지정) 1. `TE`에서 1차 결재선(`elaprCycl = 1`) 완료. -2. `AV→RK` 콜백 시 서버가 `POST /elapr-apln/copy-line` (`source=1`, `target=2`). -3. `RK` 화면은 2차 결재선을 조회해 검토자/승인자/최종승인자를 표시. -4. 프론트는 copy-line을 **호출하지 않는다.** +2. 금액검증 콜백/Fallback → `AV→EV`. +3. 공단 접수자가 `EV`에서 [검토자지정]: 1차 결재선을 시드로 편집 후 `POST /elapr-dmnd` (`elaprCycl: 2`) → `RK`. +4. `copy-line` 자동 복사는 사용하지 않는다. 결재 `process`: diff --git a/apps/as/docs/pwrk-cnsltn/ref-screen-buttons.md b/apps/as/docs/pwrk-cnsltn/ref-screen-buttons.md index a500fad11..9fb11d306 100644 --- a/apps/as/docs/pwrk-cnsltn/ref-screen-buttons.md +++ b/apps/as/docs/pwrk-cnsltn/ref-screen-buttons.md @@ -62,16 +62,17 @@ API 컬럼은 [ref-endpoint.md](./ref-endpoint.md)와 동일. | 27 | 상세 | USER04 | - | `RC` | 조치결과서제출 | `RA` | 환경청 | `updatePrgrsStts` | | 28 | 상세 | USER03 | - | `RA` | 실무협의서 관리 | 유지 | — | 웹기안기 Open. **저장 불가** | | 28a | 상세 | USER03 | - | `RA` | 조치결과 검토의뢰 | `AV` | 공단 검토 | **`request-actn-review`** | -| 29 | 자동화 | AI | - | `AV` | (금액검증) | `RK` | 공단 검토 | FE 호출 없음. 진행 패널 | +| 29 | 자동화 | AI | - | `AV` | (금액검증) | `EV` | 공단 접수 | FE 호출 없음. 진행 패널 | +| 29a | 상세 | USER01 | 접수자 | `EV` | 검토자지정 | `RK` | 공단 검토 | `POST /elapr-dmnd` cycl=2. 1차 결재선 시드 | | 30 | 상세 | USER01 | 검토자 | `RK` | 실무협의서관리 | 유지 | — | 웹기안기 | | 31 | 상세 | USER01 | 검토자 | `RK` | 금액검증 | `RK` | — | `dcrag-to/cost/verify` | | 32 | 상세 | USER01 | 검토자 | `RK` | 저장 | `RK` | — | `pwrk save-apply` | | 33 | 상세 | USER01 | 검토자 | `RK` | 검토완료 | `RK` | 다음 결재자 | `process` | | 34 | 상세 | USER01 | 승인자 | `RK` | 결재(승인) | `RK` | 다음 결재자 | `process` | | 35 | 상세 | USER01 | 최종승인자 | `RK` | 결재(회신) | `RR` | 환경청 | `process` 최종 | -| 36 | 상세 | USER03 | - | `RR` | 결과회신 | `EP` | 광역 | `updatePrgrsStts` | -| 37 | 작성/상세 | USER04 | - | `EP` | 결과회신 | `TA` | 기초 | `updatePrgrsStts` | -| 38 | 상세 | USER05 | - | `TA` | 결과회신 | — | — | **API 없음. 조회** | +| 36 | 상세 | USER03 | - | `RR` | 실무협의서 관리(조회), 검토결과 회신 | `EP` | 광역 | `tech-review` | +| 37 | 작성/상세 | USER04 | - | `EP` | 결과회신, 실무협의서 관리(조회) | `TA` | 기초 | `updatePrgrsStts` | +| 38 | 상세 | USER05 | - | `TA` | 결과회신, 실무협의서 관리(조회) | — | — | **API 없음. 조회** | `SL` 20~22행: 버튼은 직전 상태에 따라 **하나만** 노출. 파일 루프면 [파일보완완료], 그 외 [보완완료]. @@ -85,7 +86,7 @@ API 컬럼은 [ref-endpoint.md](./ref-endpoint.md)와 동일. | --- | --- | --- | | `fileSplmnt` | 파일보완 | `ER` 접수자. 현행에 없음 | | `fileVrfc` | 파일검증 | 유지. `ER` 재실행 | -| `dsgntRvwr` | 검토자지정 | 유지. 전이를 `AC`로 | +| `dsgntRvwr` | 검토자지정 | `ER→AC`(1차), `EV→RK`(2차) | | `pwrkCnsltnDocMng` | 실무협의서관리 | 유지. `TE`·`RK`·`RB`·`RA`(조회 전용) | | `pwrkCnsltnDocCrt` | 실무협의서생성 | 유지. 실패/타임아웃만 | | `rvwRsltInq` | 검토결과조회 | 신규 | @@ -108,19 +109,19 @@ ROLE_USER05 "" | SV → 저장, 신청완료, 삭제 SL → (직전 ER) 파일보완완료 | (직전 TE|RK) 보완완료 RB → 실무협의서 관리, 조치완료 - TA → 결과회신 (조회) + TA → 결과회신 (조회), 실무협의서 관리(조회) ROLE_USER04 ST → 환경청제출 // 현행 반려(rjct→SV)는 SoT 38행에 없음. 유지 시 별도 합의 RM → 지자체회신 RC → 조치결과서제출 - EP → 결과회신 + EP → 결과회신, 실무협의서 관리(조회) ROLE_USER03 LE → 결과회신(자체종결), 기술검토 의뢰 TP → 재원협의 결과 회신 RA → 실무협의서 관리(조회), 조치결과 검토의뢰 - RR → 결과회신 + RR → 실무협의서 관리(조회), 검토결과 회신 (`tech-review` → `EP`) ``` ### 3-2. 공단 결재선 × 상태 @@ -129,6 +130,9 @@ ROLE_USER03 ER / ATRZ01 접수자 → 파일보완, 파일검증, 검토자지정 +EV / ATRZ01 접수자 + → 검토자지정 (2차 결재선. 1차 결재선 시드, 서식 선택 없음) + TE / ATRZ02 검토자 → (초안 성공) 실무협의서관리, 검토결과조회, 저장, 검토완료, 보완요청 → (초안 실패·타임아웃) 실무협의서생성 (+ 위 항목 중 관리/조회는 초안 생긴 뒤) @@ -137,14 +141,18 @@ TE / ATRZ03 승인자 → 결재(승인) TE / ATRZ99 최종 → 결재(회신) RK / ATRZ02 검토자 - → 실무협의서관리, 금액검증, 저장, 검토완료, 보완요청 -RK / ATRZ03 → 결재(승인) -RK / ATRZ99 → 결재(회신) + → (초안 완료) 실무협의서관리 + → (초안 완료 + 결재 대기 ATRZ_PRGRS_002, cycl2) 보완요청, 검토완료 + · 보완요청 UI: TE와 동일 SplmntDmndDialog +RK / ATRZ03 → (초안 완료) 실무협의서관리, (cycl2 pending) 결재(승인) +RK / ATRZ99 → (초안 완료) 실무협의서관리, (cycl2 pending) 결재(회신) + · 결재 UI: ElaprApprovalDialog (elaprCycl=2) 승인/반려 → PUT process ``` `FV`, `AC`, `AV`: 액션 빈 배열 + 진행 패널. -현행 `TE`에서 `hasPendingApprovalRequest === false`이면 버튼을 숨긴다. v3.0에서는 검토자 버튼(저장·웹기안기·보완)은 결재 대기와 무관하게 두고, **검토완료/결재**만 결재건 존재 여부에 묶는다. +`PENDING_APPROVAL_ACTIONS`: `dmndSplmnt`, `rvwCmptn`, `atrzAprv`, `atrzRply`만. +`pwrkCnsltnDocMng`는 결재 대기와 무관(초안 완료만). ATRZ02·ATRZ03·ATRZ99라도 pending 없으면 관리만 노출. --- diff --git a/apps/as/package.json b/apps/as/package.json index 075300898..40aa693d4 100644 --- a/apps/as/package.json +++ b/apps/as/package.json @@ -43,6 +43,7 @@ }, "devDependencies": { "@eslint/eslintrc": "^3", + "@hookform/devtools": "^4.4.0", "@types/js-cookie": "^3.0.6", "eslint": "^9.0.0", "eslint-config-next": "15.3.8", diff --git a/apps/as/public/webhwpctrl/hwpsdkmain.html b/apps/as/public/webhwpctrl/hwpsdkmain.html index d24c7eee7..3f4bc1526 100644 --- a/apps/as/public/webhwpctrl/hwpsdkmain.html +++ b/apps/as/public/webhwpctrl/hwpsdkmain.html @@ -6,36 +6,57 @@ - - - - Hwp SDK Wasm Sample - - - - - - - {aplnQuery.isPending && elaprTmpltId ? ( + {isLoading ? (
- ) : !elaprTmpltId ? ( + ) : !isReady ? (

결재서식을 선택하세요.

- ) : visibleRows.length === 0 ? ( -

+ ) : excludedVisibleRows.length === 0 ? ( +

등록된 결재선이 없습니다. [결재선 추가]로 결재자를 등록하세요.

) : ( @@ -161,10 +175,10 @@ function ElaprAplnPickerGrid({ r.rowKey)} + items={excludedVisibleRows.map((r) => r.rowKey)} strategy={verticalListSortingStrategy} > - {visibleRows.map((row, index) => ( + {excludedVisibleRows.map((row, index) => ( void; elaprDocNo: string; currentUserUuid: string; + elaprCycl?: number; onSuccess: () => void; } @@ -58,6 +59,7 @@ function ElaprApprovalDialog({ onOpenChange, elaprDocNo, currentUserUuid, + elaprCycl, onSuccess, }: ElaprApprovalDialogProps) { const queryClient = useQueryClient(); @@ -67,9 +69,16 @@ function ElaprApprovalDialog({ const [pickerOpen, setPickerOpen] = useState(false); const [pickingRowKey, setPickingRowKey] = useState(null); - const { data: elaprDocPrgrsList, isLoading } = useElaprDocPrgrsByDocNo(open ? elaprDocNo : null); + const { data: elaprDocPrgrsList, isLoading } = useElaprDocPrgrsByDocNo(open ? elaprDocNo : null, { + elaprCycl, + structuralSharing: false, + }); + const cyclDocPrgrsList = useMemo(() => { + if (elaprCycl == null) return elaprDocPrgrsList; + return elaprDocPrgrsList?.filter((row) => Number(row.elaprCycl) === elaprCycl); + }, [elaprCycl, elaprDocPrgrsList]); - const elaprDmndId = elaprDocPrgrsList?.[0]?.elaprDmndId; + const elaprDmndId = cyclDocPrgrsList?.[0]?.elaprDmndId; const { rows, @@ -85,22 +94,19 @@ function ElaprApprovalDialog({ markDeleted, reorderRows, } = usePrgrsSttsRows({ - serverRows: elaprDocPrgrsList, + serverRows: cyclDocPrgrsList, elaprDmndId, }); const savePrgrsSttsMutation = useSaveElaprPrgrsStts(); const sensors = useDndSensors(); - const currentUserPrgrs = findCurrentUserPendingApprovalRequest( - elaprDocPrgrsList, - currentUserUuid, - ); + const currentUserPrgrs = findCurrentUserPendingApprovalRequest(cyclDocPrgrsList, currentUserUuid); const canApprove = Boolean(currentUserPrgrs?.elaprPrgrsId); const pickingRow = pickingRowKey ? rows.find((r) => r.rowKey === pickingRowKey) : undefined; const isBusy = isProcessing || savePrgrsSttsMutation.isPending; - const isPrgrsSttsUnsaved = hasUnsavedPrgrsSttsChanges(rows, elaprDocPrgrsList ?? []); + const isPrgrsSttsUnsaved = hasUnsavedPrgrsSttsChanges(rows, cyclDocPrgrsList ?? []); function handleDragEnd(event: DragEndEvent) { const { active, over } = event; @@ -164,7 +170,7 @@ function ElaprApprovalDialog({ return; } - const payload = buildPrgrsSttsSavePayload(rows, elaprDmndId, elaprDocPrgrsList ?? []); + const payload = buildPrgrsSttsSavePayload(rows, elaprDmndId, cyclDocPrgrsList ?? []); if (payload.length === 0) { alert("변경된 결재선이 없습니다."); return; diff --git a/apps/as/src/entities/elapr/ui/elapr-doc-prgrs-picker-dialog.tsx b/apps/as/src/entities/elapr/ui/elapr-doc-prgrs-picker-dialog.tsx new file mode 100644 index 000000000..0d9b5161e --- /dev/null +++ b/apps/as/src/entities/elapr/ui/elapr-doc-prgrs-picker-dialog.tsx @@ -0,0 +1,129 @@ +"use client"; + +import { useCallback, useMemo, useState } from "react"; + +import { useAlertStore } from "@/shared/lib"; +import { + Button, + Dialog, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@common/components"; + +import { useElaprDocPrgrsByDocNo } from "../api/queries"; +import { + getVisibleRows, + validateAllRowsForSave, + validateAtrzOrder, + validateUniqueSeqNo, +} from "../lib/apln-validators"; +import { mapDocPrgrsToAplnRows } from "../lib/doc-prgrs-to-apln-rows"; +import type { ElaprAplnRow } from "../model/elapr-apln-row-types"; + +import { ElaprAplnPickerGrid } from "./elapr-apln-picker-grid"; + +interface ElaprDocPrgrsPickerConfirmPayload { + aplnRows: ElaprAplnRow[]; +} + +interface ElaprDocPrgrsPickerDialogProps { + open: boolean; + elaprDocNo: string; + onClose: () => void; + onConfirm?: (payload: ElaprDocPrgrsPickerConfirmPayload) => void; +} + +function ElaprDocPrgrsPickerDialog({ + open, + elaprDocNo, + onClose, + onConfirm, +}: ElaprDocPrgrsPickerDialogProps) { + const alert = useAlertStore((state) => state.setMessage); + const [aplnRows, setAplnRows] = useState([]); + const [isEditingLocked, setIsEditingLocked] = useState(false); + + const docPrgrsQuery = useElaprDocPrgrsByDocNo(elaprDocNo, { + elaprCycl: 1, + enabled: open && Boolean(elaprDocNo), + }); + + const externalServerRows = useMemo( + () => mapDocPrgrsToAplnRows(docPrgrsQuery.data), + [docPrgrsQuery.data], + ); + + const handleRowsChange = useCallback((rows: ElaprAplnRow[]) => { + setAplnRows(rows); + }, []); + + const handleEditingLockedChange = useCallback((locked: boolean) => { + setIsEditingLocked(locked); + }, []); + + function handleOpenChange(isOpen: boolean) { + if (!isOpen) { + setAplnRows([]); + setIsEditingLocked(false); + onClose(); + } + } + + function handleConfirm() { + const rowsError = validateAllRowsForSave(aplnRows, isEditingLocked); + if (rowsError) { + alert(rowsError); + return; + } + + const orderError = validateAtrzOrder(aplnRows); + if (orderError) { + alert(orderError); + return; + } + + const seqError = validateUniqueSeqNo(aplnRows); + if (seqError) { + alert(seqError); + return; + } + + const visibleRows = getVisibleRows(aplnRows); + onConfirm?.({ aplnRows: visibleRows }); + + setAplnRows([]); + setIsEditingLocked(false); + onClose(); + } + + return ( + + + + 결재선 선택 + + + + + + + + + + + ); +} + +export { ElaprDocPrgrsPickerDialog }; +export type { ElaprDocPrgrsPickerConfirmPayload }; diff --git a/apps/as/src/entities/elapr/ui/elapr-doc-prgrs-status.tsx b/apps/as/src/entities/elapr/ui/elapr-doc-prgrs-status.tsx index 4be4cf0c3..ea23484e4 100644 --- a/apps/as/src/entities/elapr/ui/elapr-doc-prgrs-status.tsx +++ b/apps/as/src/entities/elapr/ui/elapr-doc-prgrs-status.tsx @@ -1,6 +1,6 @@ "use client"; -import { Fragment } from "react"; +import { Fragment, useEffect, useMemo, useState } from "react"; import { ChevronRight } from "lucide-react"; @@ -8,7 +8,7 @@ import { dayjs, useUserStore } from "@/shared/lib"; import type { ElaprDocPrgrsClientResponseDto } from "@/shared/model"; import { SectionCollapsible } from "@/shared/ui"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/shared/ui/tooltip"; -import { cn, Skeleton } from "@common/components"; +import { cn, Skeleton, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components"; import { useElaprDocPrgrsByDocNo } from "../api/queries"; import { getApproverStatus, sortByAtrzSeqNo } from "../lib/approval-utils"; @@ -67,10 +67,46 @@ function ElaprDocPrgrsStep({ item }: { item: ElaprDocPrgrsClientResponseDto }) { ); } +function ElaprDocPrgrsStepList({ steps }: { steps: ElaprDocPrgrsClientResponseDto[] }) { + if (steps.length === 0) { + return

결재선이 없습니다.

; + } + + return ( +
+ {steps.map((item, index) => ( + + {index > 0 && ( +
+ +
+ )} + +
+ ))} +
+ ); +} + function ElaprDocPrgrsStatus({ elaprDocNo }: ElaprDocPrgrsStatusProps) { const userInfo = useUserStore((state) => state.userInfo); const { data, isLoading } = useElaprDocPrgrsByDocNo(elaprDocNo); - const steps = sortByAtrzSeqNo(data ?? []); + const [activeCycl, setActiveCycl] = useState<"1" | "2">("1"); + + const { cycl1Steps, cycl2Steps, hasCycl2 } = useMemo(() => { + const rows = data ?? []; + const cycl1 = sortByAtrzSeqNo(rows.filter((item) => Number(item.elaprCycl ?? 1) !== 2)); + const cycl2 = sortByAtrzSeqNo(rows.filter((item) => Number(item.elaprCycl) === 2)); + return { + cycl1Steps: cycl1, + cycl2Steps: cycl2, + hasCycl2: cycl2.length > 0, + }; + }, [data]); + + useEffect(() => { + setActiveCycl(hasCycl2 ? "2" : "1"); + }, [hasCycl2]); if (userInfo?.dtlInstCd !== "ROLE_USER01") { return null; @@ -88,24 +124,40 @@ function ElaprDocPrgrsStatus({ elaprDocNo }: ElaprDocPrgrsStatusProps) { ); } - if (steps.length === 0) { + if (cycl1Steps.length === 0 && cycl2Steps.length === 0) { return null; } return ( -
- {steps.map((item, index) => ( - - {index > 0 && ( -
- {} -
- )} - -
- ))} -
+ {hasCycl2 ? ( + setActiveCycl(value as "1" | "2")} + className="p-2" + > + + + 재원협의 + + + 실무협의서 + + + +
+ +
+
+ +
+ +
+
+
+ ) : ( + + )}
); } diff --git a/apps/as/src/entities/fnrs-cnsltn/api/fnrs-cnsltn-service.ts b/apps/as/src/entities/fnrs-cnsltn/api/fnrs-cnsltn-service.ts index b48e5a059..ffac5eb4a 100644 --- a/apps/as/src/entities/fnrs-cnsltn/api/fnrs-cnsltn-service.ts +++ b/apps/as/src/entities/fnrs-cnsltn/api/fnrs-cnsltn-service.ts @@ -3,12 +3,14 @@ import { ENDPOINT } from "@/shared/config"; import type { PageResponse } from "@/shared/model"; import type { - DocPrgrsUpdateRequestDto, FnrsCnsltnApplySaveRequestDto, + FnrsCnsltnMngSaveRequestDto, +} from "../model/fnrs-cnsltn-apply-payload-schema"; +import type { + DocPrgrsUpdateRequestDto, FnrsCnsltnFileVrfcRsltResponseDto, FnrsCnsltnMngDetResponseDto, FnrsCnsltnMngResponseDto, - FnrsCnsltnMngSaveRequestDto, FnrsCnsltnMngSearchDto, FnrsCnsltnMngService, FnrsCnsltnMngUpdateRequestDto, diff --git a/apps/as/src/entities/fnrs-cnsltn/api/queries.ts b/apps/as/src/entities/fnrs-cnsltn/api/queries.ts index 357aa58c0..e136bd59f 100644 --- a/apps/as/src/entities/fnrs-cnsltn/api/queries.ts +++ b/apps/as/src/entities/fnrs-cnsltn/api/queries.ts @@ -1,9 +1,11 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import type { - DocPrgrsUpdateRequestDto, FnrsCnsltnApplySaveRequestDto, FnrsCnsltnMngSaveRequestDto, +} from "../model/fnrs-cnsltn-apply-payload-schema"; +import type { + DocPrgrsUpdateRequestDto, FnrsCnsltnMngSearchDto, FnrsCnsltnMngUpdateRequestDto, } from "../model/fnrs-cnsltn-interface"; diff --git a/apps/as/src/entities/fnrs-cnsltn/index.ts b/apps/as/src/entities/fnrs-cnsltn/index.ts index 31bc2f528..19941fb04 100644 --- a/apps/as/src/entities/fnrs-cnsltn/index.ts +++ b/apps/as/src/entities/fnrs-cnsltn/index.ts @@ -16,30 +16,44 @@ export { useUpdateFnrsCnsltnMng, } from "./api/queries"; export type { - AtflMngClientResponseDto, BizCstItem, - BizCstTreeDto, - BizSclTreeDto, BzScaleItem, CklstResultItem, + EsntlAtchFileItem, + FnrsCnsltnApplySaveRequestDto, + FnrsCnsltnMngSaveRequestDto, + FnrsCnsltnRvwOpnnSaveRequestDto, + FnrsCnsltnSplmntDmndItem, + FnrsSharItem, +} from "./model/fnrs-cnsltn-apply-payload-schema"; +export { + fnrsCnsltnApplySaveSchema, + fnrsCnsltnBizCstItemSchema, + fnrsCnsltnBzScaleItemSchema, + fnrsCnsltnCklstResultItemSchema, + fnrsCnsltnEsntlAtchFileItemSchema, + fnrsCnsltnMngSaveSchema, + fnrsCnsltnRvwOpnnSaveSchema, + fnrsCnsltnSplmntDmndItemSchema, + fnrsSharItemSchema, +} from "./model/fnrs-cnsltn-apply-payload-schema"; +export type { + AtflMngClientResponseDto, + BizCstTreeDto, + BizSclTreeDto, CklstRvwRsltTreeDto, DocPrgrsUpdateRequestDto, ElaprDocPrgrsClientResponseDto, - EsntlAtchFileItem, EsntlAtchFileMngTreeResponseDto, - FnrsCnsltnApplySaveRequestDto, FnrsCnsltnFileVrfcRsltResponseDto, FnrsCnsltnMngDetResponseDto, FnrsCnsltnMngResponseDto, - FnrsCnsltnMngSaveRequestDto, FnrsCnsltnMngSearchDto, FnrsCnsltnMngService, FnrsCnsltnMngUpdateRequestDto, FnrsCnsltnRvwOpnnResponseDto, - FnrsCnsltnRvwOpnnSaveRequestDto, - FnrsCnsltnSplmntDmndItem, FnrsCnsltnSplmntDmndResponseDto, - FnrsSharItem, + RagIndexingInfoDto, } from "./model/fnrs-cnsltn-interface"; export type { EnvoActnReviewReqDto, diff --git a/apps/as/src/entities/fnrs-cnsltn/model/fnrs-cnsltn-apply-payload-schema.ts b/apps/as/src/entities/fnrs-cnsltn/model/fnrs-cnsltn-apply-payload-schema.ts new file mode 100644 index 000000000..709ae9012 --- /dev/null +++ b/apps/as/src/entities/fnrs-cnsltn/model/fnrs-cnsltn-apply-payload-schema.ts @@ -0,0 +1,120 @@ +import { z } from "zod"; + +/** 백엔드 FnrsCnsltnMngSaveRequestDto */ +export const fnrsCnsltnMngSaveSchema = z.object({ + fnrsCnsltnDocNo: z.string().optional(), + admdstCd: z.string().optional(), + envoSeComCd: z.string().optional(), + taskSeComCd: z.string().optional(), + cfmtnBgtBizNo: z.string().optional(), + bgtAplyYr: z.string().optional(), + bizNm: z.string().optional(), + bizBgngYr: z.string().optional(), + bizEndYr: z.string().optional(), + bizCn: z.string().optional(), + aplcntNm: z.string().optional(), + aplcntMblTelno: z.string().optional(), + aplcntOgdpNm: z.string().optional(), + aplcntEmlAddr: z.string().optional(), + dsgnrNm: z.string().optional(), + dsgnrTelno: z.string().optional(), + dsgnrOgdpNm: z.string().optional(), + dsgnrEmlAddr: z.string().optional(), + atchFileGroupId: z.string().optional(), + sharAtchFileGroupId: z.string().optional(), + prgrsSttsComCd: z.string().optional(), + rtlztnFcltSeComCd: z.string().optional(), + rtlztnUsgSeComCd: z.string().optional(), +}); + +/** 백엔드 FnrsCnsltnApplySaveRequestDto.BzScaleItem */ +export const fnrsCnsltnBzScaleItemSchema = z.object({ + cnstpSeCd: z.string().optional(), + bscPl: z.number().optional(), + budgetReflct: z.number().optional(), + thtiDsgn: z.number().optional(), +}); + +/** 백엔드 FnrsCnsltnApplySaveRequestDto.BizCstItem */ +export const fnrsCnsltnBizCstItemSchema = z.object({ + acntgSeCd: z.string().optional(), + bscPlanAmt: z.number().optional(), + bgtRfltAmt: z.number().optional(), + tstmDesignAmt: z.number().optional(), +}); + +/** 백엔드 FnrsCnsltnApplySaveRequestDto.CklstResultItem */ +export const fnrsCnsltnCklstResultItemSchema = z.object({ + cklstRvwSn: z.number().optional(), + cklstArtclSn: z.number().optional(), + cklstSn: z.number().optional(), + rvwStpComCd: z.string().nullish(), + selected: z.enum(["Y", "N"]).nullish(), + picRvwCn: z.string().nullish(), +}); + +/** 백엔드 FnrsCnsltnApplySaveRequestDto.EsntlAtchFileItem */ +export const fnrsCnsltnEsntlAtchFileItemSchema = z.object({ + esntlAtchFileId: z.string().optional(), + atchFileGroupId: z.string().optional(), +}); + +/** 백엔드 FnrsCnsltnApplySaveRequestDto.FnrsCnsltnSplmntDmndItem */ +export const fnrsCnsltnSplmntDmndItemSchema = z.object({ + splmntDmndSn: z.number().nullish(), + splmntArtclComCd: z.string().nullish(), + splmntDmndCn: z.string().optional(), + splmntDmndDt: z.string().optional(), + splmntDmndAtchFileGroupId: z.string().optional(), + splmntCn: z.string().optional(), + splmntPicNm: z.string().optional(), + splmntCmptnDt: z.string().optional(), + splmntAtchFileGroupId: z.string().optional(), + cmptnYn: z.string().optional(), +}); + +/** 백엔드 FnrsCnsltnRvwOpnnSaveRequestDto */ +export const fnrsCnsltnRvwOpnnSaveSchema = z.object({ + fnrsCnsltnDocNo: z.string(), + rvwOpnnSn: z.number().optional(), + opnrSeComCd: z.string().optional(), + opnrNm: z.string().optional(), + rvwRsltCn: z.string().optional(), + rvwRsltAnmlyYn: z.string().optional(), + atchFileGroupId: z.string().optional(), +}); + +/** 백엔드 FnrsCnsltnApplySaveRequestDto.FnrsSharItem */ +export const fnrsSharItemSchema = z.object({ + fnrsSharSeCd: z.string().optional(), + exstnAmt: z.number().optional(), + chgRqtAmt: z.number().optional(), + ajmtAmt: z.number().optional(), + rmrkCn: z.string().optional(), + envoRmrkCn: z.string().optional(), +}); + +/** 백엔드 FnrsCnsltnApplySaveRequestDto */ +export const fnrsCnsltnApplySaveSchema = z.object({ + techReViewSaveCd: z.string().optional(), + rvwStpCd: z.string().optional(), + fnrsCnsltnMng: fnrsCnsltnMngSaveSchema, + bzScaleList: z.array(fnrsCnsltnBzScaleItemSchema).optional(), + bizCstList: z.array(fnrsCnsltnBizCstItemSchema).optional(), + cklstResultList: z.array(fnrsCnsltnCklstResultItemSchema).optional(), + cklstResultMap: z.record(z.array(fnrsCnsltnCklstResultItemSchema)).optional(), + esntlAtchFileList: z.array(fnrsCnsltnEsntlAtchFileItemSchema).optional(), + rvwOpnnList: z.array(fnrsCnsltnRvwOpnnSaveSchema).optional(), + splmntDmndList: z.array(fnrsCnsltnSplmntDmndItemSchema).optional(), + fnrsSharList: z.array(fnrsSharItemSchema).optional(), +}); + +export type FnrsCnsltnMngSaveRequestDto = z.infer; +export type BzScaleItem = z.infer; +export type BizCstItem = z.infer; +export type CklstResultItem = z.infer; +export type EsntlAtchFileItem = z.infer; +export type FnrsCnsltnSplmntDmndItem = z.infer; +export type FnrsCnsltnRvwOpnnSaveRequestDto = z.infer; +export type FnrsSharItem = z.infer; +export type FnrsCnsltnApplySaveRequestDto = z.infer; diff --git a/apps/as/src/entities/fnrs-cnsltn/model/fnrs-cnsltn-interface.ts b/apps/as/src/entities/fnrs-cnsltn/model/fnrs-cnsltn-interface.ts index 8e50d5d79..07d3983e9 100644 --- a/apps/as/src/entities/fnrs-cnsltn/model/fnrs-cnsltn-interface.ts +++ b/apps/as/src/entities/fnrs-cnsltn/model/fnrs-cnsltn-interface.ts @@ -1,21 +1,20 @@ import type { AtflMngClientResponseDto, - BizCstSaveBase, - BzScaleSaveBase, - CklstResultSaveBase, DocPrgrsUpdateRequestDto, - EsntlAtchFileSaveBase, PageableParams, PageResponse, - PbadmsApplySaveHeader, PbadmsMngDetResponseBase, RvwOpnnResponseExtension, - RvwOpnnSaveBase, SplmntDmndResponseExtension, - SplmntDmndSaveBase, } from "@/shared/model"; import type { FnrsSharBizCstTreeDto } from "@/entities/fnrs-shar-se"; +import type { + FnrsCnsltnApplySaveRequestDto, + FnrsCnsltnMngSaveRequestDto, + FnrsCnsltnRvwOpnnSaveRequestDto, + FnrsCnsltnSplmntDmndItem, +} from "./fnrs-cnsltn-apply-payload-schema"; import type { EnvoActnReviewReqDto, FnrsExamineRequestDto, @@ -31,33 +30,6 @@ export type { EsntlAtchFileMngTreeResponseDto, } from "@/shared/model"; -/** 백엔드 FnrsCnsltnMngSaveRequestDto */ -export interface FnrsCnsltnMngSaveRequestDto { - fnrsCnsltnDocNo?: string; - admdstCd?: string; - envoSeComCd?: string; - taskSeComCd?: string; - cfmtnBgtBizNo?: string; - bgtAplyYr?: string; - bizNm?: string; - bizBgngYr?: string; - bizEndYr?: string; - bizCn?: string; - aplcntNm?: string; - aplcntMblTelno?: string; - aplcntOgdpNm?: string; - aplcntEmlAddr?: string; - dsgnrNm?: string; - dsgnrTelno?: string; - dsgnrOgdpNm?: string; - dsgnrEmlAddr?: string; - atchFileGroupId?: string; - sharAtchFileGroupId?: string; - prgrsSttsComCd?: string; - rtlztnFcltSeComCd?: string; - rtlztnUsgSeComCd?: string; -} - /** 백엔드 FnrsCnsltnMngUpdateRequestDto */ export type FnrsCnsltnMngUpdateRequestDto = Omit; @@ -110,34 +82,6 @@ interface FnrsCnsltnMngResponseExtension { export type FnrsCnsltnMngResponseDto = Omit & FnrsCnsltnMngResponseExtension; -export type BzScaleItem = BzScaleSaveBase; - -export type BizCstItem = BizCstSaveBase; - -export interface CklstResultItem extends CklstResultSaveBase { - selected?: string; -} - -export type EsntlAtchFileItem = EsntlAtchFileSaveBase; - -export type FnrsCnsltnSplmntDmndItem = SplmntDmndSaveBase; - -/** 백엔드 FnrsCnsltnApplySaveRequestDto.FnrsSharItem */ -export interface FnrsSharItem { - fnrsSharSeCd?: string; - exstnAmt?: number; - chgRqtAmt?: number; - ajmtAmt?: number; - rmrkCn?: string; - envoRmrkCn?: string; -} - -/** 백엔드 FnrsCnsltnRvwOpnnSaveRequestDto */ -export interface FnrsCnsltnRvwOpnnSaveRequestDto extends RvwOpnnSaveBase { - fnrsCnsltnDocNo: string; - rvwOpnnSn: number; -} - /** 백엔드 FnrsCnsltnRvwOpnnResponseDto */ export type FnrsCnsltnRvwOpnnResponseDto = Partial< Pick @@ -151,6 +95,14 @@ export type FnrsCnsltnSplmntDmndResponseDto = FnrsCnsltnSplmntDmndItem & fnrsCnsltnDocNo?: string; }; +/** 백엔드 RagIndexingInfoDto */ +export interface RagIndexingInfoDto { + isInProgress: boolean; + elapsedSeconds?: number; + estimatedRemainingSeconds?: number; + message?: string; +} + /** 백엔드 FnrsCnsltnMngDetResponseDto */ export type FnrsCnsltnMngDetResponseDto = PbadmsMngDetResponseBase< FnrsCnsltnMngResponseDto, @@ -159,21 +111,9 @@ export type FnrsCnsltnMngDetResponseDto = PbadmsMngDetResponseBase< > & { fnrsSharList?: FnrsSharBizCstTreeDto[]; sharAtchFileList?: AtflMngClientResponseDto[]; + ragIndexingInfo?: RagIndexingInfoDto | null; }; -/** 백엔드 FnrsCnsltnApplySaveRequestDto */ -export interface FnrsCnsltnApplySaveRequestDto extends PbadmsApplySaveHeader { - fnrsCnsltnMng: FnrsCnsltnMngSaveRequestDto; - bzScaleList?: BzScaleItem[]; - bizCstList?: BizCstItem[]; - cklstResultList?: CklstResultItem[]; - cklstResultMap?: Record; - esntlAtchFileList?: EsntlAtchFileItem[]; - rvwOpnnList?: FnrsCnsltnRvwOpnnSaveRequestDto[]; - splmntDmndList?: FnrsCnsltnSplmntDmndItem[]; - fnrsSharList?: FnrsSharItem[]; -} - /** 백엔드 FnrsCnsltnFileVrfcRsltResponseDto */ export interface FnrsCnsltnFileVrfcRsltResponseDto { fileSeComCd?: string; diff --git a/apps/as/src/entities/instl-sncn/api/instl-sncn-service.ts b/apps/as/src/entities/instl-sncn/api/instl-sncn-service.ts index 2bef72628..4c442a3da 100644 --- a/apps/as/src/entities/instl-sncn/api/instl-sncn-service.ts +++ b/apps/as/src/entities/instl-sncn/api/instl-sncn-service.ts @@ -2,13 +2,15 @@ import { client } from "@/shared/api"; import { ENDPOINT } from "@/shared/config"; import type { PageResponse } from "@/shared/model"; +import type { + InstlSncnApplySaveRequestDto, + InstlSncnMngSaveRequestDto, +} from "../model/instl-sncn-apply-payload-schema"; import type { AdstZoneResponseDto, DocPrgrsUpdateRequestDto, - InstlSncnApplySaveRequestDto, InstlSncnMngDetResponse, InstlSncnMngResponseDto, - InstlSncnMngSaveRequestDto, InstlSncnMngSearchParams, InstlSncnMngService, InstlSncnMngUpdateRequestDto, diff --git a/apps/as/src/entities/instl-sncn/api/queries.ts b/apps/as/src/entities/instl-sncn/api/queries.ts index f9d94dbff..278277668 100644 --- a/apps/as/src/entities/instl-sncn/api/queries.ts +++ b/apps/as/src/entities/instl-sncn/api/queries.ts @@ -3,9 +3,11 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { ENDPOINT } from "@/shared/config"; import type { - DocPrgrsUpdateRequestDto, InstlSncnApplySaveRequestDto, InstlSncnMngSaveRequestDto, +} from "../model/instl-sncn-apply-payload-schema"; +import type { + DocPrgrsUpdateRequestDto, InstlSncnMngSearchParams, InstlSncnMngUpdateRequestDto, } from "../model/instl-sncn-interface"; diff --git a/apps/as/src/entities/instl-sncn/index.ts b/apps/as/src/entities/instl-sncn/index.ts index 946e756f1..5579b4c06 100644 --- a/apps/as/src/entities/instl-sncn/index.ts +++ b/apps/as/src/entities/instl-sncn/index.ts @@ -9,6 +9,26 @@ export { useInstlSncnSubmitSupplement, useInstlSncnTechReview, } from "./api/queries"; +export type { + InstlSncnApplySaveRequestDto, + InstlSncnBizCstSaveRequestDto, + InstlSncnBzScaleSaveDto, + InstlSncnCklstResultSaveDto, + InstlSncnEsntlAtchFileSaveDto, + InstlSncnMngSaveRequestDto, + InstlSncnRvwOpnnSaveRequestDto, + InstlSncnSplmntDmndSaveDto, +} from "./model/instl-sncn-apply-payload-schema"; +export { + instlSncnApplySaveSchema, + instlSncnBizCstItemSchema, + instlSncnBzScaleItemSchema, + instlSncnCklstResultItemSchema, + instlSncnEsntlAtchFileItemSchema, + instlSncnMngSaveSchema, + instlSncnRvwOpnnSaveSchema, + instlSncnSplmntDmndItemSchema, +} from "./model/instl-sncn-apply-payload-schema"; export { type AdstZoneResponseDto, type AtflMngClientResponseDto, @@ -19,18 +39,10 @@ export { type ElaprDocPrgrsClientResponseDto, type EsntlAtchFileMngTreeResponseDto, INSTL_SNCN_REGISTER_SELECTION_KEY, - type InstlSncnApplySaveRequestDto, - type InstlSncnBizCstSaveRequestDto, - type InstlSncnBzScaleSaveDto, - type InstlSncnCklstResultSaveDto, - type InstlSncnEsntlAtchFileSaveDto, type InstlSncnMngDetResponse, type InstlSncnMngResponseDto, - type InstlSncnMngSaveRequestDto, type InstlSncnMngSearchParams, type InstlSncnRvwOpnnResponseDto, - type InstlSncnRvwOpnnSaveRequestDto, type InstlSncnSplmntDmndResponseDto, - type InstlSncnSplmntDmndSaveDto, type ReviewStepCode, } from "./model/instl-sncn-interface"; diff --git a/apps/as/src/entities/instl-sncn/model/instl-sncn-apply-payload-schema.ts b/apps/as/src/entities/instl-sncn/model/instl-sncn-apply-payload-schema.ts new file mode 100644 index 000000000..8e6e17616 --- /dev/null +++ b/apps/as/src/entities/instl-sncn/model/instl-sncn-apply-payload-schema.ts @@ -0,0 +1,108 @@ +import { z } from "zod"; + +/** 백엔드 InstlSncnMngSaveRequestDto */ +export const instlSncnMngSaveSchema = z.object({ + instlSncnDocNo: z.string().optional(), + admdstCd: z.string().optional(), + envoSeComCd: z.string().optional(), + sncnSeComCd: z.string().optional(), + taskSeComCd: z.string().optional(), + bgtBizCd: z.string().optional(), + bgtAplyYr: z.string().optional(), + bizNm: z.string().optional(), + bizBgngYr: z.string().optional(), + bizEndYr: z.string().optional(), + bizCn: z.string().optional(), + aplcntNm: z.string().optional(), + aplcntMblTelno: z.string().optional(), + aplcntOgdpNm: z.string().optional(), + aplcntEmlAddr: z.string().optional(), + dsgnrNm: z.string().optional(), + dsgnrTelno: z.string().optional(), + dsgnrOgdpNm: z.string().optional(), + dsgnrEmlAddr: z.string().optional(), + atchFileGroupId: z.string().optional(), + prgrsSttsComCd: z.string().optional(), + rtlztnFcltSeComCd: z.string().optional(), + rtlztnUsgSeComCd: z.string().optional(), +}); + +/** 백엔드 InstlSncnApplySaveRequestDto.BzScaleItem (diff는 API 미전송) */ +export const instlSncnBzScaleItemSchema = z.object({ + cnstpSeCd: z.string().optional(), + bscPl: z.number().optional(), + budgetReflct: z.number().optional(), + thtiDsgn: z.number().optional(), +}); + +/** 백엔드 InstlSncnApplySaveRequestDto.BizCstItem */ +export const instlSncnBizCstItemSchema = z.object({ + acntgSeCd: z.string().optional(), + bscPlanAmt: z.number().optional(), + bgtRfltAmt: z.number().optional(), + tstmDesignAmt: z.number().optional(), +}); + +/** 백엔드 InstlSncnApplySaveRequestDto.CklstResultItem */ +export const instlSncnCklstResultItemSchema = z.object({ + cklstRvwSn: z.number().optional(), + cklstArtclSn: z.number().optional(), + cklstSn: z.number().optional(), + rvwStpComCd: z.string().nullish(), + selected: z.enum(["Y", "N"]).nullish(), + picRvwCn: z.string().nullish(), +}); + +/** 백엔드 InstlSncnApplySaveRequestDto.EsntlAtchFileItem */ +export const instlSncnEsntlAtchFileItemSchema = z.object({ + esntlAtchFileId: z.string().optional(), + atchFileGroupId: z.string().optional(), +}); + +/** 백엔드 InstlSncnRvwOpnnSaveRequestDto */ +export const instlSncnRvwOpnnSaveSchema = z.object({ + instlSncnDocNo: z.string(), + rvwOpnnSn: z.number().optional(), + opnrSeComCd: z.string().optional(), + opnrNm: z.string().optional(), + rvwRsltCn: z.string().optional(), + rvwRsltAnmlyYn: z.string().optional(), + atchFileGroupId: z.string().optional(), +}); + +/** 백엔드 InstlSncnApplySaveRequestDto.InstlSncnSplmntDmndItem */ +export const instlSncnSplmntDmndItemSchema = z.object({ + splmntDmndSn: z.number().nullish(), + splmntArtclComCd: z.string().nullish(), + splmntDmndCn: z.string().optional(), + splmntDmndDt: z.string().optional(), + splmntDmndAtchFileGroupId: z.string().optional(), + splmntCn: z.string().optional(), + splmntPicNm: z.string().optional(), + splmntCmptnDt: z.string().optional(), + splmntAtchFileGroupId: z.string().optional(), + cmptnYn: z.string().optional(), +}); + +/** 백엔드 InstlSncnApplySaveRequestDto */ +export const instlSncnApplySaveSchema = z.object({ + techReViewSaveCd: z.string().optional(), + rvwStpCd: z.string().optional(), + instlSncnMng: instlSncnMngSaveSchema, + bzScaleList: z.array(instlSncnBzScaleItemSchema).optional(), + bizCstList: z.array(instlSncnBizCstItemSchema).optional(), + cklstResultList: z.array(instlSncnCklstResultItemSchema).optional(), + cklstResultMap: z.record(z.array(instlSncnCklstResultItemSchema)).optional(), + esntlAtchFileList: z.array(instlSncnEsntlAtchFileItemSchema).optional(), + rvwOpnnList: z.array(instlSncnRvwOpnnSaveSchema).optional(), + splmntDmndList: z.array(instlSncnSplmntDmndItemSchema).optional(), +}); + +export type InstlSncnMngSaveRequestDto = z.infer; +export type InstlSncnBzScaleSaveDto = z.infer; +export type InstlSncnBizCstSaveRequestDto = z.infer; +export type InstlSncnCklstResultSaveDto = z.infer; +export type InstlSncnEsntlAtchFileSaveDto = z.infer; +export type InstlSncnRvwOpnnSaveRequestDto = z.infer; +export type InstlSncnSplmntDmndSaveDto = z.infer; +export type InstlSncnApplySaveRequestDto = z.infer; diff --git a/apps/as/src/entities/instl-sncn/model/instl-sncn-interface.ts b/apps/as/src/entities/instl-sncn/model/instl-sncn-interface.ts index a5e4ce791..1eea8c4c9 100644 --- a/apps/as/src/entities/instl-sncn/model/instl-sncn-interface.ts +++ b/apps/as/src/entities/instl-sncn/model/instl-sncn-interface.ts @@ -1,24 +1,24 @@ import type { AtflMngClientResponseDto, - BizCstSaveBase, BizCstTreeDto, BizSclTreeDto, - BzScaleSaveBase, - CklstResultSaveBase, DocPrgrsUpdateRequestDto, ElaprDocPrgrsClientResponseDto, EsntlAtchFileMngTreeResponseDto, - EsntlAtchFileSaveBase, PageableParams, PageResponse, - PbadmsApplySaveHeader, PbadmsMngDetResponseBase, RvwOpnnResponseExtension, - RvwOpnnSaveBase, SplmntDmndResponseExtension, - SplmntDmndSaveBase, } from "@/shared/model"; +import type { + InstlSncnApplySaveRequestDto, + InstlSncnMngSaveRequestDto, + InstlSncnRvwOpnnSaveRequestDto, + InstlSncnSplmntDmndSaveDto, +} from "./instl-sncn-apply-payload-schema"; + export type { AtflMngClientResponseDto, BizCstTreeDto, @@ -41,189 +41,48 @@ export const INSTL_SNCN_REGISTER_SELECTION_KEY = "instlSncnRegisterSelection"; /** 백엔드 InstlSncnMngSearchDto + Pageable */ export interface InstlSncnMngSearchParams extends PageableParams { - /** 설치인가문서번호 */ instlSncnDocNo?: string; - /** 행정구역코드 */ admdstCd?: string; - /** 환경청구분공통코드 */ envoSeComCd?: string; - /** 업무구분공통코드 */ taskSeComCd?: string; - /** 예산사업코드 */ bgtBizCd?: string; - /** 예산신청연도 */ bgtAplyYr?: string; - /** 사업명 */ bizNm?: string; - /** 사업시작연도 */ bizBgngYr?: string; - /** 사업종료연도 */ bizEndYr?: string; - /** 사업내용 */ bizCn?: string; - /** 신청자명 */ aplcntNm?: string; - /** 신청자휴대전화번호 */ aplcntMblTelno?: string; - /** 신청자소속명 */ aplcntOgdpNm?: string; - /** 신청자이메일주소 */ aplcntEmlAddr?: string; - /** 설계자명 */ dsgnrNm?: string; - /** 설계자전화번호 */ dsgnrTelno?: string; - /** 설계자소속명 */ dsgnrOgdpNm?: string; - /** 설계자이메일주소 */ dsgnrEmlAddr?: string; - /** 첨부파일그룹아이디 */ atchFileGroupId?: string; - /** 진행상태공통코드 */ prgrsSttsComCd?: string; } -/** 설치인가관리 저장 요청 DTO */ -export interface InstlSncnMngSaveRequestDto { - /** 설치인가문서번호 */ - instlSncnDocNo?: string; - /** 행정구역코드 */ - admdstCd?: string; - /** 환경청구분공통코드 */ - envoSeComCd?: string; - /** 인가구분공통코드 */ - sncnSeComCd: string; - /** 업무구분공통코드 */ - taskSeComCd: string; - /** 예산사업코드 */ - bgtBizCd?: string; - /** 예산신청연도 */ - bgtAplyYr?: string; - /** 사업명 */ - bizNm: string; - /** 사업시작연도 */ - bizBgngYr: string; - /** 사업종료연도 */ - bizEndYr: string; - /** 사업내용 */ - bizCn: string; - /** 신청자명 */ - aplcntNm?: string; - /** 신청자휴대전화번호 */ - aplcntMblTelno?: string; - /** 신청자소속명 */ - aplcntOgdpNm?: string; - /** 신청자이메일주소 */ - aplcntEmlAddr?: string; - /** 설계자명 */ - dsgnrNm: string; - /** 설계자전화번호 */ - dsgnrTelno: string; - /** 설계자소속명 */ - dsgnrOgdpNm: string; - /** 설계자이메일주소 */ - dsgnrEmlAddr: string; - /** 첨부파일그룹아이디 */ - atchFileGroupId?: string; - /** 진행상태공통코드 */ - prgrsSttsComCd?: string; - /** 재이용시설구분공통코드 */ - rtlztnFcltSeComCd?: string; - /** 재이용용도구분공통코드 */ - rtlztnUsgSeComCd?: string; -} - -/** 설치인가관리 수정 요청 DTO */ +/** 백엔드 InstlSncnMngUpdateRequestDto */ export type InstlSncnMngUpdateRequestDto = InstlSncnMngSaveRequestDto; -/** 설치인가관리 응답 DTO */ -export interface InstlSncnMngResponseDto extends InstlSncnMngSaveRequestDto { - /** 설치인가문서번호 (필수로 재정의) */ +/** 백엔드 InstlSncnMngResponseDto */ +export type InstlSncnMngResponseDto = InstlSncnMngSaveRequestDto & { instlSncnDocNo: string; - /** 환경청구분공통코드명 */ envoSeCdNm: string; - /** 인가구분공통코드명 */ sncnSeCdNm: string; - /** 업무구분공통코드명 */ taskSeCdNm: string; - /** 진행상태공통코드명 */ prgrsSttsCdNm: string; - /** 기술검토회신이력여부 */ techRplyHstryYn: "Y" | "N"; - /** 재이용시설구분공통코드명 */ rtlztnFcltSeComNm: string; - /** 재이용용도구분공통코드명 */ rtlztnUsgSeComNm: string; - /** 등록일시 */ regDt: string; - /** 등록자 UUID */ rgtr: string; - /** 등록자명 */ rgtrNm: string; - /** 수정 일시 */ mdfcnDt: string; - /** 시도 */ sido: string; - /** 구군 */ gugun: string; -} - -/** 설치인가 신청 - 사업규모 저장 DTO(배열 원소) */ -export interface InstlSncnBzScaleSaveDto extends BzScaleSaveBase { - /** 공종구분코드 */ - cnstpSeCd: string; - /** 기본계획수량 */ - bscPl: number; - /** 예산반영수량 */ - budgetReflct: number; - /** 금회설계수량 */ - thtiDsgn: number; - /** 증감(C-B) — 클라이언트 계산용, API 미전송 */ - diff?: number; -} - -/** 설치인가 신청 - 총사업비 저장 DTO(배열 원소) */ -export interface InstlSncnBizCstSaveRequestDto extends BizCstSaveBase { - /** 회계구분코드 */ - acntgSeCd: string; - /** 기본계획금액 */ - bscPlanAmt: number; - /** 예산반영금액 */ - bgtRfltAmt: number; - /** 금회설계금액 */ - tstmDesignAmt: number; -} - -/** 설치인가 신청 - 체크리스트 결과 저장 DTO(배열 원소) */ -export interface InstlSncnCklstResultSaveDto extends CklstResultSaveBase { - /** 점검표일련번호 */ - cklstSn: number; - /** 점검표항목일련번호 */ - cklstArtclSn: number; - /** 점검표검토일련번호 */ - cklstRvwSn: number; - /** 선택값(Y/N/null) */ - selected: "Y" | "N" | null; - /** 담당자검토내용(기술검토의견) */ - picRvwCn?: string | null; -} - -/** 설치인가 신청 - 필수첨부파일 저장 DTO(배열 원소) */ -export interface InstlSncnEsntlAtchFileSaveDto extends EsntlAtchFileSaveBase { - /** 필수첨부파일아이디 */ - esntlAtchFileId: string; - /** 첨부파일그룹아이디 */ - atchFileGroupId: string; -} - -/** 설치인가검토의견 저장 요청 DTO */ -export interface InstlSncnRvwOpnnSaveRequestDto extends RvwOpnnSaveBase { - /** 설치인가문서번호 */ - instlSncnDocNo: string; -} - -/** 설치인가 보완요구 저장 DTO(배열 원소) */ -export type InstlSncnSplmntDmndSaveDto = SplmntDmndSaveBase; +}; /** 설치인가 보완요구 응답 DTO */ export type InstlSncnSplmntDmndResponseDto = InstlSncnSplmntDmndSaveDto & @@ -231,26 +90,6 @@ export type InstlSncnSplmntDmndResponseDto = InstlSncnSplmntDmndSaveDto & instlSncnDocNo?: string; }; -/** 설치인가 신청 - 통합 저장 요청 DTO */ -export interface InstlSncnApplySaveRequestDto extends PbadmsApplySaveHeader { - /** 설치인가관리 정보 */ - instlSncnMng: InstlSncnMngSaveRequestDto; - /** 사업규모 목록 */ - bzScaleList?: InstlSncnBzScaleSaveDto[]; - /** 총사업비 목록 */ - bizCstList?: InstlSncnBizCstSaveRequestDto[]; - /** 체크리스트 검토결과 목록 */ - cklstResultList?: InstlSncnCklstResultSaveDto[]; - /** 체크리스트 검토결과 맵 (rvwStpComCd -> 목록) */ - cklstResultMap?: Record; - /** 필수첨부파일 목록 */ - esntlAtchFileList?: InstlSncnEsntlAtchFileSaveDto[]; - /** 검토의견 목록 */ - rvwOpnnList?: InstlSncnRvwOpnnSaveRequestDto[]; - /** 보완요구 목록 */ - splmntDmndList?: InstlSncnSplmntDmndSaveDto[]; -} - export type ReviewStepCode = "LOCAL" | "ENV" | "KECO"; /** 설치인가검토의견 응답 DTO */ diff --git a/apps/as/src/entities/pwrk-cnsltn-doc-pjtco-ajmt-mng/api/pwrk-cnsltn-doc-pjtco-ajmt-mng-service.ts b/apps/as/src/entities/pwrk-cnsltn-doc-pjtco-ajmt-mng/api/pwrk-cnsltn-doc-pjtco-ajmt-mng-service.ts new file mode 100644 index 000000000..c525ac87b --- /dev/null +++ b/apps/as/src/entities/pwrk-cnsltn-doc-pjtco-ajmt-mng/api/pwrk-cnsltn-doc-pjtco-ajmt-mng-service.ts @@ -0,0 +1,24 @@ +import { client } from "@/shared/api"; +import { ENDPOINT } from "@/shared/config"; +import type { PageResponse } from "@/shared/model"; + +import type { + PwrkCnsltnDocPjtcoAjmtMngResponseDto, + PwrkCnsltnDocPjtcoAjmtMngSearchDto, + PwrkCnsltnDocPjtcoAjmtMngService, +} from "../model/pwrk-cnsltn-doc-pjtco-ajmt-mng-interface"; + +const BASE = ENDPOINT.AS_SERVICE.PWRK_CNSLTN_DOC_PJTCO_AJMT_MNG; + +async function findPage( + searchDto: PwrkCnsltnDocPjtcoAjmtMngSearchDto, +): Promise> { + const response = await client.get>(BASE, { + params: searchDto, + }); + return response.data; +} + +export const pwrkCnsltnDocPjtcoAjmtMngService = { + findPage, +} satisfies PwrkCnsltnDocPjtcoAjmtMngService; diff --git a/apps/as/src/entities/pwrk-cnsltn-doc-pjtco-ajmt-mng/api/queries.ts b/apps/as/src/entities/pwrk-cnsltn-doc-pjtco-ajmt-mng/api/queries.ts new file mode 100644 index 000000000..21f357c2b --- /dev/null +++ b/apps/as/src/entities/pwrk-cnsltn-doc-pjtco-ajmt-mng/api/queries.ts @@ -0,0 +1,22 @@ +import { useQuery } from "@tanstack/react-query"; + +import type { PwrkCnsltnDocPjtcoAjmtMngSearchDto } from "../model/pwrk-cnsltn-doc-pjtco-ajmt-mng-interface"; + +import { pwrkCnsltnDocPjtcoAjmtMngService } from "./pwrk-cnsltn-doc-pjtco-ajmt-mng-service"; + +export const pwrkCnsltnDocPjtcoAjmtMngKeys = { + all: ["pwrk-cnsltn-doc-pjtco-ajmt-mng"] as const, + page: (params: PwrkCnsltnDocPjtcoAjmtMngSearchDto) => + [...pwrkCnsltnDocPjtcoAjmtMngKeys.all, "findPage", params] as const, +}; + +export function usePwrkCnsltnDocPjtcoAjmtMngPage( + params: PwrkCnsltnDocPjtcoAjmtMngSearchDto, + isEnabled = true, +) { + return useQuery({ + queryKey: pwrkCnsltnDocPjtcoAjmtMngKeys.page(params), + queryFn: () => pwrkCnsltnDocPjtcoAjmtMngService.findPage(params), + enabled: Boolean(params.pwrkCnsltnDocNo) && isEnabled, + }); +} diff --git a/apps/as/src/entities/pwrk-cnsltn-doc-pjtco-ajmt-mng/index.ts b/apps/as/src/entities/pwrk-cnsltn-doc-pjtco-ajmt-mng/index.ts new file mode 100644 index 000000000..143bef3ba --- /dev/null +++ b/apps/as/src/entities/pwrk-cnsltn-doc-pjtco-ajmt-mng/index.ts @@ -0,0 +1,10 @@ +export { pwrkCnsltnDocPjtcoAjmtMngService } from "./api/pwrk-cnsltn-doc-pjtco-ajmt-mng-service"; +export { + pwrkCnsltnDocPjtcoAjmtMngKeys, + usePwrkCnsltnDocPjtcoAjmtMngPage, +} from "./api/queries"; +export type { + PwrkCnsltnDocPjtcoAjmtMngResponseDto, + PwrkCnsltnDocPjtcoAjmtMngSearchDto, + PwrkCnsltnDocPjtcoAjmtMngService, +} from "./model/pwrk-cnsltn-doc-pjtco-ajmt-mng-interface"; diff --git a/apps/as/src/entities/pwrk-cnsltn-doc-pjtco-ajmt-mng/model/pwrk-cnsltn-doc-pjtco-ajmt-mng-interface.ts b/apps/as/src/entities/pwrk-cnsltn-doc-pjtco-ajmt-mng/model/pwrk-cnsltn-doc-pjtco-ajmt-mng-interface.ts new file mode 100644 index 000000000..420b9d44b --- /dev/null +++ b/apps/as/src/entities/pwrk-cnsltn-doc-pjtco-ajmt-mng/model/pwrk-cnsltn-doc-pjtco-ajmt-mng-interface.ts @@ -0,0 +1,44 @@ +import type { PageableParams, PageResponse } from "@/shared/model"; + +/** 백엔드 PwrkCnsltnDocPjtcoAjmtMngResponseDto */ +export interface PwrkCnsltnDocPjtcoAjmtMngResponseDto { + pwrkCnsltnDocNo: string; + rvwOpnnSn: number; + acntgSeCd: string; + acntgSeCdNm?: string; + rvwOpnnClsfComCd?: string; + rvwOpnnClsfComCdNm?: string; + ognTpcoAmt?: number; + ognGvexpAmt?: number; + ognLcexpAmt?: number; + ognOrgprBrdnAmt?: number; + chgTpcoAmt?: number; + chgGvexpAmt?: number; + chgLcexpAmt?: number; + chgOrgprBrdnAmt?: number; + ajmtRsn?: string; + vrfcTpcoAmt?: number; + vrfcGvexpAmt?: number; + vrfcLcexpAmt?: number; + vrfcOrgprBrdnAmt?: number; + rsbnYn?: string; + vrfcCn?: string; + regDt?: string; + rgtr?: string; + rgtrNm?: string; + mdfcnDt?: string; + mdfr?: string; +} + +/** 백엔드 PwrkCnsltnDocPjtcoAjmtMngSearchDto + Pageable */ +export interface PwrkCnsltnDocPjtcoAjmtMngSearchDto extends PageableParams { + pwrkCnsltnDocNo?: string; + rvwOpnnSn?: number; + acntgSeCd?: string; +} + +export interface PwrkCnsltnDocPjtcoAjmtMngService { + findPage( + searchDto: PwrkCnsltnDocPjtcoAjmtMngSearchDto, + ): Promise>; +} diff --git a/apps/as/src/entities/sewer-bsc-plan/model/sewer-bsc-plan-apply-payload-schema.ts b/apps/as/src/entities/sewer-bsc-plan/model/sewer-bsc-plan-apply-payload-schema.ts index 2980cfffb..6ad7af1ba 100644 --- a/apps/as/src/entities/sewer-bsc-plan/model/sewer-bsc-plan-apply-payload-schema.ts +++ b/apps/as/src/entities/sewer-bsc-plan/model/sewer-bsc-plan-apply-payload-schema.ts @@ -47,7 +47,7 @@ export const sewerBscPlanCklstResultItemSchema = z.object({ cklstArtclSn: z.number().optional(), cklstSn: z.number().optional(), rvwStpComCd: z.string(), - selected: z.string().optional(), + selected: z.enum(["Y", "N"]).nullish(), picRvwCn: z.string().nullish(), }); diff --git a/apps/as/src/pages/approval-line-management/ui/elapr-apln-grid.tsx b/apps/as/src/pages/approval-line-management/ui/elapr-apln-grid.tsx index e703551d5..5873803da 100644 --- a/apps/as/src/pages/approval-line-management/ui/elapr-apln-grid.tsx +++ b/apps/as/src/pages/approval-line-management/ui/elapr-apln-grid.tsx @@ -68,6 +68,7 @@ function ElaprAplnGrid({ elaprTmpltId }: ElaprAplnGridProps) { markDeleted, reorderRows, } = useAplnRows({ + seedKey: elaprTmpltId, elaprTmpltId, serverRows: aplnQuery.data?.content, }); diff --git a/apps/as/src/pages/design-cnstn/lib/design-cnstn-regist-schema.ts b/apps/as/src/pages/design-cnstn/lib/design-cnstn-regist-schema.ts index 3eecde578..9cbcdc270 100644 --- a/apps/as/src/pages/design-cnstn/lib/design-cnstn-regist-schema.ts +++ b/apps/as/src/pages/design-cnstn/lib/design-cnstn-regist-schema.ts @@ -1,7 +1,13 @@ import { z } from "zod"; +import { + designCnstnBzScaleItemSchema, + designCnstnEsntlAtchFileItemSchema, + designCnstnMngSaveSchema, +} from "@/entities/design-cnstn"; + export const designCnstnRegistSchema = z.object({ - designCnstnMng: z.object({ + designCnstnMng: designCnstnMngSaveSchema.extend({ bizNm: z.string().min(1, "사업명은 필수입니다"), bizBgngYr: z.string().min(1, "사업시작연도는 필수입니다"), bizEndYr: z.string().min(1, "사업종료연도는 필수입니다"), @@ -17,27 +23,11 @@ export const designCnstnRegistSchema = z.object({ .string() .min(1, "설계자 이메일은 필수입니다") .email("올바른 이메일 형식이 아닙니다"), - aplcntNm: z.string().optional(), - aplcntMblTelno: z.string().optional(), - aplcntOgdpNm: z.string().optional(), - aplcntEmlAddr: z.string().optional(), - designCnstnDocNo: z.string().optional(), - admdstCd: z.string().optional(), - envoSeComCd: z.string().optional(), - prgrsSttsComCd: z.string().optional(), - atchFileGroupId: z.string().optional(), - envoSeCdNm: z.string().optional(), - taskSeCdNm: z.string().optional(), - prgrsSttsCdNm: z.string().optional(), sido: z.string().optional(), gugun: z.string().optional(), - regDt: z.string().optional(), - rgtr: z.string().optional(), - rgtrNm: z.string().optional(), - mdfcnDt: z.string().optional(), }), bzScaleList: z.array( - z.object({ + designCnstnBzScaleItemSchema.extend({ cnstpSeCd: z.string(), bscPl: z.number(), budgetReflct: z.number(), @@ -46,7 +36,7 @@ export const designCnstnRegistSchema = z.object({ }), ), esntlAtchFileList: z.array( - z.object({ + designCnstnEsntlAtchFileItemSchema.extend({ esntlAtchFileId: z.string(), atchFileGroupId: z.string(), }), diff --git a/apps/as/src/pages/design-cnstn/lib/form-data-mapper.ts b/apps/as/src/pages/design-cnstn/lib/form-data-mapper.ts index 3150bfaa7..326f6e237 100644 --- a/apps/as/src/pages/design-cnstn/lib/form-data-mapper.ts +++ b/apps/as/src/pages/design-cnstn/lib/form-data-mapper.ts @@ -1,9 +1,13 @@ import type { IUserInfo } from "@/shared/store"; -import type { DesignCnstnMngDetResponseDto } from "@/entities/design-cnstn"; +import { + type DesignCnstnApplySaveRequestDto, + designCnstnApplySaveSchema, + type DesignCnstnMngDetResponseDto, +} from "@/entities/design-cnstn"; import { toEsntlAtchFileSaveList } from "@/widgets/pbadms-sprt"; import type { DesignCnstnRegistFormData } from "./design-cnstn-regist-schema"; -import { parseTaskSeComCdFromApi } from "./design-cnstn-regist-schema"; +import { formatTaskSeComCdForApi, parseTaskSeComCdFromApi } from "./design-cnstn-regist-schema"; export function mapDetailToFormData(data: DesignCnstnMngDetResponseDto): DesignCnstnRegistFormData { const detDto = data.detDto; @@ -33,15 +37,8 @@ export function mapDetailToFormData(data: DesignCnstnMngDetResponseDto): DesignC envoSeComCd: detDto.envoSeComCd, prgrsSttsComCd: detDto.prgrsSttsComCd, atchFileGroupId: detDto.atchFileGroupId ?? "", - envoSeCdNm: detDto.envoSeCdNm, - taskSeCdNm: detDto.taskSeCdNm, - prgrsSttsCdNm: detDto.prgrsSttsCdNm, sido: detDto.sido, gugun: detDto.gugun, - regDt: detDto.regDt, - rgtr: detDto.rgtr, - rgtrNm: detDto.rgtrNm, - mdfcnDt: detDto.mdfcnDt, }, bzScaleList: bizSclList.map((item) => ({ cnstpSeCd: item.cnstpSeCd, @@ -90,3 +87,20 @@ export function getCreateDefaultValues( privacyConsent: "N", }; } + +export function buildFormPayload( + data: DesignCnstnRegistFormData, + prgrsSttsComCd: "CV" | "CT", + options: { isEditMode: boolean; docNo?: string }, +): DesignCnstnApplySaveRequestDto { + return designCnstnApplySaveSchema.parse({ + designCnstnMng: { + ...data.designCnstnMng, + taskSeComCd: formatTaskSeComCdForApi(data.designCnstnMng.taskSeComCd), + designCnstnDocNo: options.isEditMode ? options.docNo : undefined, + prgrsSttsComCd, + }, + bzScaleList: data.bzScaleList, + esntlAtchFileList: data.esntlAtchFileList, + }); +} diff --git a/apps/as/src/pages/design-cnstn/lib/save-payload-utils.ts b/apps/as/src/pages/design-cnstn/lib/save-payload-utils.ts index 48da7a731..625ff0486 100644 --- a/apps/as/src/pages/design-cnstn/lib/save-payload-utils.ts +++ b/apps/as/src/pages/design-cnstn/lib/save-payload-utils.ts @@ -4,6 +4,7 @@ import type { DesignCnstnApplySaveRequestDto, DesignCnstnMngDetResponseDto, } from "@/entities/design-cnstn"; +import { designCnstnApplySaveSchema } from "@/entities/design-cnstn"; import { toEsntlAtchFileSaveList } from "@/widgets/pbadms-sprt"; import type { ReviewStepCode } from "../model/view-types"; @@ -30,11 +31,11 @@ export function buildSavePayload( throw new Error("상세 정보(detDto)가 없습니다."); } - return { + return designCnstnApplySaveSchema.parse({ designCnstnMng: { ...detDto, prgrsSttsComCd }, bzScaleList: toBzScaleSaveDto(data.bizSclList ?? []), esntlAtchFileList: toEsntlAtchFileSaveList(data.esntlFileList), - }; + }); } export function buildTechReviewPayload( @@ -45,10 +46,10 @@ export function buildTechReviewPayload( reviewOpinionFormData?: ReviewOpinionGetFormDataResult | null, opnrNm?: string, ): DesignCnstnApplySaveRequestDto { - return { + return designCnstnApplySaveSchema.parse({ ...buildSavePayload(data, prgrsSttsComCd), techReViewSaveCd, rvwStpCd: reviewStep, rvwOpnnList: toRvwOpnnSaveDto(data, reviewOpinionFormData, opnrNm), - }; + }); } diff --git a/apps/as/src/pages/design-cnstn/ui/design-form-page.tsx b/apps/as/src/pages/design-cnstn/ui/design-form-page.tsx index 041e1d812..254a7a010 100644 --- a/apps/as/src/pages/design-cnstn/ui/design-form-page.tsx +++ b/apps/as/src/pages/design-cnstn/ui/design-form-page.tsx @@ -9,9 +9,8 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { useQueryClient } from "@tanstack/react-query"; import { useAlertStore, useUserStore } from "@/shared/lib"; -import { SectionCollapsibleGroup, SectionCollapsibleToggle } from "@/shared/ui"; +import { RhfDevTool, SectionCollapsibleGroup, SectionCollapsibleToggle } from "@/shared/ui"; import { - type DesignCnstnApplySaveRequestDto, designCnstnKeys, type DesignCnstnMngDetResponseDto, designCnstnMngService, @@ -27,12 +26,12 @@ import { } from "@/widgets/pbadms-sprt"; import { Button } from "@common/components"; +import { type DesignCnstnRegistFormData, designCnstnRegistSchema } from "../lib/design-cnstn-regist-schema"; import { - type DesignCnstnRegistFormData, - designCnstnRegistSchema, - formatTaskSeComCdForApi, -} from "../lib/design-cnstn-regist-schema"; -import { getCreateDefaultValues, mapDetailToFormData } from "../lib/form-data-mapper"; + buildFormPayload, + getCreateDefaultValues, + mapDetailToFormData, +} from "../lib/form-data-mapper"; import { PROGRESS_STATUS_COLUMNS } from "../model/columns"; import { BasicInfoFormSection } from "./sections/basic-info-form-section"; @@ -68,29 +67,18 @@ function DesignFormPage({ mode, initialData, createDefaults }: DesignFormPagePro : getCreateDefaultValues(userInfo, createDefaults), }); - function buildFormPayload( - data: DesignCnstnRegistFormData, - prgrsSttsComCd: "CV" | "CT", - ): DesignCnstnApplySaveRequestDto { - const { taskSeComCd: formTaskSeComCd, ...designCnstnMngRest } = data.designCnstnMng; - - return { - designCnstnMng: { - ...designCnstnMngRest, - taskSeComCd: formatTaskSeComCdForApi(formTaskSeComCd), - designCnstnDocNo: isEditMode ? initialData?.detDto?.designCnstnDocNo : undefined, - prgrsSttsComCd, - }, - bzScaleList: data.bzScaleList, - esntlAtchFileList: data.esntlAtchFileList, - }; + function createFormPayload(data: DesignCnstnRegistFormData, prgrsSttsComCd: "CV" | "CT") { + return buildFormPayload(data, prgrsSttsComCd, { + isEditMode, + docNo: initialData?.detDto?.designCnstnDocNo, + }); } async function submitForm( data: DesignCnstnRegistFormData, prgrsSttsComCd: "CV" | "CT", ): Promise { - return designCnstnMngService.saveApply(buildFormPayload(data, prgrsSttsComCd)); + return designCnstnMngService.saveApply(createFormPayload(data, prgrsSttsComCd)); } async function onSave(data: DesignCnstnRegistFormData) { @@ -229,6 +217,7 @@ function DesignFormPage({ mode, initialData, createDefaults }: DesignFormPagePro + ); } diff --git a/apps/as/src/pages/design-cnstn/ui/sections/tech-review-opinion-section.tsx b/apps/as/src/pages/design-cnstn/ui/sections/tech-review-opinion-section.tsx index d467bdb31..f81f0aaf9 100644 --- a/apps/as/src/pages/design-cnstn/ui/sections/tech-review-opinion-section.tsx +++ b/apps/as/src/pages/design-cnstn/ui/sections/tech-review-opinion-section.tsx @@ -2,14 +2,11 @@ import { forwardRef, useEffect, useImperativeHandle, useState } from "react"; -import { Paperclip } from "lucide-react"; - -import { PROXY_HOST } from "@/shared/config"; import { dayjs, getDefaultFileKey, useTus } from "@/shared/lib"; import { TextShimmer } from "@/shared/ui"; import { SectionCollapsible } from "@/shared/ui"; import { Field, FieldLabel, FieldSet } from "@/shared/ui/field"; -import { attachService } from "@/entities/attach"; +import { AttachmentDownloadLink, attachService } from "@/entities/attach"; import type { DesignCnstnRvwOpnnResponseDto } from "@/entities/design-cnstn"; import { Button, @@ -319,15 +316,10 @@ function TechReviewOpinionTable({ items }: TechReviewOpinionTableProps) { {item.atchFileInfo?.atflCd ? ( - - - {item.atchFileInfo.orgnlFileNm || "다운로드"} - + ) : ( "-" )} diff --git a/apps/as/src/pages/esntl-atch-file-mng/ui/esntl-atch-file-form-dialog.tsx b/apps/as/src/pages/esntl-atch-file-mng/ui/esntl-atch-file-form-dialog.tsx index 710d6c93d..c7c63ffed 100644 --- a/apps/as/src/pages/esntl-atch-file-mng/ui/esntl-atch-file-form-dialog.tsx +++ b/apps/as/src/pages/esntl-atch-file-mng/ui/esntl-atch-file-form-dialog.tsx @@ -5,7 +5,7 @@ import { useForm } from "react-hook-form"; import { getDefaultFileKey, useTus } from "@/shared/lib"; import { AttachmentFileField } from "@/shared/ui"; -import { attachService } from "@/entities/attach"; +import { attachService, useDownloadAttachmentByAtflId } from "@/entities/attach"; import { CodeSelect } from "@/entities/code"; import type { EsntlAtchFileMng } from "@/entities/esntl"; import { @@ -61,6 +61,7 @@ function EsntlAtchFileFormDialog({ const formId = useId(); const atchFileGroupIdRef = useRef(""); const [uploadedFiles, setUploadedFiles] = useState([]); + const { mutate: downloadByAtflId } = useDownloadAttachmentByAtflId(); const { register, handleSubmit, reset, setValue, watch } = useForm({ defaultValues: { @@ -219,6 +220,9 @@ function EsntlAtchFileFormDialog({ files={uploadedFiles.map((item) => ({ fileKey: item.fileKey, fileName: item.fileName, + onDownload: item.atflId + ? () => downloadByAtflId(item.atflId!) + : undefined, }))} inputId={fileInputId} isUploadingByKey={isUploading} diff --git a/apps/as/src/pages/fnrs-cnsltn/lib/ai-pending-utils.ts b/apps/as/src/pages/fnrs-cnsltn/lib/ai-pending-utils.ts index 1ac079216..99b1b29ab 100644 --- a/apps/as/src/pages/fnrs-cnsltn/lib/ai-pending-utils.ts +++ b/apps/as/src/pages/fnrs-cnsltn/lib/ai-pending-utils.ts @@ -1,3 +1,5 @@ +import type { RagIndexingInfoDto } from "@/entities/fnrs-cnsltn"; + const AI_PENDING_STATUS_CODES = ["FV", "AC", "AV"] as const; type AiPendingStatusCode = (typeof AI_PENDING_STATUS_CODES)[number]; @@ -17,5 +19,17 @@ function getAiPendingMessage(prgrsSttsComCd: string): string { return AI_PENDING_MESSAGES[prgrsSttsComCd as AiPendingStatusCode]; } -export { AI_PENDING_STATUS_CODES, getAiPendingMessage, isAiPendingStatus }; +function isRagIndexingInProgress( + prgrsSttsComCd: string, + ragIndexingInfo?: RagIndexingInfoDto | null, +): boolean { + return prgrsSttsComCd === "ER" && ragIndexingInfo?.isInProgress === true; +} + +export { + AI_PENDING_STATUS_CODES, + getAiPendingMessage, + isAiPendingStatus, + isRagIndexingInProgress, +}; export type { AiPendingStatusCode }; diff --git a/apps/as/src/pages/fnrs-cnsltn/lib/button-config.ts b/apps/as/src/pages/fnrs-cnsltn/lib/button-config.ts index d0b906dae..6f711405b 100644 --- a/apps/as/src/pages/fnrs-cnsltn/lib/button-config.ts +++ b/apps/as/src/pages/fnrs-cnsltn/lib/button-config.ts @@ -2,14 +2,14 @@ export const FNRS_CNSLTN_VIEW_BUTTON_ACTION = { strg: "strg", aply: "aply", del: "del", - fnrsCnsltnAncmnt: "fnrsCnsltnAncmnt", envoSbmsn: "envoSbmsn", rjct: "rjct", sncnAprv: "sncnAprv", - lclRply: "lclRply", rvwRply: "rvwRply", + rvwOpnnRply: "rvwOpnnRply", rqstTechRvw: "rqstTechRvw", fileSplmnt: "fileSplmnt", + fileVrfc: "fileVrfc", dsgntRvwr: "dsgntRvwr", pwrkCnsltnDocCrt: "pwrkCnsltnDocCrt", pwrkCnsltnDocMng: "pwrkCnsltnDocMng", @@ -21,6 +21,7 @@ export const FNRS_CNSLTN_VIEW_BUTTON_ACTION = { atrzRply: "atrzRply", actnCmptn: "actnCmptn", rqstActnRvw: "rqstActnRvw", + costVrfc: "costVrfc", } as const; export type FnrsCnsltnViewButtonAction = @@ -38,37 +39,63 @@ const ROLE_STATUS_BUTTON_ACTIONS: Record > = { ER: { - ATRZ01: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.fileSplmnt, FNRS_CNSLTN_VIEW_BUTTON_ACTION.dsgntRvwr], + ATRZ01: [ + FNRS_CNSLTN_VIEW_BUTTON_ACTION.fileSplmnt, + FNRS_CNSLTN_VIEW_BUTTON_ACTION.fileVrfc, + FNRS_CNSLTN_VIEW_BUTTON_ACTION.dsgntRvwr, + ], + }, + SL: { + ATRZ01: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng], + ATRZ02: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng], + ATRZ03: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng], + ATRZ99: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng], }, TE: { + ATRZ01: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng], ATRZ02: [ FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocCrt, FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng, @@ -81,10 +108,80 @@ const ROLE_USER01_ATRZ_STATUS_ACTIONS: Record< FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng, FNRS_CNSLTN_VIEW_BUTTON_ACTION.atrzAprv, ], - ATRZ99: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.atrzRply], + ATRZ99: [ + FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng, + FNRS_CNSLTN_VIEW_BUTTON_ACTION.atrzRply, + ], + }, + TP: { + ATRZ01: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng], + ATRZ02: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng], + ATRZ03: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng], + ATRZ99: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng], + }, + RB: { + ATRZ01: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng], + ATRZ02: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng], + ATRZ03: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng], + ATRZ99: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng], + }, + RA: { + ATRZ01: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng], + ATRZ02: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng], + ATRZ03: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng], + ATRZ99: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng], + }, + AV: { + ATRZ01: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng], + ATRZ02: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng], + ATRZ03: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng], + ATRZ99: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng], + }, + EV: { + ATRZ01: [ + FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng, + FNRS_CNSLTN_VIEW_BUTTON_ACTION.dsgntRvwr, + ], + ATRZ02: [ + FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng, + FNRS_CNSLTN_VIEW_BUTTON_ACTION.costVrfc, + ], + ATRZ03: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng], + ATRZ99: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng], }, RK: { + ATRZ01: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng], + ATRZ02: [ + FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng, + FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwCmptn, + FNRS_CNSLTN_VIEW_BUTTON_ACTION.dmndSplmnt, + ], + ATRZ03: [ + FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng, + FNRS_CNSLTN_VIEW_BUTTON_ACTION.atrzAprv, + ], + ATRZ99: [ + FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng, + FNRS_CNSLTN_VIEW_BUTTON_ACTION.atrzRply, + ], + }, + RR: { + ATRZ01: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng], ATRZ02: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng], + ATRZ03: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng], + ATRZ99: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng], + }, + EP: { + ATRZ01: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng], + ATRZ02: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng], + ATRZ03: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng], + ATRZ99: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng], + }, + TA: { + ATRZ01: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng], + ATRZ02: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng], + ATRZ03: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng], + ATRZ99: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng], }, }; @@ -95,10 +192,16 @@ function resolveButtonConfigs(actions: FnrsCnsltnViewButtonAction[]): FnrsCnsltn interface GetButtonConfigsOptions { hasPendingApprovalRequest?: boolean; hasCompletedDraft?: boolean; + canRetryFileVrfc?: boolean; + canRetryDraftGen?: boolean; + canRetryCostVrfc?: boolean; } const PENDING_APPROVAL_ACTIONS = new Set([ - ...ROLE_USER01_ATRZ_STATUS_ACTIONS.TE.ATRZ02, + FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwRsltInq, + FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwStrg, + FNRS_CNSLTN_VIEW_BUTTON_ACTION.dmndSplmnt, + FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwCmptn, FNRS_CNSLTN_VIEW_BUTTON_ACTION.atrzAprv, FNRS_CNSLTN_VIEW_BUTTON_ACTION.atrzRply, ]); @@ -129,15 +232,26 @@ function resolveRoleUser01AtrzButtonActions( const needsPendingApproval = PENDING_APPROVAL_ACTIONS.has(action); if (needsPendingApproval && options?.hasPendingApprovalRequest === false) return false; - if (prgrsSttsComCd === "TE" && options?.hasCompletedDraft != null) { - if ( - action === FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng || - action === FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwRsltInq - ) { - return options.hasCompletedDraft; + if (action === FNRS_CNSLTN_VIEW_BUTTON_ACTION.fileVrfc) { + return options?.canRetryFileVrfc === true; + } + if (action === FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocCrt) { + return options?.canRetryDraftGen === true; + } + if (action === FNRS_CNSLTN_VIEW_BUTTON_ACTION.costVrfc) { + return options?.canRetryCostVrfc === true; + } + + if (options?.hasCompletedDraft != null) { + if (prgrsSttsComCd === "SL" || prgrsSttsComCd === "TE" || prgrsSttsComCd === "RK") { + if (action === FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng) { + return options.hasCompletedDraft; + } } - if (action === FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocCrt) { - return !options.hasCompletedDraft; + if (prgrsSttsComCd === "TE") { + if (action === FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwRsltInq) { + return options.hasCompletedDraft; + } } } diff --git a/apps/as/src/pages/fnrs-cnsltn/lib/fnrs-cnsltn-regist-schema.ts b/apps/as/src/pages/fnrs-cnsltn/lib/fnrs-cnsltn-regist-schema.ts index 0faa372e9..5739c8e6e 100644 --- a/apps/as/src/pages/fnrs-cnsltn/lib/fnrs-cnsltn-regist-schema.ts +++ b/apps/as/src/pages/fnrs-cnsltn/lib/fnrs-cnsltn-regist-schema.ts @@ -1,14 +1,22 @@ import { z } from "zod"; +import { + fnrsCnsltnBizCstItemSchema, + fnrsCnsltnBzScaleItemSchema, + fnrsCnsltnCklstResultItemSchema, + fnrsCnsltnEsntlAtchFileItemSchema, + fnrsCnsltnMngSaveSchema, + fnrsCnsltnSplmntDmndItemSchema, + fnrsSharItemSchema, +} from "@/entities/fnrs-cnsltn"; + export const fnrsCnsltnRegistSchema = z.object({ - fnrsCnsltnMng: z.object({ + fnrsCnsltnMng: fnrsCnsltnMngSaveSchema.extend({ bizNm: z.string().min(1, "사업명은 필수입니다"), bizBgngYr: z.string().min(1, "사업시작연도는 필수입니다"), bizEndYr: z.string().min(1, "사업종료연도는 필수입니다"), bizCn: z.string().min(1, "사업내용은 필수입니다"), taskSeComCd: z.array(z.string()).min(1, "구분은 필수입니다"), - cfmtnBgtBizNo: z.string().optional(), - bgtAplyYr: z.string().optional(), dsgnrNm: z.string().min(1, "설계자 이름은 필수입니다"), dsgnrTelno: z .string() @@ -19,30 +27,11 @@ export const fnrsCnsltnRegistSchema = z.object({ .string() .min(1, "설계자 이메일은 필수입니다") .email("올바른 이메일 형식이 아닙니다"), - aplcntNm: z.string().optional(), - aplcntMblTelno: z.string().optional(), - aplcntOgdpNm: z.string().optional(), - aplcntEmlAddr: z.string().optional(), - fnrsCnsltnDocNo: z.string().optional(), - admdstCd: z.string().optional(), - envoSeComCd: z.string().optional(), - rtlztnFcltSeComCd: z.string().optional(), - rtlztnUsgSeComCd: z.string().optional(), - prgrsSttsComCd: z.string().optional(), - atchFileGroupId: z.string().optional(), - sharAtchFileGroupId: z.string().optional(), - envoSeCdNm: z.string().optional(), - taskSeCdNm: z.string().optional(), - prgrsSttsCdNm: z.string().optional(), sido: z.string().optional(), gugun: z.string().optional(), - regDt: z.string().optional(), - rgtr: z.string().optional(), - rgtrNm: z.string().optional(), - mdfcnDt: z.string().optional(), }), bzScaleList: z.array( - z.object({ + fnrsCnsltnBzScaleItemSchema.extend({ cnstpSeCd: z.string(), bscPl: z.number(), budgetReflct: z.number(), @@ -51,7 +40,7 @@ export const fnrsCnsltnRegistSchema = z.object({ }), ), bizCstList: z.array( - z.object({ + fnrsCnsltnBizCstItemSchema.extend({ acntgSeCd: z.string(), bscPlanAmt: z.number(), bgtRfltAmt: z.number(), @@ -59,7 +48,7 @@ export const fnrsCnsltnRegistSchema = z.object({ }), ), fnrsSharList: z.array( - z.object({ + fnrsSharItemSchema.extend({ fnrsSharSeCd: z.string(), exstnAmt: z.number(), chgRqtAmt: z.number(), @@ -68,37 +57,24 @@ export const fnrsCnsltnRegistSchema = z.object({ }), ), cklstResultList: z.array( - z.object({ + fnrsCnsltnCklstResultItemSchema.extend({ cklstSn: z.number(), cklstArtclSn: z.number(), cklstRvwSn: z.number(), - rvwStpComCd: z.string(), - selected: z.enum(["Y", "N"]).nullable(), - picRvwCn: z.string().nullable().optional(), + selected: z.enum(["Y", "N"]).nullish(), + picRvwCn: z.string().nullish(), }), ), esntlAtchFileList: z.array( - z.object({ + fnrsCnsltnEsntlAtchFileItemSchema.extend({ esntlAtchFileId: z.string(), atchFileGroupId: z.string(), }), ), splmntDmndList: z.array( - z.object({ - splmntDmndSn: z.number().nullish(), - splmntArtclComCd: z.string().nullish(), + fnrsCnsltnSplmntDmndItemSchema.extend({ splmntArtclCdNm: z.string().nullish(), - splmntDmndCn: z.string().optional(), - splmntDmndDt: z.string().optional(), - splmntDmndAtchFileGroupId: z.string().optional(), splmntDmndAtchFileNm: z.string().optional(), - splmntCn: z.string().optional(), - splmntPicNm: z.string().optional(), - splmntCmptnDt: z.string().optional(), - splmntAtchFileGroupId: z.string().optional(), - splmntAtchFileNm: z.string().optional(), - splmntAtchFileAtflId: z.string().optional(), - cmptnYn: z.string().optional(), }), ), privacyConsent: z.enum(["Y", "N"], { diff --git a/apps/as/src/pages/fnrs-cnsltn/lib/form-data-mapper.ts b/apps/as/src/pages/fnrs-cnsltn/lib/form-data-mapper.ts index 09e4817f5..1e5c6902d 100644 --- a/apps/as/src/pages/fnrs-cnsltn/lib/form-data-mapper.ts +++ b/apps/as/src/pages/fnrs-cnsltn/lib/form-data-mapper.ts @@ -1,9 +1,21 @@ import type { IUserInfo } from "@/shared/store"; -import type { FnrsCnsltnMngDetResponseDto } from "@/entities/fnrs-cnsltn"; -import { mapFnrsSharDetToFormList, toEsntlAtchFileSaveList } from "@/widgets/pbadms-sprt"; +import { + type FnrsCnsltnApplySaveRequestDto, + fnrsCnsltnApplySaveSchema, + type FnrsCnsltnMngDetResponseDto, +} from "@/entities/fnrs-cnsltn"; +import { + type CostItemDef, + type FnrsSharItemDef, + mapFnrsSharDetToFormList, + recalculateFnrsSharTotals, + toEsntlAtchFileSaveList, + toFnrsSharSaveList, + validateBizCstSharBalance, +} from "@/widgets/pbadms-sprt"; import type { FnrsCnsltnRegistFormData } from "./fnrs-cnsltn-regist-schema"; -import { parseTaskSeComCdFromApi } from "./fnrs-cnsltn-regist-schema"; +import { formatTaskSeComCdForApi, parseTaskSeComCdFromApi } from "./fnrs-cnsltn-regist-schema"; function mapSelectedValue(value?: string | null): "Y" | "N" | null { if (value === "Y" || value === "N") return value; @@ -47,15 +59,8 @@ export function mapDetailToFormData(data: FnrsCnsltnMngDetResponseDto): FnrsCnsl prgrsSttsComCd: detDto.prgrsSttsComCd, atchFileGroupId: detDto.atchFileGroupId ?? "", sharAtchFileGroupId: detDto.sharAtchFileGroupId ?? "", - envoSeCdNm: detDto.envoSeCdNm, - taskSeCdNm: detDto.taskSeCdNm, - prgrsSttsCdNm: detDto.prgrsSttsCdNm, sido: detDto.sido, gugun: detDto.gugun, - regDt: detDto.regDt, - rgtr: detDto.rgtr, - rgtrNm: detDto.rgtrNm, - mdfcnDt: detDto.mdfcnDt, }, bzScaleList: bizSclList.map((item) => ({ cnstpSeCd: item.cnstpSeCd, @@ -94,8 +99,6 @@ export function mapDetailToFormData(data: FnrsCnsltnMngDetResponseDto): FnrsCnsl splmntPicNm: item.splmntPicNm ?? "", splmntCmptnDt: item.splmntCmptnDt ?? "", splmntAtchFileGroupId: item.splmntAtchFileGroupId ?? "", - splmntAtchFileNm: item.splmntAtchFileInfo?.orgnlFileNm ?? "", - splmntAtchFileAtflId: item.splmntAtchFileInfo?.atflId ?? "", cmptnYn: item.cmptnYn, })) ?? [], privacyConsent: "Y", @@ -146,3 +149,46 @@ export function getCreateDefaultValues( privacyConsent: "N", }; } + +export function buildFormPayload( + data: FnrsCnsltnRegistFormData, + prgrsSttsComCd: string, + options: { + isEditMode: boolean; + docNo?: string; + sharItems: FnrsSharItemDef[]; + costItems: CostItemDef[]; + }, +): FnrsCnsltnApplySaveRequestDto | { validationMessage: string } { + const withTotals = recalculateFnrsSharTotals(data.fnrsSharList, options.sharItems); + const validationMessage = validateBizCstSharBalance( + data.bizCstList, + withTotals, + options.sharItems, + options.costItems, + ); + if (validationMessage) { + return { validationMessage }; + } + + return fnrsCnsltnApplySaveSchema.parse({ + fnrsCnsltnMng: { + ...data.fnrsCnsltnMng, + taskSeComCd: formatTaskSeComCdForApi(data.fnrsCnsltnMng.taskSeComCd), + fnrsCnsltnDocNo: options.isEditMode ? options.docNo : undefined, + prgrsSttsComCd, + }, + bzScaleList: data.bzScaleList, + bizCstList: data.bizCstList, + fnrsSharList: toFnrsSharSaveList(withTotals), + cklstResultList: data.cklstResultList.map((item) => ({ + ...item, + selected: item.selected ?? undefined, + })), + esntlAtchFileList: data.esntlAtchFileList, + splmntDmndList: data.splmntDmndList.map((item) => ({ + ...item, + ...(prgrsSttsComCd !== "SV" && prgrsSttsComCd !== "ST" && { cmptnYn: "Y" }), + })), + }); +} diff --git a/apps/as/src/pages/fnrs-cnsltn/lib/review-step-utils.ts b/apps/as/src/pages/fnrs-cnsltn/lib/review-step-utils.ts index bce365857..0ba84b926 100644 --- a/apps/as/src/pages/fnrs-cnsltn/lib/review-step-utils.ts +++ b/apps/as/src/pages/fnrs-cnsltn/lib/review-step-utils.ts @@ -32,7 +32,10 @@ export function getEditableReviewStep( dtlInstCd?: string, prgrsSttsComCd?: string, ): ReviewStepCode | undefined { - if (dtlInstCd === "ROLE_USER03" && (prgrsSttsComCd === "LE" || prgrsSttsComCd === "TP")) { + if ( + dtlInstCd === "ROLE_USER03" && + (prgrsSttsComCd === "LE" || prgrsSttsComCd === "TP" || prgrsSttsComCd === "RR") + ) { return "ENV"; } diff --git a/apps/as/src/pages/fnrs-cnsltn/lib/save-payload-utils.ts b/apps/as/src/pages/fnrs-cnsltn/lib/save-payload-utils.ts index 39ebd7582..c4d7d5d88 100644 --- a/apps/as/src/pages/fnrs-cnsltn/lib/save-payload-utils.ts +++ b/apps/as/src/pages/fnrs-cnsltn/lib/save-payload-utils.ts @@ -10,6 +10,7 @@ import type { FnrsCnsltnMngResponseDto, FnrsCnsltnSplmntDmndItem, } from "@/entities/fnrs-cnsltn"; +import { fnrsCnsltnApplySaveSchema } from "@/entities/fnrs-cnsltn"; import type { PwrkCnsltnCnsltnResponseDto } from "@/entities/pwrk-cnsltn-mng"; import { mapFnrsSharDetToFormList, @@ -61,14 +62,14 @@ export function buildSavePayload( const cklstResultList = data.cklstResultList ?? []; const esntlFileList = data.esntlFileList ?? []; - return { + return fnrsCnsltnApplySaveSchema.parse({ fnrsCnsltnMng: { ...detDto, prgrsSttsComCd }, bzScaleList: toBzScaleSaveDto(bizSclList), bizCstList: toBizCstSaveDto(bizCstList), cklstResultList: toCklstSaveDto(cklstResultList, checklistFormState, reviewStep), esntlAtchFileList: toEsntlAtchFileSaveList(esntlFileList), fnrsSharList: toFnrsSharSaveList(mapFnrsSharDetToFormList(data.fnrsSharList ?? [])), - }; + }); } export function buildTechReviewReplyPayload( @@ -80,12 +81,12 @@ export function buildTechReviewReplyPayload( reviewOpinionFormData?: ReviewOpinionGetFormDataResult | null, opnrNm?: string, ): FnrsCnsltnApplySaveRequestDto { - return { + return fnrsCnsltnApplySaveSchema.parse({ ...buildSavePayload(data, checklistFormState, reviewStep, prgrsSttsComCd), techReViewSaveCd, rvwStpCd: reviewStep, rvwOpnnList: toRvwOpnnSaveDto(data, reviewOpinionFormData, opnrNm), - }; + }); } export function buildSupplementReqPayload( @@ -107,10 +108,10 @@ export function buildSupplementReqPayload( cmptnYn: "N", })); - return { + return fnrsCnsltnApplySaveSchema.parse({ ...buildSavePayload(data, checklistFormState, "LOCAL", "SL"), splmntDmndList: [...existingItems, ...newItems], - }; + }); } export function buildFileSplmntReqPayload( @@ -124,10 +125,10 @@ export function buildFileSplmntReqPayload( cmptnYn: "N", })); - return { + return fnrsCnsltnApplySaveSchema.parse({ ...buildSavePayload(data, checklistFormState, "LOCAL", "SL"), splmntDmndList: [...newItems], - }; + }); } export function buildRvwRsltSplmntReqPayload( @@ -144,10 +145,10 @@ export function buildRvwRsltSplmntReqPayload( cmptnYn: "N", })); - return { + return fnrsCnsltnApplySaveSchema.parse({ ...buildSavePayload(data, checklistFormState, "LOCAL", "SL"), splmntDmndList: [...existingItems, ...newItems], - }; + }); } export function toSplmntDmndSaveItem(item: FnrsCnsltnSplmntDmndItem): FnrsCnsltnSplmntDmndItem { @@ -169,21 +170,21 @@ export function buildSupplementSubmitPayload( data: FnrsCnsltnMngDetResponseDto, checklistFormState: ChecklistFormState, ): FnrsCnsltnApplySaveRequestDto { - return { + return fnrsCnsltnApplySaveSchema.parse({ ...buildSavePayload(data, checklistFormState, "LOCAL", "SL"), - splmntDmndList: data.splmntDmndList, - }; + splmntDmndList: (data.splmntDmndList ?? []).map(toSplmntDmndSaveItem), + }); } export function buildTechReviewPayload( det: FnrsCnsltnMngResponseDto, cklstResultList: CklstResultItem[], ): FnrsCnsltnApplySaveRequestDto { - return { + return fnrsCnsltnApplySaveSchema.parse({ fnrsCnsltnMng: { ...det, prgrsSttsComCd: "ER" }, bzScaleList: [], bizCstList: [], cklstResultList, esntlAtchFileList: [], - }; + }); } diff --git a/apps/as/src/pages/fnrs-cnsltn/model/columns.tsx b/apps/as/src/pages/fnrs-cnsltn/model/columns.tsx index fff55d21a..2374b0c40 100644 --- a/apps/as/src/pages/fnrs-cnsltn/model/columns.tsx +++ b/apps/as/src/pages/fnrs-cnsltn/model/columns.tsx @@ -12,7 +12,7 @@ const columnHelper = createColumnHelper(); const MAIN_STEP_LABELS = { apply: "재원협의 신청", - envoReview: "환경청 검토", + envoReview: "재원협의 검토", techReview: "기술검토", reply: "검토결과 회신", } as const; @@ -20,7 +20,7 @@ const MAIN_STEP_LABELS = { export const PROGRESS_STATUS_COLUMNS = [ { label: "재원협의 신청 (시군)", sttsComCd: "ST" }, { label: "재원협의 신청 (시도)", sttsComCd: "LE" }, - { label: "환경청 검토", sttsComCd: "ER" }, + { label: "재원협의 검토", sttsComCd: "ER" }, { label: "기술검토", sttsComCd: "TE" }, { label: "기술검토 회신", sttsComCd: "TP" }, { label: "환경청 검토 결과 회신", sttsComCd: "EP" }, @@ -61,7 +61,7 @@ function buildProgressBadges(prgrsSttsComCd: string, techRplyHstryYn: "Y" | "N") ]; } - if (prgrsSttsComCd === "ER" || prgrsSttsComCd === "TE" || prgrsSttsComCd === "SL") { + if (["FV", "ER", "AC", "TE", "RB", "RA", "AV", "EV", "RK", "RR", "SL"].includes(prgrsSttsComCd)) { return [ { label: MAIN_STEP_LABELS.apply, state: "completed" }, { label: MAIN_STEP_LABELS.envoReview, state: "completed" }, @@ -82,7 +82,7 @@ function buildProgressBadges(prgrsSttsComCd: string, techRplyHstryYn: "Y" | "N") ]; } - if (prgrsSttsComCd === "EP") { + if (prgrsSttsComCd === "EP" || prgrsSttsComCd === "TA") { return [ { label: MAIN_STEP_LABELS.apply, state: "completed" }, { label: MAIN_STEP_LABELS.envoReview, state: "completed" }, diff --git a/apps/as/src/pages/fnrs-cnsltn/model/view-types.ts b/apps/as/src/pages/fnrs-cnsltn/model/view-types.ts index db60bd696..c3a21fb94 100644 --- a/apps/as/src/pages/fnrs-cnsltn/model/view-types.ts +++ b/apps/as/src/pages/fnrs-cnsltn/model/view-types.ts @@ -61,16 +61,25 @@ export const TASK_CODE_LABELS: Record = { }; export const PBADMS_PROGRESS_ORDER = [ + // LOCAL "SV", - "SL", - "TA", "ST", + "SL", + "RB", "EP", + "TA", + + // ENV "LE", "TP", + "RA", + "RR", + + // KECO "ER", "TE", - "TB", + "EV", + "RK", ] as const; export const ROLE_TO_REVIEW_STEP: Record = { diff --git a/apps/as/src/pages/fnrs-cnsltn/ui/components/floating-action-buttons.tsx b/apps/as/src/pages/fnrs-cnsltn/ui/components/floating-action-buttons.tsx index 00f14e430..4076a83fa 100644 --- a/apps/as/src/pages/fnrs-cnsltn/ui/components/floating-action-buttons.tsx +++ b/apps/as/src/pages/fnrs-cnsltn/ui/components/floating-action-buttons.tsx @@ -9,11 +9,19 @@ import { AxiosError } from "axios"; import { ENDPOINT } from "@/shared/config"; import { useAlertStore, useUserStore } from "@/shared/lib"; +import { + AI_AUTO_SE, + type AiAutoPrcsLatestResponseDto, + useAiAutoPrcsLatest, +} from "@/entities/ai-auto-prcs-mng"; +import { useReGenerateDraft, useReTransferDocument, useReVerifyCost } from "@/entities/dcrag-to"; import { buildElaprPrgrsSttsListFromAplnRows, ElaprApprovalDialog, type ElaprDmndSaveRequestDto, elaprDmndService, + type ElaprDocPrgrsPickerConfirmPayload, + ElaprDocPrgrsPickerDialog, type ElaprTmpltPickerConfirmPayload, ElaprTmpltPickerDialog, hasCurrentUserPendingApprovalRequest, @@ -39,6 +47,7 @@ import { import { SplmntDmndDialog, type SplmntDmndDialogConfirmPayload } from "@/widgets/pbadms-sprt"; import { Button } from "@common/components"; +import { isRagIndexingInProgress } from "../../lib/ai-pending-utils"; import { FNRS_CNSLTN_VIEW_BUTTON_ACTION, type FnrsCnsltnViewButtonAction, @@ -61,6 +70,15 @@ import { FileVrfcSplmntDialog } from "./file-vrfc-splmnt-dialog"; import { PwrkCnsltnDocMngDialog } from "./pwrk-cnsltn-doc-mng-dialog"; import { PwrkCnsltnRvwRsltInqDialog } from "./pwrk-cnsltn-rvw-rslt-inq-dialog"; +const AI_RETRY_PRGRS_STTS = new Set(["ER", "TE", "EV"]); + +function findLatestByAutoSe( + list: AiAutoPrcsLatestResponseDto[] | undefined, + atmtnSeComCd: string, +): AiAutoPrcsLatestResponseDto | undefined { + return list?.find((item) => item.atmtnSeComCd === atmtnSeComCd); +} + interface FloatingActionButtonsProps { data: FnrsCnsltnMngDetResponseDto; docSeComCd: string; @@ -77,6 +95,7 @@ interface FloatingActionButtonsProps { type ActiveDialog = | "elaprTmplt" + | "elaprDocPrgrs" | "rjct" | "dmndSplmnt" | "fileSplmnt" @@ -110,42 +129,86 @@ function FloatingActionButtons({ const isRegister = data.detDto?.rgtr === userInfo?.userUnqId; const needsAtrzResolve = needsRoleUser01AtrzResolve(dtlInstCd, prgrsSttsComCd); - const needsTeApprovalCheck = needsAtrzResolve && prgrsSttsComCd === "TE"; - const needsPwrkDraftCheck = dtlInstCd === "ROLE_USER01" && ["TE", "RK"].includes(prgrsSttsComCd); + const needsElaprPrgrsCheck = needsAtrzResolve && ["TE", "RK"].includes(prgrsSttsComCd); + const needsPwrkDraftCheck = + dtlInstCd === "ROLE_USER01" && ["SL", "TE", "RK"].includes(prgrsSttsComCd); const needsPwrkForRb = dtlInstCd === "ROLE_USER05" && prgrsSttsComCd === "RB"; - const needsPwrkLookup = needsPwrkDraftCheck || needsPwrkForRb; + const needsPwrkForAiRetry = dtlInstCd === "ROLE_USER01" && ["TE", "EV"].includes(prgrsSttsComCd); + const needsPwrkLookup = needsPwrkDraftCheck || needsPwrkForRb || needsPwrkForAiRetry; + const needsAiAutoLatest = dtlInstCd === "ROLE_USER01" && AI_RETRY_PRGRS_STTS.has(prgrsSttsComCd); + const { data: elaprDocPrgrsList = [], isLoading: isElaprDocPrgrsLoading } = - useElaprDocPrgrsByDocNo(needsTeApprovalCheck ? docNo : null); + useElaprDocPrgrsByDocNo(needsElaprPrgrsCheck ? docNo : null); + const { data: pwrkCnsltnData, isLoading: isPwrkCnsltnLoading } = usePwrkCnsltnMngByUpDocNo( docNo, needsPwrkLookup, ); - const hasCompletedDraft = Boolean(findCompletedPwrkDraft(pwrkCnsltnData)); - const pwrkCnsltnDocNo = pwrkCnsltnData?.pwrkCnsltnDocNo; + const { data: aiAutoLatestList, isLoading: isAiAutoLatestLoading } = useAiAutoPrcsLatest(docNo, { + enabled: needsAiAutoLatest, + }); + + const fileVrfcLatest = findLatestByAutoSe(aiAutoLatestList, AI_AUTO_SE.FILE_VRFC); + const draftGenLatest = findLatestByAutoSe(aiAutoLatestList, AI_AUTO_SE.DRAFT_GEN); + const costVrfcLatest = findLatestByAutoSe(aiAutoLatestList, AI_AUTO_SE.COST_VRFC); + const canRetryFileVrfc = fileVrfcLatest?.isRetryable === true; + const canRetryDraftGen = draftGenLatest?.isRetryable === true; + const canRetryCostVrfc = costVrfcLatest?.isRetryable === true; + + const reTransferDocument = useReTransferDocument(); + const reGenerateDraft = useReGenerateDraft(); + const reVerifyCost = useReVerifyCost(); + const isBusy = + isProcessing || + reTransferDocument.isPending || + reGenerateDraft.isPending || + reVerifyCost.isPending; + + const hasCompletedDraft = Boolean(findCompletedPwrkDraft(pwrkCnsltnData)); + const pwrkCnsltnDocNo = + pwrkCnsltnData?.pwrkCnsltnDocNo ?? + draftGenLatest?.pwrkCnsltnDocNo ?? + costVrfcLatest?.pwrkCnsltnDocNo; + + const elaprPrgrsForPending = + prgrsSttsComCd === "RK" + ? elaprDocPrgrsList.filter((row) => Number(row.elaprCycl) === 2) + : elaprDocPrgrsList; const hasPendingApprovalRequest = hasCurrentUserPendingApprovalRequest( - elaprDocPrgrsList, + elaprPrgrsForPending, userInfo?.userUnqId, ); + const viewOnlyPwrkCnsltnDoc = + prgrsSttsComCd === "SL" || (prgrsSttsComCd === "RB" && dtlInstCd !== "ROLE_USER05"); const actionButtons = useMemo(() => { if (needsAtrzResolve && isRvwLoginUserLoading) return []; - if (needsTeApprovalCheck && isElaprDocPrgrsLoading) return []; + if (needsElaprPrgrsCheck && isElaprDocPrgrsLoading) return []; if (needsPwrkDraftCheck && isPwrkCnsltnLoading) return []; + if (needsAiAutoLatest && isAiAutoLatestLoading) return []; return getButtonConfigs(dtlInstCd, prgrsSttsComCd, atrzSeComCdList, { - hasPendingApprovalRequest: needsTeApprovalCheck ? hasPendingApprovalRequest : undefined, + hasPendingApprovalRequest: needsElaprPrgrsCheck ? hasPendingApprovalRequest : undefined, hasCompletedDraft: needsPwrkDraftCheck ? hasCompletedDraft : undefined, + canRetryFileVrfc: needsAiAutoLatest ? canRetryFileVrfc : undefined, + canRetryDraftGen: needsAiAutoLatest ? canRetryDraftGen : undefined, + canRetryCostVrfc: needsAiAutoLatest ? canRetryCostVrfc : undefined, }); }, [ atrzSeComCdList, + canRetryCostVrfc, + canRetryDraftGen, + canRetryFileVrfc, dtlInstCd, hasCompletedDraft, hasPendingApprovalRequest, + isAiAutoLatestLoading, isElaprDocPrgrsLoading, isPwrkCnsltnLoading, isRvwLoginUserLoading, + needsAiAutoLatest, needsAtrzResolve, - needsTeApprovalCheck, + needsElaprPrgrsCheck, needsPwrkDraftCheck, prgrsSttsComCd, ]); @@ -252,19 +315,6 @@ function FloatingActionButtons({ router.push("/fnrs-cnsltn"); } - async function handleFnrsCnsltnAncmntClick() { - const payload: DocPrgrsUpdateRequestDto = { - docNo, - docSeComCd, - prgrsSttsComCd: "TB", - }; - await executeAction({ - action: FNRS_CNSLTN_VIEW_BUTTON_ACTION.fnrsCnsltnAncmnt, - request: payload, - successMessage: "고시되었습니다.", - }); - } - async function handleEnvoSbmsnClick() { const payload: DocPrgrsUpdateRequestDto = { docNo, @@ -278,19 +328,6 @@ function FloatingActionButtons({ }); } - async function handleLclRplyClick() { - const payload: DocPrgrsUpdateRequestDto = { - docNo, - docSeComCd, - prgrsSttsComCd: "RB", - }; - await executeAction({ - action: FNRS_CNSLTN_VIEW_BUTTON_ACTION.lclRply, - request: payload, - successMessage: "지자체 회신이 완료되었습니다.", - }); - } - async function handleRjctClick() { const payload: DocPrgrsUpdateRequestDto = { docNo, @@ -313,7 +350,7 @@ function FloatingActionButtons({ await executeAction({ action: FNRS_CNSLTN_VIEW_BUTTON_ACTION.sncnAprv, request: payload, - successMessage: "인가승인되었습니다.", + successMessage: "결과 회신이 완료되었습니다.", }); } @@ -329,7 +366,7 @@ function FloatingActionButtons({ ); if (percent !== 100) { - alert("모든 체크리스트 항목을 체크해야 검토결과 회신이 가능합니다."); + alert("모든 체크리스트 항목을 체크해야 검토 결과 회신이 가능합니다."); return; } @@ -337,7 +374,7 @@ function FloatingActionButtons({ data, checklistFormState, "ENV", - "RM", + "EP", "CP", getReviewOpinionFormData(), opnrNm, @@ -346,7 +383,37 @@ function FloatingActionButtons({ await executeAction({ action: FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwRply, request: payload, - successMessage: "재원협의 결과 회신이 완료되었습니다.", + successMessage: "검토 결과 회신이 완료되었습니다.", + }); + techReviewOpinionRef?.current?.resetForm(); + } + + async function handleRvwOpnnRplyClick() { + const { percent } = computeReviewStepChecklistProgress( + data.cklstResultList ?? [], + checklistFormState.results, + "ENV", + ); + + if (percent !== 100) { + alert("모든 체크리스트 항목을 체크해야 검토 의견 회신이 가능합니다."); + return; + } + + const payload = buildTechReviewReplyPayload( + data, + checklistFormState, + "ENV", + "RB", + "CP", + getReviewOpinionFormData(), + opnrNm, + ); + + await executeAction({ + action: FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwOpnnRply, + request: payload, + successMessage: "검토 의견 회신이 완료되었습니다.", }); techReviewOpinionRef?.current?.resetForm(); } @@ -358,7 +425,7 @@ function FloatingActionButtons({ await executeAction({ action: FNRS_CNSLTN_VIEW_BUTTON_ACTION.rqstTechRvw, request: payload, - successMessage: "기술검토 의뢰가 완료되었습니다.", + successMessage: "기술 검토 의뢰가 완료되었습니다.", }); } @@ -367,7 +434,7 @@ function FloatingActionButtons({ await executeAction({ action: FNRS_CNSLTN_VIEW_BUTTON_ACTION.rqstActnRvw, request: payload, - successMessage: "조치결과 검토의뢰가 완료되었습니다.", + successMessage: "조치 결과 검토 의뢰가 완료되었습니다.", }); } @@ -376,7 +443,7 @@ function FloatingActionButtons({ elaprDocNo: docNo, dmndTtl: data.detDto?.bizNm ?? "", dmndCn: data.detDto?.bizCn ?? "", - elaprCycl: data.detDto?.prgrsSttsComCd === "RK" ? 2 : 1, + elaprCycl: 1, docPrgrsSttsComCd: "AC", elaprPrgrsSttsList: buildElaprPrgrsSttsListFromAplnRows(aplnRows), }; @@ -390,6 +457,25 @@ function FloatingActionButtons({ }); } + async function handleElaprDocPrgrsConfirm({ aplnRows }: ElaprDocPrgrsPickerConfirmPayload) { + const payload: ElaprDmndSaveRequestDto = { + elaprDocNo: docNo, + dmndTtl: data.detDto?.bizNm ?? "", + dmndCn: data.detDto?.bizCn ?? "", + elaprCycl: 2, + docPrgrsSttsComCd: "RK", + elaprPrgrsSttsList: buildElaprPrgrsSttsListFromAplnRows(aplnRows), + }; + await executeAction({ + action: FNRS_CNSLTN_VIEW_BUTTON_ACTION.dsgntRvwr, + request: payload, + successMessage: "검토자가 지정되었습니다.", + }); + await queryClient.invalidateQueries({ + queryKey: [ENDPOINT.AS_ELAPR_SERVICE.ELAPR_DMND, "findDocPrgrsByDocNo", docNo], + }); + } + async function handleRvwStrgClick() { const payload = buildTechReviewReplyPayload( data, @@ -410,15 +496,17 @@ function FloatingActionButtons({ } async function handleRvwCmptnClick() { - const { percent } = computeReviewStepChecklistProgress( - data.cklstResultList ?? [], - checklistFormState.results, - "KECO", - ); + if (prgrsSttsComCd === "TE") { + const { percent } = computeReviewStepChecklistProgress( + data.cklstResultList ?? [], + checklistFormState.results, + "KECO", + ); - if (percent !== 100) { - alert("모든 체크리스트 항목을 체크해야 검토완료가 가능합니다."); - return; + if (percent !== 100) { + alert("모든 체크리스트 항목을 체크해야 검토완료가 가능합니다."); + return; + } } const payload = { @@ -426,7 +514,7 @@ function FloatingActionButtons({ data, checklistFormState, "KECO", - "TE", + prgrsSttsComCd, "SV", getReviewOpinionFormData(), opnrNm, @@ -442,17 +530,13 @@ function FloatingActionButtons({ setActiveDialog("elaprApproval"); } - function handleDmndSplmntClick() { - setActiveDialog("dmndSplmnt"); - } - async function handleDmndSplmntConfirm(payload: SplmntDmndDialogConfirmPayload) { setActiveDialog(null); const requestPayload = buildSupplementReqPayload(data, payload, checklistFormState); await executeAction({ action: FNRS_CNSLTN_VIEW_BUTTON_ACTION.dmndSplmnt, request: requestPayload, - successMessage: "보완요청이 처리되었습니다.", + successMessage: "보완 요청이 처리되었습니다.", }); } @@ -466,7 +550,7 @@ function FloatingActionButtons({ await executeAction({ action: FNRS_CNSLTN_VIEW_BUTTON_ACTION.fileSplmnt, request: requestPayload, - successMessage: "파일보완 요청이 처리되었습니다.", + successMessage: "파일 보완 요청이 처리되었습니다.", }); } @@ -476,7 +560,7 @@ function FloatingActionButtons({ await executeAction({ action: FNRS_CNSLTN_VIEW_BUTTON_ACTION.dmndSplmnt, request: requestPayload, - successMessage: "보완요청이 처리되었습니다.", + successMessage: "보완 요청이 처리되었습니다.", }); } @@ -496,17 +580,60 @@ function FloatingActionButtons({ }); } + function handleFileVrfcClick() { + reTransferDocument.mutate( + { fnrsCnsltnDocNo: docNo }, + { onSuccess: () => alert("파일 검증을 요청했습니다.") }, + ); + } + + function handlePwrkCnsltnDocCrtClick() { + if (!pwrkCnsltnDocNo) { + alert("실무협의 문서번호를 찾을 수 없습니다."); + return; + } + + reGenerateDraft.mutate( + { + fnrsCnsltnDocNo: docNo, + pwrkCnsltnDocNo, + crtDmndUuid: draftGenLatest?.crtDmndUuid, + }, + { onSuccess: () => alert("초안 생성을 요청했습니다.") }, + ); + } + + function handleCostVrfcClick() { + if (!pwrkCnsltnDocNo) { + alert("실무협의 문서번호를 찾을 수 없습니다."); + return; + } + + reVerifyCost.mutate( + { + fnrsCnsltnDocNo: docNo, + pwrkCnsltnDocNo, + }, + { onSuccess: () => alert("금액 검증을 요청했습니다.") }, + ); + } + return ( <>
- {!isAiPending && ( <> {shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocCrt) && ( - )} @@ -514,118 +641,137 @@ function FloatingActionButtons({ )} {canEdit && ( - )} {shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.strg) && ( - )} {shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.aply) && ( - )} {shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.del) && ( - )} - {shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.fnrsCnsltnAncmnt) && ( - - )} {shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.envoSbmsn) && ( - )} - {shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.lclRply) && ( - - )} {shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.rjct) && ( - )} {shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.sncnAprv) && ( - + )} + {shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwOpnnRply) && ( + )} {shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwRply) && ( - )} {shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.rqstTechRvw) && ( - )} {shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.rqstActnRvw) && ( - )} {shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.fileSplmnt) && ( - )} - {shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.dsgntRvwr) && ( - + )} + {shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.dsgntRvwr) && + !isRagIndexingInProgress(prgrsSttsComCd, data.ragIndexingInfo) && ( + + )} + {shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.costVrfc) && ( + )} {shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.actnCmptn) && ( - )} {shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwRsltInq) && ( - )} {shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwStrg) && ( - )} {shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwCmptn) && ( - )} {shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.dmndSplmnt) && ( - )} {shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.atrzAprv) && ( - )} {shouldShowButton(FNRS_CNSLTN_VIEW_BUTTON_ACTION.atrzRply) && ( - )} @@ -643,18 +789,25 @@ function FloatingActionButtons({ onConfirm={handleElaprTmpltConfirm} /> + setActiveDialog(null)} + onConfirm={handleElaprDocPrgrsConfirm} + /> + setActiveDialog(null)} - isSubmitting={isProcessing} + isSubmitting={isBusy} onConfirm={handleDmndSplmntConfirm} /> setActiveDialog(null)} onConfirm={handleFileSplmntConfirm} /> @@ -664,6 +817,7 @@ function FloatingActionButtons({ onOpenChange={(open) => setActiveDialog(open ? "elaprApproval" : null)} elaprDocNo={data.detDto?.fnrsCnsltnDocNo ?? fnrsCnsltnDocNo} currentUserUuid={userInfo?.userUnqId ?? ""} + elaprCycl={prgrsSttsComCd === "RK" ? 2 : undefined} onSuccess={() => { queryClient.invalidateQueries({ queryKey: fnrsCnsltnKeys.detail(fnrsCnsltnDocNo), @@ -675,14 +829,19 @@ function FloatingActionButtons({ open={activeDialog === "pwrkCnsltnDocMng"} fnrsCnsltnDocNo={docNo} atrzSeComCdList={atrzSeComCdList} - canSave={prgrsSttsComCd === "RA" ? false : undefined} + prgrsSttsComCd={prgrsSttsComCd} + canSave={ + (needsElaprPrgrsCheck && !hasPendingApprovalRequest) || viewOnlyPwrkCnsltnDoc + ? false + : null + } onClose={() => setActiveDialog(null)} /> setActiveDialog(null)} onConfirm={handleRvwRsltSplmntConfirm} /> @@ -806,28 +965,18 @@ async function handleActnCmptn( } } -async function handleAtrzAprv(context: ActionHandlerContext): Promise { - // TODO: 결재 승인 처리 API 호출 - context.onSuccess?.(); -} - -async function handleAtrzRply(context: ActionHandlerContext): Promise { - // TODO: 결재 회신 처리 API 호출 - context.onSuccess?.(); -} - const buttonActionHandlers: Record = { strg: handleSaveApply, aply: handleSaveApply, del: handleDel, - fnrsCnsltnAncmnt: handleChangePrgrsSttsComCd, envoSbmsn: handleChangePrgrsSttsComCd, rjct: handleChangePrgrsSttsComCd, sncnAprv: handleChangePrgrsSttsComCd, - lclRply: handleChangePrgrsSttsComCd, rvwRply: handleTechReview, + rvwOpnnRply: handleTechReview, rqstTechRvw: handleRequestExamine, fileSplmnt: handleDmndSplmnt, + fileVrfc: () => Promise.resolve(), dsgntRvwr: handleDsgntRvwr, pwrkCnsltnDocCrt: () => Promise.resolve(), pwrkCnsltnDocMng: () => Promise.resolve(), @@ -835,10 +984,11 @@ const buttonActionHandlers: Record = rvwStrg: handleTechReview, rvwCmptn: handleTechReview, dmndSplmnt: handleDmndSplmnt, - atrzAprv: handleAtrzAprv, - atrzRply: handleAtrzRply, + atrzAprv: () => Promise.resolve(), + atrzRply: () => Promise.resolve(), actnCmptn: handleActnCmptn, rqstActnRvw: handleRequestActnReview, + costVrfc: () => Promise.resolve(), }; // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/apps/as/src/pages/fnrs-cnsltn/ui/components/pwrk-cnsltn-doc-mng-dialog.tsx b/apps/as/src/pages/fnrs-cnsltn/ui/components/pwrk-cnsltn-doc-mng-dialog.tsx index f87fbed9b..03363b20d 100644 --- a/apps/as/src/pages/fnrs-cnsltn/ui/components/pwrk-cnsltn-doc-mng-dialog.tsx +++ b/apps/as/src/pages/fnrs-cnsltn/ui/components/pwrk-cnsltn-doc-mng-dialog.tsx @@ -1,17 +1,23 @@ "use client"; -import { useEffect, useState } from "react"; +import { useEffect, useMemo, useState } from "react"; import dynamic from "next/dynamic"; import { useQuery, useQueryClient } from "@tanstack/react-query"; +import { PanelRightClose, PanelRightOpen } from "lucide-react"; import { getDefaultFileKey, useAlertStore, useTus } from "@/shared/lib"; import { AttachmentFileField } from "@/shared/ui"; -import { attachService, parseFilenameFromResponse } from "@/entities/attach"; +import { + attachService, + parseFilenameFromResponse, + useDownloadAttachmentByAtflId, +} from "@/entities/attach"; import { pwrkCnsltnDocCrtRsltSaveRequestSchema, pwrkCnsltnDocCrtRsltService, } from "@/entities/pwrk-cnsltn-doc-crt-rslt"; +import { usePwrkCnsltnDocPjtcoAjmtMngPage } from "@/entities/pwrk-cnsltn-doc-pjtco-ajmt-mng"; import { findCompletedPwrkDraft, pwrkCnsltnMngKeys, @@ -27,6 +33,8 @@ import { Skeleton, } from "@common/components"; +import { PwrkCnsltnDocPjtcoAjmtTable } from "./pwrk-cnsltn-doc-pjtco-ajmt-table"; + const HangulGiangi = dynamic( () => import("@/widgets/hangulgiangi").then((module) => module.HangulGiangi), { @@ -39,11 +47,14 @@ const PWRK_ATCH_FILE_KEY_PREFIX = "pwrk-cnsltn-doc::"; const APPROVER_ATRZ_SE_COM_CDS = new Set(["ATRZ03", "ATRZ99"]); +const VIEW_ONLY_PRGRS_STTS_COM_CDS = new Set(["TP", "RA", "AV", "EV", "RR", "EP", "TA"]); + interface PwrkCnsltnDocMngDialogProps { open: boolean; fnrsCnsltnDocNo: string; - atrzSeComCdList: string[]; - canSave?: boolean; + atrzSeComCdList?: string[]; + prgrsSttsComCd?: string; + canSave?: boolean | null; onClose: () => void; } @@ -62,6 +73,7 @@ function PwrkCnsltnDocMngDialog({ open, fnrsCnsltnDocNo, atrzSeComCdList, + prgrsSttsComCd, canSave: canSaveProp, onClose, }: PwrkCnsltnDocMngDialogProps) { @@ -69,6 +81,8 @@ function PwrkCnsltnDocMngDialog({ const alert = useAlertStore((state) => state.setMessage); const [atchFileGroupId, setAtchFileGroupId] = useState(""); const [uploadedFiles, setUploadedFiles] = useState([]); + const [isPjtcoAjmtTableOpen, setIsPjtcoAjmtTableOpen] = useState(false); + const { mutate: downloadByAtflId } = useDownloadAttachmentByAtflId(); const { data: pwrkCnsltnData, @@ -92,6 +106,20 @@ function PwrkCnsltnDocMngDialog({ const documentFileName = `실무협의서_${pwrkCnsltnData?.pwrkCnsltnDocNo ?? crtDmndUuid}.hwpx`; const isLoading = isPwrkCnsltnLoading || isDocumentLoading; const isError = isPwrkCnsltnError || isDocumentError; + const pjtcoAjmtSearchParams = useMemo( + () => ({ + pwrkCnsltnDocNo: pwrkCnsltnData?.pwrkCnsltnDocNo, + page: 0, + size: 1000, + sort: "rvwOpnnSn,asc", + }), + [pwrkCnsltnData?.pwrkCnsltnDocNo], + ); + const { + data: pjtcoAjmtPage, + isLoading: isPjtcoAjmtLoading, + isError: isPjtcoAjmtError, + } = usePwrkCnsltnDocPjtcoAjmtMngPage(pjtcoAjmtSearchParams, open); const { uploadFileAsync, isUploading, uploads } = useTus({ onSuccess: (atflCd, atflId, _file, fileKey) => { @@ -234,8 +262,11 @@ function PwrkCnsltnDocMngDialog({ } const isAnyAtchUploading = uploadedFiles.some((item) => isUploading[item.fileKey]); - const canSaveByAtrz = !atrzSeComCdList.some((code) => APPROVER_ATRZ_SE_COM_CDS.has(code)); - const canSave = canSaveProp ?? canSaveByAtrz; + const viewOnly = prgrsSttsComCd ? VIEW_ONLY_PRGRS_STTS_COM_CDS.has(prgrsSttsComCd) : false; + const canSaveByAtrz = atrzSeComCdList + ? !atrzSeComCdList.some((code) => APPROVER_ATRZ_SE_COM_CDS.has(code)) + : true; + const canSave = !viewOnly && (canSaveProp ?? canSaveByAtrz); return ( @@ -258,17 +289,54 @@ function PwrkCnsltnDocMngDialog({ 생성 완료된 실무협의서 초안이 없습니다.

) : null} - {completedDraft ? ( - documentBlob ? ( - - ) : null + {completedDraft && documentBlob ? ( +
+
+ +
+ {pjtcoAjmtPage?.content?.length ? ( + isPjtcoAjmtTableOpen ? ( +
+
+

금액검증결과

+ +
+
+ +
+
+ ) : ( + + ) + ) : null} +
) : null} {completedDraft ? ( @@ -281,6 +349,7 @@ function PwrkCnsltnDocMngDialog({ files={uploadedFiles.map((item) => ({ fileKey: item.fileKey, fileName: item.fileName, + onDownload: item.atflId ? () => downloadByAtflId(item.atflId!) : undefined, }))} inputId="pwrk-cnsltn-doc-atch-file-input" isUploadingByKey={isUploading} diff --git a/apps/as/src/pages/fnrs-cnsltn/ui/components/pwrk-cnsltn-doc-pjtco-ajmt-table.tsx b/apps/as/src/pages/fnrs-cnsltn/ui/components/pwrk-cnsltn-doc-pjtco-ajmt-table.tsx new file mode 100644 index 000000000..bd1cf6120 --- /dev/null +++ b/apps/as/src/pages/fnrs-cnsltn/ui/components/pwrk-cnsltn-doc-pjtco-ajmt-table.tsx @@ -0,0 +1,107 @@ +"use client"; + +import type { PwrkCnsltnDocPjtcoAjmtMngResponseDto } from "@/entities/pwrk-cnsltn-doc-pjtco-ajmt-mng"; +import { fmtNumber } from "@/widgets/pbadms-sprt"; +import { + Skeleton, + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@common/components"; + +interface PwrkCnsltnDocPjtcoAjmtTableProps { + rows: PwrkCnsltnDocPjtcoAjmtMngResponseDto[]; + isLoading: boolean; + isError: boolean; +} + +function PwrkCnsltnDocPjtcoAjmtTable({ + rows, + isLoading, + isError, +}: PwrkCnsltnDocPjtcoAjmtTableProps) { + if (isLoading) { + return ( +
+ + + +
+ ); + } + + if (isError) { + return ( +

+ 사업비조정 정보를 불러오지 못했습니다. +

+ ); + } + + return ( + + + + + + + + + 구분 + 검증결과 + +
+ 검증금액 + (단위: 백만 원) +
+
+
+
+ + {rows.length === 0 ? ( + + + 조회된 사업비조정 항목이 없습니다. + + + ) : ( + rows.map((row) => ( + + {formatDivision(row)} + +

{formatVrfcCn(row.vrfcCn)}

+
+ {formatVrfcAmt(row.vrfcTpcoAmt)} +
+ )) + )} +
+
+ ); +} + +function getPjtcoAjmtRowKey(row: PwrkCnsltnDocPjtcoAjmtMngResponseDto) { + return [row.pwrkCnsltnDocNo, row.rvwOpnnSn, row.acntgSeCd].join("::"); +} + +function formatDivision(row: PwrkCnsltnDocPjtcoAjmtMngResponseDto) { + const label = row.acntgSeCdNm?.trim() || row.acntgSeCd?.trim(); + return label || "-"; +} + +function formatVrfcCn(value: string | undefined) { + const text = value?.trim(); + return text || "-"; +} + +function formatVrfcAmt(value: number | undefined) { + if (value === undefined || value === null) return "-"; + const formatted = fmtNumber(value); + return formatted || "-"; +} + +export { PwrkCnsltnDocPjtcoAjmtTable }; +export type { PwrkCnsltnDocPjtcoAjmtTableProps }; diff --git a/apps/as/src/pages/fnrs-cnsltn/ui/components/pwrk-cnsltn-rvw-rslt-inq-dialog.tsx b/apps/as/src/pages/fnrs-cnsltn/ui/components/pwrk-cnsltn-rvw-rslt-inq-dialog.tsx index e811873f4..91a5896ff 100644 --- a/apps/as/src/pages/fnrs-cnsltn/ui/components/pwrk-cnsltn-rvw-rslt-inq-dialog.tsx +++ b/apps/as/src/pages/fnrs-cnsltn/ui/components/pwrk-cnsltn-rvw-rslt-inq-dialog.tsx @@ -96,7 +96,7 @@ function PwrkCnsltnRvwRsltInqDialog({ aria-describedby={undefined} > - 검토결과조회 + 검토 결과 조회 {isLoading ? ( @@ -159,9 +159,9 @@ function CnsltnListTable({ return ( - - - + + + @@ -172,7 +172,7 @@ function CnsltnListTable({ aria-label="전체 선택" /> - 분류 + 구분 검토의견 @@ -194,8 +194,8 @@ function CnsltnListTable({ aria-label="행 선택" /> - -

{formatOptional(row.rmrk)}

+ +

{formatOptional(row.rvwOpnnSn)}

{formatOptional(row.rvwOpnn)}

diff --git a/apps/as/src/pages/fnrs-cnsltn/ui/fnrs-form-page.tsx b/apps/as/src/pages/fnrs-cnsltn/ui/fnrs-form-page.tsx index 671b70f98..d7af093a1 100644 --- a/apps/as/src/pages/fnrs-cnsltn/ui/fnrs-form-page.tsx +++ b/apps/as/src/pages/fnrs-cnsltn/ui/fnrs-form-page.tsx @@ -9,11 +9,10 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { useQueryClient } from "@tanstack/react-query"; import { useAlertStore, useUserStore } from "@/shared/lib"; -import { SectionCollapsibleGroup, SectionCollapsibleToggle } from "@/shared/ui"; +import { RhfDevTool, SectionCollapsibleGroup, SectionCollapsibleToggle } from "@/shared/ui"; import { useAcntgSeMngTree } from "@/entities/acntg"; import { useEsntlAtchFileMngTree } from "@/entities/esntl"; import { - type FnrsCnsltnApplySaveRequestDto, fnrsCnsltnKeys, type FnrsCnsltnMngDetResponseDto, fnrsCnsltnMngService, @@ -32,21 +31,19 @@ import { isFormEsntlAtchComplete, PrivacyConsentSection, ProgressStatusSection, - recalculateFnrsSharTotals, SplmntDmndFormSection, - toFnrsSharSaveList, - validateBizCstSharBalance, } from "@/widgets/pbadms-sprt"; import { Button } from "@common/components"; +import { type FnrsCnsltnRegistFormData, fnrsCnsltnRegistSchema } from "../lib/fnrs-cnsltn-regist-schema"; import { - type FnrsCnsltnRegistFormData, - fnrsCnsltnRegistSchema, - formatTaskSeComCdForApi, -} from "../lib/fnrs-cnsltn-regist-schema"; -import { getCreateDefaultValues, mapDetailToFormData } from "../lib/form-data-mapper"; + buildFormPayload, + getCreateDefaultValues, + mapDetailToFormData, +} from "../lib/form-data-mapper"; import { PROGRESS_STATUS_COLUMNS } from "../model/columns"; +import { PwrkCnsltnDocMngDialog } from "./components/pwrk-cnsltn-doc-mng-dialog"; import { BasicInfoFormSection } from "./sections/basic-info-form-section"; interface FnrsFormPageProps { mode: "create" | "edit"; @@ -66,8 +63,10 @@ function FnrsFormPage({ mode, initialData, createDefaults }: FnrsFormPageProps) const userInfo = useUserStore((state) => state.userInfo); const alert = useAlertStore((state) => state.setMessage); const [isSubmitting, setIsSubmitting] = useState(false); + const [isPwrkCnsltnDocMngOpen, setIsPwrkCnsltnDocMngOpen] = useState(false); const isEditMode = mode === "edit"; const isSplmntPrgrsStts = initialData?.detDto?.prgrsSttsComCd === "SL"; + const fnrsCnsltnDocNo = initialData?.detDto?.fnrsCnsltnDocNo; const { data: masterSharTree = [] } = useFnrsSharSeMngTree(); const { data: acntgTree = [] } = useAcntgSeMngTree(); @@ -86,54 +85,25 @@ function FnrsFormPage({ mode, initialData, createDefaults }: FnrsFormPageProps) : getCreateDefaultValues(userInfo, createDefaults), }); - function buildFormPayload( - data: FnrsCnsltnRegistFormData, - prgrsSttsComCd: string, - ): FnrsCnsltnApplySaveRequestDto | undefined { - const withTotals = recalculateFnrsSharTotals(data.fnrsSharList, sharItems); - const validationMessage = validateBizCstSharBalance( - data.bizCstList, - withTotals, + function createFormPayload(data: FnrsCnsltnRegistFormData, prgrsSttsComCd: string) { + const result = buildFormPayload(data, prgrsSttsComCd, { + isEditMode, + docNo: initialData?.detDto?.fnrsCnsltnDocNo, sharItems, costItems, - ); - if (validationMessage) { - alert(validationMessage); + }); + if ("validationMessage" in result) { + alert(result.validationMessage); return undefined; } - - const { taskSeComCd: formTaskSeComCd, ...fnrsCnsltnMngRest } = data.fnrsCnsltnMng; - - return { - fnrsCnsltnMng: { - ...fnrsCnsltnMngRest, - taskSeComCd: formatTaskSeComCdForApi(formTaskSeComCd), - fnrsCnsltnDocNo: isEditMode ? initialData?.detDto?.fnrsCnsltnDocNo : undefined, - prgrsSttsComCd, - }, - bzScaleList: data.bzScaleList, - bizCstList: data.bizCstList, - fnrsSharList: toFnrsSharSaveList(withTotals), - cklstResultList: data.cklstResultList.map((item) => ({ - ...item, - selected: item.selected ?? undefined, - })), - esntlAtchFileList: data.esntlAtchFileList, - splmntDmndList: data.splmntDmndList.map( - // eslint-disable-next-line @typescript-eslint/no-unused-vars - ({ splmntAtchFileNm, splmntAtchFileAtflId, splmntDmndAtchFileNm, ...item }) => ({ - ...item, - ...(prgrsSttsComCd !== "SV" && prgrsSttsComCd !== "ST" && { cmptnYn: "Y" }), - }), - ), - }; + return result; } async function submitForm( data: FnrsCnsltnRegistFormData, prgrsSttsComCd: "SV" | "ST", ): Promise { - const payload = buildFormPayload(data, prgrsSttsComCd); + const payload = createFormPayload(data, prgrsSttsComCd); if (!payload) return undefined; return fnrsCnsltnMngService.saveApply(payload); } @@ -143,7 +113,7 @@ function FnrsFormPage({ mode, initialData, createDefaults }: FnrsFormPageProps) if (!beforePrgrsSttsComCd) return undefined; const docNo = initialData?.detDto?.fnrsCnsltnDocNo; - const payload = buildFormPayload(data, beforePrgrsSttsComCd); + const payload = createFormPayload(data, beforePrgrsSttsComCd); if (!docNo || !payload) return undefined; return fnrsCnsltnMngService.submitSupplement(docNo, payload); @@ -156,7 +126,7 @@ function FnrsFormPage({ mode, initialData, createDefaults }: FnrsFormPageProps) (item) => !item.splmntCn || !item.splmntCn.trim(), ); if (hasEmptySplmntCn) { - alert("보완요청내용이 있습니다. 보완완료내용을 입력해 주세요."); + alert("보완요청내용이 있습니다. 보완 완료내용을 입력해 주세요."); return; } @@ -171,12 +141,12 @@ function FnrsFormPage({ mode, initialData, createDefaults }: FnrsFormPageProps) const result = await submitSupplementForm(data); if (!result) return; - alert("보완완료 처리되었습니다."); + alert("보완 완료 처리되었습니다."); await queryClient.invalidateQueries({ queryKey: fnrsCnsltnKeys.all }); router.push(`/fnrs-cnsltn/${docNo}`); } catch (error) { - console.error("보완완료 실패:", error); - alert("보완완료 처리 중 오류가 발생했습니다."); + console.error("보완 완료 실패:", error); + alert("보완 완료 처리 중 오류가 발생했습니다."); } finally { setIsSubmitting(false); } @@ -329,19 +299,40 @@ function FnrsFormPage({ mode, initialData, createDefaults }: FnrsFormPageProps) )} ) : ( - + <> + {fnrsCnsltnDocNo ? ( + + ) : null} + + )} + {fnrsCnsltnDocNo ? ( + setIsPwrkCnsltnDocMngOpen(false)} + /> + ) : null} + ); } diff --git a/apps/as/src/pages/fnrs-cnsltn/ui/fnrs-view-page.tsx b/apps/as/src/pages/fnrs-cnsltn/ui/fnrs-view-page.tsx index 2e85018b0..e0b0bb323 100644 --- a/apps/as/src/pages/fnrs-cnsltn/ui/fnrs-view-page.tsx +++ b/apps/as/src/pages/fnrs-cnsltn/ui/fnrs-view-page.tsx @@ -14,7 +14,7 @@ import { } from "@/widgets/pbadms-sprt"; import { Skeleton } from "@common/components"; -import { getAiPendingMessage, isAiPendingStatus } from "../lib/ai-pending-utils"; +import { getAiPendingMessage, isAiPendingStatus, isRagIndexingInProgress } from "../lib/ai-pending-utils"; import { needsRoleUser01AtrzResolve } from "../lib/button-config"; import { getEditableReviewStep } from "../lib/review-step-utils"; import { EMPTY_CKLST_RESULT_LIST, useChecklistFormState } from "../lib/use-checklist-form-state"; @@ -95,20 +95,25 @@ function FnrsViewPage({ id }: FnrsViewPageProps) { detDto.prgrsSttsComCd || "", ); const isAiPending = isAiPendingStatus(detDto.prgrsSttsComCd || ""); + const isRagIndexing = isRagIndexingInProgress(detDto.prgrsSttsComCd || "", data.ragIndexingInfo); + const showPendingBanner = isAiPending || isRagIndexing; + const pendingMessage = isRagIndexing + ? (data.ragIndexingInfo?.message ?? "") + : getAiPendingMessage(detDto.prgrsSttsComCd || ""); return (
- {isAiPending && ( + {showPendingBanner && (
- {getAiPendingMessage(detDto.prgrsSttsComCd || "")} + {pendingMessage}
)} diff --git a/apps/as/src/pages/fnrs-cnsltn/ui/sections/tech-review-opinion-section.tsx b/apps/as/src/pages/fnrs-cnsltn/ui/sections/tech-review-opinion-section.tsx index caa4a3ada..b00dfce22 100644 --- a/apps/as/src/pages/fnrs-cnsltn/ui/sections/tech-review-opinion-section.tsx +++ b/apps/as/src/pages/fnrs-cnsltn/ui/sections/tech-review-opinion-section.tsx @@ -2,13 +2,14 @@ import { forwardRef, useEffect, useImperativeHandle, useState } from "react"; -import { Paperclip } from "lucide-react"; - -import { PROXY_HOST } from "@/shared/config"; import { dayjs, getDefaultFileKey, useTus } from "@/shared/lib"; import { AttachmentFileField, SectionCollapsible } from "@/shared/ui"; import { Field, FieldLabel, FieldSet } from "@/shared/ui/field"; -import { attachService } from "@/entities/attach"; +import { + AttachmentDownloadLink, + attachService, + useDownloadAttachmentByAtflId, +} from "@/entities/attach"; import type { FnrsCnsltnRvwOpnnResponseDto } from "@/entities/fnrs-cnsltn"; import { RadioGroup, @@ -82,6 +83,7 @@ const TechReviewOpinionSection = forwardRef< opnrSeComCd: editableReviewStep ?? "", }); const [uploadedFile, setUploadedFile] = useState(null); + const { mutate: downloadByAtflId } = useDownloadAttachmentByAtflId(); useEffect(() => { setFormData((prev) => ({ @@ -136,8 +138,7 @@ const TechReviewOpinionSection = forwardRef< const kecoRvwList = rvwList.filter((item) => item.opnrSeComCd === "KECO"); const hasEnvSection = envRvwList.length > 0 || editableReviewStep === "ENV"; - const hasKecoSection = - kecoRvwList.length > 0 || (editableReviewStep === "KECO" && canWriteKecoOpinion); + const hasKecoSection = kecoRvwList.length > 0 || editableReviewStep === "KECO"; if (!hasEnvSection && !hasKecoSection) { return null; @@ -247,7 +248,15 @@ const TechReviewOpinionSection = forwardRef< downloadByAtflId(uploadedFile.atflId!) + : undefined, + }, + ] : [] } inputId={`rvw-opnn-${reviewStep}-file-input`} @@ -321,15 +330,10 @@ function TechReviewOpinionTable({ items }: TechReviewOpinionTableProps) { {item.atchFileInfo?.atflCd ? ( - - - {item.atchFileInfo.orgnlFileNm || "다운로드"} - + ) : ( "-" )} diff --git a/apps/as/src/pages/instl-sncn/lib/form-data-mapper.ts b/apps/as/src/pages/instl-sncn/lib/form-data-mapper.ts index 4dfdf8685..5a3797918 100644 --- a/apps/as/src/pages/instl-sncn/lib/form-data-mapper.ts +++ b/apps/as/src/pages/instl-sncn/lib/form-data-mapper.ts @@ -1,9 +1,13 @@ import type { IUserInfo } from "@/shared/store/user-store"; -import type { InstlSncnMngDetResponse } from "@/entities/instl-sncn"; +import { + type InstlSncnApplySaveRequestDto, + instlSncnApplySaveSchema, + type InstlSncnMngDetResponse, +} from "@/entities/instl-sncn"; import { toEsntlAtchFileSaveList } from "@/widgets/pbadms-sprt"; import type { InstlSncnRegistFormData } from "./instl-sncn-regist-schema"; -import { parseTaskSeComCdFromApi } from "./instl-sncn-regist-schema"; +import { formatTaskSeComCdForApi, parseTaskSeComCdFromApi } from "./instl-sncn-regist-schema"; function mapSelectedValue(value?: string | null): "Y" | "N" | null { if (value === "Y" || value === "N") return value; @@ -16,12 +20,12 @@ export function mapDetailToFormData(data: InstlSncnMngDetResponse): InstlSncnReg return { instlSncnMng: { instlSncnDocNo: detDto.instlSncnDocNo, - bizNm: detDto.bizNm, - bizBgngYr: detDto.bizBgngYr, - bizEndYr: detDto.bizEndYr, - bizCn: detDto.bizCn, + bizNm: detDto.bizNm ?? "", + bizBgngYr: detDto.bizBgngYr ?? "", + bizEndYr: detDto.bizEndYr ?? "", + bizCn: detDto.bizCn ?? "", taskSeComCd: parseTaskSeComCdFromApi(detDto.taskSeComCd), - sncnSeComCd: detDto.sncnSeComCd, + sncnSeComCd: detDto.sncnSeComCd ?? "", dsgnrNm: detDto.dsgnrNm ?? "", dsgnrTelno: detDto.dsgnrTelno ?? "", dsgnrOgdpNm: detDto.dsgnrOgdpNm ?? "", @@ -38,16 +42,8 @@ export function mapDetailToFormData(data: InstlSncnMngDetResponse): InstlSncnReg atchFileGroupId: detDto.atchFileGroupId ?? "", rtlztnFcltSeComCd: detDto.rtlztnFcltSeComCd ?? "", rtlztnUsgSeComCd: detDto.rtlztnUsgSeComCd ?? "", - envoSeCdNm: detDto.envoSeCdNm, - sncnSeCdNm: detDto.sncnSeCdNm, - taskSeCdNm: detDto.taskSeCdNm, - prgrsSttsCdNm: detDto.prgrsSttsCdNm, sido: detDto.sido, gugun: detDto.gugun, - regDt: detDto.regDt, - rgtr: detDto.rgtr, - rgtrNm: detDto.rgtrNm, - mdfcnDt: detDto.mdfcnDt, }, bzScaleList: bizSclList.map((item) => ({ cnstpSeCd: item.cnstpSeCd, @@ -85,8 +81,6 @@ export function mapDetailToFormData(data: InstlSncnMngDetResponse): InstlSncnReg splmntPicNm: item.splmntPicNm ?? "", splmntCmptnDt: item.splmntCmptnDt ?? "", splmntAtchFileGroupId: item.splmntAtchFileGroupId ?? "", - splmntAtchFileNm: item.splmntAtchFileInfo?.orgnlFileNm ?? "", - splmntAtchFileAtflId: item.splmntAtchFileInfo?.atflId ?? "", cmptnYn: item.cmptnYn, })) ?? [], privacyConsent: "Y", @@ -132,3 +126,29 @@ export function getCreateDefaultValues( privacyConsent: "N", }; } + +export function buildFormPayload( + data: InstlSncnRegistFormData, + prgrsSttsComCd: "SV" | "ST" | "TE", + options: { isEditMode: boolean; docNo?: string }, +): InstlSncnApplySaveRequestDto { + return instlSncnApplySaveSchema.parse({ + instlSncnMng: { + ...data.instlSncnMng, + instlSncnDocNo: options.isEditMode ? options.docNo : undefined, + taskSeComCd: formatTaskSeComCdForApi(data.instlSncnMng.taskSeComCd), + prgrsSttsComCd, + }, + bzScaleList: data.bzScaleList, + bizCstList: data.bizCstList, + cklstResultList: data.cklstResultList.map((item) => ({ + ...item, + selected: item.selected ?? undefined, + })), + esntlAtchFileList: data.esntlAtchFileList, + splmntDmndList: data.splmntDmndList.map((item) => ({ + ...item, + ...(prgrsSttsComCd === "TE" ? { cmptnYn: "Y" } : null), + })), + }); +} diff --git a/apps/as/src/pages/instl-sncn/lib/instl-sncn-regist-schema.ts b/apps/as/src/pages/instl-sncn/lib/instl-sncn-regist-schema.ts index 755d3df63..befd61c4e 100644 --- a/apps/as/src/pages/instl-sncn/lib/instl-sncn-regist-schema.ts +++ b/apps/as/src/pages/instl-sncn/lib/instl-sncn-regist-schema.ts @@ -1,7 +1,16 @@ import { z } from "zod"; +import { + instlSncnBizCstItemSchema, + instlSncnBzScaleItemSchema, + instlSncnCklstResultItemSchema, + instlSncnEsntlAtchFileItemSchema, + instlSncnMngSaveSchema, + instlSncnSplmntDmndItemSchema, +} from "@/entities/instl-sncn"; + export const instlSncnRegistSchema = z.object({ - instlSncnMng: z.object({ + instlSncnMng: instlSncnMngSaveSchema.extend({ bizNm: z.string().min(1, "사업명은 필수입니다"), bizBgngYr: z.string().min(1, "사업시작연도는 필수입니다"), bizEndYr: z.string().min(1, "사업종료연도는 필수입니다"), @@ -18,32 +27,13 @@ export const instlSncnRegistSchema = z.object({ .string() .min(1, "설계자 이메일은 필수입니다") .email("올바른 이메일 형식이 아닙니다"), - bgtBizCd: z.string().optional(), - bgtAplyYr: z.string().optional(), - aplcntNm: z.string().optional(), - aplcntMblTelno: z.string().optional(), - aplcntOgdpNm: z.string().optional(), - aplcntEmlAddr: z.string().optional(), instlSncnDocNo: z.string().nullish(), - admdstCd: z.string().optional(), - envoSeComCd: z.string().optional(), prgrsSttsComCd: z.string().nullish(), - atchFileGroupId: z.string().optional(), - rtlztnFcltSeComCd: z.string().optional(), - rtlztnUsgSeComCd: z.string().optional(), - envoSeCdNm: z.string().nullish(), - sncnSeCdNm: z.string().nullish(), - taskSeCdNm: z.string().nullish(), - prgrsSttsCdNm: z.string().nullish(), sido: z.string().nullish(), gugun: z.string().nullish(), - regDt: z.string().nullish(), - rgtr: z.string().nullish(), - rgtrNm: z.string().nullish(), - mdfcnDt: z.string().nullish(), }), bzScaleList: z.array( - z.object({ + instlSncnBzScaleItemSchema.extend({ cnstpSeCd: z.string(), bscPl: z.number(), budgetReflct: z.number(), @@ -52,7 +42,7 @@ export const instlSncnRegistSchema = z.object({ }), ), bizCstList: z.array( - z.object({ + instlSncnBizCstItemSchema.extend({ acntgSeCd: z.string(), bscPlanAmt: z.number(), bgtRfltAmt: z.number(), @@ -60,37 +50,25 @@ export const instlSncnRegistSchema = z.object({ }), ), cklstResultList: z.array( - z.object({ + instlSncnCklstResultItemSchema.extend({ cklstSn: z.number(), cklstArtclSn: z.number(), cklstRvwSn: z.number(), rvwStpComCd: z.string(), - selected: z.enum(["Y", "N"]).nullable(), + selected: z.enum(["Y", "N"]).nullish(), picRvwCn: z.string().nullish(), }), ), esntlAtchFileList: z.array( - z.object({ + instlSncnEsntlAtchFileItemSchema.extend({ esntlAtchFileId: z.string(), atchFileGroupId: z.string(), }), ), splmntDmndList: z.array( - z.object({ - splmntDmndSn: z.number().nullish(), - splmntArtclComCd: z.string().nullish(), + instlSncnSplmntDmndItemSchema.extend({ splmntArtclCdNm: z.string().nullish(), - splmntDmndCn: z.string().optional(), - splmntDmndDt: z.string().optional(), - splmntDmndAtchFileGroupId: z.string().optional(), splmntDmndAtchFileNm: z.string().optional(), - splmntCn: z.string().optional(), - splmntPicNm: z.string().optional(), - splmntCmptnDt: z.string().optional(), - splmntAtchFileGroupId: z.string().optional(), - splmntAtchFileNm: z.string().optional(), - splmntAtchFileAtflId: z.string().optional(), - cmptnYn: z.string().optional(), }), ), privacyConsent: z.enum(["Y", "N"], { diff --git a/apps/as/src/pages/instl-sncn/lib/save-payload-utils.ts b/apps/as/src/pages/instl-sncn/lib/save-payload-utils.ts index f2d647aea..cd810c44b 100644 --- a/apps/as/src/pages/instl-sncn/lib/save-payload-utils.ts +++ b/apps/as/src/pages/instl-sncn/lib/save-payload-utils.ts @@ -8,6 +8,7 @@ import type { InstlSncnSplmntDmndSaveDto, ReviewStepCode, } from "@/entities/instl-sncn"; +import { instlSncnApplySaveSchema } from "@/entities/instl-sncn"; import { type SplmntDmndDialogConfirmPayload, toEsntlAtchFileSaveList } from "@/widgets/pbadms-sprt"; import type { ChecklistFormState } from "../model/view-types"; @@ -23,7 +24,6 @@ export function toBzScaleSaveDto(bizSclList: BizSclTreeDto[]): InstlSncnBzScaleS bscPl: node.bscPlanQty, budgetReflct: node.bgtRfltQty, thtiDsgn: node.tstmDesignQty, - diff: node.tstmDesignQty - node.bgtRfltQty, })); } @@ -46,13 +46,13 @@ export function buildSavePayload( ): InstlSncnApplySaveRequestDto { const { detDto, bizSclList, bizCstList, cklstResultList = [], esntlFileList } = data; - return { + return instlSncnApplySaveSchema.parse({ instlSncnMng: { ...detDto, prgrsSttsComCd }, bzScaleList: toBzScaleSaveDto(bizSclList), bizCstList: toBizCstSaveDto(bizCstList), cklstResultList: toCklstSaveDto(cklstResultList, checklistFormState, reviewStep), esntlAtchFileList: toEsntlAtchFileSaveList(esntlFileList), - }; + }); } export function buildTechReviewReplyPayload( @@ -64,12 +64,12 @@ export function buildTechReviewReplyPayload( reviewOpinionFormData?: ReviewOpinionFormData | null, opnrNm?: string, ): InstlSncnApplySaveRequestDto { - return { + return instlSncnApplySaveSchema.parse({ ...buildSavePayload(data, checklistFormState, reviewStep, prgrsSttsComCd), techReViewSaveCd, rvwStpCd: reviewStep, rvwOpnnList: toRvwOpnnSaveDto(data, reviewOpinionFormData, opnrNm), - }; + }); } export function buildSupplementReqPayload( @@ -91,10 +91,10 @@ export function buildSupplementReqPayload( cmptnYn: "N", })); - return { + return instlSncnApplySaveSchema.parse({ ...buildSavePayload(data, checklistFormState, "LOCAL", "SL"), splmntDmndList: [...existingItems, ...newItems], - }; + }); } export function toSplmntDmndSaveItem(item: InstlSncnSplmntDmndSaveDto): InstlSncnSplmntDmndSaveDto { diff --git a/apps/as/src/pages/instl-sncn/model/columns.tsx b/apps/as/src/pages/instl-sncn/model/columns.tsx index 2dcca5c58..11d51de65 100644 --- a/apps/as/src/pages/instl-sncn/model/columns.tsx +++ b/apps/as/src/pages/instl-sncn/model/columns.tsx @@ -12,7 +12,7 @@ const columnHelper = createColumnHelper(); const MAIN_STEP_LABELS = { apply: "설치인가 신청", - envoReview: "환경청 검토", + envoReview: "설치인가 검토", techReview: "기술검토", reply: "검토결과 회신", } as const; @@ -20,7 +20,7 @@ const MAIN_STEP_LABELS = { export const PROGRESS_STATUS_COLUMNS = [ { label: "설치인가 신청 (시군)", sttsComCd: "ST" }, { label: "설치인가 신청 (시도)", sttsComCd: "LE" }, - { label: "환경청 검토", sttsComCd: "ER" }, + { label: "설치인가 검토", sttsComCd: "ER" }, { label: "기술검토", sttsComCd: "TE" }, { label: "기술검토 회신", sttsComCd: "TP" }, { label: "검토결과 회신", sttsComCd: "EP" }, diff --git a/apps/as/src/pages/instl-sncn/ui/instl-form-page.tsx b/apps/as/src/pages/instl-sncn/ui/instl-form-page.tsx index a52c9776d..131872a93 100644 --- a/apps/as/src/pages/instl-sncn/ui/instl-form-page.tsx +++ b/apps/as/src/pages/instl-sncn/ui/instl-form-page.tsx @@ -9,10 +9,9 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { useQueryClient } from "@tanstack/react-query"; import { useAlertStore, useUserStore } from "@/shared/lib"; -import { SectionCollapsibleGroup, SectionCollapsibleToggle } from "@/shared/ui"; +import { RhfDevTool, SectionCollapsibleGroup, SectionCollapsibleToggle } from "@/shared/ui"; import { useEsntlAtchFileMngTree } from "@/entities/esntl"; import { - type InstlSncnApplySaveRequestDto, instlSncnKeys, type InstlSncnMngDetResponse, instlSncnService, @@ -31,12 +30,8 @@ import { } from "@/widgets/pbadms-sprt"; import { Button } from "@common/components"; -import { getCreateDefaultValues, mapDetailToFormData } from "../lib/form-data-mapper"; -import { - formatTaskSeComCdForApi, - type InstlSncnRegistFormData, - instlSncnRegistSchema, -} from "../lib/instl-sncn-regist-schema"; +import { buildFormPayload, getCreateDefaultValues, mapDetailToFormData } from "../lib/form-data-mapper"; +import { type InstlSncnRegistFormData, instlSncnRegistSchema } from "../lib/instl-sncn-regist-schema"; import { PROGRESS_STATUS_COLUMNS } from "../model/columns"; import { BasicInfoFormSection } from "./sections/basic-info-form-section"; @@ -72,47 +67,24 @@ function InstlFormPage({ mode, initialData, createDefaults }: InstlFormPageProps : getCreateDefaultValues(userInfo, createDefaults), }); - function buildFormPayload( - data: InstlSncnRegistFormData, - prgrsSttsComCd: "SV" | "ST" | "TE", - ): InstlSncnApplySaveRequestDto { - const { taskSeComCd: formTaskSeComCd, ...instlSncnMngRest } = data.instlSncnMng; - - return { - instlSncnMng: { - ...instlSncnMngRest, - instlSncnDocNo: isEditMode ? initialData?.detDto.instlSncnDocNo : undefined, - taskSeComCd: formatTaskSeComCdForApi(formTaskSeComCd), - prgrsSttsComCd, - }, - bzScaleList: data.bzScaleList, - bizCstList: data.bizCstList, - cklstResultList: data.cklstResultList.map((item) => ({ - ...item, - selected: item.selected, - })), - esntlAtchFileList: data.esntlAtchFileList, - splmntDmndList: data.splmntDmndList.map( - // eslint-disable-next-line @typescript-eslint/no-unused-vars - ({ splmntAtchFileNm, splmntAtchFileAtflId, splmntDmndAtchFileNm, ...item }) => ({ - ...item, - ...(prgrsSttsComCd === "TE" ? { cmptnYn: "Y" } : null), - }), - ), - }; + function createFormPayload(data: InstlSncnRegistFormData, prgrsSttsComCd: "SV" | "ST" | "TE") { + return buildFormPayload(data, prgrsSttsComCd, { + isEditMode, + docNo: initialData?.detDto.instlSncnDocNo, + }); } async function submitForm( data: InstlSncnRegistFormData, prgrsSttsComCd: "SV" | "ST", ): Promise { - return instlSncnService.saveApply(buildFormPayload(data, prgrsSttsComCd)); + return instlSncnService.saveApply(createFormPayload(data, prgrsSttsComCd)); } async function submitSupplementForm(data: InstlSncnRegistFormData): Promise { const docNo = initialData?.detDto.instlSncnDocNo; if (!docNo) return undefined; - return instlSncnService.submitSupplement(docNo, buildFormPayload(data, "TE")); + return instlSncnService.submitSupplement(docNo, createFormPayload(data, "TE")); } async function onSplmntCmptn(data: InstlSncnRegistFormData) { @@ -300,6 +272,7 @@ function InstlFormPage({ mode, initialData, createDefaults }: InstlFormPageProps
+ ); } diff --git a/apps/as/src/pages/instl-sncn/ui/sections/tech-review-opinion-section.tsx b/apps/as/src/pages/instl-sncn/ui/sections/tech-review-opinion-section.tsx index ba787fd61..316c62d96 100644 --- a/apps/as/src/pages/instl-sncn/ui/sections/tech-review-opinion-section.tsx +++ b/apps/as/src/pages/instl-sncn/ui/sections/tech-review-opinion-section.tsx @@ -2,14 +2,11 @@ import { forwardRef, useEffect, useImperativeHandle, useState } from "react"; -import { Paperclip } from "lucide-react"; - -import { PROXY_HOST } from "@/shared/config"; import { dayjs, getDefaultFileKey, useTus } from "@/shared/lib"; import { TextShimmer } from "@/shared/ui"; import { SectionCollapsible } from "@/shared/ui"; import { Field, FieldLabel, FieldSet } from "@/shared/ui/field"; -import { attachService } from "@/entities/attach"; +import { AttachmentDownloadLink, attachService } from "@/entities/attach"; import type { InstlSncnRvwOpnnResponseDto, ReviewStepCode } from "@/entities/instl-sncn"; import { Button, @@ -331,15 +328,10 @@ function TechReviewOpinionTable({ items }: TechReviewOpinionTableProps) { {item.atchFileInfo?.atflCd ? ( - - - {item.atchFileInfo.orgnlFileNm || "다운로드"} - + ) : ( "-" )} diff --git a/apps/as/src/pages/sewer-bsc-plan/lib/payload-utils.ts b/apps/as/src/pages/sewer-bsc-plan/lib/payload-utils.ts index 07224c8ef..df63265a4 100644 --- a/apps/as/src/pages/sewer-bsc-plan/lib/payload-utils.ts +++ b/apps/as/src/pages/sewer-bsc-plan/lib/payload-utils.ts @@ -3,6 +3,7 @@ import type { SewerBscPlanDetResponseDto, SewerBscPlanSplmntDmndItem, } from "@/entities/sewer-bsc-plan"; +import { sewerBscPlanApplySaveSchema } from "@/entities/sewer-bsc-plan"; import { type SplmntDmndDialogConfirmPayload, toEsntlAtchFileSaveList } from "@/widgets/pbadms-sprt"; import type { ChecklistFormState, ReviewStepCode } from "../model/view-types"; @@ -26,7 +27,7 @@ export function buildSavePayload( const cklstResultRows = data.cklstResultRows ?? []; const esntlFileList = data.esntlFileList ?? []; - return { + return sewerBscPlanApplySaveSchema.parse({ sewerBscPlan: { sewerBscPlanDocNo: detDto.sewerBscPlanDocNo, admdstCd: detDto.admdstCd, @@ -99,7 +100,7 @@ export function buildSavePayload( })), cklstResultList: toCklstSaveDto(cklstResultRows, checklistFormState, reviewStep), esntlAtchFileList: toEsntlAtchFileSaveList(esntlFileList), - }; + }); } export function buildTechReviewReplyPayload( @@ -111,12 +112,12 @@ export function buildTechReviewReplyPayload( reviewOpinionFormData?: ReviewOpinionGetFormDataResult | null, opnrNm?: string, ): SewerBscPlanApplySaveRequestDto { - return { + return sewerBscPlanApplySaveSchema.parse({ ...buildSavePayload(data, checklistFormState, reviewStep, prgrsSttsComCd), techReViewSaveCd, rvwStpCd: reviewStep, rvwOpnnList: toRvwOpnnSaveDto(data, reviewOpinionFormData, opnrNm), - }; + }); } export function buildSupplementReqPayload( @@ -136,10 +137,10 @@ export function buildSupplementReqPayload( cmptnYn: "N", })); - return { + return sewerBscPlanApplySaveSchema.parse({ ...buildSavePayload(data, checklistFormState, "LOCAL", "SL"), splmntDmndList: [...existingItems, ...newItems], - }; + }); } export function toSplmntDmndSaveItem(item: SewerBscPlanSplmntDmndItem): SewerBscPlanSplmntDmndItem { diff --git a/apps/as/src/pages/sewer-bsc-plan/lib/sewer-bsc-plan-regist-schema.ts b/apps/as/src/pages/sewer-bsc-plan/lib/sewer-bsc-plan-regist-schema.ts index 416b1f401..d130d0673 100644 --- a/apps/as/src/pages/sewer-bsc-plan/lib/sewer-bsc-plan-regist-schema.ts +++ b/apps/as/src/pages/sewer-bsc-plan/lib/sewer-bsc-plan-regist-schema.ts @@ -77,7 +77,7 @@ export const sewerBscPlanRegistSchema = z.object({ cklstSn: z.number(), cklstArtclSn: z.number(), cklstRvwSn: z.number(), - selected: z.enum(["Y", "N"]).nullable(), + selected: z.enum(["Y", "N"]).nullish(), picRvwCn: z.string().nullable().optional(), }), ), diff --git a/apps/as/src/pages/sewer-bsc-plan/model/columns.tsx b/apps/as/src/pages/sewer-bsc-plan/model/columns.tsx index 4dc1af0c6..e75312b0b 100644 --- a/apps/as/src/pages/sewer-bsc-plan/model/columns.tsx +++ b/apps/as/src/pages/sewer-bsc-plan/model/columns.tsx @@ -12,14 +12,14 @@ const columnHelper = createColumnHelper(); const MAIN_STEP_LABELS = { apply: "기본계획 신청", - envoReview: "환경청 검토", + envoReview: "기본계획 검토", techReview: "기술검토", reply: "검토결과 회신", } as const; export const PROGRESS_STATUS_COLUMNS = [ { label: "기본계획 신청 (시군)", sttsComCd: "LE" }, - { label: "환경청 검토", sttsComCd: "ER" }, + { label: "기본계획 검토", sttsComCd: "ER" }, { label: "기술검토", sttsComCd: "TE" }, { label: "기술검토 회신", sttsComCd: "TP" }, { label: "횐경청 검토 결과 회신", sttsComCd: "EP" }, diff --git a/apps/as/src/pages/sewer-bsc-plan/ui/sections/tech-review-opinion-section.tsx b/apps/as/src/pages/sewer-bsc-plan/ui/sections/tech-review-opinion-section.tsx index 1781f183e..f7371c342 100644 --- a/apps/as/src/pages/sewer-bsc-plan/ui/sections/tech-review-opinion-section.tsx +++ b/apps/as/src/pages/sewer-bsc-plan/ui/sections/tech-review-opinion-section.tsx @@ -2,14 +2,11 @@ import { forwardRef, useEffect, useImperativeHandle, useState } from "react"; -import { Paperclip } from "lucide-react"; - -import { PROXY_HOST } from "@/shared/config"; import { dayjs, getDefaultFileKey, useTus } from "@/shared/lib"; import { TextShimmer } from "@/shared/ui"; import { SectionCollapsible } from "@/shared/ui"; import { Field, FieldLabel, FieldSet } from "@/shared/ui/field"; -import { attachService } from "@/entities/attach"; +import { AttachmentDownloadLink, attachService } from "@/entities/attach"; import type { SewerBscPlanRvwOpnnResponseDto } from "@/entities/sewer-bsc-plan"; import { Button, @@ -338,15 +335,10 @@ function TechReviewOpinionTable({ items }: TechReviewOpinionTableProps) { {item.atchFileInfo?.atflCd ? ( - - - {item.atchFileInfo.orgnlFileNm || "다운로드"} - + ) : ( "-" )} diff --git a/apps/as/src/pages/sewer-bsc-plan/ui/sections/tmplnt-section.tsx b/apps/as/src/pages/sewer-bsc-plan/ui/sections/tmplnt-section.tsx index 7f87254d3..a2b4f8fa5 100644 --- a/apps/as/src/pages/sewer-bsc-plan/ui/sections/tmplnt-section.tsx +++ b/apps/as/src/pages/sewer-bsc-plan/ui/sections/tmplnt-section.tsx @@ -2,10 +2,8 @@ import { useMemo, useState } from "react"; -import { Paperclip } from "lucide-react"; - import { SectionCollapsible } from "@/shared/ui"; -import { useDownloadAttachmentByAtflCd } from "@/entities/attach"; +import { AttachmentDownloadLink } from "@/entities/attach"; import { useFacilityList } from "@/entities/bns-if"; import { useNowDmgMtncGnsvy } from "@/entities/dc"; import type { @@ -176,7 +174,6 @@ function TmplntFacilityMatrix({ } function TmplntSection({ tmplntList, det, dtl }: TmplntSectionProps) { - const { mutate: downloadAttachment } = useDownloadAttachmentByAtflCd(); const [selectedCode, setSelectedCode] = useState(tmplntList[0]?.swtrmtFcltCd ?? ""); const { data: facilityListData } = useFacilityList( { sido: det.sido ?? "", gugun: det.gugun ?? "" }, @@ -261,13 +258,11 @@ function TmplntSection({ tmplntList, det, dtl }: TmplntSectionProps) { {facilityName} 계획평면도 {item.atchFileGroupId ? ( - downloadAttachment(item.atchFileGroupId ?? "")} - > - - {item.atchFileInfo?.orgnlFileNm || "다운로드"} - + ) : ( "-" )} diff --git a/apps/as/src/pages/sewer-bsc-plan/ui/sewer-bsc-plan-form-page.tsx b/apps/as/src/pages/sewer-bsc-plan/ui/sewer-bsc-plan-form-page.tsx index 4c267b9a0..8c05521c3 100644 --- a/apps/as/src/pages/sewer-bsc-plan/ui/sewer-bsc-plan-form-page.tsx +++ b/apps/as/src/pages/sewer-bsc-plan/ui/sewer-bsc-plan-form-page.tsx @@ -9,7 +9,7 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { useQueryClient } from "@tanstack/react-query"; import { useAlertStore, useUserStore } from "@/shared/lib"; -import { SectionCollapsibleGroup, SectionCollapsibleToggle } from "@/shared/ui"; +import { RhfDevTool, SectionCollapsibleGroup, SectionCollapsibleToggle } from "@/shared/ui"; import { useAcntgSeMngTree } from "@/entities/acntg"; import { useEsntlAtchFileMngTree } from "@/entities/esntl"; import { @@ -319,6 +319,7 @@ function SewerBscPlanFormPage({
+ ); } diff --git a/apps/as/src/shared/config/api.ts b/apps/as/src/shared/config/api.ts index 3e7d08c08..c5005396c 100644 --- a/apps/as/src/shared/config/api.ts +++ b/apps/as/src/shared/config/api.ts @@ -26,8 +26,10 @@ export const ENDPOINT = { SEWER_BSC_PLAN: "/as-service/api/v1/sewer-bsc-plans", DASHBOARD: "/as-service/api/v1/dashboard", DCRAG_TO: "/as-service/api/v1/dcrag-to", + AI_AUTO_PRCS_MNG: "/as-service/api/v1/ai-auto-prcs-mng", PWRK_CNSLTN_MNG: "/as-service/api/v1/pwrk-cnsltn-mng", PWRK_CNSLTN_DOC_CRT_RSLT: "/as-service/api/v1/pwrk-cnsltn-doc-crt-rslt", + PWRK_CNSLTN_DOC_PJTCO_AJMT_MNG: "/as-service/api/v1/pwrk-cnsltn-doc-pjtco-ajmt-mng", }, USER_SERVICE: { LOGIN: "/user-service/login", diff --git a/apps/as/src/shared/lib/use-data-table.tsx b/apps/as/src/shared/lib/use-data-table.tsx index ef473f50d..df3b16f6e 100644 --- a/apps/as/src/shared/lib/use-data-table.tsx +++ b/apps/as/src/shared/lib/use-data-table.tsx @@ -1,3 +1,5 @@ +"use client"; + import type { HTMLAttributes } from "react"; import type { UseQueryResult } from "@tanstack/react-query"; diff --git a/apps/as/src/shared/lib/use-tus.ts b/apps/as/src/shared/lib/use-tus.ts index 1009b5adc..7a48cad6c 100644 --- a/apps/as/src/shared/lib/use-tus.ts +++ b/apps/as/src/shared/lib/use-tus.ts @@ -1,3 +1,5 @@ +"use client"; + import { useCallback, useState } from "react"; import { type Upload } from "tus-js-client"; diff --git a/apps/as/src/shared/model/pbadms-sprt.ts b/apps/as/src/shared/model/pbadms-sprt.ts index e8f2b6b1d..959c8133d 100644 --- a/apps/as/src/shared/model/pbadms-sprt.ts +++ b/apps/as/src/shared/model/pbadms-sprt.ts @@ -54,9 +54,9 @@ export interface BizCstTreeDto { bscPlanAmt: number; /** 금회설계금액(C) */ tstmDesignAmt: number; - /** 증감금액(금회설계금액 - 표준사업비, C-B) */ + /** 증감금액(금회설계금액 - 예산반영금액, C-A) */ chgAmt: number; - /** 증감율 % ((증감금액 / 표준사업비) * 100) */ + /** 증감율 % ((증감금액 / 예산반영금액(A)) * 100) */ chgRt: number; } @@ -151,6 +151,7 @@ export interface ElaprDocPrgrsClientResponseDto { jbgdSeNm?: string; ognzNm?: string; dcarbYn?: string; + elaprCycl?: number; } /** 문서 상태 변경 이력 응답 DTO */ @@ -190,7 +191,7 @@ export interface CklstResultSaveBase { cklstRvwSn?: number; cklstArtclSn?: number; cklstSn?: number; - rvwStpComCd: string; + rvwStpComCd?: string | null; picRvwCn?: string | null; } diff --git a/apps/as/src/shared/ui/attachment-file-field.tsx b/apps/as/src/shared/ui/attachment-file-field.tsx index 6867a8323..8eac0dce6 100644 --- a/apps/as/src/shared/ui/attachment-file-field.tsx +++ b/apps/as/src/shared/ui/attachment-file-field.tsx @@ -6,6 +6,7 @@ import { UploadedFileChip } from "./uploaded-file-chip"; interface AttachmentFileFieldItem { fileKey: string; fileName: string; + onDownload?: () => void; } interface AttachmentFileFieldProps { @@ -41,6 +42,7 @@ function AttachmentFileField({ fileName={file.fileName} isUploading={isUploadingByKey[file.fileKey] ?? false} onRemove={onRemove ? () => onRemove(index) : undefined} + onDownload={file.onDownload} /> ))} diff --git a/apps/as/src/shared/ui/index.ts b/apps/as/src/shared/ui/index.ts index 71cd9a481..89a554ff7 100644 --- a/apps/as/src/shared/ui/index.ts +++ b/apps/as/src/shared/ui/index.ts @@ -4,6 +4,7 @@ export type { FileAddButtonProps } from "./file-add-button"; export { FileAddButton } from "./file-add-button"; export { NoData } from "./no-data"; export { PageLayout } from "./page-layout"; +export { RhfDevTool } from "./rhf-devtool"; export { SectionCollapsible } from "./section-collapsible"; export { SectionCollapsibleGroup, @@ -12,4 +13,4 @@ export { export type { TextShimmerProps } from "./text-shimmer"; export { TEXT_SHIMMER_CLASS, TextShimmer } from "./text-shimmer"; export type { UploadedFileChipProps } from "./uploaded-file-chip"; -export { UploadedFileChip } from "./uploaded-file-chip"; +export { UploadedFileChip } from "./uploaded-file-chip"; \ No newline at end of file diff --git a/apps/as/src/shared/ui/rhf-devtool.tsx b/apps/as/src/shared/ui/rhf-devtool.tsx new file mode 100644 index 000000000..0c9e69d9d --- /dev/null +++ b/apps/as/src/shared/ui/rhf-devtool.tsx @@ -0,0 +1,23 @@ +"use client"; + +import type { Control, FieldValues } from "react-hook-form"; +import dynamic from "next/dynamic"; + +const DevTool = dynamic(() => import("@hookform/devtools").then((mod) => mod.DevTool), { + ssr: false, +}); + +interface RhfDevToolProps { + control: Control; +} + +export function RhfDevTool({ control }: RhfDevToolProps) { + if (process.env.NODE_ENV !== "development") return null; + return ( + } + placement="bottom-right" + styles={{ button: { right: "80px", bottom: "22px", scale: 2.5 } }} + /> + ); +} diff --git a/apps/as/src/shared/ui/uploaded-file-chip.tsx b/apps/as/src/shared/ui/uploaded-file-chip.tsx index a7eb6fa22..2fb4b437a 100644 --- a/apps/as/src/shared/ui/uploaded-file-chip.tsx +++ b/apps/as/src/shared/ui/uploaded-file-chip.tsx @@ -8,15 +8,36 @@ interface UploadedFileChipProps { fileName: string; isUploading?: boolean; onRemove?: () => void; + onDownload?: () => void; } -function UploadedFileChip({ fileName, isUploading = false, onRemove }: UploadedFileChipProps) { +function UploadedFileChip({ + fileName, + isUploading = false, + onRemove, + onDownload, +}: UploadedFileChipProps) { + const canDownload = Boolean(onDownload) && !isUploading; + return ( -
+
- - {fileName} - + {canDownload ? ( + + ) : ( + + {fileName} + + )} {onRemove ? (
diff --git a/apps/as/src/widgets/pbadms-sprt/ui/attachments-view-section.tsx b/apps/as/src/widgets/pbadms-sprt/ui/attachments-view-section.tsx index 4a49e5855..2a492fd56 100644 --- a/apps/as/src/widgets/pbadms-sprt/ui/attachments-view-section.tsx +++ b/apps/as/src/widgets/pbadms-sprt/ui/attachments-view-section.tsx @@ -1,11 +1,9 @@ "use client"; -import { Paperclip } from "lucide-react"; - import { formatFileSize } from "@/shared/lib/file-utils"; import type { AtflMngClientResponseDto, EsntlAtchFileMngTreeResponseDto } from "@/shared/model"; import { SectionCollapsible } from "@/shared/ui"; -import { useDownloadAttachmentByAtflCd, useDownloadAttachmentByAtflId } from "@/entities/attach"; +import { AttachmentDownloadLink } from "@/entities/attach"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@common/components"; interface AttachmentsViewSectionProps { @@ -14,9 +12,6 @@ interface AttachmentsViewSectionProps { } function AttachmentsViewSection({ esntlFileList, etcFileList }: AttachmentsViewSectionProps) { - const { mutate: downloadByAtflCd } = useDownloadAttachmentByAtflCd(); - const { mutate: downloadByAtflId } = useDownloadAttachmentByAtflId(); - return ( <> {esntlFileList.length > 0 && ( @@ -48,14 +43,12 @@ function AttachmentsViewSection({ esntlFileList, etcFileList }: AttachmentsViewS ) : (
{files.map((file) => ( - downloadByAtflId(file.atflId)} - > - - {file.orgnlFileNm || "다운로드"} - + atflId={file.atflId} + fileName={file.orgnlFileNm || "다운로드"} + className="items-center" + /> ))}
)} @@ -95,13 +88,11 @@ function AttachmentsViewSection({ esntlFileList, etcFileList }: AttachmentsViewS {file.atflCd ? ( - downloadByAtflCd(file.atflCd)} - > - - {file.orgnlFileNm || file.physFileNm || file.atflId} - + ) : ( {file.orgnlFileNm || file.physFileNm || file.atflId} )} diff --git a/apps/as/src/widgets/pbadms-sprt/ui/business-cost-form-section.tsx b/apps/as/src/widgets/pbadms-sprt/ui/business-cost-form-section.tsx index 22aa35c82..dc50504a5 100644 --- a/apps/as/src/widgets/pbadms-sprt/ui/business-cost-form-section.tsx +++ b/apps/as/src/widgets/pbadms-sprt/ui/business-cost-form-section.tsx @@ -120,14 +120,14 @@ function BusinessCostFormSection() { ( handleCostChange(idx, "bscPlanAmt", e.target.value)} + onChange={(e) => handleCostChange(idx, "bgtRfltAmt", e.target.value)} readOnly={item.readonly} aria-invalid={fieldState.invalid} className={`text-right ${item.readonly ? "bg-muted" : ""}`} @@ -138,14 +138,14 @@ function BusinessCostFormSection() { ( handleCostChange(idx, "bgtRfltAmt", e.target.value)} + onChange={(e) => handleCostChange(idx, "bscPlanAmt", e.target.value)} readOnly={item.readonly} aria-invalid={fieldState.invalid} className={`text-right ${item.readonly ? "bg-muted" : ""}`} diff --git a/apps/as/src/widgets/pbadms-sprt/ui/checklist-form-section.tsx b/apps/as/src/widgets/pbadms-sprt/ui/checklist-form-section.tsx index 4b9866877..1e4c324bb 100644 --- a/apps/as/src/widgets/pbadms-sprt/ui/checklist-form-section.tsx +++ b/apps/as/src/widgets/pbadms-sprt/ui/checklist-form-section.tsx @@ -30,7 +30,7 @@ import { buildChecklistTabs, normalizeTaskSeComCdArray, syncCklstResultListFromTree, -} from "../lib/checklist-form-utils"; +} from "../lib/checklist-form-utils"; type FieldPrefix = "fnrsCnsltnMng" | "instlSncnMng" | "designCnstnMng" | "sewerBscPlan"; @@ -39,7 +39,7 @@ interface CklstResultListItem { cklstArtclSn: number; cklstRvwSn: number; rvwStpComCd: string; - selected: "Y" | "N" | null; + selected?: "Y" | "N" | null; picRvwCn?: string | null; } diff --git a/apps/as/src/widgets/pbadms-sprt/ui/splmnt-dmnd-dialog.tsx b/apps/as/src/widgets/pbadms-sprt/ui/splmnt-dmnd-dialog.tsx index 752898721..8e1facf38 100644 --- a/apps/as/src/widgets/pbadms-sprt/ui/splmnt-dmnd-dialog.tsx +++ b/apps/as/src/widgets/pbadms-sprt/ui/splmnt-dmnd-dialog.tsx @@ -4,7 +4,7 @@ import { useEffect, useId, useState } from "react"; import { getDefaultFileKey, useTus } from "@/shared/lib"; import { AttachmentFileField } from "@/shared/ui"; -import { attachService } from "@/entities/attach"; +import { attachService, useDownloadAttachmentByAtflId } from "@/entities/attach"; import { CodeSelect } from "@/entities/code"; import { Button, @@ -96,6 +96,7 @@ function SplmntDmndDialog({ }: SplmntDmndDialogProps) { const formId = useId(); const [rows, setRows] = useState([createEmptyRow()]); + const { mutate: downloadByAtflId } = useDownloadAttachmentByAtflId(); const { uploadFileAsync, isUploading, uploads } = useTus({ onSuccess: (atflCd, atflId, _file, fileKey) => { @@ -199,7 +200,7 @@ function SplmntDmndDialog({ try { await attachService.deleteAttachmentByAtflId(atflId); } catch (error) { - console.error("보완요청 첨부파일 삭제 실패:", error); + console.error("보완 요청 첨부파일 삭제 실패:", error); } } @@ -226,7 +227,7 @@ function SplmntDmndDialog({ - 보완요청 + 보완 요청
@@ -289,6 +290,9 @@ function SplmntDmndDialog({ files={row.uploadedFiles.map((file) => ({ fileKey: file.fileKey, fileName: file.fileName, + onDownload: file.atflId + ? () => downloadByAtflId(file.atflId!) + : undefined, }))} inputId={fileInputId} isUploadingByKey={isUploading} @@ -322,7 +326,7 @@ function SplmntDmndDialog({ 취소 diff --git a/apps/as/src/widgets/pbadms-sprt/ui/splmnt-dmnd-form-section.tsx b/apps/as/src/widgets/pbadms-sprt/ui/splmnt-dmnd-form-section.tsx index eea7fb7af..8fdbec031 100644 --- a/apps/as/src/widgets/pbadms-sprt/ui/splmnt-dmnd-form-section.tsx +++ b/apps/as/src/widgets/pbadms-sprt/ui/splmnt-dmnd-form-section.tsx @@ -3,11 +3,9 @@ import { useEffect, useId, useRef, useState } from "react"; import { Controller, useFormContext, useWatch } from "react-hook-form"; -import { Paperclip } from "lucide-react"; - import { dayjs, getDefaultFileKey, useTus } from "@/shared/lib"; import { AttachmentFileField, SectionCollapsible } from "@/shared/ui"; -import { attachService } from "@/entities/attach"; +import { AttachmentDownloadLink, attachService, useDownloadAttachmentByAtflId } from "@/entities/attach"; import { Badge, Table, @@ -75,6 +73,7 @@ function SplmntDmndFormSection({ readOnly = false }: SplmntDmndFormSectionProps) const initialFilesMappedRef = useRef(false); const isMountedRef = useRef(true); const atflCdByRowRef = useRef>({}); + const { mutate: downloadByAtflId } = useDownloadAttachmentByAtflId(); const { uploadFileAsync, isUploading, uploads } = useTus({ onSuccess: (atflCd, atflId, _file, fileKey) => { @@ -274,10 +273,10 @@ function SplmntDmndFormSection({ readOnly = false }: SplmntDmndFormSectionProps)
{item.splmntDmndCn || "-"}
{item.splmntDmndAtchFileGroupId ? ( - - - {item.splmntDmndAtchFileNm || "첨부파일"} - + ) : null}
@@ -308,6 +307,9 @@ function SplmntDmndFormSection({ readOnly = false }: SplmntDmndFormSectionProps) files={rowFiles.map((file) => ({ fileKey: file.fileKey, fileName: file.fileName, + onDownload: file.atflId + ? () => downloadByAtflId(file.atflId!) + : undefined, }))} inputId={fileInputId} isUploadingByKey={isUploading} diff --git a/apps/as/src/widgets/pbadms-sprt/ui/supplement-demand-section.tsx b/apps/as/src/widgets/pbadms-sprt/ui/supplement-demand-section.tsx index 1da222fdf..7151323bb 100644 --- a/apps/as/src/widgets/pbadms-sprt/ui/supplement-demand-section.tsx +++ b/apps/as/src/widgets/pbadms-sprt/ui/supplement-demand-section.tsx @@ -1,11 +1,9 @@ "use client"; -import { Paperclip } from "lucide-react"; - import { dayjs } from "@/shared/lib"; import type { SplmntDmndResponseExtension, SplmntDmndSaveBase } from "@/shared/model"; import { SectionCollapsible } from "@/shared/ui"; -import { useDownloadAttachmentByAtflCd } from "@/entities/attach"; +import { AttachmentDownloadLink } from "@/entities/attach"; import { Badge, Table, @@ -23,8 +21,6 @@ interface SupplementDemandSectionProps { } function SupplementDemandSection({ splmntDmndList }: SupplementDemandSectionProps) { - const { mutate: downloadAttachment } = useDownloadAttachmentByAtflCd(); - if (splmntDmndList.length === 0) { return null; } @@ -70,13 +66,10 @@ function SupplementDemandSection({ splmntDmndList }: SupplementDemandSectionProp
{item.splmntDmndCn || "-"}
{item.splmntDmndAtchFileGroupId ? ( - downloadAttachment(item.splmntDmndAtchFileGroupId!)} - > - - {dmndAtchFileNm || "다운로드"} - + ) : null}
@@ -89,13 +82,10 @@ function SupplementDemandSection({ splmntDmndList }: SupplementDemandSectionProp
{item.splmntCn || "-"}
{item.splmntAtchFileGroupId ? ( - downloadAttachment(item.splmntAtchFileGroupId!)} - > - - {cmptnAtchFileNm || "다운로드"} - + ) : null}
diff --git a/apps/cs/src/entities/link/model/link-interface.ts b/apps/cs/src/entities/link/model/link-interface.ts index 67cd75807..6cd1ba560 100644 --- a/apps/cs/src/entities/link/model/link-interface.ts +++ b/apps/cs/src/entities/link/model/link-interface.ts @@ -1,61 +1,62 @@ -import { z } from "zod"; +import { z } from "zod"; import type { PageableParams, PageableList } from "@/shared/model"; export interface Link { - link_sn: string; + intg_link_sn: string; link_nm: string; link_clsf_cd: string; - link_url: string; - link_srvr_ip: string; - link_db_cd: string; - link_key: string; - link_id: string; - link_pswd: string; - link_cycle_stng: string; - srvc_usg: string; - last_link_rslt_cd: string; + link_url_addr: string; + link_srvr_ip_addr: string; + link_db_nm: string; + link_api_no: string; + link_cntn_nm: string; + link_cntn_tkn_no: string; + link_cycle_stng_nm: string; + srvc_usg_nm: string; + last_link_rslt_nm: string; last_link_excn_dt: string; next_link_excn_dt: string; - last_link_err_cd: string; + last_link_err_nm: string; mdfcn_dt: string; - mdfr_id_id: string; - link_cycle_mi: string; - link_key_expry_ymd: string; + mdfr_uuid: string; + link_cycle_no: string; + link_expry_ymd: string; } export interface LinkLog { rownum: number; - link_sn: string; + intg_link_sn: string; link_nm: string; - link_rslt_cd: string; + link_rslt_nm: string; err_cn: string; - link_err_cd: string; + link_err_nm: string; log_crt_dt: string; + link_excn_stp_nm: string; } export interface LinkInfo extends LinkFormData { - link_sn: string; + intg_link_sn: string; link_nm: string; link_clsf_cd: string; - link_url: string; - link_srvr_ip: string; - link_db_cd: string; - link_key: string; - link_id: string; - link_pswd: string; - srvc_usg: string; - last_link_rslt_cd: string; + link_url_addr: string; + link_srvr_ip_addr: string; + link_db_nm: string; + link_api_no: string; + link_cntn_nm: string; + link_cntn_tkn_no: string; + srvc_usg_nm: string; + last_link_rslt_nm: string; last_link_excn_dt: string; next_link_excn_dt: string; - last_link_err_cd: string; + last_link_err_nm: string; mdfcn_dt: string; - mdfr_id: string; - link_cycle_mi: number; - link_key_expry_ymd: string; + mdfr_uuid: string; + link_cycle_no: number; + link_expry_ymd: string; } export interface LinkStats { - link_sn: string; + intg_link_sn: string; link_nm: string; count: number; } @@ -63,16 +64,16 @@ export interface LinkStats { export interface LinkVrbl { link_vrbl_sn: string; link_vrbl_nm: string; - link_vrbl_vl: string; - link_vrbl_cd: string; + link_vrbl_vl_cn: string; + link_vrbl_cd_nm: string; link_vrbl_expln: string; mdfcn_dt: string; - mdfr_id: string; + mdfr_uuid: string; } export interface LinkTable { link_nm: string; - link_sn: string; + intg_link_sn: string; tbl_sn: string; sou_tbl_nm: string; sou_tbl_korn_nm: string; @@ -87,16 +88,16 @@ export interface LinkTableColumns { col_sn: string; sou_col_nm: string; sou_col_korn_nm: string; - sou_col_data_frm: string; + sou_col_frm_nm: string; sou_col_sz: number; trsf_col_nm: string; trsf_col_korn_nm: string; - trsf_col_data_frm: string; + trsf_col_frm_nm: string; trsf_col_sz: number pk_yn: string; col_seq: number; mdfcn_dt: string; - mdfr_id: string; + mdfr_uuid: string; } export const linkFormSchema = z.object({ @@ -106,48 +107,48 @@ export const linkFormSchema = z.object({ }, { message: "연계 분류를 선택해주세요" }), - link_sn: z.string().max(50).nullable().optional(), + intg_link_sn: z.string().max(50).nullable().optional(), link_nm: z.string().max(40, { message: '연계명은 40자 이하로 입력해 주세요.' }).nonempty({ message: '연계명은 필수입니다.' }), - link_url: z.string().max(1000, { message: '연계 URL은 1000자 이하로 입력해 주세요.' }).nonempty({ message: '연계 URL은 필수입니다.' }), - link_srvr_ip: z.string().max(20).nullable().optional(), - link_db_cd: z.string().max(10).nullable().optional(), - link_key: z.string().max(100, { message: '연계 KEY는 100자 이하로 입력해 주세요.' }).nonempty({ message: '연계 KEY는 필수입니다.' }), - link_id: z.string().max(20).nullable().optional(), - link_pswd: z.string().max(128).nullable().optional(), - srvc_usg: z.string().max(40).nullable().optional(), - last_link_rslt_cd: z.string().max(4).nullable().optional(), + link_url_addr: z.string().max(1000, { message: '연계 URL은 1000자 이하로 입력해 주세요.' }).nonempty({ message: '연계 URL은 필수입니다.' }), + link_srvr_ip_addr: z.string().max(20).nullable().optional(), + link_db_nm: z.string().max(10).nullable().optional(), + link_api_no: z.string().max(100, { message: '연계 KEY는 100자 이하로 입력해 주세요.' }).nonempty({ message: '연계 KEY는 필수입니다.' }), + link_cntn_nm: z.string().max(20).nullable().optional(), + link_cntn_tkn_no: z.string().max(25, { message: '연계 접속 토큰은 25자 이하로 입력해 주세요.' }).nullable().optional(), + srvc_usg_nm: z.string().max(40).nullable().optional(), + last_link_rslt_nm: z.string().max(4).nullable().optional(), last_link_excn_dt: z.string().max(40).nullable().optional(), next_link_excn_dt: z.string().max(40).nullable().optional(), - last_link_err_cd: z.string().max(50, { message: '마지막 연계 오류 코드는 50자 이하입니다.' }).nullable().optional(), + last_link_err_nm: z.string().max(50, { message: '마지막 연계 오류 코드는 50자 이하입니다.' }).nullable().optional(), mdfcn_dt: z.string().nullable().optional(), - mdfr_id: z.string().nullable().optional(), - link_cycle_mi: z.coerce.number().nullable().optional().refine((val) => { + mdfr_uuid: z.string().nullable().optional(), + link_cycle_no: z.coerce.number().nullable().optional().refine((val) => { if (val == null) return true; return val >= 5; }, { message: "연계 주기는 5분 이상이어야 합니다." }), - link_key_expry_ymd: z.string().nullable().optional(), + link_expry_ymd: z.string().nullable().optional(), }); export const linkVrblFormSchema = z.object({ - link_sn: z.string().max(50).nullable().optional(), + intg_link_sn: z.string().max(50).nullable().optional(), link_vrbl_sn: z.string().max(50).nullable().optional(), link_vrbl_nm: z.string().max(40, { message: '변수명은 40자 이하로 입력해 주세요.' }).nonempty({ message: '변수명은 필수입니다.' }), link_vrbl_expln: z.string().max(4000, { message: '설명은 400자 이하로 입력해 주세요.' }).nullable().optional(), - link_vrbl_cd: z.string().max(10, { message: '변수코드는 10자 이하로 입력해 주세요.' }).nullable().optional(), - link_vrbl_vl: z.string().max(1000, { message: '변수값은 1000자 이하로 입력해 주세요.' }).nonempty({ message: '변수값은 필수입니다.' }), + link_vrbl_cd_nm: z.string().max(10, { message: '변수코드는 10자 이하로 입력해 주세요.' }).nullable().optional(), + link_vrbl_vl_cn: z.string().max(1000, { message: '변수값은 1000자 이하로 입력해 주세요.' }).nonempty({ message: '변수값은 필수입니다.' }), mdfcn_dt: z.string().nullable().optional(), - mdfr_id: z.string().nullable().optional(), + mdfr_uuid: z.string().nullable().optional(), }); export const LinkTableColumnsSchema = z.object({ - link_sn: z.string().max(50).nullable().optional(), + intg_link_sn: z.string().max(50).nullable().optional(), tbl_sn: z.string().max(50).nullable().optional(), col_sn: z.string().max(50).nullable().optional(), sou_col_nm: z.string().max(40, { message: '40자 이하로 입력해 주세요.' }).nullable().optional(), sou_col_korn_nm: z.string().max(40, { message: '40자 이하로 입력해 주세요.' }).nullable().optional(), - sou_col_data_frm: z.string().max(40, { message: '40자 이하로 입력해 주세요.' }).nullable().optional(), + sou_col_frm_nm: z.string().max(40, { message: '40자 이하로 입력해 주세요.' }).nullable().optional(), sou_col_sz: z.coerce.number().nullable().optional().refine((val) => { if (val == null) return true; return val.toString().replace('.', '').length <= 14; @@ -157,7 +158,7 @@ export const LinkTableColumnsSchema = z.object({ pk_yn: z.string().max(1).nonempty(), trsf_col_nm: z.string().max(40, { message: '40자 이하로 입력해 주세요.' }).nonempty({ message: '변환컬럼명은 필수입니다.' }), trsf_col_korn_nm: z.string().max(40, { message: '40자 이하로 입력해 주세요.' }).nonempty({ message: '변환컬럼 한글명은 필수입니다.' }), - trsf_col_data_frm: z.string().max(40, { message: '40자 이하로 입력해 주세요.' }).nonempty({ message: '변환컬럼 데이터 형식은 필수입니다.' }), + trsf_col_frm_nm: z.string().max(40, { message: '40자 이하로 입력해 주세요.' }).nonempty({ message: '변환컬럼 데이터 형식은 필수입니다.' }), trsf_col_sz: z.coerce.number().nullable().optional().refine((val) => { if (val == null) return true; return val.toString().replace('.', '').length <= 14; @@ -166,11 +167,11 @@ export const LinkTableColumnsSchema = z.object({ }), col_seq: z.coerce.number().nullable().optional(), mdfcn_dt: z.string().nullable().optional(), - mdfr_id: z.string().nullable().optional(), + mdfr_uuid: z.string().nullable().optional(), }); export const LinkTableSchema = z.object({ - link_sn: z.string().max(50).nullable().optional(), + intg_link_sn: z.string().max(50).nullable().optional(), tbl_sn: z.string().max(50).nullable().optional(), link_nm: z.string().max(50).nullable().optional(), sou_tbl_nm: z.string().max(40, { message: '40자 이하로 입력해 주세요.' }).nullable().optional(), @@ -180,7 +181,7 @@ export const LinkTableSchema = z.object({ trsf_tbl_korn_nm: z.string().max(40, { message: '40자 이하로 입력해 주세요.' }).nonempty({ message: '변환테이블 한글명은 필수입니다.' }), trsf_tbl_expln: z.string().max(2000, { message: '설명은 2000자 이하로 입력해 주세요.' }).nullable().optional(), mdfcn_dt: z.string().nullable().optional(), - mdfr_id: z.string().nullable().optional(), + mdfr_uuid: z.string().nullable().optional(), }); interface LinkStatsListObject { @@ -195,6 +196,7 @@ export interface LinkSearchParam extends PageableParams { endDate?: string; isSucc?: string; linkClfsCd?: string; + linkExecStep?: string; } export type LinkFormData = z.infer; diff --git a/apps/cs/src/pages/link/model/table-columns.tsx b/apps/cs/src/pages/link/model/table-columns.tsx index 800801277..829743849 100644 --- a/apps/cs/src/pages/link/model/table-columns.tsx +++ b/apps/cs/src/pages/link/model/table-columns.tsx @@ -1,4 +1,4 @@ -import dayjs from "dayjs"; +import dayjs from "dayjs"; import { createColumnHelper } from "@tanstack/react-table"; import { type Link } from "@/entities/link"; import QuickStartButton from "../ui/QuickStartButton"; @@ -35,39 +35,39 @@ const columns = [ id: 'no', header: 'NO', }), - columnHelper.accessor('link_sn', { - id: 'link_sn', + columnHelper.accessor('intg_link_sn', { + id: 'intg_link_sn', header: '연계일련번호', }), columnHelper.accessor('link_nm', { id: 'link_nm', header: '연계 명', }), - columnHelper.accessor('link_url', { - id: 'link_url', + columnHelper.accessor('link_url_addr', { + id: 'link_url_addr', header: '연계 URL', cell: (info) => (info.getValue() ? info.getValue() : '-'), }), - columnHelper.accessor('link_srvr_ip', { - id: 'link_srvr_ip', + columnHelper.accessor('link_srvr_ip_addr', { + id: 'link_srvr_ip_addr', header: '연계서버 IP', cell: (info) => (info.getValue() ? info.getValue() : '-'), }), - columnHelper.accessor('link_cycle_mi', { - id: 'link_cycle_mi', + columnHelper.accessor('link_cycle_no', { + id: 'link_cycle_no', header: '연계 주기', cell: (info) => convertMinutesToPeriod(Number(info.getValue())), }), - columnHelper.accessor('srvc_usg', { - id: 'srvc_usg', + columnHelper.accessor('srvc_usg_nm', { + id: 'srvc_usg_nm', header: '서비스용도', cell: (info) => (info.getValue() ? info.getValue() : '-'), }), columnHelper.display({ - id: 'last_link_rslt_cd', + id: 'last_link_rslt_nm', header: '연계상태', cell: (info) => { - const resultCd = info.row.original.last_link_rslt_cd; + const resultCd = info.row.original.last_link_rslt_nm; return {resultCd === 'SUCC' ? '성공' : (resultCd === 'FAIL' ? '실패' : (resultCd === 'RUNN' ? '진행중' : '대기'))} }, }), @@ -80,7 +80,7 @@ const columns = [ id: 'quick_start_btn', header: '즉시 실행', cell: (info) => { - return + return } }) ]; diff --git a/apps/cs/src/pages/link/ui/LinkExpiredAlert.tsx b/apps/cs/src/pages/link/ui/LinkExpiredAlert.tsx index 92628a755..ab5091389 100644 --- a/apps/cs/src/pages/link/ui/LinkExpiredAlert.tsx +++ b/apps/cs/src/pages/link/ui/LinkExpiredAlert.tsx @@ -1,4 +1,4 @@ -import type { Link, LinkList } from "@/entities/link/model/link-interface"; +import type { Link, LinkList } from "@/entities/link/model/link-interface"; import { Button, DropdownMenu, @@ -18,11 +18,11 @@ const LinkExpiredAlert = ({ linkList }: { linkList: UseQueryResult }) useEffect(() => { if (linkList?.data?.content) { const expriedLinks = linkList.data.content.filter((l) => { - if (l.link_key_expry_ymd === 'none' || !l.link_key_expry_ymd) { + if (l.link_expry_ymd === 'none' || !l.link_expry_ymd) { return false; } - const expryDate = dayjs(l.link_key_expry_ymd, 'YYMMDD'); + const expryDate = dayjs(l.link_expry_ymd, 'YYMMDD'); const today = dayjs(); const daysDiff = expryDate.diff(today, 'day'); @@ -56,11 +56,11 @@ const LinkExpiredAlert = ({ linkList }: { linkList: UseQueryResult }) {expriedLinkList.length > 0 ? {expriedLinkList.map((l) => { - const expryDate = dayjs(l.link_key_expry_ymd, 'YYMMDD'); + const expryDate = dayjs(l.link_expry_ymd, 'YYMMDD'); const today = dayjs(); const daysDiff = expryDate.diff(today, 'day'); - return router.push(`link/register/${l.link_sn}`)}> + return router.push(`link/register/${l.intg_link_sn}`)}>
{daysDiff > 0 ?

{l.link_nm} API KEY 만료 {daysDiff}일전

diff --git a/apps/cs/src/pages/link/ui/link-form.tsx b/apps/cs/src/pages/link/ui/link-form.tsx index 51d8c9f18..6969c6165 100644 --- a/apps/cs/src/pages/link/ui/link-form.tsx +++ b/apps/cs/src/pages/link/ui/link-form.tsx @@ -1,4 +1,4 @@ -import { +import { Button, Input, Table, @@ -55,7 +55,7 @@ const LinkForm = ({ form, linkSn, updateLinkInfo, handleInsertLink, deleteLink, const newData = { ...data, - link_key_expry_ymd: isNoExprie ? 'none' :dayjs(expryDate).format('YYYYMMDD'), + link_expry_ymd: isNoExprie ? 'none' :dayjs(expryDate).format('YYYYMMDD'), }; await handleConfirm(async () => { @@ -133,7 +133,7 @@ const LinkForm = ({ form, linkSn, updateLinkInfo, handleInsertLink, deleteLink, ( @@ -222,7 +222,7 @@ const LinkForm = ({ form, linkSn, updateLinkInfo, handleInsertLink, deleteLink, ( @@ -233,9 +233,9 @@ const LinkForm = ({ form, linkSn, updateLinkInfo, handleInsertLink, deleteLink, autoComplete="off" /> - {form.formState.errors.link_url && ( + {form.formState.errors.link_url_addr && (

- {form.formState.errors.link_url.message} + {form.formState.errors.link_url_addr.message}

)}
@@ -250,7 +250,7 @@ const LinkForm = ({ form, linkSn, updateLinkInfo, handleInsertLink, deleteLink, ( @@ -277,7 +277,7 @@ const LinkForm = ({ form, linkSn, updateLinkInfo, handleInsertLink, deleteLink, ( @@ -288,9 +288,9 @@ const LinkForm = ({ form, linkSn, updateLinkInfo, handleInsertLink, deleteLink, autoComplete="off" /> - {form.formState.errors.link_key && ( + {form.formState.errors.link_api_no && (

- {form.formState.errors.link_key.message} + {form.formState.errors.link_api_no.message}

)}
@@ -303,7 +303,7 @@ const LinkForm = ({ form, linkSn, updateLinkInfo, handleInsertLink, deleteLink, ( @@ -333,9 +333,9 @@ const LinkForm = ({ form, linkSn, updateLinkInfo, handleInsertLink, deleteLink,
- {form.formState.errors.link_key_expry_ymd && ( + {form.formState.errors.link_expry_ymd && (

- {form.formState.errors.link_key_expry_ymd.message} + {form.formState.errors.link_expry_ymd.message}

)} @@ -348,7 +348,7 @@ const LinkForm = ({ form, linkSn, updateLinkInfo, handleInsertLink, deleteLink, ( @@ -369,7 +369,7 @@ const LinkForm = ({ form, linkSn, updateLinkInfo, handleInsertLink, deleteLink, ( @@ -391,7 +391,7 @@ const LinkForm = ({ form, linkSn, updateLinkInfo, handleInsertLink, deleteLink, ( @@ -412,7 +412,7 @@ const LinkForm = ({ form, linkSn, updateLinkInfo, handleInsertLink, deleteLink, { const timeParts = convertMinutesToPeriod(Number(field.value)); return ( @@ -510,9 +510,9 @@ const LinkForm = ({ form, linkSn, updateLinkInfo, handleInsertLink, deleteLink,
- {form.formState.errors.link_cycle_mi && ( + {form.formState.errors.link_cycle_no && (

- {form.formState.errors.link_cycle_mi.message} + {form.formState.errors.link_cycle_no.message}

)} @@ -528,7 +528,7 @@ const LinkForm = ({ form, linkSn, updateLinkInfo, handleInsertLink, deleteLink, { return ( @@ -596,7 +596,7 @@ const LinkForm = ({ form, linkSn, updateLinkInfo, handleInsertLink, deleteLink, ( diff --git a/apps/cs/src/pages/link/ui/link-page.tsx b/apps/cs/src/pages/link/ui/link-page.tsx index a59b9e751..ac13cd7d2 100644 --- a/apps/cs/src/pages/link/ui/link-page.tsx +++ b/apps/cs/src/pages/link/ui/link-page.tsx @@ -1,4 +1,4 @@ -"use client"; +"use client"; import "@common/assets/styles/grid.css"; @@ -78,7 +78,7 @@ function LinkPage() { `${process.env.NEXT_PUBLIC_LINK_BACKEND_API_HOST}/getLinkInfo`, ], }); - router.push(`link/register/${link.link_sn}`); + router.push(`link/register/${link.intg_link_sn}`); }; const handleClickRegist = () => { diff --git a/apps/cs/src/pages/link/ui/modify-page.tsx b/apps/cs/src/pages/link/ui/modify-page.tsx index 88a83f47b..97dd513e4 100644 --- a/apps/cs/src/pages/link/ui/modify-page.tsx +++ b/apps/cs/src/pages/link/ui/modify-page.tsx @@ -1,4 +1,4 @@ -"use client"; +"use client"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { LinkApi, linkFormSchema, type LinkFormData } from "@/entities/link"; @@ -37,7 +37,7 @@ function ModifyLinkPage({ linkSn }: { linkSn: string }) { mutationFn: (data: LinkFormData) => LinkApi.updateLinkInfo({ ...data, link_clsf_cd: data.link_clsf_cd !== 'none' ? data.link_clsf_cd : null, - mdfr_id: userUniqId + mdfr_uuid: userUniqId }), onSuccess: () => { alert('수정되었습니다.'); @@ -75,17 +75,17 @@ function ModifyLinkPage({ linkSn }: { linkSn: string }) { if (linkInfo) { let newData = { ...linkInfo }; - if (linkInfo.mdfr_id) { + if (linkInfo.mdfr_uuid) { const userName = await fetchUserInfo(); newData = { ...linkInfo, - mdfr_id: userName, + mdfr_uuid: userName, }; } form.reset(newData); - setInitialIsNoExprie(newData.link_key_expry_ymd === 'none'); + setInitialIsNoExprie(newData.link_expry_ymd === 'none'); } }; diff --git a/apps/cs/src/pages/link/ui/regist-page.tsx b/apps/cs/src/pages/link/ui/regist-page.tsx index 76912ab9f..e5635c8bd 100644 --- a/apps/cs/src/pages/link/ui/regist-page.tsx +++ b/apps/cs/src/pages/link/ui/regist-page.tsx @@ -1,4 +1,4 @@ -"use client"; +"use client"; import { LinkApi, type LinkFormData, linkFormSchema } from "@/entities/link"; import LinkForm from "./link-form"; @@ -20,14 +20,14 @@ function RegistLinkPage() { resolver: zodResolver(linkFormSchema), defaultValues: { link_nm: '', - link_url: '', - link_srvr_ip: '', - link_db_cd: '', - link_key: '', - link_id: '', - link_pswd: '', - srvc_usg: '', - link_cycle_mi: 5, + link_url_addr: '', + link_srvr_ip_addr: '', + link_db_nm: '', + link_api_no: '', + link_cntn_nm: '', + link_cntn_tkn_no: '', + srvc_usg_nm: '', + link_cycle_no: 5, link_clsf_cd: 'none', }, }); @@ -44,8 +44,8 @@ function RegistLinkPage() { const insertData = { ...data, link_clsf_cd: data.link_clsf_cd !== 'none' ? data.link_clsf_cd : null, - next_link_excn_dt: dayjs().add(Number(data.link_cycle_mi), 'minute').format('YYYY-MM-DD HH:mm:ss'), - mdfr_id: userUniqId, + next_link_excn_dt: dayjs().add(Number(data.link_cycle_no), 'minute').format('YYYY-MM-DD HH:mm:ss'), + mdfr_uuid: userUniqId, }; insertLinkInfo(insertData); diff --git a/apps/cs/src/pages/linkLog/model/table-columns.tsx b/apps/cs/src/pages/linkLog/model/table-columns.tsx index 30e7fa702..d5180d524 100644 --- a/apps/cs/src/pages/linkLog/model/table-columns.tsx +++ b/apps/cs/src/pages/linkLog/model/table-columns.tsx @@ -1,4 +1,4 @@ -import dayjs from "dayjs"; +import dayjs from "dayjs"; import { createColumnHelper } from "@tanstack/react-table"; import { type LinkLog } from "@/entities/link"; @@ -9,24 +9,29 @@ const columns = [ id: 'rownum', header: 'NO', }), - columnHelper.accessor('link_sn', { - id: 'link_sn', + columnHelper.accessor('intg_link_sn', { + id: 'intg_link_sn', header: '연계일련번호', }), columnHelper.accessor('link_nm', { id: 'link_nm', header: '연계 명', }), + columnHelper.accessor('link_excn_stp_nm', { + id: 'link_excn_stp_nm', + header: '연계실행단계', + cell: (info) => (info.getValue() ? info.getValue() : '-'), + }), columnHelper.display({ - id: 'link_rslt_cd', + id: 'link_rslt_nm', header: '연계실행결과', cell: (info) => { - const isSucc = info.row.original.link_rslt_cd.trim() === 'SUCC'; + const isSucc = info.row.original.link_rslt_nm.trim() === 'SUCC'; return {isSucc ? '성공' : '실패'} }, }), - columnHelper.accessor('link_err_cd', { - id: 'link_err_cd', + columnHelper.accessor('link_err_nm', { + id: 'link_err_nm', header: '연계오류코드', cell: (info) => (info.getValue() ? info.getValue() : '-'), }), diff --git a/apps/cs/src/pages/linkLog/ui/link-log-page.tsx b/apps/cs/src/pages/linkLog/ui/link-log-page.tsx index 5a0637b72..9dcd72341 100644 --- a/apps/cs/src/pages/linkLog/ui/link-log-page.tsx +++ b/apps/cs/src/pages/linkLog/ui/link-log-page.tsx @@ -33,6 +33,7 @@ function LinkLogPage() { const [linkSn, setLinkSn] = useState(""); const [linkNm, setLinkNm] = useState(""); + const [linkExecStep, setLinkExecStep] = useState("ALL"); const [isSucc, setIsSucc] = useState("ALL"); const [startDate, setStartDate] = useState( dayjs().subtract(7, "day").format("YYYY-MM-DD"), @@ -42,6 +43,7 @@ function LinkLogPage() { const [linkSearchParam, setLinkSearchParam] = useState({ linkSn: "", linkNm: "", + linkExecStep: "", isSucc: "", startDate: "", endDate: "", @@ -65,6 +67,7 @@ function LinkLogPage() { ...linkSearchParam, page: pagination.pageIndex, size: pagination.pageSize, + linkExecStep, isSucc, startDate, endDate, @@ -91,6 +94,7 @@ function LinkLogPage() { }); const handleResetSearchParam = () => { + setLinkExecStep("ALL"); setIsSucc("ALL"); setLinkNm(""); setLinkSn(""); @@ -100,6 +104,7 @@ function LinkLogPage() { ...linkSearchParam, linkSn: "", linkNm: "", + linkExecStep: "ALL", isSucc: "ALL", startDate: dayjs().subtract(7, "day").format("YYYY-MM-DD"), endDate: dayjs().format("YYYY-MM-DD"), @@ -122,6 +127,7 @@ function LinkLogPage() { ...linkSearchParam, linkSn, linkNm, + linkExecStep, isSucc, startDate, endDate, @@ -157,6 +163,24 @@ function LinkLogPage() { onChange={(e) => setLinkNm(e.target.value)} autoComplete="off" /> + + diff --git a/apps/cs/src/pages/linkParams/model/table-columns.tsx b/apps/cs/src/pages/linkParams/model/table-columns.tsx index a71a74e9d..868019e31 100644 --- a/apps/cs/src/pages/linkParams/model/table-columns.tsx +++ b/apps/cs/src/pages/linkParams/model/table-columns.tsx @@ -1,4 +1,4 @@ -import { createColumnHelper } from "@tanstack/react-table"; +import { createColumnHelper } from "@tanstack/react-table"; import { type Link } from "@/entities/link"; const columnHelper = createColumnHelper(); @@ -8,16 +8,16 @@ const columns = [ id: 'no', header: 'NO', }), - columnHelper.accessor('link_sn', { - id: 'link_sn', + columnHelper.accessor('intg_link_sn', { + id: 'intg_link_sn', header: '연계 일련번호', }), columnHelper.accessor('link_nm', { id: 'link_nm', header: '연계 명', }), - columnHelper.accessor('srvc_usg', { - id: 'srvc_usg', + columnHelper.accessor('srvc_usg_nm', { + id: 'srvc_usg_nm', header: '서비스용도', cell: (info) => (info.getValue() ? info.getValue() : '-'), }), diff --git a/apps/cs/src/pages/linkParams/model/vrbl-table-columns.tsx b/apps/cs/src/pages/linkParams/model/vrbl-table-columns.tsx index 26ccc7303..c9026097d 100644 --- a/apps/cs/src/pages/linkParams/model/vrbl-table-columns.tsx +++ b/apps/cs/src/pages/linkParams/model/vrbl-table-columns.tsx @@ -1,4 +1,4 @@ -import { createColumnHelper } from "@tanstack/react-table"; +import { createColumnHelper } from "@tanstack/react-table"; import { type LinkVrbl } from "@/entities/link"; import { Checkbox } from "@common/components"; @@ -70,15 +70,22 @@ function vrblColumns({ id: "link_vrbl_nm", header: "변수 명", }), - columnHelper.accessor("link_vrbl_cd", { - id: "link_vrbl_cd", + columnHelper.accessor("link_vrbl_cd_nm", { + id: "link_vrbl_cd_nm", header: "변수코드", cell: (info) => (info.getValue() ? info.getValue() : '-'), }), - columnHelper.accessor("link_vrbl_vl", { - id: "link_vrbl_vl", + columnHelper.accessor("link_vrbl_vl_cn", { + id: "link_vrbl_vl_cn", header: "변수 값", - cell: (info) => (info.getValue() === 'now' ? '요청일자' : info.getValue() === 'nowMonth' ? '요청월' : info.getValue()), + cell: (info) => { + const value = info.getValue(); + if (value === 'now') return '요청일자'; + if (value === 'nowMonth') return '요청월'; + if (value === 'nowHour') return '요청일자(시간포함)'; + if (value === 'nowHourMin') return '요청일자(시분포함)'; + return value; + }, }), columnHelper.accessor("link_vrbl_expln", { id: "link_vrbl_expln", diff --git a/apps/cs/src/pages/linkParams/ui/link-params-dialog.tsx b/apps/cs/src/pages/linkParams/ui/link-params-dialog.tsx index 9a3e2b87e..86675aede 100644 --- a/apps/cs/src/pages/linkParams/ui/link-params-dialog.tsx +++ b/apps/cs/src/pages/linkParams/ui/link-params-dialog.tsx @@ -1,4 +1,4 @@ -'use client'; +'use client'; import { Dialog, @@ -71,21 +71,29 @@ function LinkParamsDialog({ selectedLink, form, insertLinkVrbl, dialogOpen, onOp if (await confirm(confirmMsg)) { if (isModifyMode) { - updateLinkVrbl(isToday ? { ...cleanData, link_vrbl_vl: checkNowMonth } : cleanData); + updateLinkVrbl(isToday ? { ...cleanData, link_vrbl_vl_cn: checkNowMonth } : cleanData); } else { - insertLinkVrbl(isToday ? { ...cleanData, link_vrbl_vl: checkNowMonth } : cleanData); + insertLinkVrbl(isToday ? { ...cleanData, link_vrbl_vl_cn: checkNowMonth } : cleanData); } } }); }; + const getTodayValueLabel = (value: string) => { + if (value === 'now') return '요청일자'; + if (value === 'nowMonth') return '요청월'; + if (value === 'nowHour') return '요청일자(시간포함)'; + if (value === 'nowHourMin') return '요청일자(시분포함)'; + return value; + }; + const handleCheckTodayValue = (check: boolean | string) => { if (check) { setIsToday(true); - form.setValue('link_vrbl_vl', (checkNowMonth === 'now' ? '요청일자' : '요청월')); + form.setValue('link_vrbl_vl_cn', getTodayValueLabel(checkNowMonth)); } else { setIsToday(false); - form.setValue('link_vrbl_vl', ''); + form.setValue('link_vrbl_vl_cn', ''); } }; @@ -96,14 +104,14 @@ function LinkParamsDialog({ selectedLink, form, insertLinkVrbl, dialogOpen, onOp form.reset({ link_vrbl_nm: '', link_vrbl_expln: '', - link_vrbl_cd: '', - link_vrbl_vl: '', + link_vrbl_cd_nm: '', + link_vrbl_vl_cn: '', }); } - if (isModifyMode && (form.getValues('link_vrbl_vl') === 'now' || form.getValues('link_vrbl_vl') === 'nowMonth')) { + if (isModifyMode && ['now', 'nowMonth', 'nowHour', 'nowHourMin'].includes(form.getValues('link_vrbl_vl_cn') ?? '')) { setIsToday(true); - setCheckNowMonth(form.getValues('link_vrbl_vl')); + setCheckNowMonth(form.getValues('link_vrbl_vl_cn')); } }, [dialogOpen]); @@ -118,7 +126,7 @@ function LinkParamsDialog({ selectedLink, form, insertLinkVrbl, dialogOpen, onOp - + {selectedLink.linkNm} ({selectedLink.linkSn}) @@ -213,7 +221,7 @@ function LinkParamsDialog({ selectedLink, form, insertLinkVrbl, dialogOpen, onOp ( @@ -224,9 +232,9 @@ function LinkParamsDialog({ selectedLink, form, insertLinkVrbl, dialogOpen, onOp autoComplete="off" /> - {form.formState.errors.link_vrbl_cd && ( + {form.formState.errors.link_vrbl_cd_nm && (

- {form.formState.errors.link_vrbl_cd.message} + {form.formState.errors.link_vrbl_cd_nm.message}

)}
@@ -239,7 +247,7 @@ function LinkParamsDialog({ selectedLink, form, insertLinkVrbl, dialogOpen, onOp (
@@ -252,7 +260,7 @@ function LinkParamsDialog({ selectedLink, form, insertLinkVrbl, dialogOpen, onOp name="checkNowMonth" onValueChange={(value) => { setCheckNowMonth(value); - form.setValue('link_vrbl_vl', (value === 'now' ? '요청일자' : '요청월')); + form.setValue('link_vrbl_vl_cn', getTodayValueLabel(value)); }} >
@@ -263,21 +271,29 @@ function LinkParamsDialog({ selectedLink, form, insertLinkVrbl, dialogOpen, onOp
+
+ + +
+
+ + +
} - {form.formState.errors.link_vrbl_vl && ( + {form.formState.errors.link_vrbl_vl_cn && (

- {form.formState.errors.link_vrbl_vl.message} + {form.formState.errors.link_vrbl_vl_cn.message}

)}
@@ -292,7 +308,7 @@ function LinkParamsDialog({ selectedLink, form, insertLinkVrbl, dialogOpen, onOp ( diff --git a/apps/cs/src/pages/linkParams/ui/link-params-page.tsx b/apps/cs/src/pages/linkParams/ui/link-params-page.tsx index 001eb9953..8eba90176 100644 --- a/apps/cs/src/pages/linkParams/ui/link-params-page.tsx +++ b/apps/cs/src/pages/linkParams/ui/link-params-page.tsx @@ -1,4 +1,4 @@ -"use client"; +"use client"; import { useState, useMemo } from "react"; import { @@ -123,8 +123,8 @@ function LinkParamsPage() { mutationFn: async (data: LinkVrblFormData) => await LinkApi.insertLinkVrbl({ ...data, - link_sn: selectedLink.linkSn, - mdfr_id: userUniqId, + intg_link_sn: selectedLink.linkSn, + mdfr_uuid: userUniqId, }), onSuccess: async () => { // 쿼리 무효화를 통한 최신 데이터 가져오기 @@ -140,8 +140,8 @@ function LinkParamsPage() { mutationFn: async (data: LinkVrblFormData) => await LinkApi.updateLinkVrblInfo({ ...data, - link_sn: selectedLink.linkSn, - mdfr_id: userUniqId, + intg_link_sn: selectedLink.linkSn, + mdfr_uuid: userUniqId, }), onSuccess: async () => { await queryClient.invalidateQueries({ @@ -191,7 +191,7 @@ function LinkParamsPage() { const handleRowClick = async (link: Link) => { setSelectedLink({ - linkSn: link.link_sn, + linkSn: link.intg_link_sn, linkNm: link.link_nm, }); @@ -242,11 +242,11 @@ function LinkParamsPage() { if (linkVrblInfo) { let newData = { ...linkVrblInfo }; - if (linkVrblInfo.mdfr_id) { + if (linkVrblInfo.mdfr_uuid) { const userName = await fetchUserInfo(); newData = { ...linkVrblInfo, - mdfr_id: userName, + mdfr_uuid: userName, }; } diff --git a/apps/cs/src/pages/linkStats/ui/link-stats-page.tsx b/apps/cs/src/pages/linkStats/ui/link-stats-page.tsx index 19163d24c..503e925d7 100644 --- a/apps/cs/src/pages/linkStats/ui/link-stats-page.tsx +++ b/apps/cs/src/pages/linkStats/ui/link-stats-page.tsx @@ -1,4 +1,4 @@ -"use client"; +"use client"; import { useState, useEffect, useRef } from "react"; import dayjs from "dayjs"; @@ -255,7 +255,7 @@ function LinkStatsPage() { setLogQueryParam({ isSucc: "SUCC", linkNm: linkStatsList.linkSuccStats[index]?.link_nm, - linkSn: linkStatsList.linkSuccStats[index]?.link_sn, + linkSn: linkStatsList.linkSuccStats[index]?.intg_link_sn, }); } }; @@ -284,7 +284,7 @@ function LinkStatsPage() { setLogQueryParam({ isSucc: "FAIL", linkNm: linkStatsList.linkFailStats[index]?.link_nm, - linkSn: linkStatsList.linkFailStats[index]?.link_sn, + linkSn: linkStatsList.linkFailStats[index]?.intg_link_sn, }); } }; diff --git a/apps/cs/src/pages/linkTable/ui/link-table-dialog.tsx b/apps/cs/src/pages/linkTable/ui/link-table-dialog.tsx index be835872d..3517393b4 100644 --- a/apps/cs/src/pages/linkTable/ui/link-table-dialog.tsx +++ b/apps/cs/src/pages/linkTable/ui/link-table-dialog.tsx @@ -1,4 +1,4 @@ -'use client'; +'use client'; import { Dialog, @@ -61,13 +61,13 @@ function LinkTableDialog({ dialogOpen, onOpenChange, form, tableInfo, insertLink if (!isModifyMode) { insertLinkTableCol({ ...data, - link_sn: tableInfo.link_sn, + intg_link_sn: tableInfo.intg_link_sn, tbl_sn: tableInfo.tbl_sn, }); } else { updateLinkTableCol({ ...data, - link_sn: tableInfo.link_sn, + intg_link_sn: tableInfo.intg_link_sn, tbl_sn: tableInfo.tbl_sn, }); } @@ -92,16 +92,16 @@ function LinkTableDialog({ dialogOpen, onOpenChange, form, tableInfo, insertLink col_sn: '', sou_col_nm: '', sou_col_korn_nm: '', - sou_col_data_frm: '', + sou_col_frm_nm: '', sou_col_sz: null, trsf_col_nm: '', trsf_col_korn_nm: '', - trsf_col_data_frm: '', + trsf_col_frm_nm: '', trsf_col_sz: null, col_seq: null, pk_yn: 'N', mdfcn_dt: '', - mdfr_id: '', + mdfr_uuid: '', }); } @@ -216,7 +216,7 @@ function LinkTableDialog({ dialogOpen, onOpenChange, form, tableInfo, insertLink ( @@ -227,9 +227,9 @@ function LinkTableDialog({ dialogOpen, onOpenChange, form, tableInfo, insertLink autoComplete="off" /> - {form.formState.errors.sou_col_data_frm && ( + {form.formState.errors.sou_col_frm_nm && (

- {form.formState.errors.sou_col_data_frm.message} + {form.formState.errors.sou_col_frm_nm.message}

)}
@@ -349,7 +349,7 @@ function LinkTableDialog({ dialogOpen, onOpenChange, form, tableInfo, insertLink ( @@ -360,9 +360,9 @@ function LinkTableDialog({ dialogOpen, onOpenChange, form, tableInfo, insertLink autoComplete="off" /> - {form.formState.errors.trsf_col_data_frm && ( + {form.formState.errors.trsf_col_frm_nm && (

- {form.formState.errors.trsf_col_data_frm.message} + {form.formState.errors.trsf_col_frm_nm.message}

)}
@@ -444,7 +444,7 @@ function LinkTableDialog({ dialogOpen, onOpenChange, form, tableInfo, insertLink ( diff --git a/apps/cs/src/pages/linkTable/ui/link-table-page.tsx b/apps/cs/src/pages/linkTable/ui/link-table-page.tsx index 3ccee78e8..55e541fc4 100644 --- a/apps/cs/src/pages/linkTable/ui/link-table-page.tsx +++ b/apps/cs/src/pages/linkTable/ui/link-table-page.tsx @@ -1,4 +1,4 @@ -"use client"; +"use client"; import { useState, useMemo } from "react"; import { @@ -97,7 +97,7 @@ function LinkTablePage() { mutationFn: async (data: LinkTableColData) => await LinkApi.insertLinkTableCol({ ...data, - mdfr_id: userUniqId, + mdfr_uuid: userUniqId, }), onSuccess: async () => { if (selectedTableInfo) { @@ -113,10 +113,10 @@ function LinkTablePage() { }); const tableData = await LinkApi.getLinkTableInfo( - selectedTableInfo.link_sn, + selectedTableInfo.intg_link_sn, ); const tableColList = await LinkApi.getLinkTableColList( - selectedTableInfo.link_sn, + selectedTableInfo.intg_link_sn, ); setSelectedTableInfo(tableData); @@ -132,7 +132,7 @@ function LinkTablePage() { mutationFn: async (data: LinkTableColData) => await LinkApi.updateLinkTableCol({ ...data, - mdfr_id: userUniqId, + mdfr_uuid: userUniqId, }), onSuccess: async () => { if (selectedTableInfo) { @@ -148,10 +148,10 @@ function LinkTablePage() { }); const tableData = await LinkApi.getLinkTableInfo( - selectedTableInfo.link_sn, + selectedTableInfo.intg_link_sn, ); const tableColList = await LinkApi.getLinkTableColList( - selectedTableInfo.link_sn, + selectedTableInfo.intg_link_sn, ); setSelectedTableInfo(tableData); @@ -180,10 +180,10 @@ function LinkTablePage() { }); const tableData = await LinkApi.getLinkTableInfo( - selectedTableInfo.link_sn, + selectedTableInfo.intg_link_sn, ); const tableColList = await LinkApi.getLinkTableColList( - selectedTableInfo.link_sn, + selectedTableInfo.intg_link_sn, ); setSelectedTableInfo(tableData); @@ -212,14 +212,14 @@ function LinkTablePage() { }); const tableData = await LinkApi.getLinkTableInfo( - selectedTableInfo.link_sn, + selectedTableInfo.intg_link_sn, ); const tableColList = await LinkApi.getLinkTableColList( - selectedTableInfo.link_sn, + selectedTableInfo.intg_link_sn, ); tableForm.reset({ - link_sn: tableData.link_sn, + intg_link_sn: tableData.intg_link_sn, link_nm: tableData.link_nm, tbl_sn: tableData.tbl_sn ?? "", sou_tbl_nm: tableData.sou_tbl_nm ?? "", @@ -254,10 +254,10 @@ function LinkTablePage() { }); const tableData = await LinkApi.getLinkTableInfo( - selectedTableInfo.link_sn, + selectedTableInfo.intg_link_sn, ); const tableColList = await LinkApi.getLinkTableColList( - selectedTableInfo.link_sn, + selectedTableInfo.intg_link_sn, ); setSelectedTableInfo(tableData); @@ -316,15 +316,15 @@ function LinkTablePage() { }; const handleRowClick = async (data: Link) => { - const tableData = await LinkApi.getLinkTableInfo(data.link_sn); - const tableColList = await LinkApi.getLinkTableColList(data.link_sn); + const tableData = await LinkApi.getLinkTableInfo(data.intg_link_sn); + const tableColList = await LinkApi.getLinkTableColList(data.intg_link_sn); setSelectedTableInfo(tableData); setTableColList(tableColList.content); setSelectedColIds([]); tableForm.reset({ - link_sn: tableData.link_sn, + intg_link_sn: tableData.intg_link_sn, link_nm: tableData.link_nm, tbl_sn: tableData.tbl_sn ?? "", sou_tbl_nm: tableData.sou_tbl_nm ?? "", @@ -345,11 +345,11 @@ function LinkTablePage() { if (colInfo) { let newData = { ...colInfo }; - if (colInfo.mdfr_id) { + if (colInfo.mdfr_uuid) { const userName = await fetchUserInfo(); newData = { ...colInfo, - mdfr_id: userName, + mdfr_uuid: userName, }; } @@ -387,7 +387,7 @@ function LinkTablePage() { if (await confirm(confirmMsg)) { insertLinkTableInfo({ ...data, - mdfr_id: userUniqId, + mdfr_uuid: userUniqId, tbl_sn: regTblInfo.count > 0 ? regTblInfo.tbl_sn : "", }); } @@ -398,7 +398,7 @@ function LinkTablePage() { updateLinkTableInfo({ ...data, tbl_sn: regTblInfo.count > 0 ? regTblInfo.tbl_sn : "", - mdfr_id: userUniqId, + mdfr_uuid: userUniqId, }); } }); @@ -511,7 +511,7 @@ function LinkTablePage() { {selectedTableInfo ? ( (