fix(malaysia): 확정 시 랙 입력 누락 방지 — 저장+확정 통합

확정 버튼이 랙 폼과 별개라, 입력 후 저장 없이 확정하면 랙/라인 0건으로
빈 확정이 되던 문제 수정.

- 확정 버튼을 랙 폼 안으로 이동(formaction=/rack/finalize)
- 신규 POST /stocktakes/{id}/rack/finalize: replace_rack 후 finalize
- _parse_rack_form 헬퍼 추출(저장/확정 공용)
- 입력 저장은 outline, 확정은 primary 로 구분

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-14 17:36:11 +09:00
parent eacde5b322
commit 536e5a35f1
2 changed files with 53 additions and 19 deletions
+46 -11
View File
@@ -556,20 +556,11 @@ async def stocktake_lines_bulk(
return RedirectResponse(url=f"/malaysia/stocktakes/{stocktake_id}", status_code=303)
@router.post("/stocktakes/{stocktake_id:int}/rack/bulk")
async def stocktake_rack_bulk(
request: Request, stocktake_id: int, user: dict[str, Any] = Depends(_require_user)
) -> RedirectResponse:
"""랙 입력 일괄 저장.
def _parse_rack_form(form: Any) -> list[dict[str, Any]]:
"""랙 폼 병렬배열(rk_cell/rk_sku/rk_upb/rk_box) → 입력 항목 리스트.
form 병렬 배열: rk_cell[], rk_sku[], rk_upb[], rk_box[] (DOM 순서 유지).
SKU 미선택/입수량·박스수 ≤ 0 인 행은 스킵(미입력 행 취급).
저장 시 랙 항목 교체 + SKU별 집계로 daily_stocktake_line 재생성.
"""
st = _store(request)
if st is None:
raise HTTPException(status_code=503, detail="malaysia_stock_db 미설정")
form = await request.form()
cells = form.getlist("rk_cell")
skus = form.getlist("rk_sku")
upbs = form.getlist("rk_upb")
@@ -589,6 +580,22 @@ async def stocktake_rack_bulk(
entries.append(
{"cell_code": cell, "sku_code": code, "units_per_box": u, "box_count": b}
)
return entries
@router.post("/stocktakes/{stocktake_id:int}/rack/bulk")
async def stocktake_rack_bulk(
request: Request, stocktake_id: int, user: dict[str, Any] = Depends(_require_user)
) -> RedirectResponse:
"""랙 입력 일괄 저장.
저장 시 랙 항목 교체 + SKU별 집계로 daily_stocktake_line 재생성.
"""
st = _store(request)
if st is None:
raise HTTPException(status_code=503, detail="malaysia_stock_db 미설정")
form = await request.form()
entries = _parse_rack_form(form)
try:
st.replace_rack(stocktake_id=stocktake_id, entries=entries)
except KeyError:
@@ -598,6 +605,34 @@ async def stocktake_rack_bulk(
return RedirectResponse(url=f"/malaysia/stocktakes/{stocktake_id}", status_code=303)
@router.post("/stocktakes/{stocktake_id:int}/rack/finalize")
async def stocktake_rack_finalize(
request: Request, stocktake_id: int, user: dict[str, Any] = Depends(_require_user)
) -> RedirectResponse:
"""랙 입력 저장 + 즉시 확정(한 번에).
확정 버튼이 랙 폼과 별개라 랙 데이터가 누락되는 문제를 막기 위해,
화면의 랙 입력을 먼저 저장(replace_rack)한 뒤 finalize 한다.
"""
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 미설정")
form = await request.form()
entries = _parse_rack_form(form)
try:
st.replace_rack(stocktake_id=stocktake_id, entries=entries)
st.finalize_stocktake(stocktake_id=stocktake_id, bom_map=_bom_map(request))
except KeyError:
raise HTTPException(status_code=404, detail="재고조사를 찾을 수 없습니다.")
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc))
return RedirectResponse(url=f"/malaysia/stocktakes/{stocktake_id}", status_code=303)
@router.post("/stocktakes/{stocktake_id:int}/lines/{line_id:int}/delete")
async def stocktake_line_delete(
request: Request, stocktake_id: int, line_id: int,
@@ -103,8 +103,13 @@
</div>
{% if stocktake.finalized_at %}<div><span class="erp-muted">확정시각</span> {{ stocktake.finalized_at }}</div>{% endif %}
{% if missing_bom %}<span style="color:#b91c1c;font-weight:600;white-space:nowrap;">⚠ BOM 누락: {{ missing_bom|join(', ') }}</span>{% endif %}
<span class="erp-muted i18n mys-hint">랙 칸에 아이템·입수량(박스당 개수)·박스 수를 입력하면 낱개/콤보 수량이 자동 집계됩니다. 한 칸에 여러 아이템은 + 로 추가하세요.</span>
{% if is_draft %}<button type="submit" class="erp-btn erp-btn-primary">입력 저장</button>{% endif %}
<span class="erp-muted i18n mys-hint">랙 칸에 아이템·입수량(박스당 개수)·박스 수를 입력하면 낱개/콤보 수량이 자동 집계됩니다. 한 칸에 여러 아이템은 + 로 추가하세요. 저장 또는 확정 시 화면 입력이 함께 반영됩니다.</span>
{% if is_draft %}<button type="submit" class="erp-btn erp-btn-outline">입력 저장</button>{% endif %}
{% if is_draft and is_admin %}
<button type="submit" class="erp-btn erp-btn-primary"
formaction="/malaysia/stocktakes/{{ stocktake.id }}/rack/finalize"
onclick="return confirm('현재 입력한 랙 내용을 저장하고 확정합니다. 확정하면 전산 재고와의 차이가 STOCKTAKE 이동으로 기록되고 이후 수정 불가합니다. 계속?');">확정 (전산 반영)</button>
{% endif %}
</div>
<!-- 랙 보드 -->
@@ -151,12 +156,6 @@
<!-- 확정/취소/삭제 -->
<div class="erp-page-actions" style="margin-top:12px;display:flex;gap:8px;flex-wrap:wrap;">
{% if is_draft %}
{% if is_admin %}
<form method="post" action="/malaysia/stocktakes/{{ stocktake.id }}/finalize"
onsubmit="return confirm('확정하면 전산 재고와의 차이가 STOCKTAKE 이동으로 기록되고, 이후 수정 불가합니다. 계속?');">
<button type="submit" class="erp-btn erp-btn-primary" {% if missing_bom %}disabled title="BOM 누락 콤보 해결 필요"{% endif %}>확정 (전산 반영)</button>
</form>
{% endif %}
<form method="post" action="/malaysia/stocktakes/{{ stocktake.id }}/cancel" onsubmit="return confirm('이 조사를 취소합니다. 계속?');">
<button type="submit" class="erp-btn erp-btn-danger">취소</button>
</form>