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>
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from functools import lru_cache
|
||||
from pathlib import Path
|
||||
from typing import Any, Iterable
|
||||
@@ -50,9 +51,9 @@ PLATFORMS: tuple[str, ...] = (PLATFORM_TIKTOK, PLATFORM_SHOPEE)
|
||||
# 해당 표준키 줄에 별칭 1개만 추가하면 된다.
|
||||
# 받는 사람(recipient_*) 정보는 작업 카드 표시 + 출고 엑셀 생성을 위해 보관한다.
|
||||
COLUMN_ALIASES: dict[str, tuple[str, ...]] = {
|
||||
"order_id": ("order id", "order_id", "order sn", "ordersn", "order no.", "order number", "주문번호"),
|
||||
"order_id": ("order id", "order_id", "order sn", "order_sn", "ordersn", "order no.", "order number", "주문번호"),
|
||||
"package_id": ("package id", "package_id", "package number", "패키지번호"),
|
||||
"tracking_id": ("tracking id", "tracking_id", "tracking number", "tracking no", "tracking no.", "awb", "awb no.", "송장번호"),
|
||||
"tracking_id": ("tracking id", "tracking_id", "tracking number", "tracking_number", "tracking no", "tracking no.", "awb", "awb no.", "송장번호"),
|
||||
"shipping_provider": ("shipping provider name", "shipping provider", "shipping_provider_name",
|
||||
"shipping option", "courier", "carrier", "logistics", "배송사", "택배사"),
|
||||
"seller_sku": ("seller sku", "seller_sku", "sku", "sku reference no.", "sku reference no",
|
||||
@@ -83,6 +84,66 @@ COLUMN_DISPLAY: dict[str, str] = {
|
||||
# 박스 단위로 보존하는 받는 사람 필드(같은 박스의 첫 비어있지 않은 값 사용).
|
||||
RECIPIENT_FIELDS: tuple[str, ...] = ("recipient_name", "recipient_phone", "recipient_address")
|
||||
|
||||
# Shopee Packing List 의 상품 정보 컬럼(헤더 정규화 후 비교).
|
||||
PRODUCT_INFO_ALIASES: tuple[str, ...] = ("product_info", "product info", "상품정보")
|
||||
|
||||
# Shopee product_info 한 셀에 여러 상품이 [1]…[2]… 로 들어온다. 각 상품은
|
||||
# "Key:Value" 들이 ';' 로 구분된다(예: Product Name:…; Variation Name:…; Price:…;
|
||||
# Quantity:1; SKU Reference No.: MT-0320_3;).
|
||||
_SHOPEE_ITEM_SPLIT = re.compile(r"\[\d+\]\s*")
|
||||
_SHOPEE_KEY_MAP: dict[str, str] = {
|
||||
"product name": "product_name",
|
||||
"variation name": "variation",
|
||||
"quantity": "quantity",
|
||||
"sku reference no": "seller_sku", # 끝의 '.' 은 비교 전에 제거
|
||||
}
|
||||
|
||||
|
||||
def parse_shopee_product_info(blob: Any) -> list[dict[str, Any]]:
|
||||
"""Shopee product_info 셀 → 상품 리스트.
|
||||
|
||||
반환 각 항목: {seller_sku, product_name, variation, quantity}.
|
||||
상품명 안에 ';' 이 들어가면 필드 분리가 깨질 수 있으나 Shopee 상품명에는
|
||||
드물어 허용한다(필요 시 별도 처리).
|
||||
"""
|
||||
s = clean_text(blob)
|
||||
if not s:
|
||||
return []
|
||||
items: list[dict[str, Any]] = []
|
||||
for chunk in _SHOPEE_ITEM_SPLIT.split(s):
|
||||
chunk = chunk.strip()
|
||||
if not chunk:
|
||||
continue
|
||||
fields: dict[str, str] = {}
|
||||
for part in chunk.split(";"):
|
||||
if ":" not in part:
|
||||
continue
|
||||
raw_key, _, value = part.partition(":")
|
||||
key = raw_key.strip().lower().rstrip(".").strip()
|
||||
std = _SHOPEE_KEY_MAP.get(key)
|
||||
if std:
|
||||
fields[std] = value.strip()
|
||||
sku = fields.get("seller_sku", "")
|
||||
if not sku and not fields.get("product_name"):
|
||||
continue # 식별 불가한 빈 항목 스킵
|
||||
items.append(
|
||||
{
|
||||
"seller_sku": sku,
|
||||
"product_name": fields.get("product_name", ""),
|
||||
"variation": fields.get("variation", ""),
|
||||
"quantity": normalize_quantity(fields.get("quantity")),
|
||||
}
|
||||
)
|
||||
return items
|
||||
|
||||
|
||||
def find_column(headers: Iterable[Any], aliases: tuple[str, ...]) -> int | None:
|
||||
"""헤더에서 별칭에 맞는 첫 열 인덱스. 없으면 None."""
|
||||
for idx, h in enumerate(headers):
|
||||
if normalize_header(h) in aliases:
|
||||
return idx
|
||||
return None
|
||||
|
||||
# 박스 묶음 기준 우선순위. 앞에서부터 비어있지 않은 첫 값으로 묶는다.
|
||||
BOX_KEY_PRIORITY: tuple[str, ...] = ("package_id", "tracking_id", "order_id")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user