From a189b934271356dcf9e0989ead87ac046e93e435 Mon Sep 17 00:00:00 2001 From: king Date: Fri, 19 Jun 2026 15:27:29 +0900 Subject: [PATCH] =?UTF-8?q?fix(dispatch):=20TikTok=20export=20=ED=8C=8C?= =?UTF-8?q?=EC=8B=B1=20=E2=80=94=20read=5Fonly=20=EB=B2=84=EA=B7=B8=20?= =?UTF-8?q?=ED=9A=8C=ED=94=BC=20+=20=EC=84=A4=EB=AA=85=ED=96=89=20?= =?UTF-8?q?=EC=8A=A4=ED=82=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - load_workbook read_only=True 제거: TikTok export 의 부실한 메타 탓에 read_only 모드가 A열만 읽고 끊기는 openpyxl 버그(실제 54열 → 1열만 반환)로 'Seller SKU 컬럼 없음' 오인 발생. 일반 모드로 전환. - 헤더 바로 아래 '컬럼 설명' 행(2번째 행) 스킵: ID 필드에 공백이 있으면 데이터가 아니라 설명 문장으로 판별(_is_description_row). Co-Authored-By: Claude Opus 4.8 --- app/modules/dispatch/parser.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/app/modules/dispatch/parser.py b/app/modules/dispatch/parser.py index e6f0802..ad08a4f 100644 --- a/app/modules/dispatch/parser.py +++ b/app/modules/dispatch/parser.py @@ -32,8 +32,11 @@ def parse_order_export(path: str) -> dict[str, Any]: except ImportError as exc: # pragma: no cover raise ParseError("openpyxl 이 설치되어 있지 않습니다.") from exc + # read_only=True 는 쓰지 않는다: TikTok export 는 워크시트 메타가 + # 부실해 read_only 모드가 A열만 읽고 끊기는 openpyxl 버그가 있다(실제 54열인데 + # 1열만 반환). 일일 출고량(수백~수천 행)이라 일반 모드로 충분하다. try: - wb = load_workbook(path, read_only=True, data_only=True) + wb = load_workbook(path, data_only=True) except Exception as exc: # noqa: BLE001 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")): continue + # 헤더 바로 아래 '컬럼 설명' 행 스킵(TikTok export 2번째 행). + # 주문/송장/패키지 ID 는 공백을 포함하지 않는다. 설명 문장은 공백을 + # 포함하므로, 채워진 ID 값에 공백이 있으면 데이터가 아니라 설명 행이다. + if _is_description_row(record): + continue norm_rows.append(record) finally: wb.close() parcels = store.group_parcels(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