Files
vexplor_dev/docs/plans/transaction-packaging-loading-plan.md
kmh 79962160d0 Update admin pages, API clients, and add transfer plan docs
- Update logistics/inbound-outbound pages across 9 companies
- Update production/result and production/work-instruction admin pages
- Add inventoryTransfer API client and enhance packaging/popInventoryAdjust/popInventoryMove clients
- Add transaction-packaging-loading-plan docs
- Add AdjustHistoryModal for COMPANY_9

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 18:04:11 +09:00

11 KiB
Raw Blame History

거래 단위 포장재·적재함 추적 시스템 구현 계획

작성일: 2026-05-13 작성자: mhkim@wace.me 선행 적용 대상: COMPANY_7 성격: 신규 도메인 (입고/출고/재고이동 공통)


1. 배경

1.1 현재 한계

  • pkg_unit / loading_unit 마스터에 포장재·적재함 정의는 등록 가능
  • 입고/출고/재고이동 시 "어떤 라인이 어떤 포장재로 어떤 적재함에 담겼는지" 저장하는 트랜잭션 테이블이 없음
  • POP 장바구니 UI(InboundCartPage, OutboundCartPage)에서 입력은 받지만 DB 영속화되지 않음

1.2 도입 목표

  • 입고/출고/재고이동 등 모든 트랜잭션에서 포장재·적재함 사용 내역을 영속화
  • 박스(라벨) 단위 개체 추적 — 부분 출고, LOT 분리, 유통기한, 적재함 이동을 박스 단위로 관리
  • 신규 트랜잭션 영역이 추가되어도 DDL 변경 없이 확장 (polymorphic source_type + source_id 패턴)

2. 현황 분석

2.1 기존 마스터 테이블 (재사용)

테이블 역할
pkg_unit 포장재 마스터 (BOX-L, BOX-M 등)
pkg_unit_item 포장재-품목 매핑
loading_unit 적재함 마스터 (LU-PAL, LU-CRT 등)
loading_unit_pkg 적재함-포장재 매핑

2.2 신규 테이블 (이 계획서 범위)

테이블 역할 카디널리티
transaction_loading 적재함 사용 인스턴스 작업 헤더당 N건 (loading_seq 로 동종 적재함 구분)
transaction_packaging 박스(라벨) 단위 포장 사용 박스 1개 = row 1개

2.3 영향 받는 기존 테이블

테이블 영향
inbound_detail 컬럼 변경 없음. 컨트롤러에서 패키징 분해 로직 추가만
outbound_mng 동일
inventory_stock 유지 — 옵션 1(이중 관리), 트랜잭션 단위로 동시 갱신
inventory_history 유지 — 품목 단위 이력만 기록 (박스 단위 이력은 Phase 3 의 별도 테이블로)

3. 핵심 설계 결정

항목 결정 근거
박스 단위 모델 박스 1개 = transaction_packaging row 1개 부분 출고/LOT/유통기한을 박스별로 독립 관리하기 위해
pkg_count 컬럼 삭제 (항상 1) 박스 1개 = row 1개라 불필요
quantity 의미 박스 1개에 실제 담긴 수량 잔량 박스도 같은 모델로 자연 표현 (예: 마지막 박스 quantity=30)
라벨 형식 {문서번호}-P{4자리} (예: INB-2026-001-P0001) 단순/가독성/문서 추적 용이
라벨 UNIQUE (company_code, package_label) 회사 단위 유일
라벨 동시성 입고 트랜잭션 내 SELECT MAX(seq)+1 + UNIQUE 제약 retry 별도 sequence 객체 불필요
적재함 없는 입고 loading_id NULL 허용 LU-DIRECT 가상 적재함 미도입 (마스터 오염 방지)
적재함 인스턴스 구분 loading_code + loading_seq 조합 같은 종류 적재함을 한 작업에 여러 개 따로 사용하는 케이스
부분 출고 quantity UPDATE + 로그 라벨 분할(-S1) 미사용 — 형식 폭주 방지, 박스 라벨 안정성
출고 처리 row 의 status='SHIPPED' 변경 Phase 1 단순. 이력 추적은 Phase 3 의 transaction_package_movement
재고 동기화 옵션 1 (이중 관리) 기존 화면 영향 최소. 같은 트랜잭션 내 inventory_stock + transaction_packaging 동시 갱신
잔량(미포장) 별도 처리 안 함 (마지막 박스 quantity 가 작은 형태로 흡수) pkg_code=NULL 행/is_remainder 플래그 불필요
포장 중첩 미지원 (loading_id 1단계만) 1차 범위 단순화
바코드 인쇄 1차는 텍스트 라벨 / 2차 ZPL 연동 출시 범위 축소

