feat(malaysia): 세트 BOM을 itemcode_db에서 읽고 입력을 그리드 일괄로 전환

- 세트구성(set_bom) 수동 관리 메뉴/라우트/API/템플릿 제거
- 세트 BOM은 itemcode_db set_components(set_code/single_code/quantity)에서
  읽음 (MalaysiaItemcodeReader, ITEMCODE_DB_URL 재사용)
- db.py: create_set_out/compute_result/finalize_stocktake 에 bom_map 주입식
- 입출고: 낱개 전체 그리드 일괄 등록 + 세트 출고 그리드(BOM 분해)
- 일일 재고조사: 낱개+세트 전체 그리드에 수량만 키인 → 일괄 저장
- malaysia_stock_db.set_bom 테이블은 레거시(미사용)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-11 15:35:42 +09:00
parent f137f2a6a6
commit 6b43d9926d
10 changed files with 395 additions and 465 deletions
@@ -5,65 +5,76 @@
{% set active_tab = 'move' %}
{% include "malaysia/_nav.html" %}
<div style="display:grid;grid-template-columns:1fr 1fr;gap:16px;align-items:start;">
<!-- 입고/출고 등록 (낱개) -->
<div class="erp-card">
<div class="cpg-card-head"><h2>입고 / 출고 등록 (낱개)</h2></div>
<span class="erp-muted">허용: MT-/MX-/MZ- · 차단: MY-/MD-</span>
<form method="post" action="/malaysia/movements" style="margin-top:10px;display:flex;flex-direction:column;gap:8px;">
<input type="hidden" name="warehouse_code" value="{{ selected_wh }}" />
<!-- 낱개 입출고 그리드 일괄 -->
<div class="erp-card">
<div class="cpg-card-head"><h2>입고 / 출고 / 조정 — 낱개 일괄</h2></div>
<span class="erp-muted">전체 낱개 아이템에 수량만 입력. 빈칸/0은 제외. IN/OUT 양수, ADJUST는 ±. (세트는 아래에서 출고)</span>
<form method="post" action="/malaysia/movements/bulk" style="margin-top:10px;">
<input type="hidden" name="warehouse_code" value="{{ selected_wh }}" />
<div style="display:flex;gap:10px;flex-wrap:wrap;align-items:end;margin-bottom:10px;">
<label class="erp-field"><span>이동 종류</span>
<select class="erp-select" name="movement_type" required>
<option value="IN">IN (입고)</option>
<option value="OUT">OUT (출고)</option>
<option value="ADJUST">ADJUST (조정, ± 가능)</option>
<option value="ADJUST">ADJUST (조정 ±)</option>
</select></label>
<label class="erp-field"><span>날짜</span>
<input class="erp-input" type="date" name="movement_date" value="{{ today }}" required /></label>
<label class="erp-field"><span>아이템</span>
<select class="erp-select" name="item_code" required>
<option value="">— 선택 —</option>
{% for it in individual_items %}
<option value="{{ it.item_code }}">{{ it.item_code }} · {{ it.item_name }}</option>
{% endfor %}
</select></label>
<label class="erp-field"><span>수량</span>
<input class="erp-input" type="number" name="qty" required placeholder="IN/OUT 은 양수, ADJUST 는 ±" /></label>
<label class="erp-field"><span>Ref Type</span>
<input class="erp-input" type="text" name="ref_type" placeholder="manual / shopee / lazada ..." /></label>
<input class="erp-input" type="text" name="ref_type" placeholder="manual / shopee / lazada" /></label>
<label class="erp-field"><span>Ref No</span>
<input class="erp-input" type="text" name="ref_no" placeholder="주문번호 등" /></label>
<label class="erp-field"><span>메모</span>
<input class="erp-input" type="text" name="ref_no" /></label>
<label class="erp-field" style="flex:1 1 200px"><span>메모</span>
<input class="erp-input" type="text" name="memo" /></label>
<div class="erp-page-actions"><button type="submit" class="erp-btn erp-btn-primary">등록</button></div>
</form>
</div>
</div>
<div class="erp-table-wrap">
<table class="erp-table">
<thead><tr><th>Item Code</th><th>Product Name</th><th style="width:140px">Qty</th></tr></thead>
<tbody>
{% for it in individual_items %}
<tr>
<td>{{ it.item_code }}</td>
<td>{{ it.item_name }}</td>
<td><input class="erp-input" type="number" name="qty_{{ it.item_code }}" placeholder="0" style="width:120px" /></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="erp-page-actions" style="margin-top:10px;"><button type="submit" class="erp-btn erp-btn-primary">일괄 등록</button></div>
</form>
</div>
<!-- 세트 출고 (BOM 분해) -->
<div class="erp-card">
<div class="cpg-card-head"><h2>세트 출고 (BOM 분해)</h2></div>
<span class="erp-muted">MY- 세트 1건 → set_bom 기준으로 구성품별 OUT 자동 생성</span>
<form method="post" action="/malaysia/movements/set-out" style="margin-top:10px;display:flex;flex-direction:column;gap:8px;">
<input type="hidden" name="warehouse_code" value="{{ selected_wh }}" />
<!-- 세트 출고 그리드 (BOM 분해) -->
<div class="erp-card" style="margin-top:16px;">
<div class="cpg-card-head"><h2>세트 출고 BOM 분해 (itemcode_db)</h2></div>
<span class="erp-muted">세트 수량 입력 시 set_components 기준으로 구성품별 OUT 자동 생성. 빈칸/0 제외.</span>
<form method="post" action="/malaysia/movements/set-out-bulk" style="margin-top:10px;">
<input type="hidden" name="warehouse_code" value="{{ selected_wh }}" />
<div style="display:flex;gap:10px;flex-wrap:wrap;align-items:end;margin-bottom:10px;">
<label class="erp-field"><span>날짜</span>
<input class="erp-input" type="date" name="movement_date" value="{{ today }}" required /></label>
<label class="erp-field"><span>세트</span>
<select class="erp-select" name="set_code" required>
<option value="">— 선택 —</option>
{% for it in set_items %}
<option value="{{ it.item_code }}">{{ it.item_code }} · {{ it.item_name }}</option>
{% endfor %}
</select></label>
<label class="erp-field"><span>세트 수량</span>
<input class="erp-input" type="number" name="set_qty" min="1" required /></label>
<label class="erp-field"><span>Ref No</span>
<input class="erp-input" type="text" name="ref_no" placeholder="주문번호 등" /></label>
<label class="erp-field"><span>메모</span>
<label class="erp-field" style="flex:1 1 200px"><span>메모</span>
<input class="erp-input" type="text" name="memo" /></label>
<div class="erp-page-actions"><button type="submit" class="erp-btn erp-btn-primary">세트 출고</button></div>
</form>
</div>
</div>
<div class="erp-table-wrap">
<table class="erp-table">
<thead><tr><th>Set Code</th><th>Set Name</th><th style="width:140px">Set Qty (OUT)</th></tr></thead>
<tbody>
{% for it in set_items %}
<tr>
<td>{{ it.item_code }}</td>
<td>{{ it.item_name }}</td>
<td><input class="erp-input" type="number" min="0" name="qty_{{ it.item_code }}" placeholder="0" style="width:120px" /></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="erp-page-actions" style="margin-top:10px;"><button type="submit" class="erp-btn erp-btn-primary">세트 출고</button></div>
</form>
</div>
<!-- 이동 이력 -->