탭실 재고관리 ERP
Apple 기기 (iPad, Pencil, AirPods) 재고 운영 관리 시스템
12
관리 랙 수
6
기기 유형
26
API 엔드포인트
8
DB 테이블
Tech Stack
Hono + TypeScript + Cloudflare Pages + D1 (SQLite)
Production
기기 유형
iPad 9 SG/Silver, iPad 11, Pencil 1/C, AirPods Max
랙 구성 (물리 배치)
| ID | 랙 이름 | 유형 | 저장 기기 | 층 구성 | 비고 |
|---|---|---|---|---|---|
| 1 | iPad 9 SG 랙 | storage | 스페이스 그레이 | 1~5층 | 정상 보관 |
| 2 | iPad 9 실버 랙 | storage | 실버 | 1~4층 | 정상 보관 |
| 3 | 애플펜슬 랙 | storage | Pencil 1세대 | 1~2층 | 정상 보관 |
| 4 | 검수 테이블 | inspection | 혼합 | - | 검수 중 임시 |
| 5 | 출고 테이블 | outbound | 혼합 | - | 출고 대기 |
| 6 | 이슈재고 랙 | issue | SG/Silver/Pencil | - | 불량/이슈 격리 |
| 7 | 보류재고 랙 | hold | SG/Silver/Pencil | - | 보류 중 보관 |
| 8 | iPad 11 랙 | storage | iPad 11세대 | - | 정상 보관 |
| 9 | 펜슬 USB-C 랙 | storage | Pencil USB-C | - | 정상 보관 |
| 10 | 에어팟 맥스 랙 | storage | AirPods Max | - | 정상 보관 |
| 11 | SG 예비랙 (창문난간) | reserve | SG 오버플로우 | - | 선입선출 보호 |
| 12 | 실버 예비랙 (창문난간) | reserve | Silver 오버플로우 | - | 선입선출 보호 |
시스템 아키텍처
Frontend (Browser)
주요 UI 모듈:
- 대시보드 (정상/이슈/보류 분리)
- 랙 관리 (층별 상세 + 금일입고)
- 키오스크 모달 (출고/입고 업무)
- 데일리 카운팅 (시스템vs실물)
- 자동배치 설정 관리
- 이동 이력 조회
Backend (Edge Worker)
핵심 로직 모듈:
autoPlaceDistribute()— 자동배치 분배buildPlacementStmts()— DB 배치문 생성- 트랜잭션 배치 (db.batch) 원자적 처리
- 이슈해결 / 입고반영 / 벌크이동
Database (D1 / SQLite)
테이블 구성:
racks— 랙 마스터floors— 층 정의inventory— 재고 현황transfer_logs— 이동 이력daily_inbound— 일별 입고daily_counts— 데일리 카운팅rack_auto_config— 자동배치 설정inbound_staging— 입고 스테이징
DB 스키마 (ERD)
name TEXT UNIQUE
slug TEXT UNIQUE
rack_type TEXT (storage|issue|hold|reserve|...)
has_floors INTEGER
sort_order INTEGER
FK rack_id → racks.id
floor_number INTEGER
label TEXT ("1층","2층",...)
UNIQUE(rack_id, floor_number)
FK rack_id → racks.id
FK floor_id → floors.id (NULL=층없음)
device_type TEXT (ipad_9_sg|...)
quantity INTEGER
FK from_rack_id, from_floor_id
FK to_rack_id, to_floor_id
device_type TEXT, quantity INTEGER
operator TEXT, memo TEXT
transfer_type TEXT (outbound|inbound|move|inspection|undo|issue_resolve|staging_apply)
device_type TEXT
FK floor_id → floors.id (NULL=예비랙)
FK rack_id → racks.id
max_capacity INTEGER (0=무제한)
place_order INTEGER (배치 순서)
inbound_date DATE
device_type TEXT
quantity INTEGER
status TEXT (pending|applied)
applied_at DATETIME, applied_by TEXT
UNIQUE(inbound_date, device_type)
inbound_date DATE
device_type TEXT, quantity INTEGER
operator TEXT, memo TEXT
UNIQUE(inbound_date, device_type)
count_date DATE
FK rack_id, floor_id
device_type TEXT
system_quantity, actual_quantity INTEGER
difference INTEGER
API 엔드포인트
| Method | Path | 설명 | 카테고리 |
|---|---|---|---|
| GET | /api/dashboard | 전체 대시보드 데이터 (정상/이슈/보류 분리) | 대시보드 |
| GET | /api/racks/:id | 특정 랙 상세 (층별 재고) | 랙 관리 |
| PUT | /api/inventory/:id | 단일 슬롯 수량 수정 | 재고 |
| POST | /api/transfer | 재고 이동 (핵심 — A→B) | 이동 |
| POST | /api/transfer-bulk | 대량 이동 (여러 층에서) | 이동 |
| POST | /api/transfer-bulk-sources | 다중 소스 벌크 이동 | 이동 |
| POST | /api/transfer-from-inbound | 입고 재고 → 특정 랙 이동 | 입고 |
| GET | /api/auto-place-config | 자동배치 설정 조회 (?device_type=) | 자동배치 |
| POST | /api/auto-place-config | 자동배치 설정 저장 (순서+MAX+예비랙) | 자동배치 |
| POST | /api/transfer-auto-resolve | 이슈재고 → 자동배치 전환 | 자동배치 |
| GET | /api/inbound-staging | 전일 입고 대기 재고 조회 | 스테이징 |
| POST | /api/inbound-staging/apply | 대기 재고 → 정상재고 자동배치 반영 | 스테이징 |
| GET | /api/daily-inbound | 금일 입고 기기 조회 | 입고 |
| POST | /api/daily-inbound | 금일 입고 수량 등록/수정 | 입고 |
| GET | /api/daily-summary | 데일리 카운팅 집계 | 카운팅 |
| POST | /api/daily-count | 카운팅 결과 저장 | 카운팅 |
| GET | /api/daily-counts | 카운팅 이력 조회 | 카운팅 |
| GET | /api/transfers | 이동 이력 조회 | 이력 |
| POST | /api/quick-issue | 빠른 이슈 등록 | 이슈 |
| POST | /api/inspection-workflow | 검수 워크플로우 | 검수 |
| POST | /api/inbound-with-issue | 입고+이슈 동시 처리 | 입고 |
| POST | /api/shipment | 출고 처리 | 출고 |
| POST | /api/inventory/bulk-set | 재고 일괄 설정 | 재고 |
| POST | /api/undo/:id | 이동 이력 되돌리기 | 이력 |
출고 업무 로직
1. 출고 업무 선택
키오스크 → 출고모드
2. 기기 선택 + 수량
출고할 기기/수량/담당자
3. 출고 완료
inventory - / transfer_log 기록
출고 서브타입
입고 업무 로직
1. 정상 입고 등록
daily_inbound에 기기별 수량 기록
2. 이슈재고 입고
정상 수량 + 이슈 수량 동시 처리
3. 검수 워크플로우
검수 테이블 → 정상/이슈 분류
4. 랙 배치
입고 재고 → 정상 랙 이동
금일 입고된 기기
데일리 카운팅 시 daily_inbound 수량이 정상재고에 자동 합산됩니다. 실제 랙 이동은 별도 처리합니다.
이슈재고 해결 로직 (자동배치 D안)
키오스크 2-Step 플로우
기기 선택
이슈 랙(rack 6)에 있는 기기 목록 표시, 재고가 있는 기기만 선택 가능
자동배치 프리뷰 + 실행
auto-place-config에서 순서대로 배치 미리보기 → 수량/담당자 입력 → 실행
완료 & 결과
분할 배치 상세 표시, 예비랙 사용 시 경고
API 호출 체인
POST /api/transfer-auto-resolve
autoPlaceDistribute(db, deviceType, qty)
place_order 순서대로 남은 수량 분배
buildPlacementStmts()
INSERT/UPDATE inventory + transfer_logs
db.batch([...stmts])
원자적 트랜잭션 실행
예비랙(창문난간) 동작 원리
- 예비랙은
rack_auto_config에서floor_id = NULL,max_capacity = 0(무제한)으로 설정 - place_order 순서에 따라 정상 랙이 가득 차면 예비랙에 남은 수량 전량 적재
- 선입선출이 꼬이는 것을 방지하기 위해, 1층(맨 아래)에 넣지 않고 예비랙에 보관
- 관리자가 자동배치 설정 탭에서 예비랙 순서를 자유롭게 조정 가능
입고 스테이징 반영 로직
STEP 1 — D일 업무중
금일 입고 등록
daily_inbound에 수량 기록
STEP 2 — D일 퇴근 전
내 자리에 임시 보관
정상 여부 확인 대기
STEP 3 — D+1일 (자동)
스테이징 이동
daily_inbound → inbound_staging
STEP 4 — D+1일 (수동)
"반영하기" 클릭
자동배치 → 정상재고 분배
데이터 흐름
daily_inbound
inbound_staging
inventory
GET /api/inbound-staging 호출 시:
- inbound_staging에서 status='pending' 조회
- daily_inbound에서 오늘 이전 + qty > 0 조회
- staging에 없는 항목 자동 INSERT
- 기기별 합산 summary 반환
반영 실행 (POST /api/inbound-staging/apply)
- pending 상태 staging 항목 로드
- 기기별 수량 합산
- 각 기기에
autoPlaceDistribute()실행 buildPlacementStmts()로 DB문 생성- staging status → 'applied' 업데이트
- daily_inbound quantity → 0 리셋
db.batch()원자적 실행
프론트엔드 UI
- 대시보드 배너: 대기 재고가 있으면 주황색 알림 배너 표시 (기기별 수량 + "반영하기" 버튼)
- 반영 모달: 키오스크 형태 — 자동배치 시뮬레이션 프리뷰(게이지바) + 담당자 선택 + 실행
- 완료 화면: 기기별 분할배치 상세, 예비랙 사용 경고
자동배치 엔진 (autoPlaceDistribute)
분배 알고리즘 (의사코드)
function autoPlaceDistribute(deviceType, quantity): // 1. 설정 로드 (place_order 순서) configs = DB.query(rack_auto_config WHERE device_type ORDER BY place_order) remaining = quantity placements = [] // 2. 순서대로 분배 for each config in configs: if remaining <= 0: break if config.is_reserve: // 예비랙: 남은 수량 전부 적재 (무제한) placements.add(config.rack, remaining) remaining = 0 else: // 정상 랙: MAX - 현재수량 = 여유공간만큼 available = config.max_capacity - config.current_qty if available <= 0: skip place = min(remaining, available) placements.add(config.rack, config.floor, place) remaining -= place // 3. 결과 if remaining > 0: return ERROR "모든 랙 가득참" return { placements, usedReserve }
현재 자동배치 설정 (운영중)
| 순서 | 위치 | MAX |
|---|---|---|
| 1 | 2층 | 70 |
| 2 | 예비랙 | 무제한 |
| 3 | 3층 | 70 |
| 4 | 4층 | 60 |
| 5 | 5층 | 70 |
| 6 | 1층 | 100 |
| 순서 | 위치 | MAX |
|---|---|---|
| 1 | 3층 | 70 |
| 2 | 2층 | 70 |
| 3 | 4층 | 70 |
| 4 | 1층 | 100 |
| 순서 | 위치 | MAX |
|---|---|---|
| 1 | 1층 | 550 |
| 2 | 2층 | 550 |
관리자 설정 UI (자동배치 설정 탭)
- 기기별 각 층의 MAX 용량 직접 수정 가능
- 순서 변경: ↑↓ 화살표로 place_order 조정
- 예비랙 추가: "+예비랙을 순서에 추가" 버튼으로 원하는 위치에 삽입
- 예비랙 제거: X 버튼으로 순서에서 제거
- 저장 시 기존 설정 DELETE 후 새로 INSERT (원자적)
UI 목업 갤러리
개발 과정에서 만든 모든 목업 페이지를 모아서 확인
자동배치 D안 — MAX 기반 순환 배치 + 예비랙
최종 채택안. 각 층의 MAX 용량 설정 기반으로 자동 순환 배치. 가득 차면 예비랙(창문난간)으로 오버플로우.