已发货数量匹配逻辑修改
This commit is contained in:
parent
49d5dd38a1
commit
5e220be272
@ -88,12 +88,16 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
color: var(--text-2);
|
color: var(--text-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#customer-order-page .filtered-row {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div id="customer-order-page">
|
<div id="customer-order-page">
|
||||||
<div class="page-header">
|
<div class="page-header">
|
||||||
<h1 style="margin: 0; font-size: 24px;">客户订单管理</h1>
|
<h1 style="margin: 0; font-size: 24px;">客户订单管理</h1>
|
||||||
<div class="page-actions" style="margin-top: 16px; display: flex; gap: 12px;">
|
<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="add-order-btn" class="btn btn-primary">新增订单</button>
|
||||||
<button id="refresh-undelivered-btn" class="btn btn-secondary" title="刷新未交订单数据">
|
<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;">
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="vertical-align: middle; margin-right: 4px;">
|
||||||
@ -101,6 +105,17 @@
|
|||||||
</svg>
|
</svg>
|
||||||
刷新未交订单
|
刷新未交订单
|
||||||
</button>
|
</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>
|
</div>
|
||||||
|
|
||||||
@ -202,6 +217,25 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 物料筛选功能
|
||||||
|
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();
|
loadOrders();
|
||||||
}, 100);
|
}, 100);
|
||||||
|
|
||||||
@ -230,16 +264,24 @@
|
|||||||
tbody.innerHTML = data.list.map(order => {
|
tbody.innerHTML = data.list.map(order => {
|
||||||
const undeliveredQty = order.undelivered_qty || 0;
|
const undeliveredQty = order.undelivered_qty || 0;
|
||||||
const shippedQty = order.shipped_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 undeliveredStyle = undeliveredQty > 0 ? 'color: #f59e0b; font-weight: 700;' : 'color: #10b981;';
|
||||||
|
const manualIcon = isManual ? '<span title="手动设置" style="cursor: help; margin-left: 4px;">✏️</span>' : '';
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<tr>
|
<tr data-material="${(order.material || '').replace(/"/g, '"')}" data-order-id="${order.id}">
|
||||||
<td>${order.order_date || '—'}</td>
|
<td>${order.order_date || '—'}</td>
|
||||||
<td>${order.order_no || '—'}</td>
|
<td>${order.order_no || '—'}</td>
|
||||||
<td>${order.customer_name || '—'}</td>
|
<td>${order.customer_name || '—'}</td>
|
||||||
<td style="white-space: pre-line; word-break: break-word;">${order.material || '—'}</td>
|
<td style="white-space: pre-line; word-break: break-word;">${order.material || '—'}</td>
|
||||||
<td>${order.quantity || 0}</td>
|
<td>${order.quantity || 0}</td>
|
||||||
<td style="color: #3b82f6;">${shippedQty}</td>
|
<td style="color: #3b82f6;">${shippedQty}</td>
|
||||||
<td style="${undeliveredStyle}">${undeliveredQty}</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>${order.unit_price || 0}</td>
|
||||||
<td>
|
<td>
|
||||||
<button class="btn-text" onclick="window.CustomerOrder.editOrder(${order.id})">编辑</button>
|
<button class="btn-text" onclick="window.CustomerOrder.editOrder(${order.id})">编辑</button>
|
||||||
@ -247,6 +289,12 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
`}).join('');
|
`}).join('');
|
||||||
|
|
||||||
|
// 应用当前筛选(如果有)
|
||||||
|
const filterInput = document.getElementById('material-filter');
|
||||||
|
if (filterInput && filterInput.value) {
|
||||||
|
filterOrders(filterInput.value);
|
||||||
|
}
|
||||||
console.log('订单列表加载完成');
|
console.log('订单列表加载完成');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('加载订单失败:', err);
|
console.error('加载订单失败:', err);
|
||||||
@ -258,6 +306,47 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 materialRowIndex = 0;
|
||||||
let productList = []; // 缓存BOM产品列表
|
let productList = []; // 缓存BOM产品列表
|
||||||
|
|
||||||
@ -572,6 +661,41 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
async function editOrder(id) {
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/api/customer-orders');
|
const res = await fetch('/api/customer-orders');
|
||||||
@ -597,6 +721,7 @@
|
|||||||
saveOrder,
|
saveOrder,
|
||||||
editOrder,
|
editOrder,
|
||||||
deleteOrder,
|
deleteOrder,
|
||||||
|
editUndelivered,
|
||||||
addMaterialRow,
|
addMaterialRow,
|
||||||
removeMaterialRow
|
removeMaterialRow
|
||||||
};
|
};
|
||||||
|
|||||||
@ -261,6 +261,11 @@ def init_db():
|
|||||||
c.execute('ALTER TABLE work_orders ADD COLUMN product_model TEXT')
|
c.execute('ALTER TABLE work_orders ADD COLUMN product_model TEXT')
|
||||||
except Exception:
|
except Exception:
|
||||||
pass # 列已存在
|
pass # 列已存在
|
||||||
|
# 为客户订单表添加手动调整的未交订单数量字段
|
||||||
|
try:
|
||||||
|
c.execute('ALTER TABLE customer_orders ADD COLUMN manual_undelivered_qty INTEGER')
|
||||||
|
except Exception:
|
||||||
|
pass # 列已存在
|
||||||
|
|
||||||
# BOM物料清单表 - 定义产品的物料组成
|
# BOM物料清单表 - 定义产品的物料组成
|
||||||
c.execute('''CREATE TABLE IF NOT EXISTS bom(
|
c.execute('''CREATE TABLE IF NOT EXISTS bom(
|
||||||
@ -5053,7 +5058,7 @@ def get_customer_orders():
|
|||||||
conn = get_db()
|
conn = get_db()
|
||||||
c = conn.cursor()
|
c = conn.cursor()
|
||||||
c.execute('''SELECT id, order_date, order_no, customer_name, material, quantity, unit_price,
|
c.execute('''SELECT id, order_date, order_no, customer_name, material, quantity, unit_price,
|
||||||
created_by, created_at, updated_at
|
manual_undelivered_qty, created_by, created_at, updated_at
|
||||||
FROM customer_orders
|
FROM customer_orders
|
||||||
ORDER BY order_date DESC, id DESC''')
|
ORDER BY order_date DESC, id DESC''')
|
||||||
rows = c.fetchall()
|
rows = c.fetchall()
|
||||||
@ -5072,7 +5077,7 @@ def get_customer_orders():
|
|||||||
order_spec_model = material_lines[1].strip() if len(material_lines) > 1 else ''
|
order_spec_model = material_lines[1].strip() if len(material_lines) > 1 else ''
|
||||||
|
|
||||||
# 查询对账单中已发货数量
|
# 查询对账单中已发货数量
|
||||||
# 匹配策略:根据合同编号,判断物料名称是否互相包含
|
# 匹配策略:根据合同编号,用物料第一行名字判断是否互相包含
|
||||||
shipped_qty = 0
|
shipped_qty = 0
|
||||||
try:
|
try:
|
||||||
if order_material_name:
|
if order_material_name:
|
||||||
@ -5086,7 +5091,8 @@ def get_customer_orders():
|
|||||||
rec_material = (rec_row['material_name'] or '').strip()
|
rec_material = (rec_row['material_name'] or '').strip()
|
||||||
rec_qty = rec_row['quantity'] or 0
|
rec_qty = rec_row['quantity'] or 0
|
||||||
|
|
||||||
# 判断物料名称是否互相包含(忽略大小写)
|
# 只用物料第一行名字判断是否互相包含(忽略大小写)
|
||||||
|
# 客户订单的物料第一行 vs 对账单的物料名称
|
||||||
if rec_material and (
|
if rec_material and (
|
||||||
order_material_name.lower() in rec_material.lower() or
|
order_material_name.lower() in rec_material.lower() or
|
||||||
rec_material.lower() in order_material_name.lower()
|
rec_material.lower() in order_material_name.lower()
|
||||||
@ -5098,7 +5104,14 @@ def get_customer_orders():
|
|||||||
shipped_qty = 0
|
shipped_qty = 0
|
||||||
|
|
||||||
# 计算未交数量
|
# 计算未交数量
|
||||||
undelivered_qty = max(0, order_qty - shipped_qty)
|
auto_undelivered_qty = max(0, order_qty - shipped_qty)
|
||||||
|
|
||||||
|
# 优先使用手动设置的未交订单数量,如果没有则使用自动计算的
|
||||||
|
try:
|
||||||
|
manual_qty = row['manual_undelivered_qty']
|
||||||
|
except (KeyError, IndexError):
|
||||||
|
manual_qty = None
|
||||||
|
undelivered_qty = manual_qty if manual_qty is not None else auto_undelivered_qty
|
||||||
|
|
||||||
orders.append({
|
orders.append({
|
||||||
'id': row['id'],
|
'id': row['id'],
|
||||||
@ -5110,6 +5123,8 @@ def get_customer_orders():
|
|||||||
'unit_price': row['unit_price'],
|
'unit_price': row['unit_price'],
|
||||||
'shipped_qty': shipped_qty,
|
'shipped_qty': shipped_qty,
|
||||||
'undelivered_qty': undelivered_qty,
|
'undelivered_qty': undelivered_qty,
|
||||||
|
'manual_undelivered_qty': manual_qty,
|
||||||
|
'auto_undelivered_qty': auto_undelivered_qty,
|
||||||
'created_by': row['created_by'],
|
'created_by': row['created_by'],
|
||||||
'created_at': row['created_at'],
|
'created_at': row['created_at'],
|
||||||
'updated_at': row['updated_at']
|
'updated_at': row['updated_at']
|
||||||
@ -5220,6 +5235,49 @@ def update_customer_order(order_id):
|
|||||||
return jsonify({'ok': True, 'message': '订单更新成功'})
|
return jsonify({'ok': True, 'message': '订单更新成功'})
|
||||||
|
|
||||||
|
|
||||||
|
@app.patch('/api/customer-orders/<int:order_id>/undelivered')
|
||||||
|
@require_login
|
||||||
|
@require_any_role('superadmin')
|
||||||
|
def update_undelivered_qty(order_id):
|
||||||
|
"""更新客户订单的未交订单数量"""
|
||||||
|
data = request.get_json() or {}
|
||||||
|
undelivered_qty = data.get('undelivered_qty')
|
||||||
|
|
||||||
|
# 如果传入null或空,则清除手动设置,恢复自动计算
|
||||||
|
if undelivered_qty is None or undelivered_qty == '':
|
||||||
|
manual_qty = None
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
manual_qty = int(undelivered_qty)
|
||||||
|
if manual_qty < 0:
|
||||||
|
return jsonify({'error': '未交订单数量不能为负数'}), 400
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
return jsonify({'error': '未交订单数量必须是整数'}), 400
|
||||||
|
|
||||||
|
conn = get_db()
|
||||||
|
c = conn.cursor()
|
||||||
|
|
||||||
|
# 检查订单是否存在
|
||||||
|
c.execute('SELECT id FROM customer_orders WHERE id=?', (order_id,))
|
||||||
|
if not c.fetchone():
|
||||||
|
conn.close()
|
||||||
|
return jsonify({'error': '订单不存在'}), 404
|
||||||
|
|
||||||
|
# 更新未交订单数量
|
||||||
|
now = get_beijing_time()
|
||||||
|
c.execute('''UPDATE customer_orders
|
||||||
|
SET manual_undelivered_qty=?, updated_at=?
|
||||||
|
WHERE id=?''',
|
||||||
|
(manual_qty, now, order_id))
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
log('update_undelivered_qty', f'订单ID: {order_id}, 未交数量: {manual_qty}')
|
||||||
|
|
||||||
|
return jsonify({'ok': True, 'message': '未交订单数量已更新'})
|
||||||
|
|
||||||
|
|
||||||
@app.delete('/api/customer-orders/<int:order_id>')
|
@app.delete('/api/customer-orders/<int:order_id>')
|
||||||
@require_login
|
@require_login
|
||||||
@require_any_role('superadmin')
|
@require_any_role('superadmin')
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user