feat(dispatch): 날짜별 사방넷 재고 엑셀 다운로드 + 달력 UI
- 배치 목록을 좌(리스트)/우(달력) 2단 배치
- 달력에서 배치 있는 날짜 클릭 → 그날 전 배치 출고를 낱개로 분해해 다운로드
· _N 배수 × 주문수량 × 콤보 BOM 구성수량, MD-(뚜껑) 제외
· 사방넷 4열 템플릿(A 상품코드[필수]=사방넷코드 / B 가용수량=수량 /
C 불용수량=0 / D 바코드=아이템코드), 주황 헤더, xlsx
- db.stock_aggregate_for_date, export.explode_to_singles/build_stock_xlsx
- 라우터 GET /dispatch/stock-export?date=YYYY-MM-DD
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,18 @@
|
||||
/* 플랫폼 로고: 아이콘마다 여백이 달라 시각 크기를 맞춘다 */
|
||||
.dsp-pf-logo { height:26px;width:auto;max-width:120px;vertical-align:middle;object-fit:contain; }
|
||||
.dsp-pf-logo[src*="shopee"] { height:34px; }
|
||||
/* 좌(리스트) / 우(달력) 2단 */
|
||||
.dsp-layout { display:flex;gap:16px;align-items:flex-start;flex-wrap:wrap; }
|
||||
.dsp-list { flex:1 1 620px;min-width:320px; }
|
||||
.dsp-calwrap { flex:0 1 340px;min-width:280px; }
|
||||
/* 달력 */
|
||||
.dsp-cal-head { display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:8px; }
|
||||
.dsp-cal-grid { display:grid;grid-template-columns:repeat(7,1fr);gap:4px; }
|
||||
.dsp-cal-wd { text-align:center;font-size:12px;color:#94a3b8;padding:2px 0;font-weight:600; }
|
||||
.dsp-cal-day { text-align:center;padding:8px 0;border-radius:8px;font-size:14px;color:#cbd5e1; }
|
||||
.dsp-cal-day.has { color:#0f172a;background:#eef2ff;font-weight:700;cursor:pointer;border:1px solid #c7d2fe; }
|
||||
.dsp-cal-day.has:hover { background:#e0e7ff; }
|
||||
.dsp-cal-day.sel { background:#16a34a !important;border-color:#16a34a !important;color:#fff !important; }
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
@@ -15,7 +27,8 @@
|
||||
<section class="dsp">
|
||||
{% include "dispatch/_nav.html" %}
|
||||
|
||||
<div class="erp-card">
|
||||
<div class="dsp-layout">
|
||||
<div class="erp-card dsp-list">
|
||||
<div class="cpg-card-head" style="display:flex;justify-content:space-between;align-items:center;">
|
||||
<h2><span data-ko="출고 배치" data-en="Batches">출고 배치</span> ({{ batches|length }})</h2>
|
||||
<a class="erp-btn erp-btn-primary" href="/dispatch/batches/new" data-ko="+ 새 업로드" data-en="+ New Upload">+ 새 업로드</a>
|
||||
@@ -72,5 +85,101 @@
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="erp-card dsp-calwrap">
|
||||
<div class="cpg-card-head"><h3 style="margin:0;" data-ko="재고 다운로드 (사방넷)" data-en="Stock Download (Sabangnet)">재고 다운로드 (사방넷)</h3></div>
|
||||
<p class="erp-muted" style="margin:4px 0 10px;font-size:13px;"
|
||||
data-ko="날짜를 클릭하면 그날 출고를 낱개로 분해한 사방넷 재고 엑셀을 받습니다."
|
||||
data-en="Pick a date to download the Sabangnet stock Excel (combos split into singles).">날짜를 클릭하면 그날 출고를 낱개로 분해한 사방넷 재고 엑셀을 받습니다.</p>
|
||||
|
||||
<div class="dsp-cal" id="dsp-cal">
|
||||
<div class="dsp-cal-head">
|
||||
<button type="button" class="erp-btn erp-btn-outline" id="dsp-cal-prev">‹</button>
|
||||
<span id="dsp-cal-title" style="font-weight:700;"></span>
|
||||
<button type="button" class="erp-btn erp-btn-outline" id="dsp-cal-next">›</button>
|
||||
</div>
|
||||
<div class="dsp-cal-grid" id="dsp-cal-grid"></div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top:12px;display:flex;flex-direction:column;gap:8px;">
|
||||
<div class="erp-muted" style="font-size:13px;">
|
||||
<span data-ko="선택한 날짜" data-en="Selected">선택한 날짜</span>:
|
||||
<b id="dsp-cal-sel">—</b>
|
||||
</div>
|
||||
<a id="dsp-cal-dl" class="erp-btn erp-btn-primary" href="#"
|
||||
data-ko="📥 사방넷 재고 엑셀 다운로드" data-en="📥 Download Sabangnet Stock Excel"
|
||||
style="pointer-events:none;opacity:.5;">📥 사방넷 재고 엑셀 다운로드</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
(function () {
|
||||
// 배치가 있는 날짜(YYYY-MM-DD)
|
||||
var BATCH_DATES = {{ batch_dates|tojson }};
|
||||
var dateSet = {};
|
||||
BATCH_DATES.forEach(function (d) { dateSet[d] = true; });
|
||||
|
||||
var grid = document.getElementById('dsp-cal-grid');
|
||||
var title = document.getElementById('dsp-cal-title');
|
||||
var selLabel = document.getElementById('dsp-cal-sel');
|
||||
var dlBtn = document.getElementById('dsp-cal-dl');
|
||||
var selected = null;
|
||||
|
||||
// 초기 표시 월: 가장 최근 배치 날짜 기준(없으면 오늘)
|
||||
var base = BATCH_DATES.length ? new Date(BATCH_DATES[BATCH_DATES.length - 1] + 'T00:00:00') : new Date();
|
||||
var viewY = base.getFullYear(), viewM = base.getMonth();
|
||||
|
||||
var WD = { ko: ['일','월','화','수','목','금','토'], en: ['Su','Mo','Tu','We','Th','Fr','Sa'] };
|
||||
function lang() { return (window.dspLang && window.dspLang() === 'en') ? 'en' : 'ko'; }
|
||||
function pad(n) { return (n < 10 ? '0' : '') + n; }
|
||||
|
||||
function render() {
|
||||
var l = lang();
|
||||
title.textContent = viewY + '.' + pad(viewM + 1);
|
||||
grid.innerHTML = '';
|
||||
WD[l].forEach(function (w) {
|
||||
var c = document.createElement('div');
|
||||
c.className = 'dsp-cal-wd'; c.textContent = w; grid.appendChild(c);
|
||||
});
|
||||
var first = new Date(viewY, viewM, 1).getDay();
|
||||
var days = new Date(viewY, viewM + 1, 0).getDate();
|
||||
for (var i = 0; i < first; i++) grid.appendChild(document.createElement('div'));
|
||||
for (var d = 1; d <= days; d++) {
|
||||
var iso = viewY + '-' + pad(viewM + 1) + '-' + pad(d);
|
||||
var cell = document.createElement('div');
|
||||
cell.className = 'dsp-cal-day';
|
||||
cell.textContent = d;
|
||||
if (dateSet[iso]) {
|
||||
cell.classList.add('has');
|
||||
cell.setAttribute('data-iso', iso);
|
||||
cell.onclick = function () { pick(this.getAttribute('data-iso')); };
|
||||
}
|
||||
if (iso === selected) cell.classList.add('sel');
|
||||
grid.appendChild(cell);
|
||||
}
|
||||
}
|
||||
|
||||
function pick(iso) {
|
||||
selected = iso;
|
||||
selLabel.textContent = iso;
|
||||
dlBtn.href = '/dispatch/stock-export?date=' + encodeURIComponent(iso);
|
||||
dlBtn.style.pointerEvents = '';
|
||||
dlBtn.style.opacity = '';
|
||||
render();
|
||||
}
|
||||
|
||||
document.getElementById('dsp-cal-prev').onclick = function () {
|
||||
viewM--; if (viewM < 0) { viewM = 11; viewY--; } render();
|
||||
};
|
||||
document.getElementById('dsp-cal-next').onclick = function () {
|
||||
viewM++; if (viewM > 11) { viewM = 0; viewY++; } render();
|
||||
};
|
||||
document.addEventListener('dsp:lang', render);
|
||||
render();
|
||||
})();
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user