feat(cupang): 제품 카탈로그에 쿠팡상품코드 + 상태 토글
- cupang_products.coupang_item_code 컬럼 추가 (마이그레이션 004, init.sql 동기화)
- 설정 화면: 쿠팡상품코드 열 표시/정렬, 수기 추가 폼 입력란 추가
- 선택 등록 시 바로 등록하지 않고 쿠팡상품코드 입력 팝업을 먼저 표시
- 상태 배지를 클릭 토글로 변경 (POST /cupang/api/products/{id}/toggle)
- 빈 쿠팡상품코드는 "미입력"으로 처리해 기존 값을 덮어쓰지 않음
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -846,7 +846,11 @@ async def product_bulk(
|
||||
items: list[dict[str, Any]] = Body(..., embed=True),
|
||||
user: dict[str, Any] = Depends(_require_user),
|
||||
) -> JSONResponse:
|
||||
"""선택한 상품들을 일괄 등록(upsert). body: {"items":[{"code","name"}, ...]}."""
|
||||
"""선택한 상품들을 일괄 등록(upsert).
|
||||
|
||||
body: {"items":[{"code","name","coupang_item_code"}, ...]}
|
||||
coupang_item_code 키가 없으면 기존 값을 유지한다.
|
||||
"""
|
||||
store = _store(request)
|
||||
if store is None:
|
||||
raise HTTPException(status_code=503, detail="cupang_db 미설정")
|
||||
@@ -854,10 +858,15 @@ async def product_bulk(
|
||||
for it in items:
|
||||
code = str(it.get("code") or "").strip()
|
||||
name = str(it.get("name") or "").strip()
|
||||
cic = it.get("coupang_item_code")
|
||||
if not code or not name:
|
||||
continue
|
||||
try:
|
||||
store.upsert_product(product_code=code, product_name=name)
|
||||
store.upsert_product(
|
||||
product_code=code,
|
||||
product_name=name,
|
||||
coupang_item_code=(None if cic is None else str(cic)),
|
||||
)
|
||||
added += 1
|
||||
except ValueError:
|
||||
continue
|
||||
@@ -869,6 +878,7 @@ async def product_upsert(
|
||||
request: Request,
|
||||
product_code: str = Form(...),
|
||||
product_name: str = Form(...),
|
||||
coupang_item_code: str = Form(""),
|
||||
sort_order: int = Form(0),
|
||||
user: dict[str, Any] = Depends(_require_user),
|
||||
) -> RedirectResponse:
|
||||
@@ -877,7 +887,11 @@ async def product_upsert(
|
||||
raise HTTPException(status_code=503, detail="cupang_db 미설정")
|
||||
try:
|
||||
store.upsert_product(
|
||||
product_code=product_code, product_name=product_name, sort_order=sort_order
|
||||
product_code=product_code,
|
||||
product_name=product_name,
|
||||
# 빈 값은 "미입력" 으로 보고 기존 쿠팡상품코드를 유지한다.
|
||||
coupang_item_code=(coupang_item_code.strip() or None),
|
||||
sort_order=sort_order,
|
||||
)
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=400, detail=str(exc))
|
||||
@@ -903,6 +917,23 @@ async def product_set_active(
|
||||
return RedirectResponse(url="/cupang/products", status_code=303)
|
||||
|
||||
|
||||
@router.post("/api/products/{product_id:int}/toggle")
|
||||
async def product_toggle_active(
|
||||
request: Request,
|
||||
product_id: int,
|
||||
user: dict[str, Any] = Depends(_require_user),
|
||||
) -> JSONResponse:
|
||||
"""상태 배지 클릭 → 활성/비활성 토글(페이지 새로고침 없이)."""
|
||||
store = _store(request)
|
||||
if store is None:
|
||||
raise HTTPException(status_code=503, detail="cupang_db 미설정")
|
||||
try:
|
||||
product = store.toggle_product_active(product_id=product_id)
|
||||
except KeyError:
|
||||
raise HTTPException(status_code=404, detail="제품을 찾을 수 없습니다.")
|
||||
return JSONResponse({"ok": True, "active": bool(product["active"])})
|
||||
|
||||
|
||||
@router.post("/products/{product_id:int}/delete")
|
||||
async def product_delete(
|
||||
request: Request,
|
||||
|
||||
Reference in New Issue
Block a user