Files
wace_rps/docs/migration/development/01-part.md
T
hjjeong ea6606da0c 개발관리>PART 등록·조회 메뉴 신설 (PR-A) — wace partMng 1:1 이식
backend (M1+M2):
- devPartService: listTemp/listRelease/getByObjid/create/update/deploy/removeMany
- partMngBaseSimple SELECT + 추가 15컬럼(acctfg/odrfg/unit_dc/unitmang_dc/lot_fg 등) 라벨/CASE
- deploy 트랜잭션 3단계 (isLastInit → part_mng_history INSERT → partMngDeploy + EO_NO 채번)
- EO_NO 분기: is_longd='1'→EOB{yy}-{seq} / else EO{yy}-{seq}
- objidUtil: wace CommonUtils.createObjId() 1:1 (bigint objid 채번)
- DDL: 9 신규 테이블 + part_mng 15컬럼 ALTER (운영판 1:1 추출)

frontend (M1+M2):
- part-regist (M1) / part-search (M2): 23셀 그리드 + 검색폼 + 액션
- PartFormDialog: 등록/수정 통합 (mode prop, 4 섹션)
- PartDetailDialog: 읽기 전용 + "수정" dispatch
- AdminPageRenderer dynamic 임포트 2건 + menu_info URL spec 정렬

본 PR 제외 (별 PR): 도면 다중 업로드, ERP 업로드, Excel Import, BOM_PART_QTY R/W

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 16:14:10 +09:00

12 KiB

PR-A : PART 등록·조회 묶음 구현 명세

작성: 2026-05-12 / 범위: 개발관리 M1(PART 등록) + M2(PART 조회) — 같은 part_mng 테이블 R/W, 매퍼 공유.


1. 매퍼 쿼리 1:1 매핑

원본 wace_plm/src/com/pms/mapper/partMng.xml:

Query id Line 본 PR 매핑 용도
partMngBaseSimple (sql) 78 (서비스 측 공통 SELECT fragment) PART_MNG 메인 88+ 컬럼 SELECT
partMngTempGridList 2,354 GET /api/development/part-temp/list M1 그리드 (status != 'release') + ORDER_SPEC_MNG·ADMIN_SUPPLY_MNG JOIN
partMngGridList 1,903 GET /api/development/part/list M2 그리드 (status = 'release' 고정)
partMngInfo 2,699 GET /api/development/part/:objid 상세 단건 (편집 팝업 진입)
insertpartInfo 7,625 POST /api/development/part 신규 등록 (38 컬럼 INSERT)
updatePartDetail 2,711 PUT /api/development/part/:objid 상세 수정 (21 컬럼 UPDATE)
partMngDeploy 4,190 POST /api/development/part-temp/deploy 확정 (M1→M2) STATUS='release', EO_NO 채번
partMngIsLastInit 4,230 (deploy 트랜잭션 내부) 동일 PART_NO 이전 IS_LAST='0'
insertPartMngHistory 4,244 (deploy 트랜잭션 내부) PART_MNG_HISTORY 이력 INSERT
partMngDelete 4,486 DELETE /api/development/part (body: objids: string[]) 다중 삭제

partMngBaseSimple SELECT 핵심: PART_MNG P + COMM_CODE CC_UNIT(UNIT) + COMM_CODE CC_PART(PART_TYPE) + admin_supply_mng SUP(SUPPLY_CODE) + LATERAL BOM_PART_QTY(LAST_PART_OBJID·status='deploy'·최신 1행) + LATERAL COMM_CODE(CHANGE_OPTION 다중 라벨) + ATTACH_FILE_INFO(3D/2D/PDF 파일 카운트). 23개 그리드 컬럼 + CODE_NAME 라벨 + Y/N flag CASE 변환 자체 처리.


2. API 엔드포인트 명세

2.1 M1 그리드 — GET /api/development/part-temp/list

Query:

search_part_no?: string
search_part_name?: string
search_material?: string
search_spec?: string
search_part_type?: string  (PART_TYPE_CODE comm_code id)
writer?: string
status?: string             // 단일: 'create'/'changing'/'editing'
status_arr?: string[]       // 다중 (둘 중 하나만 사용)
product_code?: string
upg_no?: string
page?: number               // 기본 1
page_size?: number          // 기본 20

