diff --git a/app/modules/malaysia/db.py b/app/modules/malaysia/db.py index 962f997..2f6a758 100644 --- a/app/modules/malaysia/db.py +++ b/app/modules/malaysia/db.py @@ -360,6 +360,28 @@ class MalaysiaStockStore: out.sort(key=lambda x: x["item_code"]) return out + def latest_stocktake(self, *, warehouse_code: str) -> dict[str, Any] | None: + """가장 최근(취소 제외) 재고조사 헤더. 없으면 None.""" + with self._pool.connection() as conn: + row = conn.execute( + "SELECT * FROM daily_stocktake " + "WHERE warehouse_code = %s AND status <> 'cancelled' " + "ORDER BY stocktake_date DESC, id DESC LIMIT 1", + (warehouse_code,), + ).fetchone() + return self._serialize(row) if row else None + + def delete_stocktake(self, *, stocktake_id: int) -> None: + """재고조사 완전 삭제(헤더+라인 CASCADE). 슈퍼관리자 전용 동작. + + 주의: finalize 시 생성된 STOCKTAKE movement 는 별도이므로 같이 지우지 않는다 + (재고 이력 보존). 헤더/라인만 제거. + """ + with self._pool.connection() as conn: + cur = conn.execute("DELETE FROM daily_stocktake WHERE id = %s", (stocktake_id,)) + if cur.rowcount == 0: + raise KeyError(stocktake_id) + def latest_set_quantities(self, *, warehouse_code: str) -> dict[str, Any]: """가장 최근(취소 제외) 재고조사에 입력된 콤보(MY-) 수량. diff --git a/app/modules/malaysia/router.py b/app/modules/malaysia/router.py index fc4cbbd..904895f 100644 --- a/app/modules/malaysia/router.py +++ b/app/modules/malaysia/router.py @@ -124,6 +124,7 @@ def _base_ctx(request: Request, user: dict[str, Any], st: Any, *, wh: str) -> di return { "user": user, "is_admin": is_admin(user), + "is_super": bool(user.get("is_super_admin")), "nav_items": build_erp_nav(user, active="malaysia"), "warehouses": st.list_warehouses(), "selected_wh": wh, @@ -143,12 +144,26 @@ async def index(request: Request) -> HTMLResponse: st, user = guard wh = _selected_wh(request, st) ctx = _base_ctx(request, user, st, wh=wh) + rows = st.stock_status(warehouse_code=wh) + # 마지막 재고조사(취소 제외) 의 낱개 기준 total 과 전산재고 차이 + latest = st.latest_stocktake(warehouse_code=wh) + last_date = latest["stocktake_date"] if latest else None + last_map: dict[str, int] = {} + if latest: + comp = st.compute_result(stocktake_id=latest["id"], bom_map=_bom_map(request)) + last_map = {r["item_code"]: r["total_qty"] for r in comp["rows"]} + for r in rows: + code = r["item_code"] + lq = last_map.get(code) + r["last_stocktake_date"] = last_date + r["last_qty"] = lq + r["diff"] = (lq - r["current_qty"]) if lq is not None else None set_stock = st.latest_set_quantities(warehouse_code=wh) ctx.update( { "page_title": "말레이시아 재고관리", "page_subtitle": f"{wh} · 재고 현황", - "rows": st.stock_status(warehouse_code=wh), + "rows": rows, "set_rows": set_stock["rows"], "set_stocktake_date": set_stock["stocktake_date"], } @@ -410,6 +425,10 @@ async def stocktake_line_delete( async def stocktake_finalize( request: Request, stocktake_id: int, user: dict[str, Any] = Depends(_require_user) ) -> RedirectResponse: + from app.store import is_admin # noqa: WPS433 + + if not is_admin(user): + raise HTTPException(status_code=403, detail="전산 재고 반영은 관리자만 가능합니다.") st = _store(request) if st is None: raise HTTPException(status_code=503, detail="malaysia_stock_db 미설정") @@ -436,6 +455,24 @@ async def stocktake_cancel( return RedirectResponse(url=f"/malaysia/stocktakes/{stocktake_id}", status_code=303) +@router.post("/stocktakes/{stocktake_id:int}/delete") +async def stocktake_delete( + request: Request, stocktake_id: int, user: dict[str, Any] = Depends(_require_user) +) -> RedirectResponse: + """재고조사 완전 삭제 — 슈퍼관리자 전용.""" + if not user.get("is_super_admin"): + raise HTTPException(status_code=403, detail="재고조사 삭제는 슈퍼관리자만 가능합니다.") + st = _store(request) + if st is None: + raise HTTPException(status_code=503, detail="malaysia_stock_db 미설정") + wh = _selected_wh(request, st) + try: + st.delete_stocktake(stocktake_id=stocktake_id) + except KeyError: + raise HTTPException(status_code=404, detail="재고조사를 찾을 수 없습니다.") + return RedirectResponse(url=f"/malaysia/stocktakes?wh={wh}", status_code=303) + + # ════════════════════════════════════════════════════════════ # JSON API # ════════════════════════════════════════════════════════════ diff --git a/app/modules/malaysia/templates/malaysia/index.html b/app/modules/malaysia/templates/malaysia/index.html index b33331e..ceca8fb 100644 --- a/app/modules/malaysia/templates/malaysia/index.html +++ b/app/modules/malaysia/templates/malaysia/index.html @@ -19,13 +19,19 @@
-

재고 현황 — 낱개 기준 ({{ rows|length }})

+

전산 재고 현황 — 낱개 기준 ({{ rows|length }})

현재고 = 입고 − 출고 + 조정 + 재고조사반영 (콤보는 낱개로 분해 반영됨)
- + + + + + + + {% for r in rows %} @@ -34,10 +40,14 @@ + + {% endfor %} {% if not rows %} - + {% endif %}
Item CodeProduct NameCurrent QtyLast Stocktake
Item CodeProduct Name전산재고Last Stocktake조사수량차이
{{ r.item_name }} {{ r.current_qty }} {{ r.last_stocktake_date or '—' }}{{ r.last_qty if r.last_qty is not none else '—' }} + {% if r.diff is not none %}{{ '%+d'|format(r.diff) }}{% else %}—{% endif %} +
표시할 재고가 없습니다.
표시할 재고가 없습니다.
diff --git a/app/modules/malaysia/templates/malaysia/stocktake_detail.html b/app/modules/malaysia/templates/malaysia/stocktake_detail.html index 82aa564..a02a314 100644 --- a/app/modules/malaysia/templates/malaysia/stocktake_detail.html +++ b/app/modules/malaysia/templates/malaysia/stocktake_detail.html @@ -15,7 +15,7 @@ {% block content %}
@@ -88,18 +88,28 @@
- - {% if is_draft %} -
-
- -
-
- + +
+ {% if is_draft %} + {% if is_admin %} + + + + {% else %} + 전산 재고 반영(확정)은 관리자만 가능합니다. + {% endif %} +
+ +
+ {% endif %} + {% if is_super %} +
+
+ {% endif %}
- {% endif %}
diff --git a/app/modules/malaysia/templates/malaysia/stocktakes.html b/app/modules/malaysia/templates/malaysia/stocktakes.html index 0ca6776..bb6c0e1 100644 --- a/app/modules/malaysia/templates/malaysia/stocktakes.html +++ b/app/modules/malaysia/templates/malaysia/stocktakes.html @@ -39,7 +39,14 @@ {% else %}작성중{% endif %} {{ s.created_by or '—' }} - 열기 + + 열기 + {% if is_super %} +
+ +
+ {% endif %} + {% endfor %} {% if not stocktakes %}