fix(malaysia): 콤보 입고 차단 — 낱개 분리 입고 안내
- 콤보(MY-)는 입고 불가 구조. 입고 선택 + 콤보 수량 입력 시 차단. - 클라이언트: 제출 전 alert 안내(낱개로 분리 입고) - 서버: 동일 조건 400 (JS 우회 대비) - 세트 movement 는 항상 OUT 유지(create_set_out 원복) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -233,6 +233,7 @@ class MalaysiaStockStore:
|
|||||||
"""세트 주문 출고 → BOM(itemcode_db)으로 분해해 구성품별 OUT movement 생성.
|
"""세트 주문 출고 → BOM(itemcode_db)으로 분해해 구성품별 OUT movement 생성.
|
||||||
|
|
||||||
예: MY-0002 1세트 출고 → MT-0320, MT-0550 ... 각각 OUT.
|
예: MY-0002 1세트 출고 → MT-0320, MT-0550 ... 각각 OUT.
|
||||||
|
콤보는 입고 불가(낱개로만 입고). 출고만 BOM 분해한다.
|
||||||
bom_map 은 라우터가 itemcode 리더에서 받아 주입. 구성 없으면 ValueError.
|
bom_map 은 라우터가 itemcode 리더에서 받아 주입. 구성 없으면 ValueError.
|
||||||
"""
|
"""
|
||||||
sc = store.validate_bom_set_code(set_code)
|
sc = store.validate_bom_set_code(set_code)
|
||||||
|
|||||||
@@ -222,6 +222,7 @@ async def movements_apply(request: Request, user: dict[str, Any] = Depends(_requ
|
|||||||
|
|
||||||
- 낱개(individual): 선택한 movement_type 으로 등록. IN/OUT 양수만, ADJUST ±(0 스킵).
|
- 낱개(individual): 선택한 movement_type 으로 등록. IN/OUT 양수만, ADJUST ±(0 스킵).
|
||||||
- 세트(set): 항상 OUT 으로 처리 — BOM(itemcode_db) 분해해 구성품별 OUT 생성.
|
- 세트(set): 항상 OUT 으로 처리 — BOM(itemcode_db) 분해해 구성품별 OUT 생성.
|
||||||
|
콤보는 입고 불가(낱개로 분리 입고). IN 선택 + 콤보 수량 입력 시 400 안내.
|
||||||
빈칸/0 은 스킵. 코드별 에러는 모아서 부분 적용 후 안내.
|
빈칸/0 은 스킵. 코드별 에러는 모아서 부분 적용 후 안내.
|
||||||
"""
|
"""
|
||||||
st = _store(request)
|
st = _store(request)
|
||||||
@@ -238,6 +239,20 @@ async def movements_apply(request: Request, user: dict[str, Any] = Depends(_requ
|
|||||||
memo = str(form.get("memo") or "").strip()
|
memo = str(form.get("memo") or "").strip()
|
||||||
bmap = _bom_map(request)
|
bmap = _bom_map(request)
|
||||||
|
|
||||||
|
# 콤보는 입고 불가 — IN 선택 + 세트 수량 입력 시 차단(낱개로 분리 입고 안내)
|
||||||
|
if mtype == "IN":
|
||||||
|
set_codes_keyed = [
|
||||||
|
it["item_code"]
|
||||||
|
for it in st.list_items(kind="set")
|
||||||
|
if str(form.get(f"qty_{it['item_code']}") or "").strip() not in ("", "0")
|
||||||
|
]
|
||||||
|
if set_codes_keyed:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400,
|
||||||
|
detail="콤보 상품은 입고할 수 없습니다. 낱개로 분리해서 입고하세요. "
|
||||||
|
"(입고 불가 콤보: " + ", ".join(set_codes_keyed) + ")",
|
||||||
|
)
|
||||||
|
|
||||||
applied = 0
|
applied = 0
|
||||||
errors: list[str] = []
|
errors: list[str] = []
|
||||||
|
|
||||||
@@ -264,7 +279,7 @@ async def movements_apply(request: Request, user: dict[str, Any] = Depends(_requ
|
|||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
errors.append(f"{code}: {exc}")
|
errors.append(f"{code}: {exc}")
|
||||||
|
|
||||||
# 세트 — 항상 OUT(BOM 분해)
|
# 세트 — 항상 OUT(BOM 분해). 콤보 입고는 위에서 이미 차단됨.
|
||||||
for it in st.list_items(kind="set"):
|
for it in st.list_items(kind="set"):
|
||||||
code = it["item_code"]
|
code = it["item_code"]
|
||||||
raw = str(form.get(f"qty_{code}") or "").strip()
|
raw = str(form.get(f"qty_{code}") or "").strip()
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
{% include "malaysia/_nav.html" %}
|
{% include "malaysia/_nav.html" %}
|
||||||
|
|
||||||
<!-- 3분할: 좌(옵션) · 중(낱개) · 우(세트), 버튼 하나 -->
|
<!-- 3분할: 좌(옵션) · 중(낱개) · 우(세트), 버튼 하나 -->
|
||||||
<form method="post" action="/malaysia/movements/apply">
|
<form method="post" action="/malaysia/movements/apply" onsubmit="return mysCheckComboIn(this)">
|
||||||
<input type="hidden" name="warehouse_code" value="{{ selected_wh }}" />
|
<input type="hidden" name="warehouse_code" value="{{ selected_wh }}" />
|
||||||
|
|
||||||
<div style="display:grid;grid-template-columns:280px 1fr 1fr;gap:16px;align-items:start;">
|
<div style="display:grid;grid-template-columns:280px 1fr 1fr;gap:16px;align-items:start;">
|
||||||
@@ -89,6 +89,22 @@
|
|||||||
</form>
|
</form>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
// 콤보(MY-)는 입고 불가 — IN 선택 + 콤보 수량 입력 시 안내 후 제출 차단
|
||||||
|
function mysCheckComboIn(form) {
|
||||||
|
var type = form.querySelector('[name=movement_type]').value;
|
||||||
|
if (type !== 'IN') return true;
|
||||||
|
var keyed = [];
|
||||||
|
form.querySelectorAll('input[name^="qty_MY-"]').forEach(function (inp) {
|
||||||
|
var v = (inp.value || '').trim();
|
||||||
|
if (v !== '' && v !== '0') keyed.push(inp.name.replace('qty_', ''));
|
||||||
|
});
|
||||||
|
if (keyed.length) {
|
||||||
|
alert('콤보 상품은 입고할 수 없습니다.\n낱개로 분리해서 입고하세요.\n\n입고 불가 콤보: ' + keyed.join(', '));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
function mysDownloadMovements(btn) {
|
function mysDownloadMovements(btn) {
|
||||||
var form = btn.closest('form');
|
var form = btn.closest('form');
|
||||||
var type = form.querySelector('[name=movement_type]').value;
|
var type = form.querySelector('[name=movement_type]').value;
|
||||||
|
|||||||
Reference in New Issue
Block a user