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:
@@ -556,20 +556,11 @@ async def stocktake_lines_bulk(
|
|||||||
return RedirectResponse(url=f"/malaysia/stocktakes/{stocktake_id}", status_code=303)
|
return RedirectResponse(url=f"/malaysia/stocktakes/{stocktake_id}", status_code=303)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/stocktakes/{stocktake_id:int}/rack/bulk")
|
def _parse_rack_form(form: Any) -> list[dict[str, Any]]:
|
||||||
async def stocktake_rack_bulk(
|
"""랙 폼 병렬배열(rk_cell/rk_sku/rk_upb/rk_box) → 입력 항목 리스트.
|
||||||
request: Request, stocktake_id: int, user: dict[str, Any] = Depends(_require_user)
|
|
||||||
) -> RedirectResponse:
|
|
||||||
"""랙 입력 일괄 저장.
|
|
||||||
|
|
||||||
form 병렬 배열: rk_cell[], rk_sku[], rk_upb[], rk_box[] (DOM 순서 유지).
|
|
||||||
SKU 미선택/입수량·박스수 ≤ 0 인 행은 스킵(미입력 행 취급).
|
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")
|
cells = form.getlist("rk_cell")
|
||||||
skus = form.getlist("rk_sku")
|
skus = form.getlist("rk_sku")
|
||||||
upbs = form.getlist("rk_upb")
|
upbs = form.getlist("rk_upb")
|
||||||
@@ -589,6 +580,22 @@ async def stocktake_rack_bulk(
|
|||||||
entries.append(
|
entries.append(
|
||||||
{"cell_code": cell, "sku_code": code, "units_per_box": u, "box_count": b}
|
{"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:
|
try:
|
||||||
st.replace_rack(stocktake_id=stocktake_id, entries=entries)
|
st.replace_rack(stocktake_id=stocktake_id, entries=entries)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
@@ -598,6 +605,34 @@ async def stocktake_rack_bulk(
|
|||||||
return RedirectResponse(url=f"/malaysia/stocktakes/{stocktake_id}", status_code=303)
|
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")
|
@router.post("/stocktakes/{stocktake_id:int}/lines/{line_id:int}/delete")
|
||||||
async def stocktake_line_delete(
|
async def stocktake_line_delete(
|
||||||
request: Request, stocktake_id: int, line_id: int,
|
request: Request, stocktake_id: int, line_id: int,
|
||||||
|
|||||||
@@ -103,8 +103,13 @@
|
|||||||
</div>
|
</div>
|
||||||
{% if stocktake.finalized_at %}<div><span class="erp-muted">확정시각</span> {{ stocktake.finalized_at }}</div>{% endif %}
|
{% 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 %}
|
{% 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>
|
<span class="erp-muted i18n mys-hint">랙 칸에 아이템·입수량(박스당 개수)·박스 수를 입력하면 낱개/콤보 수량이 자동 집계됩니다. 한 칸에 여러 아이템은 + 로 추가하세요. 저장 또는 확정 시 화면 입력이 함께 반영됩니다.</span>
|
||||||
{% if is_draft %}<button type="submit" class="erp-btn erp-btn-primary">입력 저장</button>{% endif %}
|
{% 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>
|
</div>
|
||||||
|
|
||||||
<!-- 랙 보드 -->
|
<!-- 랙 보드 -->
|
||||||
@@ -151,12 +156,6 @@
|
|||||||
<!-- 확정/취소/삭제 -->
|
<!-- 확정/취소/삭제 -->
|
||||||
<div class="erp-page-actions" style="margin-top:12px;display:flex;gap:8px;flex-wrap:wrap;">
|
<div class="erp-page-actions" style="margin-top:12px;display:flex;gap:8px;flex-wrap:wrap;">
|
||||||
{% if is_draft %}
|
{% 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('이 조사를 취소합니다. 계속?');">
|
<form method="post" action="/malaysia/stocktakes/{{ stocktake.id }}/cancel" onsubmit="return confirm('이 조사를 취소합니다. 계속?');">
|
||||||
<button type="submit" class="erp-btn erp-btn-danger">취소</button>
|
<button type="submit" class="erp-btn erp-btn-danger">취소</button>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
Reference in New Issue
Block a user