4. 테이블 스키마

4.1 transaction_loading — 적재함 사용 인스턴스

컬럼 타입 설명
id VARCHAR(500) PK UUID
company_code VARCHAR(500) NOT NULL 멀티테넌시
source_type VARCHAR(500) NOT NULL 'inbound', 'outbound', 'inventory_move' 등 (헤더 레벨)
source_doc_id VARCHAR(500) NOT NULL inbound_mng.id / outbound_mng.id 등 헤더 ID
loading_code VARCHAR(500) NOT NULL loading_unit.loading_code
loading_seq INT 같은 적재함 종류를 한 작업에서 여러 개 쓸 때 구분 (1, 2, 3...)
loading_name VARCHAR(500) 마스터 스냅샷
memo VARCHAR(500) 비고
status VARCHAR(500) (예약) 'ACTIVE'/'CLOSED' 등
writer VARCHAR(500)
created_by VARCHAR(500)
created_date TIMESTAMP DEFAULT NOW()
updated_date TIMESTAMP DEFAULT NOW()

인덱스:

  • (company_code, source_type, source_doc_id)
  • (company_code, loading_code)

4.2 transaction_packaging — 박스(라벨) 단위 포장 사용

컬럼 타입 설명
id VARCHAR(500) PK UUID
company_code VARCHAR(500) NOT NULL 멀티테넌시
source_type VARCHAR(500) NOT NULL 'inbound_detail', 'outbound_detail', 'inventory_move_detail' 등 (라인 레벨)
source_id VARCHAR(500) NOT NULL 디테일 라인의 id
package_label VARCHAR(500) NOT NULL 라벨 (예: INB-2026-001-P0001)
pkg_code VARCHAR(500) NOT NULL pkg_unit.pkg_code
quantity NUMERIC(18,4) NOT NULL 이 박스 1개에 실제 담긴 수량
loading_id VARCHAR(500) transaction_loading.idNULL 허용 (적재함 없는 입고)
seq_no INT 라인 내 박스 순번 (1, 2, 3, ...)
status VARCHAR(500) DEFAULT 'STORED' STORED / SHIPPED / SCRAPPED / DAMAGED
lot_no VARCHAR(500) 로트번호 (옵션)
expire_date DATE 유통기한 (옵션)
writer VARCHAR(500)
created_by VARCHAR(500)
created_date TIMESTAMP DEFAULT NOW()
updated_date TIMESTAMP DEFAULT NOW()

제약:

  • UNIQUE (company_code, package_label)

인덱스:

  • (company_code, source_type, source_id)
  • (company_code, loading_id)
  • (company_code, package_label)
  • (company_code, status)

5. 입고/출고/재고이동 흐름

5.1 입고 등록 (자동 분해)

사용자 입력 (집계형 UX 유지)

  • 품목 라인별 입고 수량
  • 포장재 + 단위당 수량 + 박스 개수
  • 적재함 선택 (없으면 NULL)

백엔드 처리 (단일 트랜잭션)

  1. inbound_mng / inbound_detail INSERT (기존 로직)
  2. 적재함 신규 인스턴스면 transaction_loading INSERT → loading_id 획득
  3. 라인 수량을 박스 단위로 분해 → transaction_packaging N개 INSERT
    • 예: 입고 1030 / BOX-50 → 20 row (quantity=50) + 1 row (quantity=30) = 21 row
  4. 라벨 자동 발번: {문서번호}-P{4자리} 시퀀스
  5. inventory_stock += 총합, inventory_history INSERT (기존 흐름 그대로)

응답: 발번된 라벨 목록 (인쇄 대상)

5.2 출고 (Phase 2 범위)

  1. 출고 요청 (수주/지시) → 품목 + 수량 입력
  2. 라벨 선택 (자동 FEFO/FIFO / 수동 / 바코드 스캔)
  3. 박스 전체 출고:
    • 해당 row status='SHIPPED', updated_date=NOW()
  4. 박스 부분 출고 (예: 50 박스 중 20개만 나감):
    • quantity 50→30 UPDATE
    • 라벨/박스는 그대로 유지 (분할 없음)
    • inventory_history 에 '출고' 한 줄 (품목 단위)
  5. inventory_stock 차감 (기존 흐름)

5.3 재고이동 (Phase 2)

  • 박스의 loading_id 만 새 인스턴스로 UPDATE
  • inventory_stock 의 창고/위치 컬럼 동기화

