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; // 中间件 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 })); // 静态文件服务 - 前端应用 app.use(express.static(path.join(__dirname, '../frontend/dist'))); // 用户相关 API // 获取用户列表 // ==================== 认证路由 ==================== const authRoutes = require('./routes/auth'); app.use('/api/auth', authRoutes); // ==================== 用户路由 ==================== const usersRoutes = require('./routes/users'); app.use('/api/users', usersRoutes); // ==================== 商品路由 ==================== const productsRoutes = require('./routes/products'); app.use('/api/products', productsRoutes); // 创建供应商收款信息表 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); } } // 创建分包商收款信息表 async function createSubcontractorPaymentInfosTable() { try { await db.query(` CREATE TABLE IF NOT EXISTS subcontractor_payment_infos ( id INTEGER PRIMARY KEY AUTOINCREMENT, subcontractor_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 (subcontractor_id) REFERENCES subcontractors(id) ON DELETE CASCADE ) `); // 创建索引 await db.query(` CREATE INDEX IF NOT EXISTS idx_subcontractor_payment_infos_subcontractor_id ON subcontractor_payment_infos(subcontractor_id) `); await db.query(` CREATE INDEX IF NOT EXISTS idx_subcontractor_payment_infos_is_primary ON subcontractor_payment_infos(is_primary) `); 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(); createSubcontractorPaymentInfosTable(); 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 ==================== // 获取商品列表 // ==================== 付款节点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: 已验证可外部访问,所有服务运行正常
⚠️ 端口5000: 安全组已开放,但可能存在网络路由问题
🎯 解决方案: 使用已验证的3000端口作为生产环境
© 2026 老挝电力公司财务管理系统 | 技术支持: OpenClaw AI Assistant
这是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;