From 36b11d6b004fc54efa4beaa419c417fd5721ed55 Mon Sep 17 00:00:00 2001 From: kiminchan Date: Mon, 19 Oct 2026 17:01:37 +0900 Subject: [PATCH] =?UTF-8?q?=EB=A7=A8=ED=99=80=20=EC=A1=B0=EC=82=AC?= =?UTF-8?q?=EB=B3=B4=EA=B3=A0=EC=84=9C=20=EB=8B=A8=EB=8F=85=20=EA=B5=90?= =?UTF-8?q?=EC=B2=B4=20=EB=B0=8F=20GIS=20=EB=B2=88=ED=98=B8=20=EA=B8=B0?= =?UTF-8?q?=EB=B0=98=20=EC=A1=B0=ED=9A=8C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/exmn/use-exmn-result-map.ts | 6 ++- .../001-qa-SFR-010-004~007-조사사업-통합검증계획.md | 5 +++ .../002-qa-SFR-010-004~007-조사사업-통합검증결과.md | 41 +++++++++++++++++++ .../001-fe-SFR-010-004~007-조사사업-통합구현기록.md | 41 +++++++++++++++++++ .../features/exmn/api/exmn-business-api.ts | 6 ++- .../exmn/ui/exmn-registration-page.tsx | 2 +- .../ui/exmn-result-register-popup-page.tsx | 39 +++++++++++------- .../exmn/ui/exmn-survey-result-section.tsx | 24 ++++++----- .../ui/exmn-pipe-report-popup-content.tsx | 17 ++++---- 9 files changed, 144 insertions(+), 37 deletions(-) diff --git a/apps/sn/app/(map-layout)/map/components/exmn/use-exmn-result-map.ts b/apps/sn/app/(map-layout)/map/components/exmn/use-exmn-result-map.ts index 9e2e5f5b6..9b4f32f5c 100644 --- a/apps/sn/app/(map-layout)/map/components/exmn/use-exmn-result-map.ts +++ b/apps/sn/app/(map-layout)/map/components/exmn/use-exmn-result-map.ts @@ -87,7 +87,7 @@ export const useExmnResultMap = (options: Options) => { [gisApp, mapRef], ); - /** 지역 검색값 없이 전체를 요청하고, 선택 진입 context는 위치 이동 범위 조회에만 사용한다. */ + /** 일반 진입은 전체, 조사관리 지도보기는 선택 사업·처리분구만 조회해 겹친 타 사업 선택을 방지한다. */ const loadLayer = useCallback( async (job: ExmnMapJobType, filters: ExmnMapFilters) => { const map = mapRef.current; @@ -109,7 +109,9 @@ export const useExmnResultMap = (options: Options) => { const response = await getExmnMapLayer( job, filters, - {}, + entry.exmnBizId && entry.trtdstId + ? { exmnBizId: entry.exmnBizId, trtdstId: entry.trtdstId } + : {}, controller.signal, ); if (!active()) return; diff --git a/apps/sn/docs/작업검증/SFR-010-004~SFR-010-007-조사사업/001-qa-SFR-010-004~007-조사사업-통합검증계획.md b/apps/sn/docs/작업검증/SFR-010-004~SFR-010-007-조사사업/001-qa-SFR-010-004~007-조사사업-통합검증계획.md index b691a2ba7..2b46e84f0 100644 --- a/apps/sn/docs/작업검증/SFR-010-004~SFR-010-007-조사사업/001-qa-SFR-010-004~007-조사사업-통합검증계획.md +++ b/apps/sn/docs/작업검증/SFR-010-004~SFR-010-007-조사사업/001-qa-SFR-010-004~007-조사사업-통합검증계획.md @@ -275,3 +275,8 @@ - 레이어 API 단위 테스트는 실제 서비스가 호출하는 Repository 메서드와 동일한 메서드를 모킹해야 한다. - 전체 빌드가 장시간 걸리는 환경에서는 변경 파일 정적 검사 후 백엔드 대상 테스트와 로그인 브라우저 회귀를 분리한다. + + +## 2026-10-19 맨홀 PDF GIS 번호 검증 +- 188001 테스트 PDF 실제 판독·쪽 추출, PPL/MNHL 첨부 분리, 미첨부·없는 번호·중복 시설·다중/비연속 번호를 검증한다. +- 실제 서버에서는 맨홀 첨부 저장 → 상류맨홀 지도 선택 → 맨홀 보고서 1쪽 표시와 관로 회귀를 확인한다. 자동검증/실브라우저 여부는 통합검증결과에 구분한다. diff --git a/apps/sn/docs/작업검증/SFR-010-004~SFR-010-007-조사사업/002-qa-SFR-010-004~007-조사사업-통합검증결과.md b/apps/sn/docs/작업검증/SFR-010-004~SFR-010-007-조사사업/002-qa-SFR-010-004~007-조사사업-통합검증결과.md index 5024b66fe..4f63b758f 100644 --- a/apps/sn/docs/작업검증/SFR-010-004~SFR-010-007-조사사업/002-qa-SFR-010-004~007-조사사업-통합검증결과.md +++ b/apps/sn/docs/작업검증/SFR-010-004~SFR-010-007-조사사업/002-qa-SFR-010-004~007-조사사업-통합검증결과.md @@ -678,3 +678,44 @@ - 검증: SN 전체 타입검사 exit0, 숫자/소수/삭제 및 거부 문자열 Node 검증 통과, git diff --check 통과. 실브라우저 입력 검증은 미수행. - 수정 소스: `frontend/apps/sn/src/features/exmn/ui/exmn-business-register-popup-page.tsx`. API/백엔드 코드·저장 계약 변경 없음. 기존 GIS 작업은 변경하지 않았다. - 롤백: 해당 소스의 이번 필수 표시/입력 제한만 복원한다. + + +## 2026-10-19 맨홀 조사보고서 GIS 번호 연결 (maintenance) + +- 목적: 별도 맨홀 식별체계 확정 전 맨홀 PDF의 `맨홀번호`에 조사시설 GIS 번호(테스트 188001)를 넣고 지도에서 해당 보고서를 열람한다. 지도 도형은 기존 상류맨홀 매칭을 유지한다. +- 근거: 요구사항정의서 `3. (기능)요구사항정의 및 추적표` SFR-010-006/007, 화면설계서 실제 42쪽의 식별기준 미확정 메모 및 43쪽 `SN_MN040101` 조사결과 등록. 이번 사용자 지시로 임시 GIS 번호 연결 기준을 확정했다. +- BE: `ExmnManholeReportService.java` 신규, `ExmnPipePdfValidator.java` 표제별 불변 판독기, `ExmnBusinessController.java`의 `GET /api/v1/exmnBusinesses/{exmnBizId}/manholeReports/{pipeNumber}` 및 `/availability`, `ExmnFacilityRepository.java`의 조사유형별 첨부 존재 여부 분기. +- 맨홀 첨부 `mnhlExmnAtchFileId`와 활성 MNHL의 `gisManagementNo`만 사용한다. 선택 번호가 없거나 활성 시설이 중복되면 임의 보고서를 반환하지 않는다. 매 요청 현재 PDF를 판독하므로 이전 관로 쪽 캐시를 사용하지 않는다. DB 스키마 및 업무 데이터 변경 없음. +- FE: `exmn-business-api.ts`, `exmn-survey-result-section.tsx`, `exmn-pipe-report-popup-content.tsx`, `exmn-result-register-popup-page.tsx`. MNHL에서도 보고서 버튼을 표시하고 jobType에 따라 API·제목·번호 안내를 선택한다. PPL 기본 호출은 유지한다. +- 업로드 검토의 PDF 내부 번호 대조는 이번 범위에 추가하지 않았다. 번호 입력 기준과 열람 시 대조한다는 안내를 결과등록 화면에 표시한다. +- 산출물: 작업루트 `output/pdf/맨홀_현황조사_보고서_188001.pdf`. 사용자 원본 1쪽의 맨홀번호만 188001로 변경했으며 보고서번호·노선명 등 예시값과 사진은 원본을 유지했다. 실제 납품 조사자료가 아닌 기능 테스트용이다. +- 검증: BE compileJava --offline 성공, SN 전체 typecheck 성공, 양 저장소 diff --check 성공. 임시 JUnit 5건 성공(실제 PDF 번호/쪽 추출, PPL 표제 분리, 다중/비연속 번호 거부·연속 쪽, MNHL 첨부 선택/DB 캐시 미사용/미첨부, 없는 번호/중복 시설). PDF 재열람·텍스트 및 렌더링 육안 검증 완료. +- 미검증: 로컬 3002/8883 실행 포트가 없으므로 실제 서버 업로드·저장·지도 선택·브라우저 팝업은 수행하지 않았다. 188001 MNHL 조사시설 및 상류맨홀 GIS 도형의 실제 존재를 확인한 것으로 간주하지 않는다. +- 수동 절차: 변경 BE/FE 실행 → 188001을 가진 MNHL 조사자료의 사업에서 결과등록 → CCTV 조사결과(맨홀)에 테스트 PDF 등록·저장 → 맨홀조사 레이어의 해당 상류맨홀 선택 → 조사보고서 보기 → 제목 맨홀 조사보고서/GIS 번호188001/1쪽 확인. 관로 보고서와 미첨부·없는 번호도 회귀 확인한다. +- 롤백: 위 소스의 이번 변경과 신규 ExmnManholeReportService를 함께 복원한다. DB 롤백은 없다. 실제 첨부 등록을 수행한 경우에는 별도로 이전 첨부를 복원한다. +- 남은 리스크: 번호는 확정 맨홀번호가 아닌 조사시설 GIS 번호의 임시 계약이며, 스캔 PDF는 판독 불가하다. 맨홀 PDF는 availability/열람 때 각각 판독하므로 대형 파일 성능은 후속 검증 대상이다. 테스트 소스·원시 결과·렌더링 임시 이미지는 요약 후 정리하고 최종 PDF는 보존한다. + + +## 2026-10-19 후속: 기존 사업 맨홀 PDF 단독 교체 +- 원인: 보고서 열람을 추가했지만 결과등록 화면은 모든 검토에 Excel을 요구하고 저장에 Excel·PPL 검토 성공을 요구했다. +- 변경: 맨홀 PDF 하나만 선택하면 기존 활성 MNHL 조사시설의 GIS 번호로 PDF 번호를 대조한다. 기존 조사자료 없음·불일치·중복·잘못된 PDF는 차단한다. 선택 PDF의 일부 시설 포함은 허용한다. +- 저장 직전 다시 검토한 뒤 선택한 MNHL 첨부만 기존 attachments API로 연결한다. Excel imports/commit과 양식 생성 확인은 호출하지 않아 기존 시설·집계·관로첨부를 유지한다. Excel을 선택한 신규/전체 결과 저장 흐름은 유지한다. +- 수정: BE ExmnBusinessController(선택 mnhlFile 입력 및 Excel 없는 MNHL 검토 분기), ExmnManholeReportService(validateReplacement), FE exmn-result-register-popup-page(검토·저장 조건과 검토안내). +- 검증: BE compileJava, SN typecheck, 임시 JUnit 4건(기존번호 정상·무쓰기, 기존결과 없음, 번호 불일치/중복, 누락/손상 PDF) 통과. 원본 요구사항·지도 기준은 앞선 절과 동일하다. DB 스키마 변경 없음. +- 롤백: 이번 단독 교체 분기만 복원한다. 앞선 맨홀 보고서 열람 구현은 유지할 수 있다. + +- 실제 브라우저 확인: 기존 테스트 사업 `6fe818cb-0a7a-4379-969c-150150155f91`에서 Excel/PPL 미선택·188001 맨홀 PDF만 선택 → 검토 완료 → 저장 → 부모 사업목록 성공 안내 확인. 등록한 맨홀 PDF를 보고서 팝업에서 GIS 번호188001/1쪽으로 실제 표시했다. 지도 클릭 경로 자체는 이번 후속에서 재실행하지 않았다. +- 검토 중 기존8883 서버의 닫힌 Spring 컨텍스트로 일반 API도500 발생. 사용자 명시 승인 후 동일 실행인수·local 프로필·포트8883·context /sewerage-service로 재시작하여 정상화했다. 교체 서버 PID24204는 사용자가 이어서 테스트하도록 유지한다. 종료명령: Stop-Process -Id 24204. 실행중 필요한 classpath 사본과 stdout/stderr는 작업루트 .codex-tmp/manhole-backend-classpath.jar 및 manhole-backend.out.log/err.log에 유지한다(테스트용 임시 서버가 아닌 승인된 사용자 서버 교체). +- 부모 성공문구도 첨부 교체에 맞게 `조사결과 저장을 완료했습니다.`로 보정했다(exmn-registration-page.tsx). 실제 첨부는 변경했으며 기존 시설·집계는 Excel commit을 호출하지 않아 유지한다. 되돌릴 경우 이전 맨홀 첨부를 재등록한다. + + +## 2026-10-19 최종 정정: 실제 GIS 맨홀번호와 선택 사업 기준 +- 사용자 확정: PDF 맨홀번호는 조사자료 관로번호가 아닌 실제 GIS 맨홀번호이다. 앞선 gisManagementNo 기준 설명·구현은 이 절로 대체한다. +- 원인: 지도보기 진입은 테스트 사업이었지만 전체 사업 레이어의 겹친 시설이 선택됐다. 실제 화면은 오류 확인 테스트_수정/조사관로188003/시설관리번호188001이었다. 사용자 선택 실수가 아닌 지도 범위와 번호 계약 불일치이다. +- FE use-exmn-result-map.ts: 조사관리에서 사업·처리분구를 지정해 진입하면 해당 범위만 표출·선택한다. 일반 지도 진입은 기존 전체 범위를 유지한다. 기존 2026-10-01 전체표출 정책의 조사관리 진입 부분을 이 결정으로 대체한다. +- FE exmn-survey-result-section.tsx: MNHL 보고서 키는 fclt_mng_no/fcltMngNo, PPL은 기존 gisManagementNo. 조사 결과 표에 GIS 번호를 명시한다. 결과등록 안내도 실제 GIS 맨홀번호로 정정했다. +- BE ExmnManholeReportService: 기존 ExmnMapLayerService의 동일 지역·상류맨홀 매칭 결과 fclt_mng_no 집합으로 업로드 검토·열람 대상을 확인한다. 조사 관로번호를 맨홀번호로 대체하지 않는다. 같은 실제 맨홀에 여러 조사관로가 연결되면 번호 집합으로 처리한다. 스키마·조사시설·원천 GIS 변경 없음. +- 검증: 실제 GIS188001/조사관로188003 분리, PDF 검토·열람 성공, 조사관로 번호 직접 요청 거부 및 GIS 미연결 거부를 묶은 JUnit1건 통과. BE 컴파일·FE 타입검사·diff --check 통과. 임시 테스트 소스와 결과 정리 완료. +- 실브라우저: 테스트 사업→초이처리분구→지도3개 표시→실제188001 클릭→사업명 테스트/GIS 맨홀번호188001 확인→조사보고서 보기→1쪽 PDF와188001 표시 성공. 사용자도 최종 정상동작 확인했다. +- 실행 서버: 기존 재시작 승인 범위에서 코드 반영 후8883 PID27788로 유지한다(이전PID24204 종료). classpath/실행로그는 기존 .codex-tmp/manhole-backend-* 경로 유지. 종료명령 Stop-Process -Id 27788. +- 롤백: 위 FE3개 소스와 ExmnManholeReportService의 이번 변경을 함께 복원한다. DB 롤백 없음. 잔여 리스크는 GIS 미연결 자료 열람 불가 및 대형 사업의 지도 매칭·PDF 판독 비용이며, 임의 위치/다른 사업 fallback을 하지 않는다. diff --git a/apps/sn/docs/작업내용/SFR-010-004~SFR-010-007-조사사업/001-fe-SFR-010-004~007-조사사업-통합구현기록.md b/apps/sn/docs/작업내용/SFR-010-004~SFR-010-007-조사사업/001-fe-SFR-010-004~007-조사사업-통합구현기록.md index 60950552e..ee4b62cec 100644 --- a/apps/sn/docs/작업내용/SFR-010-004~SFR-010-007-조사사업/001-fe-SFR-010-004~007-조사사업-통합구현기록.md +++ b/apps/sn/docs/작업내용/SFR-010-004~SFR-010-007-조사사업/001-fe-SFR-010-004~007-조사사업-통합구현기록.md @@ -1004,3 +1004,44 @@ - 검증: SN 전체 타입검사 exit0, 숫자/소수/삭제 및 거부 문자열 Node 검증 통과, git diff --check 통과. 실브라우저 입력 검증은 미수행. - 수정 소스: `frontend/apps/sn/src/features/exmn/ui/exmn-business-register-popup-page.tsx`. API/백엔드 코드·저장 계약 변경 없음. 기존 GIS 작업은 변경하지 않았다. - 롤백: 해당 소스의 이번 필수 표시/입력 제한만 복원한다. + + +## 2026-10-19 맨홀 조사보고서 GIS 번호 연결 (maintenance) + +- 목적: 별도 맨홀 식별체계 확정 전 맨홀 PDF의 `맨홀번호`에 조사시설 GIS 번호(테스트 188001)를 넣고 지도에서 해당 보고서를 열람한다. 지도 도형은 기존 상류맨홀 매칭을 유지한다. +- 근거: 요구사항정의서 `3. (기능)요구사항정의 및 추적표` SFR-010-006/007, 화면설계서 실제 42쪽의 식별기준 미확정 메모 및 43쪽 `SN_MN040101` 조사결과 등록. 이번 사용자 지시로 임시 GIS 번호 연결 기준을 확정했다. +- BE: `ExmnManholeReportService.java` 신규, `ExmnPipePdfValidator.java` 표제별 불변 판독기, `ExmnBusinessController.java`의 `GET /api/v1/exmnBusinesses/{exmnBizId}/manholeReports/{pipeNumber}` 및 `/availability`, `ExmnFacilityRepository.java`의 조사유형별 첨부 존재 여부 분기. +- 맨홀 첨부 `mnhlExmnAtchFileId`와 활성 MNHL의 `gisManagementNo`만 사용한다. 선택 번호가 없거나 활성 시설이 중복되면 임의 보고서를 반환하지 않는다. 매 요청 현재 PDF를 판독하므로 이전 관로 쪽 캐시를 사용하지 않는다. DB 스키마 및 업무 데이터 변경 없음. +- FE: `exmn-business-api.ts`, `exmn-survey-result-section.tsx`, `exmn-pipe-report-popup-content.tsx`, `exmn-result-register-popup-page.tsx`. MNHL에서도 보고서 버튼을 표시하고 jobType에 따라 API·제목·번호 안내를 선택한다. PPL 기본 호출은 유지한다. +- 업로드 검토의 PDF 내부 번호 대조는 이번 범위에 추가하지 않았다. 번호 입력 기준과 열람 시 대조한다는 안내를 결과등록 화면에 표시한다. +- 산출물: 작업루트 `output/pdf/맨홀_현황조사_보고서_188001.pdf`. 사용자 원본 1쪽의 맨홀번호만 188001로 변경했으며 보고서번호·노선명 등 예시값과 사진은 원본을 유지했다. 실제 납품 조사자료가 아닌 기능 테스트용이다. +- 검증: BE compileJava --offline 성공, SN 전체 typecheck 성공, 양 저장소 diff --check 성공. 임시 JUnit 5건 성공(실제 PDF 번호/쪽 추출, PPL 표제 분리, 다중/비연속 번호 거부·연속 쪽, MNHL 첨부 선택/DB 캐시 미사용/미첨부, 없는 번호/중복 시설). PDF 재열람·텍스트 및 렌더링 육안 검증 완료. +- 미검증: 로컬 3002/8883 실행 포트가 없으므로 실제 서버 업로드·저장·지도 선택·브라우저 팝업은 수행하지 않았다. 188001 MNHL 조사시설 및 상류맨홀 GIS 도형의 실제 존재를 확인한 것으로 간주하지 않는다. +- 수동 절차: 변경 BE/FE 실행 → 188001을 가진 MNHL 조사자료의 사업에서 결과등록 → CCTV 조사결과(맨홀)에 테스트 PDF 등록·저장 → 맨홀조사 레이어의 해당 상류맨홀 선택 → 조사보고서 보기 → 제목 맨홀 조사보고서/GIS 번호188001/1쪽 확인. 관로 보고서와 미첨부·없는 번호도 회귀 확인한다. +- 롤백: 위 소스의 이번 변경과 신규 ExmnManholeReportService를 함께 복원한다. DB 롤백은 없다. 실제 첨부 등록을 수행한 경우에는 별도로 이전 첨부를 복원한다. +- 남은 리스크: 번호는 확정 맨홀번호가 아닌 조사시설 GIS 번호의 임시 계약이며, 스캔 PDF는 판독 불가하다. 맨홀 PDF는 availability/열람 때 각각 판독하므로 대형 파일 성능은 후속 검증 대상이다. 테스트 소스·원시 결과·렌더링 임시 이미지는 요약 후 정리하고 최종 PDF는 보존한다. + + +## 2026-10-19 후속: 기존 사업 맨홀 PDF 단독 교체 +- 원인: 보고서 열람을 추가했지만 결과등록 화면은 모든 검토에 Excel을 요구하고 저장에 Excel·PPL 검토 성공을 요구했다. +- 변경: 맨홀 PDF 하나만 선택하면 기존 활성 MNHL 조사시설의 GIS 번호로 PDF 번호를 대조한다. 기존 조사자료 없음·불일치·중복·잘못된 PDF는 차단한다. 선택 PDF의 일부 시설 포함은 허용한다. +- 저장 직전 다시 검토한 뒤 선택한 MNHL 첨부만 기존 attachments API로 연결한다. Excel imports/commit과 양식 생성 확인은 호출하지 않아 기존 시설·집계·관로첨부를 유지한다. Excel을 선택한 신규/전체 결과 저장 흐름은 유지한다. +- 수정: BE ExmnBusinessController(선택 mnhlFile 입력 및 Excel 없는 MNHL 검토 분기), ExmnManholeReportService(validateReplacement), FE exmn-result-register-popup-page(검토·저장 조건과 검토안내). +- 검증: BE compileJava, SN typecheck, 임시 JUnit 4건(기존번호 정상·무쓰기, 기존결과 없음, 번호 불일치/중복, 누락/손상 PDF) 통과. 원본 요구사항·지도 기준은 앞선 절과 동일하다. DB 스키마 변경 없음. +- 롤백: 이번 단독 교체 분기만 복원한다. 앞선 맨홀 보고서 열람 구현은 유지할 수 있다. + +- 실제 브라우저 확인: 기존 테스트 사업 `6fe818cb-0a7a-4379-969c-150150155f91`에서 Excel/PPL 미선택·188001 맨홀 PDF만 선택 → 검토 완료 → 저장 → 부모 사업목록 성공 안내 확인. 등록한 맨홀 PDF를 보고서 팝업에서 GIS 번호188001/1쪽으로 실제 표시했다. 지도 클릭 경로 자체는 이번 후속에서 재실행하지 않았다. +- 검토 중 기존8883 서버의 닫힌 Spring 컨텍스트로 일반 API도500 발생. 사용자 명시 승인 후 동일 실행인수·local 프로필·포트8883·context /sewerage-service로 재시작하여 정상화했다. 교체 서버 PID24204는 사용자가 이어서 테스트하도록 유지한다. 종료명령: Stop-Process -Id 24204. 실행중 필요한 classpath 사본과 stdout/stderr는 작업루트 .codex-tmp/manhole-backend-classpath.jar 및 manhole-backend.out.log/err.log에 유지한다(테스트용 임시 서버가 아닌 승인된 사용자 서버 교체). +- 부모 성공문구도 첨부 교체에 맞게 `조사결과 저장을 완료했습니다.`로 보정했다(exmn-registration-page.tsx). 실제 첨부는 변경했으며 기존 시설·집계는 Excel commit을 호출하지 않아 유지한다. 되돌릴 경우 이전 맨홀 첨부를 재등록한다. + + +## 2026-10-19 최종 정정: 실제 GIS 맨홀번호와 선택 사업 기준 +- 사용자 확정: PDF 맨홀번호는 조사자료 관로번호가 아닌 실제 GIS 맨홀번호이다. 앞선 gisManagementNo 기준 설명·구현은 이 절로 대체한다. +- 원인: 지도보기 진입은 테스트 사업이었지만 전체 사업 레이어의 겹친 시설이 선택됐다. 실제 화면은 오류 확인 테스트_수정/조사관로188003/시설관리번호188001이었다. 사용자 선택 실수가 아닌 지도 범위와 번호 계약 불일치이다. +- FE use-exmn-result-map.ts: 조사관리에서 사업·처리분구를 지정해 진입하면 해당 범위만 표출·선택한다. 일반 지도 진입은 기존 전체 범위를 유지한다. 기존 2026-10-01 전체표출 정책의 조사관리 진입 부분을 이 결정으로 대체한다. +- FE exmn-survey-result-section.tsx: MNHL 보고서 키는 fclt_mng_no/fcltMngNo, PPL은 기존 gisManagementNo. 조사 결과 표에 GIS 번호를 명시한다. 결과등록 안내도 실제 GIS 맨홀번호로 정정했다. +- BE ExmnManholeReportService: 기존 ExmnMapLayerService의 동일 지역·상류맨홀 매칭 결과 fclt_mng_no 집합으로 업로드 검토·열람 대상을 확인한다. 조사 관로번호를 맨홀번호로 대체하지 않는다. 같은 실제 맨홀에 여러 조사관로가 연결되면 번호 집합으로 처리한다. 스키마·조사시설·원천 GIS 변경 없음. +- 검증: 실제 GIS188001/조사관로188003 분리, PDF 검토·열람 성공, 조사관로 번호 직접 요청 거부 및 GIS 미연결 거부를 묶은 JUnit1건 통과. BE 컴파일·FE 타입검사·diff --check 통과. 임시 테스트 소스와 결과 정리 완료. +- 실브라우저: 테스트 사업→초이처리분구→지도3개 표시→실제188001 클릭→사업명 테스트/GIS 맨홀번호188001 확인→조사보고서 보기→1쪽 PDF와188001 표시 성공. 사용자도 최종 정상동작 확인했다. +- 실행 서버: 기존 재시작 승인 범위에서 코드 반영 후8883 PID27788로 유지한다(이전PID24204 종료). classpath/실행로그는 기존 .codex-tmp/manhole-backend-* 경로 유지. 종료명령 Stop-Process -Id 27788. +- 롤백: 위 FE3개 소스와 ExmnManholeReportService의 이번 변경을 함께 복원한다. DB 롤백 없음. 잔여 리스크는 GIS 미연결 자료 열람 불가 및 대형 사업의 지도 매칭·PDF 판독 비용이며, 임의 위치/다른 사업 fallback을 하지 않는다. diff --git a/apps/sn/src/features/exmn/api/exmn-business-api.ts b/apps/sn/src/features/exmn/api/exmn-business-api.ts index 815e3ff07..f54976c53 100644 --- a/apps/sn/src/features/exmn/api/exmn-business-api.ts +++ b/apps/sn/src/features/exmn/api/exmn-business-api.ts @@ -341,9 +341,10 @@ export const downloadExmnBusinessReport = async ( export const downloadExmnPipeReport = async ( exmnBizId: string, pipeNumber: string, + jobType: "PPL" | "MNHL" = "PPL", ): Promise => getBlobData( - `${EXMN_BUSINESS_API_PATH}/${encodeURIComponent(exmnBizId)}/pipeReports/${encodeURIComponent(pipeNumber)}`, + `${EXMN_BUSINESS_API_PATH}/${encodeURIComponent(exmnBizId)}/${jobType === "MNHL" ? "manholeReports" : "pipeReports"}/${encodeURIComponent(pipeNumber)}`, ); /** 사업 파일별 첨부서비스 키 쌍을 조사사업에 연결한다. */ @@ -351,9 +352,10 @@ export const downloadExmnPipeReport = async ( export const getExmnPipeReportAvailability = async ( exmnBizId: string, pipeNumber: string, + jobType: "PPL" | "MNHL" = "PPL", ): Promise => getData( - `${EXMN_BUSINESS_API_PATH}/${encodeURIComponent(exmnBizId)}/pipeReports/${encodeURIComponent(pipeNumber)}/availability`, + `${EXMN_BUSINESS_API_PATH}/${encodeURIComponent(exmnBizId)}/${jobType === "MNHL" ? "manholeReports" : "pipeReports"}/${encodeURIComponent(pipeNumber)}/availability`, ) as Promise; export const updateExmnAttachments = async ( diff --git a/apps/sn/src/features/exmn/ui/exmn-registration-page.tsx b/apps/sn/src/features/exmn/ui/exmn-registration-page.tsx index 52792c93b..9ff27d256 100644 --- a/apps/sn/src/features/exmn/ui/exmn-registration-page.tsx +++ b/apps/sn/src/features/exmn/ui/exmn-registration-page.tsx @@ -385,7 +385,7 @@ export function ExmnRegistrationPage() { if (refreshed) setNotice({ type: "success", - text: "조사결과 최종 저장과 PPL/MNHL 집계를 완료했습니다.", + text: "조사결과 저장을 완료했습니다.", }); }); } diff --git a/apps/sn/src/features/exmn/ui/exmn-result-register-popup-page.tsx b/apps/sn/src/features/exmn/ui/exmn-result-register-popup-page.tsx index 06c4ddd76..66dfd76c8 100644 --- a/apps/sn/src/features/exmn/ui/exmn-result-register-popup-page.tsx +++ b/apps/sn/src/features/exmn/ui/exmn-result-register-popup-page.tsx @@ -71,7 +71,7 @@ const VALIDATION_CHECK_GUIDES: Partial< }, MNHL_EXMN_PDF: { checkedItems: ["PDF 파일 형식", "조사결과 Excel 구조 및 저장 가능 여부"], - pendingNote: "PDF 내부 맨홀번호 대조는 검토 기준 협의 후 적용 예정입니다.", + pendingNote: "맨홀보고서의 맨홀번호에는 실제 GIS 맨홀번호를 입력해주세요. 보고서 열람 시 번호를 대조하며, 업로드 검토에는 PDF 내부 번호 대조가 포함되지 않습니다.", }, }; @@ -179,6 +179,9 @@ export function ExmnResultRegisterPopupPage({ const [isLoading, setIsLoading] = useState(false); const [notice, setNotice] = useState(null); + /** 맨홀 PDF 하나만 선택한 경우 기존 조사자료의 첨부 교체로 처리한다. */ + const isManholeOnly = Boolean(files.MNHL_EXMN_PDF) && Object.keys(files).length === 1; + /** popup layout 상위 html/body에서 생기는 문서 스크롤바를 팝업이 열린 동안 잠근다. */ useEffect(() => { const previousHtmlOverflow = document.documentElement.style.overflow; @@ -310,7 +313,9 @@ export function ExmnResultRegisterPopupPage({ const fileMap = Object.fromEntries( Object.entries(files).map(([type, selected]) => [type, selected?.file]), ) as Partial>; - const validationMessage = validateExmnFiles(fileMap); + const validationMessage = isManholeOnly + ? validateExmnFile("MNHL_EXMN_PDF", files.MNHL_EXMN_PDF!.file) + : validateExmnFiles(fileMap); if (validationMessage) { setFileValidationStatuses((current) => ({ ...current, @@ -373,22 +378,22 @@ export function ExmnResultRegisterPopupPage({ /** 정상 검토된 조사결과 Excel을 저장하고 부모 화면에 완료 메시지를 보낸 뒤 팝업을 닫는다. */ const handleCommit = async () => { const excelValidation = validationResults.EXMN_EXCEL; - if ( - !excelValidation?.valid || - fileValidationStatuses.EXMN_EXCEL !== "success" || - fileValidationStatuses.PPL_EXMN_PDF !== "success" - ) - return; + if (!canCommit) return; setIsLoading(true); setNotice(null); try { - if (!(await ensureTemplateReady())) return; + if (!isManholeOnly && !(await ensureTemplateReady())) return; // 로딩 화면이 저장 확인창을 가리지 않도록 사전 확인과 업로드 상태를 나눈다. setIsLoading(false); if (!(await confirm("조사결과를 저장하시겠습니까?"))) return; setIsLoading(true); + if (isManholeOnly) { + // 검토 이후 기존 조사자료가 변경됐을 수 있어 첨부 업로드 직전에 다시 대조한다. + const result = await validateExmnImport(exmnBizId, buildImportFormData(files, null, "MNHL_EXMN_PDF")); + if (!result.valid) throw new Error(result.errors.map((item) => item.message).join("\n")); + } await uploadAndLinkAttachments(); - await commitExmnImport( + if (!isManholeOnly) await commitExmnImport( exmnBizId, buildImportFormData(files, excelValidation), ); @@ -418,6 +423,11 @@ export function ExmnResultRegisterPopupPage({ (fileType) => fileValidationStatuses[fileType], ); const excelValidation = validationResults.EXMN_EXCEL; + /** 단독 첨부 교체와 신규 Excel 결과 저장의 필수 검토 조건을 분리한다. */ + const canCommit = isManholeOnly + ? fileValidationStatuses.MNHL_EXMN_PDF === "success" && Boolean(validationResults.MNHL_EXMN_PDF?.valid) + : Boolean(excelValidation?.valid) && fileValidationStatuses.EXMN_EXCEL === "success" + && fileValidationStatuses.PPL_EXMN_PDF === "success"; return ( void handleCommit()} > @@ -614,7 +621,9 @@ export function ExmnResultRegisterPopupPage({ const status = fileValidationStatuses[fileType]!; const result = validationResults[fileType]; const messages = fileValidationMessages[fileType] ?? []; - const guide = VALIDATION_CHECK_GUIDES[fileType]; + const guide = fileType === "MNHL_EXMN_PDF" && isManholeOnly + ? { checkedItems: ["PDF 맨홀번호 판독", "기존 조사자료에 연결된 실제 GIS 맨홀번호와 대조"], pendingNote: "저장 시 기존 조사자료는 유지하고 맨홀 보고서만 교체합니다." } + : VALIDATION_CHECK_GUIDES[fileType]; const countText = fileType === "EXMN_EXCEL" ? `관로 ${result?.summary.pplCount ?? 0}건 · 맨홀 ${result?.summary.mnhlCount ?? 0}건` diff --git a/apps/sn/src/features/exmn/ui/exmn-survey-result-section.tsx b/apps/sn/src/features/exmn/ui/exmn-survey-result-section.tsx index 3dded247a..4bf1b7d30 100644 --- a/apps/sn/src/features/exmn/ui/exmn-survey-result-section.tsx +++ b/apps/sn/src/features/exmn/ui/exmn-survey-result-section.tsx @@ -52,21 +52,22 @@ export default function ExmnSurveyResultSection({ const alert = useAlertStore((state) => state.setMessage); const exmnBizId = readFeatureProperty(featureData, ["exmnBizId"]); const jobType = readFeatureProperty(featureData, ["jobType"]); - const pipeNumber = readFeatureProperty(featureData, [ - "gisManagementNo", - "fclt_mng_no", - "fcltMngNo", - ]); + // 맨홀은 조사 관로번호가 아니라 지도 원천의 실제 시설관리번호로 보고서를 조회한다. + const pipeNumber = readFeatureProperty(featureData, jobType === "MNHL" + ? ["fclt_mng_no", "fcltMngNo"] + : ["gisManagementNo", "fclt_mng_no", "fcltMngNo"]); const reportAvailable = readBooleanFeatureProperty(featureData, [ "reportAvailable", ]); + /** 지도는 상류맨홀 위치를 유지하고 맨홀 보고서는 실제 GIS 맨홀번호로 연결한다. */ + const reportLabel = jobType === "MNHL" ? "맨홀" : "관로"; const canOpenPipeReport = - jobType === "PPL" && Boolean(exmnBizId && pipeNumber); + (jobType === "PPL" || jobType === "MNHL") && Boolean(exmnBizId && pipeNumber); const openPipeReport = async () => { if (!canOpenPipeReport) return; if (!reportAvailable) { - alert("등록된 관로 조사보고서가 없습니다."); + alert(`등록된 ${reportLabel} 조사보고서가 없습니다.`); return; } @@ -74,11 +75,12 @@ export default function ExmnSurveyResultSection({ const availability = await getExmnPipeReportAvailability( exmnBizId, pipeNumber, + jobType === "MNHL" ? "MNHL" : "PPL", ); if (!availability.available) { alert( availability.message?.trim() || - `선택한 관로번호의 조사보고서가 등록된 PDF에 없습니다: ${pipeNumber}`, + `선택한 GIS 번호의 조사보고서가 등록된 PDF에 없습니다: ${pipeNumber}`, ); return; } @@ -86,6 +88,7 @@ export default function ExmnSurveyResultSection({ const reportWindow = openWindowPopup("exmnPipeReport", { exmnBizId, pipeNumber, + jobType, reportNo: readFeatureProperty(featureData, ["reportNo"]), }); if (!reportWindow) { @@ -97,7 +100,7 @@ export default function ExmnSurveyResultSection({ alert( error instanceof Error && error.message ? error.message - : "관로 조사보고서 열람 가능 여부를 확인하지 못했습니다.", + : `${reportLabel} 조사보고서 열람 가능 여부를 확인하지 못했습니다.`, ); } }; @@ -107,6 +110,7 @@ export default function ExmnSurveyResultSection({ ["사업차수", readFeatureProperty(featureData, ["bizCycl"])], ["처리분구", readFeatureProperty(featureData, ["trtdstNm"])], ["조사유형", jobType === "MNHL" ? "맨홀조사" : "관로조사"], + [jobType === "MNHL" ? "GIS 맨홀번호" : "GIS 관로번호", pipeNumber], ["보고서번호", readFeatureProperty(featureData, ["reportNo"])], ["구조적결함 등급", readFeatureProperty(featureData, ["structuralGrade"])], ["운영적결함 등급", readFeatureProperty(featureData, ["operationalGrade"])], @@ -116,7 +120,7 @@ export default function ExmnSurveyResultSection({

조사 결과

- {jobType === "PPL" && ( + {(jobType === "PPL" || jobType === "MNHL") && (
) : reportUrl ? (