diff --git a/app/modules/dispatch/export.py b/app/modules/dispatch/export.py index 38edd78..4399c29 100644 --- a/app/modules/dispatch/export.py +++ b/app/modules/dispatch/export.py @@ -10,6 +10,7 @@ SKU 가 있으면 SKU 당 1행으로 펼치고 박스 단위(받는 사람/주 from __future__ import annotations import io +import re from datetime import date, datetime from typing import Any @@ -46,11 +47,36 @@ def export_filename(*, dispatch_date: str, platform: str) -> str: return f"{stamp}_{suffix}.xlsx" -def build_workbook_bytes(*, batch: dict[str, Any], parcels: list[dict[str, Any]]) -> bytes: +def resolve_product_name(seller_sku: str, name_map: dict[str, str], fallback: str = "") -> str: + """아이템코드(seller_sku)로 itemcode_db 상품명을 찾는다. + + seller_sku 는 변형 접미사가 붙을 수 있다(예: MT-0320_3 → DB 는 MT-0320). + 정확 일치 → 접미사(_숫자) 제거 후 일치 → 없으면 fallback(파싱된 상품명). + """ + key = (seller_sku or "").strip().upper() + if not key: + return fallback + if key in name_map: + return name_map[key] + base = re.sub(r"_\d+$", "", key) + if base != key and base in name_map: + return name_map[base] + return fallback + + +def build_workbook_bytes( + *, + batch: dict[str, Any], + parcels: list[dict[str, Any]], + name_map: dict[str, str] | None = None, +) -> bytes: """배치 + 박스(상품 포함) → xlsx 바이트. parcels 각 항목은 db.list_parcels 결과(items 포함, recipient_* 포함). + name_map: itemcode_db 의 {코드(대문자): 상품명}. 있으면 아이템코드로 상품명을 + 조회해 채운다(없으면 파싱된 상품명 사용). """ + name_map = name_map or {} from openpyxl import Workbook # noqa: WPS433 — 지연 import from openpyxl.styles import Font @@ -80,8 +106,11 @@ def build_workbook_bytes(*, batch: dict[str, Any], parcels: list[dict[str, Any]] items = p.get("items") or [{}] for it in items: row_data = dict(box) - row_data["product_name"] = it.get("product_name", "") or "" - row_data["seller_sku"] = it.get("seller_sku", "") or "" + sku = it.get("seller_sku", "") or "" + row_data["product_name"] = resolve_product_name( + sku, name_map, fallback=it.get("product_name", "") or "" + ) + row_data["seller_sku"] = sku row_data["quantity"] = it.get("quantity", "") if it.get("quantity") is not None else "" ws.append([_cell(row_data.get(key, "")) for _, key in _COLUMNS]) diff --git a/app/modules/dispatch/router.py b/app/modules/dispatch/router.py index 94a9d09..5283dad 100644 --- a/app/modules/dispatch/router.py +++ b/app/modules/dispatch/router.py @@ -61,6 +61,21 @@ def _data_dir(request: Request) -> Path: return Path(getattr(request.app.state, "data_dir", Path("app/data"))) +def _itemcode_name_map(request: Request) -> dict[str, str]: + """itemcode_db {코드(대문자): 상품명}. 미설정/실패 시 빈 dict. + + malaysia 모듈의 읽기 전용 리더(single_items + set_items)를 재사용한다. + """ + reader = getattr(request.app.state, "malaysia_itemcode", None) + if reader is None or not getattr(reader, "enabled", False): + return {} + try: + return reader.name_map() + except Exception: # noqa: BLE001 — 다운로드를 막지 않는다. + logger.exception("itemcode name_map 조회 실패") + return {} + + def _require_user(request: Request) -> dict[str, Any]: from app.main import get_current_user_record # noqa: WPS433 from app.store import has_module # noqa: WPS433 @@ -335,13 +350,17 @@ async def batch_download( count += 1 # 취합 출고 엑셀(발송날짜/고객/주소/상품/택배 등)을 생성해 함께 넣는다. + # 상품이름은 아이템코드로 itemcode_db 에서 조회해 채운다. parcels = st.list_parcels(batch_id=batch_id) + name_map = _itemcode_name_map(request) xlsx_name = export.export_filename( dispatch_date=batch.get("dispatch_date", ""), platform=batch.get("platform", ""), ) try: - xlsx_bytes = export.build_workbook_bytes(batch=batch, parcels=parcels) + xlsx_bytes = export.build_workbook_bytes( + batch=batch, parcels=parcels, name_map=name_map + ) zf.writestr(xlsx_name, xlsx_bytes) count += 1 except Exception: # noqa: BLE001 — 엑셀 생성 실패해도 원본 다운로드는 살린다. diff --git a/app/static/dispatch/courier/spx.svg b/app/static/dispatch/courier/spx.svg new file mode 100644 index 0000000..5e6b87b --- /dev/null +++ b/app/static/dispatch/courier/spx.svg @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + +