fix(dispatch): TikTok export 파싱 — read_only 버그 회피 + 설명행 스킵

- load_workbook read_only=True 제거: TikTok export 의 부실한 <dimension>
  메타 탓에 read_only 모드가 A열만 읽고 끊기는 openpyxl 버그(실제 54열 →
  1열만 반환)로 'Seller SKU 컬럼 없음' 오인 발생. 일반 모드로 전환.
- 헤더 바로 아래 '컬럼 설명' 행(2번째 행) 스킵: ID 필드에 공백이 있으면
  데이터가 아니라 설명 문장으로 판별(_is_description_row).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-19 15:27:29 +09:00
parent 99eea01fc1
commit a189b93427
+22 -1
View File
@@ -32,8 +32,11 @@ def parse_order_export(path: str) -> dict[str, Any]:
except ImportError as exc: # pragma: no cover except ImportError as exc: # pragma: no cover
raise ParseError("openpyxl 이 설치되어 있지 않습니다.") from exc raise ParseError("openpyxl 이 설치되어 있지 않습니다.") from exc
# read_only=True 는 쓰지 않는다: TikTok export 는 워크시트 <dimension> 메타가
# 부실해 read_only 모드가 A열만 읽고 끊기는 openpyxl 버그가 있다(실제 54열인데
# 1열만 반환). 일일 출고량(수백~수천 행)이라 일반 모드로 충분하다.
try: try:
wb = load_workbook(path, read_only=True, data_only=True) wb = load_workbook(path, data_only=True)
except Exception as exc: # noqa: BLE001 except Exception as exc: # noqa: BLE001
raise ParseError(f"엑셀 파일을 열 수 없습니다: {type(exc).__name__}") from exc raise ParseError(f"엑셀 파일을 열 수 없습니다: {type(exc).__name__}") from exc
@@ -62,9 +65,27 @@ def parse_order_export(path: str) -> dict[str, Any]:
# 완전 빈 행 스킵 # 완전 빈 행 스킵
if not any(record.get(k) for k in ("order_id", "package_id", "tracking_id", "seller_sku")): if not any(record.get(k) for k in ("order_id", "package_id", "tracking_id", "seller_sku")):
continue continue
# 헤더 바로 아래 '컬럼 설명' 행 스킵(TikTok export 2번째 행).
# 주문/송장/패키지 ID 는 공백을 포함하지 않는다. 설명 문장은 공백을
# 포함하므로, 채워진 ID 값에 공백이 있으면 데이터가 아니라 설명 행이다.
if _is_description_row(record):
continue
norm_rows.append(record) norm_rows.append(record)
finally: finally:
wb.close() wb.close()
parcels = store.group_parcels(norm_rows) parcels = store.group_parcels(norm_rows)
return {"parcels": parcels, "row_count": len(norm_rows)} return {"parcels": parcels, "row_count": len(norm_rows)}
def _is_description_row(record: dict[str, str]) -> bool:
"""헤더 아래 '컬럼 설명' 행 판별.
Order/Tracking/Package ID 는 공백 없는 식별자다. 채워진 ID 값 중 하나라도
공백을 포함하면(예: 'Platform unique order ID.') 데이터가 아닌 설명 행이다.
"""
for key in ("order_id", "tracking_id", "package_id"):
val = (record.get(key) or "").strip()
if val and any(ch.isspace() for ch in val):
return True
return False