SQL (요약):

SELECT T.*, SORT (REVISION), O.PARTNER_TITLE, Q.OBJID/CHILD_OBJID/QTY/QTY_TEMP, Q_QTY (CASE), 
       (SELECT PART_NO FROM PART_MNG SP WHERE SP.OBJID = Q.PARENT_PART_NO) PARENT_PART_INFO
FROM <partMngBaseSimple> T
LEFT JOIN (ORDER_SPEC_MNG OSM JOIN ADMIN_SUPPLY_MNG SUP) O ON T.OBJID::VARCHAR = O.PART_OBJID::VARCHAR
LEFT JOIN BOM_PART_QTY Q ON (
    T.OBJID IN (SELECT DISTINCT PM1.OBJID FROM PART_MNG PM1, PART_MNG PM2
                WHERE PM1.STATUS='changing' AND PM2.STATUS!='changing'
                  AND PM2.OBJID = Q.PART_NO AND PM1.PART_NO = PM2.PART_NO)
    AND Q.STATUS = 'beforeEdit'
)
WHERE 1=1 + 동적 (SEARCH_PART_NO/NAME/MATERIAL/SPEC/PART_TYPE, WRITER, STATUS, STATUS_ARR)
ORDER BY PARENT_PART_INFO, T.PART_NO

Response:

{
  rows: PartTempRow[];   // 그리드 23셀 + 추가 필드
  total: number;
  page: number;
  pageSize: number;
}

2.2 M2 그리드 — GET /api/development/part/list

Query: 위 + search_year? search_hardness? search_method? search_surface? customer_objid? customer_cd? project_name? unit_code? search_design_date_from? search_design_date_to? is_last? eo?

SQL (요약):

SELECT NUM (ROW_NUMBER), T.*,
       DECODE(PART_TYPE, '0000063', '1',
              (SELECT SUM(...) FROM BOM_PART_QTY Q WHERE Q.LAST_PART_OBJID=T.OBJID)::CHARACTER) BOM_QTY
FROM <partMngBaseSimple> T
WHERE 1=1 AND T.status='release'   -- M1 vs M2 핵심 차이
  + 동적 (M1 검색 필드 + 추가 5)

Response: { rows: PartRow[]; total, page, pageSize }

2.3 단건 상세 — GET /api/development/part/:objid

SELECT T.* FROM <partMngBaseSimple> T WHERE T.OBJID = #{OBJID}

PartRow 단일 반환. 404 시 { error: 'not_found' }.

2.4 신규 등록 — POST /api/development/part

Body (38 컬럼, 핵심):

{
  part_objid: string;     // numeric, 클라이언트 채번 (nanoid-based) 또는 서버 채번
  part_no: string;
  part_name: string;
  unit?: string;          // comm_code
  qty?: string;
  spec?: string;
  material?: string;
  thickness?: string; width?: string; height?: string;
  out_diameter?: string; in_diameter?: string; length?: string;
  remark?: string;
  part_type: string;      // comm_code (PART_TYPE_CODE)
  product_mgmt_objid?: string;
  supply_code?: string;
  maker?: string;
  contract_objid?: string;
  post_processing?: string;
  heat_treatment_hardness?: string;
  heat_treatment_method?: string;
  surface_treatment?: string;
  acctfg?: string;        // comm_code 계정구분
  odrfg?: string;         // 0=구매/1=생산/8=Phantom
  unit_dc?: string; unitmang_dc?: string;
  unitchng_nb?: string;
  lot_fg?: '0'|'1';
  use_yn?: '0'|'1';
  qc_fg?: '0'|'1';
  setitem_fg?: '0'|'1';
  req_fg?: '0'|'1';
  unit_length?: string;
  unit_qty?: string;
}

SQL: insertpartInfo (7,625) 그대로. STATUS='create', REG_DATE=now(), IS_LAST='1', WRITER=#{CONNECTUSERID} (서버에서 req.user.user_id 주입).

