const express = require('express'); const cors = require('cors'); const path = require('path'); const dotenv = require('dotenv'); const db = require('./db-sqlite'); const multer = require('multer'); const { body, validationResult } = require('express-validator'); // 认证工具和中间件 const { hashPassword, verifyPassword, generateToken, verifyToken } = require('./utils/auth'); const { authenticate, optionalAuth, requireRole, requireAdmin } = require('./middleware/auth'); // 验证错误处理中间件 const validate = (req, res, next) => { const errors = validationResult(req); if (!errors.isEmpty()) { return res.status(400).json({ success: false, errors: errors.array() }); } next(); }; // 加载环境变量 dotenv.config(); const app = express(); const PORT = process.env.PORT || 3002; // 中间件 app.use(cors()); app.use(express.json()); app.use(express.urlencoded({ extended: true })); // 静态文件服务 - 前端应用 app.use(express.static(path.join(__dirname, '../frontend/dist'))); // 用户相关 API // 获取用户列表 // ==================== 认证路由 ==================== const authRoutes = require('./routes/auth'); app.use('/api/auth', authRoutes); app.get('/api/users', authenticate, async (req, res) => { try { const usersResult = await db.query('SELECT id, username, name, email, phone, role, created_at, updated_at FROM users'); const users = usersResult.rows; res.json({ success: true, data: users, count: users.length }); } catch (error) { console.error('获取用户列表失败:', error); res.status(500).json({ success: false, message: '获取用户列表失败' }); } }); // 更新用户信息 app.put('/api/users/:id', authenticate, async (req, res) => { try { const { id } = req.params; const { name, email, phone, role } = req.body; await db.query( 'UPDATE users SET name = ?, email = ?, phone = ?, role = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?', [name, email, phone, role, id] ); const updatedUserResult = await db.query('SELECT id, username, name, email, phone, role, created_at, updated_at FROM users WHERE id = ?', [id]); const updatedUser = updatedUserResult.rows[0]; res.json({ success: true, data: updatedUser }); } catch (error) { console.error('更新用户信息失败:', error); res.status(500).json({ success: false, message: '更新用户信息失败' }); } }); // 更新用户密码 app.put('/api/users/:id/password', authenticate, async (req, res) => { try { const { id } = req.params; const { currentPassword, newPassword } = req.body; // 只能修改自己的密码,或者管理员可以修改任何人的密码 if (req.user.id !== parseInt(id) && req.user.role !== 'admin') { return res.status(403).json({ success: false, message: '只能修改自己的密码' }); } // 验证当前密码 const userResult = await db.query('SELECT password, password_hash FROM users WHERE id = ?', [id]); if (userResult.rows.length === 0) { return res.status(404).json({ success: false, message: '用户不存在' }); } const user = userResult.rows[0]; // 验证当前密码(优先使用哈希验证) let isValidPassword = false; if (user.password_hash) { isValidPassword = verifyPassword(currentPassword, user.password_hash); } else { isValidPassword = (user.password === currentPassword); } if (!isValidPassword) { return res.status(400).json({ success: false, message: '当前密码错误' }); } // 新密码哈希 const newPasswordHash = hashPassword(newPassword); // 更新密码(同时更新明文和哈希) await db.query( 'UPDATE users SET password = ?, password_hash = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?', [newPassword, newPasswordHash, id] ); console.log(`用户 ID ${id} 密码已更新`); res.json({ success: true, message: '密码更新成功' }); } catch (error) { console.error('更新密码失败:', error); res.status(500).json({ success: false, message: '更新密码失败' }); } }); // 创建用户 app.post('/api/users', authenticate, async (req, res) => { try { const { username, name, email, phone, role, password } = req.body; // 验证必填字段 if (!username || !name || !password) { return res.status(400).json({ success: false, message: '用户名、姓名和密码为必填项' }); } // 检查用户名是否已存在 const existingUser = await db.query('SELECT id FROM users WHERE username = ?', [username]); if (existingUser.rows.length > 0) { return res.status(400).json({ success: false, message: '用户名已存在' }); } // 密码哈希处理 const passwordHash = hashPassword(password); // 创建新用户(同时存储明文密码和哈希密码,用于兼容) const result = await db.query( 'INSERT INTO users (username, password, password_hash, name, email, phone, role, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)', [username, password, passwordHash, name, email || '', phone || '', role || 'user'] ); // 获取新创建的用户 const newUserResult = await db.query('SELECT id, username, name, email, phone, role, created_at, updated_at FROM users WHERE id = ?', [result.lastID]); const newUser = newUserResult.rows[0]; console.log(`用户 ${username} 创建成功,由 ${req.user.username} 操作`); res.json({ success: true, data: newUser }); } catch (error) { console.error('创建用户失败:', error); res.status(500).json({ success: false, message: '创建用户失败' }); } }); // 删除用户 app.delete('/api/users/:id', async (req, res) => { try { const { id } = req.params; // 检查用户是否存在 const existingUser = await db.query('SELECT id FROM users WHERE id = ?', [id]); if (existingUser.rows.length === 0) { return res.status(404).json({ success: false, message: '用户不存在' }); } // 删除用户 await db.query('DELETE FROM users WHERE id = ?', [id]); res.json({ success: true, message: '用户删除成功' }); } catch (error) { console.error('删除用户失败:', error); res.status(500).json({ success: false, message: '删除用户失败' }); } }); // 创建供应商收款信息表 async function createSupplierPaymentInfosTable() { try { await db.query(` CREATE TABLE IF NOT EXISTS supplier_payment_infos ( id INTEGER PRIMARY KEY AUTOINCREMENT, supplier_id INTEGER NOT NULL, account_name TEXT NOT NULL, bank_account TEXT NOT NULL, bank_name TEXT NOT NULL, qr_code TEXT, is_primary INTEGER DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (supplier_id) REFERENCES suppliers(id) ON DELETE CASCADE ) `); console.log('供应商收款信息表创建成功'); } catch (error) { console.error('创建供应商收款信息表失败:', error); } } // 添加purchase_type字段到purchase_requests表 async function addPurchaseTypeColumn() { try { // 检查字段是否存在 const result = await db.query(`PRAGMA table_info(purchase_requests)`); const hasPurchaseType = result.rows.some(row => row.name === 'purchase_type'); if (!hasPurchaseType) { await db.query(`ALTER TABLE purchase_requests ADD COLUMN purchase_type TEXT DEFAULT 'inventory'`); console.log('purchase_type字段添加成功'); } else { console.log('purchase_type字段已存在'); } } catch (error) { console.error('添加purchase_type字段失败:', error); } } // 添加brief_description字段到purchase_requests表 async function addBriefDescriptionColumn() { try { // 检查字段是否存在 const result = await db.query(`PRAGMA table_info(purchase_requests)`); const hasBriefDescription = result.rows.some(row => row.name === 'brief_description'); if (!hasBriefDescription) { await db.query(`ALTER TABLE purchase_requests ADD COLUMN brief_description TEXT`); console.log('brief_description字段添加成功'); } else { console.log('brief_description字段已存在'); } } catch (error) { console.error('添加brief_description字段失败:', error); } } // 添加execute_date和execute_method字段到purchase_requests表 async function addExecuteColumns() { try { // 检查字段是否存在 const result = await db.query(`PRAGMA table_info(purchase_requests)`); const hasExecuteDate = result.rows.some(row => row.name === 'execute_date'); const hasExecuteMethod = result.rows.some(row => row.name === 'execute_method'); if (!hasExecuteDate) { await db.query(`ALTER TABLE purchase_requests ADD COLUMN execute_date TEXT`); console.log('execute_date字段添加成功'); } else { console.log('execute_date字段已存在'); } if (!hasExecuteMethod) { await db.query(`ALTER TABLE purchase_requests ADD COLUMN execute_method TEXT`); console.log('execute_method字段添加成功'); } else { console.log('execute_method字段已存在'); } } catch (error) { console.error('添加执行字段失败:', error); } } // 添加attachments字段到purchase_requests表 async function addAttachmentsColumn() { try { // 检查字段是否存在 const result = await db.query(`PRAGMA table_info(purchase_requests)`); const hasAttachments = result.rows.some(row => row.name === 'attachments'); if (!hasAttachments) { await db.query(`ALTER TABLE purchase_requests ADD COLUMN attachments TEXT DEFAULT ''`); console.log('attachments字段添加成功'); } else { console.log('attachments字段已存在'); } } catch (error) { console.error('添加attachments字段失败:', error); } } // 添加request_date、expense_category和currency字段到purchase_requests表 async function addRequestDateAndCategoryColumns() { try { // 检查字段是否存在 const result = await db.query(`PRAGMA table_info(purchase_requests)`); const hasRequestDate = result.rows.some(row => row.name === 'request_date'); const hasExpenseCategory = result.rows.some(row => row.name === 'expense_category'); const hasCurrency = result.rows.some(row => row.name === 'currency'); if (!hasRequestDate) { await db.query(`ALTER TABLE purchase_requests ADD COLUMN request_date TEXT`); console.log('request_date字段添加成功'); } else { console.log('request_date字段已存在'); } if (!hasExpenseCategory) { await db.query(`ALTER TABLE purchase_requests ADD COLUMN expense_category TEXT`); console.log('expense_category字段添加成功'); } else { console.log('expense_category字段已存在'); } if (!hasCurrency) { await db.query(`ALTER TABLE purchase_requests ADD COLUMN currency TEXT DEFAULT 'CNY'`); console.log('currency字段添加成功'); } else { console.log('currency字段已存在'); } } catch (error) { console.error('添加request_date、expense_category和currency字段失败:', error); } } // 创建库存管理表 async function createInventoryTable() { try { await db.query(` CREATE TABLE IF NOT EXISTS inventory ( id INTEGER PRIMARY KEY AUTOINCREMENT, product_id INTEGER, product_name TEXT NOT NULL, quantity REAL NOT NULL, unit TEXT NOT NULL, price REAL, total_value REAL, location TEXT, status TEXT DEFAULT 'in_stock', last_updated DATE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (product_id) REFERENCES products(id) ) `); console.log('库存管理表创建成功'); } catch (error) { console.error('创建库存管理表失败:', error); } } // 初始化数据库表 createSupplierPaymentInfosTable(); createInventoryTable(); addPurchaseTypeColumn(); addBriefDescriptionColumn(); addExecuteColumns(); addAttachmentsColumn(); addRequestDateAndCategoryColumns(); // ==================== 健康检查 ==================== app.get('/api/health', (req, res) => { res.json({ success: true, message: '公司财务管理系统 API', version: '1.0.0', timestamp: new Date().toISOString(), endpoints: { upload: "/api/upload", health: '/api/health', auth: '/api/auth', customers: '/api/customers', suppliers: '/api/suppliers', projects: '/api/projects', products: '/api/products', payment_nodes: '/api/payment-nodes', payment_records: '/api/payment-records', exchange_rates: '/api/exchange-rates', advances: '/api/advances', reimbursements: '/api/reimbursements', purchase_requests: '/api/purchase-requests', inventory: '/api/inventory', finance_stats: '/api/finance-stats' } }); }); // ==================== 认证API ==================== // ==================== 客户管理API ==================== app.get('/api/customers', async (req, res) => { try { const result = await db.query(` SELECT * FROM customers ORDER BY created_at DESC LIMIT 50 `); // 为每个客户获取联系人和收款信息 const customersWithDetails = await Promise.all( result.rows.map(async (customer) => { // 获取联系人信息 const contactsResult = await db.query( `SELECT * FROM contacts WHERE entity_id = ? AND entity_type = 'customer' ORDER BY is_primary DESC`, [customer.id] ); const contacts = contactsResult.rows.map(contact => ({ name: contact.name || '未命名', position: contact.position || '', phone: contact.phone || '', is_primary: contact.is_primary === 1 })); // 获取收款信息 const paymentInfosResult = await db.query( `SELECT * FROM supplier_payment_infos WHERE supplier_id = ? ORDER BY is_default DESC`, [customer.id] ); const paymentInfos = paymentInfosResult.rows.map(payment => ({ id: payment.id, account_name: payment.account_name, bank_account: payment.account_number, bank_name: payment.bank_name, qr_code: payment.qr_code, is_primary: payment.is_default === 1 })); return { ...customer, contacts: contacts.length > 0 ? contacts : [], payment_infos: paymentInfos.length > 0 ? paymentInfos : [] }; }) ); res.json({ success: true, data: customersWithDetails, count: customersWithDetails.length }); } catch (error) { console.error('获取客户失败:', error); res.status(500).json({ success: false, message: '获取客户失败', error: error.message }); } }); app.get('/api/customers/:id', async (req, res) => { try { const { id } = req.params; // 获取客户基本信息 const customerResult = await db.query(` SELECT * FROM customers WHERE id = ? `, [id]); if (customerResult.rows.length > 0) { const customer = customerResult.rows[0]; // 获取客户的所有联系人 const contactsResult = await db.query(` SELECT * FROM contacts WHERE entity_id = ? AND entity_type = 'customer' ORDER BY is_primary DESC `, [id]); // 转换联系人数据结构 const contacts = contactsResult.rows.map(contact => ({ name: contact.name || '未命名', position: contact.position || '', phone: contact.phone || '', is_primary: contact.is_primary === 1 })); // 获取客户的所有收款信息 const paymentInfosResult = await db.query(` SELECT * FROM supplier_payment_infos WHERE supplier_id = ? ORDER BY is_default DESC `, [id]); // 转换收款信息数据结构 const payment_infos = paymentInfosResult.rows.map(info => ({ id: info.id, account_name: info.account_name || '', bank_name: info.bank_name || '', bank_account: info.account_number || '', qr_code: info.qr_code || '', is_primary: info.is_default === 1 })); // 转换数据结构以匹配前端期望 const formattedCustomer = { id: customer.id, code: `C${String(customer.id).padStart(4, '0')}`, // 生成客户编号 name: customer.name, address: customer.address, contacts: contacts.length > 0 ? contacts : [], // 使用从联系人表获取的联系人 payment_infos: payment_infos.length > 0 ? payment_infos : [], // 添加收款信息 remark: customer.remark || '', // 默认为空 total_contract_amount: 0, // 默认为0 total_received: 0, // 默认为0 total_receivable: 0, // 默认为0 created_at: customer.created_at }; res.json({ success: true, data: formattedCustomer }); } else { res.status(404).json({ success: false, message: '客户不存在' }); } } catch (error) { console.error('获取客户详情失败:', error); res.status(500).json({ success: false, message: '获取客户详情失败', error: error.message }); } }); app.post('/api/customers', async (req, res) => { try { const { name, address, remark, contacts, payment_infos } = req.body; // 从contacts中获取主联系人信息 const primaryContact = contacts?.find(c => c.is_primary) || contacts?.[0]; const contact = primaryContact?.name || ''; const position = primaryContact?.position || ''; const phone = primaryContact?.phone || ''; const email = ''; // 前端没有email字段 const result = await db.query( `INSERT INTO customers (name, address, contact, position, phone, email, remark, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))`, [name, address, contact, position, phone, email, remark] ); const customerId = result.lastID; // 插入联系人数据 if (contacts && contacts.length > 0) { for (const contactItem of contacts) { await db.query( `INSERT INTO contacts (entity_id, entity_type, name, position, phone, is_primary, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))`, [customerId, 'customer', contactItem.name, contactItem.position, contactItem.phone, contactItem.is_primary ? 1 : 0] ); } } // 插入收款信息数据 if (payment_infos && payment_infos.length > 0) { for (const paymentItem of payment_infos) { await db.query( `INSERT INTO supplier_payment_infos (supplier_id, account_name, bank_name, bank_account, qr_code, is_primary, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))`, [customerId, paymentItem.account_name, paymentItem.bank_name, paymentItem.bank_account, paymentItem.qr_code, paymentItem.is_primary ? 1 : 0] ); } } res.json({ success: true, message: '客户创建成功', data: { id: customerId, code: `C${String(customerId).padStart(4, '0')}`, name, address, contacts: contacts || [], payment_infos: payment_infos || [], remark, total_contract_amount: 0, total_received: 0, total_receivable: 0, created_at: new Date().toISOString() } }); } catch (error) { console.error('创建客户失败:', error); res.status(500).json({ success: false, message: '创建客户失败', error: error.message }); } }); app.put('/api/customers/:id', async (req, res) => { try { const { id } = req.params; const { name, address, remark, contacts, payment_infos } = req.body; // 从contacts中获取主联系人信息 const primaryContact = contacts?.find(c => c.is_primary) || contacts?.[0]; const contact = primaryContact?.name || ''; const position = primaryContact?.position || ''; const phone = primaryContact?.phone || ''; const email = ''; // 前端没有email字段 await db.query( `UPDATE customers SET name = ?, address = ?, contact = ?, position = ?, phone = ?, email = ?, remark = ?, updated_at = datetime('now') WHERE id = ?`, [name, address, contact, position, phone, email, remark, id] ); // 删除旧的联系人数据 await db.query(`DELETE FROM contacts WHERE entity_id = ? AND entity_type = 'customer'`, [id]); // 插入新的联系人数据 if (contacts && contacts.length > 0) { for (const contactItem of contacts) { await db.query( `INSERT INTO contacts (entity_id, entity_type, name, position, phone, is_primary, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))`, [id, 'customer', contactItem.name, contactItem.position, contactItem.phone, contactItem.is_primary ? 1 : 0] ); } } // 删除旧的收款信息数据 await db.query(`DELETE FROM supplier_payment_infos WHERE supplier_id = ?`, [id]); // 插入新的收款信息数据 if (payment_infos && payment_infos.length > 0) { for (const paymentItem of payment_infos) { await db.query( `INSERT INTO supplier_payment_infos (supplier_id, account_name, bank_name, bank_account, qr_code, is_primary, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))`, [id, paymentItem.account_name, paymentItem.bank_name, paymentItem.bank_account, paymentItem.qr_code, paymentItem.is_primary ? 1 : 0] ); } } res.json({ success: true, message: '客户更新成功', data: { id, code: `C${String(id).padStart(4, '0')}`, name, address, contacts: contacts || [], payment_infos: payment_infos || [], remark, total_contract_amount: 0, total_received: 0, total_receivable: 0, created_at: new Date().toISOString() } }); } catch (error) { console.error('更新客户失败:', error); res.status(500).json({ success: false, message: '更新客户失败', error: error.message }); } }); app.delete('/api/customers/:id', async (req, res) => { try { const { id } = req.params; // 先删除关联的联系人数据 await db.query(`DELETE FROM contacts WHERE entity_id = ? AND entity_type = 'customer'`, [id]); // 再删除客户数据 const result = await db.query(`DELETE FROM customers WHERE id = ?`, [id]); if (result.changes > 0) { res.json({ success: true, message: '客户删除成功' }); } else { res.status(404).json({ success: false, message: '客户不存在' }); } } catch (error) { console.error('删除客户失败:', error); res.status(500).json({ success: false, message: '删除客户失败', error: error.message }); } }); // ==================== 供应商管理API ==================== app.get('/api/suppliers', async (req, res) => { try { const result = await db.query(` SELECT * FROM suppliers ORDER BY created_at DESC LIMIT 50 `); // 为每个供应商获取联系人和收款信息 const suppliersWithDetails = await Promise.all( result.rows.map(async (supplier) => { // 获取联系人信息 const contactsResult = await db.query( `SELECT * FROM contacts WHERE entity_id = ? AND entity_type = 'supplier' ORDER BY is_primary DESC`, [supplier.id] ); const contacts = contactsResult.rows.map(contact => ({ name: contact.name || '未命名', position: contact.position || '', phone: contact.phone || '', is_primary: contact.is_primary === 1 })); // 获取收款信息 const paymentInfosResult = await db.query( `SELECT * FROM supplier_payment_infos WHERE supplier_id = ? ORDER BY is_default DESC`, [supplier.id] ); const paymentInfos = paymentInfosResult.rows.map(payment => ({ id: payment.id, account_name: payment.account_name, bank_account: payment.account_number, bank_name: payment.bank_name, qr_code: payment.qr_code, is_primary: payment.is_default === 1 })); return { ...supplier, contacts: contacts.length > 0 ? contacts : [], payment_infos: paymentInfos.length > 0 ? paymentInfos : [] }; }) ); res.json({ success: true, data: suppliersWithDetails, count: suppliersWithDetails.length }); } catch (error) { console.error('获取供应商失败:', error); res.status(500).json({ success: false, message: '获取供应商失败', error: error.message }); } }); app.get('/api/suppliers/:id', async (req, res) => { try { const { id } = req.params; // 获取供应商基本信息 const supplierResult = await db.query(` SELECT * FROM suppliers WHERE id = ? `, [id]); if (supplierResult.rows.length > 0) { const supplier = supplierResult.rows[0]; // 获取供应商的所有联系人 const contactsResult = await db.query(` SELECT * FROM contacts WHERE entity_id = ? AND entity_type = 'supplier' ORDER BY is_primary DESC `, [id]); // 转换联系人数据结构 const contacts = contactsResult.rows.map(contact => ({ name: contact.name || '未命名', position: contact.position || '', phone: contact.phone || '', is_primary: contact.is_primary === 1 })); // 获取供应商的所有收款信息 const paymentInfosResult = await db.query(` SELECT * FROM supplier_payment_infos WHERE supplier_id = ? ORDER BY is_default DESC `, [id]); // 转换收款信息数据结构 const paymentInfos = paymentInfosResult.rows.map(payment => ({ id: payment.id, account_name: payment.account_name, bank_account: payment.account_number, bank_name: payment.bank_name, qr_code: payment.qr_code, is_primary: payment.is_default === 1 })); // 转换数据结构以匹配前端期望 const formattedSupplier = { id: supplier.id, code: `S${String(supplier.id).padStart(4, '0')}`, // 生成供应商编号 name: supplier.name || '未命名', supply_category: supplier.supply_category || '电力设备', // 默认为电力设备 country: supplier.country || 'Laos', // 默认为老挝 contacts: contacts.length > 0 ? contacts : [], // 使用从联系人表获取的联系人 payment_infos: paymentInfos.length > 0 ? paymentInfos : [], // 使用从收款信息表获取的收款信息 remark: supplier.remark || '', // 默认为空 total_purchase_amount: 0, // 默认为0 total_paid: 0, // 默认为0 total_payable: 0, // 默认为0 created_at: supplier.created_at }; // 设置响应头确保UTF-8编码 res.setHeader('Content-Type', 'application/json; charset=utf-8'); res.json({ success: true, data: formattedSupplier }); } else { res.status(404).json({ success: false, message: '供应商不存在' }); } } catch (error) { console.error('获取供应商详情失败:', error); res.status(500).json({ success: false, message: '获取供应商详情失败', error: error.message }); } }); app.post('/api/suppliers', async (req, res) => { try { const { name, supply_category, country, remark, contacts, payment_infos } = req.body; // 从contacts中获取主联系人信息 const primaryContact = contacts?.find(c => c.is_primary) || contacts?.[0]; const contact = primaryContact?.name || ''; const position = primaryContact?.position || ''; const phone = primaryContact?.phone || ''; const email = ''; // 前端没有email字段 const address = ''; // 前端没有address字段 const result = await db.query( `INSERT INTO suppliers (name, address, contact, position, phone, email, supply_category, country, remark, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))`, [name, address, contact, position, phone, email, supply_category, country, remark] ); const supplierId = result.lastID; // 插入联系人数据 if (contacts && contacts.length > 0) { for (const contactItem of contacts) { await db.query( `INSERT INTO contacts (entity_id, entity_type, name, position, phone, is_primary, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))`, [supplierId, 'supplier', contactItem.name, contactItem.position, contactItem.phone, contactItem.is_primary ? 1 : 0] ); } } // 插入收款信息数据 if (payment_infos && payment_infos.length > 0) { for (const paymentInfo of payment_infos) { await db.query( `INSERT INTO supplier_payment_infos (supplier_id, account_name, bank_account, bank_name, qr_code, is_primary, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))`, [supplierId, paymentInfo.account_name, paymentInfo.bank_account, paymentInfo.bank_name, paymentInfo.qr_code, paymentInfo.is_primary ? 1 : 0] ); } } res.json({ success: true, message: '供应商创建成功', data: { id: supplierId, code: `S${String(supplierId).padStart(4, '0')}`, name, supply_category, country, contacts: contacts || [], payment_infos: payment_infos || [], remark, total_purchase_amount: 0, total_paid: 0, total_payable: 0, created_at: new Date().toISOString() } }); } catch (error) { console.error('创建供应商失败:', error); res.status(500).json({ success: false, message: '创建供应商失败', error: error.message }); } }); app.put('/api/suppliers/:id', async (req, res) => { try { const { id } = req.params; const { name, supply_category, country, remark, contacts, payment_infos } = req.body; // 从contacts中获取主联系人信息 const primaryContact = contacts?.find(c => c.is_primary) || contacts?.[0]; const contact = primaryContact?.name || ''; const position = primaryContact?.position || ''; const phone = primaryContact?.phone || ''; const email = ''; // 前端没有email字段 const address = ''; // 前端没有address字段 await db.query( `UPDATE suppliers SET name = ?, address = ?, contact = ?, position = ?, phone = ?, email = ?, supply_category = ?, country = ?, remark = ?, updated_at = datetime('now') WHERE id = ?`, [name, address, contact, position, phone, email, supply_category, country, remark, id] ); // 删除旧的联系人数据 await db.query(`DELETE FROM contacts WHERE entity_id = ? AND entity_type = 'supplier'`, [id]); // 插入新的联系人数据 if (contacts && contacts.length > 0) { for (const contactItem of contacts) { await db.query( `INSERT INTO contacts (entity_id, entity_type, name, position, phone, is_primary, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))`, [id, 'supplier', contactItem.name, contactItem.position, contactItem.phone, contactItem.is_primary ? 1 : 0] ); } } // 删除旧的收款信息数据 await db.query(`DELETE FROM supplier_payment_infos WHERE supplier_id = ?`, [id]); // 插入新的收款信息数据 if (payment_infos && payment_infos.length > 0) { for (const paymentInfo of payment_infos) { await db.query( `INSERT INTO supplier_payment_infos (supplier_id, account_name, bank_account, bank_name, qr_code, is_primary, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))`, [id, paymentInfo.account_name, paymentInfo.bank_account, paymentInfo.bank_name, paymentInfo.qr_code, paymentInfo.is_primary ? 1 : 0] ); } } res.json({ success: true, message: '供应商更新成功', data: { id, code: `S${String(id).padStart(4, '0')}`, name, supply_category, country, contacts: contacts || [], payment_infos: payment_infos || [], remark, total_purchase_amount: 0, total_paid: 0, total_payable: 0, created_at: new Date().toISOString() } }); } catch (error) { console.error('更新供应商失败:', error); res.status(500).json({ success: false, message: '更新供应商失败', error: error.message }); } }); app.delete('/api/suppliers/:id', async (req, res) => { try { const { id } = req.params; // 先删除关联的联系人数据 await db.query(`DELETE FROM contacts WHERE entity_id = ? AND entity_type = 'supplier'`, [id]); // 再删除供应商数据 const result = await db.query(`DELETE FROM suppliers WHERE id = ?`, [id]); if (result.changes > 0) { res.json({ success: true, message: '供应商删除成功' }); } else { res.status(404).json({ success: false, message: '供应商不存在' }); } } catch (error) { console.error('删除供应商失败:', error); res.status(500).json({ success: false, message: '删除供应商失败', error: error.message }); } }); // ==================== 分包商管理API ==================== app.get('/api/subcontractors', async (req, res) => { try { const result = await db.query(` SELECT * FROM subcontractors ORDER BY created_at DESC LIMIT 50 `); // 为每个分包商获取联系人和收款信息 const subcontractorsWithDetails = await Promise.all( result.rows.map(async (subcontractor) => { // 获取联系人信息 const contactsResult = await db.query( `SELECT * FROM contacts WHERE entity_id = ? AND entity_type = 'subcontractor' ORDER BY is_primary DESC`, [subcontractor.id] ); const contacts = contactsResult.rows.map(contact => ({ name: contact.name || '未命名', position: contact.position || '', phone: contact.phone || '', is_primary: contact.is_primary === 1 })); // 获取收款信息 const paymentInfosResult = await db.query( `SELECT * FROM supplier_payment_infos WHERE supplier_id = ? ORDER BY is_default DESC`, [subcontractor.id] ); const paymentInfos = paymentInfosResult.rows.map(payment => ({ id: payment.id, account_name: payment.account_name, bank_account: payment.account_number, bank_name: payment.bank_name, qr_code: payment.qr_code, is_primary: payment.is_default === 1 })); return { ...subcontractor, contacts: contacts.length > 0 ? contacts : [], payment_infos: paymentInfos.length > 0 ? paymentInfos : [] }; }) ); res.json({ success: true, data: subcontractorsWithDetails, count: subcontractorsWithDetails.length }); } catch (error) { console.error('获取分包商失败:', error); res.status(500).json({ success: false, message: '获取分包商失败', error: error.message }); } }); app.get('/api/subcontractors/:id', async (req, res) => { try { const { id } = req.params; // 获取分包商基本信息 const subcontractorResult = await db.query(` SELECT * FROM subcontractors WHERE id = ? `, [id]); if (subcontractorResult.rows.length > 0) { const subcontractor = subcontractorResult.rows[0]; // 获取分包商的所有联系人 const contactsResult = await db.query(` SELECT * FROM contacts WHERE entity_id = ? AND entity_type = 'subcontractor' ORDER BY is_primary DESC `, [id]); // 转换联系人数据结构 const contacts = contactsResult.rows.map(contact => ({ name: contact.name || '未命名', position: contact.position || '', phone: contact.phone || '', is_primary: contact.is_primary === 1 })); // 获取分包商的所有收款信息 const paymentInfosResult = await db.query(` SELECT * FROM supplier_payment_infos WHERE supplier_id = ? ORDER BY is_default DESC `, [id]); // 转换收款信息数据结构 const paymentInfos = paymentInfosResult.rows.map(payment => ({ id: payment.id, account_name: payment.account_name, bank_account: payment.account_number, bank_name: payment.bank_name, qr_code: payment.qr_code, is_primary: payment.is_default === 1 })); // 转换数据结构以匹配前端期望 const formattedSubcontractor = { id: subcontractor.id, code: `SC${String(subcontractor.id).padStart(4, '0')}`, // 生成分包商编号 name: subcontractor.name, scope: subcontractor.scope || '', // 默认为空 features: subcontractor.features || '', // 默认为空 country: subcontractor.country || '', // 默认为空 contacts: contacts.length > 0 ? contacts : [], // 使用从联系人表获取的联系人 payment_infos: paymentInfos.length > 0 ? paymentInfos : [], // 使用从收款信息表获取的收款信息 remark: subcontractor.remark || '', // 默认为空 total_contract_amount: 0, // 默认为0 total_paid: 0, // 默认为0 total_payable: 0, // 默认为0 created_at: subcontractor.created_at }; res.json({ success: true, data: formattedSubcontractor }); } else { res.status(404).json({ success: false, message: '分包商不存在' }); } } catch (error) { console.error('获取分包商详情失败:', error); res.status(500).json({ success: false, message: '获取分包商详情失败', error: error.message }); } }); app.post('/api/subcontractors', async (req, res) => { try { const { name, scope, features, country, remark, contacts, payment_infos } = req.body; // 从contacts中获取主联系人信息 const primaryContact = contacts?.find(c => c.is_primary) || contacts?.[0]; const contact = primaryContact?.name || ''; const position = primaryContact?.position || ''; const phone = primaryContact?.phone || ''; const email = ''; // 前端没有email字段 const address = ''; // 前端没有address字段 const result = await db.query( `INSERT INTO subcontractors (name, address, contact, position, phone, email, scope, features, country, remark, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))`, [name, address, contact, position, phone, email, scope, features, country, remark] ); const subcontractorId = result.lastID; // 插入联系人数据 if (contacts && contacts.length > 0) { for (const contactItem of contacts) { await db.query( `INSERT INTO contacts (entity_id, entity_type, name, position, phone, is_primary, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))`, [subcontractorId, 'subcontractor', contactItem.name, contactItem.position, contactItem.phone, contactItem.is_primary ? 1 : 0] ); } } // 插入收款信息数据 if (payment_infos && payment_infos.length > 0) { for (const paymentItem of payment_infos) { await db.query( `INSERT INTO supplier_payment_infos (supplier_id, account_name, bank_name, bank_account, qr_code, is_primary, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))`, [subcontractorId, paymentItem.account_name, paymentItem.bank_name, paymentItem.bank_account, paymentItem.qr_code, paymentItem.is_primary ? 1 : 0] ); } } res.json({ success: true, message: '分包商创建成功', data: { id: subcontractorId, code: `SC${String(subcontractorId).padStart(4, '0')}`, name, scope, features, country, contacts: contacts || [], payment_infos: payment_infos || [], remark, total_contract_amount: 0, total_paid: 0, total_payable: 0, created_at: new Date().toISOString() } }); } catch (error) { console.error('创建分包商失败:', error); res.status(500).json({ success: false, message: '创建分包商失败', error: error.message }); } }); app.put('/api/subcontractors/:id', async (req, res) => { try { const { id } = req.params; const { name, scope, features, country, remark, contacts, payment_infos } = req.body; // 从contacts中获取主联系人信息 const primaryContact = contacts?.find(c => c.is_primary) || contacts?.[0]; const contact = primaryContact?.name || ''; const position = primaryContact?.position || ''; const phone = primaryContact?.phone || ''; const email = ''; // 前端没有email字段 const address = ''; // 前端没有address字段 await db.query( `UPDATE subcontractors SET name = ?, address = ?, contact = ?, position = ?, phone = ?, email = ?, scope = ?, features = ?, country = ?, remark = ?, updated_at = datetime('now') WHERE id = ?`, [name, address, contact, position, phone, email, scope, features, country, remark, id] ); // 删除旧的联系人数据 await db.query(`DELETE FROM contacts WHERE entity_id = ? AND entity_type = 'subcontractor'`, [id]); // 插入新的联系人数据 if (contacts && contacts.length > 0) { for (const contactItem of contacts) { await db.query( `INSERT INTO contacts (entity_id, entity_type, name, position, phone, is_primary, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))`, [id, 'subcontractor', contactItem.name, contactItem.position, contactItem.phone, contactItem.is_primary ? 1 : 0] ); } } // 删除旧的收款信息数据 await db.query(`DELETE FROM supplier_payment_infos WHERE supplier_id = ?`, [id]); // 插入新的收款信息数据 if (payment_infos && payment_infos.length > 0) { for (const paymentItem of payment_infos) { await db.query( `INSERT INTO supplier_payment_infos (supplier_id, account_name, bank_name, bank_account, qr_code, is_primary, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))`, [id, paymentItem.account_name, paymentItem.bank_name, paymentItem.bank_account, paymentItem.qr_code, paymentItem.is_primary ? 1 : 0] ); } } res.json({ success: true, message: '分包商更新成功', data: { id, code: `SC${String(id).padStart(4, '0')}`, name, scope, features, country, contacts: contacts || [], payment_infos: payment_infos || [], remark, total_contract_amount: 0, total_paid: 0, total_payable: 0, created_at: new Date().toISOString() } }); } catch (error) { console.error('更新分包商失败:', error); res.status(500).json({ success: false, message: '更新分包商失败', error: error.message }); } }); app.delete('/api/subcontractors/:id', async (req, res) => { try { const { id } = req.params; // 先删除关联的联系人数据 await db.query(`DELETE FROM contacts WHERE entity_id = ? AND entity_type = 'subcontractor'`, [id]); // 再删除分包商数据 const result = await db.query(`DELETE FROM subcontractors WHERE id = ?`, [id]); if (result.changes > 0) { res.json({ success: true, message: '分包商删除成功' }); } else { res.status(404).json({ success: false, message: '分包商不存在' }); } } catch (error) { console.error('删除分包商失败:', error); res.status(500).json({ success: false, message: '删除分包商失败', error: error.message }); } }); // ==================== 项目管理API ==================== app.get('/api/projects', async (req, res) => { try { const result = await db.query(` SELECT p.*, c.name as customer_name, u.name as manager_name FROM projects p LEFT JOIN customers c ON p.customer_id = c.id LEFT JOIN users u ON p.manager_id = u.id ORDER BY p.created_at DESC LIMIT 50 `); res.json({ success: true, data: result.rows, count: result.rows.length }); } catch (error) { console.error('获取项目失败:', error); res.status(500).json({ success: false, message: '获取项目失败', error: error.message }); } }); // ==================== 项目详情API ==================== app.get('/api/projects/:id', async (req, res) => { try { const { id } = req.params; // 获取项目基本信息 const projectResult = await db.query(` SELECT p.*, c.name as customer_name, u.name as manager_name FROM projects p LEFT JOIN customers c ON p.customer_id = c.id LEFT JOIN users u ON p.manager_id = u.id WHERE p.id = ? `, [id]); if (projectResult.rows.length > 0) { const project = projectResult.rows[0]; // 获取项目合同信息 const contractResult = await db.query(` SELECT * FROM project_contracts WHERE project_id = ? ORDER BY created_at DESC LIMIT 1 `, [id]); const contract = contractResult.rows[0]; // 从合同表读取质保金数据,如果没有则使用默认值 const warrantyPercent = contract?.warranty_deposit_percentage || 5; const warrantyMonths = contract?.warranty_period || 12; const contractAmount = parseFloat(project.contract_amount || 0); // 计算质保金金额:合同金额 * 质保比例 / 100 const warrantyAmount = Math.round(contractAmount * warrantyPercent / 100); // 计算质保期结束日期 const warrantyStartDate = project.end_date; const warrantyEndDate = warrantyStartDate ? new Date(new Date(warrantyStartDate).getTime() + warrantyMonths * 30 * 24 * 60 * 60 * 1000).toISOString() : null; res.json({ success: true, data: { id: project.id, project_code: project.code || `PROJ-${String(project.id).padStart(4, '0')}`, name: project.name, customer_id: project.customer_id, customer_name: project.customer_name || '未知客户', status: project.status || 'planning', budget: '0', spent: '0', start_date: project.start_date, end_date: project.end_date, description: project.description, contract_type: 'lump_sum', contract_amount: project.contract_amount?.toString() || '0', currency: 'CNY', contract_days: contract?.contract_period || 180, project_manager_id: project.manager_id, manager_id: project.manager_id, manager_name: project.manager_name || '未知经理', location: project.location || '', work_quantity: '', project_situation: project.description || '', settlement_type: contract?.settlement_method || 'lump_sum', has_warranty: true, warranty_amount: warrantyAmount.toString(), warranty_percent: warrantyPercent.toString(), warranty_months: warrantyMonths, warranty_start_date: warrantyStartDate, warranty_end_date: warrantyEndDate, warranty_status: 'pending' } }); } else { res.status(404).json({ success: false, message: '项目不存在' }); } } catch (error) { console.error('获取项目详情失败:', error); res.status(500).json({ success: false, message: '获取项目详情失败', error: error.message }); } }); // ==================== 项目合同API ==================== app.get('/api/projects/:id/contracts', async (req, res) => { try { const { id } = req.params; const result = await db.query(` SELECT * FROM project_contracts WHERE project_id = ? ORDER BY created_at DESC `, [id]); res.json({ success: true, data: result.rows }); } catch (error) { console.error('获取项目合同失败:', error); res.status(500).json({ success: false, message: '获取项目合同失败', error: error.message }); } }); // ==================== 项目分包API ==================== app.get('/api/projects/:id/subcontracts', async (req, res) => { try { const { id } = req.params; const result = await db.query(` SELECT * FROM subcontracts WHERE project_id = ? ORDER BY created_at DESC `, [id]); // 解析unit_price_items字段 const subcontracts = result.rows.map(subcontract => { if (subcontract.unit_price_items) { try { subcontract.unit_price_items = JSON.parse(subcontract.unit_price_items); } catch (error) { subcontract.unit_price_items = []; } } else { subcontract.unit_price_items = []; } return subcontract; }); res.json({ success: true, data: subcontracts }); } catch (error) { console.error('获取项目分包失败:', error); res.status(500).json({ success: false, message: '获取项目分包失败', error: error.message }); } }); // ==================== 新增项目分包API ==================== app.post('/api/projects/:id/subcontracts', async (req, res) => { try { const { id } = req.params; const { subcontractor_id, subcontractor_name, contract_amount, currency, settlement_type, other_terms, payment_description, unit_price_items, start_date, end_date, work_days, status } = req.body; const unitPriceItemsJson = unit_price_items ? JSON.stringify(unit_price_items) : null; const result = await db.query( `INSERT INTO subcontracts (project_id, subcontractor_id, subcontractor_name, contract_amount, currency, settlement_type, other_terms, payment_description, unit_price_items, start_date, end_date, work_days, paid_amount, status, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))`, [id, subcontractor_id, subcontractor_name, contract_amount, currency || 'CNY', settlement_type || 'lump_sum', other_terms, payment_description, unitPriceItemsJson, start_date, end_date, work_days, 0, status || 'active'] ); const subcontractId = result.lastID; res.json({ success: true, message: '新增分包成功', data: { id: subcontractId, project_id: id, subcontractor_id, subcontractor_name, contract_amount, currency: currency || 'CNY', settlement_type: settlement_type || 'lump_sum', other_terms, payment_description, unit_price_items, start_date, end_date, work_days, paid_amount: 0, status: status || 'active', created_at: new Date().toISOString() } }); } catch (error) { console.error('新增项目分包失败:', error); res.status(500).json({ success: false, message: '新增项目分包失败', error: error.message }); } }); // ==================== 项目材料API ==================== app.get('/api/projects/:id/materials', async (req, res) => { try { const { id } = req.params; const result = await db.query(` SELECT * FROM project_materials WHERE project_id = ? ORDER BY created_at DESC `, [id]); res.json({ success: true, data: result.rows }); } catch (error) { console.error('获取项目材料失败:', error); res.status(500).json({ success: false, message: '获取项目材料失败', error: error.message }); } }); // ==================== 项目施工节点API ==================== app.get('/api/projects/:id/milestones', async (req, res) => { try { const { id } = req.params; const result = await db.query(` SELECT * FROM project_milestones WHERE project_id = ? ORDER BY expected_date ASC `, [id]); res.json({ success: true, data: result.rows }); } catch (error) { console.error('获取项目施工节点失败:', error); res.status(500).json({ success: false, message: '获取项目施工节点失败', error: error.message }); } }); // ==================== 项目财务API ==================== app.get('/api/projects/:id/finances', async (req, res) => { try { const { id } = req.params; const result = await db.query(` SELECT * FROM project_finances WHERE project_id = ? ORDER BY payment_date DESC `, [id]); res.json({ success: true, data: result.rows }); } catch (error) { console.error('获取项目财务失败:', error); res.status(500).json({ success: false, message: '获取项目财务失败', error: error.message }); } }); // ==================== 项目质保金API ==================== app.get('/api/projects/:id/warranty-deposits', async (req, res) => { try { const { id } = req.params; const result = await db.query(` SELECT * FROM warranty_deposits WHERE project_id = ? ORDER BY created_at DESC `, [id]); res.json({ success: true, data: result.rows }); } catch (error) { console.error('获取项目质保金失败:', error); res.status(500).json({ success: false, message: '获取项目质保金失败', error: error.message }); } }); // ==================== 项目施工日志API ==================== app.get('/api/projects/:id/construction-logs', async (req, res) => { try { const { id } = req.params; // 由于施工日志表可能不存在,返回空数组 res.json({ success: true, data: [] }); } catch (error) { console.error('获取项目施工日志失败:', error); res.status(500).json({ success: false, message: '获取项目施工日志失败', error: error.message }); } }); // ==================== 项目删除API ==================== app.delete('/api/projects/:id', checkAdmin, async (req, res) => { try { const { id } = req.params; await db.query('DELETE FROM projects WHERE id = ?', [id]); res.json({ success: true, message: '项目已删除' }); } catch (error) { console.error('删除项目失败:', error); res.status(500).json({ success: false, message: error.message }); } }); // ==================== 项目更新API ==================== app.put('/api/projects/:id', async (req, res) => { try { const { id } = req.params; const { name, manager_id, location, start_date, end_date, description, status, contract_amount } = req.body; console.log('收到项目更新请求:', { id, name, manager_id, location, start_date, end_date, description }); // 更新项目信息 await db.query( 'UPDATE projects SET name = CASE WHEN ? IS NOT NULL THEN ? ELSE name END, manager_id = CASE WHEN ? IS NOT NULL THEN ? ELSE manager_id END, location = CASE WHEN ? IS NOT NULL THEN ? ELSE location END, start_date = CASE WHEN ? IS NOT NULL THEN ? ELSE start_date END, end_date = CASE WHEN ? IS NOT NULL THEN ? ELSE end_date END, description = CASE WHEN ? IS NOT NULL THEN ? ELSE description END, status = CASE WHEN ? IS NOT NULL THEN ? ELSE status END, contract_amount = CASE WHEN ? IS NOT NULL THEN ? ELSE contract_amount END, updated_at = CURRENT_TIMESTAMP WHERE id = ?', [name, name, manager_id, manager_id, location, location, start_date, start_date, end_date, end_date, description, description, status, status, contract_amount, contract_amount, id] ); // 如果提供了开始和结束日期,更新合同的工期信息 if (start_date && end_date) { const start = new Date(start_date); const end = new Date(end_date); const contractPeriod = Math.floor((end.getTime() - start.getTime()) / (1000 * 60 * 60 * 24)) + 1; // 更新合同信息 await db.query( 'UPDATE project_contracts SET start_date = ?, end_date = ?, contract_period = ? WHERE project_id = ?', [start_date, end_date, contractPeriod, id] ); } // 查询更新后的数据 const updatedResult = await db.query('SELECT * FROM projects WHERE id = ?', [id]); res.json({ success: true, data: updatedResult.rows[0] }); } catch (error) { console.error('更新项目失败:', error); res.status(500).json({ success: false, message: error.message }); } }); // ==================== 合同细节保存API ==================== app.put('/api/projects/:id/contract', async (req, res) => { try { const { id } = req.params; const { project_overview, settlement_type, contract_total, tax_included, unit_price_items, payment_nodes, other_info, contract_file } = req.body; console.log('收到合同细节保存请求:', { id, project_overview, settlement_type, contract_total, tax_included, contract_file }); // 1. 更新项目基本信息 await db.query( `UPDATE projects SET description = ?, contract_amount = ? WHERE id = ?`, [project_overview, contract_total, id] ); // 2. 更新或创建项目合同 const contractResult = await db.query( `SELECT * FROM project_contracts WHERE project_id = ?`, [id] ); if (contractResult.rows.length > 0) { // 更新现有合同 await db.query( `UPDATE project_contracts SET settlement_method = ?, contract_amount = ?, contract_file = ?, other_info = ?, tax_included = ? WHERE project_id = ?`, [settlement_type, contract_total, contract_file, other_info, tax_included, id] ); } else { // 创建新合同 const contractCode = `CONTRACT-${Date.now()}`; await db.query( `INSERT INTO project_contracts (project_id, contract_code, contract_amount, settlement_method, contract_file, other_info, tax_included, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))`, [id, contractCode, contract_total, settlement_type, contract_file, other_info, tax_included] ); } // 3. 处理付款节点 if (payment_nodes && Array.isArray(payment_nodes)) { // 删除旧的付款节点 await db.query(`DELETE FROM project_milestones WHERE project_id = ?`, [id]); // 创建新的付款节点 for (const node of payment_nodes) { await db.query( `INSERT INTO project_milestones (project_id, milestone_name, condition, percentage, amount, status, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))`, [id, node.name, node.condition || '', node.percentage, node.amount, 'pending'] ); } } // 4. 处理单价项 if (unit_price_items && Array.isArray(unit_price_items) && settlement_type === 'unit_price') { // 删除旧的材料项 await db.query(`DELETE FROM project_materials WHERE project_id = ?`, [id]); // 创建新的材料项 for (const item of unit_price_items) { await db.query( `INSERT INTO project_materials (project_id, product_name, unit, budget_quantity, average_price, total_amount, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))`, [id, item.name, item.unit, item.quantity, item.price, item.total] ); } } res.json({ success: true, message: '合同细节保存成功' }); } catch (error) { console.error('保存合同细节失败:', error); res.status(500).json({ success: false, message: error.message }); } }); // ==================== 文件上传API ==================== const fs = require('fs'); const uploadDir = path.join(__dirname, 'uploads'); // 确保上传目录存在 if (!fs.existsSync(uploadDir)) { fs.mkdirSync(uploadDir, { recursive: true }); } const storage = multer.diskStorage({ destination: function (req, file, cb) { cb(null, uploadDir); }, filename: function (req, file, cb) { // 使用原始文件名,保持附件名不变 cb(null, file.originalname); } }); const uploadLocal = multer({ storage: storage }); app.post('/api/upload/single', uploadLocal.single('file'), (req, res) => { try { if (!req.file) { return res.status(400).json({ success: false, message: '请选择文件' }); } // 构建文件URL const fileUrl = `/uploads/${req.file.filename}`; res.json({ success: true, data: { url: fileUrl, filename: req.file.filename }, message: '文件上传成功' }); } catch (error) { console.error('文件上传失败:', error); res.status(500).json({ success: false, message: '文件上传失败' }); } }); // 静态文件服务 - 上传文件 app.use('/uploads', express.static(uploadDir)); // ==================== 预算报价管理 ==================== app.get('/api/budget-projects', async (req, res) => { try { const { customer_id } = req.query; let query = ` SELECT b.*, (SELECT json_group_array(json_object( 'id', q.id, 'version', q.version, 'quotation_date', q.quotation_date, 'amount', q.amount, 'currency', q.currency, 'status', q.status, 'file_url', q.file_url, 'remark', q.remark, 'created_at', q.created_at )) FROM budget_quotations q WHERE q.project_id = b.id) as quotations FROM budget_projects b `; if (customer_id) { query += ` WHERE b.customer_id = ?`; } query += ` ORDER BY b.created_at DESC`; const params = customer_id ? [customer_id] : []; const result = await db.query(query, params); // 解析每个项目的附件和照片数据 const projects = result.rows.map(project => { try { return { ...project, attachments: project.attachments ? JSON.parse(project.attachments) : [], survey_photos: project.survey_photos ? JSON.parse(project.survey_photos) : [], quotations: project.quotations ? JSON.parse(project.quotations) : [] }; } catch (error) { console.error('解析项目数据失败:', error); // 如果解析失败,返回原始数据,避免整个应用崩溃 return { ...project, attachments: [], survey_photos: [], quotations: [] }; } }); res.json({ success: true, data: projects }); } catch (error) { console.error('获取预算项目失败:', error); res.status(500).json({ success: false, message: error.message }); } }); // 预算项目API已修改,支持按客户ID筛选 // ==================== 施工管理 ==================== app.get('/api/construction/my-projects', async (req, res) => { try { const result = await db.query(` SELECT p.*, c.name as customer_name, (SELECT json_object( 'id', cl.id, 'log_date', cl.log_date, 'weather', cl.weather, 'work_content', cl.work_content ) FROM construction_logs cl WHERE cl.project_id = p.id ORDER BY cl.log_date DESC LIMIT 1) as latest_log FROM projects p LEFT JOIN customers c ON p.customer_id = c.id WHERE p.status IN ('active', 'pending') ORDER BY p.created_at DESC `); res.json({ success: true, data: result.rows }); } catch (error) { console.error('获取施工项目失败:', error); res.status(500).json({ success: false, message: error.message }); } }); // ==================== 分类管理API(树状结构)==================== // 获取分类树 app.get('/api/categories/tree', async (req, res) => { try { const level = req.query.level; let query = 'SELECT * FROM category_tree ORDER BY level, sort_order, id'; const params = []; if (level) { query = 'SELECT * FROM category_tree WHERE level = ? ORDER BY sort_order, id'; params.push(parseInt(level)); } const result = await db.query(query, params); if (level) { res.json({ success: true, data: result.rows }); } else { const buildTree = (categories, parentId = null) => { return categories .filter(cat => cat.parent_id === parentId) .map(cat => ({ ...cat, children: buildTree(categories, cat.id) })); }; const tree = buildTree(result.rows); res.json({ success: true, data: tree }); } } catch (error) { console.error('获取分类树失败:', error); res.status(500).json({ success: false, message: '获取分类失败', error: error.message }); } }); // 获取所有分类列表 app.get('/api/categories', async (req, res) => { try { const result = await db.query('SELECT * FROM category_tree ORDER BY level, sort_order, id'); res.json({ success: true, data: result.rows }); } catch (error) { console.error('获取分类失败:', error); res.status(500).json({ success: false, message: '获取分类失败', error: error.message }); } }); // 获取单个分类 app.get('/api/categories/:id', async (req, res) => { try { const { id } = req.params; const result = await db.query('SELECT * FROM category_tree WHERE id = ?', [id]); if (result.rows.length === 0) { return res.status(404).json({ success: false, message: '分类不存在' }); } res.json({ success: true, data: result.rows[0] }); } catch (error) { console.error('获取分类失败:', error); res.status(500).json({ success: false, message: '获取分类失败', error: error.message }); } }); // 创建分类 app.post('/api/categories', async (req, res) => { try { const { name, parent_id, level, sort_order, description } = req.body; if (!name) { return res.status(400).json({ success: false, message: '分类名称不能为空' }); } const checkResult = await db.query( 'SELECT id FROM category_tree WHERE name = ? AND (parent_id = ? OR (parent_id IS NULL AND ? IS NULL))', [name, parent_id || null, parent_id || null] ); if (checkResult.rows.length > 0) { return res.status(400).json({ success: false, message: '该分类名称已存在' }); } const result = await db.query( 'INSERT INTO category_tree (name, parent_id, level, sort_order, description) VALUES (?, ?, ?, ?, ?)', [name, parent_id || null, level || (parent_id ? 2 : 1), sort_order || 0, description || ''] ); const newCategory = await db.query('SELECT * FROM category_tree WHERE id = ?', [result.lastID]); res.json({ success: true, data: newCategory.rows[0], message: '创建成功' }); } catch (error) { console.error('创建分类失败:', error); res.status(500).json({ success: false, message: '创建分类失败', error: error.message }); } }); // 更新分类 app.put('/api/categories/:id', async (req, res) => { try { const { id } = req.params; const { name, parent_id, sort_order, description } = req.body; if (parent_id !== undefined) { const checkLoop = async (currentId, targetParentId) => { if (currentId === targetParentId) return true; const children = await db.query('SELECT id FROM category_tree WHERE parent_id = ?', [currentId]); for (const child of children.rows) { if (await checkLoop(child.id, targetParentId)) return true; } return false; }; if (parent_id && await checkLoop(parseInt(id), parseInt(parent_id))) { return res.status(400).json({ success: false, message: '不能将分类设置为自己的子分类' }); } } const updates = []; const params = []; if (name !== undefined) { updates.push('name = ?'); params.push(name); } if (parent_id !== undefined) { updates.push('parent_id = ?'); params.push(parent_id || null); } if (sort_order !== undefined) { updates.push('sort_order = ?'); params.push(sort_order); } if (description !== undefined) { updates.push('description = ?'); params.push(description); } if (updates.length === 0) { return res.status(400).json({ success: false, message: '没有要更新的字段' }); } updates.push('updated_at = datetime(\'now\')'); params.push(id); const result = await db.query( `UPDATE category_tree SET ${updates.join(', ')} WHERE id = ?`, params ); if (result.changes === 0) { return res.status(404).json({ success: false, message: '分类不存在' }); } const updated = await db.query('SELECT * FROM category_tree WHERE id = ?', [id]); res.json({ success: true, data: updated.rows[0], message: '更新成功' }); } catch (error) { console.error('更新分类失败:', error); res.status(500).json({ success: false, message: '更新分类失败', error: error.message }); } }); // 删除分类 app.delete('/api/categories/:id', async (req, res) => { try { const { id } = req.params; const productCheck = await db.query('SELECT COUNT(*) as count FROM products WHERE category_id = ?', [id]); if (productCheck.rows[0].count > 0) { return res.status(400).json({ success: false, message: '该分类下还有商品,不能删除' }); } const result = await db.query('DELETE FROM category_tree WHERE id = ?', [id]); if (result.changes === 0) { return res.status(404).json({ success: false, message: '分类不存在' }); } res.json({ success: true, message: '删除成功' }); } catch (error) { console.error('删除分类失败:', error); res.status(500).json({ success: false, message: '删除分类失败', error: error.message }); } }); // ==================== 商品管理API ==================== // 获取商品列表 app.get('/api/products', async (req, res) => { try { const { category_id, status, keyword } = req.query; let query = ` SELECT p.*, ct.name as category_name, (SELECT name FROM category_tree WHERE id = (SELECT parent_id FROM category_tree WHERE id = p.category_id)) as category_level1_name FROM products p LEFT JOIN category_tree ct ON p.category_id = ct.id WHERE 1=1 `; const params = []; if (category_id) { // 检查是否为一级分类 const isParentCategory = await db.query('SELECT level FROM category_tree WHERE id = ?', [category_id]); console.log('检查分类类型:', category_id, isParentCategory.rows); if (isParentCategory.rows.length > 0 && isParentCategory.rows[0].level === 1) { // 如果是一级分类,筛选所有属于该一级分类的二级分类的商品 const childCategories = await db.query('SELECT id FROM category_tree WHERE parent_id = ?', [category_id]); console.log('子分类:', childCategories.rows); if (childCategories.rows.length > 0) { const childIds = childCategories.rows.map(row => row.id); console.log('子分类ID:', childIds); query += ` AND p.category_id IN (${childIds.map(() => '?').join(',')})`; params.push(...childIds); } else { // 如果一级分类没有子分类,返回空结果 query += ' AND 1=0'; } } else { // 如果是二级分类,直接筛选 query += ' AND p.category_id = ?'; params.push(category_id); } } if (status) { query += ' AND p.status = ?'; params.push(status); } if (keyword) { query += ' AND (p.name LIKE ? OR p.model LIKE ? OR p.brand LIKE ?)'; const searchTerm = `%${keyword}%`; params.push(searchTerm, searchTerm, searchTerm); } query += ' ORDER BY p.created_at DESC'; const result = await db.query(query, params); res.json({ success: true, data: result.rows, count: result.rows.length }); } catch (error) { console.error('获取商品失败:', error); res.status(500).json({ success: false, message: '获取商品失败', error: error.message }); } }); // 下载商品导入模板(必须在 :id 路由之前定义) app.get('/api/products/template', (req, res) => { try { const XLSX = require('xlsx'); const templateData = [ { '商品名称': 'JKLYJ-35-22kV', '型号': 'Model-001', '一级分类': '电缆电线', '二级分类': '高压电缆', '单位': '米', '成本单价': 12.50, '销售单价': 15.50, '品牌': '云南线缆', '规格参数': '35mm², 22kV', '来源': '中国', '备注': '示例商品' }, { '商品名称': 'XP-70', '型号': 'XP-70', '一级分类': '电杆横担', '二级分类': '横担', '单位': '个', '成本单价': 20.00, '销售单价': 25.00, '品牌': '江西电瓷', '规格参数': '70kN', '来源': '老挝', '备注': '' } ]; const worksheet = XLSX.utils.json_to_sheet(templateData); const workbook = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(workbook, worksheet, '商品导入模板'); const buffer = XLSX.write(workbook, { type: 'buffer', bookType: 'xlsx' }); res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); res.setHeader('Content-Disposition', 'attachment; filename="product_template.xlsx"'); res.send(buffer); } catch (error) { console.error('生成模板失败:', error); res.status(500).json({ success: false, message: '生成模板失败', error: error.message }); } }); // 获取单个商品 app.get('/api/products/:id', async (req, res) => { try { const { id } = req.params; const result = await db.query(` SELECT p.*, ct.name as category_name, (SELECT name FROM category_tree WHERE id = (SELECT parent_id FROM category_tree WHERE id = p.category_id)) as category_level1_name FROM products p LEFT JOIN category_tree ct ON p.category_id = ct.id WHERE p.id = ? `, [id]); if (result.rows.length === 0) { return res.status(404).json({ success: false, message: '商品不存在' }); } res.json({ success: true, data: result.rows[0] }); } catch (error) { console.error('获取商品失败:', error); res.status(500).json({ success: false, message: '获取商品失败', error: error.message }); } }); // 创建商品 app.post('/api/products', async (req, res) => { try { const { name, model, category_id, unit, cost_price, price, brand, specification, source, remark, stock_quantity, status } = req.body; if (!name) { return res.status(400).json({ success: false, message: '商品名称不能为空' }); } let categoryName = null; if (category_id) { const catResult = await db.query('SELECT name FROM category_tree WHERE id = ?', [category_id]); if (catResult.rows.length > 0) { categoryName = catResult.rows[0].name; } } const result = await db.query( `INSERT INTO products (name, model, category_id, category_name, unit, cost_price, price, brand, specification, source, remark, stock_quantity, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [ name, model || '', category_id || null, categoryName, unit || '件', cost_price || null, price || 0, brand || '', specification || '', source || '老挝', remark || '', stock_quantity || 0, status || 'active' ] ); const newProduct = await db.query('SELECT * FROM products WHERE id = ?', [result.lastID]); res.json({ success: true, data: newProduct.rows[0], message: '创建成功' }); } catch (error) { console.error('创建商品失败:', error); res.status(500).json({ success: false, message: '创建商品失败', error: error.message }); } }); // 更新商品 app.put('/api/products/:id', async (req, res) => { try { const { id } = req.params; const { name, model, category_id, unit, cost_price, price, brand, specification, source, remark, stock_quantity, stock_warning, status } = req.body; let categoryName = null; if (category_id !== undefined) { if (category_id) { const catResult = await db.query('SELECT name FROM category_tree WHERE id = ?', [category_id]); if (catResult.rows.length > 0) { categoryName = catResult.rows[0].name; } } } const updates = []; const params = []; if (name !== undefined) { updates.push('name = ?'); params.push(name); } if (model !== undefined) { updates.push('model = ?'); params.push(model || ''); } if (category_id !== undefined) { updates.push('category_id = ?'); params.push(category_id || null); updates.push('category_name = ?'); params.push(categoryName); } if (unit !== undefined) { updates.push('unit = ?'); params.push(unit || '件'); } if (cost_price !== undefined) { updates.push('cost_price = ?'); params.push(cost_price); } if (price !== undefined) { updates.push('price = ?'); params.push(price || 0); } if (brand !== undefined) { updates.push('brand = ?'); params.push(brand || ''); } if (specification !== undefined) { updates.push('specification = ?'); params.push(specification || ''); } if (source !== undefined) { updates.push('source = ?'); params.push(source || '老挝'); } if (remark !== undefined) { updates.push('remark = ?'); params.push(remark || ''); } if (stock_quantity !== undefined) { updates.push('stock_quantity = ?'); params.push(stock_quantity || 0); } if (stock_warning !== undefined) { updates.push('stock_warning = ?'); params.push(stock_warning || 0); } if (status !== undefined) { updates.push('status = ?'); params.push(status || 'active'); } if (updates.length === 0) { return res.status(400).json({ success: false, message: '没有要更新的字段' }); } updates.push('updated_at = datetime(\'now\')'); params.push(id); const result = await db.query( `UPDATE products SET ${updates.join(', ')} WHERE id = ?`, params ); if (result.changes === 0) { return res.status(404).json({ success: false, message: '商品不存在' }); } const updated = await db.query('SELECT * FROM products WHERE id = ?', [id]); res.json({ success: true, data: updated.rows[0], message: '更新成功' }); } catch (error) { console.error('更新商品失败:', error); res.status(500).json({ success: false, message: '更新商品失败', error: error.message }); } }); // 删除商品 app.delete('/api/products/:id', async (req, res) => { try { const { id } = req.params; const result = await db.query('DELETE FROM products WHERE id = ?', [id]); if (result.changes === 0) { return res.status(404).json({ success: false, message: '商品不存在' }); } res.json({ success: true, message: '删除成功' }); } catch (error) { console.error('删除商品失败:', error); res.status(500).json({ success: false, message: '删除商品失败', error: error.message }); } }); // 批量导入商品(使用内存存储) const memoryStorage = multer.memoryStorage(); const uploadMemory = multer({ storage: memoryStorage, limits: { fileSize: 10 * 1024 * 1024 } }); app.post('/api/products/batch-import', uploadMemory.single('file'), async (req, res) => { try { if (!req.file) { return res.status(400).json({ success: false, message: '请选择要上传的文件' }); } const XLSX = require('xlsx'); const workbook = XLSX.read(req.file.buffer, { type: 'buffer' }); const sheetName = workbook.SheetNames[0]; const worksheet = workbook.Sheets[sheetName]; const data = XLSX.utils.sheet_to_json(worksheet); if (!data || data.length === 0) { return res.status(400).json({ success: false, message: 'Excel文件为空或格式不正确' }); } const results = { total: data.length, success: 0, failed: 0, errors: [] }; for (let i = 0; i < data.length; i++) { const row = data[i]; try { const name = row['商品名称'] || row['name']; if (!name) { throw new Error('商品名称不能为空'); } const model = row['型号'] || row['model'] || ''; const categoryLevel1 = row['一级分类'] || row['category_level1'] || ''; const categoryLevel2 = row['二级分类'] || row['category_level2'] || ''; const unit = row['单位'] || row['unit'] || '件'; const costPrice = parseFloat(row['成本单价'] || row['cost_price']) || null; const price = parseFloat(row['销售单价'] || row['price']) || 0; const brand = row['品牌'] || row['brand'] || ''; const specification = row['规格参数'] || row['specification'] || ''; const source = row['来源'] || row['source'] || '老挝'; const remark = row['备注'] || row['remark'] || ''; let categoryId = null; let categoryName = null; if (categoryLevel2) { let level1 = await db.query('SELECT * FROM category_tree WHERE name = ? AND level = 1', [categoryLevel1]); let level1Id; if (level1.rows.length === 0) { const newLevel1 = await db.query('INSERT INTO category_tree (name, parent_id, level, sort_order, description) VALUES (?, NULL, 1, 99, ?)', [categoryLevel1, '批量导入创建']); level1Id = newLevel1.lastID; } else { level1Id = level1.rows[0].id; } let level2 = await db.query('SELECT * FROM category_tree WHERE name = ? AND parent_id = ? AND level = 2', [categoryLevel2, level1Id]); if (level2.rows.length === 0) { const newLevel2 = await db.query('INSERT INTO category_tree (name, parent_id, level, sort_order, description) VALUES (?, ?, 2, 99, ?)', [categoryLevel2, level1Id, '批量导入创建']); categoryId = newLevel2.lastID; categoryName = categoryLevel2; } else { categoryId = level2.rows[0].id; categoryName = categoryLevel2; } } else if (categoryLevel1) { let level1 = await db.query('SELECT * FROM category_tree WHERE name = ? AND level = 1', [categoryLevel1]); if (level1.rows.length === 0) { const newLevel1 = await db.query('INSERT INTO category_tree (name, parent_id, level, sort_order, description) VALUES (?, NULL, 1, 99, ?)', [categoryLevel1, '批量导入创建']); categoryId = newLevel1.lastID; categoryName = categoryLevel1; } else { categoryId = level1.rows[0].id; categoryName = categoryLevel1; } } const existingProduct = await db.query('SELECT id FROM products WHERE name = ? AND model = ?', [name, model]); if (existingProduct.rows.length > 0) { await db.query( 'UPDATE products SET model = ?, category_id = ?, category_name = ?, unit = ?, cost_price = ?, price = ?, brand = ?, specification = ?, source = ?, remark = ?, updated_at = datetime(\'now\') WHERE id = ?', [model, categoryId, categoryName, unit, costPrice, price, brand, specification, source, remark, existingProduct.rows[0].id] ); } else { await db.query( 'INSERT INTO products (name, model, category_id, category_name, unit, cost_price, price, brand, specification, source, remark, stock_quantity, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, ?)', [name, model, categoryId, categoryName, unit, costPrice, price, brand, specification, source, remark, 'active'] ); } results.success++; } catch (error) { results.failed++; results.errors.push({ row: i + 2, item: name || `第${i + 1}行`, error: error.message }); } } res.json({ success: true, message: `导入完成:成功 ${results.success} 条,失败 ${results.failed} 条`, data: results }); } catch (error) { console.error('批量导入商品失败:', error); res.status(500).json({ success: false, message: '批量导入失败', error: error.message }); } }); // ==================== 付款节点API ==================== app.get('/api/payment-nodes', async (req, res) => { try { const result = await db.query(` SELECT pn.*, p.name as project_name, p.code as project_code FROM payment_nodes pn LEFT JOIN projects p ON pn.project_id = p.id ORDER BY pn.due_date ASC LIMIT 50 `); res.json({ success: true, data: result.rows, count: result.rows.length }); } catch (error) { console.error('获取付款节点失败:', error); res.status(500).json({ success: false, message: '获取付款节点失败', error: error.message }); } }); // ==================== 付款记录API ==================== app.get('/api/payment-records', async (req, res) => { try { const result = await db.query(` SELECT pr.*, pn.node_name, p.name as project_name FROM payment_records pr LEFT JOIN payment_nodes pn ON pr.node_id = pn.id LEFT JOIN projects p ON pn.project_id = p.id ORDER BY pr.payment_date DESC LIMIT 50 `); res.json({ success: true, data: result.rows, count: result.rows.length }); } catch (error) { console.error('获取付款记录失败:', error); res.status(500).json({ success: false, message: '获取付款记录失败', error: error.message }); } }); // 权限检查中间件 function checkAdmin(req, res, next) { // 简单的权限检查,实际项目中应该从token中解析用户信息 // 这里暂时假设只有管理员可以修改数据 const userRole = req.headers['x-user-role'] || 'employee'; if (userRole !== 'admin') { return res.status(403).json({ success: false, message: '权限不足,仅管理员可操作' }); } next(); } // ==================== 预算项目API ==================== app.post('/api/budget-projects', checkAdmin, async (req, res) => { try { const { name, customer_id, manager_id, location, survey_date, intermediary, intermediary_fee_type, intermediary_fee_value, customer_requirements, project_overview, attachments, survey_photos } = req.body; // 确保 attachments 和 survey_photos 是数组 const attachmentsArray = Array.isArray(attachments) ? attachments : []; const surveyPhotosArray = Array.isArray(survey_photos) ? survey_photos : []; const result = await db.query( `INSERT INTO budget_projects (name, customer_id, manager_id, location, survey_date, intermediary, intermediary_fee_type, intermediary_fee_value, customer_requirements, project_overview, attachments, survey_photos, status, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))`, [name, customer_id, manager_id, location, survey_date, intermediary, intermediary_fee_type, intermediary_fee_value, customer_requirements, project_overview, JSON.stringify(attachmentsArray), JSON.stringify(surveyPhotosArray), 'negotiating'] ); const projectId = result.lastID; res.json({ success: true, message: '创建成功', data: { id: projectId, name, customer_id, manager_id, location, survey_date, intermediary, intermediary_fee_type, intermediary_fee_value, customer_requirements, project_overview, attachments, survey_photos, status: 'negotiating', created_at: new Date().toISOString() } }); } catch (error) { console.error('创建预算项目失败:', error); res.status(500).json({ success: false, message: '创建失败', error: error.message }); } }); // ==================== 预算项目详情API ==================== app.get('/api/budget-projects/:id', async (req, res) => { try { const { id } = req.params; const result = await db.query(` SELECT b.*, c.name as customer_name, u.name as manager_name, (SELECT json_group_array(json_object( 'id', q.id, 'version', q.version, 'quotation_date', q.quotation_date, 'amount', q.amount, 'currency', q.currency, 'status', q.status, 'file_url', q.file_url, 'remark', q.remark, 'created_at', q.created_at )) FROM budget_quotations q WHERE q.project_id = b.id) as quotations FROM budget_projects b LEFT JOIN customers c ON b.customer_id = c.id LEFT JOIN users u ON b.manager_id = u.id WHERE b.id = ? `, [id]); if (result.rows.length > 0) { const project = result.rows[0]; try { // 解析JSON字符串为数组 project.attachments = project.attachments ? JSON.parse(project.attachments) : []; project.survey_photos = project.survey_photos ? JSON.parse(project.survey_photos) : []; project.quotations = project.quotations ? JSON.parse(project.quotations) : []; } catch (error) { console.error('解析项目数据失败:', error); // 如果解析失败,设置默认值 project.attachments = []; project.survey_photos = []; project.quotations = []; } res.json({ success: true, data: project }); } else { res.status(404).json({ success: false, message: '项目不存在' }); } } catch (error) { console.error('获取预算项目详情失败:', error); res.status(500).json({ success: false, message: error.message }); } }); // ==================== 预算报价API ==================== app.post('/api/budget-projects/:projectId/quotations', checkAdmin, async (req, res) => { try { const { projectId } = req.params; const { quotation_date, amount, currency, file_url, remark, version } = req.body; const result = await db.query( `INSERT INTO budget_quotations (project_id, version, quotation_date, amount, currency, status, file_url, remark, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))`, [projectId, version, quotation_date, amount, currency, 'draft', file_url, remark] ); const quotationId = result.lastID; res.json({ success: true, message: '新增报价版本成功', data: { id: quotationId, project_id: projectId, version, quotation_date, amount, currency, status: 'draft', file_url, remark, created_at: new Date().toISOString() } }); } catch (error) { console.error('创建报价版本失败:', error); res.status(500).json({ success: false, message: '创建失败', error: error.message }); } }); app.delete('/api/budget-projects/:projectId/quotations/:quotationId', checkAdmin, async (req, res) => { try { const { projectId, quotationId } = req.params; const result = await db.query( `DELETE FROM budget_quotations WHERE id = ? AND project_id = ?`, [quotationId, projectId] ); if (result.changes > 0) { res.json({ success: true, message: '删除成功' }); } else { res.status(404).json({ success: false, message: '报价版本不存在' }); } } catch (error) { console.error('删除报价版本失败:', error); res.status(500).json({ success: false, message: '删除失败', error: error.message }); } }); // ==================== 预算项目状态更新API ==================== app.put('/api/budget-projects/:id/sign', checkAdmin, async (req, res) => { try { console.log('收到签约请求:', req.body); const { id } = req.params; const { contract_code, project_name, contract_method, currency, contract_amount, start_date, end_date, contract_period, project_overview, other_requirements, warranty_deposit_percentage, warranty_period, contract_file, payment_nodes, unit_price_items } = req.body; console.log('解析请求参数成功:', { id, contract_code, project_name, contract_method, currency, contract_amount, start_date, end_date, contract_period, project_overview, other_requirements, warranty_deposit_percentage, warranty_period, contract_file, payment_nodes: payment_nodes?.length, unit_price_items: unit_price_items?.length }); // 1. 获取预算项目详细信息 const budgetProjectResult = await db.query( `SELECT b.*, c.name as customer_name, (SELECT json_group_array(json_object( 'id', q.id, 'version', q.version, 'quotation_date', q.quotation_date, 'amount', q.amount, 'currency', q.currency, 'status', q.status, 'file_url', q.file_url, 'remark', q.remark, 'created_at', q.created_at )) FROM budget_quotations q WHERE q.project_id = b.id ORDER BY q.version DESC LIMIT 1) as latest_quotation FROM budget_projects b LEFT JOIN customers c ON b.customer_id = c.id WHERE b.id = ?`, [id] ); if (budgetProjectResult.rows.length === 0) { return res.status(404).json({ success: false, message: '预算项目不存在' }); } const budgetProject = budgetProjectResult.rows[0]; // 2. 获取最新报价信息 let latestQuotation = null; let defaultContractAmount = 0; if (budgetProject.latest_quotation) { try { const quotations = JSON.parse(budgetProject.latest_quotation); if (quotations && quotations.length > 0) { latestQuotation = quotations[0]; defaultContractAmount = parseFloat(latestQuotation.amount) || 0; } } catch (e) { console.error('解析报价信息失败:', e); } } // 3. 生成项目代码 const today = new Date(); const dateStr = today.toISOString().split('T')[0].replace(/-/g, ''); // 获取当天项目数量,生成序号 const projectCountResult = await db.query( `SELECT COUNT(*) as count FROM projects WHERE DATE(created_at) = DATE('now')` ); const projectCount = parseInt(projectCountResult.rows[0].count) || 0; const sequence = String(projectCount + 1).padStart(3, '0'); const projectCode = `PROJ-${dateStr}-${sequence}`; // 4. 计算项目时间 const startDate = today.toISOString(); const endDate = new Date(today.getTime() + 6 * 30 * 24 * 60 * 60 * 1000).toISOString(); // 5. 创建项目 const finalContractAmount = contract_amount || defaultContractAmount; const projectResult = await db.query( `INSERT INTO projects (code, name, customer_id, manager_id, status, contract_amount, start_date, end_date, description, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))`, [ projectCode, project_name || budgetProject.name, budgetProject.customer_id, budgetProject.manager_id, 'active', finalContractAmount, start_date || startDate, end_date || endDate, project_overview || budgetProject.project_overview || '' ] ); const newProjectId = projectResult.lastID; // 6. 创建项目合同 const contractCode = contract_code || `CONTRACT-${dateStr}-${sequence}`; const finalContractMethod = contract_method || 'lump_sum'; const finalContractPeriod = contract_period || (end_date && start_date ? Math.floor((new Date(end_date).getTime() - new Date(start_date).getTime()) / (1000 * 60 * 60 * 24)) : 180); const finalWarrantyPercentage = warranty_deposit_percentage || 5; const finalWarrantyPeriod = warranty_period || 12; await db.query( `INSERT INTO project_contracts (project_id, contract_code, contract_amount, currency, settlement_method, contract_period, start_date, end_date, warranty_deposit_percentage, warranty_period, contract_file, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))`, [ newProjectId, contractCode, finalContractAmount, currency || 'CNY', finalContractMethod, finalContractPeriod, start_date || startDate, end_date || endDate, finalWarrantyPercentage, finalWarrantyPeriod, contract_file || null ] ); // 7. 创建付款节点 if (payment_nodes && Array.isArray(payment_nodes)) { for (const node of payment_nodes) { await db.query( `INSERT INTO project_milestones (project_id, milestone_name, percentage, amount, expected_date, status, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))`, [ newProjectId, node.node_name || `节点${node.id}`, node.percentage || 0, node.amount || 0, start_date || startDate, 'pending' ] ); } } // 8. 创建单价项(如果是单价结算) if (unit_price_items && Array.isArray(unit_price_items)) { for (const item of unit_price_items) { await db.query( `INSERT INTO project_materials (project_id, product_name, unit, budget_quantity, average_price, total_amount, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))`, [ newProjectId, item.name || `单项${item.id}`, item.unit || '个', item.quantity || 0, item.price || 0, item.total || 0 ] ); } } // 9. 更新预算项目状态 await db.query( `UPDATE budget_projects SET status = 'signed', updated_at = datetime('now') WHERE id = ?`, [id] ); res.json({ success: true, message: '标记签约成功,项目已自动创建', data: { project_id: newProjectId, project_code: projectCode, contract_code: contractCode } }); } catch (error) { console.error('标记签约失败:', error); console.error('错误堆栈:', error.stack); res.status(500).json({ success: false, message: '操作失败', error: error.message, stack: error.stack }); } }); app.put('/api/budget-projects/:id/unsigned', checkAdmin, async (req, res) => { try { const { id } = req.params; await db.query( `UPDATE budget_projects SET status = 'unsigned', updated_at = datetime('now') WHERE id = ?`, [id] ); res.json({ success: true, message: '标记未签约成功' }); } catch (error) { console.error('标记未签约失败:', error); res.status(500).json({ success: false, message: '操作失败', error: error.message }); } }); // ==================== 删除预算项目API ==================== app.delete('/api/budget-projects/:id', checkAdmin, async (req, res) => { try { const { id } = req.params; // 先删除关联的报价 await db.query(`DELETE FROM budget_quotations WHERE project_id = ?`, [id]); // 再删除预算项目 const result = await db.query(`DELETE FROM budget_projects WHERE id = ?`, [id]); if (result.changes > 0) { res.json({ success: true, message: '删除成功' }); } else { res.status(404).json({ success: false, message: '项目不存在' }); } } catch (error) { console.error('删除预算项目失败:', error); res.status(500).json({ success: false, message: '删除失败', error: error.message }); } }); // ==================== 汇率API ==================== app.get('/api/exchange-rates/latest', async (req, res) => { try { // 使用子查询获取每个汇率对的最新汇率 const result = await db.query(` SELECT e1.pair_key, e1.rate, e1.effective_date, e1.created_at FROM exchange_rates e1 JOIN ( SELECT pair_key, MAX(effective_date) as max_date FROM exchange_rates WHERE effective_date <= DATE('now') GROUP BY pair_key ) e2 ON e1.pair_key = e2.pair_key AND e1.effective_date = e2.max_date `); const data = {}; let latestUpdateTime = null; result.rows.forEach(row => { data[row.pair_key] = row.rate; if (!latestUpdateTime || new Date(row.created_at) > new Date(latestUpdateTime)) { latestUpdateTime = row.created_at; } }); // 如果没有数据,使用默认值 if (Object.keys(data).length === 0) { data.CNY_LAK = 2900; data.CNY_USD = 0.143; data.CNY_THB = 4.8; data.USD_LAK = 20300; data.THB_LAK = 604; } res.json({ success: true, data: data, updated_at: latestUpdateTime || new Date().toISOString(), date: new Date().toISOString().split('T')[0] }); } catch (error) { console.error('获取汇率失败:', error); res.status(500).json({ success: false, message: '获取汇率失败', error: error.message }); } }); app.get('/api/exchange-rates', async (req, res) => { try { const result = await db.query(` SELECT * FROM exchange_rates ORDER BY effective_date DESC LIMIT 20 `); res.json({ success: true, data: result.rows, count: result.rows.length }); } catch (error) { console.error('获取汇率失败:', error); res.status(500).json({ success: false, message: '获取汇率失败', error: error.message }); } }); app.get('/api/exchange-rates/history', async (req, res) => { try { const limit = req.query.limit || 20; const result = await db.query(` SELECT * FROM exchange_rates ORDER BY created_at DESC LIMIT ? `, [limit]); // 转换数据格式以匹配前端期望 const formattedData = result.rows.map(row => { const [from_currency, to_currency] = row.pair_key.split('_'); return { ...row, from_currency, to_currency }; }); res.json({ success: true, data: formattedData }); } catch (error) { console.error('获取历史汇率失败:', error); res.status(500).json({ success: false, message: '获取历史汇率失败', error: error.message }); } }); app.post('/api/exchange-rates', async (req, res) => { try { const { pair_key, rate, effective_date } = req.body; if (!pair_key || rate === undefined || !effective_date) { return res.status(400).json({ success: false, message: '缺少必要参数' }); } const result = await db.query( `INSERT INTO exchange_rates (pair_key, rate, effective_date, created_at, updated_at) VALUES (?, ?, ?, datetime('now'), datetime('now'))`, [pair_key, rate, effective_date] ); res.json({ success: true, message: '汇率保存成功', data: { id: result.lastID, pair_key, rate, effective_date, created_at: new Date().toISOString() } }); } catch (error) { console.error('保存汇率失败:', error); res.status(500).json({ success: false, message: '保存汇率失败', error: error.message }); } }); // ==================== 预支款API ==================== app.get('/api/advances', async (req, res) => { try { const result = await db.query(` SELECT a.*, u.name as user_name, p.name as project_name FROM advances a LEFT JOIN users u ON a.user_id = u.id LEFT JOIN projects p ON a.project_id = p.id ORDER BY a.created_at DESC `); // 解析每个预支申请的 attachments 字段为数组 const data = result.rows.map(item => { if (item.attachments) { try { item.attachments = JSON.parse(item.attachments); } catch (error) { item.attachments = []; } } else { item.attachments = []; } return item; }); res.json({ success: true, data, count: data.length }); } catch (error) { console.error('获取预支款失败:', error); res.status(500).json({ success: false, message: '获取预支款失败', error: error.message }); } }); // ==================== 创建预支申请 ==================== app.post('/api/advances', [ body('amount').isFloat({ min: 0.01 }), body('reason').notEmpty() ], validate, async (req, res) => { try { const { amount, reason, project_id, currency, advance_date, attachments, amount_cny, applicant, status } = req.body; const user_id = 1; // 临时使用admin用户 // 生成预支编号 const advanceCode = `ADV-${Date.now()}`; const result = await db.query( 'INSERT INTO advances (user_id, project_id, amount, currency, amount_cny, reason, advance_date, advance_code, status, applicant, attachments) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', [user_id, project_id, amount, currency || 'CNY', amount_cny || 0, reason, advance_date, advanceCode, status || 'pending', applicant, JSON.stringify(attachments || [])] ); // SQLite不支持RETURNING,所以需要查询刚插入的数据 const lastInsert = await db.query('SELECT * FROM advances ORDER BY id DESC LIMIT 1'); const data = lastInsert.rows[0]; // 解析 attachments 字段为数组 if (data.attachments) { try { data.attachments = JSON.parse(data.attachments); } catch (error) { data.attachments = []; } } else { data.attachments = []; } res.json({ success: true, data }); } catch (error) { console.error('创建预支申请失败:', error); res.status(500).json({ success: false, message: '创建预支申请失败', error: error.message }); } }); // ==================== 获取单个预支申请 ==================== app.get('/api/advances/:id', async (req, res) => { try { const { id } = req.params; const result = await db.query('SELECT * FROM advances WHERE id = ?', [id]); if (result.rows.length > 0) { const data = result.rows[0]; // 解析 attachments 字段为数组 if (data.attachments) { try { data.attachments = JSON.parse(data.attachments); } catch (error) { data.attachments = []; } } else { data.attachments = []; } res.json({ success: true, data }); } else { res.status(404).json({ success: false, message: '预支申请不存在' }); } } catch (error) { console.error('获取预支申请失败:', error); res.status(500).json({ success: false, message: '获取预支申请失败', error: error.message }); } }); // ==================== 更新预支申请 ==================== app.put('/api/advances/:id', async (req, res) => { try { const { id } = req.params; const { amount, reason, project_id, currency, advance_date, attachments, amount_cny, applicant, status } = req.body; const result = await db.query( 'UPDATE advances SET amount = ?, reason = ?, project_id = ?, currency = ?, advance_date = ?, attachments = ?, amount_cny = ?, applicant = ?, status = ? WHERE id = ?', [amount, reason, project_id, currency, advance_date, JSON.stringify(attachments || []), amount_cny, applicant, status, id] ); if (result.changes > 0) { res.json({ success: true, message: '更新成功' }); } else { res.status(404).json({ success: false, message: '预支申请不存在' }); } } catch (error) { console.error('更新预支申请失败:', error); res.status(500).json({ success: false, message: '更新预支申请失败', error: error.message }); } }); // ==================== 删除预支申请 ==================== app.delete('/api/advances/:id', async (req, res) => { try { const { id } = req.params; const result = await db.query('DELETE FROM advances WHERE id = ?', [id]); if (result.changes > 0) { res.json({ success: true, message: '删除成功' }); } else { res.status(404).json({ success: false, message: '预支申请不存在' }); } } catch (error) { console.error('删除预支申请失败:', error); res.status(500).json({ success: false, message: '删除预支申请失败', error: error.message }); } }); // ==================== 提交预支申请 ==================== app.post('/api/advances/:id/submit', async (req, res) => { try { const { id } = req.params; const result = await db.query('UPDATE advances SET status = ? WHERE id = ?', ['pending', id]); if (result.changes > 0) { res.json({ success: true, message: '提交成功' }); } else { res.status(404).json({ success: false, message: '预支申请不存在' }); } } catch (error) { console.error('提交预支申请失败:', error); res.status(500).json({ success: false, message: '提交预支申请失败', error: error.message }); } }); // ==================== 撤回预支申请 ==================== app.post('/api/advances/:id/withdraw', async (req, res) => { try { const { id } = req.params; const result = await db.query('UPDATE advances SET status = ? WHERE id = ?', ['withdrawn', id]); if (result.changes > 0) { res.json({ success: true, message: '撤回成功' }); } else { res.status(404).json({ success: false, message: '预支申请不存在' }); } } catch (error) { console.error('撤回预支申请失败:', error); res.status(500).json({ success: false, message: '撤回预支申请失败', error: error.message }); } }); // ==================== 审批预支申请 ==================== app.post('/api/advances/:id/approve', async (req, res) => { try { const { id } = req.params; const { remark } = req.body; const result = await db.query('UPDATE advances SET status = ?, approval_remark = ? WHERE id = ?', ['approved', remark, id]); if (result.changes > 0) { res.json({ success: true, message: '审批通过成功' }); } else { res.status(404).json({ success: false, message: '预支申请不存在' }); } } catch (error) { console.error('审批预支申请失败:', error); res.status(500).json({ success: false, message: '审批预支申请失败', error: error.message }); } }); // ==================== 退回预支申请 ==================== app.post('/api/advances/:id/reject', async (req, res) => { try { const { id } = req.params; const { rejectReason } = req.body; const result = await db.query('UPDATE advances SET status = ? WHERE id = ?', ['pending_edit', id]); if (result.changes > 0) { res.json({ success: true, message: '退回成功' }); } else { res.status(404).json({ success: false, message: '预支申请不存在' }); } } catch (error) { console.error('退回预支申请失败:', error); res.status(500).json({ success: false, message: '退回预支申请失败', error: error.message }); } }); // ==================== 付款申请API ==================== app.get('/api/payment-requests', async (req, res) => { try { const result = await db.query(` SELECT * FROM payment_requests ORDER BY created_at DESC `); const data = result.rows.map(item => { if (item.attachments) { try { item.attachments = JSON.parse(item.attachments); } catch (error) { item.attachments = []; } } else { item.attachments = []; } if (item.detail_items) { try { item.detail_items = JSON.parse(item.detail_items); } catch (error) { item.detail_items = []; } } else { item.detail_items = []; } return item; }); res.json({ success: true, data, count: data.length }); } catch (error) { console.error('获取付款申请失败:', error); res.status(500).json({ success: false, message: '获取付款申请失败', error: error.message }); } }); app.post('/api/payment-requests', async (req, res) => { try { const { payment_date, payee, bank_account, bank_name, currency, reason, detail_items, attachments, applicant, payee_type, payee_id, expense_type, expense_category, project_id, amount } = req.body; // 生成付款申请编号 const requestCode = `PAY-${Date.now()}`; // 使用默认值处理可选字段 const finalBankAccount = bank_account || ''; const finalBankName = bank_name || ''; const finalAmount = amount || 0; const result = await db.query( `INSERT INTO payment_requests ( payee, bank_account, bank_name, amount, currency, reason, payment_date, request_code, status, applicant, detail_items, attachments, payee_type, payee_id, expense_type, expense_category, project_id ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [ payee, finalBankAccount, finalBankName, finalAmount, currency || 'CNY', reason, payment_date, requestCode, 'pending', applicant, JSON.stringify(detail_items || []), JSON.stringify(attachments || []), payee_type || 'other', payee_id || null, expense_type || 'company', expense_category || '', project_id || null ] ); // SQLite不支持RETURNING,所以需要查询刚插入的数据 const lastInsert = await db.query('SELECT * FROM payment_requests ORDER BY id DESC LIMIT 1'); res.json({ success: true, data: lastInsert.rows[0] }); } catch (error) { console.error('创建付款申请失败:', error); res.status(500).json({ success: false, message: '创建付款申请失败', error: error.message }); } }); app.get('/api/payment-requests/:id', async (req, res) => { try { const { id } = req.params; const result = await db.query('SELECT * FROM payment_requests WHERE id = ?', [id]); if (result.rows.length > 0) { const data = result.rows[0]; if (data.attachments) { try { data.attachments = JSON.parse(data.attachments); } catch (error) { data.attachments = []; } } else { data.attachments = []; } if (data.detail_items) { try { data.detail_items = JSON.parse(data.detail_items); } catch (error) { data.detail_items = []; } } else { data.detail_items = []; } res.json({ success: true, data }); } else { res.status(404).json({ success: false, message: '付款申请不存在' }); } } catch (error) { console.error('获取付款申请失败:', error); res.status(500).json({ success: false, message: '获取付款申请失败', error: error.message }); } }); app.put('/api/payment-requests/:id', async (req, res) => { try { const { id } = req.params; const { payment_date, payee, bank_account, bank_name, currency, reason, detail_items, attachments, applicant, status, payee_type, payee_id, expense_type, expense_category, project_id, amount } = req.body; // 构建动态更新SQL,只更新提供的字段 const updates = []; const params = []; if (payment_date !== undefined) { updates.push('payment_date = ?'); params.push(payment_date); } if (payee !== undefined) { updates.push('payee = ?'); params.push(payee); } if (bank_account !== undefined) { updates.push('bank_account = ?'); params.push(bank_account); } if (bank_name !== undefined) { updates.push('bank_name = ?'); params.push(bank_name); } if (amount !== undefined) { updates.push('amount = ?'); params.push(amount); } if (currency !== undefined) { updates.push('currency = ?'); params.push(currency); } if (reason !== undefined) { updates.push('reason = ?'); params.push(reason); } if (detail_items !== undefined) { updates.push('detail_items = ?'); params.push(JSON.stringify(detail_items || [])); } if (attachments !== undefined) { updates.push('attachments = ?'); params.push(JSON.stringify(attachments || [])); } if (applicant !== undefined) { updates.push('applicant = ?'); params.push(applicant); } if (status !== undefined) { updates.push('status = ?'); params.push(status); } if (payee_type !== undefined) { updates.push('payee_type = ?'); params.push(payee_type); } if (payee_id !== undefined) { updates.push('payee_id = ?'); params.push(payee_id); } if (expense_type !== undefined) { updates.push('expense_type = ?'); params.push(expense_type); } if (expense_category !== undefined) { updates.push('expense_category = ?'); params.push(expense_category); } if (project_id !== undefined) { updates.push('project_id = ?'); params.push(project_id); } if (updates.length === 0) { return res.status(400).json({ success: false, message: '没有要更新的字段' }); } params.push(id); const result = await db.query( `UPDATE payment_requests SET ${updates.join(', ')} WHERE id = ?`, params ); if (result.changes > 0) { res.json({ success: true, message: '更新成功' }); } else { res.status(404).json({ success: false, message: '付款申请不存在' }); } } catch (error) { console.error('更新付款申请失败:', error); res.status(500).json({ success: false, message: '更新付款申请失败', error: error.message }); } }); app.delete('/api/payment-requests/:id', async (req, res) => { try { const { id } = req.params; const result = await db.query('DELETE FROM payment_requests WHERE id = ?', [id]); if (result.changes > 0) { res.json({ success: true, message: '删除成功' }); } else { res.status(404).json({ success: false, message: '付款申请不存在' }); } } catch (error) { console.error('删除付款申请失败:', error); res.status(500).json({ success: false, message: '删除付款申请失败', error: error.message }); } }); // ==================== 提交付款申请 ==================== app.post('/api/payment-requests/:id/submit', async (req, res) => { try { const { id } = req.params; const result = await db.query('UPDATE payment_requests SET status = ? WHERE id = ?', ['pending', id]); if (result.changes > 0) { res.json({ success: true, message: '提交成功' }); } else { res.status(404).json({ success: false, message: '付款申请不存在' }); } } catch (error) { console.error('提交付款申请失败:', error); res.status(500).json({ success: false, message: '提交付款申请失败', error: error.message }); } }); app.post('/api/payment-requests/:id/withdraw', async (req, res) => { try { const { id } = req.params; const result = await db.query('UPDATE payment_requests SET status = ? WHERE id = ?', ['withdrawn', id]); if (result.changes > 0) { res.json({ success: true, message: '撤回成功' }); } else { res.status(404).json({ success: false, message: '付款申请不存在' }); } } catch (error) { console.error('撤回报销申请失败:', error); res.status(500).json({ success: false, message: '撤回报销申请失败', error: error.message }); } }); app.post('/api/payment-requests/:id/approve', async (req, res) => { try { const { id } = req.params; const { remark } = req.body; const result = await db.query('UPDATE payment_requests SET status = ?, approval_remark = ? WHERE id = ?', ['approved', remark, id]); if (result.changes > 0) { res.json({ success: true, message: '审批通过成功' }); } else { res.status(404).json({ success: false, message: '付款申请不存在' }); } } catch (error) { console.error('审批付款申请失败:', error); res.status(500).json({ success: false, message: '审批付款申请失败', error: error.message }); } }); app.post('/api/payment-requests/:id/reject', async (req, res) => { try { const { id } = req.params; const { rejectReason } = req.body; const result = await db.query('UPDATE payment_requests SET status = ? WHERE id = ?', ['pending_edit', id]); if (result.changes > 0) { res.json({ success: true, message: '退回成功' }); } else { res.status(404).json({ success: false, message: '付款申请不存在' }); } } catch (error) { console.error('退回报销申请失败:', error); res.status(500).json({ success: false, message: '退回报销申请失败', error: error.message }); } }); // ==================== 核销申请API ==================== app.get('/api/verifications', async (req, res) => { try { const { advance_id } = req.query; let query = ` SELECT v.*, a.advance_code, a.applicant as advance_applicant FROM verifications v LEFT JOIN advances a ON v.advance_id = a.id `; const params = []; if (advance_id) { query += ` WHERE v.advance_id = ?`; params.push(advance_id); } query += ` ORDER BY v.created_at DESC`; const result = await db.query(query, params); const data = result.rows.map(item => { if (item.attachments) { try { item.attachments = JSON.parse(item.attachments); } catch (error) { item.attachments = []; } } else { item.attachments = []; } if (item.detail_items) { try { item.detail_items = JSON.parse(item.detail_items); } catch (error) { item.detail_items = []; } } else { item.detail_items = []; } return item; }); res.json({ success: true, data, count: data.length }); } catch (error) { console.error('获取核销记录失败:', error); res.status(500).json({ success: false, message: '获取核销记录失败', error: error.message }); } }); app.post('/api/verifications', async (req, res) => { try { const { verification_date, advance_id, advance_code, advance_amount, currency, reason, detail_items, attachments, applicant, expense_type, project_id, settlement, settlement_amount } = req.body; // 生成核销编号 const verificationCode = `VER-${Date.now()}`; const amount = detail_items?.reduce((sum, item) => sum + (item.amount || 0), 0) || 0; // 验证关联预支单 if (!advance_id && !advance_code) { return res.status(400).json({ success: false, message: '关联预支单是必填项' }); } let finalAdvanceCode = advance_code; let finalAdvanceId = advance_id; // 如果advance_code为空,根据advance_id查询预支单的advance_code if (!finalAdvanceCode && finalAdvanceId) { const advanceResult = await db.query('SELECT advance_code FROM advances WHERE id = ?', [finalAdvanceId]); if (advanceResult.rows.length > 0) { finalAdvanceCode = advanceResult.rows[0].advance_code; } else { return res.status(400).json({ success: false, message: '关联的预支单不存在' }); } } // 如果advance_id为空,根据advance_code查询预支单的id if (!finalAdvanceId && finalAdvanceCode) { const advanceResult = await db.query('SELECT id FROM advances WHERE advance_code = ?', [finalAdvanceCode]); if (advanceResult.rows.length > 0) { finalAdvanceId = advanceResult.rows[0].id; } else { return res.status(400).json({ success: false, message: '关联的预支单不存在' }); } } // 如果仍然为空,返回错误 if (!finalAdvanceCode || !finalAdvanceId) { return res.status(400).json({ success: false, message: '关联预支单不存在' }); } // 开始事务 await db.query('BEGIN TRANSACTION'); try { // 插入核销申请 const result = await db.query( 'INSERT INTO verifications (advance_id, amount, currency, reason, verification_date, verification_code, status, applicant, advance_code, advance_amount, detail_items, attachments, expense_type, project_id, settlement, settlement_amount) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', [finalAdvanceId, amount, currency || 'CNY', reason, verification_date, verificationCode, 'pending', applicant, finalAdvanceCode, advance_amount || 0, JSON.stringify(detail_items || []), JSON.stringify(attachments || []), expense_type || 'company', project_id, settlement ? 1 : 0, settlement_amount || 0] ); // 提交事务 await db.query('COMMIT'); // SQLite不支持RETURNING,所以需要查询刚插入的数据 const lastInsert = await db.query('SELECT * FROM verifications ORDER BY id DESC LIMIT 1'); res.json({ success: true, data: lastInsert.rows[0] }); } catch (error) { // 回滚事务 await db.query('ROLLBACK'); throw error; } } catch (error) { console.error('创建核销申请失败:', error); res.status(500).json({ success: false, message: '创建核销申请失败', error: error.message }); } }); app.get('/api/verifications/:id', async (req, res) => { try { const { id } = req.params; const result = await db.query('SELECT * FROM verifications WHERE id = ?', [id]); if (result.rows.length > 0) { const data = result.rows[0]; if (data.attachments) { try { data.attachments = JSON.parse(data.attachments); } catch (error) { data.attachments = []; } } else { data.attachments = []; } if (data.detail_items) { try { data.detail_items = JSON.parse(data.detail_items); } catch (error) { data.detail_items = []; } } else { data.detail_items = []; } res.json({ success: true, data }); } else { res.status(404).json({ success: false, message: '核销申请不存在' }); } } catch (error) { console.error('获取核销申请失败:', error); res.status(500).json({ success: false, message: '获取核销申请失败', error: error.message }); } }); app.put('/api/verifications/:id', async (req, res) => { try { const { id } = req.params; const { verification_date, advance_id, advance_code, advance_amount, currency, reason, detail_items, attachments, applicant, status, expense_type, project_id, settlement, settlement_amount } = req.body; const amount = detail_items?.reduce((sum, item) => sum + (item.amount || 0), 0) || 0; // 开始事务 await db.query('BEGIN TRANSACTION'); try { // 获取原核销金额 const oldVerification = await db.query('SELECT amount, advance_id FROM verifications WHERE id = ?', [id]); const oldAmount = oldVerification.rows[0]?.amount || 0; const oldAdvanceId = oldVerification.rows[0]?.advance_id; let finalAdvanceCode = advance_code; // 如果advance_code为空,根据advance_id查询预支单的advance_code if (!finalAdvanceCode && advance_id) { const advanceResult = await db.query('SELECT advance_code FROM advances WHERE id = ?', [advance_id]); if (advanceResult.rows.length > 0) { finalAdvanceCode = advanceResult.rows[0].advance_code; } } // 如果仍然为空,使用默认值 if (!finalAdvanceCode) { finalAdvanceCode = 'UNKNOWN'; } // 更新核销申请 const result = await db.query( 'UPDATE verifications SET verification_date = ?, advance_id = ?, amount = ?, currency = ?, reason = ?, advance_code = ?, advance_amount = ?, detail_items = ?, attachments = ?, applicant = ?, status = ?, expense_type = ?, project_id = ?, settlement = ?, settlement_amount = ? WHERE id = ?', [verification_date, advance_id, amount, currency, reason, finalAdvanceCode, advance_amount || 0, JSON.stringify(detail_items || []), JSON.stringify(attachments || []), applicant, status, expense_type || 'company', project_id, settlement ? 1 : 0, settlement_amount || 0, id] ); // 不在这里更新预支单已核销金额,而是在执行核销时更新 // if (oldAdvanceId) { // const amountDiff = amount - oldAmount; // if (amountDiff !== 0) { // await db.query( // 'UPDATE advances SET total_reimbursed = total_reimbursed + ? WHERE id = ?', // [amountDiff, oldAdvanceId] // ); // } // } // 提交事务 await db.query('COMMIT'); if (result.changes > 0) { res.json({ success: true, message: '更新成功' }); } else { res.status(404).json({ success: false, message: '核销申请不存在' }); } } catch (error) { // 回滚事务 await db.query('ROLLBACK'); throw error; } } catch (error) { console.error('更新核销申请失败:', error); res.status(500).json({ success: false, message: '更新核销申请失败', error: error.message }); } }); app.delete('/api/verifications/:id', async (req, res) => { try { const { id } = req.params; // 开始事务 await db.query('BEGIN TRANSACTION'); try { // 获取核销金额和预支单ID const verification = await db.query('SELECT amount, advance_id FROM verifications WHERE id = ?', [id]); const amount = verification.rows[0]?.amount || 0; const advanceId = verification.rows[0]?.advance_id; // 删除核销申请 const result = await db.query('DELETE FROM verifications WHERE id = ?', [id]); // 不在这里恢复预支单已核销金额,因为只有执行的核销才会增加已核销金额 // if (advanceId && amount > 0) { // await db.query( // 'UPDATE advances SET total_reimbursed = total_reimbursed - ? WHERE id = ?', // [amount, advanceId] // ); // } // 提交事务 await db.query('COMMIT'); if (result.changes > 0) { res.json({ success: true, message: '删除成功' }); } else { res.status(404).json({ success: false, message: '核销申请不存在' }); } } catch (error) { // 回滚事务 await db.query('ROLLBACK'); throw error; } } catch (error) { console.error('删除核销申请失败:', error); res.status(500).json({ success: false, message: '删除核销申请失败', error: error.message }); } }); // ==================== 提交核销申请 ==================== app.post('/api/verifications/:id/submit', async (req, res) => { try { const { id } = req.params; const result = await db.query('UPDATE verifications SET status = ? WHERE id = ?', ['pending', id]); if (result.changes > 0) { res.json({ success: true, message: '提交成功' }); } else { res.status(404).json({ success: false, message: '核销申请不存在' }); } } catch (error) { console.error('提交核销申请失败:', error); res.status(500).json({ success: false, message: '提交核销申请失败', error: error.message }); } }); app.post('/api/verifications/:id/withdraw', async (req, res) => { try { const { id } = req.params; const result = await db.query('UPDATE verifications SET status = ? WHERE id = ?', ['withdrawn', id]); if (result.changes > 0) { res.json({ success: true, message: '撤回成功' }); } else { res.status(404).json({ success: false, message: '核销申请不存在' }); } } catch (error) { console.error('撤回核销申请失败:', error); res.status(500).json({ success: false, message: '撤回核销申请失败', error: error.message }); } }); app.post('/api/verifications/:id/approve', async (req, res) => { try { const { id } = req.params; const { remark } = req.body; const result = await db.query('UPDATE verifications SET status = ?, approval_remark = ? WHERE id = ?', ['approved', remark, id]); if (result.changes > 0) { res.json({ success: true, message: '审批通过成功' }); } else { res.status(404).json({ success: false, message: '核销申请不存在' }); } } catch (error) { console.error('审批核销申请失败:', error); res.status(500).json({ success: false, message: '审批核销申请失败', error: error.message }); } }); app.post('/api/verifications/:id/reject', async (req, res) => { try { const { id } = req.params; const { rejectReason } = req.body; // 开始事务 await db.query('BEGIN TRANSACTION'); try { // 获取核销金额和预支单ID const verification = await db.query('SELECT amount, advance_id FROM verifications WHERE id = ?', [id]); const amount = verification.rows[0]?.amount || 0; const advanceId = verification.rows[0]?.advance_id; // 退回核销申请 const result = await db.query('UPDATE verifications SET status = ? WHERE id = ?', ['pending_edit', id]); // 不在这里恢复预支单已核销金额,因为只有执行的核销才会增加已核销金额 // if (advanceId && amount > 0) { // await db.query( // 'UPDATE advances SET total_reimbursed = total_reimbursed - ? WHERE id = ?', // [amount, advanceId] // ); // } // 提交事务 await db.query('COMMIT'); if (result.changes > 0) { res.json({ success: true, message: '退回成功' }); } else { res.status(404).json({ success: false, message: '核销申请不存在' }); } } catch (error) { // 回滚事务 await db.query('ROLLBACK'); throw error; } } catch (error) { console.error('退回核销申请失败:', error); res.status(500).json({ success: false, message: '退回核销申请失败', error: error.message }); } }); // ==================== 执行管理API ==================== app.get('/api/executions', async (req, res) => { try { const result = await db.query(` SELECT * FROM executions ORDER BY created_at DESC `); res.json({ success: true, data: result.rows, count: result.rows.length }); } catch (error) { console.error('获取执行记录失败:', error); res.status(500).json({ success: false, message: '获取执行记录失败', error: error.message }); } }); app.get('/api/executions/pending', async (req, res) => { try { // 获取待执行的申请(已审批通过但未执行) const advances = await db.query('SELECT * FROM advances WHERE status = ?', ['approved']); const reimbursements = await db.query('SELECT * FROM reimbursements WHERE status = ?', ['approved']); const payments = await db.query('SELECT * FROM payment_requests WHERE status = ?', ['approved']); const verifications = await db.query('SELECT * FROM verifications WHERE status = ?', ['approved']); const purchaseRequests = await db.query('SELECT * FROM purchase_requests WHERE status = ?', ['approved']); const pendingData = [ ...advances.rows.map(item => ({ ...item, type: '预支申请', code: item.advance_code })), ...reimbursements.rows.map(item => ({ ...item, type: '报销申请', code: item.reimbursement_code })), ...payments.rows.map(item => ({ ...item, type: '付款申请', code: item.request_code })), ...verifications.rows.map(item => ({ ...item, type: '核销申请', code: item.verification_code })), ...purchaseRequests.rows.map(item => ({ ...item, type: '采购申请', code: item.request_code, amount: item.total_amount, date: item.request_date, reason: item.brief_description || item.remark || '采购申请' })) ]; res.json({ success: true, data: pendingData, count: pendingData.length }); } catch (error) { console.error('获取待执行列表失败:', error); res.status(500).json({ success: false, message: '获取待执行列表失败', error: error.message }); } }); app.get('/api/executions/executed', async (req, res) => { try { // 获取已执行的申请 const advances = await db.query('SELECT * FROM advances WHERE status = ?', ['executed']); const reimbursements = await db.query('SELECT * FROM reimbursements WHERE status = ?', ['executed']); const payments = await db.query('SELECT * FROM payment_requests WHERE status = ?', ['executed']); const verifications = await db.query('SELECT * FROM verifications WHERE status = ?', ['executed']); const purchaseRequests = await db.query('SELECT * FROM purchase_requests WHERE status = ?', ['executed']); const executedData = [ ...advances.rows.map(item => ({ ...item, type: '预支申请', code: item.advance_code })), ...reimbursements.rows.map(item => ({ ...item, type: '报销申请', code: item.reimbursement_code })), ...payments.rows.map(item => ({ ...item, type: '付款申请', code: item.request_code })), ...verifications.rows.map(item => ({ ...item, type: '核销申请', code: item.verification_code })), ...purchaseRequests.rows.map(item => ({ ...item, type: '采购申请', code: item.request_code, amount: item.total_amount, date: item.request_date, reason: item.brief_description || item.remark || '采购申请', executeDate: item.execute_date, executeMethod: item.execute_method })) ]; res.json({ success: true, data: executedData, count: executedData.length }); } catch (error) { console.error('获取已执行列表失败:', error); res.status(500).json({ success: false, message: '获取已执行列表失败', error: error.message }); } }); app.post('/api/executions', async (req, res) => { try { const { apply_id, apply_type, action, execute_method, voucher_no, remark, reject_reason, voucher_files } = req.body; const operator = '系统管理员'; const operator_role = 'admin'; // 记录执行操作 await db.query( 'INSERT INTO executions (apply_id, apply_type, action, execute_method, voucher_no, remark, reject_reason, voucher_files, operator, operator_role, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime(\'now\'))', [apply_id, apply_type, action, execute_method, voucher_no, remark, reject_reason, JSON.stringify(voucher_files || []), operator, operator_role] ); // 更新申请状态 let status = action === 'execute' ? 'executed' : 'rejected'; if (action === 'reject') { status = 'pending_edit'; // 退回后状态改为待编辑 } const executeDate = new Date().toISOString().split('T')[0]; switch (apply_type) { case 'advance': await db.query('UPDATE advances SET status = ?, execute_date = ?, execute_method = ? WHERE id = ?', [status, executeDate, execute_method, apply_id]); break; case 'reimbursement': await db.query('UPDATE reimbursements SET status = ?, execute_date = ?, execute_method = ? WHERE id = ?', [status, executeDate, execute_method, apply_id]); break; case 'payment': await db.query('UPDATE payment_requests SET status = ?, execute_date = ?, execute_method = ? WHERE id = ?', [status, executeDate, execute_method, apply_id]); break; case 'verification': // 开始事务 await db.query('BEGIN TRANSACTION'); try { // 更新核销申请状态 await db.query('UPDATE verifications SET status = ?, execute_date = ?, execute_method = ? WHERE id = ?', [status, executeDate, execute_method, apply_id]); // 获取核销申请信息 const verification = await db.query('SELECT advance_id, settlement, amount FROM verifications WHERE id = ?', [apply_id]); const advanceId = verification.rows[0]?.advance_id; const isSettlement = verification.rows[0]?.settlement === 1; const verificationAmount = verification.rows[0]?.amount || 0; // 更新预支单状态和已核销金额 if (advanceId && status === 'executed') { // 更新预支单已核销金额 await db.query('UPDATE advances SET total_reimbursed = total_reimbursed + ? WHERE id = ?', [verificationAmount, advanceId]); if (isSettlement) { // 如果是结算核销,将预支单状态改为已完成 await db.query('UPDATE advances SET status = ? WHERE id = ?', ['completed', advanceId]); } else { // 如果不是结算核销,将预支单状态改为部分核销 await db.query('UPDATE advances SET status = ? WHERE id = ?', ['partial_verification', advanceId]); } } // 提交事务 await db.query('COMMIT'); } catch (error) { // 回滚事务 await db.query('ROLLBACK'); throw error; } break; case 'purchase': await db.query('UPDATE purchase_requests SET status = ?, execute_date = ?, execute_method = ? WHERE id = ?', [status, executeDate, execute_method, apply_id]); break; } res.json({ success: true, message: '执行操作成功' }); } catch (error) { console.error('执行操作失败:', error); res.status(500).json({ success: false, message: '执行操作失败', error: error.message }); } }); app.get('/api/reimbursements', async (req, res) => { try { const result = await db.query(` SELECT r.*, u.name as user_name, p.name as project_name FROM reimbursements r LEFT JOIN users u ON r.user_id = u.id LEFT JOIN projects p ON r.project_id = p.id ORDER BY r.created_at DESC `); // 解析每个报销申请的 attachments 和 detail_items 字段为数组 const data = result.rows.map(item => { // 解析 attachments 字段 if (item.attachments) { try { item.attachments = JSON.parse(item.attachments); } catch (error) { item.attachments = []; } } else { item.attachments = []; } // 解析 detail_items 字段 if (item.detail_items) { try { item.detail_items = JSON.parse(item.detail_items); } catch (error) { item.detail_items = []; } } else { item.detail_items = []; } return item; }); res.json({ success: true, data, count: data.length }); } catch (error) { console.error('获取报销记录失败:', error); res.status(500).json({ success: false, message: '获取报销记录失败', error: error.message }); } }); // ==================== 创建报销申请 ==================== app.post('/api/reimbursements', [ body('amount').isFloat({ min: 0.01 }), body('reason').notEmpty(), body('expense_type').notEmpty() ], validate, async (req, res) => { try { const { amount, reason, project_id, currency, reimbursement_date, attachments, amount_cny, applicant, expense_type, detail_items } = req.body; const user_id = 1; // 临时使用admin用户 // 生成报销编号 const reimbursementCode = `REIMB-${Date.now()}`; const result = await db.query( 'INSERT INTO reimbursements (user_id, project_id, amount, currency, amount_cny, reason, reimbursement_date, reimbursement_code, status, applicant, expense_type, detail_items, attachments) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', [user_id, project_id, amount, currency || 'CNY', amount_cny || 0, reason, reimbursement_date, reimbursementCode, 'pending', applicant, expense_type, JSON.stringify(detail_items || []), JSON.stringify(attachments || [])] ); // SQLite不支持RETURNING,所以需要查询刚插入的数据 const lastInsert = await db.query('SELECT * FROM reimbursements ORDER BY id DESC LIMIT 1'); res.json({ success: true, data: lastInsert.rows[0] }); } catch (error) { console.error('创建报销申请失败:', error); res.status(500).json({ success: false, message: '创建报销申请失败', error: error.message }); } }); // ==================== 获取单个报销申请 ==================== app.get('/api/reimbursements/:id', async (req, res) => { try { const { id } = req.params; const result = await db.query('SELECT * FROM reimbursements WHERE id = ?', [id]); if (result.rows.length > 0) { const data = result.rows[0]; // 解析 attachments 字段为数组 if (data.attachments) { try { data.attachments = JSON.parse(data.attachments); } catch (error) { data.attachments = []; } } else { data.attachments = []; } // 解析 detail_items 字段为数组 if (data.detail_items) { try { data.detail_items = JSON.parse(data.detail_items); } catch (error) { data.detail_items = []; } } else { data.detail_items = []; } res.json({ success: true, data }); } else { res.status(404).json({ success: false, message: '报销申请不存在' }); } } catch (error) { console.error('获取报销申请失败:', error); res.status(500).json({ success: false, message: '获取报销申请失败', error: error.message }); } }); // ==================== 更新报销申请 ==================== app.put('/api/reimbursements/:id', async (req, res) => { try { const { id } = req.params; const { amount, reason, project_id, currency, reimbursement_date, attachments, amount_cny, applicant, expense_type, detail_items, status } = req.body; const result = await db.query( 'UPDATE reimbursements SET amount = ?, reason = ?, project_id = ?, currency = ?, reimbursement_date = ?, attachments = ?, amount_cny = ?, applicant = ?, expense_type = ?, detail_items = ?, status = ? WHERE id = ?', [amount, reason, project_id, currency, reimbursement_date, JSON.stringify(attachments || []), amount_cny, applicant, expense_type, JSON.stringify(detail_items || []), status, id] ); if (result.changes > 0) { res.json({ success: true, message: '更新成功' }); } else { res.status(404).json({ success: false, message: '报销申请不存在' }); } } catch (error) { console.error('更新报销申请失败:', error); res.status(500).json({ success: false, message: '更新报销申请失败', error: error.message }); } }); // ==================== 删除报销申请 ==================== app.delete('/api/reimbursements/:id', async (req, res) => { try { const { id } = req.params; const result = await db.query('DELETE FROM reimbursements WHERE id = ?', [id]); if (result.changes > 0) { res.json({ success: true, message: '删除成功' }); } else { res.status(404).json({ success: false, message: '报销申请不存在' }); } } catch (error) { console.error('删除报销申请失败:', error); res.status(500).json({ success: false, message: '删除报销申请失败', error: error.message }); } }); // ==================== 撤回报销申请 ==================== // ==================== 提交报销申请 ==================== app.post('/api/reimbursements/:id/submit', async (req, res) => { try { const { id } = req.params; const result = await db.query('UPDATE reimbursements SET status = ? WHERE id = ?', ['pending', id]); if (result.changes > 0) { res.json({ success: true, message: '提交成功' }); } else { res.status(404).json({ success: false, message: '报销申请不存在' }); } } catch (error) { console.error('提交报销申请失败:', error); res.status(500).json({ success: false, message: '提交报销申请失败', error: error.message }); } }); app.post('/api/reimbursements/:id/withdraw', async (req, res) => { try { const { id } = req.params; const result = await db.query('UPDATE reimbursements SET status = ? WHERE id = ?', ['withdrawn', id]); if (result.changes > 0) { res.json({ success: true, message: '撤回成功' }); } else { res.status(404).json({ success: false, message: '报销申请不存在' }); } } catch (error) { console.error('撤回报销申请失败:', error); res.status(500).json({ success: false, message: '撤回报销申请失败', error: error.message }); } }); // ==================== 审批报销申请 ==================== app.post('/api/reimbursements/:id/approve', async (req, res) => { try { const { id } = req.params; const { remark } = req.body; const result = await db.query('UPDATE reimbursements SET status = ?, approval_remark = ? WHERE id = ?', ['approved', remark, id]); if (result.changes > 0) { res.json({ success: true, message: '审批通过成功' }); } else { res.status(404).json({ success: false, message: '报销申请不存在' }); } } catch (error) { console.error('审批报销申请失败:', error); res.status(500).json({ success: false, message: '审批报销申请失败', error: error.message }); } }); // ==================== 退回报销申请 ==================== app.post('/api/reimbursements/:id/reject', async (req, res) => { try { const { id } = req.params; const { rejectReason } = req.body; const result = await db.query('UPDATE reimbursements SET status = ? WHERE id = ?', ['pending_edit', id]); if (result.changes > 0) { res.json({ success: true, message: '退回成功' }); } else { res.status(404).json({ success: false, message: '报销申请不存在' }); } } catch (error) { console.error('退回报销申请失败:', error); res.status(500).json({ success: false, message: '退回报销申请失败', error: error.message }); } }); // ==================== 采购申请API ==================== app.get('/api/purchase-requests', async (req, res) => { try { const { project_id, status } = req.query; let query = ` SELECT pr.*, p.name as project_name, s.name as supplier_name FROM purchase_requests pr LEFT JOIN projects p ON pr.project_id = p.id LEFT JOIN suppliers s ON pr.supplier_id = s.id `; const params = []; if (project_id) { query += ' WHERE pr.project_id = ?'; params.push(project_id); } if (status) { query += project_id ? ' AND pr.status = ?' : ' WHERE pr.status = ?'; params.push(status); } query += ' ORDER BY pr.created_at DESC'; const result = await db.query(query, params); // 转换字段名,保持向后兼容 const data = result.rows.map(row => ({ ...row, request_code: row.code // 添加request_code字段以保持兼容性 })); res.json({ success: true, data: data, count: data.length }); } catch (error) { console.error('获取采购申请列表失败:', error); res.status(500).json({ success: false, message: '获取采购申请列表失败', error: error.message }); } }); app.get('/api/purchase-requests/:id', async (req, res) => { try { const { id } = req.params; const requestResult = await db.query(` SELECT pr.*, p.name as project_name, s.name as supplier_name FROM purchase_requests pr LEFT JOIN projects p ON pr.project_id = p.id LEFT JOIN suppliers s ON pr.supplier_id = s.id WHERE pr.id = ? `, [id]); if (requestResult.rows.length === 0) { return res.status(404).json({ success: false, message: '采购申请不存在' }); } const purchaseRequest = requestResult.rows[0]; const itemsResult = await db.query(` SELECT * FROM purchase_request_items WHERE purchase_request_id = ? `, [id]); purchaseRequest.items = itemsResult.rows; // 添加request_code字段以保持向后兼容 purchaseRequest.request_code = purchaseRequest.code; // 处理附件字段,将字符串转换为数组 if (purchaseRequest.attachments) { if (typeof purchaseRequest.attachments === 'string') { // 如果是字符串,将其转换为数组 purchaseRequest.attachments = purchaseRequest.attachments.split(',').map((url) => ({ url: url, name: url.split('/').pop() || '', uid: url, status: 'done' })); } } else { // 如果没有附件,设置为空数组 purchaseRequest.attachments = []; } // 获取供应商的付款信息 if (purchaseRequest.supplier_id) { const paymentInfosResult = await db.query(` SELECT * FROM supplier_payment_infos WHERE supplier_id = ? ORDER BY is_default DESC `, [purchaseRequest.supplier_id]); purchaseRequest.supplier_payment_infos = paymentInfosResult.rows.map(payment => ({ id: payment.id, account_name: payment.account_name, bank_account: payment.account_number, bank_name: payment.bank_name, qr_code: payment.qr_code, is_primary: payment.is_default === 1 })); } res.json({ success: true, data: purchaseRequest }); } catch (error) { console.error('获取采购申请详情失败:', error); res.status(500).json({ success: false, message: '获取采购申请详情失败', error: error.message }); } }); app.post('/api/purchase-requests', async (req, res) => { try { const { project_id, applicant, request_date, supplier_id, supplier_name, expense_category, total_amount, currency, remark, attachments, items, purchase_type, brief_description, title } = req.body; const date = new Date(); const requestCode = `PUR-${date.getFullYear()}${String(date.getMonth() + 1).padStart(2, '0')}${String(date.getDate()).padStart(2, '0')}-${String(Math.floor(Math.random() * 1000)).padStart(3, '0')}`; const result = await db.query(` INSERT INTO purchase_requests (code, title, project_id, applicant, request_date, expense_category, total_amount, currency, execute_date, supplier_id, supplier_name, status, purchase_type, brief_description, attachments, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'), datetime('now')) `, [requestCode, title || '采购申请', project_id, applicant, request_date, expense_category, total_amount || 0, currency || 'CNY', request_date, supplier_id, supplier_name, 'pending_edit', purchase_type || 'inventory', brief_description, attachments || '']); const purchaseRequestId = result.lastID; if (items && items.length > 0) { for (const item of items) { await db.query(` INSERT INTO purchase_request_items (purchase_request_id, product_id, product_name, specification, unit, quantity, unit_price, total_price) VALUES (?, ?, ?, ?, ?, ?, ?, ?) `, [purchaseRequestId, item.product_id || null, item.product_name, item.specification || null, item.unit || null, item.quantity || 0, item.unit_price || 0, item.total_price || 0]); } } res.json({ success: true, message: '采购申请创建成功', data: { id: purchaseRequestId, request_code: requestCode } }); } catch (error) { console.error('创建采购申请失败:', error); res.status(500).json({ success: false, message: '创建采购申请失败', error: error.message }); } }); app.put('/api/purchase-requests/:id', async (req, res) => { try { const { id } = req.params; const { project_id, applicant, request_date, supplier_id, supplier_name, expense_category, total_amount, currency, remark, attachments, items, purchase_type, brief_description, title } = req.body; console.log('更新采购申请 ID:', id); console.log('请求数据:', req.body); console.log('items 数据:', items); const result = await db.query(` UPDATE purchase_requests SET project_id = ?, applicant = ?, request_date = ?, expense_category = ?, total_amount = ?, currency = ?, execute_date = ?, supplier_id = ?, supplier_name = ?, purchase_type = ?, brief_description = ?, title = ?, attachments = ?, updated_at = datetime('now') WHERE id = ? `, [project_id, applicant, request_date, expense_category, total_amount, currency || 'CNY', request_date, supplier_id, supplier_name, purchase_type || 'inventory', brief_description, title || '采购申请', attachments || '', id]); console.log('更新结果:', result); if (result.changes === 0) { return res.status(404).json({ success: false, message: '采购申请不存在' }); } if (items && Array.isArray(items)) { console.log('开始更新 items,数量:', items.length); await db.query('DELETE FROM purchase_request_items WHERE purchase_request_id = ?', [id]); for (let i = 0; i < items.length; i++) { const item = items[i]; console.log(`插入 item ${i}:`, item); try { await db.query(` INSERT INTO purchase_request_items (purchase_request_id, product_id, product_name, specification, unit, quantity, unit_price, total_price) VALUES (?, ?, ?, ?, ?, ?, ?, ?) `, [id, item.product_id || null, item.product_name, item.specification || null, item.unit || null, item.quantity || 0, item.unit_price || 0, item.total_price || 0]); } catch (itemError) { console.error(`插入 item ${i} 失败:`, itemError); throw itemError; } } } res.json({ success: true, message: '采购申请更新成功' }); } catch (error) { console.error('更新采购申请失败:', error); res.status(500).json({ success: false, message: '更新采购申请失败', error: error.message }); } }); app.delete('/api/purchase-requests/:id', async (req, res) => { try { const { id } = req.params; const result = await db.query('DELETE FROM purchase_requests WHERE id = ?', [id]); if (result.changes === 0) { return res.status(404).json({ success: false, message: '采购申请不存在' }); } res.json({ success: true, message: '采购申请删除成功' }); } catch (error) { console.error('删除采购申请失败:', error); res.status(500).json({ success: false, message: '删除采购申请失败', error: error.message }); } }); app.post('/api/purchase-requests/:id/submit', async (req, res) => { try { const { id } = req.params; const result = await db.query('UPDATE purchase_requests SET status = ?, updated_at = datetime(\'now\') WHERE id = ?', ['pending', id]); if (result.changes === 0) { return res.status(404).json({ success: false, message: '采购申请不存在' }); } res.json({ success: true, message: '提交成功' }); } catch (error) { console.error('提交采购申请失败:', error); res.status(500).json({ success: false, message: '提交采购申请失败', error: error.message }); } }); app.post('/api/purchase-requests/:id/approve', async (req, res) => { try { const { id } = req.params; const result = await db.query('UPDATE purchase_requests SET status = ?, updated_at = datetime(\'now\') WHERE id = ?', ['approved', id]); if (result.changes === 0) { return res.status(404).json({ success: false, message: '采购申请不存在' }); } res.json({ success: true, message: '审批通过成功' }); } catch (error) { console.error('审批采购申请失败:', error); res.status(500).json({ success: false, message: '审批采购申请失败', error: error.message }); } }); app.post('/api/purchase-requests/:id/reject', async (req, res) => { try { const { id } = req.params; const result = await db.query('UPDATE purchase_requests SET status = ?, updated_at = datetime(\'now\') WHERE id = ?', ['pending_edit', id]); if (result.changes === 0) { return res.status(404).json({ success: false, message: '采购申请不存在' }); } res.json({ success: true, message: '驳回成功' }); } catch (error) { console.error('驳回采购申请失败:', error); res.status(500).json({ success: false, message: '驳回采购申请失败', error: error.message }); } }); app.post('/api/purchase-requests/:id/execute', async (req, res) => { try { const { id } = req.params; const { operator } = req.body; await db.query('UPDATE purchase_requests SET status = ?, updated_at = datetime(\'now\') WHERE id = ?', ['executed', id]); const itemsResult = await db.query('SELECT * FROM purchase_request_items WHERE purchase_request_id = ?', [id]); for (const item of itemsResult.rows) { await db.query(` INSERT INTO inventory_records (record_type, purchase_request_id, product_id, quantity, unit_price, total_amount, record_date, operator) VALUES (?, ?, ?, ?, ?, ?, date('now'), ?) `, ['in', id, item.product_id, item.quantity, item.unit_price, item.total_price, operator || '系统']); } res.json({ success: true, message: '执行成功,已自动入库' }); } catch (error) { console.error('执行采购申请失败:', error); res.status(500).json({ success: false, message: '执行采购申请失败', error: error.message }); } }); app.post('/api/purchase-requests/:id/withdraw', async (req, res) => { try { const { id } = req.params; const result = await db.query('UPDATE purchase_requests SET status = ?, updated_at = datetime(\'now\') WHERE id = ?', ['withdrawn', id]); if (result.changes === 0) { return res.status(404).json({ success: false, message: '采购申请不存在' }); } res.json({ success: true, message: '撤回成功' }); } catch (error) { console.error('撤回采购申请失败:', error); res.status(500).json({ success: false, message: '撤回采购申请失败', error: error.message }); } }); // ==================== 采购订单API ==================== app.get('/api/purchase-orders', async (req, res) => { try { const result = await db.query('SELECT * FROM purchase_orders ORDER BY created_at DESC'); res.json({ success: true, data: result.rows }); } catch (error) { console.error('获取采购订单列表失败:', error); res.status(500).json({ success: false, message: '获取采购订单列表失败', error: error.message }); } }); app.post('/api/purchase-orders', async (req, res) => { try { const { purchase_request_id, supplier_id, supplier_name, total_amount, currency, order_date, delivery_date, items } = req.body; const code = 'PO' + new Date().toISOString().slice(0, 10).replace(/-/g, '') + Math.floor(1000 + Math.random() * 9000); // 开始事务 await db.query('BEGIN TRANSACTION'); // 插入采购订单 await db.query( 'INSERT INTO purchase_orders (code, purchase_request_id, supplier_id, supplier_name, total_amount, currency, order_date, delivery_date, status, created_by) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', [code, purchase_request_id, supplier_id, supplier_name, total_amount, currency, order_date, delivery_date, 'pending', 'system'] ); // 获取刚插入的采购订单ID const orderResult = await db.query('SELECT id FROM purchase_orders ORDER BY id DESC LIMIT 1'); const purchase_order_id = orderResult.rows[0].id; // 插入采购订单明细 for (const item of items) { await db.query( 'INSERT INTO purchase_order_items (purchase_order_id, product_id, product_name, specification, quantity, unit, unit_price, total_price, remark) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)', [purchase_order_id, item.product_id, item.product_name, item.specification, item.quantity, item.unit, item.unit_price, item.total_price, item.remark] ); } // 提交事务 await db.query('COMMIT'); res.json({ success: true, message: '采购订单创建成功' }); } catch (error) { // 回滚事务 await db.query('ROLLBACK'); console.error('创建采购订单失败:', error); res.status(500).json({ success: false, message: '创建采购订单失败', error: error.message }); } }); app.get('/api/purchase-orders/:id', async (req, res) => { try { const { id } = req.params; // 获取采购订单信息 const orderResult = await db.query('SELECT * FROM purchase_orders WHERE id = ?', [id]); if (orderResult.rows.length === 0) { return res.status(404).json({ success: false, message: '采购订单不存在' }); } // 获取采购订单明细 const itemsResult = await db.query('SELECT * FROM purchase_order_items WHERE purchase_order_id = ?', [id]); const order = orderResult.rows[0]; order.items = itemsResult.rows; res.json({ success: true, data: order }); } catch (error) { console.error('获取采购订单详情失败:', error); res.status(500).json({ success: false, message: '获取采购订单详情失败', error: error.message }); } }); // ==================== 付款计划API ==================== app.get('/api/payment-plans', async (req, res) => { try { const result = await db.query('SELECT * FROM payment_plans ORDER BY created_at DESC'); res.json({ success: true, data: result.rows }); } catch (error) { console.error('获取付款计划列表失败:', error); res.status(500).json({ success: false, message: '获取付款计划列表失败', error: error.message }); } }); app.post('/api/payment-plans', async (req, res) => { try { const { purchase_order_id, payment_date, amount, currency, payment_type, description } = req.body; const code = 'PP' + new Date().toISOString().slice(0, 10).replace(/-/g, '') + Math.floor(1000 + Math.random() * 9000); await db.query( 'INSERT INTO payment_plans (purchase_order_id, code, payment_date, amount, currency, payment_type, status, description, created_by) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)', [purchase_order_id, code, payment_date, amount, currency, payment_type, 'pending', description, 'system'] ); res.json({ success: true, message: '付款计划创建成功' }); } catch (error) { console.error('创建付款计划失败:', error); res.status(500).json({ success: false, message: '创建付款计划失败', error: error.message }); } }); app.get('/api/payment-plans/:id', async (req, res) => { try { const { id } = req.params; const result = await db.query('SELECT * FROM payment_plans WHERE id = ?', [id]); if (result.rows.length === 0) { return res.status(404).json({ success: false, message: '付款计划不存在' }); } res.json({ success: true, data: result.rows[0] }); } catch (error) { console.error('获取付款计划详情失败:', error); res.status(500).json({ success: false, message: '获取付款计划详情失败', error: error.message }); } }); app.put('/api/payment-plans/:id', async (req, res) => { try { const { id } = req.params; const { payment_date, amount, currency, payment_type, status, description } = req.body; await db.query( 'UPDATE payment_plans SET payment_date = ?, amount = ?, currency = ?, payment_type = ?, status = ?, description = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?', [payment_date, amount, currency, payment_type, status, description, id] ); res.json({ success: true, message: '付款计划更新成功' }); } catch (error) { console.error('更新付款计划失败:', error); res.status(500).json({ success: false, message: '更新付款计划失败', error: error.message }); } }); // ==================== 库存管理API ==================== app.get('/api/inventory', async (req, res) => { try { const { product_id, project_id, record_type } = req.query; let query = ` SELECT ir.*, p.name as product_name, prj.name as project_name FROM inventory_records ir LEFT JOIN products p ON ir.product_id = p.id LEFT JOIN projects prj ON ir.project_id = prj.id `; const params = []; const conditions = []; if (product_id) { conditions.push('ir.product_id = ?'); params.push(product_id); } if (project_id) { conditions.push('ir.project_id = ?'); params.push(project_id); } if (record_type) { conditions.push('ir.record_type = ?'); params.push(record_type); } if (conditions.length > 0) { query += ' WHERE ' + conditions.join(' AND '); } query += ' ORDER BY ir.created_at DESC'; const result = await db.query(query, params); res.json({ success: true, data: result.rows, count: result.rows.length }); } catch (error) { console.error('获取库存记录失败:', error); res.status(500).json({ success: false, message: '获取库存记录失败', error: error.message }); } }); app.get('/api/inventory/summary', async (req, res) => { try { const result = await db.query(` SELECT p.id as product_id, p.name as product_name, p.unit, SUM(CASE WHEN ir.record_type = 'in' THEN ir.quantity ELSE 0 END) as total_in, SUM(CASE WHEN ir.record_type = 'out' THEN ir.quantity ELSE 0 END) as total_out, SUM(CASE WHEN ir.record_type = 'in' THEN ir.quantity ELSE -ir.quantity END) as current_quantity FROM products p LEFT JOIN inventory_records ir ON p.id = ir.product_id GROUP BY p.id, p.name, p.unit `); res.json({ success: true, data: result.rows }); } catch (error) { console.error('获取库存汇总失败:', error); res.status(500).json({ success: false, message: '获取库存汇总失败', error: error.message }); } }); app.post('/api/inventory/out', async (req, res) => { try { const { project_id, product_id, quantity, unit_price, total_amount, operator, remark } = req.body; const result = await db.query(` INSERT INTO inventory_records (record_type, project_id, product_id, quantity, unit_price, total_amount, record_date, operator, remark) VALUES (?, ?, ?, ?, ?, ?, date('now'), ?, ?) `, ['out', project_id || null, product_id, quantity, unit_price || null, total_amount || null, operator || '系统', remark || null]); res.json({ success: true, message: '出库成功', data: { id: result.lastID } }); } catch (error) { console.error('出库失败:', error); res.status(500).json({ success: false, message: '出库失败', error: error.message }); } }); // ==================== 项目成本统计API ==================== app.get('/api/projects/:id/cost-summary', async (req, res) => { try { const { id } = req.params; const purchaseResult = await db.query(` SELECT expense_category, SUM(total_amount) as total_amount FROM purchase_requests WHERE project_id = ? AND status IN ('approved', 'executed') GROUP BY expense_category `, [id]); const paymentResult = await db.query(` SELECT SUM(amount) as total_payment FROM payment_requests WHERE project_id = ? AND status = 'approved' AND payment_type = 'company' `, [id]); const projectResult = await db.query('SELECT * FROM projects WHERE id = ?', [id]); if (projectResult.rows.length === 0) { return res.status(404).json({ success: false, message: '项目不存在' }); } const project = projectResult.rows[0]; const purchaseByCategory = {}; let totalPurchase = 0; purchaseResult.rows.forEach(row => { purchaseByCategory[row.expense_category] = row.total_amount; totalPurchase += row.total_amount; }); const totalPayment = paymentResult.rows[0]?.total_payment || 0; res.json({ success: true, data: { project_name: project.name, contract_amount: project.contract_amount || 0, purchase_cost: { total: totalPurchase, by_category: purchaseByCategory }, payment_cost: totalPayment, total_cost: totalPurchase + totalPayment, profit: (project.contract_amount || 0) - (totalPurchase + totalPayment) } }); } catch (error) { console.error('获取项目成本统计失败:', error); res.status(500).json({ success: false, message: '获取项目成本统计失败', error: error.message }); } }); // ==================== 财务统计API ==================== app.get('/api/finance-stats', async (req, res) => { try { // 获取统计数据 const [customers, suppliers, projects, paymentNodes, paymentRecords] = await Promise.all([ db.query('SELECT COUNT(*) as count FROM customers'), db.query('SELECT COUNT(*) as count FROM suppliers'), db.query('SELECT COUNT(*) as count FROM projects'), db.query('SELECT COUNT(*) as count FROM payment_nodes'), db.query('SELECT COUNT(*) as count FROM payment_records') ]); res.json({ success: true, data: { summary: { customers: parseInt(customers.rows[0].count) || 0, suppliers: parseInt(suppliers.rows[0].count) || 0, projects: parseInt(projects.rows[0].count) || 0, payment_nodes: parseInt(paymentNodes.rows[0].count) || 0, payment_records: parseInt(paymentRecords.rows[0].count) || 0 }, timestamp: new Date().toISOString() } }); } catch (error) { res.json({ success: false, message: '获取财务统计失败', error: error.message }); } }); // ==================== 系统状态页面 ==================== app.get('/status', (req, res) => { res.send(` 系统状态 - 公司财务管理系统

