// 采购需求清单管理
(() => {
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 = `
`;
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();
loadFactories();
}, 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) => {
// 如果选择了工厂,只显示该工厂的产品
if (currentFactory && item.factory !== currentFactory) {
return; // 跳过不属于当前工厂的数据
}
// 优先使用 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-tabs-container .product-tab').forEach(tab => {
if (tab.dataset.product === productCode) {
tab.classList.add('active');
} else {
tab.classList.remove('active');
}
});
// 如果当前在"全部工厂"视图,且选择了具体产品,自动切换到该产品所属的工厂
if (!currentFactory && productCode) {
// 找到该产品所属的工厂
const productData = demandList.find(item => item.product_code === productCode);
if (productData && productData.factory) {
// 保存当前选中的产品
const selectedProduct = productCode;
// 自动切换到该工厂(这会重置currentProduct)
switchFactory(productData.factory);
// 恢复产品选中状态
currentProduct = selectedProduct;
window.PurchaseDemand.currentProduct = selectedProduct;
// 更新产品标签的选中状态
updateProductTabs();
// 重新渲染列表
renderList();
return;
}
}
renderList();
}
function loadProducts(factory = '') {
// 直接使用已有的demandList数据,避免重新请求API导致闪烁
let demandData = demandList;
// 如果指定了工厂,只显示该工厂的产品
if (factory) {
demandData = demandList.filter(item => item.factory === factory);
}
// 提取唯一的产品编码
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(`加载产品列表 (工厂: ${factory || '全部'}):`, productList); // 调试日志
const select = document.getElementById('product-select');
if (select) {
if (productList.length === 0) {
select.innerHTML = '
';
} else {
select.innerHTML = '
' +
productList.map(p => `
`).join('');
}
}
}
async function loadFactories() {
try {
const res = await API.get('/api/factories');
const factories = res.factories || [];
renderFactoryTabs(factories);
} catch (e) {
console.error('加载工厂列表失败:', e);
}
}
async function loadFactoriesForSelect(selectId) {
try {
const res = await API.get('/api/factories');
const factories = res.factories || [];
const select = document.getElementById(selectId);
if (select) {
const currentValue = select.value;
const firstOption = select.options[0] ? select.options[0].outerHTML : '
';
select.innerHTML = firstOption;
factories.forEach(factory => {
const option = document.createElement('option');
option.value = factory;
option.textContent = factory;
select.appendChild(option);
});
if (currentValue) {
select.value = currentValue;
}
}
} catch (e) {
console.error('加载工厂列表失败:', e);
}
}
function renderFactoryTabs(factories) {
const container = document.getElementById('factory-tabs');
if (!container) return;
// 始终保留"全部工厂"按钮
let html = `
`;
// 添加工厂按钮
factories.forEach(factory => {
const isActive = factory === currentFactory ? 'active' : '';
html += `
`;
});
container.innerHTML = html;
}
async function showDeleteFactoryModal() {
if (!currentFactory) {
API.toast('请先选择要删除的工厂', 'error');
return;
}
if (!confirm(`确定要删除工厂"${currentFactory}"吗?\n\n这将删除该工厂的所有采购需求和期初库存数据,且不可恢复!`)) {
return;
}
try {
// 删除采购需求
await API.delete(`/api/purchase-demand/by-factory?factory=${encodeURIComponent(currentFactory)}`);
// 删除期初库存
await API.delete(`/api/initial-stock/by-factory?factory=${encodeURIComponent(currentFactory)}`);
API.toast(`成功删除工厂"${currentFactory}"及其所有数据`, 'success');
// 重新加载数据
loadList();
loadFactories();
loadProducts();
// 切换到全部工厂
switchFactory('');
} catch (e) {
console.error('删除工厂失败:', e);
API.toast('删除工厂失败:' + (e.message || '未知错误'), 'error');
}
}
let currentProduct = ''; // 当前选中的产品
let currentFactory = ''; // 当前选中的工厂
function switchFactory(factory) {
currentFactory = factory;
currentProduct = ''; // 切换工厂时重置产品筛选
// 只更新工厂按钮状态(不影响产品标签)
document.querySelectorAll('#factory-tabs .product-tab').forEach(btn => {
if (btn.getAttribute('data-factory') === factory) {
btn.classList.add('active');
} else {
btn.classList.remove('active');
}
});
// 显示或隐藏删除工厂按钮(只在选中具体工厂时显示)
const deleteBtn = document.getElementById('delete-factory-btn');
if (deleteBtn) {
deleteBtn.style.display = factory ? 'inline-block' : 'none';
}
// 重新加载产品列表(根据工厂筛选)
loadProducts(factory);
// 更新产品标签
updateProductTabs();
renderList();
}
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 filteredList = demandList;
if (currentFactory) {
filteredList = demandList.filter(item => item.factory === currentFactory);
}
// 按产品分组
let grouped = {};
filteredList.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.factory || '-')} |
${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;
}
// 保存工厂选择的事件处理函数
let calcFactoryChangeHandler = null;
async function openCalcModal() {
document.getElementById('factory-select').value = '';
document.getElementById('product-select').value = '';
document.getElementById('order-qty').value = 1;
document.getElementById('calc-modal').style.display = 'flex';
// 动态加载工厂列表
await loadFactoriesForSelect('factory-select');
// 添加工厂选择监听
const factorySelect = document.getElementById('factory-select');
if (factorySelect) {
// 移除旧的监听器(如果存在)
if (calcFactoryChangeHandler) {
factorySelect.removeEventListener('change', calcFactoryChangeHandler);
}
// 创建新的事件处理函数
calcFactoryChangeHandler = function() {
const selectedFactory = this.value;
// 清空产品选择
document.getElementById('product-select').value = '';
// 根据选择的工厂加载产品
loadProducts(selectedFactory);
};
// 添加新的监听器
factorySelect.addEventListener('change', calcFactoryChangeHandler);
}
// 初始加载时不显示产品,等待选择工厂
const productSelect = document.getElementById('product-select');
if (productSelect) {
productSelect.innerHTML = '
';
}
}
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() {
const factory = document.getElementById('factory-select').value;
if (!factory) {
alert('请选择工厂');
return;
}
if (!confirm('将根据所有客户订单自动计算采购需求,确定继续吗?')) {
return;
}
try {
const res = await API.post('/api/purchase-demand/calculate-from-orders', { factory });
alert(res.message || '计算完成');
closeCalcModal();
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 optionalHeaders = ['工厂'];
const missingHeaders = requiredHeaders.filter(h => !headers.includes(h));
if (missingHeaders.length > 0) {
API.toast(`缺少必要的列:${missingHeaders.join(', ')}。当前列:${headers.join(', ')}`, 'error');
return;
}
// 检查工厂列
const hasFactoryColumn = headers.includes('工厂');
if (!hasFactoryColumn) {
const defaultFactory = document.getElementById('import-factory').value.trim();
if (!defaultFactory) {
API.toast('Excel中没有工厂列,且未设置默认工厂,请至少提供一种方式', '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 factory = hasFactoryColumn ? String(row[headers.indexOf('工厂')] || '').trim() : '';
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 = {
factory: factory,
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.factory)} |
${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('保存失败');
}
}
// 保存批量编辑工厂选择的事件处理函数
let batchFactoryChangeHandler = null;
async function openBatchEditStatusModal() {
// 获取选中的复选框
const checkboxes = document.querySelectorAll('.row-checkbox:checked');
const selectedCount = checkboxes.length;
// 更新选中数量显示
document.getElementById('selected-count').textContent = selectedCount;
// 重置表单
document.getElementById('batch-status').value = '';
document.getElementById('batch-remark').value = '';
document.getElementById('batch-factory-select').value = '';
document.getElementById('batch-product-select').innerHTML = '
';
document.querySelector('input[name="batch-scope"][value="selected"]').checked = true;
document.getElementById('batch-factory-product-container').style.display = 'none';
// 动态加载工厂列表
await loadFactoriesForSelect('batch-factory-select');
// 添加范围选择事件监听
document.getElementById('batch-scope-all').onchange = function() {
document.getElementById('batch-factory-product-container').style.display =
this.checked ? 'block' : 'none';
};
document.getElementById('batch-scope-selected').onchange = function() {
document.getElementById('batch-factory-product-container').style.display =
this.checked ? 'none' : 'block';
};
// 添加工厂选择监听
const batchFactorySelect = document.getElementById('batch-factory-select');
if (batchFactorySelect) {
// 移除旧的监听器(如果存在)
if (batchFactoryChangeHandler) {
batchFactorySelect.removeEventListener('change', batchFactoryChangeHandler);
}
// 创建新的事件处理函数
batchFactoryChangeHandler = function() {
const selectedFactory = this.value;
// 清空产品选择
document.getElementById('batch-product-select').value = '';
// 根据选择的工厂加载产品
loadBatchProductOptions(selectedFactory);
};
// 添加新的监听器
batchFactorySelect.addEventListener('change', batchFactoryChangeHandler);
}
// 显示弹窗
document.getElementById('batch-edit-status-modal').style.display = 'flex';
}
function loadBatchProductOptions(factory = '') {
// 获取指定工厂的产品
const products = new Set();
demandList.forEach(item => {
// 如果指定了工厂,只显示该工厂的产品
if (factory && item.factory !== factory) {
return;
}
if (item.product_code) {
products.add(item.product_code);
}
});
const productSelect = document.getElementById('batch-product-select');
if (!factory) {
productSelect.innerHTML = '
';
} else if (products.size === 0) {
productSelect.innerHTML = '
';
} else {
productSelect.innerHTML = '
';
// 添加产品选项
Array.from(products).sort().forEach(product => {
const option = document.createElement('option');
option.value = product;
option.textContent = product;
productSelect.appendChild(option);
});
}
}
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 selectedFactory = document.getElementById('batch-factory-select').value;
const selectedProduct = document.getElementById('batch-product-select').value;
if (!selectedFactory) {
alert('请选择工厂');
return;
}
if (!selectedProduct) {
alert('请选择要更新的产品');
return;
}
// 获取该工厂和产品的所有记录ID
const ids = demandList
.filter(item => item.factory === selectedFactory && item.product_code === selectedProduct)
.map(item => item.id);
if (ids.length === 0) {
alert(`未找到工厂"${selectedFactory}"下产品"${selectedProduct}"的记录`);
return;
}
requestData.ids = ids;
confirmMessage = `确定要将工厂"${selectedFactory}"下产品"${selectedProduct}"的所有 ${ids.length} 条记录状态更新为"${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 downloadImportTemplate() {
try {
// 确保XLSX库已加载
await ensureXLSXLoaded();
// 创建模板数据
const templateData = [
{
'工厂': '友辉',
'型号': '示例物料编码',
'供应商': '示例供应商',
'期初库存': 100,
'MOQ': 1000,
'单机数量': 1
},
{
'工厂': '铨宝',
'型号': '示例物料编码2',
'供应商': '示例供应商2',
'期初库存': 200,
'MOQ': 500,
'单机数量': 2
},
{
'工厂': '新工厂',
'型号': '示例物料编码3',
'供应商': '示例供应商3',
'期初库存': 150,
'MOQ': 300,
'单机数量': 1
}
];
// 创建工作簿
const ws = XLSX.utils.json_to_sheet(templateData);
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, "采购需求导入模板");
// 设置列宽
const colWidths = [
{ wch: 10 }, // 工厂
{ wch: 20 }, // 型号
{ wch: 20 }, // 供应商
{ wch: 12 }, // 期初库存
{ wch: 10 }, // MOQ
{ wch: 12 } // 单机数量
];
ws['!cols'] = colWidths;
// 导出文件
XLSX.writeFile(wb, '采购需求导入模板.xlsx');
console.log('成功下载导入模板');
} catch (err) {
console.error('下载模板失败:', err);
alert('下载模板失败,请稍后重试');
}
}
async function importExcel() {
if (excelData.length === 0) {
API.toast('没有可导入的数据', 'error');
return;
}
// 获取工厂和产品编码
const factory = document.getElementById('import-factory').value.trim();
const productCode = document.getElementById('import-product-code').value.trim();
if (!productCode) {
API.toast('请输入产品编码', 'error');
return;
}
try {
// 确保XLSX库已加载(虽然这里不需要,但为了保持一致性)
await ensureXLSXLoaded();
console.log('准备导入的数据:', excelData); // 调试日志
console.log('工厂:', factory); // 调试日志
console.log('产品编码:', productCode); // 调试日志
const res = await API.post('/api/purchase-demand/import', {
data: excelData,
factory: factory,
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(); // 重新加载列表
loadFactories(); // 重新加载工厂列表
loadProducts(); // 重新加载产品列表
} 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,
downloadImportTemplate,
exportExcel,
deleteProductData,
edit,
closeEditModal,
saveEdit,
openBatchEditStatusModal,
closeBatchEditStatusModal,
saveBatchEditStatus,
syncToInitialStock,
toggleSelectAll,
switchProduct,
switchFactory,
showDeleteFactoryModal,
goPage
};
})();