From 6d5c3f31812f59ff1a50a97afd797682c6bc4cd8 Mon Sep 17 00:00:00 2001 From: king Date: Tue, 30 Jun 2026 12:08:06 +0900 Subject: [PATCH] =?UTF-8?q?fix(dispatch):=20=EC=BD=A4=EB=B3=B4=20BOM=20?= =?UTF-8?q?=EC=B0=A8=EA=B0=90=20=EC=8B=A4=ED=8C=A8=20=EC=9B=90=EC=9D=B8?= =?UTF-8?q?=EB=B3=84=20=EB=A9=94=EC=8B=9C=EC=A7=80=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 기존엔 콤보 BOM 누락 시 (a)set_components 미등록 (b)세트는 등록됐으나 유효 낱개(MT/MX/MZ) 구성품 없음 (c)itemcode_ro 권한/조회 실패 를 한 덩어리로 "콤보 BOM 이 없어 차감할 수 없습니다"로만 알려, 실제 원인을 못 짚어 같은 에러가 반복됐다. - itemcode.py: set_codes_present() 추가 — set_components 의 MY- 세트 코드 전체를 구성품 유효성 무관하게 조회. (a)/(b) 구분용. - router.py stock_deduct: 누락 콤보를 등록여부로 분기, 권한/조회 실패는 별도 안내. 정확한 원인과 조치를 메시지에 노출. Co-Authored-By: Claude Opus 4.8 --- app/modules/dispatch/router.py | 41 ++++++++++++++++++++++++++++---- app/modules/malaysia/itemcode.py | 24 +++++++++++++++++++ 2 files changed, 61 insertions(+), 4 deletions(-) diff --git a/app/modules/dispatch/router.py b/app/modules/dispatch/router.py index 31eb161..ba3a63e 100644 --- a/app/modules/dispatch/router.py +++ b/app/modules/dispatch/router.py @@ -519,10 +519,43 @@ async def stock_deduct( bom_map, _sab = _itemcode_bom_and_sabangnet(request) missing = [c for c in combos if c not in bom_map] if missing: - raise HTTPException( - status_code=400, - detail="콤보 BOM 이 없어 차감할 수 없습니다(세트구성 먼저 등록): " + ", ".join(sorted(missing)), - ) + # 'BOM 누락'을 원인별로 쪼개 정확히 알린다. 그동안 이 메시지가 + # (a)미등록 (b)구성품없음 (c)권한/조회실패 를 한 덩어리로 가려서 + # "고쳐도 또 난다"가 반복됐다. + reader = getattr(request.app.state, "malaysia_itemcode", None) + registered: set[str] = set() + if reader is not None and getattr(reader, "enabled", False): + try: + registered = reader.set_codes_present() + except Exception: # noqa: BLE001 + logger.exception("set_codes_present 조회 실패") + query_failed = bool(getattr(reader, "last_error", "")) if reader else False + + if query_failed: + # set_components 조회 자체가 실패 — 보통 itemcode_ro 권한 문제. + raise HTTPException( + status_code=400, + detail=( + "itemcode_db 의 세트구성(set_components) 조회에 실패해 콤보를 분해할 수 " + "없습니다. 읽기 전용 역할(itemcode_ro)에 set_components SELECT 권한이 " + f"있는지 확인하세요. (상세: {reader.last_error})" + ), + ) + + no_components = sorted(c for c in missing if c in registered) + not_registered = sorted(c for c in missing if c not in registered) + parts: list[str] = [] + if not_registered: + parts.append( + "세트구성 미등록(itemcode_db.set_components 에 코드 자체가 없음): " + + ", ".join(not_registered) + ) + if no_components: + parts.append( + "세트는 등록됐으나 유효한 낱개(MT-/MX-/MZ-) 구성품이 없습니다" + "(구성품 코드·수량 확인): " + ", ".join(no_components) + ) + raise HTTPException(status_code=400, detail="콤보 BOM 문제 — " + " / ".join(parts)) # 마커 선점(레이스/재실행 차단). 충돌 시 이미 차감됨. try: diff --git a/app/modules/malaysia/itemcode.py b/app/modules/malaysia/itemcode.py index 8ef8d76..5ea991a 100644 --- a/app/modules/malaysia/itemcode.py +++ b/app/modules/malaysia/itemcode.py @@ -96,6 +96,30 @@ class MalaysiaItemcodeReader: ) return out + def set_codes_present(self) -> set[str]: + """set_components 에 등록된 MY- 세트 코드 전체(구성품 유효성 무관). + + set_bom_map() 은 낱개(MT/MX/MZ) 구성품이 하나도 없는 세트를 결과에서 + 떨어뜨린다. 그래서 'BOM 누락'이 두 가지 원인을 가린다: + (a) set_components 에 set_code 자체가 없음 → 세트구성 미등록 + (b) set_code 는 있으나 유효 낱개 구성품이 없음 → 구성품 점검 필요 + 이 메서드로 (a)/(b)를 구분해 정확한 에러 메시지를 만든다. + """ + if not self.enabled or not self._pool: + return set() + try: + with self._pool.connection() as conn: + rows = conn.execute( + "SELECT DISTINCT set_code FROM set_components " + "WHERE set_code LIKE 'MY-%'" + ).fetchall() + self.last_error = "" + except Exception as exc: # noqa: BLE001 — 모듈을 죽이지 않음 + self.last_error = f"{type(exc).__name__}: {exc}" + logger.exception("itemcode set_components set_code 조회 실패") + return set() + return {str(r["set_code"]).strip().upper() for r in rows} + def name_map(self) -> dict[str, str]: """낱개+세트 코드 → 이름(itemcode_db 기준). 표시 보강용(선택).""" if not self.enabled or not self._pool: