406 lines
19 KiB
JavaScript
406 lines
19 KiB
JavaScript
|
|
// 仓库管理 —— 发货单
|
|||
|
|
window.ShipmentOrders = (() => {
|
|||
|
|
let currentUser = null;
|
|||
|
|
let itemIndex = 0;
|
|||
|
|
|
|||
|
|
const isSuperadmin = () => currentUser && currentUser.role === 'superadmin';
|
|||
|
|
const isAdmin = () => currentUser && currentUser.role === 'admin';
|
|||
|
|
|
|||
|
|
const statusLabel = s => ({ pending: '待确认', approved: '已确认', rejected: '已驳回' }[s] || s);
|
|||
|
|
const statusColor = s => ({ pending: '#f59e0b', approved: '#10b981', rejected: '#ef4444' }[s] || '#94a3b8');
|
|||
|
|
|
|||
|
|
const render = async () => {
|
|||
|
|
try {
|
|||
|
|
const res = await fetch('/api/auth/me', { credentials: 'include' });
|
|||
|
|
currentUser = res.ok ? await res.json() : null;
|
|||
|
|
} catch (e) { currentUser = null; }
|
|||
|
|
return `
|
|||
|
|
<div style="padding:20px;max-width:1100px;margin:0 auto">
|
|||
|
|
<div id="shipment-content"></div>
|
|||
|
|
</div>
|
|||
|
|
<style>
|
|||
|
|
.sh-table{width:100%;border-collapse:collapse;font-size:13px}
|
|||
|
|
.sh-table th,.sh-table td{padding:10px 14px;text-align:left;border-bottom:1px solid var(--border)}
|
|||
|
|
.sh-table th{background:var(--bg);font-weight:600;color:var(--text-secondary)}
|
|||
|
|
.sh-table tr:hover td{background:var(--bg)}
|
|||
|
|
.sh-badge{display:inline-block;border-radius:99px;padding:2px 10px;font-size:11px;font-weight:600}
|
|||
|
|
.sh-btn{padding:6px 14px;border:none;border-radius:6px;cursor:pointer;font-size:12px;font-weight:500}
|
|||
|
|
.sh-btn-primary{background:var(--primary,#3b82f6);color:#fff}
|
|||
|
|
.sh-btn-success{background:#10b981;color:#fff}
|
|||
|
|
.sh-btn-danger{background:#ef4444;color:#fff}
|
|||
|
|
.sh-btn-secondary{background:var(--bg);color:var(--text);border:1px solid var(--border)}
|
|||
|
|
.sh-input{width:100%;padding:7px 10px;border:1px solid var(--border);border-radius:6px;background:var(--surface);color:var(--text);font-size:13px;box-sizing:border-box}
|
|||
|
|
select.sh-input{height:36px;padding-top:0;padding-bottom:0;appearance:auto}
|
|||
|
|
.sh-card{background:var(--surface);border-radius:12px;border:1px solid var(--border);overflow:hidden}
|
|||
|
|
</style>
|
|||
|
|
`;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const init = async () => {
|
|||
|
|
stopPolling();
|
|||
|
|
if (!currentUser) {
|
|||
|
|
try {
|
|||
|
|
const res = await fetch('/api/auth/me', { credentials: 'include' });
|
|||
|
|
currentUser = res.ok ? await res.json() : null;
|
|||
|
|
} catch (e) { currentUser = null; }
|
|||
|
|
}
|
|||
|
|
const el = document.getElementById('shipment-content');
|
|||
|
|
if (!el) return;
|
|||
|
|
if (isSuperadmin()) { await renderSuperadminView(el); startPolling(); }
|
|||
|
|
else if (isAdmin()) { await renderAdminView(el); startPolling(); }
|
|||
|
|
else el.innerHTML = '<div style="color:var(--text-secondary);padding:20px">无权限</div>';
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
let _modelList = [];
|
|||
|
|
|
|||
|
|
const loadModels = async () => {
|
|||
|
|
const res = await fetch('/api/warehouse/shipment-models', { credentials: 'include' });
|
|||
|
|
const data = res.ok ? await res.json() : { list: [] };
|
|||
|
|
_modelList = data.list || [];
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const refreshModelDatalist = () => {
|
|||
|
|
const dl = document.getElementById('sh-model-datalist');
|
|||
|
|
if (!dl) return;
|
|||
|
|
dl.innerHTML = _modelList.map(m => `<option value="${m.model}">`).join('');
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const refreshModelManageList = () => {
|
|||
|
|
const box = document.getElementById('sh-model-list');
|
|||
|
|
if (!box) return;
|
|||
|
|
box.innerHTML = _modelList.length === 0
|
|||
|
|
? '<span style="color:var(--text-secondary);font-size:12px">暂无型号</span>'
|
|||
|
|
: _modelList.map(m => `
|
|||
|
|
<span style="display:inline-flex;align-items:center;gap:4px;background:var(--bg);border:1px solid var(--border);border-radius:6px;padding:3px 8px;font-size:12px">
|
|||
|
|
${m.model}
|
|||
|
|
<button onclick="ShipmentOrders.deleteModel(${m.id})" style="background:none;border:none;cursor:pointer;color:#ef4444;font-size:13px;line-height:1;padding:0 2px" title="删除">×</button>
|
|||
|
|
</span>`).join('');
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const addModel = async () => {
|
|||
|
|
const input = document.getElementById('sh-new-model');
|
|||
|
|
if (!input) return;
|
|||
|
|
const model = input.value.trim();
|
|||
|
|
if (!model) return;
|
|||
|
|
const res = await fetch('/api/warehouse/shipment-models', {
|
|||
|
|
method: 'POST', credentials: 'include',
|
|||
|
|
headers: { 'Content-Type': 'application/json' },
|
|||
|
|
body: JSON.stringify({ model })
|
|||
|
|
});
|
|||
|
|
const data = await res.json();
|
|||
|
|
if (res.ok && data.ok) {
|
|||
|
|
_modelList.push({ id: data.id, model: data.model });
|
|||
|
|
_modelList.sort((a, b) => a.model.localeCompare(b.model));
|
|||
|
|
input.value = '';
|
|||
|
|
refreshModelManageList();
|
|||
|
|
refreshModelDatalist();
|
|||
|
|
} else {
|
|||
|
|
alert(data.error || '添加失败');
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const deleteModel = async (id) => {
|
|||
|
|
if (!confirm('确定删除此型号?')) return;
|
|||
|
|
const res = await fetch(`/api/warehouse/shipment-models/${id}`, { method: 'DELETE', credentials: 'include' });
|
|||
|
|
if (res.ok) {
|
|||
|
|
_modelList = _modelList.filter(m => m.id !== id);
|
|||
|
|
refreshModelManageList();
|
|||
|
|
refreshModelDatalist();
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// ── 管理员视图:上传发货数量 ─────────────────────────────────
|
|||
|
|
const renderAdminView = async (el) => {
|
|||
|
|
el.innerHTML = '<div style="color:var(--text-secondary);padding:20px">加载中...</div>';
|
|||
|
|
const [ordersRes] = await Promise.all([
|
|||
|
|
fetch('/api/warehouse/shipment-orders', { credentials: 'include' }),
|
|||
|
|
loadModels()
|
|||
|
|
]);
|
|||
|
|
const data = ordersRes.ok ? await ordersRes.json() : { list: [] };
|
|||
|
|
const list = data.list || [];
|
|||
|
|
|
|||
|
|
itemIndex = 0;
|
|||
|
|
el.innerHTML = `
|
|||
|
|
<datalist id="sh-model-datalist">${_modelList.map(m => `<option value="${m.model}">`).join('')}</datalist>
|
|||
|
|
|
|||
|
|
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:20px">
|
|||
|
|
<span style="font-size:15px;font-weight:600">发货单</span>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 新增发货单 -->
|
|||
|
|
<div class="sh-card" style="padding:20px;margin-bottom:24px">
|
|||
|
|
<div style="font-size:14px;font-weight:600;margin-bottom:16px;color:var(--text)">新增发货数量上传</div>
|
|||
|
|
<div id="sh-items-container"></div>
|
|||
|
|
<button class="sh-btn sh-btn-secondary" style="margin-top:4px" onclick="ShipmentOrders.addItem()">+ 添加产品</button>
|
|||
|
|
<div style="margin-top:16px">
|
|||
|
|
<label style="font-size:12px;color:var(--text-secondary)">备注(可选)</label>
|
|||
|
|
<textarea id="sh-note" class="sh-input" rows="2" placeholder="备注..." style="margin-top:4px;resize:vertical"></textarea>
|
|||
|
|
</div>
|
|||
|
|
<div style="margin-top:16px;display:flex;justify-content:flex-end">
|
|||
|
|
<button class="sh-btn sh-btn-primary" onclick="ShipmentOrders.submit()">提交</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<!-- 历史记录 -->
|
|||
|
|
<div style="font-size:14px;font-weight:600;margin-bottom:12px">我的提交记录</div>
|
|||
|
|
<div class="sh-card">
|
|||
|
|
<table class="sh-table">
|
|||
|
|
<thead><tr>
|
|||
|
|
<th>单号</th><th>产品明细</th><th>备注</th><th>提交时间</th><th>状态</th><th>驳回原因</th><th>确认人</th>
|
|||
|
|
</tr></thead>
|
|||
|
|
<tbody id="sh-admin-tbody">
|
|||
|
|
${buildAdminRows(list)}
|
|||
|
|
</tbody>
|
|||
|
|
</table>
|
|||
|
|
</div>
|
|||
|
|
`;
|
|||
|
|
addItem();
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const buildAdminRows = (list) => {
|
|||
|
|
if (list.length === 0) return '<tr><td colspan="7" style="text-align:center;color:var(--text-secondary);padding:30px">暂无记录</td></tr>';
|
|||
|
|
return list.map(r => `
|
|||
|
|
<tr>
|
|||
|
|
<td style="font-family:monospace;font-size:12px">${r.order_no}</td>
|
|||
|
|
<td style="font-size:12px">${(r.items || []).map(i => `${i.model} × ${i.qty}`).join('<br>')}</td>
|
|||
|
|
<td style="font-size:12px;color:var(--text-secondary)">${r.note || '—'}</td>
|
|||
|
|
<td style="font-size:12px;color:var(--text-secondary)">${(r.created_at || '').slice(0,16)}</td>
|
|||
|
|
<td><span class="sh-badge" style="background:${statusColor(r.status)}22;color:${statusColor(r.status)}">${statusLabel(r.status)}</span></td>
|
|||
|
|
<td style="font-size:12px;color:#ef4444">${r.reject_reason || '—'}</td>
|
|||
|
|
<td style="font-size:12px;color:var(--text-secondary)">${r.confirmed_by || '—'}</td>
|
|||
|
|
</tr>`).join('');
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const adminSilentRefresh = async () => {
|
|||
|
|
const tbody = document.getElementById('sh-admin-tbody');
|
|||
|
|
if (!tbody) return;
|
|||
|
|
const res = await fetch('/api/warehouse/shipment-orders', { credentials: 'include' });
|
|||
|
|
if (!res.ok) return;
|
|||
|
|
const data = await res.json();
|
|||
|
|
tbody.innerHTML = buildAdminRows(data.list || []);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const addItem = () => {
|
|||
|
|
const container = document.getElementById('sh-items-container');
|
|||
|
|
if (!container) return;
|
|||
|
|
const idx = itemIndex++;
|
|||
|
|
const row = document.createElement('div');
|
|||
|
|
row.id = `sh-item-${idx}`;
|
|||
|
|
row.style.cssText = 'display:flex;gap:10px;align-items:flex-end;margin-bottom:10px;flex-wrap:wrap';
|
|||
|
|
row.innerHTML = `
|
|||
|
|
<div style="flex:2 1 160px">
|
|||
|
|
<label style="font-size:12px;color:var(--text-secondary)">产品型号 *</label>
|
|||
|
|
<select class="sh-input sh-model">
|
|||
|
|
<option value="">请选择产品型号</option>
|
|||
|
|
${_modelList.map(m => `<option value="${m.model}">${m.model}</option>`).join('')}
|
|||
|
|
</select>
|
|||
|
|
</div>
|
|||
|
|
<div style="flex:1 1 100px">
|
|||
|
|
<label style="font-size:12px;color:var(--text-secondary)">发货数量 *</label>
|
|||
|
|
<input type="number" class="sh-input sh-qty" placeholder="数量" min="1" />
|
|||
|
|
</div>
|
|||
|
|
<button class="sh-btn sh-btn-danger" style="flex-shrink:0;margin-bottom:2px" onclick="ShipmentOrders.removeItem(${idx})">删除</button>
|
|||
|
|
`;
|
|||
|
|
container.appendChild(row);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const removeItem = (idx) => {
|
|||
|
|
const row = document.getElementById(`sh-item-${idx}`);
|
|||
|
|
if (row) row.remove();
|
|||
|
|
const container = document.getElementById('sh-items-container');
|
|||
|
|
if (container && container.children.length === 0) addItem();
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const submit = async () => {
|
|||
|
|
const rows = document.querySelectorAll('#sh-items-container > div');
|
|||
|
|
const items = [];
|
|||
|
|
for (const row of rows) {
|
|||
|
|
const model = row.querySelector('.sh-model').value.trim();
|
|||
|
|
const qty = parseInt(row.querySelector('.sh-qty').value);
|
|||
|
|
if (!model || !qty || qty <= 0) {
|
|||
|
|
alert('请填写所有产品型号和发货数量(数量须大于0)');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
items.push({ model, qty });
|
|||
|
|
}
|
|||
|
|
if (items.length === 0) { alert('请至少添加一个产品'); return; }
|
|||
|
|
const note = document.getElementById('sh-note')?.value.trim() || '';
|
|||
|
|
const res = await fetch('/api/warehouse/shipment-orders', {
|
|||
|
|
method: 'POST',
|
|||
|
|
credentials: 'include',
|
|||
|
|
headers: { 'Content-Type': 'application/json' },
|
|||
|
|
body: JSON.stringify({ items, note })
|
|||
|
|
});
|
|||
|
|
const data = await res.json();
|
|||
|
|
if (res.ok && data.ok) {
|
|||
|
|
alert(`提交成功,单号:${data.order_no}`);
|
|||
|
|
await renderAdminView(document.getElementById('shipment-content'));
|
|||
|
|
} else {
|
|||
|
|
alert(data.error || '提交失败');
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 生成表格行 HTML(供首次渲染和静默刷新复用)
|
|||
|
|
const buildTableRows = (list) => {
|
|||
|
|
if (list.length === 0) return '<tr><td colspan="9" style="text-align:center;color:var(--text-secondary);padding:30px">暂无记录</td></tr>';
|
|||
|
|
return list.map(r => `
|
|||
|
|
<tr>
|
|||
|
|
<td style="font-family:monospace;font-size:12px">${r.order_no}</td>
|
|||
|
|
<td>${r.created_by}</td>
|
|||
|
|
<td style="font-size:12px">${(r.items || []).map(i => `${i.model} \u00d7 ${i.qty}`).join('<br>')}</td>
|
|||
|
|
<td style="font-size:12px;color:var(--text-secondary)">${r.note || '—'}</td>
|
|||
|
|
<td style="font-size:12px;color:var(--text-secondary)">${(r.created_at || '').slice(0,16)}</td>
|
|||
|
|
<td><span class="sh-badge" style="background:${statusColor(r.status)}22;color:${statusColor(r.status)}">${statusLabel(r.status)}</span></td>
|
|||
|
|
<td style="font-size:12px;color:#ef4444">${r.reject_reason || '—'}</td>
|
|||
|
|
<td style="font-size:12px;color:var(--text-secondary)">${r.confirmed_by || '—'}</td>
|
|||
|
|
<td style="white-space:nowrap">
|
|||
|
|
${r.status === 'pending'
|
|||
|
|
? `<button class="sh-btn sh-btn-success" style="margin-right:6px" onclick="ShipmentOrders.confirm('${r.order_no}','approve')">确认</button>
|
|||
|
|
<button class="sh-btn sh-btn-danger" onclick="ShipmentOrders.showRejectModal('${r.order_no}')">驳回</button>`
|
|||
|
|
: '—'}
|
|||
|
|
</td>
|
|||
|
|
</tr>`).join('');
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const buildModelManageHtml = () => `
|
|||
|
|
<div class="sh-card" style="padding:16px 20px;margin-bottom:20px">
|
|||
|
|
<div style="font-size:13px;font-weight:600;margin-bottom:12px;color:var(--text)">\u4ea7\u54c1\u578b\u53f7\u7ba1\u7406</div>
|
|||
|
|
<div style="display:flex;gap:8px;margin-bottom:10px">
|
|||
|
|
<input id="sh-new-model" class="sh-input" placeholder="\u8f93\u5165\u65b0\u578b\u53f7..." style="flex:1;max-width:260px"
|
|||
|
|
onkeydown="if(event.key==='Enter')ShipmentOrders.addModel()" />
|
|||
|
|
<button class="sh-btn sh-btn-primary" onclick="ShipmentOrders.addModel()">\u6dfb\u52a0</button>
|
|||
|
|
</div>
|
|||
|
|
<div id="sh-model-list" style="display:flex;flex-wrap:wrap;gap:6px">${
|
|||
|
|
_modelList.length === 0
|
|||
|
|
? '<span style="color:var(--text-secondary);font-size:12px">\u6682\u65e0\u578b\u53f7</span>'
|
|||
|
|
: _modelList.map(m => `
|
|||
|
|
<span style="display:inline-flex;align-items:center;gap:4px;background:var(--bg);border:1px solid var(--border);border-radius:6px;padding:3px 8px;font-size:12px">
|
|||
|
|
${m.model}
|
|||
|
|
<button onclick="ShipmentOrders.deleteModel(${m.id})" style="background:none;border:none;cursor:pointer;color:#ef4444;font-size:13px;line-height:1;padding:0 2px" title="\u5220\u9664">\u00d7</button>
|
|||
|
|
</span>`).join('')
|
|||
|
|
}</div>
|
|||
|
|
</div>`;
|
|||
|
|
|
|||
|
|
// ── 超级管理员视图:首次渲染骨架 ────────────────────────────
|
|||
|
|
const renderSuperadminView = async (el) => {
|
|||
|
|
el.innerHTML = '<div style="color:var(--text-secondary);padding:20px">加载中...</div>';
|
|||
|
|
const [res] = await Promise.all([
|
|||
|
|
fetch('/api/warehouse/shipment-orders', { credentials: 'include' }),
|
|||
|
|
loadModels()
|
|||
|
|
]);
|
|||
|
|
const data = res.ok ? await res.json() : { list: [] };
|
|||
|
|
const list = data.list || [];
|
|||
|
|
const pending = list.filter(r => r.status === 'pending').length;
|
|||
|
|
|
|||
|
|
el.innerHTML = `
|
|||
|
|
<div style="display:flex;align-items:center;gap:10px;margin-bottom:20px">
|
|||
|
|
<span style="font-size:15px;font-weight:600">发货单审批</span>
|
|||
|
|
<span id="sh-pending-badge" style="${pending > 0 ? '' : 'display:none;'}background:#f59e0b;color:#fff;border-radius:99px;padding:2px 8px;font-size:11px;font-weight:700">${pending} 待确认</span>
|
|||
|
|
</div>
|
|||
|
|
${buildModelManageHtml()}
|
|||
|
|
<div class="sh-card">
|
|||
|
|
<table class="sh-table">
|
|||
|
|
<thead><tr>
|
|||
|
|
<th>单号</th><th>申请人</th><th>产品明细</th><th>备注</th><th>提交时间</th><th>状态</th><th>驳回原因</th><th>确认人</th><th>操作</th>
|
|||
|
|
</tr></thead>
|
|||
|
|
<tbody id="sh-tbody">${buildTableRows(list)}</tbody>
|
|||
|
|
</table>
|
|||
|
|
</div>
|
|||
|
|
<!-- 驳回弹窗 -->
|
|||
|
|
<div id="sh-reject-modal" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:9999;align-items:center;justify-content:center">
|
|||
|
|
<div style="background:var(--surface);border-radius:12px;padding:24px;width:420px;max-width:90vw;box-shadow:0 8px 32px rgba(0,0,0,.2)">
|
|||
|
|
<div style="font-size:15px;font-weight:600;margin-bottom:16px">驳回原因</div>
|
|||
|
|
<textarea id="sh-reject-reason" class="sh-input" rows="3" placeholder="请填写驳回原因..." style="resize:vertical"></textarea>
|
|||
|
|
<div style="display:flex;justify-content:flex-end;gap:10px;margin-top:16px">
|
|||
|
|
<button class="sh-btn sh-btn-secondary" onclick="ShipmentOrders.closeRejectModal()">取消</button>
|
|||
|
|
<button class="sh-btn sh-btn-danger" onclick="ShipmentOrders.submitReject()">确认驳回</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
`;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// ── 静默刷新(仅更新 tbody 和徽标,不重建整个页面)──────────
|
|||
|
|
const silentRefresh = async () => {
|
|||
|
|
const tbody = document.getElementById('sh-tbody');
|
|||
|
|
const badge = document.getElementById('sh-pending-badge');
|
|||
|
|
if (!tbody) return;
|
|||
|
|
const res = await fetch('/api/warehouse/shipment-orders', { credentials: 'include' });
|
|||
|
|
if (!res.ok) return;
|
|||
|
|
const data = await res.json();
|
|||
|
|
const list = data.list || [];
|
|||
|
|
const pending = list.filter(r => r.status === 'pending').length;
|
|||
|
|
tbody.innerHTML = buildTableRows(list);
|
|||
|
|
if (badge) {
|
|||
|
|
badge.textContent = `${pending} 待确认`;
|
|||
|
|
badge.style.display = pending > 0 ? '' : 'none';
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
let _rejectOrderNo = null;
|
|||
|
|
let _pollTimer = null;
|
|||
|
|
|
|||
|
|
const startPolling = () => {
|
|||
|
|
stopPolling();
|
|||
|
|
_pollTimer = setInterval(async () => {
|
|||
|
|
const el = document.getElementById('shipment-content');
|
|||
|
|
if (!el) { stopPolling(); return; }
|
|||
|
|
if (isSuperadmin()) {
|
|||
|
|
const modal = document.getElementById('sh-reject-modal');
|
|||
|
|
const modalOpen = modal && modal.style.display === 'flex';
|
|||
|
|
if (!modalOpen) await silentRefresh();
|
|||
|
|
} else if (isAdmin()) {
|
|||
|
|
await adminSilentRefresh();
|
|||
|
|
}
|
|||
|
|
}, 5000);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const stopPolling = () => {
|
|||
|
|
if (_pollTimer) { clearInterval(_pollTimer); _pollTimer = null; }
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const showRejectModal = (orderNo) => {
|
|||
|
|
_rejectOrderNo = orderNo;
|
|||
|
|
const modal = document.getElementById('sh-reject-modal');
|
|||
|
|
if (modal) { modal.style.display = 'flex'; document.getElementById('sh-reject-reason').value = ''; }
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const closeRejectModal = () => {
|
|||
|
|
const modal = document.getElementById('sh-reject-modal');
|
|||
|
|
if (modal) modal.style.display = 'none';
|
|||
|
|
_rejectOrderNo = null;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const submitReject = async () => {
|
|||
|
|
const reason = document.getElementById('sh-reject-reason')?.value.trim();
|
|||
|
|
if (!reason) { alert('请填写驳回原因'); return; }
|
|||
|
|
await confirm(_rejectOrderNo, 'reject', reason);
|
|||
|
|
closeRejectModal();
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const confirm = async (orderNo, action, rejectReason = '') => {
|
|||
|
|
const res = await fetch(`/api/warehouse/shipment-orders/${orderNo}/confirm`, {
|
|||
|
|
method: 'PATCH',
|
|||
|
|
credentials: 'include',
|
|||
|
|
headers: { 'Content-Type': 'application/json' },
|
|||
|
|
body: JSON.stringify({ action, reject_reason: rejectReason })
|
|||
|
|
});
|
|||
|
|
const data = await res.json();
|
|||
|
|
if (res.ok && data.ok) {
|
|||
|
|
await silentRefresh();
|
|||
|
|
} else {
|
|||
|
|
alert(data.error || '操作失败');
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
return { render, init, addItem, removeItem, submit, confirm, showRejectModal, closeRejectModal, submitReject, stopPolling, addModel, deleteModel };
|
|||
|
|
})();
|
|||
|
|
|
|||
|
|
Router.register('/warehouse/shipment', async () => {
|
|||
|
|
const html = await window.ShipmentOrders.render();
|
|||
|
|
setTimeout(() => window.ShipmentOrders.init(), 0);
|
|||
|
|
return html;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
Router.onBeforeEach(() => { window.ShipmentOrders.stopPolling(); });
|