Files
distribution_erp/src/app/api/CLAUDE.md
T
chpark 6af863199f feat: 모모유통 유통관리 ERP (Next.js 16) — MOMO 브랜딩 + distribution DB + momo.junggomoa.com
- fito-nextjs 기반으로 재구성
- 로그인: MOMO 로고 + 모모유통 + 유통관리 ERP, 하단에 본사/지사 주소 표시
- 사이드바 상단: MOMO 아이콘 + 모모유통 + 유통관리 ERP
- 파비콘: /src/app/icon.svg (MOMO 그린 배지)
- layout.tsx title: 모모유통 | 유통관리 ERP
- DB: 183.99.177.40:5432/distribution (fito 스키마 import 완료)
- Traefik: Host(momo.junggomoa.com), 컨테이너 momo-erp
2026-04-25 02:44:40 +09:00

4.3 KiB

역할

Next.js 15 App Router API 라우트. 레거시 Java Spring Controller(*.do 엔드포인트)를 Node.js로 마이그레이션한 65개 이상의 엔드포인트. PostgreSQL raw SQL로 데이터베이스 통신.

공통 패턴

인증 체크 (모든 엔드포인트 필수)

const user = await getSession();
if (!user) return NextResponse.json({ success: false }, { status: 401 });

HTTP 메서드

  • GET — 단순 조회 (메뉴, 카운트 등)
  • POST — 목록 조회 + 필터링 + 저장/수정 (기본값)
  • actionType 필드로 INSERT/UPDATE 구분: actionType === "regist" → INSERT

동적 SQL 파라미터

const conditions = ["1=1"];
const params: unknown[] = [];
let idx = 1;
if (body.field) {
  conditions.push(`TABLE.field = $${idx++}`);
  params.push(body.field);
}
// LIKE 검색: COLUMN LIKE '%' || $${idx++} || '%'

응답 형식

유형 형식
목록 조회 { RESULTLIST: [], TOTAL_CNT: number }
단순 조회 { success: true, data: rows }
저장/수정 { success: true, objId? }
메뉴 { RESULT: [] }
페이지 권한 { CREATE_AUTH_CNT, READ_AUTH_CNT, ... }
오류 { success: false, message } + HTTP 400/401/500

연결 고리

  • @/lib/sessiongetSession() (모든 엔드포인트)
  • @/lib/dbqueryRows, queryOne, execute
  • @/lib/utilscreateObjectId() (INSERT PK 생성), checkNull
  • @/lib/authverifyCredentials (login 전용)
  • @/lib/encryptencrypt (login 전용)

공통 조회 API (드롭다운/공용 데이터)

신규 메뉴에서 아래 엔드포인트 바로 사용 — 별도 매퍼 이식 불필요.

  • /api/common/code-list — 공통코드(comm_code) parent_code_id 기준. body { codeId }{ data: [{CODE_ID, CODE_NAME, OBJID}] }. 원본 common.getCodeselect 대응
  • /api/common/supply-list — 거래처/고객사(supply_mng). body { CHARGER_TYPE? }{ RESULTLIST: [{OBJID, SUPPLY_NAME, SUPPLY_CODE, STATUS}] }. 원본 common.getsupplyselect
  • /api/common/project-list — 프로젝트(project_mgmt). body { customer_cd? }{ RESULTLIST: [{OBJID, PROJECT_NO, CUSTOMER_PROJECT_NAME, LABEL}] }. 원본 common.getProjectNameList
  • /api/common/unit-list — WBS 유닛(pms_wbs_task). body { contract_objid }{ RESULTLIST: [{OBJID, UNIT_NO, TASK_NAME, UNIT_NAME}] }. 원본 common.getBomCodeList
  • /api/common/product-list — 양산제품(product_mgmt). → { RESULTLIST: [{OBJID, PRODUCT_CODE, NAME}] }. 원본 common.getProductCodeselect
  • /api/common/upg-list — 제품 UPG(product_mgmt_upg_detail). body { PRODUCT_MGMT_OBJID }{ RESULTLIST: [{CODE, NAME}] }. 원본 common.getProductUPGNEWselect
  • /api/admin/users — 사용자 목록. → { RESULTLIST: [{USER_ID, USER_NAME, ...}] }. 원본 common.getUserselect
  • /api/common/files — 첨부파일 조회. body { objId }{ success, files: [{OBJID, REAL_FILE_NAME, DOC_TYPE, ...}] }
  • /api/common/file-upload — 멀티파트 업로드. formData { file, targetObjId, docType, docTypeName }{ success }

프론트엔드에서 SearchableSelect 옵션으로 변환 패턴:

fetch("/api/common/supply-list", { method: "POST", ... })
  .then(r => r.json())
  .then(j => setOptions((j.RESULTLIST || []).map(r => ({
    value: String(r.OBJID), label: String(r.SUPPLY_NAME),
  }))));

등록 절차

새 API 추가 시 파일만 생성하면 자동 라우팅:

src/app/api/{module}/{feature}/route.ts

별도 등록 파일 없음 (Next.js App Router 자동 인식).

숨겨진 스펙

  • SQL alias 대문자: SELECT id AS "ID" (큰따옴표 필수, 없으면 PostgreSQL이 소문자 반환)
  • 삭제 플래그: COALESCE(IS_DEL, 'N') != 'Y'
  • 날짜 포맷: TO_CHAR(regdate, 'YYYY-MM-DD')
  • 기본 정렬: ORDER BY regdate DESC
  • 파일 저장 경로: {FILE_STORAGE_PATH}/{YYYYMMDD}/{createObjectId()}{ext}
  • 파일 DB 테이블: ATTACH_FILE_INFO (OBJID, TARGET_OBJID, DOC_TYPE, FILE_PATH, WRITER)
  • objId 타입: DB에 numeric/bigint, 조회 시 objid::text AS "OBJID" 문자열 변환
  • 목록 LIMIT: 일반 없음(rows.length), 대시보드 300, 공통코드 500
  • 관리자 권한 신속 처리: user.isAdmin → 모든 권한 카운트 = 1

@MISTAKES.md