6. API 명세 (Phase 1 범위)

메서드 경로 역할
POST /api/packaging/labels/generate 입고 시 라벨 자동 발번 (입고 컨트롤러 내부 호출)
GET /api/packaging 라벨 조회 (viewMode=summary 집계 / detail 박스 목록)
GET /api/packaging/:label 라벨 1건 상세
GET /api/loading 적재함 인스턴스 조회

수정 API:

  • POST /api/inbound — 자동 분해 로직 추가 (단일 트랜잭션 안에서 transaction_packaging/loading INSERT)

Phase 2 추가 예정:

  • PATCH /api/packaging/:id/status
  • PATCH /api/packaging/:id/move

7. 프론트엔드 영향

7.1 적용 범위 (1차)

  • frontend/app/(main)/COMPANY_7/pop/_components/inbound/*
  • frontend/app/(main)/COMPANY_7/logistics/packaging/page.tsx (조회)
  • 검증 완료 후 COMPANY_8/9/16/28/29/30 등으로 확대

7.2 수정 대상 파일

파일 변경 내용
pop/_components/inbound/InboundCartPage.tsx 백엔드 응답으로 받은 라벨 목록 표시 영역 추가
pop/_components/inbound/NumberPadModal.tsx 동작 변경 없음 (UI 그대로, 백엔드가 분해)
logistics/packaging/page.tsx 라벨 조회 뷰 추가 (집계/상세 토글)
lib/api/packaging.ts 신규 엔드포인트 클라이언트 추가

8. 단계별 로드맵

Phase 1 — MVP (이번 계획서 범위)

  • DDL 마이그레이션 적용
  • 백엔드 packagingService.ts 신규 (분해/발번)
  • receivingController.ts 에 패키징 INSERT 통합
  • 라벨 조회 API
  • 프론트 라벨 결과 표시
  • COMPANY_7 검증

Phase 2 — 운영 안정화

  • 출고 시 라벨 선택 UI (자동/수동/스캔)
  • 부분 출고 quantity UPDATE 로직
  • 상태/위치 변경 API
  • 재고이동에서 박스 단위 이동

Phase 3 — 고도화

  • transaction_package_movement 이력 테이블
  • 바코드 인쇄 (ZPL)
  • 스캐너 입력 연동
  • FEFO/FIFO 자동 선택

9. 위험 및 완화책

위험 완화책
inventory_stocktransaction_packaging 정합성 불일치 모든 변경을 단일 DB 트랜잭션으로 묶음. 서비스 레이어에 단일 진입점 강제
동시 입고 시 라벨 시퀀스 충돌 UNIQUE 제약 + 트랜잭션 내 retry
적재함 NULL 허용으로 쿼리 분기 증가 LEFT JOIN transaction_loading 패턴으로 통일
COMPANY_7 외 회사 확대 시 코드 중복 공통 라이브러리화 후 회사별 페이지에서 import

10. 기존 데이터 처리

  • 신규 입고부터만 적용 — 기존 inbound 데이터는 마이그레이션하지 않음
  • 운영 중 변경된 입고를 박스 단위로 풀어내는 것은 입력 의도가 불명확해 데이터 왜곡 위험
  • 필요 시 Phase 3 에 별도 마이그레이션 스크립트 작성 검토

11. 검증 계획

11.1 단위

  • 박스 분해 함수: 1030 / 50 → 21 row (20×50, 1×30)
  • 라벨 발번: 동시 호출 시 충돌 없이 순차 발번
  • 트랜잭션 롤백: 한쪽 실패 시 inventory_stock/transaction_packaging 모두 원복

11.2 통합 (COMPANY_7)

  • POP 입고 장바구니 → 등록 → 라벨 21개 발번 → 조회 화면에서 21개 표시 확인
  • 잔량 케이스 (1030/50=21박스): 마지막 박스 quantity=30 표시 확인
  • 같은 종류 적재함 2개 따로 사용: loading_seq 1, 2 로 구분 확인
  • 적재함 없는 입고: loading_id=NULL 정상 저장 확인

11.3 회귀

  • 기존 입고/출고/재고이동 화면 동작 유지
  • 재고현황 화면 (inventory_stock 기반) 수치 변화 없음

12. 마이그레이션 파일

  • 위치: db/migrations/add_transaction_packaging_loading.sql
  • 양식: 기존 add_packaging_to_pop_production.sql 패턴 준수 (헤더 주석 / 검증 쿼리 / 롤백 섹션)