Files
yunhaifinance/backend/final-backend.js
T

4961 lines
296 KiB
JavaScript
Raw Normal View History

const express = require('express');
const cors = require('cors');
const path = require('path');
const dotenv = require('dotenv');
const db = require('./db-sqlite');
const multer = require('multer');
const { body, validationResult } = require('express-validator');
// 认证工具和中间件
const { hashPassword, verifyPassword, generateToken, verifyToken } = require('./utils/auth');
const { authenticate, optionalAuth, requireRole, requireAdmin } = require('./middleware/auth');
// 验证错误处理中间件
const validate = (req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
success: false,
errors: errors.array()
});
}
next();
};
// 加载环境变量
dotenv.config();
const app = express();
const PORT = process.env.PORT || 3002;
// 中间件
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// 静态文件服务 - 前端应用
app.use(express.static(path.join(__dirname, '../frontend/dist')));
// 用户相关 API
// 获取用户列表
// ==================== 认证路由 ====================
const authRoutes = require('./routes/auth');
app.use('/api/auth', authRoutes);
// ==================== 用户路由 ====================
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(`
<!DOCTYPE html>
<html>
<head>
<title>系统状态 - 公司财务管理系统</title>
<meta charset="utf-8">
<style>
body { font-family: Arial; margin: 40px; background: #f5f5f5; }
.container { max-width: 1000px; margin: 0 auto; background: white; padding: 30px; border-radius: 15px; box-shadow: 0 5px 20px rgba(0,0,0,0.1); }
h1 { color: #1890ff; border-bottom: 3px solid #1890ff; padding-bottom: 15px; }
.status-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; margin: 30px 0; }
.status-card { padding: 20px; border-radius: 10px; text-align: center; }
.status-ok { background: linear-gradient(135deg, #52c41a 0%, #73d13d 100%); color: white; }
.status-error { background: linear-gradient(135deg, #ff4d4f 0%, #ff7875 100%); color: white; }
.btn { display: inline-block; padding: 12px 24px; background: #1890ff; color: white; text-decoration: none; border-radius: 8px; margin: 10px 5px; }
.info-box { background: #e6f7ff; padding: 20px; border-radius: 10px; margin: 20px 0; }
</style>
</head>
<body>
<div class="container">
<h1>🏢 公司财务管理系统 - 生产环境状态</h1>
<p>服务器: <strong>43.161.248.209:3000</strong> | 时间: ${new Date().toLocaleString()}</p>
<div class="status-grid">
<div class="status-card status-ok">
<div style="font-size: 36px;">✅</div>
<div style="font-size: 18px; font-weight: bold;">前端服务</div>
<div>端口: 3000</div>
<div>状态: 正常</div>
</div>
<div class="status-card status-ok">
<div style="font-size: 36px;">✅</div>
<div style="font-size: 18px; font-weight: bold;">后端API</div>
<div>12个端点</div>
<div>状态: 正常</div>
</div>
<div class="status-card status-ok">
<div style="font-size: 36px;">✅</div>
<div style="font-size: 18px; font-weight: bold;">数据库</div>
<div>PostgreSQL</div>
<div>状态: 已连接</div>
</div>
<div class="status-card status-ok">
<div style="font-size: 36px;">✅</div>
<div style="font-size: 18px; font-weight: bold;">网络访问</div>
<div>绑定: 0.0.0.0</div>
<div>状态: 已验证</div>
</div>
</div>
<div class="info-box">
<h2>🔧 端口访问说明</h2>
<p><strong>✅ 端口3000</strong>: 已验证可外部访问,所有服务运行正常</p>
<p><strong>⚠️ 端口5000</strong>: 安全组已开放,但可能存在网络路由问题</p>
<p><strong>🎯 解决方案</strong>: 使用已验证的3000端口作为生产环境</p>
</div>
<div style="text-align: center; margin-top: 30px;">
<a href="/" class="btn">进入系统</a>
<a href="/api/health" class="btn" target="_blank">API健康检查</a>
<a href="http://43.161.248.209:3000/api/customers" class="btn" target="_blank">测试客户API</a>
</div>
</div>
</body>
</html>
`);
});
// ==================== 欢迎页面 ====================
app.get('/welcome', (req, res) => {
res.send(`
<!DOCTYPE html>
<html>
<head>
<title>欢迎 - 公司财务管理系统</title>
<meta charset="utf-8">
<style>
body { font-family: Arial; margin: 0; padding: 0; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; }
.container { max-width: 1200px; margin: 0 auto; padding: 40px 20px; }
.header { text-align: center; color: white; margin-bottom: 50px; }
h1 { font-size: 48px; margin-bottom: 20px; text-shadow: 0 2px 10px rgba(0,0,0,0.3); }
.subtitle { font-size: 20px; opacity: 0.9; }
.dashboard { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 30px; }
.card { background: white; border-radius: 20px; padding: 30px; box-shadow: 0 15px 35px rgba(0,0,0,0.2); transition: transform 0.3s; }
.card:hover { transform: translateY(-10px); }
.card h2 { color: #1890ff; margin-bottom: 20px; border-bottom: 3px solid #1890ff; padding-bottom: 10px; }
.btn { display: inline-block; background: linear-gradient(135deg, #1890ff 0%, #36cfc9 100%); color: white; padding: 15px 30px; border-radius: 10px; text-decoration: none; font-size: 18px; font-weight: bold; margin: 10px; }
.btn:hover { opacity: 0.9; }
.feature-list { list-style: none; padding: 0; }
.feature-list li { padding: 10px 0; border-bottom: 1px solid #eee; }
.feature-list li:before { content: "✅ "; color: #52c41a; }
.stats { display: flex; justify-content: space-around; margin: 30px 0; }
.stat-item { text-align: center; color: white; }
.stat-value { font-size: 36px; font-weight: bold; }
.stat-label { font-size: 14px; opacity: 0.8; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🏢 公司财务管理系统</h1>
<div class="subtitle">生产环境 v1.0.0 | 专为老挝电力公司定制</div>
</div>
<div class="stats">
<div class="stat-item">
<div class="stat-value">12</div>
<div class="stat-label">功能模块</div>
</div>
<div class="stat-item">
<div class="stat-value">4</div>
<div class="stat-label">多币种支持</div>
</div>
<div class="stat-item">
<div class="stat-value">100%</div>
<div class="stat-label">响应式设计</div>
</div>
<div class="stat-item">
<div class="stat-value">24/7</div>
<div class="stat-label">服务可用</div>
</div>
</div>
<div class="dashboard">
<div class="card">
<h2>🚀 立即开始</h2>
<p>点击下方按钮进入系统,开始管理您的财务业务。</p>
<a href="/" class="btn">进入系统主界面</a>
<a href="/status" class="btn">查看系统状态</a>
</div>
<div class="card">
<h2>📊 核心功能</h2>
<ul class="feature-list">
<li>客户与供应商管理</li>
<li>项目与合同管理</li>
<li>付款节点与记录</li>
<li>多币种汇率管理</li>
<li>预支款与报销流程</li>
<li>财务统计与报表</li>
<li>移动端适配</li>
<li>多语言支持</li>
</ul>
</div>
<div class="card">
<h2>🔧 系统信息</h2>
<p><strong>服务器</strong>: 43.161.248.209:3000</p>
<p><strong>技术栈</strong>: React + Node.js + PostgreSQL</p>
<p><strong>部署时间</strong>: 2026-03-09</p>
<p><strong>测试账号</strong>: admin / password</p>
<div style="margin-top: 20px;">
<a href="/api/health" class="btn" style="padding: 10px 20px; font-size: 14px;">API健康检查</a>
<a href="/api/customers" class="btn" style="padding: 10px 20px; font-size: 14px;">客户API</a>
</div>
</div>
</div>
<div style="text-align: center; margin-top: 50px; color: white; opacity: 0.8;">
<p>© 2026 老挝电力公司财务管理系统 | 技术支持: OpenClaw AI Assistant</p>
</div>
</div>
</body>
</html>
`);
});
// ==================== 默认路由 ====================
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, '../frontend/dist/index.html'));
});
// ==================== API文档页面 ====================
app.get('/api-docs', (req, res) => {
res.send(`
<!DOCTYPE html>
<html>
<head><title>API文档</title><meta charset="utf-8"></head>
<body style="font-family: Arial; padding: 40px;">
<h1>📚 API文档</h1>
<p>这是API端点文档页面。如果您想使用业务界面,请访问:</p>
<p><a href="/" style="font-size: 18px; color: #1890ff;">👉 点击这里进入业务系统</a></p>
<p>或访问:<a href="/welcome">欢迎页面</a></p>
</body>
</html>
`);
});
// ==================== 文件上传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;