// 采购需求清单管理
(() => {
let demandList = [];
let productList = [];
let currentPage = 1;
const pageSize = 20;
const statusMap = {
'pending': { text: '待处理', color: 'var(--warning)' },
'ordered': { text: '已下单', color: 'var(--info)' },
'received': { text: '已收货', color: 'var(--primary)' },
'completed': { text: '已完成', color: 'var(--success)' },
'cancelled': { text: '已取消', color: 'var(--text-2)' }
};
Router.register('/plan-mgmt/purchase-demand', async () => {
const html = `
📋 计算将:
- 根据选中产品的BOM展开所有物料
- 计算每种物料的总需求量
- 扣减期初库存得出净需求
- 按最小包装取整得出实际采购量
`;
setTimeout(() => {
document.getElementById('calc-demand-btn')?.addEventListener('click', () => openCalcModal());
document.getElementById('import-excel-btn')?.addEventListener('click', () => openImportModal());
document.getElementById('batch-delete-btn')?.addEventListener('click', () => batchDelete());
document.getElementById('search-keyword')?.addEventListener('keypress', (e) => {
if (e.key === 'Enter') PurchaseDemand.search();
});
document.getElementById('excel-file')?.addEventListener('change', handleFileSelect);
loadList();
loadProducts();
}, 0);
return html;
});
async function loadList() {
try {
const res = await API.get('/api/purchase-demand');
demandList = res.list || [];
console.log('加载采购需求列表:', demandList.length, '条'); // 调试日志
console.log('前3条数据:', demandList.slice(0, 3)); // 调试日志
console.log('完整数据结构:', demandList[0]); // 查看第一条数据的完整结构
console.log('数据中的所有字段:', Object.keys(demandList[0] || {})); // 查看所有字段名
console.log('第一条数据的product_code:', demandList[0]?.product_code); // 直接查看第一条的product_code
console.log('第二条数据的product_code:', demandList[1]?.product_code); // 直接查看第二条的product_code
// 更新产品标签
updateProductTabs();
renderList();
} catch (e) {
console.error('加载采购需求失败:', e);
document.getElementById('demand-list').innerHTML = '| 加载失败 |
';
}
}
function updateProductTabs() {
// 提取所有产品编码
const products = new Set();
demandList.forEach((item, index) => {
// 优先使用 product_code 字段,如果没有则从 demand_no 中提取
let productCode = item.product_code;
console.log(`第${index + 1}条数据的product_code:`, productCode); // 调试每条数据
if (productCode) {
products.add(productCode);
}
});
console.log('提取到的产品编码:', Array.from(products)); // 调试日志
console.log('检查数据是否有product_code字段:', demandList.some(item => 'product_code' in item)); // 检查字段是否存在
// 更新标签页
const tabsContainer = document.getElementById('product-tabs-container');
if (!tabsContainer) return;
const productArray = Array.from(products).sort();
let html = ``;
productArray.forEach(product => {
const isActive = product === currentProduct ? 'active' : '';
html += ``;
});
tabsContainer.innerHTML = html;
}
function switchProduct(productCode) {
currentProduct = productCode; // 保存当前选中的产品
window.PurchaseDemand.currentProduct = productCode; // 也在全局对象中保存
// 更新标签样式
document.querySelectorAll('.product-tab').forEach(tab => {
if (tab.dataset.product === productCode) {
tab.classList.add('active');
} else {
tab.classList.remove('active');
}
});
renderList();
}
async function loadProducts() {
try {
// 从采购需求表中获取产品列表
const res = await API.get('/api/purchase-demand');
const demandData = res.list || [];
// 提取唯一的产品编码
const products = {};
demandData.forEach(item => {
if (item.product_code) {
products[item.product_code] = item.product_code;
}
});
productList = Object.keys(products).map(code => ({
product_code: code,
product_name: code // 使用产品编码作为名称
}));
console.log('从采购需求表加载到的产品列表:', productList); // 调试日志
const select = document.getElementById('product-select');
if (select) {
select.innerHTML = '' +
productList.map(p => ``).join('');
}
} catch (e) {
console.error('加载产品列表失败:', e);
}
}
let currentProduct = ''; // 当前选中的产品
function toggleSelectAll(source) {
const checkboxes = document.querySelectorAll('.row-checkbox');
checkboxes.forEach(checkbox => {
checkbox.checked = source.checked;
});
}
function renderList() {
const tbody = document.getElementById('demand-list');
const keyword = (document.getElementById('search-keyword')?.value || '').toLowerCase();
const filterStatus = document.getElementById('filter-status')?.value || '';
// 按产品分组
let grouped = {};
demandList.forEach(item => {
const productCode = item.product_code || '';
if (!grouped[productCode]) {
grouped[productCode] = [];
}
grouped[productCode].push(item);
});
// 如果选择了特定产品,只显示该产品的数据
if (currentProduct) {
grouped = currentProduct in grouped ? { [currentProduct]: grouped[currentProduct] } : {};
}
// 合并所有数据用于搜索和过滤
let allData = [];
Object.values(grouped).forEach(items => {
allData = allData.concat(items);
});
// 应用搜索过滤
if (keyword) {
allData = allData.filter(item =>
(item.demand_no || '').toLowerCase().includes(keyword) ||
(item.material_code || '').toLowerCase().includes(keyword) ||
(item.material_name || '').toLowerCase().includes(keyword)
);
}
if (filterStatus) {
allData = allData.filter(item => item.status === filterStatus);
}
// 分页
const totalPages = Math.ceil(allData.length / pageSize);
const start = (currentPage - 1) * pageSize;
const pageData = allData.slice(start, start + pageSize);
if (pageData.length === 0) {
tbody.innerHTML = '| 暂无数据 |
';
} else {
// 按产品分组渲染
let html = '';
let currentGroup = null;
// 计算每个产品的总采购数量(跨所有页面)
const productTotals = {};
allData.forEach(item => {
if (item.product_code) {
productTotals[item.product_code] = (productTotals[item.product_code] || 0) + (item.actual_purchase_qty || 0);
}
});
pageData.forEach(item => {
if (item.product_code !== currentGroup) {
currentGroup = item.product_code;
// 只在有产品编码时才添加分组标题
if (currentGroup) {
const productTotal = productTotals[currentGroup] || 0;
html += `
|
产品: ${currentGroup}
|
总采购数量: ${productTotal}
|
`;
}
}
const status = statusMap[item.status] || { text: item.status, color: 'var(--text-2)' };
html += `
|
${escapeHtml(item.demand_no || '')} |
${escapeHtml(item.material_code || '')} |
${escapeHtml(item.material_name || '')} |
${item.order_qty || 0} |
${item.bom_unit_qty || 1} |
${item.total_demand || 0} |
${item.initial_stock || 0} |
${item.net_demand || 0} |
${item.min_package || 1} |
${item.actual_purchase_qty || 0} |
${escapeHtml(item.unit || 'pcs')} |
${escapeHtml(item.supplier || '-')} |
${status.text} |
|
`;
});
tbody.innerHTML = html;
}
renderPagination(totalPages);
}
function renderPagination(totalPages) {
const container = document.getElementById('pagination');
if (totalPages <= 1) {
container.innerHTML = '';
return;
}
let html = '';
html += ``;
html += `第 ${currentPage} / ${totalPages} 页`;
html += ``;
container.innerHTML = html;
}
function openCalcModal() {
document.getElementById('product-select').value = '';
document.getElementById('order-qty').value = 1;
document.getElementById('calc-modal').style.display = 'flex';
// 重新加载产品列表
loadProducts();
}
function closeCalcModal() {
document.getElementById('calc-modal').style.display = 'none';
}
async function doCalculate() {
const productCode = document.getElementById('product-select').value;
const orderQty = parseInt(document.getElementById('order-qty').value) || 0;
const useInitialStock = document.getElementById('use-initial-stock').checked;
if (!productCode) {
alert('请选择产品');
return;
}
if (orderQty <= 0) {
alert('订单数量必须大于0');
return;
}
try {
// 基于现有采购需求重新计算
const res = await API.post('/api/purchase-demand/recalculate', {
product_code: productCode,
order_qty: orderQty,
use_initial_stock: useInitialStock
});
closeCalcModal();
alert(res.message || '计算完成');
loadList();
// 显示计算结果摘要
if (res.list && res.list.length > 0) {
const totalPurchase = res.list.reduce((sum, item) => sum + (item.actual_purchase_qty || 0), 0);
console.log(`采购需求重新计算完成,需求编号: ${res.demand_no},共 ${res.list.length} 种物料,总采购数量: ${totalPurchase}`);
}
} catch (e) {
alert(e.message || '计算失败');
}
}
async function calcFromOrders() {
if (!confirm('将根据所有客户订单自动计算采购需求,确定继续吗?')) {
return;
}
try {
const res = await API.post('/api/purchase-demand/calculate-from-orders', {});
alert(res.message || '计算完成');
loadList();
} catch (e) {
alert(e.message || '计算失败');
}
}
let updatingId = null;
function updateStatus(id) {
updatingId = id;
const item = demandList.find(x => x.id === id);
document.getElementById('update-status').value = item?.status || 'pending';
document.getElementById('update-remark').value = item?.remark || '';
document.getElementById('status-modal').style.display = 'flex';
}
function closeStatusModal() {
document.getElementById('status-modal').style.display = 'none';
updatingId = null;
}
async function doUpdateStatus() {
if (!updatingId) return;
const status = document.getElementById('update-status').value;
const remark = document.getElementById('update-remark').value.trim();
try {
await API.put(`/api/purchase-demand/${updatingId}`, { status, remark });
closeStatusModal();
alert('更新成功');
loadList();
} catch (e) {
alert(e.message || '更新失败');
}
}
async function deleteDemand(id) {
if (!confirm('确定要删除这条采购需求吗?')) return;
try {
await API.delete(`/api/purchase-demand/${id}`);
alert('删除成功');
loadList();
} catch (e) {
alert(e.message || '删除失败');
}
}
async function batchDelete() {
const checked = document.querySelectorAll('.row-checkbox:checked');
if (checked.length === 0) {
alert('请先选择要删除的项');
return;
}
if (!confirm(`确定要删除选中的 ${checked.length} 条采购需求吗?`)) return;
const ids = Array.from(checked).map(cb => parseInt(cb.dataset.id));
try {
await API.post('/api/purchase-demand/batch-delete', { ids });
alert('批量删除成功');
loadList();
} catch (e) {
alert(e.message || '批量删除失败');
}
}
function toggleSelectAll(checkbox) {
document.querySelectorAll('.row-checkbox').forEach(cb => cb.checked = checkbox.checked);
}
function search() {
currentPage = 1;
renderList();
}
function resetSearch() {
document.getElementById('search-keyword').value = '';
document.getElementById('filter-status').value = '';
currentPage = 1;
renderList();
}
function goPage(page) {
currentPage = page;
renderList();
}
function escapeHtml(str) {
if (!str) return '';
return String(str).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"');
}
// Excel导入相关函数
let excelData = [];
function openImportModal() {
document.getElementById('excel-file').value = '';
document.getElementById('import-preview').style.display = 'none';
document.getElementById('import-excel-modal').style.display = 'flex';
}
function closeImportModal() {
document.getElementById('import-excel-modal').style.display = 'none';
excelData = [];
}
// 测试XLSX库
window.testXLSX = function() {
console.log('测试XLSX库...');
console.log('typeof XLSX:', typeof XLSX);
if (typeof XLSX !== 'undefined') {
console.log('XLSX版本:', XLSX.version);
API.toast('XLSX库已加载,版本: ' + XLSX.version);
} else {
console.error('XLSX库未加载');
// 尝试动态加载
loadXLSXLibrary();
}
};
// 动态加载XLSX库
function loadXLSXLibrary() {
if (typeof XLSX !== 'undefined') {
return Promise.resolve();
}
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = './assets/xlsx.min.js';
script.onload = () => {
console.log('XLSX库动态加载成功,版本:', XLSX.version);
API.toast('XLSX库加载成功');
resolve();
};
script.onerror = () => {
console.error('XLSX库加载失败');
API.toast('XLSX库加载失败,请刷新页面重试', 'error');
reject(new Error('Failed to load XLSX library'));
};
document.head.appendChild(script);
});
}
// 确保XLSX库已加载
async function ensureXLSXLoaded() {
if (typeof XLSX === 'undefined') {
try {
await loadXLSXLibrary();
} catch (err) {
throw err;
}
}
}
async function handleFileSelect(e) {
const file = e.target.files[0];
if (!file) return;
try {
// 确保XLSX库已加载
await ensureXLSXLoaded();
// 检查文件大小
if (file.size > 10 * 1024 * 1024) {
API.toast('文件大小不能超过10MB', 'error');
e.target.value = '';
return;
}
// 检查文件类型
const fileName = file.name.toLowerCase();
if (!fileName.endsWith('.xlsx') && !fileName.endsWith('.xls')) {
API.toast('请选择Excel文件(.xlsx或.xls格式)', 'error');
e.target.value = '';
return;
}
// 读取Excel文件
const reader = new FileReader();
reader.onload = async function(e) {
try {
const data = new Uint8Array(e.target.result);
const workbook = XLSX.read(data, { type: 'array' });
const firstSheet = workbook.Sheets[workbook.SheetNames[0]];
const jsonData = XLSX.utils.sheet_to_json(firstSheet, { header: 1 });
console.log('Excel解析结果:', jsonData); // 调试日志
// 验证数据格式
if (jsonData.length < 2) {
API.toast('Excel文件没有数据', 'error');
return;
}
// 获取表头
const headers = jsonData[0];
console.log('表头:', headers); // 调试日志
const requiredHeaders = ['型号', '供应商', '期初库存', 'MOQ', '单机数量'];
const missingHeaders = requiredHeaders.filter(h => !headers.includes(h));
if (missingHeaders.length > 0) {
API.toast(`缺少必要的列:${missingHeaders.join(', ')}。当前列:${headers.join(', ')}`, 'error');
return;
}
// 解析数据
excelData = [];
let emptyRows = 0;
let validRows = 0;
for (let i = 1; i < jsonData.length; i++) {
const row = jsonData[i];
if (!row) {
emptyRows++;
continue;
}
// 获取各列的值
const model = String(row[headers.indexOf('型号')] || '').trim();
const supplier = String(row[headers.indexOf('供应商')] || '').trim();
const initial_stock = row[headers.indexOf('期初库存')] || 0;
const moq = row[headers.indexOf('MOQ')] || 1;
const per_unit_qty = row[headers.indexOf('单机数量')] || 1;
// 跳过完全空白的行
if (!model && !supplier) {
emptyRows++;
continue;
}
const item = {
model: model,
supplier: supplier,
initial_stock: parseInt(initial_stock) || 0,
moq: parseInt(moq) || 1,
per_unit_qty: parseInt(per_unit_qty) || 1
};
console.log(`第${i}行数据:`, item); // 调试日志
excelData.push(item);
validRows++;
}
console.log(`总共处理 ${jsonData.length - 1} 行,有效数据 ${validRows} 条,空行 ${emptyRows} 条`);
if (excelData.length === 0) {
API.toast('Excel文件没有有效数据', 'error');
return;
}
// 显示预览
showPreview(excelData.slice(0, 5));
API.toast(`成功读取 ${excelData.length} 条数据`, 'success');
} catch (err) {
console.error('解析Excel失败:', err);
API.toast(`解析Excel文件失败:${err.message || '请检查文件格式'}`, 'error');
}
};
reader.readAsArrayBuffer(file);
} catch (err) {
console.error('读取文件失败:', err);
API.toast('读取文件失败', 'error');
}
}
function showPreview(data) {
const tbody = document.getElementById('preview-tbody');
tbody.innerHTML = data.map(item => `
| ${escapeHtml(item.model)} |
${escapeHtml(item.supplier)} |
${item.initial_stock} |
${item.moq} |
${item.per_unit_qty} |
`).join('');
document.getElementById('import-preview').style.display = 'block';
}
async function edit(id) {
try {
const res = await API.get(`/api/purchase-demand/${id}`);
const item = res;
if (!item) {
alert('未找到该采购需求');
return;
}
// 填充表单
document.getElementById('edit-material-code').value = item.material_code || '';
document.getElementById('edit-material-name').value = item.material_name || '';
document.getElementById('edit-order-qty').value = item.order_qty || 0;
document.getElementById('edit-bom-unit-qty').value = item.bom_unit_qty || 0;
document.getElementById('edit-initial-stock').value = item.initial_stock || 0;
document.getElementById('edit-min-package').value = item.min_package || 1;
document.getElementById('edit-actual-purchase-qty').value = item.actual_purchase_qty || 0;
document.getElementById('edit-unit').value = item.unit || 'pcs';
document.getElementById('edit-supplier').value = item.supplier || '';
document.getElementById('edit-status').value = item.status || 'pending';
document.getElementById('edit-remark').value = item.remark || '';
// 保存当前编辑的ID
window.PurchaseDemand.editingId = id;
// 显示弹窗
document.getElementById('edit-modal').style.display = 'flex';
} catch (e) {
console.error('获取数据失败:', e);
alert('获取数据失败');
}
}
function closeEditModal() {
document.getElementById('edit-modal').style.display = 'none';
window.PurchaseDemand.editingId = null;
}
async function saveEdit() {
const id = window.PurchaseDemand.editingId;
if (!id) {
alert('无效的编辑ID');
return;
}
// 获取表单数据
const data = {
order_qty: parseInt(document.getElementById('edit-order-qty').value) || 0,
bom_unit_qty: parseFloat(document.getElementById('edit-bom-unit-qty').value) || 0,
initial_stock: parseInt(document.getElementById('edit-initial-stock').value) || 0,
min_package: parseInt(document.getElementById('edit-min-package').value) || 1,
actual_purchase_qty: parseInt(document.getElementById('edit-actual-purchase-qty').value) || 0,
unit: document.getElementById('edit-unit').value.trim() || 'pcs',
supplier: document.getElementById('edit-supplier').value.trim(),
status: document.getElementById('edit-status').value,
remark: document.getElementById('edit-remark').value.trim()
};
try {
const res = await API.put(`/api/purchase-demand/${id}`, data);
if (res.ok) {
alert('保存成功');
closeEditModal();
loadList();
} else {
alert(res.error || '保存失败');
}
} catch (e) {
console.error('保存失败:', e);
alert('保存失败');
}
}
function openBatchEditStatusModal() {
// 获取选中的复选框
const checkboxes = document.querySelectorAll('.row-checkbox:checked');
const selectedCount = checkboxes.length;
// 更新选中数量显示
document.getElementById('selected-count').textContent = selectedCount;
// 加载产品列表
loadProductOptions();
// 重置表单
document.getElementById('batch-status').value = '';
document.getElementById('batch-remark').value = '';
document.querySelector('input[name="batch-scope"][value="selected"]').checked = true;
document.getElementById('batch-product-select-container').style.display = 'none';
// 添加事件监听
document.getElementById('batch-scope-all').onchange = function() {
document.getElementById('batch-product-select-container').style.display =
this.checked ? 'block' : 'none';
};
document.getElementById('batch-scope-selected').onchange = function() {
document.getElementById('batch-product-select-container').style.display =
this.checked ? 'none' : 'block';
};
// 显示弹窗
document.getElementById('batch-edit-status-modal').style.display = 'flex';
}
function loadProductOptions() {
// 获取所有产品
const products = new Set();
demandList.forEach(item => {
if (item.product_code) {
products.add(item.product_code);
}
});
const productSelect = document.getElementById('batch-product-select');
productSelect.innerHTML = '';
// 添加产品选项
Array.from(products).sort().forEach(product => {
const option = document.createElement('option');
option.value = product;
option.textContent = product;
productSelect.appendChild(option);
});
// 如果当前有选中的产品,设为默认值
if (currentProduct) {
productSelect.value = currentProduct;
}
}
function closeBatchEditStatusModal() {
document.getElementById('batch-edit-status-modal').style.display = 'none';
}
async function saveBatchEditStatus() {
const status = document.getElementById('batch-status').value;
const remark = document.getElementById('batch-remark').value.trim();
const scope = document.querySelector('input[name="batch-scope"]:checked').value;
if (!status) {
alert('请选择状态');
return;
}
let requestData = {
status: status,
remark: remark
};
let confirmMessage = '';
if (scope === 'all') {
// 更新指定产品的所有记录
const selectedProduct = document.getElementById('batch-product-select').value;
if (!selectedProduct) {
alert('请选择要更新的产品');
return;
}
requestData.product_code = selectedProduct;
confirmMessage = `确定要将"${selectedProduct}"的所有记录状态更新为"${getStatusText(status)}"吗?`;
} else {
// 仅更新选中的记录
const checkboxes = document.querySelectorAll('.row-checkbox:checked');
const ids = Array.from(checkboxes).map(cb => parseInt(cb.dataset.id));
if (ids.length === 0) {
alert('请选择要更新的记录');
return;
}
requestData.ids = ids;
confirmMessage = `确定要将选中的 ${ids.length} 条记录状态更新为"${getStatusText(status)}"吗?`;
}
if (!confirm(confirmMessage)) {
return;
}
try {
const res = await API.post('/api/purchase-demand/batch-update-status', requestData);
if (res.ok) {
alert(`成功更新 ${res.count} 条记录`);
closeBatchEditStatusModal();
loadList();
// 清除选择
document.getElementById('select-all').checked = false;
} else {
alert(res.error || '更新失败');
}
} catch (e) {
console.error('批量更新失败:', e);
alert('更新失败');
}
}
function getStatusText(status) {
const statusMap = {
'pending': '待处理',
'ordered': '已下单',
'received': '已收货',
'completed': '已完成',
'cancelled': '已取消'
};
return statusMap[status] || status;
}
async function syncToInitialStock() {
try {
// 获取当前显示的数据
const res = await API.get('/api/purchase-demand');
let dataToSync = res.list || [];
// 应用当前的筛选条件
const keyword = document.getElementById('search-keyword').value.trim();
const statusFilter = document.getElementById('filter-status').value;
const currentProduct = window.PurchaseDemand.currentProduct || '';
if (currentProduct) {
dataToSync = dataToSync.filter(item => item.product_code === currentProduct);
}
if (statusFilter) {
dataToSync = dataToSync.filter(item => item.status === statusFilter);
}
if (keyword) {
const lowerKeyword = keyword.toLowerCase();
dataToSync = dataToSync.filter(item =>
(item.material_code && item.material_code.toLowerCase().includes(lowerKeyword)) ||
(item.material_name && item.material_name.toLowerCase().includes(lowerKeyword)) ||
(item.supplier && item.supplier.toLowerCase().includes(lowerKeyword))
);
}
if (dataToSync.length === 0) {
alert('没有数据可同步');
return;
}
// 提取唯一物料
const uniqueMaterials = {};
dataToSync.forEach(item => {
const key = item.material_code;
if (!uniqueMaterials[key] || item.initial_stock > uniqueMaterials[key].initial_stock) {
uniqueMaterials[key] = {
material_code: item.material_code,
material_name: item.material_name,
stock_qty: item.initial_stock,
unit: item.unit || 'pcs',
min_package: item.min_package || 1,
supplier: item.supplier || ''
};
}
});
const materialsToSync = Object.values(uniqueMaterials);
if (!confirm(`确定要将 ${materialsToSync.length} 个物料的期初库存同步到期初库存管理吗?\n注意:如果物料已存在,将更新其库存数量。`)) {
return;
}
const res2 = await API.post('/api/purchase-demand/sync-to-initial-stock', {
materials: materialsToSync
});
if (res2.ok) {
alert(`成功同步 ${res2.success_count} 个物料的期初库存`);
} else {
alert(res2.error || '同步失败');
}
} catch (e) {
console.error('同步失败:', e);
alert('同步失败');
}
}
async function deleteProductData() {
const currentProduct = window.PurchaseDemand.currentProduct || '';
if (!currentProduct) {
alert('请先选择一个产品');
return;
}
if (!confirm(`确定要删除产品 ${currentProduct} 的所有采购需求数据吗?\n此操作不可恢复!`)) {
return;
}
try {
const res = await API.post('/api/purchase-demand/delete-product', {
product_code: currentProduct
});
if (res.ok) {
alert(`成功删除产品 ${currentProduct} 的 ${res.count} 条数据`);
window.PurchaseDemand.currentProduct = '';
loadList();
} else {
alert(res.error || '删除失败');
}
} catch (e) {
alert('删除失败:' + e.message);
}
}
async function exportExcel() {
try {
// 获取当前显示的数据(包括筛选条件)
const res = await API.get('/api/purchase-demand');
let dataToExport = res.list || [];
// 应用当前的筛选条件
const keyword = document.getElementById('search-keyword').value.trim();
const statusFilter = document.getElementById('filter-status').value;
const currentProduct = window.PurchaseDemand.currentProduct || '';
if (currentProduct) {
dataToExport = dataToExport.filter(item => item.product_code === currentProduct);
}
if (statusFilter) {
dataToExport = dataToExport.filter(item => item.status === statusFilter);
}
if (keyword) {
const lowerKeyword = keyword.toLowerCase();
dataToExport = dataToExport.filter(item =>
(item.material_code && item.material_code.toLowerCase().includes(lowerKeyword)) ||
(item.material_name && item.material_name.toLowerCase().includes(lowerKeyword)) ||
(item.supplier && item.supplier.toLowerCase().includes(lowerKeyword))
);
}
if (dataToExport.length === 0) {
alert('没有数据可导出');
return;
}
// 确保XLSX库已加载
await ensureXLSXLoaded();
// 准备导出数据
const exportData = dataToExport.map(item => ({
'需求编号': item.demand_no || '',
'产品编码': item.product_code || '',
'物料编码': item.material_code || '',
'物料名称': item.material_name || '',
'订单数量': item.order_qty || 0,
'单机用量': item.bom_unit_qty || 0,
'总需求': item.total_demand || 0,
'期初库存': item.initial_stock || 0,
'净需求': item.net_demand || 0,
'最小包装': item.min_package || 1,
'实际采购数量': item.actual_purchase_qty || 0,
'单位': item.unit || 'pcs',
'供应商': item.supplier || '',
'状态': getStatusText(item.status),
'备注': item.remark || '',
'创建时间': item.created_at || '',
'创建人': item.created_by || ''
}));
// 创建工作簿
const ws = XLSX.utils.json_to_sheet(exportData);
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, "采购需求清单");
// 设置列宽
const colWidths = [
{ wch: 20 }, // 需求编号
{ wch: 12 }, // 产品编码
{ wch: 20 }, // 物料编码
{ wch: 30 }, // 物料名称
{ wch: 10 }, // 订单数量
{ wch: 10 }, // 单机用量
{ wch: 10 }, // 总需求
{ wch: 10 }, // 期初库存
{ wch: 10 }, // 净需求
{ wch: 10 }, // 最小包装
{ wch: 15 }, // 实际采购数量
{ wch: 8 }, // 单位
{ wch: 20 }, // 供应商
{ wch: 10 }, // 状态
{ wch: 20 }, // 备注
{ wch: 20 }, // 创建时间
{ wch: 10 } // 创建人
];
ws['!cols'] = colWidths;
// 生成文件名
const now = new Date();
const timestamp = now.getFullYear() +
String(now.getMonth() + 1).padStart(2, '0') +
String(now.getDate()).padStart(2, '0') + '_' +
String(now.getHours()).padStart(2, '0') +
String(now.getMinutes()).padStart(2, '0');
const filename = `采购需求清单_${currentProduct || '全部'}_${timestamp}.xlsx`;
// 导出文件
XLSX.writeFile(wb, filename);
console.log(`成功导出 ${dataToExport.length} 条数据到 ${filename}`);
} catch (err) {
console.error('导出失败:', err);
alert('导出失败,请稍后重试');
}
}
function getStatusText(status) {
const statusMap = {
'pending': '待处理',
'ordered': '已下单',
'received': '已收货',
'completed': '已完成',
'cancelled': '已取消'
};
return statusMap[status] || status;
}
async function importExcel() {
if (excelData.length === 0) {
API.toast('没有可导入的数据', 'error');
return;
}
// 获取产品编码
const productCode = document.getElementById('import-product-code').value.trim();
if (!productCode) {
API.toast('请输入产品编码', 'error');
return;
}
try {
// 确保XLSX库已加载(虽然这里不需要,但为了保持一致性)
await ensureXLSXLoaded();
console.log('准备导入的数据:', excelData); // 调试日志
console.log('产品编码:', productCode); // 调试日志
const res = await API.post('/api/purchase-demand/import', {
data: excelData,
product_code: productCode
});
console.log('导入响应:', res); // 调试日志
if (res.ok) {
API.toast(`成功导入 ${res.count || excelData.length} 条数据`, 'success');
if (res.errors && res.errors.length > 0) {
console.log('导入错误:', res.errors);
}
closeImportModal();
loadList(); // 重新加载列表
} else {
API.toast(res.error || '导入失败', 'error');
}
} catch (err) {
console.error('导入失败:', err);
API.toast('导入失败,请稍后重试', 'error');
}
}
window.PurchaseDemand = {
search,
resetSearch,
delete: deleteDemand,
updateStatus,
closeCalcModal,
doCalculate,
openImportModal,
closeImportModal,
handleFileSelect,
importExcel,
exportExcel,
deleteProductData,
edit,
closeEditModal,
saveEdit,
openBatchEditStatusModal,
closeBatchEditStatusModal,
saveBatchEditStatus,
syncToInitialStock,
toggleSelectAll,
switchProduct,
goPage
};
})();