[as] docs: 실무협의 로직 개발 가이드 추가

This commit is contained in:
2026-09-07 15:22:21 +09:00
parent 2bfe653ac0
commit 94af73a810
6 changed files with 1461 additions and 800 deletions
@@ -0,0 +1,363 @@
# 재원협의·실무협의 프론트엔드 개발 가이드
> **대상 앱**: `frontend/apps/as`
> **작성일**: 2026-08-27
> **개정일**: 2026-09-07
> **버전**: 2.0
> **Process**: PR-AS-003 (재원협의 관리 — 실무협의 및 AI 검증 통합)
>
> **SoT (2026-09-03 완결)**:
>
> - 프로세스·상태·버튼: `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`)
>
> **백엔드 서비스**:
>
> - `as-service` — `/api/v1/fnrs-cnsltn-mng`, `/api/v1/pwrk-cnsltn-mng`, `/api/v1/pwrk-cnsltn-doc-crt-rslt`, `/api/v1/dcrag-to`
> - `as-elapr-service` — `/api/v1/elapr-dmnd`, `/api/v1/elapr-prgrs-stts`, `/api/v1/elapr-apln`
>
> **현재 프론트 상태**: 재원협의(`fnrs-cnsltn`)·전자결재(`elapr`)·DC RAG TO(`dcrag-to`)는 존재한다. 실무협의(`pwrk-cnsltn-*`) 엔티티·화면은 **미구현**. 버튼 매트릭스·상태코드는 **v2.1 잔존**이며 v3.0(`FNRSSTTS`)과 불일치한다. [9절](#9-현재-코드-vs-to-be-갭) 참고.
---
## 1. 문서 목적
재원협의 4대 주체(기초·광역·환경청·공단)의 **상태 전이·버튼·API**를 `apps/as`에 구현할 때 쓰는 프론트엔드 가이드이다.
v1.0은 공단 접수자의 **자료검증 → 초안생성 → 웹기안기**만 다뤘다. v2.0은 2026-09-03 완결 프로세스 기준으로 **신청부터 최종종결(`TA`)까지**, 실무협의·AI 3파이프라인·결재선·보완 루프를 포함한다.
상세 표·시퀀스·DTO는 본문에 넣지 않고 레퍼런스로 분리한다. 구현 중에는 본문의 규칙만 보고, 표가 필요하면 해당 파일을 연다.
---
## 2. 문서 맵
| 파일 | 내용 | 언제 보는가 |
| -------------------------------------------------------- | ------------------------------------------------------------------------- | ----------------------- |
| [development-guide.md](./development-guide.md) (본 문서) | 도메인, 핵심 규칙, 화면 흐름, 갭, 구현 순서 | 일상 구현 |
| [ref-process-status.md](./ref-process-status.md) | 4-Lane 시퀀스, Activity, `FNRSSTTS`, 보완 3루프, AI 타임아웃, 결재선 복사 | 상태 전이·폴링 UX |
| [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 | 엔티티 레이어 구현 |
---
## 3. 도메인 개요
실무협의서(PWRK)는 재원협의서(FNRS)의 **하위 문서**이다. 재원협의 1건당 실무협의서는 **1건만** 생성된다.
| 문서 | 번호 예시 | 필드 |
| -------- | ----------------- | ----------------------------- |
| 재원협의 | `F-20260824-0001` | `fnrsCnsltnDocNo` / `upDocNo` |
| 실무협의 | `P-20260827-0001` | `pwrkCnsltnDocNo` |
진행상태는 **재원협의 마스터** `tb_as_fnrs_cnsltn_mng.prgrs_stts_com_cd` (`FNRSSTTS`)가 SSOT이다. 실무협의 문서의 `prgrsSttsComCd`는 연계 표시용이며, 화면 버튼은 재원협의 상태로 분기한다.
### 3-1. 4대 주체
| 역할 | 코드 | 화면 소유 | 핵심 구간 |
| --------------- | ------------- | -------------- | ------------------------------------ |
| 기초자치단체 | `ROLE_USER05` | 작성/수정/상세 | 신청, 3종 보완, 조치결과, 최종확인 |
| 광역자치단체 | `ROLE_USER04` | 상세 | 경유 제출, 결과 전달 |
| 유역·지방환경청 | `ROLE_USER03` | 상세 | 의뢰/자체종결, 1·2차 회신 |
| 한국환경공단 | `ROLE_USER01` | 상세 + 결재선 | 파일검증, 검토자지정, 1·2차 기술검토 |
### 3-2. 핵심 식별자
| 필드 | 의미 | 비고 |
| ----------------- | ---------------- | ----------------------------------------------------------- |
| `fnrsCnsltnDocNo` | 재원협의문서번호 | 상태 전이·목록 PK |
| `pwrkCnsltnDocNo` | 실무협의문서번호 | `P-YYYYMMDD-NNNN`. 검토자지정(`ER→AC`) 시 발급 |
| `upDocNo` | 상위문서번호 | 재원협의 번호. `upDocSeComCd = "FNRS"` |
| `crtDmndUuid` | 생성요청 UUID | 초안 폴링·웹기안기·다운로드 키 |
| `verSn` | 버전일련번호 | 복합 PK `(crtDmndUuid, verSn)`. 저장 시 서버가 `+1` |
| `orgnlOtptDocCn` | HWP 바이너리 | 목록/상세 응답은 항상 `null`. 다운로드·`webhwp/open`만 사용 |
| `elaprCycl` | 결재 차수 | 1차 `TE`, 2차 `RK`. 금액검증 완료 후 1차 결재선 복사 |
### 3-3. IIPBS 약어
| 한글 | 약어 | 필드 예 |
| ---- | ------------ | --------------------------- |
| 재원 | FNRS | `fnrsCnsltnDocNo` |
| 실무 | PWRK | `pwrkCnsltnDocNo` |
| 협의 | CNSLTN | `fnrs-cnsltn-mng` |
| 생성 | CRT | `crtDmndUuid` |
| 요청 | DMND | `crtDmndUuid`, `elapr-dmnd` |
| 검증 | VRFC | `vrfcSttsComCd` |
| 보완 | SPLMNT | `splmntDmndList` |
| 결재 | ELAPR / ATRZ | `elaprCycl`, `atrzSeComCd` |
---
## 4. 상태코드 한눈에 보기
상위코드 **`FNRSSTTS`**. v1.0의 `PWRKSTTS`(`DV`, `AR`, `TR` 등)는 **폐기**한다.
정방향:
```
SV → ST → LE → FV → ER → AC → TE → TP → RM → RB → RC → RA → AV → RK → RR → EP → TA
```
자체종결: `LE → EP → TA`
보완: `ER|TE|RK → SL` 후 직전 상태로 복귀.
| 코드 | 명칭 | FE에서 할 일 |
| ---- | ---------------------- | ------------------------------------------------- |
| `SV` | 저장 | 기초 작성화면 저장/신청/삭제 |
| `ST` | 신청완료 | 광역 환경청제출 |
| `LE` | 환경청 검토 | 자체종결(`EP`) 또는 기술검토 의뢰(`FV`) |
| `FV` | 파일검증 | AI 진행 표시. **30분** 후 `ER` Fallback |
| `ER` | 기술검토 의뢰 | 공단 접수자: 파일보완 / 수동파일검증 / 검토자지정 |
| `SL` | 보완요청 | 기초 수정화면. 복귀 대상은 이력의 직전 상태 |
| `AC` | 초안생성 | AI 진행 표시. **30분** 후 `TE` Fallback |
| `TE` | 기술검토 | 웹기안기·검토완료·보완. 결재선 1차 |
| `TP` | 기술검토 회신 | 환경청 → 광역 `RM` |
| `RM` | 재원협의결과회신 | 광역 → 기초 `RB` |
| `RB` | 지자체회신 | 기초 조치결과 저장/협의회신 |
| `RC` | 조치결과서 제출 | 광역 → 환경청 `RA` |
| `RA` | 조치결과서제출(환경청) | 환경청 2차 의뢰 `AV` |
| `AV` | 금액검증 | AI 진행 표시. **30분** 후 `RK` Fallback |
| `RK` | 조치결과서검토 | 웹기안기·금액검증 재실행·보완. 결재선 2차 |
| `RR` | 조치결과서검토 회신 | 환경청 → 광역 `EP` |
| `EP` | 검토결과 회신 | 광역 → 기초 `TA` |
| `TA` | 검토결과 회신(종료) | 기초 조회만. **고시(`TB`) 아님** |
코드 의미·구버전 대조·보완 루프·AI Fallback은 [ref-process-status.md](./ref-process-status.md).
---
## 5. 프론트가 지켜야 할 핵심 규칙
1. **상태 SSOT는 `FNRSSTTS`**. 버튼 가시성은 `(dtlInstCd, prgrsSttsComCd, atrzSeComCd)`로만 계산한다. 공단은 결재선 구분(`ATRZ01` 접수자, `ATRZ02` 검토자, `ATRZ03` 승인자, `ATRZ99` 최종승인자)을 추가로 본다.
2. **AI 콜백 API는 화면에서 호출하지 않는다.** `POST /dcrag/documents/verify-result`, `POST /dcrag/draft/complete`, `POST /dcrag/cost/verify-result`는 서버 전용이다.
3. **초안생성 1차 트리거는 검토자지정이다.** `POST /elapr-dmnd` (`ER → AC`). 예전처럼 `POST /pwrk-cnsltn-mng`로 초안을 시작하지 않는다.
4. **기술검토 의뢰는 `LE → FV` 전용 API** `POST /fnrs-cnsltn-mng/{docNo}/request-examine`. 현재 코드의 `updatePrgrsStts(ER)` 직행은 폐기.
5. **수동 AI 재실행은 상태를 바꾸지 않는다.**
- `ER` 파일검증 → `POST /dcrag-to/documents/transfer`
- `TE` 초안생성 → `POST /dcrag-to/documents/draft/generate` (타임아웃·실패 시에만)
- `RK` 금액검증 → `POST /dcrag-to/documents/cost/verify`
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차 결재선을 **조회·표시**만 한다.
10. **웹기안기는 URL을 직접 Open**. blob Object URL을 `HwpCtrl.Open`에 넘기지 않는다. `orgnlOtptDocCn`은 쓰지 않는다.
---
## 6. 화면 데이터 흐름
재원협의 **목록 → 상세/작성**이 본체다. 실무협의 목록은 공단 검토 보조 화면이다.
```
BEGIN
CALL fnrsCnsltnMngService.findPage(...)
ON 행 CLICK → /fnrs-cnsltn/{fnrsCnsltnDocNo}
CALL findById(fnrsCnsltnDocNo)
COMPUTE buttons = getButtonConfigs(dtlInstCd, prgrsSttsComCd, atrzSeComCdList)
// ----- 기초 ROLE_USER05 -----
ON [저장] (빈값/SV) → saveApply(prgrsSttsComCd: SV)
ON [신청완료] (SV) → saveApply(prgrsSttsComCd: ST)
ON [삭제] (SV) → remove
ON [파일보완완료] (SL←ER) → submitsupplement
ON [보완완료] (SL←TE|RK) → submitsupplement
ON [저장] (RB) → submit-actn-result { isSubmit: false }
ON [협의회신] (RB) → submit-actn-result { isSubmit: true }
ON [결과회신] (TA) → API 없음 (조회)
// ----- 광역 ROLE_USER04 -----
ON [환경청제출] (ST) → updatePrgrsStts(LE)
ON [지자체회신] (RM) → updatePrgrsStts(RB)
ON [조치결과서제출] (RC) → updatePrgrsStts(RA)
ON [결과회신] (EP) → updatePrgrsStts(TA)
// ----- 환경청 ROLE_USER03 -----
ON [결과회신] (LE 자체종결) → tech-review → EP
ON [기술검토 의뢰] (LE) → request-examine → FV → START POLL FV
ON [재원협의 결과 회신] (TP) → tech-review → RM
ON [조치결과서 검토요청] (RA) → request-actn-review → AV → START POLL AV
ON [결과회신] (RR) → updatePrgrsStts(EP)
// ----- 공단 ROLE_USER01 -----
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 [실무협의서관리] (TE|RK) → HwpCtrl.Open(webhwp/open)
ON [실무협의서생성] (TE, 실패/타임아웃) → dcrag-to draft/generate
ON [검토결과조회] (TE) → pwrk-cnsltn-mng findById | findByUpDocNo
ON [저장] (TE|RK) → pwrk-cnsltn-mng save-apply
ON [검토완료] → elapr-prgrs-stts process (상신)
ON [보완요청] (TE|RK) → 보완 팝업 → reqsupplement
ON [금액검증] (RK) → dcrag-to cost/verify
ON [결재(승인)] → process (중간)
ON [결재(회신)] → process (최종 → TP 또는 RR)
// ----- AI 진행 중 (FV / AC / AV) -----
POLL 상태 또는 draft status. 30분 안내. 종료 후 상세 invalidate
END
```
38행 버튼 표는 [ref-screen-buttons.md](./ref-screen-buttons.md). API 상세는 [ref-endpoint.md](./ref-endpoint.md).
---
## 7. 버튼 → API 축약
프론트가 치는 것만. 콜백·스케줄러는 제외.
| 주체 | 버튼 | 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` / `RB→RC` | **신규** `POST /pwrk-cnsltn-mng/{pwrkDocNo}/submit-actn-result` |
| 광역 | 조치결과서제출 | `RC→RA` | `updatePrgrsStts` |
| 환경청 | 조치결과서 검토요청 | `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`.
---
## 8. 프론트엔드 구현 가이드
### 8-1. 기술 스택·원칙
- App Router + FSD (`app/` → `pages/` → `widgets/` → `entities/` → `shared/`)
- React Query. ad-hoc `useQuery` + service 직접 호출 금지
- `@common/components`, `useDataTable`, `@tanstack/react-table`
- `'use client'`는 Web API(다운로드, 폴링, 웹기안기)가 있는 페이지만
- 함수명 = Controller public 메서드명 (`delete` → `remove`)
- 타입명 = 백엔드 DTO명
- Save/Apply Request = zod SSOT. Response는 `interface`
참조 슬라이스: `entities/fnrs-cnsltn/`, `entities/elapr/`, `entities/dcrag-to/`, `entities/sewer-bsc-plan/`
### 8-2. 모듈 배치
| 구분 | 경로 |
| ------------- | ------------------------------------------------------------------------------------------------- |
| ENDPOINT | `apps/as/src/shared/config/api.ts` |
| 재원협의 | `entities/fnrs-cnsltn/` — **확장** (`requestExamine`, `requestActnReview`, `findFileVrfcResults`) |
| 실무협의 관리 | `entities/pwrk-cnsltn-mng/` — **신규** |
| 생성결과 | `entities/pwrk-cnsltn-doc-crt-rslt/` — **신규** |
| DC RAG TO | `entities/dcrag-to/` — 구현됨 |
| 전자결재 | `entities/elapr/` — 구현됨. `copyLine`은 서버 전용 |
| 재원협의 화면 | `pages/fnrs-cnsltn/` — 버튼 매트릭스 전면 개편 |
| 실무협의 화면 | `pages/pwrk-cnsltn/` — 목록/상세 (선택, 공단 보조) |
파일 트리·Service 시그니처·폴링 훅은 [ref-frontend-entities.md](./ref-frontend-entities.md).
### 8-3. 버튼 매트릭스 구현
`pages/fnrs-cnsltn/lib/button-config.ts`를 v3.0 표로 교체한다.
- 키: `Record<Role, Record<FnrsStts, Action[]>>`
- 공단은 `Record<FnrsStts, Record<AtrzSe, Action[]>>` (`ER`/`TE`/`RK`)
- AI 상태(`FV`, `AC`, `AV`)는 버튼 대신 **진행 패널** (진행률·경과시간·30분 안내)
- `SL`은 기초만 수정 버튼. 복귀 종류(파일/기술/금액)는 `sttsChgHstryList` 직전 코드로 라벨만 분기
- `TA`는 조회 전용. **고시(`TB`) 액션 삭제**
### 8-4. AI 3파이프라인 UX
| 상태 | 진입 버튼 | 완료 상태 | 30분 Fallback | 수동 버튼 |
| ---- | ------------------- | --------- | ----------------------- | ----------- |
| `FV` | 기술검토 의뢰 | `ER` | `ER` + [파일검증] | 공단 접수자 |
| `AC` | 검토자지정 | `TE` | `TE` + [실무협의서생성] | 공단 검토자 |
| `AV` | 조치결과서 검토요청 | `RK` | `RK` + [금액검증] | 공단 검토자 |
폴링: 초안은 `GET /dcrag-to/documents/draft/status/{crtDmndUuid}` (5~10초). 파일검증·금액검증은 상세 `prgrsSttsComCd` refetch로도 충분하다. `COMPLETED`/`FAILED` 또는 상태 이탈 시 중단하고 상세 invalidate.
초안 `COMPLETED` 전에는 [실무협의서관리]·[다운로드] 비활성. Fallback으로 `TE`가 되었는데 초안이 없으면 [실무협의서생성]만 활성.
### 8-5. 보완 팝업 3종
같은 `reqsupplement` / `submitsupplement`를 쓰고 팝업 UI만 나눈다.
| 단계 | 팝업 | 요청 주체 | 복귀 |
| ---- | ------------------------- | ----------- | ---------------------- |
| `ER` | 보완 검토팝업(파일리스트) | 공단 접수자 | `SL→ER` [파일보완완료] |
| `TE` | 보완 검토팝업() | 공단 검토자 | `SL→TE` [보완완료] |
| `RK` | 보완 검토팝업(금액) | 공단 검토자 | `SL→RK` [보완완료] |
파일 팝업 데이터: `GET /fnrs-cnsltn-mng/{docNo}/file-vrfc-results`.
---
## 9. 현재 코드 vs To-Be 갭
`pages/fnrs-cnsltn` 기준.
| 현재 | v3.0 |
| ------------------------------------------------------ | --------------------------------------------------------------------- |
| 기술검토 의뢰 → `updatePrgrsStts("ER")` | `request-examine` → **`FV`** |
| `ROLE_USER01` 결재 상태 `ER`, `TE`만 | `RK` 2차 결재선 추가. `ER`에 파일보완 |
| `TA` [고시] → `TB` | `TA`는 종료 조회. `TB` 없음 |
| `EP` [인가승인] → `TA` | `EP` [결과회신] → `TA` (광역) |
| `TP` 환경청 [검토결과 회신] → `EP` | `TP` → **`RM`** (1차). 최종은 `RR→EP` |
| `LE` 결과회신 → `EP` (자체종결만 맞음) | 자체종결 유지. 일반 건은 의뢰 |
| 조치결과 `RB/RC/RA/AV` 버튼 없음 | 기초 `submit-actn-result`, 광역 `RC→RA`, 환경청 `request-actn-review` |
| `SL` 수정화면 미연결 | 파일/기술/금액 3버튼 |
| 초안생성을 `pwrk-cnsltn-mng.save`로 가정 (가이드 v1.0) | **검토자지정**이 1차 트리거 |
| `pwrk-cnsltn-*` 엔티티 없음 | 신규 슬라이스 |
| `dcrag-to` 구현됨 | 수동 재실행 3종에 연결 |
| `elapr-dmnd.save` 구현됨 | `docPrgrsSttsComCd: "AC"` 로 지정 |
---
## 10. 구현 순서
```
Task Progress:
- [ ] 1. FNRSSTTS 상수·표시명. button-config / floating-action-buttons 를 38행 매트릭스로 교체
- [ ] 2. fnrs-cnsltn 엔티티 확장: requestExamine, requestActnReview, findFileVrfcResults
(기존 saveApply, updatePrgrsStts, techReview, req/submit supplement 재사용)
- [ ] 3. entities/pwrk-cnsltn-mng (findPage, findById, findByUpDocNo, saveApply, submitActnResult)
- [ ] 4. entities/pwrk-cnsltn-doc-crt-rslt (download, webhwp open URL, saveMultipart)
- [ ] 5. AI 진행 패널: FV / AC / AV (draft status 폴링 + 상세 refetch, 30분 안내)
- [ ] 6. 환경청: 기술검토 의뢰(FV), 자체종결(EP), TP→RM, RA→AV, RR→EP
- [ ] 7. 공단 접수: 파일검증 결과 배지·파일보완 팝업, 검토자지정(AC)
- [ ] 8. 공단 검토 TE/RK: 웹기안기, 수동 초안/금액검증, 보완 팝업, 결재 process
- [ ] 9. 기초: SL 보완완료 3분기, RB 조치결과 submit-actn-result, TA 조회
- [ ] 10. 광역: ST→LE, RM→RB, RC→RA, EP→TA
- [ ] 11. (선택) 실무협의 목록·상세 라우트 /pwrk-cnsltn
```
1~4는 엔티티·버튼 뼈대. 화면 전이는 5부터 역할 레인 단위로 닫는다.
---
## 11. 공통코드 (화면 분기용)
| 상위코드 | 필드 | 값 | 명칭 |
| ----------------------- | ---------------- | ----------------------------------- | ------------------------------ |
| `ASDOCSE` | `upDocSeComCd` | `FNRS` / `PWRK` | 재원협의 / 실무협의 |
| `FNRSSTTS` | `prgrsSttsComCd` | 18종 | [4절](#4-상태코드-한눈에-보기) |
| `AICRTSTTS` / `CRTSTTS` | `crtSttsComCd` | `R` `I` `C` `F` | 생성요청/중/완료/실패 |
| (검증) | `vrfcSttsComCd` | `PASS` / `FAIL` / `WARN` | 파일별 검증 |
| 결재선 구분 | `atrzSeComCd` | `ATRZ01` `ATRZ02` `ATRZ03` `ATRZ99` | 접수/검토/승인/최종 |
표시명은 응답 `*Nm` / `*CdNm`. 분기는 코드값.
초안 직후 `crtSttsComCd = R`, 폴링 `COMPLETED` 이후 `C`. 문서 상태는 `AC → TE`.
+336
View File
@@ -0,0 +1,336 @@
# 레퍼런스 — 상태별 Endpoint
> 상위: [개발가이드.md](./개발가이드.md)
> SoT: `05.재원협의_상태별_endpoint_설계.md` (v1.0, 2026-09-03)
> Gateway: `/as-service`, `/as-elapr-service`
> Auth: `Authorization`, `X-Site-Id`, `token-id` (기존 `client` 인터셉터)
> 날짜: `yyyy-MM-dd HH:mm:ss`
프론트가 치는 API만 상세히 적는다. AI 콜백·스케줄러·Feign은 [§6 호출 금지](#6-프론트-호출-금지).
---
## 1. 총괄 매트릭스 (38 + WebHWP)
| 순번 | 버튼 | From → To | 서비스 | Method & URI | 구분 |
| :---: | --- | --- | --- | --- | --- |
| 1 | 저장 | →`SV` | as | `POST /api/v1/fnrs-cnsltn-mng/save-apply` | 기존 |
| 2 | 신청완료 | `SV→ST` | as | 동일 (`prgrsSttsComCd=ST`) | 기존 |
| 3 | 삭제 | `SV` | as | `DELETE /api/v1/fnrs-cnsltn-mng/{docNo}` | 기존 |
| 4 | 환경청제출 | `ST→LE` | as | `POST /api/v1/fnrs-cnsltn-mng/doc/updatePrgrsStts` | 기존 |
| 5 | 결과회신(자체종결) | `LE→EP` | as | `POST /api/v1/fnrs-cnsltn-mng/{docNo}/tech-review` | 기존 |
| 6 | 기술검토 의뢰 | `LE→FV` | as | `POST /api/v1/fnrs-cnsltn-mng/{docNo}/request-examine` | **신규** |
| 7 | 파일검증 콜백 | `FV→ER` | as | `POST /api/v1/dcrag/documents/verify-result` | 서버 |
| 8 | 파일보완 팝업 | `ER` | as | `GET /api/v1/fnrs-cnsltn-mng/{docNo}/file-vrfc-results` | 기존 |
| 9 | 파일보완요청 | `ER→SL` | as | `POST /api/v1/fnrs-cnsltn-mng/{docNo}/reqsupplement` | 기존 |
| 10 | 파일검증 재실행 | `ER` | as | `POST /api/v1/dcrag-to/documents/transfer` | 기존 |
| 11 | 검토자지정 | `ER→AC` | elapr | `POST /api/v1/elapr-dmnd` | 기존 |
| 12 | 초안 콜백 | `AC→TE` | as | `POST /api/v1/dcrag/draft/complete` | 서버 |
| 13 | 실무협의서관리 | `TE` | as | `GET /api/v1/pwrk-cnsltn-mng/updocno/{upDocNo}` | 기존 |
| 14 | 검토결과조회 | `TE` | as | `GET /api/v1/pwrk-cnsltn-mng/{pwrkDocNo}` | 기존 |
| 15 | 실무협의서생성 | `TE` | as | `POST /api/v1/dcrag-to/documents/draft/generate` | 기존 |
| 16 | 저장(실무) | `TE` | as | `POST /api/v1/pwrk-cnsltn-mng/save-apply` | 기존 |
| 17 | 검토완료 | `TE` | elapr | `PUT /api/v1/elapr-prgrs-stts/{id}/process` | 기존 |
| 18 | 보완요청 | `TE→SL` | as | `POST .../reqsupplement` | 기존 |
| 19 | 결재(승인) | `TE` | elapr | `PUT .../process` | 기존 |
| 20 | 결재(회신) | `TE→TP` | elapr | `PUT .../process` | 기존 |
| 21 | 파일보완완료 | `SL→ER` | as | `POST .../submitsupplement` | 기존 |
| 22 | 보완완료(기술) | `SL→TE` | as | 동일 | 기존 |
| 23 | 보완완료(조치) | `SL→RK` | as | 동일 | 기존 |
| 24 | 재원협의 결과 회신 | `TP→RM` | as | `POST .../{docNo}/tech-review` | 기존 |
| 25 | 지자체회신 | `RM→RB` | as | `POST .../doc/updatePrgrsStts` | 기존 |
| 26 | 저장(조치결과) | `RB` | as | `POST /api/v1/pwrk-cnsltn-mng/{pwrkDocNo}/submit-actn-result` | **신규** |
| 27 | 협의회신 | `RB→RC` | as | 동일 (`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` | 서버 |
| 31 | 실무협의서관리 | `RK` | as | `GET /pwrk-cnsltn-mng/{pwrkDocNo}` | 기존 |
| 32 | 금액검증 재실행 | `RK` | as | `POST /api/v1/dcrag-to/documents/cost/verify` | 기존 |
| 33 | 보완요청(금액) | `RK→SL` | as | `reqsupplement` | 기존 |
| 34 | 결재(승인) | `RK` | elapr | `process` | 기존 |
| 35 | 결재(회신) | `RK→RR` | elapr | `process` | 기존 |
| 36 | 결과회신 | `RR→EP` | as | `updatePrgrsStts` | 기존 |
| 37 | 결과회신 | `EP→TA` | as | `updatePrgrsStts` | 기존 |
| 38 | 결과회신(확인) | `TA` | — | 없음 | — |
| W1 | 웹기안기 Open | — | as | `GET /api/v1/pwrk-cnsltn-doc-crt-rslt/{uuid}/webhwp/open` | 기존 |
| W2 | HWPX 저장 | — | as | `POST /api/v1/pwrk-cnsltn-doc-crt-rslt` multipart | 기존 |
| W3 | 다운로드 | — | as | `GET /api/v1/pwrk-cnsltn-doc-crt-rslt/{uuid}/download` | 기존 |
| E-N | 결재선 복사 | `AV→RK` 부수 | elapr | `POST /api/v1/elapr-apln/copy-line` | **신규·서버** |
조회 공통: `GET /fnrs-cnsltn-mng`, `GET /fnrs-cnsltn-mng/{docNo}`, `GET /pwrk-cnsltn-mng`, `GET /dcrag-to/documents/draft/status/{crtDmndUuid}`.
---
## 2. 공통 요청
### `DocPrgrsUpdateRequestDto`
`POST /fnrs-cnsltn-mng/doc/updatePrgrsStts` — `ST→LE`, `RM→RB`, `RC→RA`, `RR→EP`, `EP→TA`.
```json
{
"docNo": "F-20260824-0001",
"docSeComCd": "FNRS",
"prgrsSttsComCd": "LE",
"rgtrUuid": "optional-uuid",
"rmrk": "optional"
}
```
응답: `204/200` void. 프론트 엔티티: 기존 `changePrgrsSttsComCd`.
### `FnrsCnsltnApplySaveRequestDto`
`save-apply`, `tech-review`, `reqsupplement`, `submitsupplement`가 공유. 핵심은 `fnrsCnsltnMng.prgrsSttsComCd`와 중첩 리스트(`bzScaleList`, `bizCstList`, `cklstResultList`, `esntlAtchFileList`, `splmntDmndList`).
---
## 3. 단계별 FE API
### 3-1. `SV → ST → LE`
**저장 / 신청완료** — `POST /fnrs-cnsltn-mng/save-apply`
권한: `ROLE_USER05`. 임시저장은 `prgrsSttsComCd=SV`, 신청은 `ST`.
응답: `string` (문서번호). 기존 `fnrsCnsltnMngService.saveApply`.
**삭제** — `DELETE /fnrs-cnsltn-mng/{docNo}` (`SV`만).
**환경청제출** — `updatePrgrsStts` `LE`. 권한: `ROLE_USER04`.
### 3-2. `LE → FV | EP`
**자체종결** — `POST /fnrs-cnsltn-mng/{docNo}/tech-review`
Body: `FnrsCnsltnApplySaveRequestDto` (환경청 의견·체크리스트). `LE→EP`.
기존 `techReview`. 현재 화면은 `TP`에서도 이 API를 쓰며 목표 상태를 `EP`로 넣고 있다 → **`TP`는 `RM`으로 바꿔야 함**.
**기술검토 의뢰 (신규)** — `POST /fnrs-cnsltn-mng/{docNo}/request-examine`
```json
{ "prgrsSttsComCd": "FV" }
```
응답: `string` (docNo). `LE→FV` + AI 파일검증 + 30분 타이머(서버).
프론트: `fnrsCnsltnMngService.requestExamine(docNo, body)` 추가. **`updatePrgrsStts("ER")` 금지.**
### 3-3. `FV → ER → SL → ER`
파일검증 콜백은 서버. FE는 `FV`에서 상세 refetch.
**검증결과 목록** — `GET /fnrs-cnsltn-mng/{fnrsCnsltnDocNo}/file-vrfc-results`
응답: `FnrsCnsltnFileVrfcRsltResponseDto[]`
| 필드 | 설명 |
| --- | --- |
| `fileSeComCd` | `ESSENTIAL` / `ADDITIONAL` |
| `fileSeNm` | 구분명 |
| `esntlAtchFileId` | 필수첨부 ID |
| `atchFileNm` | 파일명 |
| `vrfcRsltComCd` | `PASS` / `FAIL` / `WARN` |
| `vrfcOpnn` | 검증의견 |
엔티티에 `findFileVrfcResults` 추가. 첨부 행 배지는 상세 `esntlFileList`/`etcFileList`의 `vrfcSttsComCd`, `vrfcRsltCn`, `vrfcDt`도 사용 (타입 확장 필요).
**파일보완요청** — `POST .../{docNo}/reqsupplement` (`ER→SL`).
**파일보완완료** — `POST .../{docNo}/submitsupplement` (`SL→ER`). 복귀는 서버.
**파일검증 재실행** — `POST /dcrag-to/documents/transfer`
```json
{ "fnrsCnsltnDocNo": "F-20260824-0001", "taskSeComCd": "SF", "taskSeCdNm": "하수처리시설" }
```
응답: `{ resultCode, message, requestId }`. 상태 유지. 기존 `dcRagApiToService.requestDocumentTransfer`.
### 3-4. `ER → AC → TE → TP`
**검토자지정** — `POST /elapr-dmnd`
기존 `elaprDmndService.save`.
| 필드 | 값 |
| --- | --- |
| `elaprDocNo` | 재원협의 번호 |
| `elaprCycl` | `1` |
| `docPrgrsSttsComCd` | **`AC`** (현행은 현재 상태 `ER`를 넣고 있음 → 수정) |
| `elaprPrgrsSttsList[].atrzOrdr` | 1 검토자, 2 승인자, 3 최종 |
서버가 실무협의 문서 생성 + RAG 초안 트리거 + 30분 타이머. 응답의 문서번호/`crtDmndUuid`가 있으면 폴링 키로 쓴다. 없으면 `findByUpDocNo`로 복원.
**초안 상태 폴링** — `GET /dcrag-to/documents/draft/status/{crtDmndUuid}`
기존 `getDraftGenerationStatus`. 주기 5~10초. 종료 `COMPLETED`/`FAILED`.
**수동 초안생성** — `POST /dcrag-to/documents/draft/generate` (`TE` 유지)
```json
{
"fnrsCnsltnDocNo": "F-...",
"pwrkCnsltnDocNo": "P-...",
"crtDmndUuid": "optional"
}
```
202 + `DcRagDraftGenerateResponseDto` (`crtDmndUuid`, `crtSttsComCd`, `estimatedMinutes`).
v1.0 가이드의 “이 API를 프론트가 치면 안 된다”는 **폐기**. 1차 트리거는 검토자지정이고, 이 API는 Fallback 전용.
**실무 저장** — `POST /pwrk-cnsltn-mng/save-apply` → `string`.
**보완요청/완료** — fnrs `reqsupplement` / `submitsupplement`.
**결재** — `PUT /elapr-prgrs-stts/{elaprPrgrsId}/process`
```json
{
"atrzPrgrsSttsComCd": "ATRZ_PRGRS_003",
"atrzOpnnCn": "의견",
"finalApprovalYn": false
}
```
반려: `ATRZ_PRGRS_004`. 기존 `elaprPrgrsSttsService.process`. 최종 시 서버가 `TP`/`RR` 전이.
### 3-5. `TP → RM → RB → RC`
**환경청 1차 회신** — `tech-review` (`TP→RM`).
**광역 지자체회신** — `updatePrgrsStts` `RB`.
**조치결과 저장/제출 (신규)** — `POST /pwrk-cnsltn-mng/{pwrkDocNo}/submit-actn-result`
```json
{
"isSubmit": false,
"actnList": [
{
"rvwOpnnSn": 1,
"actnRsltCn": "수용 — 관로 연장 재산정",
"atchFileGroupId": "optional"
}
],
"totActnSummary": "제출 시 필수",
"muniDocNo": "제출 시 필수"
}
```
- `isSubmit: false` → `RB` 유지
- `isSubmit: true` → `RB→RC`
응답: `string` (pwrkDocNo). DTO명 `PwrkActnResultSubmitDto`.
### 3-6. `RC → RA → AV → RK`
**광역 제출** — `updatePrgrsStts` `RA`.
**2차 의뢰 (신규)** — `POST /fnrs-cnsltn-mng/{docNo}/request-actn-review`
```json
{
"envoDocNo": "환경청 공문번호",
"specialRqstCn": "중점 검증 요청 (선택)"
}
```
`RA→AV`. 응답: `string` (pwrkDocNo). DTO명 `EnvoActnReviewReqDto`.
금액검증 콜백은 서버. 콜백 후 서버가 `copy-line` 호출.
**금액검증 재실행** — `POST /dcrag-to/documents/cost/verify`
```json
{
"fnrsCnsltnDocNo": "F-...",
"pwrkCnsltnDocNo": "P-..."
}
```
202. 기존 `requestCostVerification`. `RK` 유지.
### 3-7. `RK → RR | SL`
보완은 3-4와 동일 API. 팝업만 금액 체크.
결재 `process` 2차 최종 → 서버가 `RR`.
### 3-8. `RR → EP → TA`
둘 다 `updatePrgrsStts`. `TA` 확인은 API 없음.
---
## 4. 웹기안기
설계서 기준 **기존**. 구현 시 컨트롤러 존재 여부를 확인한다.
| ID | 용도 | URI | 응답 |
| --- | --- | --- | --- |
| W1 | `HwpCtrl.Open` | `GET .../pwrk-cnsltn-doc-crt-rslt/{crtDmndUuid}/webhwp/open` | `inline` HWPX |
| W2 | 편집 저장 | `POST .../pwrk-cnsltn-doc-crt-rslt` multipart `file` + `crtDmndUuid` | 201, `verSn+1` |
| W3 | PC 저장 | `GET .../{crtDmndUuid}/download` | `attachment` blob |
버전 지정: `GET .../{crtDmndUuid}/{verSn}/webhwp/open`, `.../{verSn}/download`.
`/{uuid}/latest`, `/{uuid}/download`가 `/{uuid}/{verSn}`보다 우선. path를 섞지 말 것.
다운로드:
- axios `responseType: "blob"`
- 파일명: `Content-Disposition` `filename*=UTF-8''...` (`parseFilenameFromResponse`)
- 서버 파일명: `재원협의 실무협의서({bizNm})_v{verSn}.hwp`
- Content-Type은 `application/x-hwp` 또는 `application/haansofthwpx`일 수 있음. 타입에 의존하지 말 것
Open은 **절대 URL**을 기안기에 전달. blob Object URL 금지. 쿠키/게이트웨이 인증 가능 여부는 백엔드 확인.
multipart `Content-Type` 헤더를 수동 지정하지 않는다 (boundary). JSON `saveJson`은 UI에서 쓰지 않는다.
---
## 5. 전자결재 (`as-elapr-service`)
| API | URI | FE |
| --- | --- | --- |
| 검토자지정·1차 결재요청 | `POST /elapr-dmnd` | O |
| 결재 처리 | `PUT /elapr-prgrs-stts/{id}/process` | O |
| 결재선 복사 | `POST /elapr-apln/copy-line` | **X (서버)** |
`copy-line` body (참고만):
```json
{
"docNo": "P-20260827-0001",
"sourceElaprCycl": 1,
"targetElaprCycl": 2,
"rgtrUuid": "system-uuid"
}
```
`RK`에서 2차 결재선은 기존 `findDocPrgrsByDocNo` / `elaprCycl=2` 조회로 표시.
---
## 6. 프론트 호출 금지
| URI | 이유 |
| --- | --- |
| `POST /dcrag/documents/verify-result` | AI→AS 콜백 |
| `POST /dcrag/draft/complete` | AI→AS 콜백 |
| `POST /dcrag/cost/verify-result` | AI→AS 콜백. 여기서 copy-line도 호출 |
| `POST /elapr-apln/copy-line` | 콜백 Feign |
| `POST /pwrk-cnsltn-mng` (`save` 초안 트리거) | v3.0 1차 트리거는 `elapr-dmnd`. 컨트롤러가 남아 있어도 화면에서 쓰지 않음 |
`POST /dcrag-to/documents/draft/generate`는 **Fallback에서만** 허용.
---
## 7. Gap Analysis (프론트 엔티티)
### 이미 있는 것
`fnrsCnsltnMngService`: `findPage`, `findById`, `save`, `saveApply`, `update`, `techReview`, `submitSupplement`, `submitReqSupplement`, `remove`, `changePrgrsSttsComCd`
`dcRagApiToService`: `requestDocumentTransfer`, `requestDraftGeneration`, `getDraftGenerationStatus`, `requestCostVerification`
`elaprDmndService.save`, `elaprPrgrsSttsService.process`
### 추가할 것
| 메서드 | URI |
| --- | --- |
| `requestExamine` | `POST /fnrs-cnsltn-mng/{id}/request-examine` |
| `findFileVrfcResults` | `GET /fnrs-cnsltn-mng/{id}/file-vrfc-results` |
| `requestActnReview` | `POST /fnrs-cnsltn-mng/{id}/request-actn-review` |
| `pwrkCnsltnMngService.*` | 슬라이스 신규 |
| `submitActnResult` | `POST /pwrk-cnsltn-mng/{id}/submit-actn-result` |
| `pwrkCnsltnDocCrtRsltService.*` | 슬라이스 신규 |
`copyLine`은 프론트 서비스에 넣지 않는다.
@@ -0,0 +1,343 @@
# 레퍼런스 — 프론트 엔티티·DTO
> 상위: [개발가이드.md](./개발가이드.md)
> 엔티티 규칙: `frontend/.cursor/skills/backend-controller-to-entity`
---
## 1. ENDPOINT
`apps/as/src/shared/config/api.ts`
```ts
AS_SERVICE: {
// 기존
FNRS_CNSLTN_MNG: "/as-service/api/v1/fnrs-cnsltn-mng",
DCRAG_TO: "/as-service/api/v1/dcrag-to",
// 추가
PWRK_CNSLTN_MNG: "/as-service/api/v1/pwrk-cnsltn-mng",
PWRK_CNSLTN_DOC_CRT_RSLT: "/as-service/api/v1/pwrk-cnsltn-doc-crt-rslt",
}
AS_ELAPR_SERVICE: {
ELAPR_TMPLT: "/as-elapr-service/api/v1/elapr-tmplt",
ELAPR_APLN: "/as-elapr-service/api/v1/elapr-apln",
ELAPR_DMND: "/as-elapr-service/api/v1/elapr-dmnd",
ELAPR_PRGRS_STTS: "/as-elapr-service/api/v1/elapr-prgrs-stts",
}
```
`copy-line`은 `ELAPR_APLN` 하위이나 FE에서 호출하지 않음.
---
## 2. 슬라이스 배치
```
entities/fnrs-cnsltn/ # 확장
entities/dcrag-to/ # 구현됨
entities/elapr/ # 구현됨
entities/pwrk-cnsltn-mng/ # 신규
entities/pwrk-cnsltn-doc-crt-rslt/ # 신규
```
```
entities/pwrk-cnsltn-mng/
├── model/
│ ├── pwrk-cnsltn-mng-interface.ts
│ └── pwrk-cnsltn-mng-payload-schema.ts
├── api/
│ ├── pwrk-cnsltn-mng-service.ts
│ └── queries.ts
└── index.ts
```
`submit-actn-result` Request는 zod SSOT (`PwrkActnResultSubmitDto`).
`request-examine` / `request-actn-review`도 fnrs payload-schema에 추가.
라우트:
| 화면 | 경로 |
| --- | --- |
| 재원협의 목록/상세/작성 | 기존 `app/(main)/(with-base-layout)/fnrs-cnsltn/...` |
| 실무협의 목록 | `.../pwrk-cnsltn/page.tsx` (선택) |
| 실무협의 상세 | `.../pwrk-cnsltn/[id]/page.tsx` (선택) |
| 페이지 UI | `pages/fnrs-cnsltn/`, `pages/pwrk-cnsltn/` |
---
## 3. Service 시그니처
`fnrsCnsltnMngService`에 추가:
```ts
requestExamine(docNo: string, body: FnrsExamineRequestDto): Promise<string>;
findFileVrfcResults(fnrsCnsltnDocNo: string): Promise<FnrsCnsltnFileVrfcRsltResponseDto[]>;
requestActnReview(docNo: string, body: EnvoActnReviewReqDto): Promise<string>;
```
실무협의:
```ts
export interface PwrkCnsltnMngService {
findPage(searchDto: PwrkCnsltnMngSearchDto): Promise<PageResponse<PwrkCnsltnMngResponseDto>>;
findById(pwrkCnsltnDocNo: string): Promise<PwrkCnsltnMngDetResponseDto>;
findByUpDocNo(upDocNo: string): Promise<PwrkCnsltnMngDetResponseDto>;
saveApply(body: PwrkCnsltnApplySaveRequestDto): Promise<string>;
submitActnResult(
pwrkCnsltnDocNo: string,
body: PwrkActnResultSubmitDto,
): Promise<string>;
update(pwrkCnsltnDocNo: string, body: PwrkCnsltnMngUpdateRequestDto): Promise<string>;
remove(pwrkCnsltnDocNo: string): Promise<void>;
changePrgrsSttsComCd(body: DocPrgrsUpdateRequestDto): Promise<void>;
}
```
v1.0의 `save(): PwrkCnsltnMngSaveResponseDto`(초안 트리거)는 화면에서 사용하지 않는다. 컨트롤러가 남아 있으면 엔티티에 넣더라도 UI 연결 금지.
생성결과:
```ts
export interface PwrkCnsltnDocCrtRsltService {
findPage(
searchDto: PwrkCnsltnDocCrtRsltSearchDto,
): Promise<PageResponse<PwrkCnsltnDocCrtRsltResponseDto>>;
findById(crtDmndUuid: string, verSn: number): Promise<PwrkCnsltnDocCrtRsltResponseDto>;
findLatestByCrtDmndUuid(crtDmndUuid: string): Promise<PwrkCnsltnDocCrtRsltResponseDto>;
download(crtDmndUuid: string, verSn: number): Promise<AxiosResponse<Blob>>;
downloadLatest(crtDmndUuid: string): Promise<AxiosResponse<Blob>>;
saveMultipart(formData: FormData): Promise<PwrkCnsltnDocCrtRsltResponseDto>;
updateMultipart(
crtDmndUuid: string,
verSn: number,
formData: FormData,
): Promise<PwrkCnsltnDocCrtRsltResponseDto>;
remove(crtDmndUuid: string, verSn: number): Promise<void>;
}
```
`webhwp/open`은 fetch하지 않고 URL 빌더만:
```ts
function webhwpOpenUrl(crtDmndUuid: string, verSn?: number): string {
const base = `${ENDPOINT.AS_SERVICE.PWRK_CNSLTN_DOC_CRT_RSLT}/${crtDmndUuid}`;
return verSn == null ? `${base}/webhwp/open` : `${base}/${verSn}/webhwp/open`;
}
```
DC RAG TO (구현됨, 시그니처 유지):
```ts
requestDocumentTransfer(body: DcRagDocumentTransferRequestDto): Promise<DcRagCommonResponseDto>;
requestDraftGeneration(body: DcRagDraftGenerateRequestDto): Promise<DcRagDraftGenerateResponseDto>;
getDraftGenerationStatus(crtDmndUuid: string): Promise<DcRagDraftStatusResponseDto>;
requestCostVerification(body: DcRagCostVerifyRequestDto): Promise<DcRagCommonResponseDto>;
```
---
## 4. DTO
타입명 = 백엔드 클래스명. Save/Apply는 zod.
### 4-1. 재원협의 추가
**`FnrsExamineRequestDto`**
| 필드 | 필수 | 값 |
| --- | --- | --- |
| `prgrsSttsComCd` | N | 기본 `FV` |
**`EnvoActnReviewReqDto`**
| 필드 | 필수 |
| --- | --- |
| `envoDocNo` | Y |
| `specialRqstCn` | N |
**`FnrsCnsltnFileVrfcRsltResponseDto`**: [ref-endpoint.md](./ref-endpoint.md) §3-3.
첨부 검증 배지용으로 `AtflMngClientResponseDto` / `EsntlAtchFileMngTreeResponseDto`에 `vrfcDt`, `vrfcRsltCn`, `vrfcSttsComCd` 확장.
### 4-2. `PwrkActnResultSubmitDto` (zod)
| 필드 | 필수 | 비고 |
| --- | --- | --- |
| `isSubmit` | Y | `false` 저장, `true` 협의회신 |
| `actnList` | Y | |
| `actnList[].rvwOpnnSn` | Y | |
| `actnList[].actnRsltCn` | Y | |
| `actnList[].atchFileGroupId` | N | |
| `totActnSummary` | 제출 시 | |
| `muniDocNo` | 제출 시 | |
### 4-3. `PwrkCnsltnMng*`
**Search**: `pwrkCnsltnDocNo`, `upDocNo`, `upDocSeComCd`, `prgrsSttsComCd`, `admdstCd`, `envoSeComCd`, `taskSeComCd`, `bizNm`
**DetResponse 중첩**
- `cnsltnList`: 협의내역 (`rvwOpnnSn`, `rvwOpnnClsfComCd`, `cnsltnCn`, `rfltYn`, …)
- `pjtcoAjmtList`: 사업비조정
- `crtDmndList[].crtRslt`: 생성요청 + 최신 결과
- `splmntDmndList`: 보완요청
- `sttsChgHstryList`: 상태변경이력 — `SL` 복귀 라벨에 사용
- `trsmFileList`: 전송파일 (Java Det DTO 확인 후 반영)
마스터 조인: `bizNm`, `admdstCd`, `envoSeComCd`, `taskSeComCd`, `aplcntNm` 등.
`crtRslt.orgnlOtptDocCn`은 항상 `null`.
**상세 예**
```json
{
"pwrkCnsltnDocNo": "P-20260827-0001",
"upDocNo": "F-20260824-0001",
"upDocSeComCd": "FNRS",
"prgrsSttsComCd": "TE",
"cnsltnList": [],
"pjtcoAjmtList": [],
"crtDmndList": [
{
"crtDmndUuid": "d0545bef-735f-4d7b-a781-88e96e03577a",
"pwrkCnsltnDocNo": "P-20260827-0001",
"rqstrUuid": "...",
"rqstrNm": "관리자",
"crtSttsComCd": "C",
"crtRslt": {
"crtDmndUuid": "d0545bef-735f-4d7b-a781-88e96e03577a",
"verSn": 1,
"rgtrNm": "관리자",
"orgnlOtptDocCn": null,
"crtCmptnDt": "2026-08-27 10:47:01"
}
}
],
"splmntDmndList": [],
"sttsChgHstryList": []
}
```
### 4-4. `PwrkCnsltnDocCrtRslt*`
복합 PK: `crtDmndUuid` + `verSn`
**Response**
| 필드 | 타입 | 비고 |
| --- | --- | --- |
| `crtDmndUuid` | `string` | |
| `verSn` | `number` | |
| `rgtrUuid` / `rgtrNm` | `string` | |
| `orgnlOtptDocCn` | `null` | 목록/상세 미포함 |
| `crtCmptnDt` | `string` | |
**Search** (`PageableParams`): `crtDmndUuid`(이력 조회 시 필수), `pwrkCnsltnDocNo`, `verSn`, `rgtrUuid`
```http
GET /as-service/api/v1/pwrk-cnsltn-doc-crt-rslt?page=0&size=10&crtDmndUuid={uuid}&sort=verSn,desc
```
**Save (zod)**: `crtDmndUuid` 필수. `rgtrUuid` 생략 시 서버 로그인 사용자. `file`은 multipart part (DTO 필드 아님).
### 4-5. DC RAG (구현됨)
```ts
interface DcRagDocumentTransferRequestDto {
fnrsCnsltnDocNo: string;
taskSeComCd?: string;
taskSeCdNm?: string;
}
interface DcRagDraftStatusResponseDto {
crtDmndUuid: string;
status: DraftStatus;
statusName?: string;
progressPercent?: number;
events?: EventInfoDto[];
startedDt?: string;
estimatedCompletionDt?: string;
errorMessage?: string | null;
}
type DraftStatus =
| "REQUESTED"
| "DOWNLOADING"
| "ANALYZING"
| "GENERATING"
| "COMPLETED"
| "FAILED";
```
`estimatedMinutes`는 초안 응답에 있을 수 있으나 UX 안내는 **30분 Fallback**을 따른다.
---
## 5. 폴링 훅
초안 (`AC` 또는 `TE`이면서 생성 중):
```ts
useQuery({
queryKey: [...dcragToKeys.draftStatus(crtDmndUuid)],
queryFn: () => dcRagApiToService.getDraftGenerationStatus(crtDmndUuid),
enabled: Boolean(crtDmndUuid) && isPolling,
refetchInterval: (query) => {
const status = query.state.data?.status;
if (status === "COMPLETED" || status === "FAILED") return false;
return 5_000;
},
});
```
`COMPLETED` 시 `fnrs-cnsltn` 상세·`pwrk-cnsltn-mng` `findByUpDocNo` invalidate.
`FV`/`AV`는 `findById`를 5~10초 refetch. `prgrsSttsComCd`가 `ER`/`RK`가 되면 중단. 시작 시각 기준 30분 경과 안내 문구.
`crtDmndUuid` 복원: `findByUpDocNo(fnrsCnsltnDocNo).crtDmndList[0]`.
---
## 6. 다운로드
```ts
const response = await client.get<Blob>(url, { responseType: "blob" });
const filename = parseFilenameFromResponse(response);
const objectUrl = window.URL.createObjectURL(response.data);
```
기존 `attachService`와 동일.
---
## 7. 실무협의 목록 컬럼 (선택 화면)
| 표시명 | Field |
| --- | --- |
| 실무협의번호 | `pwrkCnsltnDocNo` |
| 재원협의번호 | `upDocNo` |
| 진행상태 | `prgrsSttsComCdNm` |
| 사업명 | `bizNm` |
| 시도/구군 | `sido` / `gugun` |
| 신청자 | `aplcntNm` |
| 등록일 | `regDt` |
행 클릭 → `/pwrk-cnsltn/{pwrkCnsltnDocNo}` 또는 재원협의 상세로 이동 (화면설계 확정 후).
---
## 8. `elapr-dmnd` 저장 시 주의
현행 `floating-action-buttons.tsx`:
```ts
elaprCycl: data.detDto?.prgrsSttsComCd === "RK" ? 2 : 1,
docPrgrsSttsComCd: data.detDto?.prgrsSttsComCd, // ER
```
To-Be:
- 검토자지정은 **`ER`에서만**. `elaprCycl: 1`, `docPrgrsSttsComCd: "AC"`
- 2차 결재선은 서버 `copy-line`. `RK`에서 검토자지정 팝업을 다시 열지 않음
@@ -0,0 +1,246 @@
# 레퍼런스 — 프로세스·상태 전이
> 상위: [개발가이드.md](./개발가이드.md)
> SoT: `03.재원협의_실무협의_프로세스_통합_v완결.md` (v3.0, 2026-09-03)
---
## 1. 프로세스 개요
| 항목 | 내용 |
| --- | --- |
| Process No. | PR-AS-003 |
| 메뉴 | 행정지원 > 2.2.2 재원협의 > 2.2.2.1 재원협의 관리 |
| 목적 | 기초 → 광역(경유) → 환경청(승인) → 공단(기술검토/AI) 4자 협의로 사업규모·국비·분담금을 확정 |
2-Pass:
- **Pass 1** 기술 타당성: `SV→ST→LE→FV→ER→AC→TE→TP→RM→RB`
- **Pass 2** 조치결과·금액: `RB→RC→RA→AV→RK→RR→EP→TA`
자체종결(기술검토 생략): `LE→EP→TA`
---
## 2. 4-Lane 시퀀스
```mermaid
sequenceDiagram
autonumber
actor 기초 as 기초 (ROLE_USER05)
actor 광역 as 광역 (ROLE_USER04)
actor 환경청 as 환경청 (ROLE_USER03)
actor 공단 as 공단 (ROLE_USER01)
actor AI as AI 엔진
rect rgb(240, 248, 255)
기초->>기초: [저장] SV
기초->>광역: [신청완료] ST
광역->>환경청: [환경청제출] LE
end
rect rgb(255, 250, 240)
alt 자체종결
환경청->>광역: [결과회신] LE → EP
광역->>기초: [결과회신] EP → TA
else 기술검토 의뢰
환경청->>AI: [기술검토 의뢰] LE → FV
end
end
rect rgb(245, 255, 245)
AI-->>공단: 파일검증 콜백 FV → ER
Note over 공단: 30분 미응답 시 ER + 수동 [파일검증]
alt 파일 미비
공단->>기초: [파일보완] ER → SL
기초->>공단: [파일보완완료] SL → ER
else 적격
공단->>AI: [검토자지정] ER → AC
end
end
rect rgb(255, 245, 245)
AI-->>공단: 초안 콜백 AC → TE
Note over 공단: 30분 미응답 시 TE + 수동 [실무협의서생성]
alt 설계 미비
공단->>기초: [보완요청] TE → SL
기초->>공단: [보완완료] SL → TE
else 검토 완료
공단->>공단: [검토완료] 상신, [결재(승인)] TE 유지
공단->>환경청: [결재(회신)] TE → TP
end
end
rect rgb(250, 240, 255)
환경청->>광역: [재원협의 결과 회신] TP → RM
광역->>기초: [지자체회신] RM → RB
기초->>광역: [협의회신] RB → RC
end
rect rgb(240, 255, 255)
광역->>환경청: [조치결과서제출] RC → RA
환경청->>AI: [조치결과서 검토요청] RA → AV
AI-->>공단: 금액검증 콜백 AV → RK
Note over 공단: 30분 미응답 시 RK + 수동 [금액검증]
Note over 공단: AV→RK 시 1차 결재선 → 2차 복사 (서버)
end
rect rgb(255, 255, 230)
alt 소명 미비
공단->>기초: [보완요청] RK → SL
기초->>공단: [보완완료] SL → RK
else 확정
공단->>환경청: [결재(회신)] RK → RR
end
end
rect rgb(255, 240, 240)
환경청->>광역: [결과회신] RR → EP
광역->>기초: [결과회신] EP → TA
end
```
거버넌스:
```
기초 (USER05) --ST--> 광역 (USER04) --LE--> 환경청 (USER03)
기초 <-----TA------ 광역 <-----EP/RM------ 환경청
환경청 --FV/ER--> 공단 (USER01) --TP/RR--> 환경청
```
---
## 3. Activity 정의 (14)
| No. | Activity | 주체 | From → To | FE 포인트 |
| --- | --- | --- | --- | --- |
| 1 | 재원협의 조회 | 기초 | — | 기본계획·자가점검 대조 |
| 2 | 등록 및 제출 | 기초·광역 | `SV→ST`, 광역 `LE` | `save-apply`, `updatePrgrsStts` |
| 3 | 접수 및 의뢰 | 환경청 | `LE→FV` / `EP` | 의뢰 vs 자체종결 분기 |
| 4 | AI 파일검증 | 공단·AI | `FV→ER` / `SL` | 30분 Fallback, 파일보완 팝업 |
| 5 | 자체종결 회신 | 환경청·광역 | `LE→EP→TA` | 공단 레인 없음 |
| 6 | 검토자지정·초안 | 공단·AI | `ER→AC→TE` | `elapr-dmnd`, 초안 폴링 |
| 7 | 1차 기술검토 | 공단 결재선 | `TE` / `SL` | 웹기안기, HWP 파싱은 서버 |
| 8 | 기술검토 회신 | 공단 최종 | `TE→TP` | `process` 최종 |
| 9 | 검토결과 회신 | 환경청·광역 | `TP→RM→RB` | 1차 회신. `EP`와 혼동 금지 |
| 10 | 조치결과 작성 | 기초 | `RB→RC` | `submit-actn-result` |
| 11 | 조치 제출·의뢰 | 광역·환경청 | `RC→RA→AV` | `request-actn-review` |
| 12 | AI 금액검증 | AI·공단 | `AV→RK` | 결재선 복사(서버) |
| 13 | 2차 검토·결재 | 공단 | `RK→RR` / `SL` | 2차 결재선 |
| 14 | 최종 회신 | 환경청·광역·기초 | `RR→EP→TA` | 종결 |
---
## 4. `FNRSSTTS` 상세
관리: `tb_cs_lwpo_cd` / `tb_as_fnrs_cnsltn_mng.prgrs_stts_com_cd`
| 코드 | 국문 | 영문 | 화면 주체 | 전이 |
| --- | --- | --- | --- | --- |
| `SV` | 저장 | Save | 기초 작성 | 신규/`SV` → `SV` |
| `ST` | 신청완료 | Submit to Province | 기초→광역 | `SV→ST` |
| `LE` | 환경청 검토 | Local Environment Review | 광역→환경청 | `ST→LE` |
| `FV` | 파일검증 | File Verification | AI | `LE→FV` |
| `ER` | 기술검토 의뢰 | Examine Request | 공단 접수자 | `FV→ER`, `SL→ER` |
| `SL` | 보완요청 | Supplement Loop | 공단→기초 | `ER\|TE\|RK → SL` |
| `AC` | 초안생성 | AI Auto Creation | AI | `ER→AC` |
| `TE` | 기술검토 | Technical Examine | 공단 1차 결재 | `AC→TE`, `SL→TE` |
| `TP` | 기술검토 회신 | Technical Pass to Basin | 공단→환경청 | `TE→TP` |
| `RM` | 재원협의결과회신 | Result Message to Province | 환경청→광역 | `TP→RM` |
| `RB` | 재원협의결과회신(지자체) | Result Back to Municipality | 광역→기초 | `RM→RB` |
| `RC` | 조치결과서 제출 | Result Complete | 기초→광역 | `RB→RC` |
| `RA` | 조치결과서 제출(환경청) | Result Ask to Basin | 광역→환경청 | `RC→RA` |
| `AV` | 금액검증 | Amount Verification | AI | `RA→AV` |
| `RK` | 조치결과서검토 | Result Check | 공단 2차 결재 | `AV→RK`, `SL→RK` |
| `RR` | 조치결과서검토 회신 | Result Return to Basin | 공단→환경청 | `RK→RR` |
| `EP` | 검토결과 회신 | Examine Pass to Province | 환경청→광역 | `LE→EP`(자체), `RR→EP`(최종) |
| `TA` | 검토결과 회신(종료) | Technical Approval | 광역→기초 | `EP→TA` |
### 4-1. v2.1 → v3.0 대조 (프론트 폐기 대상)
| 구간 | v2.1 | v3.0 |
| --- | --- | --- |
| 의뢰 직후 파일검증 | `ER` | **`FV` → `ER`** |
| 검토자지정 후 초안 | `EE` → `DV` | **`ER` → `AC` → `TE`** |
| 1차 내부 결재 | `TR` → `TP` | **`TE` 유지 → `TP`** |
| 환경청→광역 1차 회신 | `EP` | **`RM`** |
| 금액검증 | `RA` | **`RA` → `AV` → `RK`** |
| 공단 2차 회신 | `TP` | **`RR`** |
| 최종 완료 | `TP` / 고시 `TB` | **`TA`** (고시 없음) |
가이드 v1.0 `PWRKSTTS`(`DV`, `AR`, `TR`, `AA` 등)도 동일하게 폐기.
---
## 5. 보완(`SL`) 3루프
복귀 상태는 `tb_as_doc_stts_chg_hstry` 직전 코드. 프론트는 복귀 코드를 요청에 넣지 않고, 라벨만 이력으로 고른다.
```mermaid
stateDiagram-v2
ER --> SL: 파일보완 팝업(파일리스트)
SL --> ER: 파일보완완료
TE --> SL: 보완 검토팝업()
SL --> TE: 보완완료
RK --> SL: 보완 검토팝업(금액)
SL --> RK: 보완완료
```
| 루프 | 팝업 | 요청 | 제출 버튼 |
| --- | --- | --- | --- |
| 파일 무결성 | 파일리스트 + 검증결과 | 공단 접수자 `ER` | [파일보완완료] |
| 1차 기술 | 설계 미달·사유 | 공단 검토자 `TE` | [보완완료] |
| 2차 금액 | 공종별 금액 체크 | 공단 검토자 `RK` | [보완완료] |
---
## 6. AI 3파이프라인·30분 Fallback
| ID | 단계 | 진입 → 완료 | 프론트 진입 | Fallback | 수동 재실행 |
| --- | --- | --- | --- | --- | --- |
| 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` |
- 예상 소요 안내는 **30분** (v1.0의 10분은 폐기).
- 콜백 수신·스케줄러 전이는 서버. FE는 상세 refetch / draft status 폴링만.
- `AC→TE` 성공 시에만 [실무협의서관리]·[검토결과조회] 활성. 실패·타임아웃이면 [실무협의서생성].
초안 폴링 이벤트 예:
| `eventType` | `eventMessage` |
| --- | --- |
| `REQUESTED` | 요청을 접수했습니다. |
| `DOWNLOADING` | 신청서를 수신하는 중입니다. |
| `ANALYZING` | 자료 준비가 완료되었습니다. |
| `GENERATING` | 문서 저장이 완료되었습니다. |
| `COMPLETED` | 문서 생성이 완료되었습니다. |
| `FAILED` | `errorMessage` 표시 |
종료: `status === "COMPLETED" | "FAILED"` 또는 `events`에 해당 `eventType`.
---
## 7. 결재선 복사
규칙: `"금액검증후: 2차결재선 1차결재선 복사생성"`
1. `TE`에서 1차 결재선(`elaprCycl = 1`) 완료.
2. `AV→RK` 콜백 시 서버가 `POST /elapr-apln/copy-line` (`source=1`, `target=2`).
3. `RK` 화면은 2차 결재선을 조회해 검토자/승인자/최종승인자를 표시.
4. 프론트는 copy-line을 **호출하지 않는다.**
결재 `process`:
- 상신·중간 승인: 문서 상태 `TE` 또는 `RK` 유지.
- 1차 최종: 서버가 `TE→TP`.
- 2차 최종: 서버가 `RK→RR`.
- 반려: 직전 결재자 복귀. 문서 상태 유지.
---
## 8. 웹기안기
- `TE`/`RK` [실무협의서관리] → 팝업에서 `HwpCtrl.Open(webhwp/open URL)`.
- [검토완료] 시 HWP 파싱·DB 등재는 **서버**. FE는 결재 `process`만.
- 편집본 저장: `POST /pwrk-cnsltn-doc-crt-rslt` multipart. `verSn`은 클라이언트가 보내지 않음.
@@ -0,0 +1,173 @@
# 레퍼런스 — 화면·버튼 매트릭스
> 상위: [개발가이드.md](./개발가이드.md)
> SoT: `03` §4 화면 및 버튼별 상세 상태전이 명세서 (Row 4~42, 38행)
구현 위치: `pages/fnrs-cnsltn/lib/button-config.ts` + `ui/components/floating-action-buttons.tsx`
---
## 1. 화면 구분
| 화면 | 소유 | 라우트 (현행) | 비고 |
| --- | --- | --- | --- |
| 작성화면 | 기초 `ROLE_USER05` | `/fnrs-cnsltn/new`, `/fnrs-cnsltn/{id}/edit` | `SV`, `RB` |
| 수정화면 | 기초 | `/fnrs-cnsltn/{id}/edit` | `SL` 보완 |
| 상세화면 | 광역·환경청·공단·기초(TA) | `/fnrs-cnsltn/{id}` | 조회 + 액션 버튼 |
| 자동화 | AI | 버튼 없음 | `FV`/`AC`/`AV` 진행 패널 |
공단 상세는 결재선 구분으로 같은 상태라도 버튼이 다르다.
| `atrzSeComCd` | 역할 |
| --- | --- |
| `ATRZ01` | 접수자 |
| `ATRZ02` | 검토자 |
| `ATRZ03` | 승인자 |
| `ATRZ99` | 최종승인자 |
---
## 2. 38행 매트릭스
API 컬럼은 [ref-endpoint.md](./ref-endpoint.md)와 동일.
| 순번 | 화면 | 권한 | 결재선 | 상태 | 버튼 | 전이 | 다음 담당 | FE 동작 |
| :---: | --- | --- | --- | --- | --- | --- | --- | --- |
| 1 | 작성 | USER05 | - | 빈값, `SV` | 저장 | `SV` | 본인 | `save-apply` `SV` |
| 2 | 작성 | USER05 | - | `SV` | 신청완료 | `ST` | 광역 | `save-apply` `ST` |
| 3 | 작성 | USER05 | - | `SV` | 삭제 | 삭제 | — | `DELETE` |
| 4 | 상세 | USER04 | - | `ST` | 환경청제출 | `LE` | 환경청 | `updatePrgrsStts` |
| 5 | 상세 | USER03 | - | `LE` | 결과회신 | `EP` | 광역 | `tech-review` 자체종결 |
| 6 | 상세 | USER03 | - | `LE` | 기술검토 의뢰 | `FV` | 공단 접수 | **`request-examine`** |
| 7 | 자동화 | AI | - | `FV` | (파일검증) | `ER` | 공단 접수 | FE 호출 없음. 진행 패널 |
| 8 | 상세 | USER01 | 접수자 | `ER` | 파일보완 | `SL` | 기초 | 팝업(파일리스트) → `reqsupplement` |
| 9 | 상세 | USER01 | 접수자 | `ER` | 파일검증 | `ER` | 본인 | `dcrag-to/transfer` 재실행 |
| 10 | 상세 | USER01 | 접수자 | `ER` | 검토자지정 | `AC` | 다음 결재자 | `POST /elapr-dmnd` |
| 11 | 자동화 | AI | - | `AC` | (초안생성) | `TE` | 공단 검토 | FE 호출 없음. 폴링 |
| 12 | 상세 | USER01 | 검토자 | `TE` | 실무협의서관리 | 유지 | — | 웹기안기 Open. **초안 성공 시만** |
| 13 | 상세 | USER01 | 검토자 | `TE` | 검토결과조회 | 유지 | — | 실무협의 상세 |
| 14 | 상세 | USER01 | 검토자 | `TE` | 실무협의서생성 | 유지 | — | **실패/타임아웃/초안 없음** 시 |
| 15 | 상세 | USER01 | 검토자 | `TE` | 저장 | `TE` | — | `pwrk save-apply` |
| 16 | 상세 | USER01 | 검토자 | `TE` | 검토완료 | `TE` | 다음 결재자 | `elapr process` 상신. 반려 시 직전 결재자 |
| 17 | 상세 | USER01 | 검토자 | `TE` | 보완요청 | `SL` | 기초 | 보완 팝업() → `reqsupplement` |
| 18 | 상세 | USER01 | 승인자 | `TE` | 결재(승인) | `TE` | 다음 결재자 | `process` |
| 19 | 상세 | USER01 | 최종승인자 | `TE` | 결재(회신) | `TP` | 환경청 | `process` 최종 |
| 20 | 수정 | USER05 | - | `SL` | 파일보완완료 | `ER` | 공단 접수 | `submitsupplement` (직전=`ER`) |
| 21 | 수정 | USER05 | - | `SL` | 보완완료 | `TE` | 공단 검토 | `submitsupplement` (직전=`TE`) |
| 22 | 수정 | USER05 | - | `SL` | 보완완료 | `RK` | 공단 검토 | `submitsupplement` (직전=`RK`) |
| 23 | 상세 | USER03 | - | `TP` | 재원협의 결과 회신 | `RM` | 광역 | `tech-review` |
| 24 | 상세 | USER04 | - | `RM` | 지자체회신 | `RB` | 기초 | `updatePrgrsStts` |
| 25 | 작성 | USER05 | - | `RB` | 저장 | `RB` | 본인 | **`submit-actn-result` `isSubmit:false`** |
| 26 | 작성 | USER05 | - | `RB` | 협의회신 | `RC` | 광역 | **`submit-actn-result` `isSubmit:true`** |
| 27 | 상세 | USER04 | - | `RC` | 조치결과서제출 | `RA` | 환경청 | `updatePrgrsStts` |
| 28 | 상세 | USER03 | - | `RA` | 조치결과서 검토요청 | `AV` | 공단 검토 | **`request-actn-review`** |
| 29 | 자동화 | AI | - | `AV` | (금액검증) | `RK` | 공단 검토 | FE 호출 없음. 진행 패널 |
| 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 없음. 조회** |
`SL` 20~22행: 버튼은 직전 상태에 따라 **하나만** 노출. 파일 루프면 [파일보완완료], 그 외 [보완완료].
---
## 3. `button-config` 권장 구조
액션 키는 현행 `FNRS_CNSLTN_VIEW_BUTTON_ACTION`을 확장한다. 신규·이름 변경:
| 신규/변경 액션 | 라벨 | 비고 |
| --- | --- | --- |
| `fileSplmnt` | 파일보완 | `ER` 접수자. 현행에 없음 |
| `fileVrfc` | 파일검증 | 유지. `ER` 재실행 |
| `dsgntRvwr` | 검토자지정 | 유지. 전이를 `AC`로 |
| `pwrkCnsltnDocMng` | 실무협의서관리 | 유지. `TE`·`RK` |
| `pwrkCnsltnDocCrt` | 실무협의서생성 | 유지. 실패/타임아웃만 |
| `rvwRsltInq` | 검토결과조회 | 신규 |
| `actnStrg` | 저장(조치결과) | `RB` |
| `cnsltnRply` | 협의회신 | `RB→RC` |
| `actnSbmsn` | 조치결과서제출 | 광역 `RC→RA` |
| `rqstActnRvw` | 조치결과서 검토요청 | 환경청 `RA→AV` |
| `costVrfc` | 금액검증 | `RK` |
| `lclRply` | 지자체회신 | 광역 `RM→RB` |
| `fnrsRsltRply` | 재원협의 결과 회신 | 환경청 `TP→RM` |
| `rsltRply` | 결과회신 | `LE→EP`, `RR→EP`, `EP→TA`, `TA` 조회 |
| `fileSplmntCmptn` | 파일보완완료 | `SL←ER` |
| `splmntCmptn` | 보완완료 | `SL←TE\|RK` |
**삭제**: `fnrsCnsltnAncmnt`(고시→`TB`), `sncnAprv`(인가승인). `EP`/`TA`는 `rsltRply`로 통일.
### 3-1. 역할 × 상태 (공단 제외)
```
ROLE_USER05
"" | SV → 저장, 신청완료, 삭제
SL → (직전 ER) 파일보완완료 | (직전 TE|RK) 보완완료
RB → 저장(조치), 협의회신
TA → 결과회신 (조회)
ROLE_USER04
ST → 환경청제출 // 현행 반려(rjct→SV)는 SoT 38행에 없음. 유지 시 별도 합의
RM → 지자체회신
RC → 조치결과서제출
EP → 결과회신
ROLE_USER03
LE → 결과회신(자체종결), 기술검토 의뢰
TP → 재원협의 결과 회신
RA → 조치결과서 검토요청
RR → 결과회신
```
### 3-2. 공단 결재선 × 상태
```
ER / ATRZ01 접수자
→ 파일보완, 파일검증, 검토자지정
TE / ATRZ02 검토자
→ (초안 성공) 실무협의서관리, 검토결과조회, 저장, 검토완료, 보완요청
→ (초안 실패·타임아웃) 실무협의서생성 (+ 위 항목 중 관리/조회는 초안 생긴 뒤)
TE / ATRZ03 승인자 → 결재(승인)
TE / ATRZ99 최종 → 결재(회신)
RK / ATRZ02 검토자
→ 실무협의서관리, 금액검증, 저장, 검토완료, 보완요청
RK / ATRZ03 → 결재(승인)
RK / ATRZ99 → 결재(회신)
```
`FV`, `AC`, `AV`: 액션 빈 배열 + 진행 패널.
현행 `TE`에서 `hasPendingApprovalRequest === false`이면 버튼을 숨긴다. v3.0에서는 검토자 버튼(저장·웹기안기·보완)은 결재 대기와 무관하게 두고, **검토완료/결재**만 결재건 존재 여부에 묶는다.
---
## 4. 초안 성공 vs 실패 버튼
`TE`에서 `crtDmndList[].crtSttsComCd` / 폴링 `status`로 분기.
| 조건 | 노출 |
| --- | --- |
| `C` / `COMPLETED` | 실무협의서관리, 검토결과조회, 저장, 검토완료, 보완요청 |
| `F` / `FAILED` 또는 30분 Fallback 후 초안 없음 | 실무협의서생성 |
| `R`/`I` 생성 중 | 진행 패널. 위 버튼 비활성 (`AC`와 동일 UX, 상태가 이미 `TE`면 패널+생성 버튼) |
`RK`는 금액검증 성공이 기본. Fallback 후 [금액검증] 재실행만 추가 노출.
---
## 5. 현행 `button-config.ts`와의 차이
| 항목 | 현재 | To-Be |
| --- | --- | --- |
| USER05 | `""/SV/TA(고시)` | `SL`, `RB` 추가. `TA`는 조회 |
| USER04 | `ST`(제출+반려), `EP`(인가승인) | `RM/RC/EP` 추가. 인가승인 삭제 |
| USER03 | `LE`, `TP`(→EP로 회신) | `RA`, `RR` 추가. `TP`는 `RM` |
| USER01 | `ER`(파일검증, 검토자지정), `TE` | `ER`에 파일보완. `RK` 전면 추가 |
| USER01 ATRZ | `ER`, `TE`만 `needsRoleUser01AtrzResolve` | `RK` 포함 |
-800
View File
@@ -1,800 +0,0 @@
# 실무협의문서 개발 가이드
> **대상 앱**: `frontend/apps/as`
> **작성일**: 2026-08-27
> **버전**: 1.0
> **SoT (백엔드)**:
>
> - `PwrkCnsltnDocCrtRsltController` — `/api/v1/pwrk-cnsltn-doc-crt-rslt`
> - `PwrkCnsltnMngController` — `/api/v1/pwrk-cnsltn-mng`
> - `DcRagApiToController` — `/api/v1/dcrag-to`
> - 초안생성 데이터 설명 (`02.03.초안생성_데이터_설명.md`)
>
> **현재 프론트 상태**: `pwrk-cnsltn-`\* 엔티티·화면 **미구현**. 재원협의(`fnrs-cnsltn`)만 존재한다.
---
## 1. 문서 목적
재원협의 접수자가 **자료 검증 → 실무협의서 초안 생성 → 한글 웹기안기 열람/다운로드 → 편집본 버전 저장**까지 구현할 때 사용하는 프론트엔드 개발 가이드이다.
백엔드 컨트롤러·DTO를 **API source of truth**로 두고, 초안생성 데이터 설명의 업무 시퀀스를 화면 동작에 매핑한다.
---
## 2. 도메인 개요
실무협의서(PWRK)는 재원협의서(FNRS)의 **하위 문서**이다. 문서번호 규칙:
| 문서 | 번호 예시 | 필드 |
| -------- | ----------------- | ----------------------------- |
| 재원협의 | `F-20260824-0001` | `fnrsCnsltnDocNo` / `upDocNo` |
| 실무협의 | `P-20260827-0001` | `pwrkCnsltnDocNo` |
재원협의 1건당 실무협의서는 **1건만** 생성된다. `POST /pwrk-cnsltn-mng`는 `upDocNo`로 기존 문서를 재사용하고, 없으면 문서번호를 발급한다.
### 2-1. 핵심 식별자
| 필드 | 의미 | 비고 |
| ----------------- | ------------------------------ | --------------------------------------------------------- |
| `pwrkCnsltnDocNo` | 실무협의문서번호 | 문서 PK. `P-YYYYMMDD-NNNN` |
| `upDocNo` | 상위문서번호(재원협의) | `upDocSeComCd = "FNRS"` |
| `crtDmndUuid` | 생성요청 UUID | 초안 생성 작업·결과·다운로드의 키 |
| `verSn` | 버전일련번호 | 복합 PK `(crtDmndUuid, verSn)`. 저장 시 서버가 `+1` |
| `orgnlOtptDocCn` | 원본출력문서내용(HWP 바이너리) | **목록/상세 응답에서는 항상** `null`. 다운로드 API만 제공 |
### 2-2. IIPBS 약어
| 한글 | 약어 | 필드 예 |
| -------- | ------ | ----------------------------- |
| 실무 | PWRK | `pwrkCnsltnDocNo` |
| 협의 | CNSLTN | `pwrk-cnsltn-mng` |
| 관리 | MNG | `PwrkCnsltnMng` |
| 문서 | DOC | `pwrkCnsltnDocNo` |
| 생성 | CRT | `crtDmndUuid`, `crtSttsComCd` |
| 요청 | DMND | `crtDmndUuid` |
| 결과 | RSLT | `crtRslt` |
| 버전 | VER | `verSn` |
| 일련번호 | SN | `verSn` |
| 원본 | ORGNL | `orgnlOtptDocCn` |
| 출력 | OTPT | `orgnlOtptDocCn` |
| 내용 | CN | `orgnlOtptDocCn` |
| 완료 | CMPTN | `crtCmptnDt` |
| 일시 | DT | `crtCmptnDt` |
| 검증 | VRFC | `vrfcSttsComCd` |
| 전송 | TRSM | `trsmFileList` |
| 보완 | SPLMNT | `splmntDmndList` |
| 요청자 | RQSTR | `rqstrUuid`, `rqstrNm` |
---
## 3. 전체 업무 흐름
재원협의 **접수자**가 실무협의서 초안을 만드는 경로이다. 프론트는 DC RAG를 직접 호출하지 않고 **as-service**만 호출한다.
```mermaid
sequenceDiagram
autonumber
participant User as 재원협의 접수자
participant FE as AS Frontend
participant AS as as-service
participant RAG as DC RAG
User->>FE: [자료 검증 요청]
FE->>AS: POST /dcrag-to/documents/transfer<br/>{ fnrsCnsltnDocNo }
AS->>RAG: 자료전송 (IF-01)
AS-->>FE: { resultCode: SUCCESS, requestId }
Note over FE: 재원협의 상세 재조회
AS-->>FE: esntlFileList / etcFileList 에<br/>vrfcDt, vrfcRsltCn, vrfcSttsComCd
User->>FE: [초안 생성]
FE->>AS: POST /pwrk-cnsltn-mng<br/>{ upDocNo, upDocSeComCd: FNRS, prgrsSttsComCd: DV }
alt 실무협의서 번호 없음
AS->>AS: P-번호 발급 + 문서 등록
else 이미 존재
AS->>AS: 기존 pwrkCnsltnDocNo 재사용
end
AS->>RAG: 초안생성 비동기 요청 (IF-03-1)
AS-->>FE: 201 { pwrkCnsltnDocNo, crtDmndUuid, crtSttsComCd: R, estimatedMinutes: 10 }
loop 약 10분, status !== COMPLETED/FAILED
FE->>AS: GET /dcrag-to/documents/draft/status/{crtDmndUuid}
AS-->>FE: { status, events[], progressPercent }
end
Note over FE: events 에 COMPLETED 가 있으면<br/>웹기안기 열기 / 다운로드 가능
User->>FE: [문서 열기] 또는 [다운로드]
FE->>AS: GET /pwrk-cnsltn-doc-crt-rslt/{crtDmndUuid}/webhwp/open<br/>또는 /download
User->>FE: 웹기안기 편집 후 [저장]
FE->>AS: POST /pwrk-cnsltn-doc-crt-rslt (multipart)<br/>crtDmndUuid + file
AS-->>FE: 201 { verSn: N+1, ... }
```
### 3-1. 프론트가 호출하면 안 되는 API
| API | 이유 |
| ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `POST /dcrag-to/documents/draft/generate` | 초안 생성은 `POST /pwrk-cnsltn-mng`가 문서번호 발급 + DC RAG 호출을 **한 트랜잭션**으로 처리한다. 프론트는 관리 API만 호출한다. |
---
## 4. 화면 데이터 흐름
초안 생성은 **재원협의 상세**에서 시작하고, 실무협의 목록/상세는 별도 화면이다.
```
BEGIN
// ===== 재원협의 상세 (기존 fnrs-cnsltn view) =====
CALL findById(fnrsCnsltnDocNo)
DISPLAY 재원협의 상세 + 필수/기타 첨부
ON [자료 검증 요청] 버튼 CLICK DO
CALL requestDocumentTransfer({ fnrsCnsltnDocNo })
CALL findById(fnrsCnsltnDocNo) // 검증결과 필드 반영
DISPLAY 첨부 리스트 (vrfcSttsComCd: PASS | FAIL)
END ON
ON [초안 생성] 버튼 CLICK DO
CALL pwrkCnsltnMngService.save({
upDocNo: fnrsCnsltnDocNo,
upDocSeComCd: "FNRS",
prgrsSttsComCd: "DV"
})
// → pwrkCnsltnDocNo, crtDmndUuid, estimatedMinutes
START POLL getDraftGenerationStatus(crtDmndUuid)
DISPLAY 초안 생성 진행률 (약 10분)
END ON
ON POLL status = COMPLETED 또는 events.eventType 에 COMPLETED DO
ENABLE [웹기안기 열기] [다운로드]
END ON
ON [웹기안기 열기] 버튼 CLICK DO
CALL HwpCtrl.Open( webhwpOpenUrl(crtDmndUuid) ) // TBD: 컨트롤러 미구현
END ON
ON [다운로드] 버튼 CLICK DO
CALL downloadLatest(crtDmndUuid) // blob + Content-Disposition
SAVE 로컬 파일
END ON
// ===== 실무협의 목록 =====
CALL pwrkCnsltnMngService.findPage(searchDto + pageable)
DISPLAY 실무협의 문서 그리드
ON 행 CLICK DO
NAVIGATE 실무협의 상세 (pwrkCnsltnDocNo)
END ON
// ===== 실무협의 상세 / 재원협의에서 연계 조회 =====
CALL findById(pwrkCnsltnDocNo)
// 또는 재원협의 번호로: findByUpDocNo(fnrsCnsltnDocNo)
DISPLAY 마스터 + cnsltnList + pjtcoAjmtList + crtDmndList.crtRslt
// crtRslt.orgnlOtptDocCn 은 항상 null — 바이너리는 download API
ON 버전 이력 조회 DO
CALL pwrkCnsltnDocCrtRsltService.findPage({ crtDmndUuid, sort: "verSn,desc" })
DISPLAY 버전 목록
END ON
ON 웹기안기 편집 완료 [저장] DO
CALL saveMultipart({ crtDmndUuid, file }) // 서버가 verSn + 1
INVALIDATE 상세·버전 목록
END ON
END
```
---
## 5. API 명세
### 5-1. 공통
| 항목 | 값 |
| ----------- | ----------------------------------------------------------------- |
| Gateway | `/as-service` |
| Base prefix | `ENDPOINT.AS_SERVICE.*` (`apps/as/src/shared/config/api.ts`) |
| Auth 헤더 | `Authorization`, `X-Site-Id`, `token-id` (기존 `client` 인터셉터) |
| 날짜 포맷 | `yyyy-MM-dd HH:mm:ss` |
추가할 ENDPOINT:
```ts
AS_SERVICE: {
// 기존 …
PWRK_CNSLTN_MNG: "/as-service/api/v1/pwrk-cnsltn-mng",
PWRK_CNSLTN_DOC_CRT_RSLT: "/as-service/api/v1/pwrk-cnsltn-doc-crt-rslt",
DCRAG_TO: "/as-service/api/v1/dcrag-to",
}
```
---
### 5-2. DC RAG 연계 (`DcRagApiToController`)
프론트 엔티티 슬라이스: `entities/dcrag-to/`
| 기능 | Controller | Method | URL | Request | Response |
| ----------------- | -------------------------- | ------ | ------------------------------------------------ | --------------------------------- | ------------------------------------- |
| 자료전송요청 | `requestDocumentTransfer` | POST | `/dcrag-to/documents/transfer` | `DcRagDocumentTransferRequestDto` | `DcRagCommonResponseDto` |
| 초안생성 진행상태 | `getDraftGenerationStatus` | GET | `/dcrag-to/documents/draft/status/{crtDmndUuid}` | path | `DcRagDraftStatusResponseDto` |
| 금액검증요청 | `requestCostVerification` | POST | `/dcrag-to/documents/cost/verify` | `DcRagCostVerifyRequestDto` | `DcRagCommonResponseDto` (202) |
| 초안생성요청 | `requestDraftGeneration` | POST | `/dcrag-to/documents/draft/generate` | `DcRagDraftGenerateRequestDto` | `DcRagDraftGenerateResponseDto` (202) |
**화면에서 쓰는 것**: 자료전송 + 상태 폴링 (+ 추후 금액검증).
초안생성요청은 5-3의 `pwrkCnsltnMngService.save`로 대체한다.
#### 자료전송 Request / Response
```json
POST /as-service/api/v1/dcrag-to/documents/transfer
{ "fnrsCnsltnDocNo": "F-20260824-0001" }
```
```json
{
"resultCode": "SUCCESS",
"message": "자료전송요청 접수 완료",
"requestId": "F-20260824-0001"
}
```
자료 검증이 **완료된 뒤** 재원협의 상세를 다시 조회하면, 필수첨부(`esntlFileList`)·기타첨부(`etcFileList`) 각 파일에 검증 결과가 붙는다.
| 필드 | 예 | 의미 |
| --------------- | ----------------------------- | ------------ |
| `vrfcDt` | `2026-08-25 18:13:49` | 검증일시 |
| `vrfcRsltCn` | `정상 확인 (재원협의 신청서)` | 검증결과내용 |
| `vrfcSttsComCd` | `PASS` / `FAIL` | 검증상태 |
프론트 `FnrsCnsltnMngDetResponseDto` / `AtflMngClientResponseDto` / `EsntlAtchFileMngTreeResponseDto`에 위 3필드가 **아직 없다**. 재원협의 상세 첨부 영역에 배지를 그리려면 타입을 확장해야 한다.
#### 초안생성 상태 폴링
```http
GET /as-service/api/v1/dcrag-to/documents/draft/status/{crtDmndUuid}
```
| 필드 | 설명 |
| ----------------------- | --------------------------------------------------------------------------------- |
| `status` | `REQUESTED` / `DOWNLOADING` / `ANALYZING` / `GENERATING` / `COMPLETED` / `FAILED` |
| `statusName` | 표시명 (예: `생성 완료`) |
| `progressPercent` | 0~100 |
| `events[]` | `{ eventType, eventMessage, occurredDt }` |
| `startedDt` | 시작일시 |
| `estimatedCompletionDt` | 예상 완료일시 |
| `errorMessage` | 실패 시 메시지 |
폴링 규칙:
- 주기: **5~10초** (TBD, UX 확정).
- 예상 소요: **약 10분** (`estimatedMinutes`).
- 종료 조건: `status === "COMPLETED"` **또는** `events`에 `eventType === "COMPLETED"`.
- 실패: `status === "FAILED"` → 폴링 중단, `errorMessage` 표시.
- 완료 전에는 웹기안기 열기·다운로드 버튼을 **비활성**.
이벤트 순서 예시:
| eventType | eventMessage |
| ------------- | --------------------------- |
| `REQUESTED` | 요청을 접수했습니다. |
| `DOWNLOADING` | 신청서를 수신하는 중입니다. |
| `ANALYZING` | 자료 준비가 완료되었습니다. |
| `GENERATING` | 문서 저장이 완료되었습니다. |
| `COMPLETED` | 문서 생성이 완료되었습니다. |
---
### 5-3. 실무협의관리 (`PwrkCnsltnMngController`)
프론트 엔티티 슬라이스: `entities/pwrk-cnsltn-mng/`
| 기능 | Controller | Method | URL | Request | Response |
| ------------------- | ---------------------- | ------ | ----------------------------------------------------- | ----------------------------------- | ------------------------------------ |
| 페이징 조회 | `findPage` | GET | `/pwrk-cnsltn-mng` | `PwrkCnsltnMngSearchDto` + Pageable | `Page<PwrkCnsltnMngResponseDto>` |
| 상세 (문서번호) | `findById` | GET | `/pwrk-cnsltn-mng/{pwrkCnsltnDocNo}` | path | `PwrkCnsltnMngDetResponseDto` |
| 상세 (재원협의번호) | `findByUpDocNo` | GET | `/pwrk-cnsltn-mng/updocno/{upDocNo}` | path | `PwrkCnsltnMngDetResponseDto` |
| 등록 + 초안생성 | `save` | POST | `/pwrk-cnsltn-mng` | `PwrkCnsltnMngSaveRequestDto` | `PwrkCnsltnMngSaveResponseDto` (201) |
| 신청 통합 저장 | `saveApply` | POST | `/pwrk-cnsltn-mng/save-apply` | `PwrkCnsltnApplySaveRequestDto` | `string` (문서번호, 201) |
| 수정 | `update` | PUT | `/pwrk-cnsltn-mng/{pwrkCnsltnDocNo}` | `PwrkCnsltnMngUpdateRequestDto` | `string` |
| 삭제 | `delete` → `remove` | DELETE | `/pwrk-cnsltn-mng/{pwrkCnsltnDocNo}` | path | 204 |
| 보완완료 제출 | `submitSupplement` | POST | `/pwrk-cnsltn-mng/{pwrkCnsltnDocNo}/submitsupplement` | `PwrkCnsltnApplySaveRequestDto` | `string` |
| 보완요청 등록 | `submitReqSupplement` | POST | `/pwrk-cnsltn-mng/{pwrkCnsltnDocNo}/reqsupplement` | `PwrkCnsltnApplySaveRequestDto` | `string` |
| 진행상태 수정 | `changePrgrsSttsComCd` | POST | `/pwrk-cnsltn-mng/doc/updatePrgrsStts` | `DocPrgrsUpdateRequestDto` | 200 |
목록 기본 호출:
```http
GET /as-service/api/v1/pwrk-cnsltn-mng?page=0&size=10&sort=pwrkCnsltnDocNo,desc
```
#### 초안 생성 Request / Response (`save`)
```json
POST /as-service/api/v1/pwrk-cnsltn-mng
{
"upDocNo": "F-20260824-0001",
"upDocSeComCd": "FNRS",
"prgrsSttsComCd": "DV"
}
```
```json
{
"pwrkCnsltnDocNo": "P-20260826-0001",
"upDocNo": "F-20260824-0001",
"upDocSeComCd": "FNRS",
"prgrsSttsComCd": "DV",
"resultCode": "ACCEPTED",
"message": "초안 생성 작업이 접수되었습니다.",
"crtDmndUuid": "382f2821-9231-42e6-8309-4a1718a2ead8",
"crtSttsComCd": "R",
"requestId": "382f2821-9231-42e6-8309-4a1718a2ead8",
"estimatedMinutes": 10
}
```
백엔드 동작 (`PwrkCnsltnMngService.save`):
1. `upDocNo`로 기존 실무협의서가 있으면 **그 번호를 재사용** (재원협의 1 : 실무협의 1).
2. 없으면 `docNoMngService.issueDocNo(PWRK)`로 `P-` 번호 발급 후 insert.
3. DC RAG 초안생성을 비동기로 호출하고 `crtDmndUuid`를 응답에 실어 반환.
4. DC RAG 실패 시에도 **문서는 롤백하지 않고** 로그만 남긴다. 프론트는 `resultCode`가 `ACCEPTED`/`SUCCESS`가 아니면 토스트로 실패를 알린다.
#### 상세 응답 구조 (`PwrkCnsltnMngDetResponseDto`)
재원협의 상세에서 `GET .../updocno/{fnrsCnsltnDocNo}`로 연계 데이터를 한 번에 가져온다.
```json
{
"pwrkCnsltnDocNo": "P-20260827-0001",
"upDocNo": "F-20260824-0001",
"upDocSeComCd": "FNRS",
"prgrsSttsComCd": "DV",
"cnsltnList": [],
"pjtcoAjmtList": [],
"crtDmndList": [
{
"crtDmndUuid": "d0545bef-735f-4d7b-a781-88e96e03577a",
"pwrkCnsltnDocNo": "P-20260827-0001",
"rqstrUuid": "65a00f65-8460-49af-98ec-042977e56f4b",
"rqstrNm": "관리자",
"crtSttsComCd": "C",
"crtRslt": {
"crtDmndUuid": "d0545bef-735f-4d7b-a781-88e96e03577a",
"verSn": 1,
"rgtrUuid": "...",
"rgtrNm": "관리자",
"orgnlOtptDocCn": null,
"crtCmptnDt": "2026-08-27 10:47:01"
}
}
],
"splmntDmndList": [],
"trsmFileList": []
}
```
`crtRslt`는 최신 버전 메타만 포함한다. `orgnlOtptDocCn`은 절대 쓰지 말고 download API를 호출한다.
마스터에 재원협의 사업정보가 조인된다: `bizNm`, `admdstCd`, `envoSeComCd`, `taskSeComCd`, `aplcntNm` 등.
---
### 5-4. 문서생성결과 (`PwrkCnsltnDocCrtRsltController`)
프론트 엔티티 슬라이스: `entities/pwrk-cnsltn-doc-crt-rslt/`
복합 PK: `crtDmndUuid` + `verSn`
| 기능 | Controller | Method | URL | Request | Response |
| ------------------ | ------------------------- | ------ | ---------------------------------- | ----------------------------------------------- | --------------------------------------- |
| 버전 이력 페이징 | `findPage` | GET | `/pwrk-cnsltn-doc-crt-rslt` | `PwrkCnsltnDocCrtRsltSearchDto` + Pageable | `Page<PwrkCnsltnDocCrtRsltResponseDto>` |
| 특정 버전 상세 | `findById` | GET | `/{crtDmndUuid}/{verSn}` | path | `PwrkCnsltnDocCrtRsltResponseDto` |
| 최신 버전 상세 | `findLatestByCrtDmndUuid` | GET | `/{crtDmndUuid}/latest` | path | `PwrkCnsltnDocCrtRsltResponseDto` |
| 특정 버전 다운로드 | `download` | GET | `/{crtDmndUuid}/{verSn}/download` | path | `byte[]` (attachment) |
| 최신 버전 다운로드 | `downloadLatest` | GET | `/{crtDmndUuid}/download` | path | `byte[]` (attachment) |
| 등록 (multipart) | `saveMultipart` | POST | `/` `multipart/form-data` | `PwrkCnsltnDocCrtRsltSaveRequestDto` + `file` | `PwrkCnsltnDocCrtRsltResponseDto` (201) |
| 등록 (JSON) | `saveJson` | POST | `/` `application/json` | `PwrkCnsltnDocCrtRsltSaveRequestDto` | 201 |
| 수정 (multipart) | `updateMultipart` | PUT | `/{crtDmndUuid}/{verSn}` multipart | `PwrkCnsltnDocCrtRsltUpdateRequestDto` + `file` | ResponseDto |
| 수정 (JSON) | `updateJson` | PUT | `/{crtDmndUuid}/{verSn}` json | UpdateRequestDto | ResponseDto |
| 삭제 | `delete` → `remove` | DELETE | `/{crtDmndUuid}/{verSn}` | path | 204 |
Spring 매핑 주의: `/{crtDmndUuid}/latest` 와 `/{crtDmndUuid}/download` 는 `/{crtDmndUuid}/{verSn}` 보다 **구체 경로가 우선**한다. 프론트 path를 섞지 말 것.
버전 이력 조회 예:
```http
GET /as-service/api/v1/pwrk-cnsltn-doc-crt-rslt?page=0&size=10&crtDmndUuid={uuid}&sort=verSn,desc
```
#### 다운로드 (attachment)
화면 [다운로드] 버튼 → PC 저장.
```http
GET /as-service/api/v1/pwrk-cnsltn-doc-crt-rslt/{crtDmndUuid}/download
GET /as-service/api/v1/pwrk-cnsltn-doc-crt-rslt/{crtDmndUuid}/{verSn}/download
```
- axios: `responseType: "blob"`.
- 파일명: `Content-Disposition`의 `filename*=UTF-8''...` (`parseFilenameFromResponse` 재사용).
- 서버 파일명 규칙: `재원협의 실무협의서({bizNm})_v{verSn}.hwp`.
- **현재 컨트롤러 Content-Type**: `application/x-hwp`.
데이터 설명 예시는 `application/haansofthwpx`. 프론트는 Content-Type에 의존하지 말고 blob으로 저장한다.
기존 `attachService` 다운로드와 동일 패턴:
```ts
const response = await client.get<Blob>(url, { responseType: "blob" });
const filename = parseFilenameFromResponse(response);
const objectUrl = window.URL.createObjectURL(response.data);
```
#### 웹기안기 열기 (inline) — TBD
데이터 설명 기준 엔드포인트. **현재** `PwrkCnsltnDocCrtRsltController`**에는 없음.** 백엔드 추가 후 연결한다.
```http
GET /as-service/api/v1/pwrk-cnsltn-doc-crt-rslt/{crtDmndUuid}/webhwp/open
GET /as-service/api/v1/pwrk-cnsltn-doc-crt-rslt/{crtDmndUuid}/{verSn}/webhwp/open
```
의도:
- `Content-Disposition: inline`
- 한컴 웹기안기 `HwpCtrl.Open(url)` 이 이 URL을 직접 호출
- 브라우저 다운로드 창이 뜨지 않고 캔버스에 로드
프론트는 **blob fetch 후 Object URL을 넘기지 않는다.** 인증 헤더가 필요한 절대 URL을 기안기에 전달하는 방식이므로, 쿠키/게이트웨이 인증 가능 여부를 백엔드와 확인한다 (TBD).
#### 신규 버전 등록 (웹기안기 저장)
서버가 최신 `verSn + 1`을 부여한다. 클라이언트가 `verSn`을 보내지 않는다.
```http
POST /as-service/api/v1/pwrk-cnsltn-doc-crt-rslt
Content-Type: multipart/form-data
crtDmndUuid: {uuid}
file: 재원협의 실무협의서_v2.hwpx
```
화면에서는 **multipart만** 사용한다. JSON `saveJson`은 바이너리를 body에 넣는 경로라 UI에서 쓰지 않는다.
`file` part 이름: `"file"`. DTO 필드는 `@ModelAttribute` 로 평문 필드(`crtDmndUuid`) 또는 `saveDto` part.
---
## 6. DTO 매핑 (프론트 타입명 = 백엔드 클래스명)
### 6-1. `PwrkCnsltnDocCrtRslt*`
| 계층 | 파일 | 정의 |
| ------------------------------------ | -------------------------------------------- | ----------------------------------------------- |
| Save Request | `pwrk-cnsltn-doc-crt-rslt-payload-schema.ts` | zod SSOT → `PwrkCnsltnDocCrtRsltSaveRequestDto` |
| Response / Search / Update / Service | `pwrk-cnsltn-doc-crt-rslt-interface.ts` | `interface` |
**Response**
| 필드 | 타입 | 비고 |
| ---------------- | -------- | --------------------- |
| `crtDmndUuid` | `string` | UUID |
| `verSn` | `number` | BigDecimal |
| `rgtrUuid` | `string` | |
| `rgtrNm` | `string` | 서버 enrich |
| `orgnlOtptDocCn` | `null` | 목록/상세 미포함 |
| `crtCmptnDt` | `string` | `yyyy-MM-dd HH:mm:ss` |
**Search** (`PageableParams` 확장)
| 필드 | 비고 |
| ----------------- | ---------------------- |
| `crtDmndUuid` | 버전 이력 조회 시 필수 |
| `pwrkCnsltnDocNo` | |
| `verSn` | |
| `rgtrUuid` | |
**Save Request (zod)**
| 필드 | 필수 | 비고 |
| ------------- | ---- | ------------------------------------ |
| `crtDmndUuid` | Y | |
| `rgtrUuid` | N | 미전송 시 서버가 로그인 사용자 사용 |
| `file` | UI | multipart `file` part. DTO 필드 아님 |
**Update Request**
| 필드 | 비고 |
| ------------ | --------- |
| `rgtrUuid` | |
| `crtCmptnDt` | |
| `file` | multipart |
### 6-2. `PwrkCnsltnMng*`
**Search**
`pwrkCnsltnDocNo`, `upDocNo`, `upDocSeComCd`, `prgrsSttsComCd`, `admdstCd`, `envoSeComCd`, `taskSeComCd`, `bizNm`
**Save Request (초안 생성)** — 필드가 얇으므로 interface만으로도 가능. 권장은 zod.
| 필드 | 필수 | 값 |
| ----------------- | ---- | ----------------- |
| `upDocNo` | Y | 재원협의 문서번호 |
| `upDocSeComCd` | | `"FNRS"` |
| `prgrsSttsComCd` | | `"DV"` (초안생성) |
| `pwrkCnsltnDocNo` | N | 서버 발급 |
**Save Response** — `save`만 이 DTO. `fnrsCnsltnMngService.save`가 `string`을 반환하는 것과 **다름**.
| 필드 | 의미 |
| ------------------ | --------------------------------- |
| `pwrkCnsltnDocNo` | 발급/재사용 번호 |
| `resultCode` | `ACCEPTED` / `SUCCESS` / 실패코드 |
| `crtDmndUuid` | 폴링 키 |
| `crtSttsComCd` | `R` 생성요청 |
| `estimatedMinutes` | 기본 10 |
**DetResponse 중첩**
- `cnsltnList`: 협의내역 (`rvwOpnnSn`, `rvwOpnnClsfComCd`, `cnsltnCn`, `rfltYn`, …)
- `pjtcoAjmtList`: 사업비조정
- `crtDmndList[].crtRslt`: 생성요청 + 최신 결과
- `splmntDmndList`: 보완요청
- `sttsChgHstryList`: 상태변경이력
- `trsmFileList`: 전송파일 (데이터 설명 기준. Det DTO Java 필드 확인 후 타입 반영)
### 6-3. DC RAG
```ts
interface DcRagDocumentTransferRequestDto {
fnrsCnsltnDocNo: string;
taskSeComCd?: string;
taskSeCdNm?: string;
}
interface DcRagCommonResponseDto {
resultCode: string;
message?: string;
requestId?: string;
}
interface DcRagDraftStatusResponseDto {
crtDmndUuid: string;
status: DraftStatus;
statusName?: string;
progressPercent?: number;
events?: EventInfoDto[];
startedDt?: string;
estimatedCompletionDt?: string;
errorMessage?: string | null;
}
type DraftStatus =
| "REQUESTED"
| "DOWNLOADING"
| "ANALYZING"
| "GENERATING"
| "COMPLETED"
| "FAILED";
```
---
## 7. 공통코드
| 상위코드 | 필드 | 값 | 명칭 |
| ----------------------- | ---------------- | --------------- | ------------- |
| `ASDOCSE` | `upDocSeComCd` | `FNRS` | 재원협의 |
| `ASDOCSE` | 문서구분 | `PWRK` | 실무협의 |
| `PWRKSTTS` | `prgrsSttsComCd` | `DV` | 초안생성 |
| `PWRKSTTS` | | `AR` | 검토자지정 |
| `PWRKSTTS` | | `SV` | 작성중 |
| `PWRKSTTS` | | `RB` | 검토회신 |
| `PWRKSTTS` | | `RC` | 협의회신 |
| `PWRKSTTS` | | `TR` | 검토결과 회신 |
| `PWRKSTTS` | | `SL` | 보완요청 |
| `PWRKSTTS` | | `AA` | 금액조정중 |
| `AICRTSTTS` / `CRTSTTS` | `crtSttsComCd` | `R` | 생성요청 |
| | | `I` | 생성중 |
| | | `C` | 생성완료 |
| | | `F` | 생성실패 |
| (검증) | `vrfcSttsComCd` | `PASS` / `FAIL` | |
`CrtDmndResponseDto`는 `@CodeName(upCd = "CRTSTTS")`, 생성요청 단건 DTO는 `AICRTSTTS`를 쓴다. 코드값이 `R/I/C/F`로 같으므로 UI는 코드값으로 분기하고, 표시명은 응답의 `*Nm` 필드를 쓴다.
초안 생성 직후 `crtSttsComCd = R`, 폴링 `COMPLETED` 이후 상세의 `crtSttsComCd = C`.
---
## 8. 프론트엔드 구현 가이드
### 8-1. 기술 스택·원칙
- App Router + FSD (`app/` → `pages/` → `widgets/` → `entities/` → `shared/`)
- React Query. ad-hoc `useQuery` + service 직접 호출 금지
- `@common/components`, `useDataTable`, `@tanstack/react-table`
- `'use client'`는 Web API(다운로드, 폴링, 웹기안기)가 있는 페이지만
- 함수명 = Controller public 메서드명 (`delete` → `remove`)
- 타입명 = 백엔드 DTO명. 프론트 전용 축약 금지
- Save/Apply Request = zod SSOT. Response는 `interface`
참조 슬라이스: `entities/fnrs-cnsltn/`, `entities/sewer-bsc-plan/`
### 8-2. 모듈·파일 배치
| 구분 | 경로 |
| -------------------- | ------------------------------------------------------------------- |
| ENDPOINT | `apps/as/src/shared/config/api.ts` |
| 실무협의 관리 엔티티 | `entities/pwrk-cnsltn-mng/` |
| 생성결과 엔티티 | `entities/pwrk-cnsltn-doc-crt-rslt/` |
| DC RAG 엔티티 | `entities/dcrag-to/` |
| 목록 라우트 | `app/(main)/(with-base-layout)/pwrk-cnsltn/page.tsx` |
| 상세 라우트 | `app/(main)/(with-base-layout)/pwrk-cnsltn/[id]/page.tsx` |
| 페이지 UI | `pages/pwrk-cnsltn/` |
| 재원협의 상세 연계 | `pages/fnrs-cnsltn/ui/fnrs-view-page.tsx` (버튼·폴링·첨부 검증배지) |
엔티티 내부:
```
entities/pwrk-cnsltn-mng/
├── model/
│ ├── pwrk-cnsltn-mng-interface.ts
│ └── pwrk-cnsltn-mng-payload-schema.ts
├── api/
│ ├── pwrk-cnsltn-mng-service.ts
│ └── queries.ts
└── index.ts
entities/pwrk-cnsltn-doc-crt-rslt/
├── model/
│ ├── pwrk-cnsltn-doc-crt-rslt-interface.ts
│ └── pwrk-cnsltn-doc-crt-rslt-payload-schema.ts
├── api/
│ ├── pwrk-cnsltn-doc-crt-rslt-service.ts
│ └── queries.ts
└── index.ts
```
### 8-3. Service 시그니처 요지
`pwrkCnsltnMngService.save` 반환 타입이 `string`이 아니라 `PwrkCnsltnMngSaveResponseDto` 인 점을 `fnrs-cnsltn`과 혼동하지 말 것.
```ts
export interface PwrkCnsltnMngService {
findPage(searchDto: PwrkCnsltnMngSearchDto): Promise<PageResponse<PwrkCnsltnMngResponseDto>>;
findById(pwrkCnsltnDocNo: string): Promise<PwrkCnsltnMngDetResponseDto>;
findByUpDocNo(upDocNo: string): Promise<PwrkCnsltnMngDetResponseDto>;
save(body: PwrkCnsltnMngSaveRequestDto): Promise<PwrkCnsltnMngSaveResponseDto>;
saveApply(body: PwrkCnsltnApplySaveRequestDto): Promise<string>;
update(pwrkCnsltnDocNo: string, body: PwrkCnsltnMngUpdateRequestDto): Promise<string>;
submitSupplement(pwrkCnsltnDocNo: string, body: PwrkCnsltnApplySaveRequestDto): Promise<string>;
submitReqSupplement(
pwrkCnsltnDocNo: string,
body: PwrkCnsltnApplySaveRequestDto,
): Promise<string>;
remove(pwrkCnsltnDocNo: string): Promise<void>;
changePrgrsSttsComCd(body: DocPrgrsUpdateRequestDto): Promise<void>;
}
export interface PwrkCnsltnDocCrtRsltService {
findPage(
searchDto: PwrkCnsltnDocCrtRsltSearchDto,
): Promise<PageResponse<PwrkCnsltnDocCrtRsltResponseDto>>;
findById(crtDmndUuid: string, verSn: number): Promise<PwrkCnsltnDocCrtRsltResponseDto>;
findLatestByCrtDmndUuid(crtDmndUuid: string): Promise<PwrkCnsltnDocCrtRsltResponseDto>;
download(crtDmndUuid: string, verSn: number): Promise<AxiosResponse<Blob>>;
downloadLatest(crtDmndUuid: string): Promise<AxiosResponse<Blob>>;
saveMultipart(formData: FormData): Promise<PwrkCnsltnDocCrtRsltResponseDto>;
updateMultipart(
crtDmndUuid: string,
verSn: number,
formData: FormData,
): Promise<PwrkCnsltnDocCrtRsltResponseDto>;
remove(crtDmndUuid: string, verSn: number): Promise<void>;
}
```
multipart `Content-Type`은 axios가 boundary를 붙이도록 **헤더를 수동 지정하지 않는다.**
### 8-4. 폴링 훅
`useQuery` + `refetchInterval`:
```ts
useQuery({
queryKey: [...dcragToKeys.draftStatus(crtDmndUuid)],
queryFn: () => dcragToService.getDraftGenerationStatus(crtDmndUuid),
enabled: Boolean(crtDmndUuid) && isPolling,
refetchInterval: (query) => {
const status = query.state.data?.status;
if (status === "COMPLETED" || status === "FAILED") return false;
return 5_000;
},
});
```
`COMPLETED` 시 `pwrkCnsltnMng` 상세 쿼리를 invalidate 하여 `crtDmndList[].crtRslt`를 갱신한다.
### 8-5. 재원협의 상세에 붙일 UI
`pages/fnrs-cnsltn` 조회 모드:
1. **자료 검증 요청** — `requestDocumentTransfer` → 상세 refetch → 첨부 행에 PASS/FAIL 배지 (`vrfcSttsComCd`, `vrfcRsltCn`, `vrfcDt`).
2. **초안 생성** — 검증이 끝난 뒤 활성. `pwrkCnsltnMngService.save`.
3. **진행 표시** — `progressPercent` + 이벤트 타임라인. `estimatedMinutes`(10)를 안내.
4. **웹기안기 열기 / 다운로드** — `COMPLETED` 이후에만.
`findByUpDocNo(fnrsCnsltnDocNo)`로 이미 초안이 있으면 `crtDmndUuid`를 복원해 폴링·열기를 이어간다.
### 8-6. 실무협의 목록 컬럼 (권장)
마스터 응답 + 재원협의 조인 필드 기준. 화면설계 확정 전 TBD.
| 표시명 | Field |
| ------------ | ------------------ |
| 실무협의번호 | `pwrkCnsltnDocNo` |
| 재원협의번호 | `upDocNo` |
| 진행상태 | `prgrsSttsComCdNm` |
| 사업명 | `bizNm` |
| 시도/구군 | `sido` / `gugun` |
| 신청자 | `aplcntNm` |
| 등록일 | `regDt` |
행 클릭 → `/pwrk-cnsltn/{pwrkCnsltnDocNo}`.
---
## 9. 구현 순서
```
Task Progress:
- [ ] 1. ENDPOINT 3개 추가 (PWRK_CNSLTN_MNG, PWRK_CNSLTN_DOC_CRT_RSLT, DCRAG_TO)
- [ ] 2. entities/dcrag-to (transfer + draft status)
- [ ] 3. entities/pwrk-cnsltn-mng (findPage, findById, findByUpDocNo, save)
- [ ] 4. entities/pwrk-cnsltn-doc-crt-rslt (findPage, downloadLatest, saveMultipart)
- [ ] 5. 재원협의 상세: 검증결과 3필드 타입 확장 + 배지
- [ ] 6. 재원협의 상세: 자료검증 / 초안생성 / 폴링 UI
- [ ] 7. 실무협의 목록·상세 라우트
- [ ] 8. 다운로드 (blob)
- [ ] 9. 웹기안기 Open — 백엔드 webhwp/open 확인 후
- [ ] 10. 웹기안기 저장 → saveMultipart (verSn + 1)
```
1~4는 엔티티 레이어만으로 먼저 닫는다. 화면은 5부터.
---
## 10. TBD · 백엔드 확인
| 항목 | 내용 |
| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `webhwp/open` | 데이터 설명에는 있으나 **현재 Controller에 없음**. 추가 일정·인증 방식 확인 |
| Content-Type | Controller `application/x-hwp` vs 설명 `application/haansofthwpx` |
| 파일 확장자 | 다운로드 파일명이 `.hwp`로 고정. 실제 바이너리가 hwpx일 수 있음 |
| 폴링 주기 | 5초 vs 10초, 탭 비활성 시 중단 여부 |
| 검증 필드 타입 | `FnrsCnsltnMngDetResponseDto.esntlFileList` / `etcFileList` Java 타입이 아직 `FnrsCnsltnAtflResponseDto`가 아님. 런타임 JSON에 `vrfc*`가 오는지 확인 |
| `trsmFileList` | 데이터 설명 상세 예시에는 있으나 Det DTO Java 선언과 불일치 가능 |
| `crtSttsComCd` 상위코드 | `CRTSTTS` vs `AICRTSTTS` |
| 웹기안기 라이선스 URL | `HwpCtrl.Open`에 넘길 게이트웨이 절대 경로 (쿠키 인증 가능 여부) |
| 초안 생성 1회 제한 | `requestDraftGeneration` 주석에 “1회 제한”. 재클릭 UX (비활성 / 재요청 확인) |
| 자료전송 허용 확장자 | `.doc .docx .hwp .hwpx .md .pdf .txt .xls .xlsm .xlsx`. 이미지 첨부 시 DC RAG 실패 |
---
## 11. 참고
| 자료 | 위치 |
| ---------------------- | ------------------------------------------------------------------------------ |
| 생성결과 컨트롤러 | `backend/.../pwrkcnsltndoccrtrslt/PwrkCnsltnDocCrtRsltController.java` |
| 실무협의관리 컨트롤러 | `backend/.../pwrkcnsltnmng/PwrkCnsltnMngController.java` |
| DC RAG TO 컨트롤러 | `backend/.../dcrag/DcRagApiToController.java` |
| 초안생성 데이터 설명 | `02.03.초안생성_데이터_설명.md` |
| 엔티티 구현 스킬 | `frontend/.cursor/skills/backend-controller-to-entity/` |
| 재원협의 엔티티 (패턴) | `apps/as/src/entities/fnrs-cnsltn/` |
| IIPBS 단어목록 | `frontend/docs/IIPBS_단어목록.md` |
| 진행상태 코드 | `backend/IIPBS_CS/docs/2026/05.행정지원/01.문서상태별_사용자_상태변경_이력.md` |