feat(dispatch): 메뉴얼 오더 플랫폼 추가(엑셀 단독)
- 플랫폼 Manual 추가. 엑셀 첫 시트(Receiver Name/Phone/Address/Product Name/ Item Code/Quantity)를 전용 파서로 읽어 1행=1박스 생성 · 받는 사람 정보가 엑셀에 직접 있어 PDF 불필요(라벨 슬롯 없음) · 묶음키 없음 → 행마다 별도 박스 - 업로드 화면: Manual 선택 시 라벨 PDF 칸 숨김 + 안내문 - 출고 카드: Order ID/Tracking 은 값 있을 때만 표시(Manual 은 숨김), Manual 은 상품명 표기(받는사람/전화/주소/상품명/수량) - 재고 차감/사방넷 출고는 기존 집계 로직 그대로(콤보 _N 분해 포함) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -35,13 +35,99 @@ def parse_export(path: str, platform: str = "") -> dict[str, Any]:
|
||||
"""출고 XLSX 경로 → {"parcels": [...], "row_count": int}.
|
||||
|
||||
parcels 는 store.group_parcels 결과(1박스=1항목). 실패 시 ParseError.
|
||||
Shopee 는 SKU/수량이 product_info 한 셀에 묶여 있어 별도 경로로 파싱한다.
|
||||
- Shopee: SKU/수량이 product_info 한 셀에 묶여 있어 별도 경로.
|
||||
- Manual: 받는 사람/상품/수량이 첫 시트 한 행에 있는 수동 오더(1행=1박스).
|
||||
"""
|
||||
if (platform or "").strip() == store.PLATFORM_SHOPEE:
|
||||
p = (platform or "").strip()
|
||||
if p == store.PLATFORM_SHOPEE:
|
||||
return _parse_shopee(path)
|
||||
if p == store.PLATFORM_MANUAL:
|
||||
return _parse_manual(path)
|
||||
return _parse_generic(path)
|
||||
|
||||
|
||||
# 메뉴얼 오더 엑셀 컬럼 별칭(헤더 정규화 후 비교). 받는 사람 정보가 엑셀에 직접 있다.
|
||||
_MANUAL_ALIASES: dict[str, tuple[str, ...]] = {
|
||||
"recipient_name": ("receiver name", "recipient name", "recipient", "받는 사람", "받는사람", "수취인", "이름", "name"),
|
||||
"recipient_phone": ("phone", "phone number", "phone #", "contact", "전화", "전화번호", "연락처"),
|
||||
"recipient_address": ("address", "delivery address", "주소", "배송지", "배송주소"),
|
||||
"product_name": ("product name", "item name", "product", "상품명", "상품이름"),
|
||||
"seller_sku": ("item code", "seller sku", "sku", "아이템코드", "아이템 코드", "상품코드", "코드"),
|
||||
"quantity": ("quantity", "qty", "수량", "주문수량"),
|
||||
}
|
||||
|
||||
|
||||
def _parse_manual(path: str) -> dict[str, Any]:
|
||||
"""메뉴얼 오더 엑셀(첫 시트). 1행 = 1박스.
|
||||
|
||||
컬럼: Receiver Name / Phone / Address / Product Name / Item Code / Quantity.
|
||||
받는 사람 정보가 엑셀에 직접 있어 PDF 없이 카드/엑셀에 표시된다. 묶음키
|
||||
(Order/Tracking/Package)가 없으므로 행마다 별도 박스(seq)로 만든다.
|
||||
"""
|
||||
all_rows = _load_rows(path)
|
||||
|
||||
header_idx = None
|
||||
mapping: dict[str, int] = {}
|
||||
for idx, raw in enumerate(all_rows[:_HEADER_SCAN_ROWS]):
|
||||
if raw is None:
|
||||
continue
|
||||
m = {
|
||||
key: store.find_column(raw, aliases)
|
||||
for key, aliases in _MANUAL_ALIASES.items()
|
||||
}
|
||||
# 최소: 아이템코드 컬럼이 잡혀야 한다.
|
||||
if m.get("seller_sku") is not None:
|
||||
header_idx = idx
|
||||
mapping = {k: v for k, v in m.items() if v is not None}
|
||||
break
|
||||
|
||||
if header_idx is None:
|
||||
raise ParseError(
|
||||
"메뉴얼 오더 엑셀에서 컬럼을 찾지 못했습니다. "
|
||||
"필요 컬럼: Item Code(필수), Receiver Name, Phone, Address, Product Name, Quantity"
|
||||
)
|
||||
|
||||
def cell(raw: Any, key: str) -> str:
|
||||
idx = mapping.get(key)
|
||||
if idx is None or idx >= len(raw):
|
||||
return ""
|
||||
return store.clean_text(raw[idx])
|
||||
|
||||
parcels: list[dict[str, Any]] = []
|
||||
seq = 0
|
||||
for raw in all_rows[header_idx + 1:]:
|
||||
if raw is None:
|
||||
continue
|
||||
sku = cell(raw, "seller_sku")
|
||||
name = cell(raw, "recipient_name")
|
||||
if not sku and not name:
|
||||
continue # 빈 행
|
||||
if not sku:
|
||||
continue # 아이템코드 없으면 출고 대상 아님
|
||||
seq += 1
|
||||
parcels.append(
|
||||
{
|
||||
"seq": seq,
|
||||
"order_id": "",
|
||||
"package_id": "",
|
||||
"tracking_id": "",
|
||||
"shipping_provider": "",
|
||||
"recipient_name": name,
|
||||
"recipient_phone": cell(raw, "recipient_phone"),
|
||||
"recipient_address": cell(raw, "recipient_address"),
|
||||
"items": [
|
||||
{
|
||||
"seller_sku": sku,
|
||||
"product_name": cell(raw, "product_name"),
|
||||
"quantity": store.normalize_quantity(cell(raw, "quantity")),
|
||||
}
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
return {"parcels": parcels, "row_count": len(parcels)}
|
||||
|
||||
|
||||
def _load_rows(path: str) -> list:
|
||||
"""첫 시트 전체 행(values_only). 열기/빈 파일 예외는 ParseError 로."""
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user