Files
dbx-main/app/modules/dispatch/tests/test_grouping.py
T
king 691eecee52 fix(dispatch): Shopee Packing List xlsx 파싱 추가
- 헤더 별칭에 underscore형(order_sn, tracking_number) 추가
- Shopee 는 SKU/수량이 product_info 한 셀에 묶여 있어 전용 파서 추가
  · store.parse_shopee_product_info: [n] 단위 상품 분해, SKU/상품명/수량 추출
  · parser._parse_shopee: 1행=1주문=1박스, 상품 펼쳐 박스로 묶음, 배송사 SPX 추론
- parse_export(platform) 로 Shopee/일반 경로 분기, 라우터가 platform 전달
- 이름/주소/전화는 xlsx 에 없음 → 라벨 PDF 단계에서 보강 예정
- 테스트 3개 추가

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 14:29:54 +09:00

122 lines
5.0 KiB
Python

"""dispatch 모듈 박스 묶기/SKU 합산 순수 로직 테스트.
DB/엑셀 없이 store.py 의 규칙만 검증한다.
python -m app.modules.dispatch.tests.test_grouping
또는 pytest 로 실행 가능.
"""
from __future__ import annotations
from app.modules.dispatch import store
def test_package_id_priority():
"""묶음키 우선순위: package_id > tracking_id > order_id."""
rows = [
{"order_id": "O1", "package_id": "P1", "tracking_id": "T1", "seller_sku": "MT-0320", "quantity": "1"},
{"order_id": "O1", "package_id": "P1", "tracking_id": "T1", "seller_sku": "MT-0320", "quantity": "1"},
{"order_id": "O1", "package_id": "P2", "tracking_id": "T2", "seller_sku": "MY-0001", "quantity": "1"},
]
parcels = store.group_parcels(rows)
assert len(parcels) == 2, parcels # P1, P2 → 박스 2개
p1 = next(p for p in parcels if p["package_id"] == "P1")
# 같은 박스 같은 SKU 수량 합산 → 2
assert p1["items"][0]["quantity"] == 2, p1
def test_tracking_then_order_fallback():
"""package_id 없으면 tracking_id, 그것도 없으면 order_id 로 묶는다."""
rows = [
{"order_id": "O9", "package_id": "", "tracking_id": "TRK", "seller_sku": "MT-0550", "quantity": ""},
{"order_id": "O9", "package_id": "", "tracking_id": "TRK", "seller_sku": "MX-0001", "quantity": "3"},
{"order_id": "O8", "package_id": "", "tracking_id": "", "seller_sku": "MT-0750", "quantity": "2"},
]
parcels = store.group_parcels(rows)
assert len(parcels) == 2, parcels
trk = next(p for p in parcels if p["tracking_id"] == "TRK")
assert len(trk["items"]) == 2 # 다른 SKU 2종
# quantity 빈칸 → 1 로 보정
qty_by_sku = {it["seller_sku"]: it["quantity"] for it in trk["items"]}
assert qty_by_sku["MT-0550"] == 1
assert qty_by_sku["MX-0001"] == 3
def test_quantity_and_tracking_preserved_as_string():
"""수량 비면 1, 송장번호 숫자로 들어와도 문자열 보존."""
assert store.normalize_quantity("") == 1
assert store.normalize_quantity(None) == 1
assert store.normalize_quantity("0") == 1
assert store.normalize_quantity("5") == 5
# 큰 송장번호가 float 로 읽혀도 과학표기/.0 없이 보존
assert store.clean_text(12345678901.0) == "12345678901"
assert store.clean_text(float("nan")) == ""
def test_sku_summary():
rows = [
{"order_id": "A", "package_id": "PA", "tracking_id": "", "seller_sku": "MT-0320_3", "quantity": "1"},
{"order_id": "B", "package_id": "PB", "tracking_id": "", "seller_sku": "MT-0320_3", "quantity": "1"},
{"order_id": "C", "package_id": "PC", "tracking_id": "", "seller_sku": "MY-0001", "quantity": "5"},
]
parcels = store.group_parcels(rows)
summary = store.sku_summary(parcels)
totals = {r["seller_sku"]: r["total_qty"] for r in summary}
assert totals == {"MT-0320_3": 2, "MY-0001": 5}, totals
def test_missing_required_columns():
"""필수 컬럼 누락 감지: seller_sku 없거나 묶음키 전무."""
mapping = store.resolve_columns(["Order ID", "Product Name", "Quantity"])
missing = store.missing_required(mapping)
assert any("Seller SKU" in m for m in missing), missing
# 묶음키(order id) 는 있으므로 그 누락 메시지는 없어야 함
assert not any("최소 1개" in m for m in missing), missing
def test_column_alias_strip_and_case():
"""헤더 앞뒤 공백/대소문자 무시하고 매핑."""
mapping = store.resolve_columns([" Order ID ", "TRACKING ID", "Seller SKU", "Quantity"])
assert "order_id" in mapping
assert "tracking_id" in mapping
assert "seller_sku" in mapping
def test_shopee_product_info_single():
blob = ("[1] Product Name:[1+1=3] Mira's Kitchen Korean Grade Miracle Food "
"Container Launch PROMO; Variation Name:3x 320ml; Price: RM 20.00; "
"Quantity: 1; SKU Reference No.: MT-0320_3;")
items = store.parse_shopee_product_info(blob)
assert len(items) == 1, items
it = items[0]
assert it["seller_sku"] == "MT-0320_3", it
assert it["quantity"] == 1, it
assert it["variation"] == "3x 320ml", it
assert "Mira's Kitchen" in it["product_name"], it
def test_shopee_product_info_multi():
blob = ("[1] Product Name:Item A; Variation Name:S; Quantity: 2; "
"SKU Reference No.: MT-0001; "
"[2] Product Name:Item B; Variation Name:L; Quantity: 3; "
"SKU Reference No.: MX-0002;")
items = store.parse_shopee_product_info(blob)
skus = {i["seller_sku"]: i["quantity"] for i in items}
assert skus == {"MT-0001": 2, "MX-0002": 3}, skus
def test_shopee_product_info_empty():
assert store.parse_shopee_product_info("") == []
assert store.parse_shopee_product_info(None) == []
def _run_all():
fns = [v for k, v in sorted(globals().items()) if k.startswith("test_") and callable(v)]
for fn in fns:
fn()
print("PASS", fn.__name__)
print(f"\n{len(fns)} tests passed.")
if __name__ == "__main__":
_run_all()