const express = require('express'); const cors = require('cors'); const { body, param, query, validationResult } = require('express-validator'); const multer = require('multer'); const xlsx = require('xlsx'); require('dotenv').config(); const db = require('./db-sqlite'); const { verifyPassword, generateToken } = require('./utils/auth'); // 文件上传配置 const storage = multer.memoryStorage(); const upload = multer({ storage, limits: { fileSize: 10 * 1024 * 1024 } }); const imageFormats = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'svg']; const app = express(); const PORT = process.argv[2] || process.env.PORT || 3000; // 中间件 const corsOptions = { origin: process.env.CORS_ORIGIN ? process.env.CORS_ORIGIN.split(',') : ['http://localhost:5173', 'http://localhost:3001'], credentials: true, methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'], allowedHeaders: ['Content-Type', 'Authorization'] }; app.use(cors(corsOptions)); app.use(express.json()); app.use(express.urlencoded({ extended: true })); // 验证错误处理 const validate = (req, res, next) => { const errors = validationResult(req); if (!errors.isEmpty()) { return res.status(400).json({ success: false, errors: errors.array() }); } next(); }; // ==================== 健康检查 ==================== app.get('/health', (req, res) => { res.json({ status: 'healthy', timestamp: new Date().toISOString() }); }); app.get('/', (req, res) => { res.json({ success: true, message: '轻远电力老挝ERP API', version: '2.0.0', endpoints: { health: '/api/health', auth: '/api/auth', users: '/api/users', projects: '/api/projects', products: '/api/products', customers: '/api/customers', suppliers: '/api/suppliers', advances: '/api/advances', reimbursements: '/api/reimbursements', exchangeRates: '/api/exchange-rates' } }); }); // ==================== 用户认证 ==================== app.post('/api/auth/login', [ body('username').notEmpty(), body('password').notEmpty() ], validate, async (req, res) => { try { const { username, password } = req.body; const result = await db.query( 'SELECT id, username, role, name, password_hash FROM users WHERE username = ?', [username] ); if (result.rows.length === 0) { return res.status(401).json({ success: false, message: '用户名或密码错误' }); } const user = result.rows[0]; if (!user.password_hash || !verifyPassword(password, user.password_hash)) { return res.status(401).json({ success: false, message: '用户名或密码错误' }); } const token = generateToken({ id: user.id, username: user.username, role: user.role }); res.json({ success: true, data: { id: user.id, username: user.username, role: user.role, name: user.name, token } }); } catch (error) { res.status(500).json({ success: false, message: '登录失败' }); } }); // 兼容前端的登录路径 app.post('/api/users/login', [ body('username').notEmpty(), body('password').notEmpty() ], validate, async (req, res) => { try { const { username, password } = req.body; const result = await db.query( 'SELECT id, username, role, name, password_hash FROM users WHERE username = ?', [username] ); if (result.rows.length === 0) { return res.status(401).json({ user: null, token: null, message: '用户名或密码错误' }); } const user = result.rows[0]; if (!user.password_hash || !verifyPassword(password, user.password_hash)) { return res.status(401).json({ user: null, token: null, message: '用户名或密码错误' }); } const token = generateToken({ id: user.id, username: user.username, role: user.role }); res.json({ user: { id: user.id, username: user.username, role: user.role, name: user.name }, token }); } catch (error) { res.status(500).json({ user: null, token: null, message: '登录失败' }); } }); // ==================== 项目管理 ==================== app.get('/api/projects', async (req, res) => { try { const result = await db.query('SELECT * FROM projects ORDER BY created_at DESC'); res.json({ success: true, data: result.rows }); } catch (error) { res.status(500).json({ success: false, message: error.message }); } }); app.post('/api/projects', [ body('name').notEmpty(), body('code').notEmpty() ], validate, async (req, res) => { try { const { name, code, customer_id, contract_amount, start_date, end_date, description } = req.body; const result = await db.query( `INSERT INTO projects (name, code, customer_id, contract_amount, start_date, end_date, description, status) VALUES (?, ?, ?, ?, ?, ?, ?, 'active')`, [name, code, customer_id, contract_amount, start_date, end_date, description] ); // SQLite不支持RETURNING,所以需要查询刚插入的数据 const lastInsert = await db.query('SELECT * FROM projects ORDER BY id DESC LIMIT 1'); res.json({ success: true, data: lastInsert.rows[0] }); } catch (error) { res.status(500).json({ success: false, message: error.message }); } }); // ==================== 客户管理 ==================== app.get('/api/customers', async (req, res) => { try { const result = await db.query('SELECT * FROM customers ORDER BY created_at DESC'); res.json({ success: true, data: result.rows }); } catch (error) { res.status(500).json({ success: false, message: error.message }); } }); app.post('/api/customers', [ body('name').notEmpty() ], validate, async (req, res) => { try { const { name, contact, phone, email, address, type } = req.body; const result = await db.query( `INSERT INTO customers (name, contact, phone, email, address, type) VALUES (?, ?, ?, ?, ?, ?)`, [name, contact, phone, email, address, type || 'corporate'] ); // SQLite不支持RETURNING,所以需要查询刚插入的数据 const lastInsert = await db.query('SELECT * FROM customers ORDER BY id DESC LIMIT 1'); res.json({ success: true, data: lastInsert.rows[0] }); } catch (error) { res.status(500).json({ success: false, message: error.message }); } }); // ==================== 供应商管理 ==================== app.get('/api/suppliers', async (req, res) => { try { const result = await db.query('SELECT * FROM suppliers ORDER BY created_at DESC'); res.json({ success: true, data: result.rows }); } catch (error) { res.status(500).json({ success: false, message: error.message }); } }); // ==================== 产品管理 ==================== app.get('/api/products', async (req, res) => { try { // 获取所有分类信息 const categoriesResult = await db.query('SELECT * FROM categories'); const categoriesMap = new Map(); categoriesResult.rows.forEach(cat => { categoriesMap.set(cat.id, cat); }); // 获取所有商品 const result = await db.query('SELECT * FROM products ORDER BY created_at DESC'); // 转换数据结构以匹配前端期望 const products = result.rows.map((product, index) => { let category_name = product.category; let parent_category_name = null; // 根据category_id获取分类信息 if (product.category_id && categoriesMap.has(product.category_id)) { const category = categoriesMap.get(product.category_id); category_name = category.name; // 获取父分类信息 if (category.parent_id && categoriesMap.has(category.parent_id)) { const parentCategory = categoriesMap.get(category.parent_id); parent_category_name = parentCategory.name; } } return { id: product.id, name: product.name, model: product.model || product.code, // 如果model为空,使用code作为fallback category_id: product.category_id || index + 1, // 使用实际的category_id category_name: category_name, parent_category_name: parent_category_name, unit: '个', // 默认单位 quantity: 0, // 默认数量 cost_price: product.price, thumbnail: null, // 默认无缩略图 specification: product.description, // 使用description作为specification brand: null, // 默认无品牌 remark: null, // 默认无备注 source: product.source || '老挝', // 确保source字段存在 created_at: product.created_at, updated_at: product.updated_at }; }); res.json({ success: true, data: products, total: products.length }); } catch (error) { console.error('获取商品失败:', error); res.status(500).json({ success: false, message: error.message }); } }); // ==================== 预支管理 ==================== 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 `); res.json({ success: true, data: result.rows }); } catch (error) { res.status(500).json({ success: false, message: 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 `); res.json({ success: true, data: result.rows }); } catch (error) { res.status(500).json({ success: false, message: error.message }); } }); // ==================== 汇率管理 ==================== app.get('/api/exchange-rates', async (req, res) => { try { const result = await db.query(` SELECT * FROM exchange_rates WHERE is_active = true ORDER BY effective_date DESC LIMIT 10 `); res.json({ success: true, data: result.rows }); } catch (error) { res.status(500).json({ success: false, message: error.message }); } }); // ==================== 预算报价管理 ==================== app.get('/api/budget-projects', async (req, res) => { try { const result = await db.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 quotations q WHERE q.project_id = b.id) as quotations FROM budget_projects b ORDER BY b.created_at DESC `); res.json({ success: true, data: result.rows }); } catch (error) { res.status(500).json({ success: false, message: error.message }); } }); // ==================== 施工管理 ==================== 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) { res.status(500).json({ success: false, message: error.message }); } }); // ==================== 文件上传API (模拟) ==================== app.post('/api/upload/single', upload.single('file'), async (req, res) => { try { if (!req.file) return res.status(400).json({ success: false, error: '没有上传文件' }); 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; // 模拟文件上传,返回模拟的文件URL const fileUrl = 'https://example.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 results = req.files.map(file => { const ext = file.originalname.split('.').pop().toLowerCase(); const filename = 'uploads/' + Date.now() + '_' + Math.random().toString(36).substring(2, 8) + '.' + ext; const fileUrl = 'https://example.com/' + filename; return { url: fileUrl, name: file.originalname, size: file.size, isImage: imageFormats.includes(ext) }; }); res.json({ success: true, data: results }); } catch (error) { console.error('批量上传失败:', error); res.status(500).json({ success: false, error: '上传失败' }); } }); // 处理分类的函数 async function handleCategory(firstCategory, secondCategory = null) { try { // 检查一级分类是否存在 let firstCategoryId; const firstCategoryResult = await db.query( 'SELECT id FROM categories WHERE name = ? AND parent_id IS NULL', [firstCategory] ); if (firstCategoryResult.rows.length === 0) { // 创建一级分类 await db.query( 'INSERT INTO categories (name, parent_id, level, created_at, updated_at) VALUES (?, NULL, 1, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)', [firstCategory] ); // 获取刚创建的一级分类ID const newFirstCategoryResult = await db.query( 'SELECT id FROM categories WHERE name = ? AND parent_id IS NULL ORDER BY id DESC LIMIT 1', [firstCategory] ); firstCategoryId = newFirstCategoryResult.rows[0].id; } else { firstCategoryId = firstCategoryResult.rows[0].id; } // 如果有二级分类,检查并创建 if (secondCategory) { let secondCategoryId; const secondCategoryResult = await db.query( 'SELECT id FROM categories WHERE name = ? AND parent_id = ?', [secondCategory, firstCategoryId] ); if (secondCategoryResult.rows.length === 0) { // 创建二级分类 await db.query( 'INSERT INTO categories (name, parent_id, level, created_at, updated_at) VALUES (?, ?, 2, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)', [secondCategory, firstCategoryId] ); // 获取刚创建的二级分类ID const newSecondCategoryResult = await db.query( 'SELECT id FROM categories WHERE name = ? AND parent_id = ? ORDER BY id DESC LIMIT 1', [secondCategory, firstCategoryId] ); secondCategoryId = newSecondCategoryResult.rows[0].id; } else { secondCategoryId = secondCategoryResult.rows[0].id; } return secondCategoryId; } return firstCategoryId; } catch (error) { throw new Error('处理分类失败: ' + error.message); } } // ==================== 商品批量上传API ==================== app.post('/api/products/batch-import', upload.single('file'), async (req, res) => { try { console.log('收到批量导入请求'); if (!req.file) { console.error('没有上传文件'); return res.status(400).json({ success: false, error: '没有上传文件' }); } console.log('文件信息:', req.file.originalname, req.file.size); const ext = req.file.originalname.split('.').pop().toLowerCase(); if (!['xlsx', 'xls'].includes(ext)) { console.error('文件类型不支持:', ext); return res.status(400).json({ success: false, error: '只支持Excel文件' }); } // 解析Excel文件 console.log('开始解析Excel文件'); const workbook = xlsx.read(req.file.buffer, { type: 'buffer' }); const sheetName = workbook.SheetNames[0]; console.log('工作表名称:', sheetName); const worksheet = workbook.Sheets[sheetName]; const data = xlsx.utils.sheet_to_json(worksheet); console.log('解析成功,数据条数:', data.length); if (data.length === 0) { console.error('Excel文件为空'); return res.status(400).json({ success: false, error: 'Excel文件为空' }); } console.log('前5条数据:', data.slice(0, 5)); // 批量导入商品 let successCount = 0; let errorCount = 0; const errors = []; for (const item of data) { try { // 验证必填字段 if (!item['商品名称']) { throw new Error('商品名称不能为空'); } if (!item['一级分类']) { throw new Error('一级分类不能为空'); } // 处理分类 const categoryId = await handleCategory(item['一级分类'], item['二级分类']); // 处理来源字段,默认为老挝 const source = item['来源'] || '老挝'; // 生成唯一的code const baseCode = item['型号'] || item['商品名称']; const uniqueCode = baseCode + '_' + Date.now() + '_' + Math.floor(Math.random() * 1000); // 确定分类名称 let categoryName = item['一级分类']; if (item['二级分类']) { categoryName = item['二级分类']; } // 插入商品 await db.query( `INSERT INTO products (name, code, model, category_id, category, price, description, source, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`, [ item['商品名称'], uniqueCode, item['型号'], categoryId, categoryName, item['成本单价'] || 0, item['规格参数'] || item['备注'] || '', source ] ); successCount++; } catch (error) { errorCount++; console.error('导入商品失败:', item['商品名称'], error.message); errors.push({ item: item['商品名称'], error: error.message }); } } res.json({ success: true, message: `导入完成,成功 ${successCount} 条,失败 ${errorCount} 条`, data: { successCount, errorCount, errors } }); } catch (error) { console.error('批量导入失败:', error); res.status(500).json({ success: false, error: '导入失败: ' + error.message }); } }); // ==================== 下载商品模板 ==================== app.get('/api/products/template', async (req, res) => { try { // 创建模板数据 const templateData = [ { '商品名称': '示例商品1', '型号': 'MODEL001', '一级分类': '电器', '二级分类': '家用电器', '单位': '个', '成本单价': 100.00, '品牌': '品牌A', '规格参数': '220V', '来源': '老挝', '备注': '示例商品' }, { '商品名称': '示例商品2', '型号': 'MODEL002', '一级分类': '工具', '二级分类': '手动工具', '单位': '把', '成本单价': 50.00, '品牌': '品牌B', '规格参数': '标准型', '来源': '中国', '备注': '示例商品' } ]; // 创建工作簿和工作表 const workbook = xlsx.utils.book_new(); const worksheet = xlsx.utils.json_to_sheet(templateData); xlsx.utils.book_append_sheet(workbook, worksheet, '商品模板'); // 设置响应头 res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'); res.setHeader('Content-Disposition', 'attachment; filename=' + encodeURIComponent('商品导入模板.xlsx')); // 发送文件 const buffer = xlsx.write(workbook, { bookType: 'xlsx', type: 'buffer' }); res.send(buffer); } catch (error) { console.error('下载模板失败:', error); res.status(500).json({ success: false, error: '下载模板失败' }); } }); // ==================== 付款申请 ==================== app.get('/api/payment-requests', async (req, res) => { try { const result = await db.query('SELECT * FROM payment_requests ORDER BY created_at DESC'); res.json({ success: true, data: result.rows }); } catch (error) { res.status(500).json({ success: false, message: error.message }); } }); app.post('/api/payment-requests', async (req, res) => { try { const { applicant, payee, bank_account, bank_name, amount, amount_cny, currency, payment_date, reason, detail_items, attachments } = req.body; const requestCode = 'PAY' + new Date().toISOString().slice(0, 10).replace(/-/g, '') + Math.floor(1000 + Math.random() * 9000); await db.query( 'INSERT INTO payment_requests (request_code, applicant, payee, bank_account, bank_name, amount, amount_cny, currency, payment_date, reason, detail_items, attachments, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', [requestCode, applicant, payee, bank_account, bank_name, amount, amount_cny, currency, payment_date, reason, JSON.stringify(detail_items), JSON.stringify(attachments), 'pending'] ); 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) { res.status(500).json({ success: false, message: error.message }); } }); app.put('/api/payment-requests/:id', async (req, res) => { try { const { id } = req.params; const { applicant, payee, bank_account, bank_name, amount, amount_cny, currency, payment_date, reason, detail_items, attachments } = req.body; await db.query( 'UPDATE payment_requests SET applicant = ?, payee = ?, bank_account = ?, bank_name = ?, amount = ?, amount_cny = ?, currency = ?, payment_date = ?, reason = ?, detail_items = ?, attachments = ? WHERE id = ?', [applicant, payee, bank_account, bank_name, amount, amount_cny, currency, payment_date, reason, JSON.stringify(detail_items), JSON.stringify(attachments), id] ); const updatedResult = await db.query('SELECT * FROM payment_requests WHERE id = ?', [id]); res.json({ success: true, data: updatedResult.rows[0] }); } catch (error) { res.status(500).json({ success: false, message: error.message }); } }); app.delete('/api/payment-requests/:id', async (req, res) => { try { const { id } = req.params; await db.query('DELETE FROM payment_requests WHERE id = ?', [id]); res.json({ success: true, message: '删除成功' }); } catch (error) { res.status(500).json({ success: false, message: error.message }); } }); app.post('/api/payment-requests/:id/withdraw', async (req, res) => { try { const { id } = req.params; await db.query('UPDATE payment_requests SET status = ? WHERE id = ?', ['withdrawn', id]); res.json({ success: true, message: '已撤回' }); } catch (error) { res.status(500).json({ success: false, message: error.message }); } }); // ==================== 核销申请 ==================== app.get('/api/verifications', async (req, res) => { try { const result = await db.query('SELECT * FROM verifications ORDER BY created_at DESC'); res.json({ success: true, data: result.rows }); } catch (error) { res.status(500).json({ success: false, message: error.message }); } }); app.post('/api/verifications', async (req, res) => { try { const { applicant, advance_code, advance_amount, amount, amount_cny, currency, verification_date, reason, detail_items, attachments } = req.body; const verificationCode = 'VER' + new Date().toISOString().slice(0, 10).replace(/-/g, '') + Math.floor(1000 + Math.random() * 9000); await db.query( 'INSERT INTO verifications (verification_code, applicant, advance_code, advance_amount, amount, amount_cny, currency, verification_date, reason, detail_items, attachments, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', [verificationCode, applicant, advance_code, advance_amount, amount, amount_cny, currency, verification_date, reason, JSON.stringify(detail_items), JSON.stringify(attachments), 'pending'] ); const lastInsert = await db.query('SELECT * FROM verifications ORDER BY id DESC LIMIT 1'); res.json({ success: true, data: lastInsert.rows[0] }); } catch (error) { res.status(500).json({ success: false, message: error.message }); } }); app.put('/api/verifications/:id', async (req, res) => { try { const { id } = req.params; const { applicant, advance_code, advance_amount, amount, amount_cny, currency, verification_date, reason, detail_items, attachments } = req.body; await db.query( 'UPDATE verifications SET applicant = ?, advance_code = ?, advance_amount = ?, amount = ?, amount_cny = ?, currency = ?, verification_date = ?, reason = ?, detail_items = ?, attachments = ? WHERE id = ?', [applicant, advance_code, advance_amount, amount, amount_cny, currency, verification_date, reason, JSON.stringify(detail_items), JSON.stringify(attachments), id] ); const updatedResult = await db.query('SELECT * FROM verifications WHERE id = ?', [id]); res.json({ success: true, data: updatedResult.rows[0] }); } catch (error) { res.status(500).json({ success: false, message: error.message }); } }); app.delete('/api/verifications/:id', async (req, res) => { try { const { id } = req.params; await db.query('DELETE FROM verifications WHERE id = ?', [id]); res.json({ success: true, message: '删除成功' }); } catch (error) { res.status(500).json({ success: false, message: error.message }); } }); app.post('/api/verifications/:id/withdraw', async (req, res) => { try { const { id } = req.params; await db.query('UPDATE verifications SET status = ? WHERE id = ?', ['withdrawn', id]); res.json({ success: true, message: '已撤回' }); } catch (error) { res.status(500).json({ success: false, message: error.message }); } }); // ==================== 分类管理API ==================== app.get('/api/categories', async (req, res) => { try { const result = await db.query('SELECT * FROM categories ORDER BY level, parent_id, name'); res.json({ success: true, data: result.rows }); } catch (error) { res.status(500).json({ success: false, message: error.message }); } }); app.post('/api/categories', async (req, res) => { try { const { name, parent_id, level } = req.body; await db.query( 'INSERT INTO categories (name, parent_id, level, created_at, updated_at) VALUES (?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)', [name, parent_id, level] ); res.json({ success: true, message: '分类创建成功' }); } catch (error) { res.status(500).json({ success: false, message: error.message }); } }); app.put('/api/categories/:id', async (req, res) => { try { const { id } = req.params; const { name, parent_id, level } = req.body; await db.query( 'UPDATE categories SET name = ?, parent_id = ?, level = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?', [name, parent_id, level, id] ); res.json({ success: true, message: '分类更新成功' }); } catch (error) { res.status(500).json({ success: false, message: error.message }); } }); app.delete('/api/categories/:id', async (req, res) => { try { const { id } = req.params; // 先检查是否有子分类 const children = await db.query('SELECT id FROM categories WHERE parent_id = ?', [id]); if (children.rows.length > 0) { return res.status(400).json({ success: false, message: '该分类下有子分类,无法删除' }); } // 再检查是否有商品使用该分类 const products = await db.query('SELECT id FROM products WHERE category_id = ?', [id]); if (products.rows.length > 0) { return res.status(400).json({ success: false, message: '该分类下有商品,无法删除' }); } await db.query('DELETE FROM categories WHERE id = ?', [id]); res.json({ success: true, message: '分类删除成功' }); } catch (error) { res.status(500).json({ success: false, message: error.message }); } }); // ==================== 汇率API ==================== app.get('/api/exchange-rates/latest', async (req, res) => { try { const result = await db.query('SELECT * FROM exchange_rates WHERE is_active = true ORDER BY effective_date DESC LIMIT 10'); const rates = {}; result.rows.forEach(row => { rates[row.from_currency + '_' + row.to_currency] = row.rate; }); res.json({ success: true, data: rates }); } catch (error) { res.status(500).json({ success: false, message: error.message }); } }); app.post('/api/exchange-rates', async (req, res) => { try { const { pair_key, rate, effective_date } = req.body; // 解析货币对 const [from_currency, to_currency] = pair_key.split('_'); // 先将所有同货币对的汇率设置为非激活 await db.query( 'UPDATE exchange_rates SET is_active = 0 WHERE from_currency = ? AND to_currency = ?', [from_currency, to_currency] ); // 插入新的汇率记录 await db.query( 'INSERT INTO exchange_rates (from_currency, to_currency, rate, effective_date, is_active) VALUES (?, ?, ?, ?, ?)', [from_currency, to_currency, rate, effective_date, 1] ); res.json({ success: true, message: '汇率保存成功' }); } catch (error) { console.error('保存汇率失败:', error); res.status(500).json({ success: false, message: 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] ); res.json({ success: true, data: result.rows }); } catch (error) { res.status(500).json({ success: false, message: error.message }); } }); // 启动服务器 app.listen(PORT, '0.0.0.0', () => { console.log(`轻远电力老挝ERP API运行在端口 ${PORT}`); }); // ==================== 项目详情 ==================== app.get('/api/projects/:id', async (req, res) => { try { const { id } = req.params; const result = await db.query('SELECT * FROM projects 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) { res.status(500).json({ success: false, message: error.message }); } }); // ==================== 更新项目 ==================== app.put('/api/projects/:id', [ param('id').notEmpty() ], validate, async (req, res) => { try { const { id } = req.params; const { name, manager_id, location, start_date, end_date, description, status, contract_amount } = req.body; // SQLite不支持COALESCE和NOW()的组合使用,需要使用不同的方法 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] ); // 查询更新后的数据 const updatedResult = await db.query('SELECT * FROM projects WHERE id = ?', [id]); res.json({ success: true, data: updatedResult.rows[0] }); } catch (error) { res.status(500).json({ success: false, message: error.message }); } }); // ==================== 删除项目 ==================== app.delete('/api/projects/:id', async (req, res) => { try { const { id } = req.params; await db.query('DELETE FROM projects WHERE id = ?', [id]); res.json({ success: true, message: '项目已删除' }); } catch (error) { res.status(500).json({ success: false, message: error.message }); } }); // ==================== 预算项目管理API ==================== app.post('/api/budget-projects', 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; 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, days_in_status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'negotiating', 0)`, [name, customer_id, manager_id, location, survey_date, intermediary, intermediary_fee_type, intermediary_fee_value, customer_requirements, project_overview, attachments, survey_photos] ); res.json({ success: true, message: '预算项目创建成功' }); } catch (error) { res.status(500).json({ success: false, message: error.message }); } }); app.put('/api/budget-projects/:id/sign', async (req, res) => { try { const { id } = req.params; await db.query('UPDATE budget_projects SET status = ? WHERE id = ?', ['signed', id]); res.json({ success: true, message: '标记签约成功' }); } catch (error) { res.status(500).json({ success: false, message: error.message }); } }); app.put('/api/budget-projects/:id/unsigned', async (req, res) => { try { const { id } = req.params; await db.query('UPDATE budget_projects SET status = ? WHERE id = ?', ['unsigned', id]); res.json({ success: true, message: '标记未签约成功' }); } catch (error) { res.status(500).json({ success: false, message: error.message }); } }); // ==================== 报价管理API ==================== app.post('/api/budget-projects/:projectId/quotations', async (req, res) => { try { const { projectId } = req.params; const { version, quotation_date, amount, currency, status, file_url, remark } = req.body; await db.query( `INSERT INTO quotations (project_id, version, quotation_date, amount, currency, status, file_url, remark) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [projectId, version, quotation_date, amount, currency, status, file_url, remark] ); res.json({ success: true, message: '报价版本创建成功' }); } catch (error) { res.status(500).json({ success: false, message: error.message }); } }); app.delete('/api/budget-projects/:projectId/quotations/:quotationId', async (req, res) => { try { const { quotationId } = req.params; await db.query('DELETE FROM quotations WHERE id = ?', [quotationId]); res.json({ success: true, message: '报价版本删除成功' }); } catch (error) { res.status(500).json({ success: false, message: error.message }); } }); // ==================== 项目合同API ==================== app.post('/api/projects/:projectId/contracts', async (req, res) => { try { const { projectId } = req.params; const { contract_code, contract_amount, currency, settlement_method, contract_period, start_date, end_date, warranty_deposit_percentage, warranty_period, contract_file } = req.body; 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) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [projectId, contract_code, contract_amount, currency, settlement_method, contract_period, start_date, end_date, warranty_deposit_percentage, warranty_period, contract_file] ); res.json({ success: true, message: '合同创建成功' }); } catch (error) { res.status(500).json({ success: false, message: error.message }); } }); app.get('/api/projects/:projectId/contracts', async (req, res) => { try { const { projectId } = req.params; const result = await db.query('SELECT * FROM project_contracts WHERE project_id = ?', [projectId]); res.json({ success: true, data: result.rows }); } catch (error) { res.status(500).json({ success: false, message: error.message }); } }); // ==================== 分包管理API ==================== app.post('/api/projects/:projectId/subcontracts', async (req, res) => { try { const { projectId } = req.params; const { subcontractor_name, contract_amount, start_date, end_date, contract_file } = req.body; await db.query( `INSERT INTO subcontracts (project_id, subcontractor_name, contract_amount, start_date, end_date, contract_file) VALUES (?, ?, ?, ?, ?, ?)`, [projectId, subcontractor_name, contract_amount, start_date, end_date, contract_file] ); res.json({ success: true, message: '分包合同创建成功' }); } catch (error) { res.status(500).json({ success: false, message: error.message }); } }); app.get('/api/projects/:projectId/subcontracts', async (req, res) => { try { const { projectId } = req.params; const result = await db.query('SELECT * FROM subcontracts WHERE project_id = ?', [projectId]); res.json({ success: true, data: result.rows }); } catch (error) { res.status(500).json({ success: false, message: error.message }); } }); // ==================== 材料管理API ==================== app.post('/api/projects/:projectId/materials', async (req, res) => { try { const { projectId } = req.params; const { product_id, product_name, unit, budget_quantity, purchase_quantity, used_quantity, average_price, total_amount } = req.body; await db.query( `INSERT INTO project_materials (project_id, product_id, product_name, unit, budget_quantity, purchase_quantity, used_quantity, average_price, total_amount) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [projectId, product_id, product_name, unit, budget_quantity, purchase_quantity, used_quantity, average_price, total_amount] ); res.json({ success: true, message: '材料添加成功' }); } catch (error) { res.status(500).json({ success: false, message: error.message }); } }); app.get('/api/projects/:projectId/materials', async (req, res) => { try { const { projectId } = req.params; const result = await db.query('SELECT * FROM project_materials WHERE project_id = ?', [projectId]); res.json({ success: true, data: result.rows }); } catch (error) { res.status(500).json({ success: false, message: error.message }); } }); // ==================== 施工节点API ==================== app.post('/api/projects/:projectId/milestones', async (req, res) => { try { const { projectId } = req.params; const { milestone_name, percentage, amount, completion_progress, status, expected_date, voucher } = req.body; await db.query( `INSERT INTO project_milestones (project_id, milestone_name, percentage, amount, completion_progress, status, expected_date, voucher) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [projectId, milestone_name, percentage, amount, completion_progress, status, expected_date, voucher] ); res.json({ success: true, message: '节点添加成功' }); } catch (error) { res.status(500).json({ success: false, message: error.message }); } }); app.get('/api/projects/:projectId/milestones', async (req, res) => { try { const { projectId } = req.params; const result = await db.query('SELECT * FROM project_milestones WHERE project_id = ?', [projectId]); res.json({ success: true, data: result.rows }); } catch (error) { res.status(500).json({ success: false, message: error.message }); } }); // ==================== 财务信息API ==================== app.post('/api/projects/:projectId/finances', async (req, res) => { try { const { projectId } = req.params; const { payment_type, amount, currency, payment_date, status, description } = req.body; await db.query( `INSERT INTO project_finances (project_id, payment_type, amount, currency, payment_date, status, description) VALUES (?, ?, ?, ?, ?, ?, ?)`, [projectId, payment_type, amount, currency, payment_date, status, description] ); res.json({ success: true, message: '财务记录添加成功' }); } catch (error) { res.status(500).json({ success: false, message: error.message }); } }); app.get('/api/projects/:projectId/finances', async (req, res) => { try { const { projectId } = req.params; const result = await db.query('SELECT * FROM project_finances WHERE project_id = ?', [projectId]); res.json({ success: true, data: result.rows }); } catch (error) { res.status(500).json({ success: false, message: error.message }); } }); // ==================== 质保金API ==================== app.post('/api/projects/:projectId/warranty-deposits', async (req, res) => { try { const { projectId } = req.params; const { amount, currency, warranty_period, start_date, end_date } = req.body; await db.query( `INSERT INTO warranty_deposits (project_id, amount, currency, warranty_period, start_date, end_date) VALUES (?, ?, ?, ?, ?, ?)`, [projectId, amount, currency, warranty_period, start_date, end_date] ); res.json({ success: true, message: '质保金设置成功' }); } catch (error) { res.status(500).json({ success: false, message: error.message }); } }); app.get('/api/projects/:projectId/warranty-deposits', async (req, res) => { try { const { projectId } = req.params; const result = await db.query('SELECT * FROM warranty_deposits WHERE project_id = ?', [projectId]); res.json({ success: true, data: result.rows }); } catch (error) { res.status(500).json({ success: false, message: error.message }); } }); // ==================== 施工日志API ==================== app.post('/api/projects/:projectId/construction-logs', async (req, res) => { try { const { projectId } = req.params; const { log_date, weather, work_content, photos } = req.body; await db.query( `INSERT INTO construction_logs (project_id, log_date, weather, work_content, photos) VALUES (?, ?, ?, ?, ?)`, [projectId, log_date, weather, work_content, photos] ); res.json({ success: true, message: '施工日志添加成功' }); } catch (error) { res.status(500).json({ success: false, message: error.message }); } }); app.get('/api/projects/:projectId/construction-logs', async (req, res) => { try { const { projectId } = req.params; const result = await db.query('SELECT * FROM construction_logs WHERE project_id = ? ORDER BY log_date DESC', [projectId]); res.json({ success: true, data: result.rows }); } catch (error) { res.status(500).json({ success: false, message: error.message }); } }); app.delete('/api/construction-logs/:logId', async (req, res) => { try { const { logId } = req.params; await db.query('DELETE FROM construction_logs WHERE id = ?', [logId]); res.json({ success: true, message: '施工日志删除成功' }); } catch (error) { res.status(500).json({ success: false, message: 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 } = req.body; const user_id = 1; // 临时使用admin用户 const result = await db.query( 'INSERT INTO advances (user_id, project_id, amount, reason, status) VALUES (?, ?, ?, ?, ?)', [user_id, project_id, amount, reason, 'pending'] ); // SQLite不支持RETURNING,所以需要查询刚插入的数据 const lastInsert = await db.query('SELECT * FROM advances ORDER BY id DESC LIMIT 1'); res.json({ success: true, data: lastInsert.rows[0] }); } catch (error) { res.status(500).json({ success: false, message: error.message }); } }); // ==================== 创建报销申请 ==================== app.post('/api/reimbursements', [ body('amount').isFloat({ min: 0.01 }), body('type').notEmpty() ], validate, async (req, res) => { try { const { amount, type, description, project_id } = req.body; const user_id = 1; // 临时使用admin用户 const result = await db.query( 'INSERT INTO reimbursements (user_id, project_id, amount, type, description, status) VALUES (?, ?, ?, ?, ?, ?)', [user_id, project_id, amount, type, description, 'pending'] ); // 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) { res.status(500).json({ success: false, message: error.message }); } }); console.log('轻远电力老挝ERP API服务已启动 - 完整版');