feat(dispatch): Shopee 플랫폼 추가 + 받는 사람 정보 저장/출고 엑셀
- 플랫폼 TikTok/Shopee 분리: 업로드 화면이 선택에 따라 파일 안내 변경 (TikTok=Order Export xlsx, Shopee=Packing List xlsx / 라벨 PDF 선택) - TikTok Picking List.pdf 업로드 제거(피킹 요약은 데이터 엑셀로 생성) - 파서 공용화 + 헤더 행 자동탐지(Shopee 제목행 대응), 두 포맷 헤더 별칭 통합 - 받는 사람 이름/전화/주소를 박스 단위로 dispatch_db 에 저장(개인정보) · init SQL 갱신 + 기존 DB용 멱등 마이그레이션 dispatch_add_recipient_columns.sql - 출고 작업 카드: Package 제거, 받는 사람 이름(크게)+주소(여러 줄)+전화 표시 - 배치 다운로드 zip 에 취합 출고 엑셀 포함 파일명 YYYY.MM.DD(Ddd)_tictoc.xlsx / _shopee.xlsx (export.py) - 문서(CLAUDE.md, DISPATCH_MODULE.md) PII 보관·Shopee 반영 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -37,16 +37,33 @@ STATUS_LABELS: dict[str, dict[str, str]] = {
|
||||
"courier_scanned": {"ko": "택배스캔 확인", "en": "Courier Scanned"},
|
||||
}
|
||||
|
||||
# ── 플랫폼 ──
|
||||
# 주문처(엑셀 '주문처' 컬럼)이자 업로드 슬롯/파서 선택의 기준값.
|
||||
PLATFORM_TIKTOK = "TikTok"
|
||||
PLATFORM_SHOPEE = "Shopee"
|
||||
PLATFORMS: tuple[str, ...] = (PLATFORM_TIKTOK, PLATFORM_SHOPEE)
|
||||
|
||||
|
||||
# ── 엑셀 컬럼명(앞뒤 공백 제거 후, 소문자 비교). 표준키 → 가능한 헤더들 ──
|
||||
# 개인정보 컬럼은 의도적으로 매핑하지 않는다(이름/주소/전화는 버린다).
|
||||
# TikTok(03_TikTok_Order_Export.xlsx)·Shopee(Packing List.Doorstep Delivery.xlsx)
|
||||
# 두 포맷의 헤더를 한 테이블에 모은다(매칭되는 것만 사용). 헤더가 다르면
|
||||
# 해당 표준키 줄에 별칭 1개만 추가하면 된다.
|
||||
# 받는 사람(recipient_*) 정보는 작업 카드 표시 + 출고 엑셀 생성을 위해 보관한다.
|
||||
COLUMN_ALIASES: dict[str, tuple[str, ...]] = {
|
||||
"order_id": ("order id", "order_id", "주문번호"),
|
||||
"package_id": ("package id", "package_id", "패키지번호"),
|
||||
"tracking_id": ("tracking id", "tracking_id", "tracking number", "송장번호"),
|
||||
"shipping_provider": ("shipping provider name", "shipping provider", "shipping_provider_name", "배송사", "택배사"),
|
||||
"seller_sku": ("seller sku", "seller_sku", "sku", "판매자 sku"),
|
||||
"product_name": ("product name", "product_name", "상품명"),
|
||||
"quantity": ("quantity", "qty", "수량"),
|
||||
"order_id": ("order id", "order_id", "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.", "송장번호"),
|
||||
"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",
|
||||
"parent sku reference no.", "판매자 sku", "상품코드"),
|
||||
"product_name": ("product name", "product_name", "item name", "product", "상품명"),
|
||||
"quantity": ("quantity", "qty", "수량", "수량(개)"),
|
||||
"recipient_name": ("recipient", "recipient name", "buyer name", "consignee", "받는 사람", "받는사람", "수취인", "수령인"),
|
||||
"recipient_phone": ("phone #", "phone#", "phone", "phone number", "contact number", "contact",
|
||||
"전화", "전화번호", "연락처"),
|
||||
"recipient_address": ("detail address", "detailed address", "address", "delivery address",
|
||||
"shipping address", "full address", "주소", "배송지", "배송주소", "수령지 주소"),
|
||||
}
|
||||
|
||||
# 사용자에게 보여줄 컬럼 이름(누락 안내 메시지용). 비교키는 소문자라 별도 표기.
|
||||
@@ -58,8 +75,14 @@ COLUMN_DISPLAY: dict[str, str] = {
|
||||
"seller_sku": "Seller SKU",
|
||||
"product_name": "Product Name",
|
||||
"quantity": "Quantity",
|
||||
"recipient_name": "Recipient",
|
||||
"recipient_phone": "Phone",
|
||||
"recipient_address": "Address",
|
||||
}
|
||||
|
||||
# 박스 단위로 보존하는 받는 사람 필드(같은 박스의 첫 비어있지 않은 값 사용).
|
||||
RECIPIENT_FIELDS: tuple[str, ...] = ("recipient_name", "recipient_phone", "recipient_address")
|
||||
|
||||
# 박스 묶음 기준 우선순위. 앞에서부터 비어있지 않은 첫 값으로 묶는다.
|
||||
BOX_KEY_PRIORITY: tuple[str, ...] = ("package_id", "tracking_id", "order_id")
|
||||
|
||||
@@ -153,12 +176,16 @@ def group_parcels(rows: list[dict[str, str]]) -> list[dict[str, Any]]:
|
||||
"package_id": (row.get("package_id") or "").strip(),
|
||||
"tracking_id": (row.get("tracking_id") or "").strip(),
|
||||
"shipping_provider": (row.get("shipping_provider") or "").strip(),
|
||||
"recipient_name": (row.get("recipient_name") or "").strip(),
|
||||
"recipient_phone": (row.get("recipient_phone") or "").strip(),
|
||||
"recipient_address": (row.get("recipient_address") or "").strip(),
|
||||
"_items": {}, # sku -> {product_name, quantity}
|
||||
}
|
||||
boxes[key] = box
|
||||
else:
|
||||
# 같은 박스인데 식별값이 빈 경우 뒤늦게 채운다(누락 보강).
|
||||
for field in ("order_id", "package_id", "tracking_id", "shipping_provider"):
|
||||
for field in ("order_id", "package_id", "tracking_id", "shipping_provider",
|
||||
"recipient_name", "recipient_phone", "recipient_address"):
|
||||
if not box[field] and (row.get(field) or "").strip():
|
||||
box[field] = (row.get(field) or "").strip()
|
||||
|
||||
@@ -188,6 +215,9 @@ def group_parcels(rows: list[dict[str, str]]) -> list[dict[str, Any]]:
|
||||
"package_id": box["package_id"],
|
||||
"tracking_id": box["tracking_id"],
|
||||
"shipping_provider": box["shipping_provider"],
|
||||
"recipient_name": box["recipient_name"],
|
||||
"recipient_phone": box["recipient_phone"],
|
||||
"recipient_address": box["recipient_address"],
|
||||
"items": items,
|
||||
}
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user