wr: 하수재이용시설 조회 api 정리2

This commit is contained in:
2026-07-01 18:18:36 +09:00
parent 8d17623d3a
commit 01e339cc61
10 changed files with 432 additions and 97 deletions
+2 -2
View File
@@ -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
@@ -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',
+3 -2
View File
@@ -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 };
export { fetchHasuDetail, fetchpbnstDetail, detainSttn };
@@ -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(() => {
+38 -56
View File
@@ -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,
@@ -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;
};
@@ -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,
@@ -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[];
};
@@ -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(() => {
@@ -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 합계만 내려주게 정리한다.