feat(dispatch): 말레이시아 TikTok 출고관리 모듈 추가
03_TikTok_Order_Export.xlsx 업로드 → 1박스=1카드 출고 작업 리스트, SKU 피킹 요약, Kagayaku 전달표(A4 인쇄)를 자동 생성. - 1박스 묶음 기준: Package ID > Tracking ID > Order ID, 같은 박스 같은 SKU 합산 - 작업 상태 5단계 토글(AJAX 즉시 저장) + dispatch_logs 기록 - 고객 이름/주소/전화 미저장(파싱 시 폐기, 스키마에 컬럼 없음) - 엑셀은 openpyxl 파싱(pandas 미사용), DB는 dispatch_db 전용 - 인증은 기존 Google OAuth + 신규 권한키 dispatch 재사용 - scripts/sql/dispatch_db_init.sql, docs/DISPATCH_MODULE.md 동봉 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
{# 말레이시아 배송 공용 상단 바: 목록/업로드 + (배치 진입 시) 작업/피킹/전달 서브탭 #}
|
||||
<div class="erp-page-actions" style="display:flex;gap:8px;flex-wrap:wrap;align-items:center;">
|
||||
<a class="erp-btn {% if active_tab=='batches' %}erp-btn-primary{% else %}erp-btn-outline{% endif %}" href="/dispatch/">출고 배치</a>
|
||||
<a class="erp-btn {% if active_tab=='new' %}erp-btn-primary{% else %}erp-btn-outline{% endif %}" href="/dispatch/batches/new">+ 새 업로드</a>
|
||||
|
||||
{% if batch %}
|
||||
<span style="width:1px;height:24px;background:#e2e8f0;margin:0 4px;"></span>
|
||||
<a class="erp-btn {% if active_tab=='work' %}erp-btn-primary{% else %}erp-btn-outline{% endif %}" href="/dispatch/batches/{{ batch.id }}">출고 작업</a>
|
||||
<a class="erp-btn {% if active_tab=='picking' %}erp-btn-primary{% else %}erp-btn-outline{% endif %}" href="/dispatch/batches/{{ batch.id }}/picking">피킹 요약</a>
|
||||
<a class="erp-btn {% if active_tab=='handover' %}erp-btn-primary{% else %}erp-btn-outline{% endif %}" href="/dispatch/batches/{{ batch.id }}/handover">Kagayaku 전달</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -0,0 +1,50 @@
|
||||
{% extends "erp_base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<section class="dsp">
|
||||
{% include "dispatch/_nav.html" %}
|
||||
|
||||
<div class="erp-card">
|
||||
<div class="cpg-card-head" style="display:flex;justify-content:space-between;align-items:center;">
|
||||
<h2>출고 배치 ({{ batches|length }})</h2>
|
||||
<a class="erp-btn erp-btn-primary" href="/dispatch/batches/new">+ 새 업로드</a>
|
||||
</div>
|
||||
<div class="erp-table-wrap">
|
||||
<table class="erp-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>날짜</th><th>플랫폼</th><th>배치명</th>
|
||||
<th style="text-align:right">박스</th>
|
||||
<th style="text-align:right">스캔완료</th>
|
||||
<th>생성시각</th><th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for b in batches %}
|
||||
<tr>
|
||||
<td style="white-space:nowrap;">{{ b.dispatch_date }}</td>
|
||||
<td>{{ b.platform }}</td>
|
||||
<td><a href="/dispatch/batches/{{ b.id }}" style="font-weight:600;">{{ b.batch_name or '(이름없음)' }}</a></td>
|
||||
<td style="text-align:right;">{{ b.parcel_count }}</td>
|
||||
<td style="text-align:right;">
|
||||
{% if b.parcel_count and b.scanned_count == b.parcel_count %}
|
||||
<span style="color:#16a34a;font-weight:600;">{{ b.scanned_count }} ✓</span>
|
||||
{% else %}{{ b.scanned_count }}{% endif %}
|
||||
</td>
|
||||
<td class="erp-muted" style="white-space:nowrap;">{{ b.created_at }}</td>
|
||||
<td style="text-align:right;white-space:nowrap;">
|
||||
<a class="erp-btn erp-btn-outline" href="/dispatch/batches/{{ b.id }}">작업</a>
|
||||
<a class="erp-btn erp-btn-outline" href="/dispatch/batches/{{ b.id }}/picking">피킹</a>
|
||||
<a class="erp-btn erp-btn-outline" href="/dispatch/batches/{{ b.id }}/handover">전달</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% if not batches %}
|
||||
<tr><td colspan="7" class="erp-muted">아직 출고 배치가 없습니다. <a href="/dispatch/batches/new">새 업로드</a>로 시작하세요.</td></tr>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,177 @@
|
||||
{% extends "erp_base.html" %}
|
||||
|
||||
{% block head_extra %}
|
||||
<style>
|
||||
.dsp-toolbar { display:flex;gap:8px;flex-wrap:wrap;align-items:center;margin:12px 0; }
|
||||
.dsp-filter { display:flex;gap:6px;flex-wrap:wrap; }
|
||||
.dsp-search { flex:1 1 220px;min-width:180px; }
|
||||
.dsp-cards { display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:14px; }
|
||||
.dsp-card { border:1px solid #e2e8f0;border-radius:12px;padding:14px;background:#fff;display:flex;flex-direction:column;gap:10px; }
|
||||
.dsp-card.is-done { border-color:#86efac;background:#f0fdf4; }
|
||||
.dsp-no { font-size:20px;font-weight:700;color:#0f172a; }
|
||||
.dsp-meta { font-size:13px;line-height:1.5;color:#334155;word-break:break-all; }
|
||||
.dsp-meta b { color:#0f172a; }
|
||||
.dsp-items { list-style:none;margin:0;padding:8px 0;border-top:1px dashed #e2e8f0;border-bottom:1px dashed #e2e8f0; }
|
||||
.dsp-items li { font-size:16px;font-weight:600;color:#0f172a;padding:2px 0; }
|
||||
.dsp-items .qty { color:#2563eb; }
|
||||
.dsp-status { display:grid;grid-template-columns:1fr 1fr;gap:6px; }
|
||||
.dsp-status .full { grid-column:1 / -1; }
|
||||
.dsp-btn-status { border:1px solid #cbd5e1;border-radius:8px;padding:8px 6px;background:#f1f5f9;color:#475569;
|
||||
font-weight:600;cursor:pointer;text-align:center;line-height:1.25;transition:background .12s,border-color .12s,color .12s; }
|
||||
.dsp-btn-status small { display:block;font-size:10px;font-weight:500;opacity:.8; }
|
||||
.dsp-btn-status.on { background:#16a34a;border-color:#16a34a;color:#fff; }
|
||||
.dsp-btn-status:disabled { opacity:.6;cursor:wait; }
|
||||
.dsp-hidden { display:none !important; }
|
||||
@media (max-width:480px){ .dsp-cards{grid-template-columns:1fr;} }
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<section class="dsp" data-batch="{{ batch.id }}">
|
||||
{% include "dispatch/_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>출고 작업 — 박스 {{ parcels|length }}개</h2>
|
||||
<span class="erp-muted" id="dsp-done-label">완료 {{ done_count }} / {{ parcels|length }}</span>
|
||||
</div>
|
||||
|
||||
<div class="dsp-toolbar">
|
||||
<div class="dsp-filter" id="dsp-filter">
|
||||
<button class="erp-btn erp-btn-primary" data-filter="all" type="button">전체</button>
|
||||
<button class="erp-btn erp-btn-outline" data-filter="unpacked" type="button">미포장</button>
|
||||
<button class="erp-btn erp-btn-outline" data-filter="packed" type="button">포장완료</button>
|
||||
<button class="erp-btn erp-btn-outline" data-filter="label_attached" type="button">라벨부착</button>
|
||||
<button class="erp-btn erp-btn-outline" data-filter="handed_to_kagayaku" type="button">Kagayaku 전달</button>
|
||||
<button class="erp-btn erp-btn-outline" data-filter="courier_scanned" type="button">택배스캔 확인</button>
|
||||
</div>
|
||||
<input class="erp-input dsp-search" id="dsp-search" type="search"
|
||||
placeholder="검색: Order ID / Tracking ID / Package ID / SKU" />
|
||||
</div>
|
||||
|
||||
<div class="dsp-cards" id="dsp-cards">
|
||||
{% for p in parcels %}
|
||||
{% set is_done = p.product_ready and p.packed and p.label_attached and p.handed_to_kagayaku and p.courier_scanned %}
|
||||
<article class="dsp-card {% if is_done %}is-done{% endif %}"
|
||||
data-parcel="{{ p.id }}"
|
||||
data-product_ready="{{ p.product_ready|lower }}"
|
||||
data-packed="{{ p.packed|lower }}"
|
||||
data-label_attached="{{ p.label_attached|lower }}"
|
||||
data-handed_to_kagayaku="{{ p.handed_to_kagayaku|lower }}"
|
||||
data-courier_scanned="{{ p.courier_scanned|lower }}"
|
||||
data-search="{{ (p.order_id ~ ' ' ~ p.package_id ~ ' ' ~ p.tracking_id ~ ' ' ~ (p['items']|map(attribute='seller_sku')|join(' ')))|lower }}">
|
||||
<div style="display:flex;justify-content:space-between;align-items:baseline;">
|
||||
<span class="dsp-no">No. {{ p.seq }}</span>
|
||||
{% if p.shipping_provider %}<span class="erp-badge">{{ p.shipping_provider }}</span>{% endif %}
|
||||
</div>
|
||||
<div class="dsp-meta">
|
||||
<div><b>Order</b> {{ p.order_id or '—' }}</div>
|
||||
<div><b>Package</b> {{ p.package_id or '—' }}</div>
|
||||
<div><b>Tracking</b> {{ p.tracking_id or '—' }}</div>
|
||||
</div>
|
||||
<ul class="dsp-items">
|
||||
{% for it in p['items'] %}
|
||||
<li>{{ it.seller_sku }} <span class="qty">× {{ it.quantity }}</span></li>
|
||||
{% else %}
|
||||
<li class="erp-muted" style="font-weight:400;">상품 없음</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<div class="dsp-status">
|
||||
{% for field in status_fields %}
|
||||
<button type="button"
|
||||
class="dsp-btn-status {% if (field == status_fields[-1]) and (status_fields|length % 2 == 1) %}full{% endif %} {% if p[field] %}on{% endif %}"
|
||||
data-field="{{ field }}"
|
||||
onclick="dspToggle(this)">
|
||||
{{ status_labels[field].ko }}
|
||||
<small>{{ status_labels[field].en }}</small>
|
||||
</button>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</article>
|
||||
{% endfor %}
|
||||
{% if not parcels %}
|
||||
<p class="erp-muted">이 배치에는 박스가 없습니다.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
(function () {
|
||||
var STATUS_FIELDS = {{ status_fields|list|tojson }};
|
||||
|
||||
// ── 상태 토글 (즉시 DB 저장) ──
|
||||
window.dspToggle = function (btn) {
|
||||
var card = btn.closest('.dsp-card');
|
||||
var parcelId = card.getAttribute('data-parcel');
|
||||
var field = btn.getAttribute('data-field');
|
||||
btn.disabled = true;
|
||||
var body = new URLSearchParams();
|
||||
body.set('field', field);
|
||||
fetch('/dispatch/parcels/' + parcelId + '/toggle', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: body.toString(),
|
||||
})
|
||||
.then(function (r) { if (!r.ok) throw new Error('HTTP ' + r.status); return r.json(); })
|
||||
.then(function (data) {
|
||||
btn.classList.toggle('on', data.value);
|
||||
card.setAttribute('data-' + field, data.value ? 'true' : 'false');
|
||||
refreshDone(card);
|
||||
applyFilter();
|
||||
})
|
||||
.catch(function () { alert('저장에 실패했습니다. 다시 시도하세요.'); })
|
||||
.finally(function () { btn.disabled = false; });
|
||||
};
|
||||
|
||||
function refreshDone(card) {
|
||||
var done = STATUS_FIELDS.every(function (f) {
|
||||
return card.getAttribute('data-' + f) === 'true';
|
||||
});
|
||||
card.classList.toggle('is-done', done);
|
||||
updateDoneLabel();
|
||||
}
|
||||
|
||||
function updateDoneLabel() {
|
||||
var cards = document.querySelectorAll('.dsp-card');
|
||||
var done = document.querySelectorAll('.dsp-card.is-done').length;
|
||||
var label = document.getElementById('dsp-done-label');
|
||||
if (label) label.textContent = '완료 ' + done + ' / ' + cards.length;
|
||||
}
|
||||
|
||||
// ── 필터 ──
|
||||
var currentFilter = 'all';
|
||||
function matchesFilter(card) {
|
||||
switch (currentFilter) {
|
||||
case 'all': return true;
|
||||
case 'unpacked': return card.getAttribute('data-packed') !== 'true';
|
||||
default: return card.getAttribute('data-' + currentFilter) === 'true';
|
||||
}
|
||||
}
|
||||
|
||||
function applyFilter() {
|
||||
var term = (document.getElementById('dsp-search').value || '').trim().toLowerCase();
|
||||
document.querySelectorAll('.dsp-card').forEach(function (card) {
|
||||
var ok = matchesFilter(card) &&
|
||||
(term === '' || (card.getAttribute('data-search') || '').indexOf(term) !== -1);
|
||||
card.classList.toggle('dsp-hidden', !ok);
|
||||
});
|
||||
}
|
||||
|
||||
document.getElementById('dsp-filter').addEventListener('click', function (e) {
|
||||
var btn = e.target.closest('[data-filter]');
|
||||
if (!btn) return;
|
||||
currentFilter = btn.getAttribute('data-filter');
|
||||
this.querySelectorAll('[data-filter]').forEach(function (b) {
|
||||
b.classList.toggle('erp-btn-primary', b === btn);
|
||||
b.classList.toggle('erp-btn-outline', b !== btn);
|
||||
});
|
||||
applyFilter();
|
||||
});
|
||||
|
||||
document.getElementById('dsp-search').addEventListener('input', applyFilter);
|
||||
})();
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,77 @@
|
||||
{% extends "erp_base.html" %}
|
||||
|
||||
{% block head_extra %}
|
||||
<style>
|
||||
.dsp-ho-grid { display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:12px;margin:12px 0; }
|
||||
.dsp-ho-stat { border:1px solid #e2e8f0;border-radius:10px;padding:12px;text-align:center;background:#fff; }
|
||||
.dsp-ho-stat .n { font-size:26px;font-weight:700;color:#0f172a; }
|
||||
.dsp-ho-stat .l { font-size:13px;color:#64748b; }
|
||||
.dsp-track td { font-family:ui-monospace,SFMono-Regular,Menlo,monospace;white-space:nowrap; }
|
||||
@media print {
|
||||
.erp-sidebar, .erp-topbar, .erp-page-actions, .dsp-noprint { display:none !important; }
|
||||
.erp-content, .erp-page, .erp-app { margin:0 !important;padding:0 !important;display:block !important; }
|
||||
.erp-card { border:none !important;box-shadow:none !important;padding:0 !important; }
|
||||
body { background:#fff !important; }
|
||||
.dsp-print-head { display:block !important; }
|
||||
@page { size:A4;margin:14mm; }
|
||||
}
|
||||
.dsp-print-head { display:none; }
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<section class="dsp">
|
||||
{% include "dispatch/_nav.html" %}
|
||||
|
||||
<div class="dsp-noprint" style="margin:8px 0;">
|
||||
<button class="erp-btn erp-btn-primary" type="button" onclick="window.print()">🖨 인쇄하기</button>
|
||||
</div>
|
||||
|
||||
<div class="erp-card">
|
||||
<div class="dsp-print-head" style="margin-bottom:10px;">
|
||||
<h2 style="margin:0;">Kagayaku 전달 리스트</h2>
|
||||
</div>
|
||||
<div class="cpg-card-head">
|
||||
<h2>{{ batch.batch_name or 'TikTok 출고' }}</h2>
|
||||
</div>
|
||||
<p class="erp-muted" style="margin:4px 0 4px;">
|
||||
날짜 <b>{{ batch.dispatch_date }}</b> · 플랫폼 <b>{{ batch.platform }}</b>
|
||||
</p>
|
||||
|
||||
<div class="dsp-ho-grid">
|
||||
<div class="dsp-ho-stat">
|
||||
<div class="n">{{ handover.total_boxes }}</div>
|
||||
<div class="l">총 박스 수</div>
|
||||
</div>
|
||||
{% for pv in handover.by_provider %}
|
||||
<div class="dsp-ho-stat">
|
||||
<div class="n">{{ pv.box_count }}</div>
|
||||
<div class="l">{{ pv.provider }}</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<h3 style="margin:16px 0 8px;">Tracking ID 목록 ({{ handover.tracking_list|length }})</h3>
|
||||
<div class="erp-table-wrap">
|
||||
<table class="erp-table dsp-track">
|
||||
<thead>
|
||||
<tr><th style="text-align:right">No</th><th>Tracking ID</th><th>배송사</th><th>Order ID</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for t in handover.tracking_list %}
|
||||
<tr>
|
||||
<td style="text-align:right">{{ t.seq }}</td>
|
||||
<td>{{ t.tracking_id or '—' }}</td>
|
||||
<td>{{ t.shipping_provider or '—' }}</td>
|
||||
<td>{{ t.order_id or '—' }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% if not handover.tracking_list %}
|
||||
<tr><td colspan="4" class="erp-muted">전달할 박스가 없습니다.</td></tr>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,55 @@
|
||||
{% extends "erp_base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<section class="dsp">
|
||||
{% include "dispatch/_nav.html" %}
|
||||
|
||||
<div class="erp-card" style="max-width:640px;">
|
||||
<div class="cpg-card-head"><h2>TikTok 파일 업로드</h2></div>
|
||||
<p class="erp-muted" style="margin:4px 0 16px;">
|
||||
03_TikTok_Order_Export.xlsx 만 있으면 출고 리스트가 자동 생성됩니다.
|
||||
PDF 2개는 보관용(선택)입니다. 고객 이름/주소/전화는 저장하지 않습니다.
|
||||
</p>
|
||||
|
||||
<form method="post" action="/dispatch/batches" enctype="multipart/form-data" style="display:flex;flex-direction:column;gap:14px;">
|
||||
<label style="display:flex;flex-direction:column;gap:4px;">
|
||||
<span>출고 날짜 <span style="color:#dc2626;">*</span></span>
|
||||
<input class="erp-input" type="date" name="dispatch_date" value="{{ today }}" required />
|
||||
</label>
|
||||
|
||||
<label style="display:flex;flex-direction:column;gap:4px;">
|
||||
<span>플랫폼</span>
|
||||
<select class="erp-select" name="platform">
|
||||
<option value="TikTok" selected>TikTok</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label style="display:flex;flex-direction:column;gap:4px;">
|
||||
<span>배치명 <span class="erp-muted">(예: 2026-06-19 TikTok 오전 출고)</span></span>
|
||||
<input class="erp-input" type="text" name="batch_name" placeholder="오전 출고 / 오후 출고 등으로 구분" />
|
||||
</label>
|
||||
|
||||
<label style="display:flex;flex-direction:column;gap:4px;">
|
||||
<span>01_Picking_List.pdf <span class="erp-muted">(선택 · 전체 피킹 참고용)</span></span>
|
||||
<input class="erp-input" type="file" name="picking_pdf" accept="application/pdf" />
|
||||
</label>
|
||||
|
||||
<label style="display:flex;flex-direction:column;gap:4px;">
|
||||
<span>02_Shipping_Label_Packing_Slip.pdf <span class="erp-muted">(선택 · 실제 포장/라벨 원본)</span></span>
|
||||
<input class="erp-input" type="file" name="label_pdf" accept="application/pdf" />
|
||||
</label>
|
||||
|
||||
<label style="display:flex;flex-direction:column;gap:4px;">
|
||||
<span>03_TikTok_Order_Export.xlsx <span style="color:#dc2626;">*</span> <span class="erp-muted">(자동 출고 리스트 기준)</span></span>
|
||||
<input class="erp-input" type="file" name="order_export"
|
||||
accept="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" required />
|
||||
</label>
|
||||
|
||||
<div style="display:flex;gap:8px;margin-top:8px;">
|
||||
<button class="erp-btn erp-btn-primary" type="submit">업로드 후 배치 생성</button>
|
||||
<a class="erp-btn erp-btn-outline" href="/dispatch/">취소</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,41 @@
|
||||
{% extends "erp_base.html" %}
|
||||
|
||||
{% block head_extra %}
|
||||
<style>
|
||||
.dsp-pick td.sku { font-size:18px;font-weight:700;color:#0f172a;white-space:nowrap; }
|
||||
.dsp-pick td.qty { font-size:18px;font-weight:700;text-align:right;color:#2563eb;white-space:nowrap; }
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<section class="dsp">
|
||||
{% include "dispatch/_nav.html" %}
|
||||
|
||||
<div class="erp-card" style="max-width:560px;">
|
||||
<div class="cpg-card-head" style="display:flex;justify-content:space-between;align-items:center;">
|
||||
<h2>피킹 요약 (SKU별 총 수량)</h2>
|
||||
<span class="erp-muted">총 {{ total_qty }}개 · {{ summary|length }} SKU</span>
|
||||
</div>
|
||||
<p class="erp-muted" style="margin:4px 0 12px;">이 화면을 보고 먼저 전체 상품을 꺼내세요.</p>
|
||||
<div class="erp-table-wrap">
|
||||
<table class="erp-table dsp-pick">
|
||||
<thead>
|
||||
<tr><th>Seller SKU</th><th>상품명</th><th style="text-align:right">수량</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for r in summary %}
|
||||
<tr>
|
||||
<td class="sku">{{ r.seller_sku }}</td>
|
||||
<td class="erp-muted">{{ r.product_name or '—' }}</td>
|
||||
<td class="qty">{{ r.total_qty }}개</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% if not summary %}
|
||||
<tr><td colspan="3" class="erp-muted">피킹할 상품이 없습니다.</td></tr>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user