032fb78867
낱개/콤보 평면 수량 그리드를 창고 랙 그림(A/B 36칸) 입력으로 대체.
각 칸에 아이템·박스당 입수량·박스 수를 넣고 + 로 한 칸 여러 아이템 추가.
저장 시 SKU별 qty=SUM(입수량×박스수) 집계로 daily_stocktake_line 재생성.
이후 분해/확정 로직은 라인 기준 그대로 동작.
- DB: daily_stocktake_rack 테이블 + grant (멱등)
- store: rack_cell_codes/rack_layout/aggregate_rack 순수함수
- db: list_rack_entries/replace_rack(1 트랜잭션, draft만)
- router: rack 컨텍스트 + POST /stocktakes/{id}/rack/bulk
- UI: 랙 보드 템플릿 + malaysia_rack.js(행 추가/삭제·실시간 소계)
- 테스트 4건 추가(18 전부 통과)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
121 lines
5.5 KiB
Markdown
121 lines
5.5 KiB
Markdown
# 말레이시아 창고 재고관리 모듈 (`malaysia`)
|
||
|
||
말레이시아 현지 창고의 입고/출고/조정과 세트 BOM, 일일 재고조사를 관리한다.
|
||
cupang 모듈과 동일한 패턴: **malaysia_stock_db(PostgreSQL) 전용**, JSON 폴백 없음.
|
||
상품명은 기존 `itemcode_db`(읽기 전용)을 재사용한다.
|
||
|
||
- 경로(prefix): `/malaysia`
|
||
- 권한키: `malaysia` (admin 은 항상 통과)
|
||
- 환경변수: `MALAYSIA_STOCK_DB_URL` (미설정 시 "설정 필요" 안내), `ITEMCODE_DB_URL`(세트 BOM/이름)
|
||
- DB: `malaysia_stock_db` / 역할 `malaysia_app`
|
||
|
||
> **세트 구성(BOM)은 별도 관리하지 않는다.** itemcode_db `set_components`
|
||
> (set_code / single_code / quantity)에서 읽는다. 모듈 안에 세트구성 메뉴 없음.
|
||
> `malaysia_stock_db.set_bom` 테이블은 더 이상 쓰지 않는다(레거시, 무시).
|
||
> 입출고·재고조사는 전체 아이템(+세트) 그리드에 **수량만 키인**해 일괄 등록한다.
|
||
|
||
---
|
||
|
||
## 재고 원칙
|
||
|
||
- **입고/출고/조정은 낱개 아이템(MT-/MX-/MZ-) 기준**으로만 movement 저장.
|
||
- **세트(MY-)는 movement 불가.** 세트는 ① 재고조사 입력, ② BOM 구성 계산에만 사용.
|
||
- 세트 주문 출고는 `set_bom` 으로 분해해 구성품별 `OUT` movement 를 생성(`/malaysia/movements/set-out`).
|
||
- 뚜껑(MD-)은 재고관리 대상에서 **완전 제외**(어디에도 사용 불가).
|
||
|
||
### 현재고 계산
|
||
|
||
```
|
||
현재고 = SUM(IN) - SUM(OUT) + SUM(ADJUST) + SUM(STOCKTAKE)
|
||
```
|
||
|
||
- `IN`/`OUT` qty 는 양수만. `ADJUST`/`STOCKTAKE` 는 음수(±) 허용.
|
||
- 재고조사 확정 시 **시스템 재고와 조사 최종치의 "차이"만** `STOCKTAKE` movement 로 기록 → 이력 보존 + 계산 단순.
|
||
|
||
### 일일 재고조사 집계
|
||
|
||
```
|
||
낱개 최종 재고(total_qty)
|
||
= direct_qty(낱개 직접 조사)
|
||
+ from_set_qty(세트 수량을 set_bom 으로 분해한 합)
|
||
```
|
||
|
||
예) `MT-0320` 낱개 100 + `MY-0002`(MT-0320 1개 포함) 20세트 → `MT-0320` total = **120**.
|
||
> 낱개 입력 시 **세트 안에 든 건 빼고** 낱개 보관분만 입력한다. 세트 안 상품은 BOM 으로 자동 계산.
|
||
|
||
### 랙(rack) 입력 — 재고조사 상세
|
||
|
||
재고조사 상세는 평면 수량 그리드 대신 **창고 랙 그림(A/B 두 면, 각 3행×3열×2분할 = 36칸)**
|
||
으로 입력한다. 각 칸에 `아이템 · 박스당 입수량 · 박스 수`를 넣고, 한 칸에 여러 아이템은
|
||
`+` 로 행을 추가한다. 저장하면 서버가 SKU별로
|
||
|
||
```
|
||
qty = SUM(units_per_box × box_count)
|
||
```
|
||
|
||
집계해 `daily_stocktake_line`을 **재생성**한다(랙이 라인의 단일 출처). 낱개·콤보(MY-)
|
||
모두 랙에 입력 가능하며, 이후 분해/확정 로직(`compute_result`/`finalize`)은 라인 기준으로
|
||
기존과 동일하게 동작한다. 칸 코드 형식: `{면}{행}-{열}-{분할}` (예 `A3-1-1`).
|
||
|
||
---
|
||
|
||
## 테이블 (`scripts/sql/malaysia_stock_db_init.sql`)
|
||
|
||
| 테이블 | 용도 |
|
||
| --- | --- |
|
||
| `warehouses` | 창고. seed: `MY-WH-01 / Malaysia Warehouse` |
|
||
| `malaysia_items` | 관리 대상 코드 스코프(낱개/세트) + 이름 스냅샷. seed: 낱개 12 + 세트 5 |
|
||
| `set_bom` | 세트 구성표. `UNIQUE(set_code, component_code)`. prefix CHECK 내장 |
|
||
| `stock_movement` | 입고/출고/조정/조사 이력. `item_code` 는 낱개만(CHECK) |
|
||
| `daily_stocktake` | 재고조사 헤더. 같은 날짜+창고 finalized 1건(부분 유니크) |
|
||
| `daily_stocktake_line` | 조사 라인(SKU별 최종 qty). 랙 입력 집계로 **재생성**됨. `UNIQUE(stocktake_id, sku_code)` |
|
||
| `daily_stocktake_rack` | 랙 칸별 입력(셀×SKU×입수량×박스수). 라인의 단일 출처. `cell_code` 예 `A3-1-1` |
|
||
|
||
검증 규칙(요구사항 8)은 **DB CHECK + 서비스 레이어(`store.py`)** 양쪽에 걸려 있다.
|
||
|
||
---
|
||
|
||
## 주요 화면 / API
|
||
|
||
화면: `/malaysia/`(재고 현황) · `/malaysia/movements`(입출고) · `/malaysia/stocktakes`(재고조사) · `/malaysia/sets`(세트 구성)
|
||
|
||
JSON API:
|
||
|
||
| Method | 경로 | 설명 |
|
||
| --- | --- | --- |
|
||
| GET | `/malaysia/api/warehouses` | 창고 목록 |
|
||
| GET | `/malaysia/api/items?kind=individual\|set` | 아이템 목록 |
|
||
| GET/POST | `/malaysia/api/bom` | 세트 구성 조회/등록 |
|
||
| GET | `/malaysia/api/stock?wh=` | 현재고 현황 |
|
||
| POST | `/malaysia/api/movements` | 낱개 movement 등록 |
|
||
| POST | `/malaysia/api/movements/set-out` | 세트 출고(BOM 분해) |
|
||
| GET | `/malaysia/api/stocktakes/{id}/result` | 재고조사 최종 계산 |
|
||
| POST | `/malaysia/stocktakes/{id}/rack/bulk` | 랙 입력 일괄 저장(병렬배열 `rk_cell/rk_sku/rk_upb/rk_box`) → 라인 재생성 |
|
||
|
||
---
|
||
|
||
## 초기화 (운영, 1회 — 사용자 승인 후)
|
||
|
||
```bash
|
||
read -s -p "malaysia_app password: " APP_PWD; echo
|
||
docker exec -i postgres-db psql -U postgres \
|
||
-v app_password="$APP_PWD" \
|
||
< scripts/sql/malaysia_stock_db_init.sql
|
||
|
||
# main-app .env 에 추가:
|
||
# MALAYSIA_STOCK_DB_URL=postgresql://malaysia_app:<APP_PWD>@postgres-db:5432/malaysia_stock_db
|
||
cd /opt/www/main && docker compose up -d --build
|
||
```
|
||
|
||
> 멱등 스크립트. 기존 DB 가 있으면 DROP 하지 않음. itemcode_db 는 건드리지 않음.
|
||
> **랙 입력 추가(`daily_stocktake_rack`)** 후 기존 운영 DB 에 반영하려면 위 init 스크립트를
|
||
> 그대로 1회 재실행한다(`CREATE TABLE IF NOT EXISTS` + grant 만 적용, 기존 데이터 보존).
|
||
|
||
## 테스트
|
||
|
||
```bash
|
||
python -m pytest tests/test_malaysia_stock.py -v # pytest 있으면
|
||
python tests/test_malaysia_stock.py # 없으면 standalone 폴백
|
||
```
|
||
순수 로직(코드 검증 / 세트 분해 / 재고조사 집계)만 검증하므로 DB 불필요.
|