feat(malaysia): 창고 랙 보기 페이지
최근(취소 제외) 재고조사의 랙 입력을 칸별로 한눈에 보는 읽기전용 메뉴. 각 칸에 상품명·박스수×입수량=총개수, 칸 합계(∑) 표시. - nav: '창고 랙 보기' 탭 추가(active_tab='rack') - router: GET /malaysia/rack — latest_stocktake → list_rack_entries 칸별 그룹 - 템플릿 rack_view.html — Side A/B 세로 적층, 칸별 아이템 목록 - i18n 키 추가(Rack View 등) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -174,6 +174,40 @@ async def index(request: Request) -> HTMLResponse:
|
||||
return render_template(request, "malaysia/index.html", ctx)
|
||||
|
||||
|
||||
@router.get("/rack", response_class=HTMLResponse)
|
||||
async def rack_view(request: Request) -> HTMLResponse:
|
||||
"""창고 랙 보기 — 최근(취소 제외) 재고조사의 랙 입력을 칸별로 한눈에.
|
||||
|
||||
읽기 전용. 각 칸에 어떤 상품이 몇 박스(× 입수량 = 총개수) 있는지 표시.
|
||||
"""
|
||||
from app.main import render_template # noqa: WPS433
|
||||
|
||||
guard = _guard(request)
|
||||
if not isinstance(guard, tuple):
|
||||
return guard
|
||||
st, user = guard
|
||||
wh = _selected_wh(request, st)
|
||||
latest = st.latest_stocktake(warehouse_code=wh)
|
||||
rack_by_cell: dict[str, list[dict[str, Any]]] = {}
|
||||
names = st.item_name_map()
|
||||
if latest:
|
||||
for e in st.list_rack_entries(stocktake_id=latest["id"]):
|
||||
e["item_name"] = names.get(e["sku_code"], e["sku_code"])
|
||||
e["total"] = int(e["units_per_box"]) * int(e["box_count"])
|
||||
rack_by_cell.setdefault(e["cell_code"], []).append(e)
|
||||
ctx = _base_ctx(request, user, st, wh=wh)
|
||||
ctx.update(
|
||||
{
|
||||
"page_title": "말레이시아 재고관리 — 창고 랙",
|
||||
"page_subtitle": f"{wh} · 랙 보기",
|
||||
"rack_layout": store.rack_layout(),
|
||||
"rack_by_cell": rack_by_cell,
|
||||
"stocktake": latest,
|
||||
}
|
||||
)
|
||||
return render_template(request, "malaysia/rack_view.html", ctx)
|
||||
|
||||
|
||||
@router.post("/reset")
|
||||
async def reset_data(
|
||||
request: Request, user: dict[str, Any] = Depends(_require_user)
|
||||
|
||||
@@ -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=20260614a"></script>
|
||||
<script src="/static/malaysia.js?v=20260614b"></script>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<a class="erp-btn {% if active_tab=='status' %}erp-btn-primary{% else %}erp-btn-outline{% endif %}" href="/malaysia/?wh={{ selected_wh }}">재고 현황</a>
|
||||
<a class="erp-btn {% if active_tab=='move' %}erp-btn-primary{% else %}erp-btn-outline{% endif %}" href="/malaysia/movements?wh={{ selected_wh }}">입출고</a>
|
||||
<a class="erp-btn {% if active_tab=='stocktake' %}erp-btn-primary{% else %}erp-btn-outline{% endif %}" href="/malaysia/stocktakes?wh={{ selected_wh }}">일일 재고조사</a>
|
||||
<a class="erp-btn {% if active_tab=='rack' %}erp-btn-primary{% else %}erp-btn-outline{% endif %}" href="/malaysia/rack?wh={{ selected_wh }}">창고 랙 보기</a>
|
||||
|
||||
<span style="flex:1 1 auto"></span>
|
||||
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
{% extends "erp_base.html" %}
|
||||
|
||||
{% block head_extra %}
|
||||
<style>
|
||||
.rv-board { display:flex; flex-direction:column; gap:18px; }
|
||||
.rv-side { width:100%; }
|
||||
.rv-side h3 { text-align:center; margin:0 0 8px; }
|
||||
.rv-row { display:grid; grid-template-columns:repeat(6, 1fr); gap:6px; margin-bottom:6px; }
|
||||
.rv-cell {
|
||||
border:1px solid #cbd5e1; border-radius:6px; padding:5px;
|
||||
background:#f8fafc; min-height:80px; display:flex; flex-direction:column; min-width:0;
|
||||
}
|
||||
.rv-cell.is-empty { background:#fff; }
|
||||
.rv-cell-head {
|
||||
display:flex; align-items:center; justify-content:space-between;
|
||||
font-size:11px; font-weight:600; color:#475569; margin-bottom:4px;
|
||||
border-bottom:1px solid #e2e8f0; padding-bottom:3px;
|
||||
}
|
||||
.rv-cell-head .rv-total { color:#0f172a; white-space:nowrap; }
|
||||
.rv-items { display:flex; flex-direction:column; gap:3px; }
|
||||
.rv-item { font-size:11px; line-height:1.25; }
|
||||
.rv-item .rv-name { font-weight:600; color:#0f172a; }
|
||||
.rv-item .rv-calc { color:#475569; }
|
||||
.rv-item .rv-qty { color:#2563eb; font-weight:600; }
|
||||
.rv-empty-note { font-size:11px; color:#cbd5e1; }
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<section class="mys mys-compact">
|
||||
{% include "malaysia/_i18n.html" %}
|
||||
{% set active_tab = 'rack' %}
|
||||
{% include "malaysia/_nav.html" %}
|
||||
|
||||
<div class="erp-card">
|
||||
<div class="cpg-card-head" style="display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:8px;">
|
||||
<h2>창고 랙 보기</h2>
|
||||
<span class="erp-muted">
|
||||
{% if stocktake %}
|
||||
<span class="i18n">기준 재고조사</span>: #{{ stocktake.id }} · {{ stocktake.stocktake_date }}
|
||||
{% if stocktake.status=='finalized' %}<span class="erp-badge erp-badge-success">확정</span>
|
||||
{% elif stocktake.status=='cancelled' %}<span class="erp-badge erp-badge-neutral">취소</span>
|
||||
{% else %}<span class="erp-badge erp-badge-inverse">작성중</span>{% endif %}
|
||||
{% else %}
|
||||
<span class="i18n">표시할 재고조사가 없습니다.</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="rv-board" style="margin-top:10px;">
|
||||
{% for side in rack_layout %}
|
||||
<div class="rv-side">
|
||||
<h3>Side {{ side.side }}</h3>
|
||||
{% for row in side.rows %}
|
||||
<div class="rv-row">
|
||||
{% for col in row.cols %}
|
||||
{% for cell in col %}
|
||||
{% set entries = rack_by_cell.get(cell, []) %}
|
||||
{% set ns = namespace(total=0) %}
|
||||
{% for e in entries %}{% set ns.total = ns.total + e.total %}{% endfor %}
|
||||
<div class="rv-cell {% if not entries %}is-empty{% endif %}">
|
||||
<div class="rv-cell-head">
|
||||
<span>{{ cell }}</span>
|
||||
<span class="rv-total">∑ {{ ns.total }}</span>
|
||||
</div>
|
||||
<div class="rv-items">
|
||||
{% for e in entries %}
|
||||
<div class="rv-item">
|
||||
<span class="rv-name" data-en="{{ e.item_name }}" data-ko="{{ ko_names.get(e.sku_code, e.item_name) }}">{{ e.item_name }}</span><br>
|
||||
<span class="rv-calc">{{ e.box_count }}<span class="i18n">박스</span> × {{ e.units_per_box }} = <span class="rv-qty">{{ e.total }}</span></span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% if not entries %}<span class="rv-empty-note">—</span>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
||||
@@ -17,6 +17,10 @@
|
||||
"재고 현황": "Stock Status",
|
||||
"입출고": "In / Out",
|
||||
"일일 재고조사": "Daily Stocktake",
|
||||
"창고 랙 보기": "Rack View",
|
||||
"기준 재고조사": "Based on stocktake",
|
||||
"표시할 재고조사가 없습니다.": "No stocktake to display.",
|
||||
"박스": "box",
|
||||
"창고": "Warehouse",
|
||||
"날짜": "Date",
|
||||
"메모": "Memo",
|
||||
|
||||
Reference in New Issue
Block a user