10 KiB
레퍼런스 — 프론트 엔티티·DTO
상위: 개발가이드.md
엔티티 규칙:frontend/.cursor/skills/backend-controller-to-entity
1. ENDPOINT
apps/as/src/shared/config/api.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에 추가:
requestExamine(docNo: string, body: FnrsExamineRequestDto): Promise<string>;
findFileVrfcResults(fnrsCnsltnDocNo: string): Promise<FnrsCnsltnFileVrfcRsltResponseDto[]>;
requestActnReview(docNo: string, body: EnvoActnReviewReqDto): Promise<string>;
실무협의:
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 연결 금지.
생성결과:
export interface PwrkCnsltnDocCrtRsltService {
findPage(
searchDto: PwrkCnsltnDocCrtRsltSearchDto,
): Promise<PageResponse<PwrkCnsltnDocCrtRsltResponseDto>>;
findById(crtDmndUuid: string, verSn: number): Promise<PwrkCnsltnDocCrtRsltResponseDto>;
findLatestByCrtDmndUuid(crtDmndUuid: string): Promise<PwrkCnsltnDocCrtRsltResponseDto>;
openWebHwpLatest(crtDmndUuid: string): Promise<AxiosResponse<Blob>>;
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은 인증된 axios 요청으로 Blob을 조회한다:
async function openWebHwpLatest(crtDmndUuid: string) {
return client.get<Blob>(`${BASE}/${crtDmndUuid}/webhwp/open`, {
responseType: "blob",
});
}
응답 Blob은 HWPX File로 감싸 HwpCtrl.Open(file, "HWPX", ...)에 전달한다. blob: Object URL은 사용하지 않는다.
DC RAG TO (구현됨, 시그니처 유지):
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
| 필드 | 필수 |
|---|---|
specialRqstCn |
N |
FnrsCnsltnFileVrfcRsltResponseDto: ref-endpoint.md §3-3.
첨부 검증 배지용으로 AtflMngClientResponseDto / EsntlAtchFileMngTreeResponseDto에 vrfcDt, vrfcRsltCn, vrfcSttsComCd 확장.
4-2. PwrkActnResultSubmitDto (zod)
| 필드 | 필수 | 비고 |
|---|---|---|
isSubmit |
Y | false API만(화면 버튼 없음), true 조치완료(RB→RA) |
actnList |
N | 조치완료 시 선택 |
actnList[].rvwOpnnSn |
Y (항목 있을 때) | |
actnList[].actnRsltCn |
N | |
actnList[].atchFileGroupId |
N | |
totActnSummary |
N | 제출 시 권장 |
muniDocNo |
N | 제출 시 권장 |
pwrkAtchFileGroupId |
N |
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.
상세 예
{
"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
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 (구현됨)
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이면서 생성 중):
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. 다운로드
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:
elaprCycl: data.detDto?.prgrsSttsComCd === "RK" ? 2 : 1,
docPrgrsSttsComCd: data.detDto?.prgrsSttsComCd, // ER
To-Be:
- 검토자지정은
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.