diff --git a/app/modules/cupang/db.py b/app/modules/cupang/db.py index d404673..33ea977 100644 --- a/app/modules/cupang/db.py +++ b/app/modules/cupang/db.py @@ -294,24 +294,38 @@ class CupangDBStore: return [self._product_serialize(r) for r in rows] def upsert_product( - self, *, product_code: str, product_name: str, sort_order: int = 0 + self, + *, + product_code: str, + product_name: str, + coupang_item_code: str | None = None, + sort_order: int = 0, ) -> dict[str, Any]: + """제품 등록/수정. + + coupang_item_code 가 None 이면 기존 값을 유지한다(빈 문자열은 지우기). + """ code = (product_code or "").strip() name = (product_name or "").strip() + cic = None if coupang_item_code is None else coupang_item_code.strip() if not code or not name: raise ValueError("제품코드와 제품명 모두 필요합니다.") with self._pool.connection() as conn: row = conn.execute( """ - INSERT INTO cupang_products (product_code, product_name, sort_order) - VALUES (%s, %s, %s) + INSERT INTO cupang_products + (product_code, product_name, coupang_item_code, sort_order) + VALUES (%(code)s, %(name)s, COALESCE(%(cic)s, ''), %(sort)s) ON CONFLICT (product_code) DO UPDATE - SET product_name = EXCLUDED.product_name, - sort_order = EXCLUDED.sort_order, - active = TRUE + SET product_name = EXCLUDED.product_name, + coupang_item_code = COALESCE( + %(cic)s, cupang_products.coupang_item_code + ), + sort_order = EXCLUDED.sort_order, + active = TRUE RETURNING * """, - (code, name, sort_order), + {"code": code, "name": name, "cic": cic, "sort": sort_order}, ).fetchone() return self._product_serialize(row) @@ -324,6 +338,18 @@ class CupangDBStore: if cur.rowcount == 0: raise KeyError(product_id) + def toggle_product_active(self, *, product_id: int) -> dict[str, Any]: + """활성 ↔ 비활성 뒤집기. 갱신된 행을 반환.""" + with self._pool.connection() as conn: + row = conn.execute( + "UPDATE cupang_products SET active = NOT active " + "WHERE id = %s RETURNING *", + (product_id,), + ).fetchone() + if row is None: + raise KeyError(product_id) + return self._product_serialize(row) + def delete_product(self, *, product_id: int) -> None: with self._pool.connection() as conn: cur = conn.execute( @@ -642,6 +668,7 @@ class CupangDBStore: out["id"] = int(out["id"]) out["active"] = bool(out.get("active", True)) out["sort_order"] = int(out.get("sort_order", 0)) + out["coupang_item_code"] = (out.get("coupang_item_code") or "").strip() for k in ("created_at", "updated_at"): v = out.get(k) if isinstance(v, datetime): @@ -685,6 +712,7 @@ class CupangDBStore: out["id"] = int(out["id"]) out["active"] = bool(out.get("active", True)) out["sort_order"] = int(out.get("sort_order", 0)) + out["coupang_item_code"] = (out.get("coupang_item_code") or "").strip() for k in ("created_at", "updated_at"): v = out.get(k) if isinstance(v, datetime): diff --git a/app/modules/cupang/router.py b/app/modules/cupang/router.py index 7405682..84c6dae 100644 --- a/app/modules/cupang/router.py +++ b/app/modules/cupang/router.py @@ -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, diff --git a/app/modules/cupang/templates/cupang/box_calc.html b/app/modules/cupang/templates/cupang/box_calc.html index 270c0fa..40f11fc 100644 --- a/app/modules/cupang/templates/cupang/box_calc.html +++ b/app/modules/cupang/templates/cupang/box_calc.html @@ -1,6 +1,6 @@ {% extends "erp_base.html" %} -{% block head_extra %}{% endblock %} +{% block head_extra %}{% endblock %} {% block content %} diff --git a/app/modules/cupang/templates/cupang/box_rules.html b/app/modules/cupang/templates/cupang/box_rules.html index b00d165..16e0083 100644 --- a/app/modules/cupang/templates/cupang/box_rules.html +++ b/app/modules/cupang/templates/cupang/box_rules.html @@ -1,6 +1,6 @@ {% extends "erp_base.html" %} -{% block head_extra %}{% endblock %} +{% block head_extra %}{% endblock %} {% block content %} diff --git a/app/modules/cupang/templates/cupang/centers.html b/app/modules/cupang/templates/cupang/centers.html index 92ab701..7446537 100644 --- a/app/modules/cupang/templates/cupang/centers.html +++ b/app/modules/cupang/templates/cupang/centers.html @@ -1,6 +1,6 @@ {% extends "erp_base.html" %} -{% block head_extra %}{% endblock %} +{% block head_extra %}{% endblock %} {% block content %} diff --git a/app/modules/cupang/templates/cupang/detail.html b/app/modules/cupang/templates/cupang/detail.html index 58ec6da..88e05f0 100644 --- a/app/modules/cupang/templates/cupang/detail.html +++ b/app/modules/cupang/templates/cupang/detail.html @@ -1,6 +1,6 @@ {% extends "erp_base.html" %} -{% block head_extra %}{% endblock %} +{% block head_extra %}{% endblock %} {% block content %} diff --git a/app/modules/cupang/templates/cupang/form.html b/app/modules/cupang/templates/cupang/form.html index 2389ff7..a951eda 100644 --- a/app/modules/cupang/templates/cupang/form.html +++ b/app/modules/cupang/templates/cupang/form.html @@ -1,6 +1,6 @@ {% extends "erp_base.html" %} -{% block head_extra %}{% endblock %} +{% block head_extra %}{% endblock %} {% block content %} diff --git a/app/modules/cupang/templates/cupang/index.html b/app/modules/cupang/templates/cupang/index.html index 66502ea..e9fbbb7 100644 --- a/app/modules/cupang/templates/cupang/index.html +++ b/app/modules/cupang/templates/cupang/index.html @@ -1,6 +1,6 @@ {% extends "erp_base.html" %} -{% block head_extra %}{% endblock %} +{% block head_extra %}{% endblock %} {% block content %} diff --git a/app/modules/cupang/templates/cupang/products.html b/app/modules/cupang/templates/cupang/products.html index 84070fb..259caa2 100644 --- a/app/modules/cupang/templates/cupang/products.html +++ b/app/modules/cupang/templates/cupang/products.html @@ -1,6 +1,6 @@ {% extends "erp_base.html" %} -{% block head_extra %}{% endblock %} +{% block head_extra %}{% endblock %} {% block content %} @@ -32,6 +32,8 @@ 제품코드 * + 쿠팡상품코드 + 추가 @@ -57,29 +59,28 @@ 제품명 제품코드 + 쿠팡상품코드 상태 동작 {% for p in products %} - + {{ p.product_name }} {{ p.product_code }} - {% if p.active %}활성{% else %}비활성{% endif %} + {% if p.coupang_item_code %}{{ p.coupang_item_code }}{% else %}—{% endif %} + + + {% if p.active %}활성{% else %}비활성{% endif %} + - {% if p.active %} - - - 비활성 - - {% else %} - - - 활성 - - {% endif %} 삭제 @@ -89,7 +90,7 @@ {% endfor %} {% if not products %} - 등록된 제품명이 없습니다. 왼쪽에서 선택해 등록하세요. + 등록된 제품명이 없습니다. 왼쪽에서 선택해 등록하세요. {% endif %} @@ -136,7 +137,50 @@ })(); + + {% if search_enabled %} + + + + + 쿠팡상품코드 입력 + + 선택한 상품을 등록합니다. 쿠팡상품코드는 비워두면 나중에 채울 수 있습니다. + + + + 취소 + 등록 + + + +
+ 선택한 상품을 등록합니다. 쿠팡상품코드는 비워두면 나중에 채울 수 있습니다. +