🏢 公司财务管理系统 - 生产环境状态

服务器: 43.161.248.209:3000 | 时间: ${new Date().toLocaleString()}

前端服务
端口: 3000
状态: 正常
后端API
12个端点
状态: 正常
数据库
PostgreSQL
状态: 已连接
网络访问
绑定: 0.0.0.0
状态: 已验证

🔧 端口访问说明

✅ 端口3000: 已验证可外部访问,所有服务运行正常

⚠️ 端口5000: 安全组已开放,但可能存在网络路由问题

🎯 解决方案: 使用已验证的3000端口作为生产环境

进入系统 API健康检查 测试客户API
`); }); // ==================== 欢迎页面 ==================== app.get('/welcome', (req, res) => { res.send(` 欢迎 - 公司财务管理系统

🏢 公司财务管理系统

生产环境 v1.0.0 | 专为老挝电力公司定制
12
功能模块
4
多币种支持
100%
响应式设计
24/7
服务可用

🚀 立即开始

点击下方按钮进入系统,开始管理您的财务业务。

进入系统主界面 查看系统状态

📊 核心功能

  • 客户与供应商管理
  • 项目与合同管理
  • 付款节点与记录
  • 多币种汇率管理
  • 预支款与报销流程
  • 财务统计与报表
  • 移动端适配
  • 多语言支持

