feat(malaysia): 재고조사 랙(rack) 박스 단위 입력

낱개/콤보 평면 수량 그리드를 창고 랙 그림(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>
This commit is contained in:
2026-06-14 16:25:40 +09:00
parent 65997adfd3
commit 032fb78867
10 changed files with 456 additions and 47 deletions
+45
View File
@@ -170,6 +170,51 @@ def test_missing_bom_sets():
assert missing == ["MY-0005"]
# ────────────────────────────────────────────────────────────
# 랙 입력 — 레이아웃 / 집계
# ────────────────────────────────────────────────────────────
def test_rack_cell_codes_count_and_format():
codes = store.rack_cell_codes()
# A/B 2면 × 3행 × 3열 × 2분할 = 36칸
assert len(codes) == 36
assert len(set(codes)) == 36
assert "A3-1-1" in codes
assert "B1-3-2" in codes
# 그림과 동일: 첫 칸은 A면 3행 1열 1분할
assert codes[0] == "A3-1-1"
def test_rack_layout_structure():
layout = store.rack_layout()
assert [s["side"] for s in layout] == ["A", "B"]
side_a = layout[0]
assert [r["row"] for r in side_a["rows"]] == [3, 2, 1]
# 각 행 3열, 각 열 2분할
first_row = side_a["rows"][0]
assert len(first_row["cols"]) == 3
assert first_row["cols"][0] == ["A3-1-1", "A3-1-2"]
def test_aggregate_rack_sums_units_times_boxes():
entries = [
{"sku_code": "MT-0320", "units_per_box": 24, "box_count": 3}, # 72
{"sku_code": "MT-0320", "units_per_box": 24, "box_count": 1}, # +24 (다른 칸)
{"sku_code": "MY-0002", "units_per_box": 6, "box_count": 2}, # 12 (콤보도 집계)
]
out = store.aggregate_rack(entries)
assert out == {"MT-0320": 96, "MY-0002": 12}
def test_aggregate_rack_skips_incomplete():
entries = [
{"sku_code": "", "units_per_box": 10, "box_count": 2}, # 코드 없음
{"sku_code": "MT-0550", "units_per_box": 0, "box_count": 5}, # 입수량 0
{"sku_code": "MT-0750", "units_per_box": 5, "box_count": 0}, # 박스 0
{"sku_code": "MT-0950", "units_per_box": "x", "box_count": 2}, # 정수 아님
]
assert store.aggregate_rack(entries) == {}
# pytest 미설치 환경 폴백
if __name__ == "__main__":
import traceback