From 01e339cc61c5cd91755b5c69b25a60b2e2948136 Mon Sep 17 00:00:00 2001 From: ljpark Date: Wed, 1 Jul 2026 18:18:36 +0900 Subject: [PATCH] =?UTF-8?q?wr:=20=ED=95=98=EC=88=98=EC=9E=AC=EC=9D=B4?= =?UTF-8?q?=EC=9A=A9=EC=8B=9C=EC=84=A4=20=EC=A1=B0=ED=9A=8C=20api=20?= =?UTF-8?q?=EC=A0=95=EB=A6=AC2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/wr/.env.local | 4 +- .../(popUp-layout)/splyPopup/service/map.ts | 3 +- apps/wr/app/(popUp-layout)/util.ts | 5 +- .../ruseFcltMng/fcltSttn/pbnstSttn/page.tsx | 4 +- apps/wr/app/store/map/ruseMap/index.tsx | 94 ++--- .../ruseFcltMng/fcltReg/pbnstReg/index.tsx | 3 +- .../ruseFcltMng/fcltSttn/pbnstSttn/index.tsx | 20 +- .../ruseFcltMng/fcltSttn/pbnstSttn/type.tsx | 3 - .../sttn/pbnstSttnModal/pbnstSttnModal.tsx | 22 +- .../하수재이용시설 현황 api 정리_20260701.md | 371 ++++++++++++++++++ 10 files changed, 432 insertions(+), 97 deletions(-) create mode 100644 apps/wr/docs/작업계획/하수재이용시설 현황 api 정리_20260701.md diff --git a/apps/wr/.env.local b/apps/wr/.env.local index 36faf6c47..96b66b536 100644 --- a/apps/wr/.env.local +++ b/apps/wr/.env.local @@ -13,8 +13,8 @@ NEXT_PUBLIC_CS_SERVER_API_URL = http://dev.onasys.co.kr:8000 NEXT_PUBLIC_CS_TUS_UPLOAD = /attach-service/api/v1/tus/upload NEXT_PUBLIC_GEOSERVER_URL = https://hasudo.geo.rsonesoft.com/geoserver NEXT_PUBLIC_LX_GEOSERVER_URL = http://con.mind-one.co.kr:11306/geoserver -# NEXT_PUBLIC_WR_SERVER_API_URL = http://con.mind-one.co.kr:11083/reuse-service/api/v1 -NEXT_PUBLIC_WR_SERVER_API_URL = http://localhost:8883/reuse-service/api/v1 +NEXT_PUBLIC_WR_SERVER_API_URL = http://con.mind-one.co.kr:11083/reuse-service/api/v1 +# NEXT_PUBLIC_WR_SERVER_API_URL = http://localhost:8883/reuse-service/api/v1 NEXT_PUBLIC_SEWERAGE_API_URL = http://con.mind-one.co.kr:11083/sewerage-service # 개발 서버 API # NEXT_PUBLIC_SEWERAGE_API_URL=http://localhost:8883/sewerage-service NEXT_PUBLIC_NGII_URL = https://map.ngii.go.kr diff --git a/apps/wr/app/(popUp-layout)/splyPopup/service/map.ts b/apps/wr/app/(popUp-layout)/splyPopup/service/map.ts index 11d556468..7ceaa3892 100644 --- a/apps/wr/app/(popUp-layout)/splyPopup/service/map.ts +++ b/apps/wr/app/(popUp-layout)/splyPopup/service/map.ts @@ -46,7 +46,8 @@ export const mapService = { return response.json(); }, getPbnstRuseFclts: async (ruseFcltPk:any) => { - const response = await fetch(`${DEV_URL_WR}/reuseFc/pubFc/${ruseFcltPk}`, { + const year = new Date().getFullYear(); + const response = await fetch(`${DEV_URL_WR}/reuseFc/pubFc/${ruseFcltPk}?year=${year}`, { method: 'GET', headers: { 'Accept': 'application/json', diff --git a/apps/wr/app/(popUp-layout)/util.ts b/apps/wr/app/(popUp-layout)/util.ts index e3428b300..f280aaab9 100644 --- a/apps/wr/app/(popUp-layout)/util.ts +++ b/apps/wr/app/(popUp-layout)/util.ts @@ -2,7 +2,8 @@ import { getFetch, postFectch, fetchType } from "@/components/lib/api"; const fetchpbnstDetail = async (featureCd: string, setTabData: any) => { try { - const response = await getFetch("wr", `/reuseFc/pubFc/${featureCd}`); + const year = new Date().getFullYear(); + const response = await getFetch("wr", `/reuseFc/pubFc/${featureCd}?year=${year}`); // 중복 데이터 추가 방지 setTabDataFunc(response, featureCd, setTabData); } catch (error) { @@ -55,4 +56,4 @@ const detainSttn = async (featureCd: string, layerNm: string) => { } } -export { fetchHasuDetail, fetchpbnstDetail, detainSttn }; \ No newline at end of file +export { fetchHasuDetail, fetchpbnstDetail, detainSttn }; diff --git a/apps/wr/app/(with-layout)/ruseFcltMng/fcltSttn/pbnstSttn/page.tsx b/apps/wr/app/(with-layout)/ruseFcltMng/fcltSttn/pbnstSttn/page.tsx index ecb57d726..cb03bd0ae 100644 --- a/apps/wr/app/(with-layout)/ruseFcltMng/fcltSttn/pbnstSttn/page.tsx +++ b/apps/wr/app/(with-layout)/ruseFcltMng/fcltSttn/pbnstSttn/page.tsx @@ -126,10 +126,10 @@ export default function PbnstSttn() { (index: number | undefined) => { if (index !== undefined && pbnstSttnRes && pbnstSttnRes[index]) { const pbnstRuseFcltId = pbnstSttnRes[index].pbnstRuseFcltId; - getPbnStnDetail(pbnstRuseFcltId); + getPbnStnDetail(pbnstRuseFcltId, searchYear); } }, - [getPbnStnDetail, pbnstSttnRes] + [getPbnStnDetail, pbnstSttnRes, searchYear] ); useEffect(() => { diff --git a/apps/wr/app/store/map/ruseMap/index.tsx b/apps/wr/app/store/map/ruseMap/index.tsx index bf56ea572..fa5ce06b9 100644 --- a/apps/wr/app/store/map/ruseMap/index.tsx +++ b/apps/wr/app/store/map/ruseMap/index.tsx @@ -11,6 +11,36 @@ import type { ReturnType } from "@common/business_components/ui"; import type { customPbnst, pubHasuType } from "../../ruseFcltMng/fcltReg/pbnstReg/type"; import { excelPbnstSttnParsing } from "../../ruseFcltMng/fcltSttn/pbnstSttn/excel"; +const getCurrentYear = () => String(new Date().getFullYear()); + +const sumPrfUsage = (prfList: FcPrfType["prfList"] = []) => { + return prfList.reduce( + (acc, cur) => { + acc.inUsage += + cur.wshwtr + + cur.coolUswtr + + cur.cleanUswtr + + cur.drwtUswtr + + cur.dltnUswtr + + cur.ithlRswtr + + cur.ithlEtcUswtr; + acc.outUsage += + cur.cleanToilUswtr + + cur.resn + + cur.lndwtr + + cur.hydUswtr + + cur.ugwtChr + + cur.farmUswtr + + cur.rvrKeepUswtr + + cur.indstUswtr; + acc.total = acc.inUsage + acc.outUsage; + + return acc; + }, + { total: 0, inUsage: 0, outUsage: 0 } + ); +}; + export const ruseMapSize = 15; export type ruseMapState = { ruseMap: { @@ -147,10 +177,10 @@ const hasuListFetch = async ( cur.drwtUswtr + cur.dltnUswtr + cur.ithlRswtr + - cur.ithlEtcUswtr + - cur.cleanToilUswtr + - cur.resn; + cur.ithlEtcUswtr; acc.outUsage += + cur.cleanToilUswtr + + cur.resn + cur.lndwtr + cur.hydUswtr + cur.ugwtChr + @@ -247,22 +277,12 @@ export const ruseMapActionsExport: (set: any) => ruseMapActions = ( try { const ruseDetailList = await getFetch( fetchType.WR, - `/reuseFc/pubFc/${bscFctCd}` - ); - const year = new Date().getFullYear(); - const data = [bscFctCd]; - const rusePrfList = await postFectch( - `/reuseFc/pubDetailList/${year}/0/1/10`, - data, - fetchType.WR + `/reuseFc/pubFc/${bscFctCd}?year=${getCurrentYear()}` ); const dataList = ruseDetailList.map((ruse) => { - const matchRuse = rusePrfList?.data.find( - (prf) => prf.pbnstRuseFcltId === ruse.pbnstRuseFcltId - ); return { ...ruse, - prfList: matchRuse.prfList, + prfList: ruse.prfList ?? [], }; }); console.log("/dataList ", dataList); @@ -331,51 +351,13 @@ export const ruseMapActionsExport: (set: any) => ruseMapActions = ( try { const ruseDetailList = await getFetch( fetchType.WR, - `/reuseFc/pubFc/${bscFctCd}` - ); - const year = new Date().getFullYear(); - const data = [bscFctCd]; - const rusePrfList = await postFectch( - `/reuseFc/pubDetailList/${year}/0/1/10`, - data, - fetchType.WR + `/reuseFc/pubFc/${bscFctCd}?year=${getCurrentYear()}` ); const dataList = ruseDetailList.map((ruse) => { - const matchRuse = rusePrfList?.data.find( - (prf) => prf.pbnstRuseFcltId === ruse.pbnstRuseFcltId - ); - - - const usage = matchRuse.prfList.reduce( - (acc, cur) => { - acc.inUsage += - cur.wshwtr + - cur.coolUswtr + - cur.cleanUswtr + - cur.drwtUswtr + - cur.dltnUswtr + - cur.ithlRswtr + - cur.ithlEtcUswtr + - cur.cleanToilUswtr + - cur.resn; - acc.outUsage += - cur.lndwtr + - cur.hydUswtr + - cur.ugwtChr + - cur.farmUswtr + - cur.rvrKeepUswtr + - cur.indstUswtr; - acc.total = acc.inUsage + acc.outUsage; - - return acc; - }, - { total: 0, inUsage: 0, outUsage: 0 } - ); - - + const usage = sumPrfUsage(ruse.prfList ?? []); return { ...ruse, - prfList: matchRuse.prfList, + prfList: ruse.prfList ?? [], inUsage: usage?.inUsage, outUsage: usage?.outUsage, diff --git a/apps/wr/app/store/ruseFcltMng/fcltReg/pbnstReg/index.tsx b/apps/wr/app/store/ruseFcltMng/fcltReg/pbnstReg/index.tsx index fe6b606f6..1305c0126 100644 --- a/apps/wr/app/store/ruseFcltMng/fcltReg/pbnstReg/index.tsx +++ b/apps/wr/app/store/ruseFcltMng/fcltReg/pbnstReg/index.tsx @@ -152,7 +152,8 @@ const hasuListFetch = async ( }; const initFetch = async (ruseFcltPk: string | undefined) => { - const pbnstRes = await getFetch("wr", `/reuseFc/pubFc/${ruseFcltPk}`); + const year = new Date().getFullYear(); + const pbnstRes = await getFetch("wr", `/reuseFc/pubFc/${ruseFcltPk}?year=${year}`); return pbnstRes; }; diff --git a/apps/wr/app/store/ruseFcltMng/fcltSttn/pbnstSttn/index.tsx b/apps/wr/app/store/ruseFcltMng/fcltSttn/pbnstSttn/index.tsx index 819c3a300..0aca48f71 100644 --- a/apps/wr/app/store/ruseFcltMng/fcltSttn/pbnstSttn/index.tsx +++ b/apps/wr/app/store/ruseFcltMng/fcltSttn/pbnstSttn/index.tsx @@ -57,7 +57,7 @@ type pbnstSttnActions = { searchNm: string | undefined, year: string ) => void; - getPbnStnDetail: (pbnstRuseFcltId: string) => void; + getPbnStnDetail: (pbnstRuseFcltId: string, year: string) => void; resetPbnstDetail: () => void; }; @@ -187,7 +187,6 @@ export const pbnstListFetch = async ( rprtvBzenty: item.rprtvBzenty || "-", bgngDt: item.bgngDt || "", endDt: item.endDt || "", - prfList: item.prfList ?? [], }; }); @@ -267,17 +266,18 @@ export const pbnstSttnActionsExport: (set: any) => pbnstSttnActions = ( }); }, //하수처리시설을 조회하여 하수재이용시설을 조회한다. - getPbnStnDetail: async (pbnstRuseFcltId: string) => { + getPbnStnDetail: async (pbnstRuseFcltId: string, year: string) => { try { - let pbnstDetail = await getFetch( + const pbnstDetailList = await getFetch( "wr", - `/reuseFc/pubFc/${pbnstRuseFcltId}` + `/reuseFc/pubFc/${pbnstRuseFcltId}?year=${year}` ); - if (pbnstDetail.length >= 0) { - pbnstDetail = pbnstDetail.find( - (item: customPbnst) => item.pbnstRuseFcltId === pbnstRuseFcltId - ); - } + const pbnstDetail = Array.isArray(pbnstDetailList) + ? pbnstDetailList.find( + (item: customPbnst) => item.pbnstRuseFcltId === pbnstRuseFcltId + ) + : undefined; +console.log(pbnstDetail); set((state: pbnstSttnState) => { return { ...state, diff --git a/apps/wr/app/store/ruseFcltMng/fcltSttn/pbnstSttn/type.tsx b/apps/wr/app/store/ruseFcltMng/fcltSttn/pbnstSttn/type.tsx index ee202d4f6..955ecd270 100644 --- a/apps/wr/app/store/ruseFcltMng/fcltSttn/pbnstSttn/type.tsx +++ b/apps/wr/app/store/ruseFcltMng/fcltSttn/pbnstSttn/type.tsx @@ -1,5 +1,3 @@ -import type { PrfType } from "@/app/store/prfmncReg/pbnstPrfmncReg/type"; - export type WaterReuseFacility = { swtrmtFcltId?: string; bscFctNm: string; // 기본시설명 (예: "난지") @@ -24,5 +22,4 @@ export type WaterReuseFacility = { rprtvBzenty: string; bgngDt: string; endDt: string; - prfList:PrfType[]; }; diff --git a/apps/wr/components/ui/modal/sttn/pbnstSttnModal/pbnstSttnModal.tsx b/apps/wr/components/ui/modal/sttn/pbnstSttnModal/pbnstSttnModal.tsx index b7d9bab51..fe69e0b4c 100644 --- a/apps/wr/components/ui/modal/sttn/pbnstSttnModal/pbnstSttnModal.tsx +++ b/apps/wr/components/ui/modal/sttn/pbnstSttnModal/pbnstSttnModal.tsx @@ -228,26 +228,8 @@ export function PbnstSttnModal({ data, isMap }: { data: customPbnst; isMap?: boo } }; useEffect(() => { - if(isMap){ - if(data.prfList &&data.prfList.length>0){ - getTablePrfData(data.prfList); - }else{ - getTablePrfData([]); - } - }else{ - - if (pbnstSttn.pbnstSttnRes && data) { - const prfList = pbnstSttn.pbnstSttnRes.find( - (item) => item.pbnstRuseFcltId === data.pbnstRuseFcltId - )?.prfList; - - if (prfList) { - - getTablePrfData(prfList); - } - } - } - }, [pbnstSttn.pbnstSttnRes, data,isMap]); + getTablePrfData(data.prfList ?? []); + }, [data]); useEffect(() => { diff --git a/apps/wr/docs/작업계획/하수재이용시설 현황 api 정리_20260701.md b/apps/wr/docs/작업계획/하수재이용시설 현황 api 정리_20260701.md new file mode 100644 index 000000000..b3a32c7d3 --- /dev/null +++ b/apps/wr/docs/작업계획/하수재이용시설 현황 api 정리_20260701.md @@ -0,0 +1,371 @@ +# 하수재이용시설 현황 API 정리 작업계획 + +작성일: 2026-07-01 + +## 1. 배경 + +현재 하수재이용시설 현황/상세 조회는 백엔드와 프런트 모두에서 응답 구조가 과도하게 분산되어 있다. + +- 상세 조회는 `GET /reuseFc/pubFc/{pbnstRuseFcltId}` 호출 후, 다시 `POST /reuseFc/pubDetailList/...`를 추가 호출해서 실적을 붙이고 있다. +- 현황 목록은 `GET /reuseFc/pbnstList/...` 호출 시 월별 실적 내역 `prfList`까지 함께 조회한 뒤, 서비스 레이어에서 장내/장외/총합계를 계산하고 있다. +- 프런트도 위 구조에 맞춰 상세 모달과 현황 화면에서 중복 후처리를 수행한다. + +이번 작업의 목표는 아래 2가지다. + +1. `GET /reuseFc/pubFc/{pbnstRuseFcltId}` 단일 호출만으로 상세 모달에 필요한 데이터가 모두 내려오도록 정리 +2. `GET /reuseFc/pbnstList/...`는 목록에 필요한 합계만 SQL에서 계산해 내려주고, 월별 실적 내역 `prfList`는 제외 + +추가 전제: + +- 본 작업은 backend 정리 후 이에 맞춰 `frontend`도 함께 수정한다. + +## 2. 현재 구조 분석 + +### 2.1 상세 조회 API + +현재 백엔드 상세 조회 엔드포인트: + +- `GET /reuseFc/pubFc/{pbnstRuseFcltId}` + +현재 SQL/매핑: + +- `public_fc_select.xml`의 `getFc` +- `PublicFcInsertResultMap` + +현재 `getFc`는 아래 성격의 데이터만 내려준다. + +- 기본 시설 정보 +- 파일 정보 +- 사업 정보 +- 운영 정보 +- 원수 취수 정보 +- 공급관로 정보 +- 수요처 정보 + +현재 `prfList`는 포함하지 않는다. + +### 2.2 상세 실적 추가 조회 API + +현재 추가 호출 엔드포인트: + +- `POST /reuseFc/pubDetailList/{year}/{sggCd}/{pageNo}/{pageCnt}` + +현재 역할: + +- 시설 목록 기준으로 `prfList`를 포함한 실적 정보를 별도 조회 +- 프런트에서 `pubFc/{id}` 결과와 `pubDetailList` 결과를 다시 매핑 + +### 2.3 상세 모달 사용 위치 분석 + +실제 상세 모달 렌더링 위치는 아래 2곳이다. + +- `apps/wr/components/ui/map/detailPopup/ruseDetailPopup.tsx` +- `apps/wr/components/ui/modal/sttn/pbnstSttnModal/pbnstSttnModal.tsx` + +추가 영향 지점은 아래와 같다. + +- `apps/wr/app/store/map/ruseMap/index.tsx` + - `getRuseMaplList()` + - `getRuseMaplListReturn()` + - 현재 `pubFc/{id}` 호출 후 `pubDetailList`를 다시 호출해 `prfList`를 수동 병합 + +- `apps/wr/app/store/ruseFcltMng/fcltSttn/pbnstSttn/index.tsx` + - `getPbnStnDetail()` + - 현재는 `pubFc/{id}`만 조회하지만, 모달 내부는 상황에 따라 목록의 `prfList`를 참조 + +정리하면, 상세 모달 렌더링 지점은 2곳이 맞고, 실제 수정 범위는 스토어까지 확장된다. + +### 2.4 현황 목록 API + +현재 목록 엔드포인트: + +- `GET /reuseFc/pbnstList/{year}/{pageNo}/{pageCnt}/{searchingCd}/{nameCd}/{fcltCpct}/{oprtngSttsCd}/{bscFctClsCd}` + +현재 구조: + +- SQL `getPbnstList`에서 `tb_wr_pbnst_ruse_fclt_prfmnc`를 조인 +- `PublicPrfListMap`으로 월별 `prfList`까지 전부 매핑 +- `PublicFcService.getPbnstList()`에서 `sumUsage()`로 장내/장외/총합 계산 +- 응답 객체 `PublicPbnstList`에 `prfList`도 포함 + +문제점: + +- 목록 조회인데 월별 실적 상세까지 매번 로딩 +- 페이지 목록 응답에 비해 payload가 과다 +- 서비스 레이어에서 반복 합계 계산 수행 +- 프런트는 목록 화면에서 합계만 사용하지만 상세 내역까지 수신 + +## 3. 목표 구조 + +### 3.1 상세 조회 목표 + +`GET /reuseFc/pubFc/{pbnstRuseFcltId}` 한 번으로 아래가 모두 내려오도록 변경한다. + +- 기본 시설 정보 +- 파일/사업/운영/원수취수/공급관로/수요처 정보 +- 해당 시설의 하수재이용실적 `prfList` +- 필요 시 장내/장외/총합도 함께 포함 여부 검토 + +프런트는 상세 모달 진입 시 더 이상 `POST /reuseFc/pubDetailList/...`를 호출하지 않도록 변경한다. + +### 3.2 목록 조회 목표 + +`GET /reuseFc/pbnstList/...`는 아래만 내려오도록 정리한다. + +- 시설 기본 정보 +- 운영정보 1건 +- 장내 합계 +- 장외 합계 +- 총합계 + +제외 대상: + +- 월별 실적 상세 `prfList` + +합계는 SQL에서 계산한다. + +## 4. 백엔드 작업계획 + +### 4.1 `GET /reuseFc/pubFc/{pbnstRuseFcltId}` 응답 확장 + +대상 파일: + +- `wr_back_out/src/main/resources/postgres/mapper/wr/public_fc_select.xml` +- `wr_back_out/src/main/java/kr/co/mindone/wrout/object/publicFc/PublicFcInsert.java` 대신 PublicFcSelect.java 로 신규 object생성 +- `wr_back_out/src/main/java/kr/co/mindone/wrout/controller/PublicFcController.java` +- `wr_back_out/src/main/java/kr/co/mindone/wrout/service/public_fc/PublicFcService.java` + +계획: + +1. `getFc` SQL에 `tb_wr_pbnst_ruse_fclt_prfmnc` 조인 추가 +2. `year` query param을 추가하고, 해당 연도 실적만 조회하도록 수정 +3. frontend에서 상세 조회 시 현재년도를 세팅해서 호출 + +적용안: + +- `GET /reuseFc/pubFc/{pbnstRuseFcltId}?year=2026` +- frontend는 상세 모달 진입 시 `new Date().getFullYear()` 기준 현재년도를 세팅해서 호출 +- backend는 전달받은 `year` 기준으로 `prfList`를 조회 + +이유: + +- 상세 모달은 현재 연도 기준 실적을 보는 흐름이 명확하다 +- 전체 연도 실적을 무제한 포함하면 payload가 다시 커진다 +- 실적 조회 기준 연도가 API 계약에 명시되어야 응답이 예측 가능하다 + +추가 검토: + +- 현재 `getFc`는 `like #{fcltPk}||'%'`라서 같은 하수처리시설 하위 재이용시설 여러 건을 반환하는 구조다. +- 호출명이 `/{pbnstRuseFcltId}` 이므로 실제 의도가 + - 특정 재이용시설 1건 조회인지 + - 기반 시설코드(prefix) 하위 재이용시설 다건 조회인지 + 를 명확히 해야 한다. + +현 프런트 사용상 분석: + +- `ruseDetailPopup.tsx`는 탭으로 여러 재이용시설을 보여주므로 prefix 기반 다건 조회 구조를 활용 중이다. +- `getPbnStnDetail()`는 정확한 `pbnstRuseFcltId`로 단건 선택 후 화면에서 1건 사용한다. + +따라서 이번 변경에서는 기존 다건 반환 계약은 유지하되, 반환 각 항목에 `prfList`를 포함하는 쪽이 안전하다. + +### 4.2 `pubDetailList` 영향 검토 + +현재 `pubDetailList`는 아래에서 사용된다. + +- `apps/wr/app/store/map/ruseMap/index.tsx` + - `hasuListFetch()` + - `getRuseMaplList()` + - `getRuseMaplListReturn()` +- `packages/common/business_components/src/ui/map/searchBar/searchBar.tsx` + - 경로 문자열 참조 1건 +- 과거 실적등록 관련 코드/문서 + +계획: + +- 이번 작업에서 상세 모달 경로는 `pubFc/{id}` 단일 조회로 전환 +- `pubDetailList`는 잔여 런타임 사용처가 확인되었으므로 즉시 제거하지 않는다 +- 1차로 상세 모달 경로에서 제거하고, `ruseMap` 목록/지도 경로는 영향도 확인 후 후속 정리한다 +- 작업계획 기준으로는 "상세 모달에서는 더 이상 사용하지 않음"까지를 1차 목표로 둔다 + +### 4.3 `GET /reuseFc/pbnstList/...` SQL 합계화 + +대상 파일: + +- `public_fc_select.xml` +- `PublicPbnstList.java` +- `PublicPrfListMap` 및 관련 DTO +- `PublicFcService.getPbnstList()` + +계획: + +1. `getPbnstList` SQL에서 `prfList` 월별 내역 컬럼 제거 +2. 연도 조건이 반영된 실적 집계 서브쿼리 또는 집계 조인 추가 +3. SQL에서 아래 컬럼 직접 계산 + - `in_usage` + - `out_usage` + - `usage_total` +4. 서비스 레이어 `sumUsage()` 기반 계산 제거 +5. `PublicPbnstList`에서 `prfList` 제거 + +- `pbnstList`는 현황 목록 용도 +- `pubFc/{id}`는 상세 용도 +- 응답 목적이 달라 DTO를 분리하는 편이 유지보수에 유리 + +### 4.4 서비스 레이어 정리 + +`PublicFcService.getPbnstList()`에서는 아래를 제거할 수 있다. + +- `item.setPrfList(detail.getPrfList())` +- `sumUsage(...)` 연쇄 호출 + +남는 역할: + +- SQL 결과를 DTO로 변환 +- 운영정보 1건 정리 +- 기본 문자열 보정 + +## 5. 프런트 작업계획 + +### 5.1 상세 모달 경로 정리 + +대상 파일: + +- `apps/wr/components/ui/map/detailPopup/ruseDetailPopup.tsx` +- `apps/wr/components/ui/modal/sttn/pbnstSttnModal/pbnstSttnModal.tsx` +- `apps/wr/app/store/map/ruseMap/index.tsx` +- `apps/wr/app/store/ruseFcltMng/fcltSttn/pbnstSttn/index.tsx` +- `apps/wr/app/store/ruseFcltMng/fcltReg/pbnstReg/type.tsx` + +계획: + +1. `ruseMap.getRuseMaplList()`에서 `pubDetailList` 추가 호출 제거 +2. `pubFc/{id}` 응답만으로 `ruseDetailList` 구성 +3. `PbnstSttnModal`이 `isMap` 여부에 따라 목록 `prfList`를 다시 참조하는 구조 단순화 +4. 상세 모달 공통 데이터 타입에 `prfList`가 항상 포함되도록 정리 +5. 상세 조회 호출 시 frontend에서 현재년도를 query param으로 전달 + +목표: + +- 상세 모달 렌더링 시 실적 데이터 원천을 `pubFc/{id}` 하나로 통일 + +### 5.2 현황 목록 경로 정리 + +대상 파일: + +- `apps/wr/app/store/ruseFcltMng/fcltSttn/pbnstSttn/index.tsx` +- 관련 화면: + - `apps/wr/app/(with-layout)/ruseFcltMng/fcltSttn/pbnstSttn/page.tsx` + +계획: + +1. `pbnstListFetch()`가 `pbnstList` 응답의 `inUsage/outUsage/usageTotal`만 사용하도록 정리 +2. 목록 응답에서 `prfList`를 기대하는 코드 제거 +3. 상세 버튼 클릭 시 목록 데이터가 아니라 상세 API 응답을 기준으로 모달 표시 + +## 6. 데이터 구조 변경 계획 + +### 6.1 상세 응답 + +현재 후보: + +- `PublicFcInsert` 확장 +- 또는 `PublicFcDetailWithPrf` 신규 DTO 추가 + +권장: + +- 신규 상세 DTO 분리 검토 + +이유: + +- 등록/수정용 DTO와 조회용 DTO 성격이 다름 +- 상세 조회에 `prfList`, 합계 필드, 화면 전용 값이 추가될 가능성이 높음 + +### 6.2 목록 응답 + +현재 `PublicPbnstList`는 `prfList`를 포함한다. + +변경 방향: + +- 목록 DTO에서는 `prfList` 제거 +- 합계 숫자 필드만 유지 + +프런트 타입도 이에 맞춰 변경: + +- `WaterReuseFacility` +- `customPbnst` +- `FcPrfType` 사용 위치 재점검 + +## 7. 구현 순서 + +1. `pubFc/{id}` 응답에 실적 포함 설계 확정 +2. 상세 모달 사용처 2곳 및 스토어를 `pubFc/{id}` 단일 호출 구조로 전환 +3. `pbnstList` SQL을 합계형 목록 조회로 변경 +4. 목록 DTO 및 프런트 타입 정리 +5. 잔여 `pubDetailList` 사용처 영향 확인 +6. 화면 수동 검증 + +## 8. 검증 계획 + +### 8.1 상세 모달 검증 + +- 지도 팝업 상세 + - `ruseDetailPopup.tsx` +- 현황 목록 상세 모달 + - `pbnstSttnModal.tsx` + +확인 항목: + +- `pubFc/{id}` 1회 호출만 발생하는지 +- 추가 `pubDetailList` 호출이 사라졌는지 +- 실적 탭의 장내/장외/합계 표가 기존과 동일하게 보이는지 + +### 8.2 목록 검증 + +- `pbnstList` 응답에서 `prfList`가 제거되었는지 +- `inUsage`, `outUsage`, `usageTotal` 합계가 기존과 동일한지 +- 검색/페이징/시도시군구 조건에서 정상 조회되는지 + +## 9. 리스크 및 확인 필요사항 + +### 9.1 `pubFc/{id}`의 조회 단위 + +현재 `getFc` SQL은 prefix 조회다. + +- `WHERE pf.pbnst_ruse_fclt_id like #{fcltPk} || '%'` + +이 계약을 유지할지, +정확한 ID 단건 조회로 바꿀지 먼저 결정이 필요하다. + +현 프런트와의 호환성을 고려하면 1차는 prefix 다건 조회 유지가 안전하다. + +### 9.2 실적 연도 파라미터 + +상세 응답에 실적을 포함할 때 연도 조건은 필요하다. + +이번 계획에서는 아래처럼 확정한다. + +- `getFc` 조회 시 `year` query param을 받는다 +- frontend는 상세 실적 조회 시 현재년도를 세팅해서 호출한다 +- backend는 해당 연도 실적만 `prfList`에 포함한다 + +### 9.3 `pubDetailList` 잔여 사용처 + +상세 모달 외에도 아래 스토어에서 여전히 사용 중이다. + +- `apps/wr/app/store/map/ruseMap/index.tsx` + +이번 작업에서 상세 경로는 정리하되, 다른 화면의 잔여 사용 여부는 후속 정리 대상으로 남을 수 있다. + +## 10. 결론 + +이번 작업은 단순 API 이름 정리가 아니라, + +- 상세 조회는 `pubFc/{id}` 중심 +- 목록 조회는 `pbnstList` 중심 + +으로 응답 책임을 다시 나누는 작업이다. + +핵심은 아래 두 줄로 요약된다. + +1. 상세 모달은 `GET /reuseFc/pubFc/{pbnstRuseFcltId}` 한 번만 호출해도 되게 만든다. +2. 목록 API `GET /reuseFc/pbnstList/...`는 월별 내역을 빼고 SQL 합계만 내려주게 정리한다.