🔧 系统信息

服务器: 43.161.248.209:3000

技术栈: React + Node.js + PostgreSQL

部署时间: 2026-03-09

测试账号: admin / password

API健康检查 客户API

© 2026 老挝电力公司财务管理系统 | 技术支持: OpenClaw AI Assistant

`); }); // ==================== 默认路由 ==================== app.get('/', (req, res) => { res.sendFile(path.join(__dirname, '../frontend/dist/index.html')); }); // ==================== API文档页面 ==================== app.get('/api-docs', (req, res) => { res.send(` API文档

📚 API文档

这是API端点文档页面。如果您想使用业务界面,请访问:

👉 点击这里进入业务系统

或访问:欢迎页面

`); }); // ==================== 文件上传API (腾讯云COS) ==================== // 暂时注释掉腾讯云COS上传,使用本地文件存储 /* const COS = require('cos-nodejs-sdk-v5'); const cosStorage = multer.memoryStorage(); const upload = multer({ storage: cosStorage, limits: { fileSize: 10 * 1024 * 1024 } }); const cosConfig = { SecretId: process.env.TENCENT_SECRET_ID || '', SecretKey: process.env.TENCENT_SECRET_KEY || '', Bucket: 'qingyuan-erp-files-1310040146', Region: 'ap-hongkong' }; const cos = new COS(cosConfig); const imageFormats = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'svg']; app.post('/api/upload/single/cos', upload.single('file'), async (req, res) => { try { if (!req.file) return res.status(400).json({ success: false, error: '没有上传文件' }); console.log('接收到文件:', req.file.originalname); const ext = req.file.originalname.split('.').pop().toLowerCase(); const timestamp = Date.now(); const randomStr = Math.random().toString(36).substring(2, 8); const filename = 'uploads/' + timestamp + '_' + randomStr + '.' + ext; console.log('准备上传到COS:', filename); cos.putObject({ Bucket: cosConfig.Bucket, Region: cosConfig.Region, Key: filename, Body: req.file.buffer, ContentType: req.file.mimetype }, (err, data) => { if (err) { console.error('COS上传失败:', err); return res.status(500).json({ success: false, error: '上传失败' }); } console.log('COS上传成功:', data); const fileUrl = 'https://' + cosConfig.Bucket + '.cos.' + cosConfig.Region + '.myqcloud.com/' + filename; res.json({ success: true, data: { url: fileUrl, name: req.file.originalname, size: req.file.size, type: req.file.mimetype, isImage: imageFormats.includes(ext) } }); }); } catch (error) { console.error('上传异常:', error); res.status(500).json({ success: false, error: '上传失败' }); } }); app.post('/api/upload/multiple', upload.array('files', 10), async (req, res) => { try { if (!req.files || req.files.length === 0) { return res.status(400).json({ success: false, error: '没有上传文件' }); } const uploadPromises = req.files.map(file => { return new Promise((resolve, reject) => { const ext = file.originalname.split('.').pop().toLowerCase(); const filename = 'uploads/' + Date.now() + '_' + Math.random().toString(36).substring(2, 8) + '.' + ext; cos.putObject({ Bucket: cosConfig.Bucket, Region: cosConfig.Region, Key: filename, Body: file.buffer, ContentType: file.mimetype }, (err, data) => { if (err) reject(err); else { const fileUrl = 'https://' + cosConfig.Bucket + '.cos.' + cosConfig.Region + '.myqcloud.com/' + filename; resolve({ url: fileUrl, name: file.originalname, size: file.size, isImage: imageFormats.includes(ext) }); } }); }); }); const results = await Promise.all(uploadPromises); res.json({ success: true, data: results }); } catch (error) { console.error('批量上传失败:', error); res.status(500).json({ success: false, error: '上传失败' }); } }); */ // ==================== 404处理 ==================== app.use((req, res) => { res.status(404).json({ success: false, message: '端点未找到', requested_url: req.originalUrl }); }); // ==================== 错误处理 ==================== app.use((err, req, res, next) => { console.error('服务器错误:', err.stack); res.status(500).json({ success: false, message: '服务器内部错误', error: process.env.NODE_ENV === 'development' ? err.message : undefined }); }); // ==================== 启动服务器 ==================== if (require.main === module) { app.listen(PORT, '0.0.0.0', () => { console.log(` 🚀 公司财务管理系统 - 最终生产后端 =========================================== 📍 服务器地址: http://0.0.0.0:${PORT} 🌐 外部访问: http://43.161.248.209:${PORT} 🔗 核心API端点: - 健康检查: /api/health - 客户管理: /api/customers - 供应商管理: /api/suppliers - 项目管理: /api/projects - 商品管理: /api/products - 采购申请: /api/purchase-requests - 库存管理: /api/inventory - 付款节点: /api/payment-nodes - 付款记录: /api/payment-records - 汇率管理: /api/exchange-rates - 预支款管理: /api/advances - 报销管理: /api/reimbursements - 财务统计: /api/finance-stats 👤 测试账号: - 用户名: admin - 密码: X123c321@ ✅ 所有API已就绪 ✅ 前端应用已集成 ✅ 数据库已连接 ✅ 等待用户访问 ⏰ 启动时间: ${new Date().toISOString()} =========================================== `); }); } module.exports = app;