채번 정책: part_mng.objidbigint 타입(다른 영업관리 테이블 contract_mgmt.objid 등은 varchar — genObjid("CM") 패턴 사용). bigint 컬럼은 prefix-string 못 쓰므로 wace CommonUtils.createObjId() 1:1 구현 사용:

// backend-node/src/utils/objidUtil.ts (신규)
import { randomUUID } from 'crypto';

function javaStringHashCode(s: string): number {
  let h = 0;
  for (let i = 0; i < s.length; i++) h = (Math.imul(31, h) + s.charCodeAt(i)) | 0;
  return h;
}

/** wace CommonUtils.createObjId() 1:1 — UUID v4 → 하이픈 제거(32 hex) → Java String.hashCode (int32) → String. 결과: -2,147,483,648 ~ 2,147,483,647. */
export function createObjId(): string {
  return String(javaStringHashCode(randomUUID().replaceAll('-', '')));
}

INSERT 시 body.part_objid 가 비어 있으면 서버에서 createObjId() 호출(클라이언트 채번도 허용하되 권장 X).

2.5 상세 수정 — PUT /api/development/part/:objid

Body (21 컬럼, updatePartDetail 1:1): part_name, material, heat_treatment_hardness, heat_treatment_method, surface_treatment, maker, part_type, acctfg, odrfg, spec, unit_dc, unitmang_dc, unitchng_nb, lot_fg, use_yn, qc_fg, setitem_fg, req_fg, unit_length, unit_qty, remark

EDIT_DATE = NOW() 자동.

2.6 확정 — POST /api/development/part-temp/deploy

Body: { objids: string[] } — 다중 선택 확정.

트랜잭션 (각 objid에 대해 순차 처리):

  1. partMngIsLastInit: 같은 PART_NO 모든 행 IS_LAST='0'
  2. insertPartMngHistory: 현재 행을 PART_MNG_HISTORY로 복사 (이력 보존)
  3. partMngDeploy: 본 행 IS_LAST='1', STATUS='release', DEPLOY_DATE=NOW(), REVISION=COALESCE(REVISION,'RE'), EO_DATE=..., EO_NO= 채번 (IS_LONGD에 따라 EOB{yy}-{seq} or EO{yy}-{seq})

EO_NO 채번 SQL (wace 운영판 그대로):

CASE WHEN P.IS_LONGD = '1' THEN
       'EOB' || TO_CHAR(NOW(),'yy') || '-' || LPAD(
         (SELECT COALESCE(SUBSTR(MAX(EO_NO),7,8)::INTEGER+1, 1)
          FROM PART_MNG SP
          WHERE SP.EO_NO LIKE 'EOB' || TO_CHAR(NOW(),'yy') || '-%'
            AND SP.PART_NO != P.PART_NO
            AND SP.REVISION != P.REVISION
         )||'', 4, '0')
     ELSE
       'EO' || TO_CHAR(NOW(),'yy') || '-' || LPAD(... 'EO{yy}-{seq}' ...)
END

Response: { deployed: number, eo_nos: Record<objid, eo_no> }

2.7 다중 삭제 — DELETE /api/development/part

Body: { objids: string[] }

SQL (wace 그대로 POSITION 트릭):

