fix(dispatch): 콤보 BOM 차감 실패 원인별 메시지 분리

기존엔 콤보 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 <noreply@anthropic.com>
This commit is contained in:
2026-06-30 12:08:06 +09:00
parent b440c0eed0
commit 6d5c3f3181
2 changed files with 61 additions and 4 deletions
+37 -4
View File
@@ -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:
+24
View File
@@ -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: