import 'server-only'; import { cache } from 'react'; import { getSessionAccessToken } from '@/lib/auth/dal'; import { BackendRequestError, backendFetch } from '@/lib/http/backend-fetch'; import type { CommonCode } from '@/lib/domain/common-code'; /** * 공통코드 Repository. * * ``` * GET /api/v1/mngr/code/list/{comCd} 그룹의 상세코드 목록 (ROLE_ADMIN) * ``` * * 백엔드(edupay-backend, develop)의 MngrCodeApiController / MngrCodeMapper.xml을 읽고 확인한 것: * * - 응답은 `TB_SYS_COM_CD_DTL` 행 배열이고, 쓸 값은 **`comDtlCd`(코드)와 `cdNm`(이름)** 둘이다. * - 삭제된 코드(`DEL_YN='Y'`)는 SQL이 이미 걸러 준다. * - **정렬은 백엔드가 준 순서를 그대로 쓴다.** SQL이 `ROW_NUMBER() OVER (ORDER BY FRST_REG_DT * DESC, SORT_SEQ)`를 다시 `ORDER BY RNUM DESC`로 뒤집어, 결과는 등록일 오름차순 + 정렬순서 * 내림차순이다(아이템 목록과 같은 이중 역순 패턴). 화면에서 다시 정렬하면 다른 화면과 순서가 * 어긋나므로 손대지 않는다. * * `cache()`로 감싼 이유 — 한 요청 안에서 화면(선택지 그리기)과 Server Action(입력값 검증)이 * 같은 그룹을 각각 부른다. 요청 단위로 기억해 두면 왕복이 한 번으로 줄고, 그리는 데 쓴 목록과 * 검증에 쓴 목록이 반드시 같아진다. * * 캐시: `no-store` — 코드는 자주 바뀌지 않지만 관리 화면에서 코드를 고치자마자 반영돼야 하고, * 위 `cache()`가 이미 요청 안의 중복 호출을 막는다. */ export const fetchCommonCodes = cache(async function fetchCommonCodes( groupCode: string ): Promise { const accessToken = await getSessionAccessToken(); const result = await backendFetch(`/api/v1/mngr/code/list/${groupCode}`, { method: 'GET', accessToken: accessToken ?? undefined, cache: 'no-store', }); if (!result.ok) { throw new BackendRequestError(result); } if (!Array.isArray(result.data)) { throw new Error('공통코드 목록 응답의 형식이 올바르지 않습니다.'); } return result.data.flatMap(toCommonCode); }); /** * 응답 1건 → 도메인 타입. 코드나 이름이 없는 행은 선택지로 쓸 수 없으므로 **예외 대신 건너뛴다** * — 코드 한 줄이 깨졌다고 화면 전체를 못 쓰게 만들 이유가 없다(목록 조회의 fail-fast와 다른 * 판단이다. 그쪽은 없으면 화면의 존재 이유가 사라진다). */ function toCommonCode(raw: unknown): CommonCode[] { if (raw === null || typeof raw !== 'object') { return []; } const source = raw as Record; const code = source.comDtlCd; const label = source.cdNm; if (typeof code !== 'string' || code.length === 0) { return []; } return [{ code, label: typeof label === 'string' && label ? label : code }]; }