DELETE FROM PART_MNG WHERE POSITION(OBJID||',' IN #{checkArr}||',') > 0

→ backend-node에서는 PostgreSQL 표준인 WHERE OBJID = ANY($1::numeric[]) 로 정리(동일 효과 + 인덱스 활용 가능).


3. Backend 파일 구조

backend-node/src/
  routes/
    devPartRoutes.ts          // Express Router — 7 endpoint
  controllers/
    devPartController.ts      // req/res 처리, validation
  services/
    devPartService.ts         // SQL 실행 (pg 트랜잭션 처리 포함)
    devPartSqlFragments.ts    // partMngBaseSimple SELECT fragment 재사용

app.tsapp.use('/api/development', devPartRoutes) 추가 (또는 메뉴 묶음 라우터 도입 시 그쪽).


4. Frontend 파일 구조

frontend/
  app/(main)/COMPANY_16/development/
    part-regist/
      page.tsx                // M1 그리드 + 상단 액션 + 페이징
    part-search/
      page.tsx                // M2 그리드 + 상단 액션 + 페이징
  components/development/
    PartFormDialog.tsx        // 신규/수정 통합 (mode prop)
    PartDetailDialog.tsx      // 읽기 전용 상세
  lib/api/
    devPart.ts                // 7 endpoint 호출 함수 + 타입

4.1 그리드 23셀 (M1·M2 공통)

key 라벨 정렬 너비
part_no 품번 left 140
part_name 품명 left 220
cu01_cnt 3D right 60
cu02_cnt 2D right 60
cu03_cnt PDF right 60
material 재료 left 100
heat_treatment_hardness 열처리경도 left 110
heat_treatment_method 열처리방법 left 110
surface_treatment 표면처리 left 100
maker 메이커 left 100
part_type_title 범주이름 left 100
spec 규격 left 140
acctfg_nm 계정구분 center 80
odrfg_nm 조달구분 center 80
unit_dc_nm 재고단위 center 80
unitmang_dc_nm 관리단위 center 80
unitchng_nb 환산수량 right 90
lot_fg_nm LOT구분 center 80
use_yn_nm 사용여부 center 80
qc_fg_nm 검사여부 center 80
setitem_fg_nm SET품여부 center 90
req_fg_nm 의뢰여부 center 80
unit_length / unit_qty 개당길이/수량 right 100

추가 (M1만): partner_title, q_qty, parent_part_info 추가 (M2만): bom_qty

4.2 검색 폼

M1 (PART 등록) — 2 필드: SEARCH_PART_NO · SEARCH_PART_NAME (둘 다 PartSelect autocomplete) M2 (PART 조회) — 메인 조회 화면 (별도 검색 폼 없음, 그리드 헤더 inline 필터로 처리하거나 상단 간소화 검색바 1줄로 통합 — 본 PR 우선 <Input> 2개로 시작, 추후 보강)

4.3 액션 버튼 (각 page 상단)

M1: 등록 · 수정 · 삭제 · 확정 · 조회 M2: 등록 · 수정 · 삭제 · 조회 (도면연동/ERP업로드/Excel은 본 PR 제외)

4.4 PartFormDialog (신규/수정 통합)

  • mode: 'create' | 'edit'
  • 38 필드 — <Input> + <CommCodeSelect> 조합
  • 검증: part_no/part_name 필수, comm_code 필드는 SmartSelect
  • 신규: POST → 신규 행 추가
  • 수정: PUT → 21 필드만 전송 (insertpartInfo는 38, updatePartDetail는 21 — wace 그대로)

4.5 PartDetailDialog (읽기 전용)

행 더블클릭 시 진입. 모든 필드 disabled. "수정" 버튼 → PartFormDialog(mode='edit') 전환.


5. 본 PR 제외 항목

항목 사유 / 후속
도면 다중 업로드 (M1) ATTACH_FILE_INFO 다파일 업로드 — 별 PR
ERP 업로드 (M2) wace 외부 시스템 연동 — 별 PR
Excel Upload (M1·M2) openPartExcelImportPopUp.jsp 별도 — 별 PR
BOM_PART_QTY R/W (M3 영역) PR-B 에서 다룸
EO_NO 채번 분기 일부 (IS_LONGD flag) 본 PR 포함 — 운영판 그대로

6. 검증 시나리오 (verify.md 기준)

  1. M1 페이지 진입 → 그리드 표시(status != 'release') 확인
  2. "등록" → PartFormDialog 신규 → POST → M1 그리드에 새 행
  3. M1 행 선택 → "확정" → POST deploy → STATUS='release', EO_NO 채번 확인
  4. M2 페이지 진입 → deploy된 행이 M2 그리드에 표시
  5. M2 행 선택 → "수정" → PartFormDialog 수정 → PUT
  6. M2 행 다중 선택 → "삭제" → DELETE → 그리드에서 제거
  7. 검색 (SEARCH_PART_NO/NAME) → 필터 적용 확인
  8. 운영DB 11133/waceplm 의 동일 SQL 결과와 vexplor_rps 결과 행 수 비교 (sanity)