ERP/frontend/js/components/outsourcing-orders.js

430 lines
16 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 委外工单管理
(() => {
let orderList = [];
let customerOrders = [];
let currentPage = 1;
const pageSize = 20;
const statusMap = {
'pending': { text: '待发料', color: 'var(--warning)' },
'issued': { text: '已发料', color: 'var(--info)' },
'completed': { text: '已完成', color: 'var(--success)' },
'cancelled': { text: '已取消', color: 'var(--text-2)' }
};
Router.register('/outsourcing-mgmt/orders', async () => {
const html = `
<style>
#outsourcing-orders-page .page-header {
padding: 20px;
background: var(--surface);
border-bottom: 1px solid var(--border);
flex-shrink: 0;
}
#outsourcing-orders-page .content-area {
flex: 1;
padding: 20px;
overflow: hidden;
display: flex;
flex-direction: column;
}
#outsourcing-orders-page .table-wrapper {
flex: 1;
overflow: auto;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 12px;
}
#outsourcing-orders-page table {
width: 100%;
border-collapse: collapse;
margin: 0;
}
#outsourcing-orders-page thead {
background: var(--border);
position: sticky;
top: 0;
z-index: 10;
}
#outsourcing-orders-page th {
padding: 8px 12px;
text-align: left;
font-weight: 600;
color: var(--text);
border-bottom: 2px solid var(--border);
font-size: 14px;
white-space: nowrap;
}
#outsourcing-orders-page td {
padding: 8px 12px;
border-bottom: 1px solid var(--border);
color: var(--text);
font-size: 14px;
}
#outsourcing-orders-page tbody tr:hover {
background: var(--hover);
}
#outsourcing-orders-page .btn-text {
background: none;
border: none;
color: var(--primary);
cursor: pointer;
padding: 4px 8px;
margin-right: 8px;
}
#outsourcing-orders-page .btn-text:hover {
text-decoration: underline;
}
</style>
<div id="outsourcing-orders-page">
<div class="page-header">
<h1 style="margin: 0; font-size: 24px;">委外工单管理</h1>
<div class="page-actions" style="margin-top: 16px;">
<button id="add-order-btn" class="btn btn-primary">新增委外工单</button>
</div>
</div>
<div class="content-area">
<div class="filter-section" style="padding: 20px; display: flex; gap: 15px; align-items: center;">
<input type="text" id="search-keyword" class="input" placeholder="搜索工单号、客户订单号或产品" style="flex: 1; max-width: 400px;" />
<button class="btn btn-secondary" onclick="OutsourcingOrders.search()">搜索</button>
<button class="btn btn-secondary" onclick="OutsourcingOrders.resetSearch()">重置</button>
</div>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th>委外工单号</th>
<th>客户订单号</th>
<th>成品编码</th>
<th>成品名称</th>
<th>生产数量</th>
<th>外协厂</th>
<th>交期</th>
<th>状态</th>
<th>创建人</th>
<th>创建时间</th>
<th>操作</th>
</tr>
</thead>
<tbody id="order-list">
<tr><td colspan="11" class="text-center">加载中...</td></tr>
</tbody>
</table>
</div>
<div class="pagination" id="pagination" style="padding: 20px; display: flex; justify-content: center; gap: 8px;"></div>
</div>
</div>
<!-- 新增/编辑委外工单弹窗 -->
<div id="order-modal" class="modal" style="display:none;">
<div class="modal-content" style="max-width: 700px;">
<div class="modal-header">
<h2 id="modal-title">新增委外工单</h2>
<button class="modal-close" onclick="OutsourcingOrders.closeModal()">&times;</button>
</div>
<div class="modal-body">
<div class="field">
<label>客户订单号 <span style="color: var(--danger);">*</span></label>
<select id="customer-order-no" class="input">
<option value="">请选择客户订单</option>
</select>
</div>
<div class="field">
<label>成品编码 <span style="color: var(--danger);">*</span></label>
<input type="text" id="product-code" class="input" placeholder="输入成品编码" />
</div>
<div class="field">
<label>成品名称 <span style="color: var(--danger);">*</span></label>
<input type="text" id="product-name" class="input" placeholder="输入成品名称" />
</div>
<div class="field">
<label>生产数量 <span style="color: var(--danger);">*</span></label>
<input type="number" id="production-qty" class="input" min="1" value="1" placeholder="输入生产数量" />
</div>
<div class="field">
<label>外协厂 <span style="color: var(--danger);">*</span></label>
<input type="text" id="outsourcing-factory" class="input" placeholder="输入外协厂名称" />
</div>
<div class="field">
<label>交期 <span style="color: var(--danger);">*</span></label>
<input type="date" id="delivery-date" class="input" />
</div>
<div id="material-list-section" style="display: none; margin-top: 20px;">
<h3 style="margin-bottom: 10px;">物料明细根据BOM自动计算</h3>
<div class="table-wrapper" style="max-height: 300px; overflow: auto;">
<table>
<thead>
<tr>
<th>物料编码</th>
<th>物料名称</th>
<th>BOM用量</th>
<th>需发数量</th>
<th>单位</th>
</tr>
</thead>
<tbody id="material-list">
</tbody>
</table>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" onclick="OutsourcingOrders.closeModal()">取消</button>
<button class="btn btn-primary" onclick="OutsourcingOrders.save()">保存</button>
</div>
</div>
</div>
`;
setTimeout(() => {
document.getElementById('add-order-btn')?.addEventListener('click', () => openModal());
document.getElementById('search-keyword')?.addEventListener('keypress', (e) => {
if (e.key === 'Enter') search();
});
loadList();
loadCustomerOrders();
}, 0);
return html;
});
async function loadList() {
try {
const res = await API.get('/api/outsourcing-orders');
orderList = res.list || [];
renderList();
} catch (e) {
console.error('加载委外工单失败:', e);
document.getElementById('order-list').innerHTML = '<tr><td colspan="11" class="text-center" style="color: var(--danger);">加载失败</td></tr>';
}
}
async function loadCustomerOrders() {
try {
const res = await API.get('/api/customer-orders-for-outsourcing');
customerOrders = res.list || [];
const select = document.getElementById('customer-order-no');
if (select) {
select.innerHTML = '<option value="">请选择客户订单</option>' +
customerOrders.map(o => `<option value="${escapeHtml(o.order_no)}">${escapeHtml(o.order_no)} - ${escapeHtml(o.customer_name)} - ${escapeHtml(o.material)}</option>`).join('');
}
} catch (e) {
console.error('加载客户订单失败:', e);
}
}
function renderList() {
const tbody = document.getElementById('order-list');
const keyword = (document.getElementById('search-keyword')?.value || '').toLowerCase();
let filtered = orderList;
if (keyword) {
filtered = orderList.filter(item =>
(item.order_no || '').toLowerCase().includes(keyword) ||
(item.customer_order_no || '').toLowerCase().includes(keyword) ||
(item.product_code || '').toLowerCase().includes(keyword) ||
(item.product_name || '').toLowerCase().includes(keyword)
);
}
const totalPages = Math.ceil(filtered.length / pageSize);
const start = (currentPage - 1) * pageSize;
const pageData = filtered.slice(start, start + pageSize);
if (pageData.length === 0) {
tbody.innerHTML = '<tr><td colspan="11" class="text-center">暂无数据</td></tr>';
} else {
tbody.innerHTML = pageData.map(item => {
const status = statusMap[item.status] || { text: item.status, color: 'var(--text-2)' };
return `
<tr>
<td><span style="font-family: monospace; font-size: 12px;">${escapeHtml(item.order_no || '')}</span></td>
<td>${escapeHtml(item.customer_order_no || '')}</td>
<td>${escapeHtml(item.product_code || '')}</td>
<td>${escapeHtml(item.product_name || '')}</td>
<td style="font-weight: 600; color: var(--primary);">${item.production_qty || 0}</td>
<td>${escapeHtml(item.outsourcing_factory || '')}</td>
<td>${escapeHtml(item.delivery_date || '')}</td>
<td><span style="padding: 2px 8px; border-radius: 4px; background: ${status.color}20; color: ${status.color}; font-size: 12px;">${status.text}</span></td>
<td>${escapeHtml(item.created_by || '')}</td>
<td>${formatTime(item.created_at)}</td>
<td>
<button class="btn btn-sm btn-secondary" onclick="OutsourcingOrders.viewDetail(${item.id})">查看</button>
<button class="btn btn-sm btn-danger" onclick="OutsourcingOrders.deleteOrder(${item.id})">删除</button>
</td>
</tr>
`;
}).join('');
}
renderPagination(totalPages);
}
function renderPagination(totalPages) {
const container = document.getElementById('pagination');
if (totalPages <= 1) {
container.innerHTML = '';
return;
}
let html = '';
html += `<button class="btn btn-sm btn-secondary" ${currentPage <= 1 ? 'disabled' : ''} onclick="OutsourcingOrders.goPage(${currentPage - 1})">上一页</button>`;
html += `<span style="padding: 0 12px; line-height: 32px;">第 ${currentPage} / ${totalPages} 页</span>`;
html += `<button class="btn btn-sm btn-secondary" ${currentPage >= totalPages ? 'disabled' : ''} onclick="OutsourcingOrders.goPage(${currentPage + 1})">下一页</button>`;
container.innerHTML = html;
}
let editingId = null;
function openModal(item = null) {
editingId = item?.id || null;
document.getElementById('modal-title').textContent = item ? '编辑委外工单' : '新增委外工单';
document.getElementById('customer-order-no').value = item?.customer_order_no || '';
document.getElementById('product-code').value = item?.product_code || '';
document.getElementById('product-name').value = item?.product_name || '';
document.getElementById('production-qty').value = item?.production_qty || 1;
document.getElementById('outsourcing-factory').value = item?.outsourcing_factory || '';
document.getElementById('delivery-date').value = item?.delivery_date || '';
document.getElementById('material-list-section').style.display = 'none';
document.getElementById('order-modal').style.display = 'flex';
}
function closeModal() {
document.getElementById('order-modal').style.display = 'none';
editingId = null;
}
async function save() {
const data = {
customer_order_no: document.getElementById('customer-order-no').value.trim(),
product_code: document.getElementById('product-code').value.trim(),
product_name: document.getElementById('product-name').value.trim(),
production_qty: parseInt(document.getElementById('production-qty').value) || 0,
outsourcing_factory: document.getElementById('outsourcing-factory').value.trim(),
delivery_date: document.getElementById('delivery-date').value.trim()
};
if (!data.customer_order_no || !data.product_code || !data.product_name || !data.outsourcing_factory || !data.delivery_date) {
alert('请填写所有必填字段');
return;
}
if (data.production_qty <= 0) {
alert('生产数量必须大于0');
return;
}
try {
if (editingId) {
await API.put(`/api/outsourcing-orders/${editingId}`, data);
alert('更新成功');
} else {
const res = await API.post('/api/outsourcing-orders', data);
alert(`委外工单创建成功,工单号:${res.order_no}`);
}
closeModal();
loadList();
} catch (e) {
alert(e.message || '操作失败');
}
}
async function viewDetail(id) {
try {
const res = await API.get(`/api/outsourcing-orders/${id}`);
const order = res.order;
const materials = res.materials || [];
document.getElementById('modal-title').textContent = '查看委外工单';
document.getElementById('customer-order-no').value = order.customer_order_no || '';
document.getElementById('product-code').value = order.product_code || '';
document.getElementById('product-name').value = order.product_name || '';
document.getElementById('production-qty').value = order.production_qty || 0;
document.getElementById('outsourcing-factory').value = order.outsourcing_factory || '';
document.getElementById('delivery-date').value = order.delivery_date || '';
if (materials.length > 0) {
document.getElementById('material-list-section').style.display = 'block';
document.getElementById('material-list').innerHTML = materials.map(m => `
<tr>
<td>${escapeHtml(m.material_code)}</td>
<td>${escapeHtml(m.material_name)}</td>
<td>${m.bom_unit_qty}</td>
<td style="font-weight: 600; color: var(--primary);">${m.need_qty}</td>
<td>${escapeHtml(m.unit)}</td>
</tr>
`).join('');
}
document.getElementById('order-modal').style.display = 'flex';
} catch (e) {
alert(e.message || '获取详情失败');
}
}
async function deleteOrder(id) {
if (!confirm('确定要删除这条委外工单吗?')) return;
try {
await API.delete(`/api/outsourcing-orders/${id}`);
alert('删除成功');
loadList();
} catch (e) {
alert(e.message || '删除失败');
}
}
function search() {
currentPage = 1;
renderList();
}
function resetSearch() {
document.getElementById('search-keyword').value = '';
currentPage = 1;
renderList();
}
function goPage(page) {
currentPage = page;
renderList();
}
function formatTime(ts) {
if (!ts) return '-';
try {
const d = new Date(ts);
return d.toLocaleString('zh-CN', { hour12: false });
} catch {
return ts;
}
}
function escapeHtml(str) {
if (!str) return '';
return String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
}
window.OutsourcingOrders = {
search,
resetSearch,
viewDetail,
deleteOrder,
closeModal,
save,
goPage
};
})();