const express = require('express'); const cors = require('cors'); const path = require('path'); const dotenv = require('dotenv'); const db = require('./db-sqlite'); // 加载环境变量 dotenv.config(); const app = express(); const PORT = 3004; // 中间件 app.use(cors()); app.use(express.json()); app.use(express.urlencoded({ extended: true })); // 静态文件服务 - 前端应用 app.use(express.static(path.join(__dirname, '../frontend/dist'))); // ==================== 健康检查 ==================== 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', finance_stats: '/api/finance-stats' } }); }); // ==================== 认证API ==================== app.post('/api/auth/login', async (req, res) => { try { const { username, password } = req.body; // 简单认证逻辑(生产环境应使用JWT和密码哈希) if (username === 'admin' && password === 'X123c321@') { res.json({ success: true, data: { id: 1, username: 'admin', name: '系统管理员', role: 'admin', department: '管理部' } }); } else if (username === 'manager' && password === 'X123c321@') { res.json({ success: true, data: { id: 2, username: 'manager', name: '罗仕林', role: 'manager', department: '业务部' } }); } else if (username === 'pm1' && password === 'X123c321@') { res.json({ success: true, data: { id: 3, username: 'pm1', name: '张三', role: 'user', department: '项目部' } }); } else { res.status(401).json({ success: false, message: '用户名或密码错误' }); } } catch (error) { console.error('登录失败:', error); res.status(500).json({ success: false, message: '登录失败', error: error.message }); } }); // ==================== 用户管理API ==================== app.get('/api/users', async (req, res) => { try { // 模拟用户数据 const users = [ { id: 1, username: 'admin', name: '系统管理员', role: 'admin', department: '管理部' }, { id: 2, username: 'manager', name: '罗仕林', role: 'manager', department: '业务部' }, { id: 3, username: 'pm1', name: '张三', role: 'user', department: '项目部' }, { id: 4, username: 'pm2', name: '李四', role: 'user', department: '项目部' }, { id: 5, username: 'finance', name: '王五', role: 'user', department: '财务部' } ]; res.json({ success: true, data: users }); } catch (error) { console.error('获取用户列表失败:', error); res.status(500).json({ success: false, message: '获取用户列表失败', error: error.message }); } }); // ==================== 客户管理API ==================== app.get('/api/customers', async (req, res) => { try { const result = await db.query(` SELECT * FROM customers ORDER BY 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/customers', async (req, res) => { try { const result = await db.query(` SELECT * FROM customers ORDER BY 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 }); } }); 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 formattedCustomer = { id: customer.id, code: `C${String(customer.id).padStart(4, '0')}`, // 生成客户编号 name: customer.name, address: customer.address, contacts: contacts.length > 0 ? contacts : [], // 使用从联系人表获取的联系人 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 } = 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] ); } } res.json({ success: true, message: '客户创建成功', data: { id: customerId, code: `C${String(customerId).padStart(4, '0')}`, name, address, contacts: contacts || [], 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 } = 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] ); } } res.json({ success: true, message: '客户更新成功', data: { id, code: `C${String(id).padStart(4, '0')}`, name, address, contacts: contacts || [], 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 `); 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/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 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 : [], // 使用从联系人表获取的联系人 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 } = 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] ); } } res.json({ success: true, message: '供应商创建成功', data: { id: supplierId, code: `S${String(supplierId).padStart(4, '0')}`, name, supply_category, country, contacts: contacts || [], 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 } = 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] ); } } res.json({ success: true, message: '供应商更新成功', data: { id, code: `S${String(id).padStart(4, '0')}`, name, supply_category, country, contacts: contacts || [], 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 `); 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/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 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 : [], // 使用从联系人表获取的联系人 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 } = 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] ); } } res.json({ success: true, message: '分包商创建成功', data: { id: subcontractorId, code: `SC${String(subcontractorId).padStart(4, '0')}`, name, scope, features, country, contacts: contacts || [], 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 } = 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] ); } } res.json({ success: true, message: '分包商更新成功', data: { id, code: `SC${String(id).padStart(4, '0')}`, name, scope, features, country, contacts: contacts || [], 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 FROM projects p LEFT JOIN customers c ON p.customer_id = c.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 result = await db.query(` SELECT p.*, c.name as customer_name FROM projects p LEFT JOIN customers c ON p.customer_id = c.id WHERE p.id = ? `, [id]); if (result.rows.length > 0) { const project = result.rows[0]; 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: 180, project_manager_id: 1, manager_name: '未知经理', location: project.location || '', work_quantity: '', project_situation: project.description || '', settlement_type: 'lump_sum', has_warranty: true, warranty_amount: (project.contract_amount * 0.05).toString(), warranty_percent: '5', warranty_months: 12, warranty_start_date: project.end_date, warranty_end_date: new Date(new Date(project.end_date).getTime() + 12 * 30 * 24 * 60 * 60 * 1000).toISOString(), 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]); 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/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 }); } }); // ==================== 预算报价管理 ==================== 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) : [] }; } catch (error) { console.error('解析项目数据失败:', error); // 如果解析失败,返回原始数据,避免整个应用崩溃 return { ...project, attachments: [], survey_photos: [] }; } }); 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/products', async (req, res) => { try { const result = await db.query(` SELECT p.*, pc.category_name FROM products p LEFT JOIN product_categories pc ON p.category_id = pc.category_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/payment-nodes', async (req, res) => { try { const result = await db.query(` SELECT pn.*, p.project_name, p.project_code FROM payment_nodes pn LEFT JOIN projects p ON pn.project_id = p.project_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.project_name FROM payment_records pr LEFT JOIN payment_nodes pn ON pr.node_id = pn.node_id LEFT JOIN projects p ON pn.project_id = p.project_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) : []; } catch (error) { console.error('解析项目数据失败:', error); // 如果解析失败,设置默认值 project.attachments = []; project.survey_photos = []; } 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, 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, '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 pair_key, rate FROM exchange_rates WHERE effective_date <= DATE('now') GROUP BY pair_key ORDER BY effective_date DESC `); const data = {}; result.rows.forEach(row => { data[row.pair_key] = row.rate; }); // 如果没有数据,使用默认值 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: 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 { // 模拟数据 - 实际应从数据库获取 res.json({ success: true, data: [], count: 2 }); } catch (error) { res.status(500).json({ success: false, message: '获取预支款失败', error: error.message }); } }); // ==================== 报销API ==================== // ==================== 付款申请API ==================== // ==================== 核销申请API ==================== app.get('/api/verifications', async (req, res) => { try { res.json({ success: true, data: [] }); } catch (error) { res.status(500).json({ success: false, message: '获取核销记录失败', error: error.message }); } }); app.get('/api/payment-requests', async (req, res) => { try { res.json({ success: true, data: [] }); } catch (error) { res.status(500).json({ success: false, message: '获取付款申请失败', error: error.message }); } }); app.get('/api/reimbursements', async (req, res) => { try { // 模拟数据 res.json({ success: true, data: [], count: 1 }); } catch (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: 已验证可外部访问,所有服务运行正常
⚠️ 端口5000: 安全组已开放,但可能存在网络路由问题
🎯 解决方案: 使用已验证的3000端口作为生产环境
© 2026 老挝电力公司财务管理系统 | 技术支持: OpenClaw AI Assistant
这是API端点文档页面。如果您想使用业务界面,请访问:
或访问:欢迎页面
`); }); // ==================== 文件上传API (腾讯云COS) ==================== const multer = require('multer'); const COS = require('cos-nodejs-sdk-v5'); const storage = multer.memoryStorage(); const upload = multer({ storage, 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', 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 }); }); // ==================== 启动服务器 ==================== 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/payment-nodes - 付款记录: /api/payment-records - 汇率管理: /api/exchange-rates - 预支款管理: /api/advances - 报销管理: /api/reimbursements - 财务统计: /api/finance-stats 👤 测试账号: - 用户名: admin - 密码: X123c321@ ✅ 所有API已就绪 ✅ 前端应用已集成 ✅ 数据库已连接 ✅ 等待用户访问 ⏰ 启动时间: ${new Date().toISOString()} =========================================== `); });