feat(malaysia): 슈퍼관리자 재고 데이터 초기화

- 재고 현황 하단 '위험 구역' 카드(슈퍼관리자만 표시)
- POST /malaysia/reset: stock_movement + daily_stocktake(+line) 전체 삭제
  → 전체 창고 현재고/조사값 0. 마스터(창고/아이템) 보존.
- 권한: is_super_admin 아니면 403 (버튼 숨김 + 서버 이중 검사)
- 2단 확인(confirm + RESET 타이핑)
- i18n 추가, malaysia.js 캐시 버전 bump

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-12 11:32:30 +09:00
parent 84d266ea89
commit 8f882ff4cd
5 changed files with 60 additions and 1 deletions
+18
View File
@@ -688,6 +688,24 @@ class MalaysiaStockStore:
head = self.get_stocktake(stocktake_id=stocktake_id, with_lines=True)
return {"stocktake": head, "adjustments": adjustments, "rows": rows}
# ════════════════════════════════════════════════════════════
# 데이터 초기화 (슈퍼관리자 전용 — 라우터에서 권한 검사)
# ════════════════════════════════════════════════════════════
def reset_all_data(self) -> dict[str, int]:
"""전체 창고의 재고 수량 데이터 초기화.
삭제 대상: stock_movement(입고/출고/조정/조사반영) +
daily_stocktake/daily_stocktake_line(재고조사).
보존 대상: warehouses, malaysia_items(마스터).
한 트랜잭션으로 처리. 반환: 삭제 건수.
"""
with self._pool.connection() as conn:
with conn.transaction():
mv = conn.execute("DELETE FROM stock_movement").rowcount or 0
ln = conn.execute("DELETE FROM daily_stocktake_line").rowcount or 0
hd = conn.execute("DELETE FROM daily_stocktake").rowcount or 0
return {"movements": mv, "stocktake_lines": ln, "stocktakes": hd}
# ════════════════════════════════════════════════════════════
# 직렬화
# ════════════════════════════════════════════════════════════
+18
View File
@@ -174,6 +174,24 @@ async def index(request: Request) -> HTMLResponse:
return render_template(request, "malaysia/index.html", ctx)
@router.post("/reset")
async def reset_data(
request: Request, user: dict[str, Any] = Depends(_require_user)
) -> RedirectResponse:
"""전체 창고 재고 수량 데이터 초기화 — 슈퍼관리자 전용.
stock_movement + daily_stocktake(+line) 전체 삭제(현재고/조사값 0).
마스터(warehouses, malaysia_items)는 보존.
"""
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 미설정")
st.reset_all_data()
return RedirectResponse(url="/malaysia/", status_code=303)
# ════════════════════════════════════════════════════════════
# 입고 / 출고 등록 (그리드 일괄)
# ════════════════════════════════════════════════════════════
@@ -2,4 +2,4 @@
<div style="display:flex;justify-content:flex-end;margin-bottom:8px;">
<button type="button" id="mys-lang-toggle" class="erp-btn erp-btn-outline">ENG</button>
</div>
<script src="/static/malaysia.js?v=20260612"></script>
<script src="/static/malaysia.js?v=20260612b"></script>
@@ -80,5 +80,26 @@
</div>
</div>
</div>
{% if is_super %}
<div class="erp-card" style="margin-top:16px;border:1px solid #fecaca;background:#fef2f2;">
<div class="cpg-card-head"><h2 style="color:#b91c1c;">위험 구역 (슈퍼관리자)</h2></div>
<p class="erp-muted" style="margin:4px 0 12px;">
전체 창고의 입출고 이력·재고조사 데이터를 모두 삭제해 재고 수량을 0으로 초기화합니다.
창고/아이템 마스터는 유지됩니다. <strong>되돌릴 수 없습니다.</strong>
</p>
<form method="post" action="/malaysia/reset" onsubmit="return mysConfirmReset()">
<button type="submit" class="erp-btn" style="background:#b91c1c;color:#fff;">재고 초기화</button>
</form>
</div>
<script>
function mysConfirmReset() {
if (!confirm('전체 창고의 입출고 이력과 재고조사 데이터를 모두 삭제합니다.\n재고 수량이 0으로 초기화되며 되돌릴 수 없습니다.\n\n계속하시겠습니까?')) return false;
var t = prompt('정말 초기화하려면 RESET 을 입력하세요.');
if (t !== 'RESET') { alert('초기화가 취소되었습니다.'); return false; }
return true;
}
</script>
{% endif %}
</section>
{% endblock %}
+2
View File
@@ -32,6 +32,8 @@
"재고조사 입력 없음": "No stocktake input",
"표시할 재고가 없습니다.": "No stock to display.",
"콤보 재고 입력이 없습니다.": "No combo stock input.",
"위험 구역 (슈퍼관리자)": "Danger Zone (Super Admin)",
"재고 초기화": "Reset Stock",
// 입출고
"등록 옵션": "Options",
"이동 종류 (낱개)": "Type (single)",