729 lines
28 KiB
JavaScript
Executable File
729 lines
28 KiB
JavaScript
Executable File
// 客户订单管理
|
||
(() => {
|
||
Router.register('/plan-mgmt/customer-order', async () => {
|
||
// 先返回 HTML
|
||
const html = `
|
||
<style>
|
||
/* 完全重新设计页面布局 */
|
||
.view:has(#customer-order-page) {
|
||
padding: 0 !important;
|
||
display: flex;
|
||
flex-direction: column;
|
||
height: 100vh;
|
||
}
|
||
|
||
@keyframes spin {
|
||
from { transform: rotate(0deg); }
|
||
to { transform: rotate(360deg); }
|
||
}
|
||
|
||
#customer-order-page {
|
||
display: flex;
|
||
flex-direction: column;
|
||
height: 100%;
|
||
background: var(--bg);
|
||
}
|
||
|
||
#customer-order-page .page-header {
|
||
padding: 20px;
|
||
background: var(--surface);
|
||
border-bottom: 1px solid var(--border);
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
#customer-order-page .content-area {
|
||
flex: 1;
|
||
padding: 20px;
|
||
overflow: hidden;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
#customer-order-page .table-wrapper {
|
||
flex: 1;
|
||
overflow: auto;
|
||
background: var(--surface);
|
||
border: 1px solid var(--border);
|
||
border-radius: 12px;
|
||
}
|
||
|
||
#customer-order-page table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
margin: 0;
|
||
}
|
||
|
||
#customer-order-page thead {
|
||
background: var(--border);
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 10;
|
||
}
|
||
|
||
#customer-order-page th {
|
||
padding: 8px 12px;
|
||
text-align: left;
|
||
font-weight: 600;
|
||
color: var(--text);
|
||
border-bottom: 2px solid var(--border);
|
||
font-size: 14px;
|
||
}
|
||
|
||
#customer-order-page td {
|
||
padding: 8px 12px;
|
||
border-bottom: 1px solid var(--border);
|
||
color: var(--text);
|
||
font-size: 14px;
|
||
}
|
||
|
||
#customer-order-page tbody tr:last-child td {
|
||
border-bottom: none;
|
||
}
|
||
|
||
#customer-order-page tbody tr:hover {
|
||
background: var(--hover);
|
||
}
|
||
|
||
#customer-order-page .text-center {
|
||
text-align: center;
|
||
color: var(--text-2);
|
||
}
|
||
|
||
#customer-order-page .filtered-row {
|
||
display: none !important;
|
||
}
|
||
</style>
|
||
|
||
<div id="customer-order-page">
|
||
<div class="page-header">
|
||
<h1 style="margin: 0; font-size: 24px;">客户订单管理</h1>
|
||
<div class="page-actions" style="margin-top: 16px; display: flex; gap: 12px; align-items: center; flex-wrap: wrap;">
|
||
<button id="add-order-btn" class="btn btn-primary">新增订单</button>
|
||
<button id="refresh-undelivered-btn" class="btn btn-secondary" title="刷新未交订单数据">
|
||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="vertical-align: middle; margin-right: 4px;">
|
||
<path d="M21.5 2v6h-6M2.5 22v-6h6M2 11.5a10 10 0 0 1 18.8-4.3M22 12.5a10 10 0 0 1-18.8 4.2"/>
|
||
</svg>
|
||
刷新未交订单
|
||
</button>
|
||
<div style="display: flex; align-items: center; gap: 8px; margin-left: auto;">
|
||
<label style="font-size: 14px; color: var(--text-2);">物料筛选:</label>
|
||
<input
|
||
type="text"
|
||
id="material-filter"
|
||
placeholder="输入物料名称筛选..."
|
||
class="input"
|
||
style="width: 200px; padding: 6px 12px; font-size: 14px;"
|
||
/>
|
||
<button id="clear-filter-btn" class="btn btn-secondary" style="padding: 6px 12px;" title="清除筛选">✕</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="content-area">
|
||
<div class="table-wrapper">
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>下单时间</th>
|
||
<th>订单编号</th>
|
||
<th>客户名称</th>
|
||
<th>物料</th>
|
||
<th>订单数量</th>
|
||
<th>已发货</th>
|
||
<th style="color: #f59e0b; font-weight: 700;">未交订单</th>
|
||
<th>单价</th>
|
||
<th>操作</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="order-list">
|
||
<tr>
|
||
<td colspan="9" class="text-center">加载中...</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 新增订单弹窗 -->
|
||
<div id="order-modal" class="modal" style="display:none;">
|
||
<div class="modal-content" style="max-width: 900px; max-height: 90vh; overflow-y: auto;">
|
||
<div class="modal-header">
|
||
<h2 id="modal-title">新增订单</h2>
|
||
<button class="modal-close" onclick="window.CustomerOrder.closeModal()">×</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<form id="order-form">
|
||
<!-- 订单基本信息 -->
|
||
<div style="background: var(--surface); padding: 20px; border-radius: 8px; margin-bottom: 20px; border: 1px solid var(--border);">
|
||
<h3 style="margin: 0 0 15px 0; font-size: 16px; color: var(--text); font-weight: 600;">订单基本信息</h3>
|
||
<div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 15px;">
|
||
<div class="field" style="margin: 0;">
|
||
<label>下单时间 <span style="color: var(--danger);">*</span></label>
|
||
<input type="date" id="order-date" class="input" required />
|
||
</div>
|
||
<div class="field" style="margin: 0;">
|
||
<label>订单编号 <span style="color: var(--danger);">*</span></label>
|
||
<input type="text" id="order-no" class="input" placeholder="如:CGDD001695" required />
|
||
</div>
|
||
<div class="field" style="margin: 0;">
|
||
<label>客户名称 <span style="color: var(--danger);">*</span></label>
|
||
<input type="text" id="customer-name" class="input" placeholder="如:易泰勒" required />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 物料列表 -->
|
||
<div style="background: var(--surface); padding: 20px; border-radius: 8px; border: 1px solid var(--border);">
|
||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">
|
||
<h3 style="margin: 0; font-size: 16px; color: var(--text); font-weight: 600;">物料清单</h3>
|
||
<button type="button" class="btn btn-primary" onclick="window.CustomerOrder.addMaterialRow()">
|
||
➕ 添加物料
|
||
</button>
|
||
</div>
|
||
|
||
<div id="materials-container">
|
||
<!-- 物料行将动态添加到这里 -->
|
||
</div>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button class="btn btn-secondary" onclick="window.CustomerOrder.closeModal()">取消</button>
|
||
<button class="btn btn-primary" onclick="window.CustomerOrder.saveOrder()">保存订单</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
// DOM 渲染后初始化
|
||
setTimeout(() => {
|
||
const addBtn = document.getElementById('add-order-btn');
|
||
if (addBtn) {
|
||
addBtn.addEventListener('click', () => {
|
||
openModal();
|
||
});
|
||
}
|
||
|
||
const refreshBtn = document.getElementById('refresh-undelivered-btn');
|
||
if (refreshBtn) {
|
||
refreshBtn.addEventListener('click', async () => {
|
||
refreshBtn.disabled = true;
|
||
refreshBtn.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="vertical-align: middle; margin-right: 4px; animation: spin 1s linear infinite;"><path d="M21.5 2v6h-6M2.5 22v-6h6M2 11.5a10 10 0 0 1 18.8-4.3M22 12.5a10 10 0 0 1-18.8 4.2"/></svg>刷新中...';
|
||
await loadOrders();
|
||
refreshBtn.disabled = false;
|
||
refreshBtn.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="vertical-align: middle; margin-right: 4px;"><path d="M21.5 2v6h-6M2.5 22v-6h6M2 11.5a10 10 0 0 1 18.8-4.3M22 12.5a10 10 0 0 1-18.8 4.2"/></svg>刷新未交订单';
|
||
API.toast('未交订单数据已更新', 'success');
|
||
});
|
||
}
|
||
|
||
// 物料筛选功能
|
||
const filterInput = document.getElementById('material-filter');
|
||
const clearFilterBtn = document.getElementById('clear-filter-btn');
|
||
|
||
if (filterInput) {
|
||
filterInput.addEventListener('input', (e) => {
|
||
filterOrders(e.target.value);
|
||
});
|
||
}
|
||
|
||
if (clearFilterBtn) {
|
||
clearFilterBtn.addEventListener('click', () => {
|
||
if (filterInput) {
|
||
filterInput.value = '';
|
||
filterOrders('');
|
||
}
|
||
});
|
||
}
|
||
|
||
loadOrders();
|
||
}, 100);
|
||
|
||
return html;
|
||
});
|
||
|
||
async function loadOrders() {
|
||
try {
|
||
console.log('开始加载订单列表...');
|
||
const res = await fetch('/api/customer-orders');
|
||
console.log('API响应状态:', res.status);
|
||
const data = await res.json();
|
||
console.log('订单数据:', data);
|
||
|
||
const tbody = document.getElementById('order-list');
|
||
if (!tbody) {
|
||
console.error('找不到 order-list 元素');
|
||
return;
|
||
}
|
||
|
||
if (!data.list || data.list.length === 0) {
|
||
tbody.innerHTML = '<tr><td colspan="9" class="text-center">暂无数据</td></tr>';
|
||
return;
|
||
}
|
||
|
||
tbody.innerHTML = data.list.map(order => {
|
||
const undeliveredQty = order.undelivered_qty || 0;
|
||
const shippedQty = order.shipped_qty || 0;
|
||
const isManual = order.manual_undelivered_qty !== null && order.manual_undelivered_qty !== undefined;
|
||
const undeliveredStyle = undeliveredQty > 0 ? 'color: #f59e0b; font-weight: 700;' : 'color: #10b981;';
|
||
const manualIcon = isManual ? '<span title="手动设置" style="cursor: help; margin-left: 4px;">✏️</span>' : '';
|
||
|
||
return `
|
||
<tr data-material="${(order.material || '').replace(/"/g, '"')}" data-order-id="${order.id}">
|
||
<td>${order.order_date || '—'}</td>
|
||
<td>${order.order_no || '—'}</td>
|
||
<td>${order.customer_name || '—'}</td>
|
||
<td style="white-space: pre-line; word-break: break-word;">${order.material || '—'}</td>
|
||
<td>${order.quantity || 0}</td>
|
||
<td style="color: #3b82f6;">${shippedQty}</td>
|
||
<td style="${undeliveredStyle}"
|
||
onclick="window.CustomerOrder.editUndelivered(${order.id}, ${undeliveredQty}, ${order.auto_undelivered_qty || 0})"
|
||
title="点击编辑未交订单数量(自动计算: ${order.auto_undelivered_qty || 0})"
|
||
style="cursor: pointer; ${undeliveredStyle}">
|
||
${undeliveredQty}${manualIcon}
|
||
</td>
|
||
<td>${order.unit_price || 0}</td>
|
||
<td>
|
||
<button class="btn-text" onclick="window.CustomerOrder.editOrder(${order.id})">编辑</button>
|
||
<button class="btn-text btn-danger" onclick="window.CustomerOrder.deleteOrder(${order.id})">删除</button>
|
||
</td>
|
||
</tr>
|
||
`}).join('');
|
||
|
||
// 应用当前筛选(如果有)
|
||
const filterInput = document.getElementById('material-filter');
|
||
if (filterInput && filterInput.value) {
|
||
filterOrders(filterInput.value);
|
||
}
|
||
console.log('订单列表加载完成');
|
||
} catch (err) {
|
||
console.error('加载订单失败:', err);
|
||
const tbody = document.getElementById('order-list');
|
||
if (tbody) {
|
||
tbody.innerHTML = '<tr><td colspan="9" class="text-center" style="color: red;">加载失败,请刷新重试</td></tr>';
|
||
}
|
||
API.toast('加载订单失败', 'error');
|
||
}
|
||
}
|
||
|
||
function filterOrders(keyword) {
|
||
const tbody = document.getElementById('order-list');
|
||
if (!tbody) return;
|
||
|
||
const rows = tbody.querySelectorAll('tr');
|
||
const filterText = keyword.toLowerCase().trim();
|
||
|
||
let visibleCount = 0;
|
||
rows.forEach(row => {
|
||
// 跳过空数据行
|
||
if (row.querySelector('.text-center')) {
|
||
return;
|
||
}
|
||
|
||
const material = (row.getAttribute('data-material') || '').toLowerCase();
|
||
|
||
if (!filterText || material.includes(filterText)) {
|
||
row.classList.remove('filtered-row');
|
||
visibleCount++;
|
||
} else {
|
||
row.classList.add('filtered-row');
|
||
}
|
||
});
|
||
|
||
// 如果没有匹配结果,显示提示
|
||
if (visibleCount === 0 && filterText) {
|
||
const existingMsg = tbody.querySelector('.no-filter-results');
|
||
if (!existingMsg) {
|
||
const msgRow = document.createElement('tr');
|
||
msgRow.className = 'no-filter-results';
|
||
msgRow.innerHTML = '<td colspan="9" class="text-center" style="color: var(--text-2);">没有匹配的物料</td>';
|
||
tbody.appendChild(msgRow);
|
||
}
|
||
} else {
|
||
const msgRow = tbody.querySelector('.no-filter-results');
|
||
if (msgRow) {
|
||
msgRow.remove();
|
||
}
|
||
}
|
||
}
|
||
|
||
let materialRowIndex = 0;
|
||
let productList = []; // 缓存BOM产品列表
|
||
|
||
async function loadProductList() {
|
||
try {
|
||
const res = await fetch('/api/bom/products');
|
||
const data = await res.json();
|
||
productList = (data.list || []).map(p => p.product_name).filter(Boolean);
|
||
// 同时从已有订单中提取物料名称(去重)
|
||
const orderRes = await fetch('/api/customer-orders');
|
||
const orderData = await orderRes.json();
|
||
if (orderData.list) {
|
||
for (const o of orderData.list) {
|
||
if (o.material && !productList.includes(o.material)) {
|
||
productList.push(o.material);
|
||
}
|
||
}
|
||
}
|
||
} catch (e) {
|
||
console.error('加载产品列表失败:', e);
|
||
}
|
||
}
|
||
|
||
function buildMaterialOptions() {
|
||
return productList.map(name => `<option value="${name}">`).join('');
|
||
}
|
||
|
||
function addMaterialRow(material = '', quantity = '', unitPrice = '') {
|
||
const container = document.getElementById('materials-container');
|
||
const index = materialRowIndex++;
|
||
const datalistId = `material-options-${index}`;
|
||
|
||
const row = document.createElement('div');
|
||
row.className = 'material-row';
|
||
row.id = `material-row-${index}`;
|
||
row.style.cssText = 'display: grid; grid-template-columns: 2fr 1fr 1fr auto; gap: 12px; margin-bottom: 12px; padding: 16px; background: var(--surface-2); border-radius: 8px; border: 1px solid var(--border);';
|
||
|
||
row.innerHTML = `
|
||
<div class="field" style="margin: 0;">
|
||
<label>物料名称 <span style="color: var(--danger);">*</span></label>
|
||
<input type="text" class="input material-name" list="${datalistId}" placeholder="输入或选择物料名称" value="${material}" required autocomplete="off" />
|
||
<datalist id="${datalistId}">${buildMaterialOptions()}</datalist>
|
||
</div>
|
||
<div class="field" style="margin: 0;">
|
||
<label>数量 <span style="color: var(--danger);">*</span></label>
|
||
<input type="number" class="input material-quantity" placeholder="数量" value="${quantity}" min="1" required />
|
||
</div>
|
||
<div class="field" style="margin: 0;">
|
||
<label>单价 <span style="color: var(--danger);">*</span></label>
|
||
<input type="number" class="input material-price" placeholder="单价" value="${unitPrice}" step="0.01" min="0" required />
|
||
</div>
|
||
<div style="display: flex; align-items: flex-end; padding-bottom: 2px;">
|
||
<button type="button" class="btn btn-danger" onclick="window.CustomerOrder.removeMaterialRow(${index})" style="white-space: nowrap;">
|
||
🗑️ 删除
|
||
</button>
|
||
</div>
|
||
`;
|
||
|
||
container.appendChild(row);
|
||
}
|
||
|
||
function removeMaterialRow(index) {
|
||
const row = document.getElementById(`material-row-${index}`);
|
||
if (row) {
|
||
row.remove();
|
||
}
|
||
|
||
// 如果没有物料行了,至少保留一行
|
||
const container = document.getElementById('materials-container');
|
||
if (container.children.length === 0) {
|
||
addMaterialRow();
|
||
}
|
||
}
|
||
|
||
let currentEditId = null;
|
||
|
||
async function openModal(order = null) {
|
||
// 加载产品列表用于下拉选择
|
||
await loadProductList();
|
||
|
||
const modal = document.getElementById('order-modal');
|
||
const title = document.getElementById('modal-title');
|
||
|
||
// 清空物料列表
|
||
const container = document.getElementById('materials-container');
|
||
container.innerHTML = '';
|
||
materialRowIndex = 0;
|
||
|
||
if (order) {
|
||
// 编辑模式
|
||
title.textContent = '编辑订单';
|
||
currentEditId = order.id;
|
||
|
||
document.getElementById('order-date').value = order.order_date || '';
|
||
document.getElementById('order-no').value = order.order_no || '';
|
||
document.getElementById('customer-name').value = order.customer_name || '';
|
||
|
||
// 添加物料信息
|
||
addMaterialRow(order.material || '', order.quantity || '', order.unit_price || '');
|
||
} else {
|
||
// 新增模式
|
||
title.textContent = '新增订单';
|
||
currentEditId = null;
|
||
document.getElementById('order-form').reset();
|
||
|
||
// 设置默认日期为今天
|
||
const today = new Date().toISOString().split('T')[0];
|
||
document.getElementById('order-date').value = today;
|
||
|
||
// 添加一行空物料
|
||
addMaterialRow();
|
||
}
|
||
|
||
modal.style.display = 'flex';
|
||
}
|
||
|
||
function closeModal() {
|
||
const modal = document.getElementById('order-modal');
|
||
modal.style.display = 'none';
|
||
document.getElementById('order-form').reset();
|
||
|
||
// 清空物料列表
|
||
const container = document.getElementById('materials-container');
|
||
if (container) {
|
||
container.innerHTML = '';
|
||
}
|
||
materialRowIndex = 0;
|
||
}
|
||
|
||
async function saveOrder() {
|
||
const orderDate = document.getElementById('order-date').value.trim();
|
||
const orderNo = document.getElementById('order-no').value.trim();
|
||
const customerName = document.getElementById('customer-name').value.trim();
|
||
|
||
if (!orderDate || !orderNo || !customerName) {
|
||
API.toast('请填写订单基本信息', 'error');
|
||
return;
|
||
}
|
||
|
||
// 收集所有物料信息
|
||
const materials = [];
|
||
const materialRows = document.querySelectorAll('.material-row');
|
||
|
||
if (materialRows.length === 0) {
|
||
API.toast('请至少添加一个物料', 'error');
|
||
return;
|
||
}
|
||
|
||
for (const row of materialRows) {
|
||
const materialName = row.querySelector('.material-name').value.trim();
|
||
const quantity = parseInt(row.querySelector('.material-quantity').value);
|
||
const unitPrice = parseFloat(row.querySelector('.material-price').value);
|
||
|
||
if (!materialName || !quantity || isNaN(unitPrice)) {
|
||
API.toast('请填写所有物料信息', 'error');
|
||
return;
|
||
}
|
||
|
||
if (quantity <= 0) {
|
||
API.toast('物料数量必须大于0', 'error');
|
||
return;
|
||
}
|
||
|
||
if (unitPrice < 0) {
|
||
API.toast('单价不能为负数', 'error');
|
||
return;
|
||
}
|
||
|
||
materials.push({
|
||
material: materialName,
|
||
quantity: quantity,
|
||
unit_price: unitPrice
|
||
});
|
||
}
|
||
|
||
// 保存前确认金额
|
||
let totalAmount = 0;
|
||
const detailLines = materials.map(m => {
|
||
const amount = m.quantity * m.unit_price;
|
||
totalAmount += amount;
|
||
return `<tr><td style="padding:6px 12px;">${m.material}</td><td style="padding:6px 12px;text-align:right;">${m.quantity}</td><td style="padding:6px 12px;text-align:right;">${m.unit_price}</td><td style="padding:6px 12px;text-align:right;font-weight:600;">${amount.toFixed(2)}</td></tr>`;
|
||
}).join('');
|
||
|
||
const confirmed = await new Promise(resolve => {
|
||
const overlay = document.createElement('div');
|
||
overlay.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.5);display:flex;align-items:center;justify-content:center;z-index:10000;';
|
||
overlay.innerHTML = `
|
||
<div style="background:var(--surface,#fff);border-radius:12px;padding:24px;max-width:600px;width:90%;box-shadow:0 8px 32px rgba(0,0,0,0.2);">
|
||
<h3 style="margin:0 0 16px 0;font-size:18px;">确认订单金额</h3>
|
||
<table style="width:100%;border-collapse:collapse;margin-bottom:16px;">
|
||
<thead><tr style="background:var(--border,#eee);">
|
||
<th style="padding:8px 12px;text-align:left;">物料</th>
|
||
<th style="padding:8px 12px;text-align:right;">数量</th>
|
||
<th style="padding:8px 12px;text-align:right;">单价</th>
|
||
<th style="padding:8px 12px;text-align:right;">金额</th>
|
||
</tr></thead>
|
||
<tbody>${detailLines}</tbody>
|
||
<tfoot><tr style="border-top:2px solid var(--border,#ccc);">
|
||
<td colspan="3" style="padding:8px 12px;font-weight:700;font-size:16px;">合计</td>
|
||
<td style="padding:8px 12px;text-align:right;font-weight:700;font-size:16px;color:var(--primary,#1989FA);">${totalAmount.toFixed(2)}</td>
|
||
</tr></tfoot>
|
||
</table>
|
||
<div style="display:flex;justify-content:flex-end;gap:12px;">
|
||
<button id="confirm-cancel" class="btn btn-secondary" style="padding:8px 24px;">取消</button>
|
||
<button id="confirm-ok" class="btn btn-primary" style="padding:8px 24px;">确认保存</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
document.body.appendChild(overlay);
|
||
overlay.querySelector('#confirm-ok').onclick = () => { document.body.removeChild(overlay); resolve(true); };
|
||
overlay.querySelector('#confirm-cancel').onclick = () => { document.body.removeChild(overlay); resolve(false); };
|
||
});
|
||
|
||
if (!confirmed) return;
|
||
|
||
try {
|
||
if (currentEditId) {
|
||
// 编辑模式:只能编辑单条记录
|
||
if (materials.length !== 1) {
|
||
API.toast('编辑模式下只能有一个物料', 'error');
|
||
return;
|
||
}
|
||
|
||
const mat = materials[0];
|
||
const res = await fetch(`/api/customer-orders/${currentEditId}`, {
|
||
method: 'PUT',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
order_date: orderDate,
|
||
order_no: orderNo,
|
||
customer_name: customerName,
|
||
material: mat.material,
|
||
quantity: mat.quantity,
|
||
unit_price: mat.unit_price
|
||
})
|
||
});
|
||
|
||
const data = await res.json();
|
||
|
||
if (res.ok && data.ok) {
|
||
API.toast('更新成功', 'success');
|
||
closeModal();
|
||
await loadOrders();
|
||
} else {
|
||
API.toast(data.error || '更新失败', 'error');
|
||
}
|
||
} else {
|
||
// 新增模式:为每个物料创建一条订单记录
|
||
let successCount = 0;
|
||
for (const mat of materials) {
|
||
const res = await fetch('/api/customer-orders', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
order_date: orderDate,
|
||
order_no: orderNo,
|
||
customer_name: customerName,
|
||
material: mat.material,
|
||
quantity: mat.quantity,
|
||
unit_price: mat.unit_price
|
||
})
|
||
});
|
||
|
||
const data = await res.json();
|
||
|
||
if (res.ok && data.ok) {
|
||
successCount++;
|
||
} else {
|
||
console.error('保存物料失败:', mat.material, data.error);
|
||
}
|
||
}
|
||
|
||
if (successCount === materials.length) {
|
||
API.toast(`订单保存成功,共 ${successCount} 个物料`, 'success');
|
||
closeModal();
|
||
await loadOrders();
|
||
} else if (successCount > 0) {
|
||
API.toast(`部分保存成功(${successCount}/${materials.length})`, 'warning');
|
||
closeModal();
|
||
await loadOrders();
|
||
} else {
|
||
API.toast('保存失败', 'error');
|
||
}
|
||
}
|
||
} catch (err) {
|
||
console.error('保存订单失败:', err);
|
||
API.toast('保存失败', 'error');
|
||
}
|
||
}
|
||
|
||
async function deleteOrder(id) {
|
||
if (!confirm('确定要删除这条订单吗?')) {
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const res = await fetch(`/api/customer-orders/${id}`, {
|
||
method: 'DELETE'
|
||
});
|
||
|
||
const data = await res.json();
|
||
|
||
if (res.ok && data.ok) {
|
||
API.toast('删除成功', 'success');
|
||
await loadOrders();
|
||
} else {
|
||
API.toast(data.error || '删除失败', 'error');
|
||
}
|
||
} catch (err) {
|
||
console.error('删除订单失败:', err);
|
||
API.toast('删除失败', 'error');
|
||
}
|
||
}
|
||
|
||
async function editUndelivered(orderId, currentQty, autoQty) {
|
||
const newQty = prompt(
|
||
`编辑未交订单数量\n\n当前值: ${currentQty}\n自动计算值: ${autoQty}\n\n请输入新的未交订单数量(留空恢复自动计算):`,
|
||
currentQty
|
||
);
|
||
|
||
// 用户取消
|
||
if (newQty === null) {
|
||
return;
|
||
}
|
||
|
||
// 用户输入空值,恢复自动计算
|
||
const finalQty = newQty.trim() === '' ? null : newQty.trim();
|
||
|
||
try {
|
||
const res = await fetch(`/api/customer-orders/${orderId}/undelivered`, {
|
||
method: 'PATCH',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ undelivered_qty: finalQty })
|
||
});
|
||
|
||
const data = await res.json();
|
||
|
||
if (res.ok && data.ok) {
|
||
API.toast(finalQty === null ? '已恢复自动计算' : '未交订单数量已更新', 'success');
|
||
await loadOrders();
|
||
} else {
|
||
API.toast(data.error || '更新失败', 'error');
|
||
}
|
||
} catch (err) {
|
||
console.error('更新未交订单数量失败:', err);
|
||
API.toast('更新失败', 'error');
|
||
}
|
||
}
|
||
|
||
async function editOrder(id) {
|
||
try {
|
||
const res = await fetch('/api/customer-orders');
|
||
const data = await res.json();
|
||
|
||
const order = data.list.find(o => o.id === id);
|
||
if (!order) {
|
||
API.toast('订单不存在', 'error');
|
||
return;
|
||
}
|
||
|
||
openModal(order);
|
||
} catch (err) {
|
||
console.error('加载订单失败:', err);
|
||
API.toast('加载失败', 'error');
|
||
}
|
||
}
|
||
|
||
// 暴露给全局
|
||
window.CustomerOrder = {
|
||
openModal,
|
||
closeModal,
|
||
saveOrder,
|
||
editOrder,
|
||
deleteOrder,
|
||
editUndelivered,
|
||
addMaterialRow,
|
||
removeMaterialRow
|
||
};
|
||
})();
|