2026-04-19 19:15:01 +08:00
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 );
}
}
// 添加purchase_type字段到purchase_requests表
async function addPurchaseTypeColumn () {
try {
// 检查字段是否存在
const result = await db . query ( `PRAGMA table_info(purchase_requests)` );
const hasPurchaseType = result . rows . some ( row => row . name === 'purchase_type' );
if ( ! hasPurchaseType ) {
await db . query ( `ALTER TABLE purchase_requests ADD COLUMN purchase_type TEXT DEFAULT 'inventory'` );
console . log ( 'purchase_type字段添加成功' );
} else {
console . log ( 'purchase_type字段已存在' );
}
} catch ( error ) {
console . error ( '添加purchase_type字段失败:' , error );
}
}
// 添加brief_description字段到purchase_requests表
async function addBriefDescriptionColumn () {
try {
// 检查字段是否存在
const result = await db . query ( `PRAGMA table_info(purchase_requests)` );
const hasBriefDescription = result . rows . some ( row => row . name === 'brief_description' );
if ( ! hasBriefDescription ) {
await db . query ( `ALTER TABLE purchase_requests ADD COLUMN brief_description TEXT` );
console . log ( 'brief_description字段添加成功' );
} else {
console . log ( 'brief_description字段已存在' );
}
} catch ( error ) {
console . error ( '添加brief_description字段失败:' , error );
}
}
// 添加execute_date和execute_method字段到purchase_requests表
async function addExecuteColumns () {
try {
// 检查字段是否存在
const result = await db . query ( `PRAGMA table_info(purchase_requests)` );
const hasExecuteDate = result . rows . some ( row => row . name === 'execute_date' );
const hasExecuteMethod = result . rows . some ( row => row . name === 'execute_method' );
if ( ! hasExecuteDate ) {
await db . query ( `ALTER TABLE purchase_requests ADD COLUMN execute_date TEXT` );
console . log ( 'execute_date字段添加成功' );
} else {
console . log ( 'execute_date字段已存在' );
}
if ( ! hasExecuteMethod ) {
await db . query ( `ALTER TABLE purchase_requests ADD COLUMN execute_method TEXT` );
console . log ( 'execute_method字段添加成功' );
} else {
console . log ( 'execute_method字段已存在' );
}
} catch ( error ) {
console . error ( '添加执行字段失败:' , error );
}
}
// 添加attachments字段到purchase_requests表
async function addAttachmentsColumn () {
try {
// 检查字段是否存在
const result = await db . query ( `PRAGMA table_info(purchase_requests)` );
const hasAttachments = result . rows . some ( row => row . name === 'attachments' );
if ( ! hasAttachments ) {
await db . query ( `ALTER TABLE purchase_requests ADD COLUMN attachments TEXT DEFAULT ''` );
console . log ( 'attachments字段添加成功' );
} else {
console . log ( 'attachments字段已存在' );
}
} catch ( error ) {
console . error ( '添加attachments字段失败:' , error );
}
}
// 添加request_date、expense_category和currency字段到purchase_requests表
async function addRequestDateAndCategoryColumns () {
try {
// 检查字段是否存在
const result = await db . query ( `PRAGMA table_info(purchase_requests)` );
const hasRequestDate = result . rows . some ( row => row . name === 'request_date' );
const hasExpenseCategory = result . rows . some ( row => row . name === 'expense_category' );
const hasCurrency = result . rows . some ( row => row . name === 'currency' );
if ( ! hasRequestDate ) {
await db . query ( `ALTER TABLE purchase_requests ADD COLUMN request_date TEXT` );
console . log ( 'request_date字段添加成功' );
} else {
console . log ( 'request_date字段已存在' );
}
if ( ! hasExpenseCategory ) {
await db . query ( `ALTER TABLE purchase_requests ADD COLUMN expense_category TEXT` );
console . log ( 'expense_category字段添加成功' );
} else {
console . log ( 'expense_category字段已存在' );
}
if ( ! hasCurrency ) {
await db . query ( `ALTER TABLE purchase_requests ADD COLUMN currency TEXT DEFAULT 'CNY'` );
console . log ( 'currency字段添加成功' );
} else {
console . log ( 'currency字段已存在' );
}
} catch ( error ) {
console . error ( '添加request_date、expense_category和currency字段失败:' , error );
}
}
// 创建库存管理表
async function createInventoryTable () {
try {
await db . query ( `
CREATE TABLE IF NOT EXISTS inventory (
id INTEGER PRIMARY KEY AUTOINCREMENT,
product_id INTEGER,
product_name TEXT NOT NULL,
quantity REAL NOT NULL,
unit TEXT NOT NULL,
price REAL,
total_value REAL,
location TEXT,
status TEXT DEFAULT 'in_stock',
last_updated DATE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (product_id) REFERENCES products(id)
)
` );
console . log ( '库存管理表创建成功' );
} catch ( error ) {
console . error ( '创建库存管理表失败:' , error );
}
}
// 初始化数据库表
createSupplierPaymentInfosTable ();
createInventoryTable ();
addPurchaseTypeColumn ();
addBriefDescriptionColumn ();
addExecuteColumns ();
addAttachmentsColumn ();
addRequestDateAndCategoryColumns ();
// ==================== 健康检查 ====================
app . get ( '/api/health' , ( req , res ) => {
res . json ({
success : true ,
message : '公司财务管理系统 API' ,
version : '1.0.0' ,
timestamp : new Date (). toISOString (),
endpoints : {
upload : "/api/upload" ,
health : '/api/health' ,
auth : '/api/auth' ,
customers : '/api/customers' ,
suppliers : '/api/suppliers' ,
projects : '/api/projects' ,
products : '/api/products' ,
payment_nodes : '/api/payment-nodes' ,
payment_records : '/api/payment-records' ,
exchange_rates : '/api/exchange-rates' ,
advances : '/api/advances' ,
reimbursements : '/api/reimbursements' ,
purchase_requests : '/api/purchase-requests' ,
inventory : '/api/inventory' ,
finance_stats : '/api/finance-stats'
}
});
});
// ==================== 认证API ====================
// ==================== 客户管理API ====================
app . get ( '/api/customers' , async ( req , res ) => {
try {
const result = await db . query ( `
SELECT * FROM customers
ORDER BY created_at DESC
LIMIT 50
` );
// 为每个客户获取联系人和收款信息
const customersWithDetails = await Promise . all (
result . rows . map ( async ( customer ) => {
// 获取联系人信息
const contactsResult = await db . query (
`SELECT * FROM contacts WHERE entity_id = ? AND entity_type = 'customer' ORDER BY is_primary DESC` ,
[ customer . id ]
);
const contacts = contactsResult . rows . map ( contact => ({
name : contact . name || '未命名' ,
position : contact . position || '' ,
phone : contact . phone || '' ,
is_primary : contact . is_primary === 1
}));
// 获取收款信息
const paymentInfosResult = await db . query (
`SELECT * FROM supplier_payment_infos WHERE supplier_id = ? ORDER BY is_default DESC` ,
[ customer . id ]
);
const paymentInfos = paymentInfosResult . rows . map ( payment => ({
id : payment . id ,
account_name : payment . account_name ,
bank_account : payment . account_number ,
bank_name : payment . bank_name ,
qr_code : payment . qr_code ,
is_primary : payment . is_default === 1
}));
return {
... customer ,
contacts : contacts . length > 0 ? contacts : [],
payment_infos : paymentInfos . length > 0 ? paymentInfos : []
};
})
);
res . json ({
success : true ,
data : customersWithDetails ,
count : customersWithDetails . length
});
} catch ( error ) {
console . error ( '获取客户失败:' , error );
res . status ( 500 ). json ({
success : false ,
message : '获取客户失败' ,
error : error . message
});
}
});
app . get ( '/api/customers/:id' , async ( req , res ) => {
try {
const { id } = req . params ;
// 获取客户基本信息
const customerResult = await db . query ( `
SELECT * FROM customers
WHERE id = ?
` , [ id ]);
if ( customerResult . rows . length > 0 ) {
const customer = customerResult . rows [ 0 ];
// 获取客户的所有联系人
const contactsResult = await db . query ( `
SELECT * FROM contacts
WHERE entity_id = ? AND entity_type = 'customer'
ORDER BY is_primary DESC
` , [ id ]);
// 转换联系人数据结构
const contacts = contactsResult . rows . map ( contact => ({
name : contact . name || '未命名' ,
position : contact . position || '' ,
phone : contact . phone || '' ,
is_primary : contact . is_primary === 1
}));
// 获取客户的所有收款信息
const paymentInfosResult = await db . query ( `
SELECT * FROM supplier_payment_infos
WHERE supplier_id = ?
ORDER BY is_default DESC
` , [ id ]);
// 转换收款信息数据结构
const payment_infos = paymentInfosResult . rows . map ( info => ({
id : info . id ,
account_name : info . account_name || '' ,
bank_name : info . bank_name || '' ,
bank_account : info . account_number || '' ,
qr_code : info . qr_code || '' ,
is_primary : info . is_default === 1
}));
// 转换数据结构以匹配前端期望
const formattedCustomer = {
id : customer . id ,
code : `C ${ String ( customer . id ). padStart ( 4 , '0' ) } ` , // 生成客户编号
name : customer . name ,
address : customer . address ,
contacts : contacts . length > 0 ? contacts : [], // 使用从联系人表获取的联系人
payment_infos : payment_infos . length > 0 ? payment_infos : [], // 添加收款信息
remark : customer . remark || '' , // 默认为空
total_contract_amount : 0 , // 默认为0
total_received : 0 , // 默认为0
total_receivable : 0 , // 默认为0
created_at : customer . created_at
};
res . json ({
success : true ,
data : formattedCustomer
});
} else {
res . status ( 404 ). json ({
success : false ,
message : '客户不存在'
});
}
} catch ( error ) {
console . error ( '获取客户详情失败:' , error );
res . status ( 500 ). json ({
success : false ,
message : '获取客户详情失败' ,
error : error . message
});
}
});
app . post ( '/api/customers' , async ( req , res ) => {
try {
const { name , address , remark , contacts , payment_infos } = req . body ;
// 从contacts中获取主联系人信息
const primaryContact = contacts ? . find ( c => c . is_primary ) || contacts ? .[ 0 ];
const contact = primaryContact ? . name || '' ;
const position = primaryContact ? . position || '' ;
const phone = primaryContact ? . phone || '' ;
const email = '' ; // 前端没有email字段
const result = await db . query (
`INSERT INTO customers (name, address, contact, position, phone, email, remark, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))` ,
[ name , address , contact , position , phone , email , remark ]
);
const customerId = result . lastID ;
// 插入联系人数据
if ( contacts && contacts . length > 0 ) {
for ( const contactItem of contacts ) {
await db . query (
`INSERT INTO contacts (entity_id, entity_type, name, position, phone, is_primary, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))` ,
[ customerId , 'customer' , contactItem . name , contactItem . position , contactItem . phone , contactItem . is_primary ? 1 : 0 ]
);
}
}
// 插入收款信息数据
if ( payment_infos && payment_infos . length > 0 ) {
for ( const paymentItem of payment_infos ) {
await db . query (
`INSERT INTO supplier_payment_infos (supplier_id, account_name, bank_name, bank_account, qr_code, is_primary, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))` ,
[ customerId , paymentItem . account_name , paymentItem . bank_name , paymentItem . bank_account , paymentItem . qr_code , paymentItem . is_primary ? 1 : 0 ]
);
}
}
res . json ({
success : true ,
message : '客户创建成功' ,
data : {
id : customerId ,
code : `C ${ String ( customerId ). padStart ( 4 , '0' ) } ` ,
name ,
address ,
contacts : contacts || [],
payment_infos : payment_infos || [],
remark ,
total_contract_amount : 0 ,
total_received : 0 ,
total_receivable : 0 ,
created_at : new Date (). toISOString ()
}
});
} catch ( error ) {
console . error ( '创建客户失败:' , error );
res . status ( 500 ). json ({
success : false ,
message : '创建客户失败' ,
error : error . message
});
}
});
app . put ( '/api/customers/:id' , async ( req , res ) => {
try {
const { id } = req . params ;
const { name , address , remark , contacts , payment_infos } = req . body ;
// 从contacts中获取主联系人信息
const primaryContact = contacts ? . find ( c => c . is_primary ) || contacts ? .[ 0 ];
const contact = primaryContact ? . name || '' ;
const position = primaryContact ? . position || '' ;
const phone = primaryContact ? . phone || '' ;
const email = '' ; // 前端没有email字段
await db . query (
`UPDATE customers
SET name = ?, address = ?, contact = ?, position = ?, phone = ?, email = ?, remark = ?, updated_at = datetime('now')
WHERE id = ?` ,
[ name , address , contact , position , phone , email , remark , id ]
);
// 删除旧的联系人数据
await db . query ( `DELETE FROM contacts WHERE entity_id = ? AND entity_type = 'customer'` , [ id ]);
// 插入新的联系人数据
if ( contacts && contacts . length > 0 ) {
for ( const contactItem of contacts ) {
await db . query (
`INSERT INTO contacts (entity_id, entity_type, name, position, phone, is_primary, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))` ,
[ id , 'customer' , contactItem . name , contactItem . position , contactItem . phone , contactItem . is_primary ? 1 : 0 ]
);
}
}
// 删除旧的收款信息数据
await db . query ( `DELETE FROM supplier_payment_infos WHERE supplier_id = ?` , [ id ]);
// 插入新的收款信息数据
if ( payment_infos && payment_infos . length > 0 ) {
for ( const paymentItem of payment_infos ) {
await db . query (
`INSERT INTO supplier_payment_infos (supplier_id, account_name, bank_name, bank_account, qr_code, is_primary, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))` ,
[ id , paymentItem . account_name , paymentItem . bank_name , paymentItem . bank_account , paymentItem . qr_code , paymentItem . is_primary ? 1 : 0 ]
);
}
}
res . json ({
success : true ,
message : '客户更新成功' ,
data : {
id ,
code : `C ${ String ( id ). padStart ( 4 , '0' ) } ` ,
name ,
address ,
contacts : contacts || [],
payment_infos : payment_infos || [],
remark ,
total_contract_amount : 0 ,
total_received : 0 ,
total_receivable : 0 ,
created_at : new Date (). toISOString ()
}
});
} catch ( error ) {
console . error ( '更新客户失败:' , error );
res . status ( 500 ). json ({
success : false ,
message : '更新客户失败' ,
error : error . message
});
}
});
app . delete ( '/api/customers/:id' , async ( req , res ) => {
try {
const { id } = req . params ;
// 先删除关联的联系人数据
await db . query ( `DELETE FROM contacts WHERE entity_id = ? AND entity_type = 'customer'` , [ id ]);
// 再删除客户数据
const result = await db . query ( `DELETE FROM customers WHERE id = ?` , [ id ]);
if ( result . changes > 0 ) {
res . json ({
success : true ,
message : '客户删除成功'
});
} else {
res . status ( 404 ). json ({
success : false ,
message : '客户不存在'
});
}
} catch ( error ) {
console . error ( '删除客户失败:' , error );
res . status ( 500 ). json ({
success : false ,
message : '删除客户失败' ,
error : error . message
});
}
});
// ==================== 供应商管理API ====================
app . get ( '/api/suppliers' , async ( req , res ) => {
try {
const result = await db . query ( `
SELECT * FROM suppliers
ORDER BY created_at DESC
LIMIT 50
` );
// 为每个供应商获取联系人和收款信息
const suppliersWithDetails = await Promise . all (
result . rows . map ( async ( supplier ) => {
// 获取联系人信息
const contactsResult = await db . query (
`SELECT * FROM contacts WHERE entity_id = ? AND entity_type = 'supplier' ORDER BY is_primary DESC` ,
[ supplier . id ]
);
const contacts = contactsResult . rows . map ( contact => ({
name : contact . name || '未命名' ,
position : contact . position || '' ,
phone : contact . phone || '' ,
is_primary : contact . is_primary === 1
}));
// 获取收款信息
const paymentInfosResult = await db . query (
`SELECT * FROM supplier_payment_infos WHERE supplier_id = ? ORDER BY is_default DESC` ,
[ supplier . id ]
);
const paymentInfos = paymentInfosResult . rows . map ( payment => ({
id : payment . id ,
account_name : payment . account_name ,
bank_account : payment . account_number ,
bank_name : payment . bank_name ,
qr_code : payment . qr_code ,
is_primary : payment . is_default === 1
}));
return {
... supplier ,
contacts : contacts . length > 0 ? contacts : [],
payment_infos : paymentInfos . length > 0 ? paymentInfos : []
};
})
);
res . json ({
success : true ,
data : suppliersWithDetails ,
count : suppliersWithDetails . length
});
} catch ( error ) {
console . error ( '获取供应商失败:' , error );
res . status ( 500 ). json ({
success : false ,
message : '获取供应商失败' ,
error : error . message
});
}
});
app . get ( '/api/suppliers/:id' , async ( req , res ) => {
try {
const { id } = req . params ;
// 获取供应商基本信息
const supplierResult = await db . query ( `
SELECT * FROM suppliers
WHERE id = ?
` , [ id ]);
if ( supplierResult . rows . length > 0 ) {
const supplier = supplierResult . rows [ 0 ];
// 获取供应商的所有联系人
const contactsResult = await db . query ( `
SELECT * FROM contacts
WHERE entity_id = ? AND entity_type = 'supplier'
ORDER BY is_primary DESC
` , [ id ]);
// 转换联系人数据结构
const contacts = contactsResult . rows . map ( contact => ({
name : contact . name || '未命名' ,
position : contact . position || '' ,
phone : contact . phone || '' ,
is_primary : contact . is_primary === 1
}));
// 获取供应商的所有收款信息
const paymentInfosResult = await db . query ( `
SELECT * FROM supplier_payment_infos
WHERE supplier_id = ?
ORDER BY is_default DESC
` , [ id ]);
// 转换收款信息数据结构
const paymentInfos = paymentInfosResult . rows . map ( payment => ({
id : payment . id ,
account_name : payment . account_name ,
bank_account : payment . account_number ,
bank_name : payment . bank_name ,
qr_code : payment . qr_code ,
is_primary : payment . is_default === 1
}));
// 转换数据结构以匹配前端期望
const formattedSupplier = {
id : supplier . id ,
code : `S ${ String ( supplier . id ). padStart ( 4 , '0' ) } ` , // 生成供应商编号
name : supplier . name || '未命名' ,
supply_category : supplier . supply_category || '电力设备' , // 默认为电力设备
country : supplier . country || 'Laos' , // 默认为老挝
contacts : contacts . length > 0 ? contacts : [], // 使用从联系人表获取的联系人
payment_infos : paymentInfos . length > 0 ? paymentInfos : [], // 使用从收款信息表获取的收款信息
remark : supplier . remark || '' , // 默认为空
total_purchase_amount : 0 , // 默认为0
total_paid : 0 , // 默认为0
total_payable : 0 , // 默认为0
created_at : supplier . created_at
};
// 设置响应头确保UTF-8编码
res . setHeader ( 'Content-Type' , 'application/json; charset=utf-8' );
res . json ({
success : true ,
data : formattedSupplier
});
} else {
res . status ( 404 ). json ({
success : false ,
message : '供应商不存在'
});
}
} catch ( error ) {
console . error ( '获取供应商详情失败:' , error );
res . status ( 500 ). json ({
success : false ,
message : '获取供应商详情失败' ,
error : error . message
});
}
});
app . post ( '/api/suppliers' , async ( req , res ) => {
try {
const { name , supply_category , country , remark , contacts , payment_infos } = req . body ;
// 从contacts中获取主联系人信息
const primaryContact = contacts ? . find ( c => c . is_primary ) || contacts ? .[ 0 ];
const contact = primaryContact ? . name || '' ;
const position = primaryContact ? . position || '' ;
const phone = primaryContact ? . phone || '' ;
const email = '' ; // 前端没有email字段
const address = '' ; // 前端没有address字段
const result = await db . query (
`INSERT INTO suppliers (name, address, contact, position, phone, email, supply_category, country, remark, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))` ,
[ name , address , contact , position , phone , email , supply_category , country , remark ]
);
const supplierId = result . lastID ;
// 插入联系人数据
if ( contacts && contacts . length > 0 ) {
for ( const contactItem of contacts ) {
await db . query (
`INSERT INTO contacts (entity_id, entity_type, name, position, phone, is_primary, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))` ,
[ supplierId , 'supplier' , contactItem . name , contactItem . position , contactItem . phone , contactItem . is_primary ? 1 : 0 ]
);
}
}
// 插入收款信息数据
if ( payment_infos && payment_infos . length > 0 ) {
for ( const paymentInfo of payment_infos ) {
await db . query (
`INSERT INTO supplier_payment_infos (supplier_id, account_name, bank_account, bank_name, qr_code, is_primary, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))` ,
[ supplierId , paymentInfo . account_name , paymentInfo . bank_account , paymentInfo . bank_name , paymentInfo . qr_code , paymentInfo . is_primary ? 1 : 0 ]
);
}
}
res . json ({
success : true ,
message : '供应商创建成功' ,
data : {
id : supplierId ,
code : `S ${ String ( supplierId ). padStart ( 4 , '0' ) } ` ,
name ,
supply_category ,
country ,
contacts : contacts || [],
payment_infos : payment_infos || [],
remark ,
total_purchase_amount : 0 ,
total_paid : 0 ,
total_payable : 0 ,
created_at : new Date (). toISOString ()
}
});
} catch ( error ) {
console . error ( '创建供应商失败:' , error );
res . status ( 500 ). json ({
success : false ,
message : '创建供应商失败' ,
error : error . message
});
}
});
app . put ( '/api/suppliers/:id' , async ( req , res ) => {
try {
const { id } = req . params ;
const { name , supply_category , country , remark , contacts , payment_infos } = req . body ;
// 从contacts中获取主联系人信息
const primaryContact = contacts ? . find ( c => c . is_primary ) || contacts ? .[ 0 ];
const contact = primaryContact ? . name || '' ;
const position = primaryContact ? . position || '' ;
const phone = primaryContact ? . phone || '' ;
const email = '' ; // 前端没有email字段
const address = '' ; // 前端没有address字段
await db . query (
`UPDATE suppliers
SET name = ?, address = ?, contact = ?, position = ?, phone = ?, email = ?, supply_category = ?, country = ?, remark = ?, updated_at = datetime('now')
WHERE id = ?` ,
[ name , address , contact , position , phone , email , supply_category , country , remark , id ]
);
// 删除旧的联系人数据
await db . query ( `DELETE FROM contacts WHERE entity_id = ? AND entity_type = 'supplier'` , [ id ]);
// 插入新的联系人数据
if ( contacts && contacts . length > 0 ) {
for ( const contactItem of contacts ) {
await db . query (
`INSERT INTO contacts (entity_id, entity_type, name, position, phone, is_primary, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))` ,
[ id , 'supplier' , contactItem . name , contactItem . position , contactItem . phone , contactItem . is_primary ? 1 : 0 ]
);
}
}
// 删除旧的收款信息数据
await db . query ( `DELETE FROM supplier_payment_infos WHERE supplier_id = ?` , [ id ]);
// 插入新的收款信息数据
if ( payment_infos && payment_infos . length > 0 ) {
for ( const paymentInfo of payment_infos ) {
await db . query (
`INSERT INTO supplier_payment_infos (supplier_id, account_name, bank_account, bank_name, qr_code, is_primary, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))` ,
[ id , paymentInfo . account_name , paymentInfo . bank_account , paymentInfo . bank_name , paymentInfo . qr_code , paymentInfo . is_primary ? 1 : 0 ]
);
}
}
res . json ({
success : true ,
message : '供应商更新成功' ,
data : {
id ,
code : `S ${ String ( id ). padStart ( 4 , '0' ) } ` ,
name ,
supply_category ,
country ,
contacts : contacts || [],
payment_infos : payment_infos || [],
remark ,
total_purchase_amount : 0 ,
total_paid : 0 ,
total_payable : 0 ,
created_at : new Date (). toISOString ()
}
});
} catch ( error ) {
console . error ( '更新供应商失败:' , error );
res . status ( 500 ). json ({
success : false ,
message : '更新供应商失败' ,
error : error . message
});
}
});
app . delete ( '/api/suppliers/:id' , async ( req , res ) => {
try {
const { id } = req . params ;
// 先删除关联的联系人数据
await db . query ( `DELETE FROM contacts WHERE entity_id = ? AND entity_type = 'supplier'` , [ id ]);
// 再删除供应商数据
const result = await db . query ( `DELETE FROM suppliers WHERE id = ?` , [ id ]);
if ( result . changes > 0 ) {
res . json ({
success : true ,
message : '供应商删除成功'
});
} else {
res . status ( 404 ). json ({
success : false ,
message : '供应商不存在'
});
}
} catch ( error ) {
console . error ( '删除供应商失败:' , error );
res . status ( 500 ). json ({
success : false ,
message : '删除供应商失败' ,
error : error . message
});
}
});
// ==================== 分包商管理API ====================
app . get ( '/api/subcontractors' , async ( req , res ) => {
try {
const result = await db . query ( `
SELECT * FROM subcontractors
ORDER BY created_at DESC
LIMIT 50
` );
// 为每个分包商获取联系人和收款信息
const subcontractorsWithDetails = await Promise . all (
result . rows . map ( async ( subcontractor ) => {
// 获取联系人信息
const contactsResult = await db . query (
`SELECT * FROM contacts WHERE entity_id = ? AND entity_type = 'subcontractor' ORDER BY is_primary DESC` ,
[ subcontractor . id ]
);
const contacts = contactsResult . rows . map ( contact => ({
name : contact . name || '未命名' ,
position : contact . position || '' ,
phone : contact . phone || '' ,
is_primary : contact . is_primary === 1
}));
// 获取收款信息
const paymentInfosResult = await db . query (
`SELECT * FROM supplier_payment_infos WHERE supplier_id = ? ORDER BY is_default DESC` ,
[ subcontractor . id ]
);
const paymentInfos = paymentInfosResult . rows . map ( payment => ({
id : payment . id ,
account_name : payment . account_name ,
bank_account : payment . account_number ,
bank_name : payment . bank_name ,
qr_code : payment . qr_code ,
is_primary : payment . is_default === 1
}));
return {
... subcontractor ,
contacts : contacts . length > 0 ? contacts : [],
payment_infos : paymentInfos . length > 0 ? paymentInfos : []
};
})
);
res . json ({
success : true ,
data : subcontractorsWithDetails ,
count : subcontractorsWithDetails . length
});
} catch ( error ) {
console . error ( '获取分包商失败:' , error );
res . status ( 500 ). json ({
success : false ,
message : '获取分包商失败' ,
error : error . message
});
}
});
app . get ( '/api/subcontractors/:id' , async ( req , res ) => {
try {
const { id } = req . params ;
// 获取分包商基本信息
const subcontractorResult = await db . query ( `
SELECT * FROM subcontractors
WHERE id = ?
` , [ id ]);
if ( subcontractorResult . rows . length > 0 ) {
const subcontractor = subcontractorResult . rows [ 0 ];
// 获取分包商的所有联系人
const contactsResult = await db . query ( `
SELECT * FROM contacts
WHERE entity_id = ? AND entity_type = 'subcontractor'
ORDER BY is_primary DESC
` , [ id ]);
// 转换联系人数据结构
const contacts = contactsResult . rows . map ( contact => ({
name : contact . name || '未命名' ,
position : contact . position || '' ,
phone : contact . phone || '' ,
is_primary : contact . is_primary === 1
}));
// 获取分包商的所有收款信息
const paymentInfosResult = await db . query ( `
SELECT * FROM supplier_payment_infos
WHERE supplier_id = ?
ORDER BY is_default DESC
` , [ id ]);
// 转换收款信息数据结构
const paymentInfos = paymentInfosResult . rows . map ( payment => ({
id : payment . id ,
account_name : payment . account_name ,
bank_account : payment . account_number ,
bank_name : payment . bank_name ,
qr_code : payment . qr_code ,
is_primary : payment . is_default === 1
}));
// 转换数据结构以匹配前端期望
const formattedSubcontractor = {
id : subcontractor . id ,
code : `SC ${ String ( subcontractor . id ). padStart ( 4 , '0' ) } ` , // 生成分包商编号
name : subcontractor . name ,
scope : subcontractor . scope || '' , // 默认为空
features : subcontractor . features || '' , // 默认为空
country : subcontractor . country || '' , // 默认为空
contacts : contacts . length > 0 ? contacts : [], // 使用从联系人表获取的联系人
payment_infos : paymentInfos . length > 0 ? paymentInfos : [], // 使用从收款信息表获取的收款信息
remark : subcontractor . remark || '' , // 默认为空
total_contract_amount : 0 , // 默认为0
total_paid : 0 , // 默认为0
total_payable : 0 , // 默认为0
created_at : subcontractor . created_at
};
res . json ({
success : true ,
data : formattedSubcontractor
});
} else {
res . status ( 404 ). json ({
success : false ,
message : '分包商不存在'
});
}
} catch ( error ) {
console . error ( '获取分包商详情失败:' , error );
res . status ( 500 ). json ({
success : false ,
message : '获取分包商详情失败' ,
error : error . message
});
}
});
app . post ( '/api/subcontractors' , async ( req , res ) => {
try {
const { name , scope , features , country , remark , contacts , payment_infos } = req . body ;
// 从contacts中获取主联系人信息
const primaryContact = contacts ? . find ( c => c . is_primary ) || contacts ? .[ 0 ];
const contact = primaryContact ? . name || '' ;
const position = primaryContact ? . position || '' ;
const phone = primaryContact ? . phone || '' ;
const email = '' ; // 前端没有email字段
const address = '' ; // 前端没有address字段
const result = await db . query (
`INSERT INTO subcontractors (name, address, contact, position, phone, email, scope, features, country, remark, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))` ,
[ name , address , contact , position , phone , email , scope , features , country , remark ]
);
const subcontractorId = result . lastID ;
// 插入联系人数据
if ( contacts && contacts . length > 0 ) {
for ( const contactItem of contacts ) {
await db . query (
`INSERT INTO contacts (entity_id, entity_type, name, position, phone, is_primary, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))` ,
[ subcontractorId , 'subcontractor' , contactItem . name , contactItem . position , contactItem . phone , contactItem . is_primary ? 1 : 0 ]
);
}
}
// 插入收款信息数据
if ( payment_infos && payment_infos . length > 0 ) {
for ( const paymentItem of payment_infos ) {
await db . query (
`INSERT INTO supplier_payment_infos (supplier_id, account_name, bank_name, bank_account, qr_code, is_primary, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))` ,
[ subcontractorId , paymentItem . account_name , paymentItem . bank_name , paymentItem . bank_account , paymentItem . qr_code , paymentItem . is_primary ? 1 : 0 ]
);
}
}
res . json ({
success : true ,
message : '分包商创建成功' ,
data : {
id : subcontractorId ,
code : `SC ${ String ( subcontractorId ). padStart ( 4 , '0' ) } ` ,
name ,
scope ,
features ,
country ,
contacts : contacts || [],
payment_infos : payment_infos || [],
remark ,
total_contract_amount : 0 ,
total_paid : 0 ,
total_payable : 0 ,
created_at : new Date (). toISOString ()
}
});
} catch ( error ) {
console . error ( '创建分包商失败:' , error );
res . status ( 500 ). json ({
success : false ,
message : '创建分包商失败' ,
error : error . message
});
}
});
app . put ( '/api/subcontractors/:id' , async ( req , res ) => {
try {
const { id } = req . params ;
const { name , scope , features , country , remark , contacts , payment_infos } = req . body ;
// 从contacts中获取主联系人信息
const primaryContact = contacts ? . find ( c => c . is_primary ) || contacts ? .[ 0 ];
const contact = primaryContact ? . name || '' ;
const position = primaryContact ? . position || '' ;
const phone = primaryContact ? . phone || '' ;
const email = '' ; // 前端没有email字段
const address = '' ; // 前端没有address字段
await db . query (
`UPDATE subcontractors
SET name = ?, address = ?, contact = ?, position = ?, phone = ?, email = ?, scope = ?, features = ?, country = ?, remark = ?, updated_at = datetime('now')
WHERE id = ?` ,
[ name , address , contact , position , phone , email , scope , features , country , remark , id ]
);
// 删除旧的联系人数据
await db . query ( `DELETE FROM contacts WHERE entity_id = ? AND entity_type = 'subcontractor'` , [ id ]);
// 插入新的联系人数据
if ( contacts && contacts . length > 0 ) {
for ( const contactItem of contacts ) {
await db . query (
`INSERT INTO contacts (entity_id, entity_type, name, position, phone, is_primary, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))` ,
[ id , 'subcontractor' , contactItem . name , contactItem . position , contactItem . phone , contactItem . is_primary ? 1 : 0 ]
);
}
}
// 删除旧的收款信息数据
await db . query ( `DELETE FROM supplier_payment_infos WHERE supplier_id = ?` , [ id ]);
// 插入新的收款信息数据
if ( payment_infos && payment_infos . length > 0 ) {
for ( const paymentItem of payment_infos ) {
await db . query (
`INSERT INTO supplier_payment_infos (supplier_id, account_name, bank_name, bank_account, qr_code, is_primary, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))` ,
[ id , paymentItem . account_name , paymentItem . bank_name , paymentItem . bank_account , paymentItem . qr_code , paymentItem . is_primary ? 1 : 0 ]
);
}
}
res . json ({
success : true ,
message : '分包商更新成功' ,
data : {
id ,
code : `SC ${ String ( id ). padStart ( 4 , '0' ) } ` ,
name ,
scope ,
features ,
country ,
contacts : contacts || [],
payment_infos : payment_infos || [],
remark ,
total_contract_amount : 0 ,
total_paid : 0 ,
total_payable : 0 ,
created_at : new Date (). toISOString ()
}
});
} catch ( error ) {
console . error ( '更新分包商失败:' , error );
res . status ( 500 ). json ({
success : false ,
message : '更新分包商失败' ,
error : error . message
});
}
});
app . delete ( '/api/subcontractors/:id' , async ( req , res ) => {
try {
const { id } = req . params ;
// 先删除关联的联系人数据
await db . query ( `DELETE FROM contacts WHERE entity_id = ? AND entity_type = 'subcontractor'` , [ id ]);
// 再删除分包商数据
const result = await db . query ( `DELETE FROM subcontractors WHERE id = ?` , [ id ]);
if ( result . changes > 0 ) {
res . json ({
success : true ,
message : '分包商删除成功'
});
} else {
res . status ( 404 ). json ({
success : false ,
message : '分包商不存在'
});
}
} catch ( error ) {
console . error ( '删除分包商失败:' , error );
res . status ( 500 ). json ({
success : false ,
message : '删除分包商失败' ,
error : error . message
});
}
});
// ==================== 项目管理API ====================
app . get ( '/api/projects' , async ( req , res ) => {
try {
const result = await db . query ( `
SELECT
p.*,
c.name as customer_name,
u.name as manager_name
FROM projects p
LEFT JOIN customers c ON p.customer_id = c.id
LEFT JOIN users u ON p.manager_id = u.id
ORDER BY p.created_at DESC
LIMIT 50
` );
res . json ({
success : true ,
data : result . rows ,
count : result . rows . length
});
} catch ( error ) {
console . error ( '获取项目失败:' , error );
res . status ( 500 ). json ({
success : false ,
message : '获取项目失败' ,
error : error . message
});
}
});
// ==================== 项目详情API ====================
app . get ( '/api/projects/:id' , async ( req , res ) => {
try {
const { id } = req . params ;
// 获取项目基本信息
const projectResult = await db . query ( `
SELECT
p.*,
c.name as customer_name,
u.name as manager_name
FROM projects p
LEFT JOIN customers c ON p.customer_id = c.id
LEFT JOIN users u ON p.manager_id = u.id
WHERE p.id = ?
` , [ id ]);
if ( projectResult . rows . length > 0 ) {
const project = projectResult . rows [ 0 ];
// 获取项目合同信息
const contractResult = await db . query ( `
SELECT * FROM project_contracts
WHERE project_id = ?
ORDER BY created_at DESC
LIMIT 1
` , [ id ]);
const contract = contractResult . rows [ 0 ];
// 从合同表读取质保金数据,如果没有则使用默认值
const warrantyPercent = contract ? . warranty_deposit_percentage || 5 ;
const warrantyMonths = contract ? . warranty_period || 12 ;
const contractAmount = parseFloat ( project . contract_amount || 0 );
// 计算质保金金额:合同金额 * 质保比例 / 100
const warrantyAmount = Math . round ( contractAmount * warrantyPercent / 100 );
// 计算质保期结束日期
const warrantyStartDate = project . end_date ;
const warrantyEndDate = warrantyStartDate
? new Date ( new Date ( warrantyStartDate ). getTime () + warrantyMonths * 30 * 24 * 60 * 60 * 1000 ). toISOString ()
: null ;
res . json ({
success : true ,
data : {
id : project . id ,
project_code : project . code || `PROJ- ${ String ( project . id ). padStart ( 4 , '0' ) } ` ,
name : project . name ,
customer_id : project . customer_id ,
customer_name : project . customer_name || '未知客户' ,
status : project . status || 'planning' ,
budget : '0' ,
spent : '0' ,
start_date : project . start_date ,
end_date : project . end_date ,
description : project . description ,
contract_type : 'lump_sum' ,
contract_amount : project . contract_amount ? . toString () || '0' ,
currency : 'CNY' ,
contract_days : contract ? . contract_period || 180 ,
project_manager_id : project . manager_id ,
manager_id : project . manager_id ,
manager_name : project . manager_name || '未知经理' ,
location : project . location || '' ,
work_quantity : '' ,
project_situation : project . description || '' ,
settlement_type : contract ? . settlement_method || 'lump_sum' ,
has_warranty : true ,
warranty_amount : warrantyAmount . toString (),
warranty_percent : warrantyPercent . toString (),
warranty_months : warrantyMonths ,
warranty_start_date : warrantyStartDate ,
warranty_end_date : warrantyEndDate ,
warranty_status : 'pending'
}
});
} else {
res . status ( 404 ). json ({
success : false ,
message : '项目不存在'
});
}
} catch ( error ) {
console . error ( '获取项目详情失败:' , error );
res . status ( 500 ). json ({
success : false ,
message : '获取项目详情失败' ,
error : error . message
});
}
});
// ==================== 项目合同API ====================
app . get ( '/api/projects/:id/contracts' , async ( req , res ) => {
try {
const { id } = req . params ;
const result = await db . query ( `
SELECT * FROM project_contracts
WHERE project_id = ?
ORDER BY created_at DESC
` , [ id ]);
res . json ({
success : true ,
data : result . rows
});
} catch ( error ) {
console . error ( '获取项目合同失败:' , error );
res . status ( 500 ). json ({
success : false ,
message : '获取项目合同失败' ,
error : error . message
});
}
});
// ==================== 项目分包API ====================
app . get ( '/api/projects/:id/subcontracts' , async ( req , res ) => {
try {
const { id } = req . params ;
const result = await db . query ( `
SELECT * FROM subcontracts
WHERE project_id = ?
ORDER BY created_at DESC
` , [ id ]);
// 解析unit_price_items字段
const subcontracts = result . rows . map ( subcontract => {
if ( subcontract . unit_price_items ) {
try {
subcontract . unit_price_items = JSON . parse ( subcontract . unit_price_items );
} catch ( error ) {
subcontract . unit_price_items = [];
}
} else {
subcontract . unit_price_items = [];
}
return subcontract ;
});
res . json ({
success : true ,
data : subcontracts
});
} catch ( error ) {
console . error ( '获取项目分包失败:' , error );
res . status ( 500 ). json ({
success : false ,
message : '获取项目分包失败' ,
error : error . message
});
}
});
// ==================== 新增项目分包API ====================
app . post ( '/api/projects/:id/subcontracts' , async ( req , res ) => {
try {
const { id } = req . params ;
const { subcontractor_id , subcontractor_name , contract_amount , currency , settlement_type , other_terms , payment_description , unit_price_items , start_date , end_date , work_days , status } = req . body ;
const unitPriceItemsJson = unit_price_items ? JSON . stringify ( unit_price_items ) : null ;
const result = await db . query (
`INSERT INTO subcontracts (project_id, subcontractor_id, subcontractor_name, contract_amount, currency, settlement_type, other_terms, payment_description, unit_price_items, start_date, end_date, work_days, paid_amount, status, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))` ,
[ id , subcontractor_id , subcontractor_name , contract_amount , currency || 'CNY' , settlement_type || 'lump_sum' , other_terms , payment_description , unitPriceItemsJson , start_date , end_date , work_days , 0 , status || 'active' ]
);
const subcontractId = result . lastID ;
res . json ({
success : true ,
message : '新增分包成功' ,
data : {
id : subcontractId ,
project_id : id ,
subcontractor_id ,
subcontractor_name ,
contract_amount ,
currency : currency || 'CNY' ,
settlement_type : settlement_type || 'lump_sum' ,
other_terms ,
payment_description ,
unit_price_items ,
start_date ,
end_date ,
work_days ,
paid_amount : 0 ,
status : status || 'active' ,
created_at : new Date (). toISOString ()
}
});
} catch ( error ) {
console . error ( '新增项目分包失败:' , error );
res . status ( 500 ). json ({
success : false ,
message : '新增项目分包失败' ,
error : error . message
});
}
});
// ==================== 项目材料API ====================
app . get ( '/api/projects/:id/materials' , async ( req , res ) => {
try {
const { id } = req . params ;
const result = await db . query ( `
SELECT * FROM project_materials
WHERE project_id = ?
ORDER BY created_at DESC
` , [ id ]);
res . json ({
success : true ,
data : result . rows
});
} catch ( error ) {
console . error ( '获取项目材料失败:' , error );
res . status ( 500 ). json ({
success : false ,
message : '获取项目材料失败' ,
error : error . message
});
}
});
// ==================== 项目施工节点API ====================
app . get ( '/api/projects/:id/milestones' , async ( req , res ) => {
try {
const { id } = req . params ;
const result = await db . query ( `
SELECT * FROM project_milestones
WHERE project_id = ?
ORDER BY expected_date ASC
` , [ id ]);
res . json ({
success : true ,
data : result . rows
});
} catch ( error ) {
console . error ( '获取项目施工节点失败:' , error );
res . status ( 500 ). json ({
success : false ,
message : '获取项目施工节点失败' ,
error : error . message
});
}
});
// ==================== 项目财务API ====================
app . get ( '/api/projects/:id/finances' , async ( req , res ) => {
try {
const { id } = req . params ;
const result = await db . query ( `
SELECT * FROM project_finances
WHERE project_id = ?
ORDER BY payment_date DESC
` , [ id ]);
res . json ({
success : true ,
data : result . rows
});
} catch ( error ) {
console . error ( '获取项目财务失败:' , error );
res . status ( 500 ). json ({
success : false ,
message : '获取项目财务失败' ,
error : error . message
});
}
});
// ==================== 项目质保金API ====================
app . get ( '/api/projects/:id/warranty-deposits' , async ( req , res ) => {
try {
const { id } = req . params ;
const result = await db . query ( `
SELECT * FROM warranty_deposits
WHERE project_id = ?
ORDER BY created_at DESC
` , [ id ]);
res . json ({
success : true ,
data : result . rows
});
} catch ( error ) {
console . error ( '获取项目质保金失败:' , error );
res . status ( 500 ). json ({
success : false ,
message : '获取项目质保金失败' ,
error : error . message
});
}
});
// ==================== 项目施工日志API ====================
app . get ( '/api/projects/:id/construction-logs' , async ( req , res ) => {
try {
const { id } = req . params ;
// 由于施工日志表可能不存在,返回空数组
res . json ({
success : true ,
data : []
});
} catch ( error ) {
console . error ( '获取项目施工日志失败:' , error );
res . status ( 500 ). json ({
success : false ,
message : '获取项目施工日志失败' ,
error : error . message
});
}
});
// ==================== 项目删除API ====================
app . delete ( '/api/projects/:id' , checkAdmin , async ( req , res ) => {
try {
const { id } = req . params ;
await db . query ( 'DELETE FROM projects WHERE id = ?' , [ id ]);
res . json ({ success : true , message : '项目已删除' });
} catch ( error ) {
console . error ( '删除项目失败:' , error );
res . status ( 500 ). json ({ success : false , message : error . message });
}
});
// ==================== 项目更新API ====================
app . put ( '/api/projects/:id' , async ( req , res ) => {
try {
const { id } = req . params ;
const { name , manager_id , location , start_date , end_date , description , status , contract_amount } = req . body ;
console . log ( '收到项目更新请求:' , { id , name , manager_id , location , start_date , end_date , description });
// 更新项目信息
await db . query (
'UPDATE projects SET name = CASE WHEN ? IS NOT NULL THEN ? ELSE name END, manager_id = CASE WHEN ? IS NOT NULL THEN ? ELSE manager_id END, location = CASE WHEN ? IS NOT NULL THEN ? ELSE location END, start_date = CASE WHEN ? IS NOT NULL THEN ? ELSE start_date END, end_date = CASE WHEN ? IS NOT NULL THEN ? ELSE end_date END, description = CASE WHEN ? IS NOT NULL THEN ? ELSE description END, status = CASE WHEN ? IS NOT NULL THEN ? ELSE status END, contract_amount = CASE WHEN ? IS NOT NULL THEN ? ELSE contract_amount END, updated_at = CURRENT_TIMESTAMP WHERE id = ?' ,
[ name , name , manager_id , manager_id , location , location , start_date , start_date , end_date , end_date , description , description , status , status , contract_amount , contract_amount , id ]
);
// 如果提供了开始和结束日期,更新合同的工期信息
if ( start_date && end_date ) {
const start = new Date ( start_date );
const end = new Date ( end_date );
const contractPeriod = Math . floor (( end . getTime () - start . getTime ()) / ( 1000 * 60 * 60 * 24 )) + 1 ;
// 更新合同信息
await db . query (
'UPDATE project_contracts SET start_date = ?, end_date = ?, contract_period = ? WHERE project_id = ?' ,
[ start_date , end_date , contractPeriod , id ]
);
}
// 查询更新后的数据
const updatedResult = await db . query ( 'SELECT * FROM projects WHERE id = ?' , [ id ]);
res . json ({ success : true , data : updatedResult . rows [ 0 ] });
} catch ( error ) {
console . error ( '更新项目失败:' , error );
res . status ( 500 ). json ({ success : false , message : error . message });
}
});
// ==================== 合同细节保存API ====================
app . put ( '/api/projects/:id/contract' , async ( req , res ) => {
try {
const { id } = req . params ;
const {
project_overview ,
settlement_type ,
contract_total ,
tax_included ,
unit_price_items ,
payment_nodes ,
other_info ,
contract_file
} = req . body ;
console . log ( '收到合同细节保存请求:' , { id , project_overview , settlement_type , contract_total , tax_included , contract_file });
// 1. 更新项目基本信息
await db . query (
`UPDATE projects
SET description = ?, contract_amount = ?
WHERE id = ?` ,
[ project_overview , contract_total , id ]
);
// 2. 更新或创建项目合同
const contractResult = await db . query (
`SELECT * FROM project_contracts WHERE project_id = ?` ,
[ id ]
);
if ( contractResult . rows . length > 0 ) {
// 更新现有合同
await db . query (
`UPDATE project_contracts
SET settlement_method = ?, contract_amount = ?, contract_file = ?, other_info = ?, tax_included = ?
WHERE project_id = ?` ,
[ settlement_type , contract_total , contract_file , other_info , tax_included , id ]
);
} else {
// 创建新合同
const contractCode = `CONTRACT- ${ Date . now () } ` ;
await db . query (
`INSERT INTO project_contracts (project_id, contract_code, contract_amount, settlement_method, contract_file, other_info, tax_included, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))` ,
[ id , contractCode , contract_total , settlement_type , contract_file , other_info , tax_included ]
);
}
// 3. 处理付款节点
if ( payment_nodes && Array . isArray ( payment_nodes )) {
// 删除旧的付款节点
await db . query ( `DELETE FROM project_milestones WHERE project_id = ?` , [ id ]);
// 创建新的付款节点
for ( const node of payment_nodes ) {
await db . query (
`INSERT INTO project_milestones (project_id, milestone_name, condition, percentage, amount, status, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))` ,
[ id , node . name , node . condition || '' , node . percentage , node . amount , 'pending' ]
);
}
}
// 4. 处理单价项
if ( unit_price_items && Array . isArray ( unit_price_items ) && settlement_type === 'unit_price' ) {
// 删除旧的材料项
await db . query ( `DELETE FROM project_materials WHERE project_id = ?` , [ id ]);
// 创建新的材料项
for ( const item of unit_price_items ) {
await db . query (
`INSERT INTO project_materials (project_id, product_name, unit, budget_quantity, average_price, total_amount, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))` ,
[ id , item . name , item . unit , item . quantity , item . price , item . total ]
);
}
}
res . json ({
success : true ,
message : '合同细节保存成功'
});
} catch ( error ) {
console . error ( '保存合同细节失败:' , error );
res . status ( 500 ). json ({ success : false , message : error . message });
}
});
// ==================== 文件上传API ====================
const fs = require ( 'fs' );
const uploadDir = path . join ( __dirname , 'uploads' );
// 确保上传目录存在
if ( ! fs . existsSync ( uploadDir )) {
fs . mkdirSync ( uploadDir , { recursive : true });
}
const storage = multer . diskStorage ({
destination : function ( req , file , cb ) {
cb ( null , uploadDir );
},
filename : function ( req , file , cb ) {
// 使用原始文件名,保持附件名不变
cb ( null , file . originalname );
}
});
const uploadLocal = multer ({ storage : storage });
app . post ( '/api/upload/single' , uploadLocal . single ( 'file' ), ( req , res ) => {
try {
if ( ! req . file ) {
return res . status ( 400 ). json ({ success : false , message : '请选择文件' });
}
// 构建文件URL
const fileUrl = `/uploads/ ${ req . file . filename } ` ;
res . json ({
success : true ,
data : {
url : fileUrl ,
filename : req . file . filename
},
message : '文件上传成功'
});
} catch ( error ) {
console . error ( '文件上传失败:' , error );
res . status ( 500 ). json ({ success : false , message : '文件上传失败' });
}
});
// 静态文件服务 - 上传文件
app . use ( '/uploads' , express . static ( uploadDir ));
// ==================== 预算报价管理 ====================
app . get ( '/api/budget-projects' , async ( req , res ) => {
try {
const { customer_id } = req . query ;
let query = `
SELECT b.*,
(SELECT json_group_array(json_object(
'id', q.id,
'version', q.version,
'quotation_date', q.quotation_date,
'amount', q.amount,
'currency', q.currency,
'status', q.status,
'file_url', q.file_url,
'remark', q.remark,
'created_at', q.created_at
)) FROM budget_quotations q WHERE q.project_id = b.id) as quotations
FROM budget_projects b
` ;
if ( customer_id ) {
query += ` WHERE b.customer_id = ?` ;
}
query += ` ORDER BY b.created_at DESC` ;
const params = customer_id ? [ customer_id ] : [];
const result = await db . query ( query , params );
// 解析每个项目的附件和照片数据
const projects = result . rows . map ( project => {
try {
return {
... project ,
attachments : project . attachments ? JSON . parse ( project . attachments ) : [],
survey_photos : project . survey_photos ? JSON . parse ( project . survey_photos ) : [],
quotations : project . quotations ? JSON . parse ( project . quotations ) : []
};
} catch ( error ) {
console . error ( '解析项目数据失败:' , error );
// 如果解析失败,返回原始数据,避免整个应用崩溃
return {
... project ,
attachments : [],
survey_photos : [],
quotations : []
};
}
});
res . json ({ success : true , data : projects });
} catch ( error ) {
console . error ( '获取预算项目失败:' , error );
res . status ( 500 ). json ({ success : false , message : error . message });
}
});
// 预算项目API已修改,支持按客户ID筛选
// ==================== 施工管理 ====================
app . get ( '/api/construction/my-projects' , async ( req , res ) => {
try {
const result = await db . query ( `
SELECT p.*,
c.name as customer_name,
(SELECT json_object(
'id', cl.id,
'log_date', cl.log_date,
'weather', cl.weather,
'work_content', cl.work_content
) FROM construction_logs cl WHERE cl.project_id = p.id ORDER BY cl.log_date DESC LIMIT 1) as latest_log
FROM projects p
LEFT JOIN customers c ON p.customer_id = c.id
WHERE p.status IN ('active', 'pending')
ORDER BY p.created_at DESC
` );
res . json ({ success : true , data : result . rows });
} catch ( error ) {
console . error ( '获取施工项目失败:' , error );
res . status ( 500 ). json ({ success : false , message : error . message });
}
});
// ==================== 分类管理API(树状结构)====================
// 获取分类树
app . get ( '/api/categories/tree' , async ( req , res ) => {
try {
const level = req . query . level ;
let query = 'SELECT * FROM category_tree ORDER BY level, sort_order, id' ;
const params = [];
if ( level ) {
query = 'SELECT * FROM category_tree WHERE level = ? ORDER BY sort_order, id' ;
params . push ( parseInt ( level ));
}
const result = await db . query ( query , params );
if ( level ) {
res . json ({ success : true , data : result . rows });
} else {
const buildTree = ( categories , parentId = null ) => {
return categories
. filter ( cat => cat . parent_id === parentId )
. map ( cat => ({
... cat ,
children : buildTree ( categories , cat . id )
}));
};
const tree = buildTree ( result . rows );
res . json ({ success : true , data : tree });
}
} catch ( error ) {
console . error ( '获取分类树失败:' , error );
res . status ( 500 ). json ({ success : false , message : '获取分类失败' , error : error . message });
}
});
// 获取所有分类列表
app . get ( '/api/categories' , async ( req , res ) => {
try {
const result = await db . query ( 'SELECT * FROM category_tree ORDER BY level, sort_order, id' );
res . json ({ success : true , data : result . rows });
} catch ( error ) {
console . error ( '获取分类失败:' , error );
res . status ( 500 ). json ({ success : false , message : '获取分类失败' , error : error . message });
}
});
// 获取单个分类
app . get ( '/api/categories/:id' , async ( req , res ) => {
try {
const { id } = req . params ;
const result = await db . query ( 'SELECT * FROM category_tree WHERE id = ?' , [ id ]);
if ( result . rows . length === 0 ) {
return res . status ( 404 ). json ({ success : false , message : '分类不存在' });
}
res . json ({ success : true , data : result . rows [ 0 ] });
} catch ( error ) {
console . error ( '获取分类失败:' , error );
res . status ( 500 ). json ({ success : false , message : '获取分类失败' , error : error . message });
}
});
// 创建分类
app . post ( '/api/categories' , async ( req , res ) => {
try {
const { name , parent_id , level , sort_order , description } = req . body ;
if ( ! name ) {
return res . status ( 400 ). json ({ success : false , message : '分类名称不能为空' });
}
const checkResult = await db . query (
'SELECT id FROM category_tree WHERE name = ? AND (parent_id = ? OR (parent_id IS NULL AND ? IS NULL))' ,
[ name , parent_id || null , parent_id || null ]
);
if ( checkResult . rows . length > 0 ) {
return res . status ( 400 ). json ({ success : false , message : '该分类名称已存在' });
}
const result = await db . query (
'INSERT INTO category_tree (name, parent_id, level, sort_order, description) VALUES (?, ?, ?, ?, ?)' ,
[ name , parent_id || null , level || ( parent_id ? 2 : 1 ), sort_order || 0 , description || '' ]
);
const newCategory = await db . query ( 'SELECT * FROM category_tree WHERE id = ?' , [ result . lastID ]);
res . json ({ success : true , data : newCategory . rows [ 0 ], message : '创建成功' });
} catch ( error ) {
console . error ( '创建分类失败:' , error );
res . status ( 500 ). json ({ success : false , message : '创建分类失败' , error : error . message });
}
});
// 更新分类
app . put ( '/api/categories/:id' , async ( req , res ) => {
try {
const { id } = req . params ;
const { name , parent_id , sort_order , description } = req . body ;
if ( parent_id !== undefined ) {
const checkLoop = async ( currentId , targetParentId ) => {
if ( currentId === targetParentId ) return true ;
const children = await db . query ( 'SELECT id FROM category_tree WHERE parent_id = ?' , [ currentId ]);
for ( const child of children . rows ) {
if ( await checkLoop ( child . id , targetParentId )) return true ;
}
return false ;
};
if ( parent_id && await checkLoop ( parseInt ( id ), parseInt ( parent_id ))) {
return res . status ( 400 ). json ({ success : false , message : '不能将分类设置为自己的子分类' });
}
}
const updates = [];
const params = [];
if ( name !== undefined ) { updates . push ( 'name = ?' ); params . push ( name ); }
if ( parent_id !== undefined ) { updates . push ( 'parent_id = ?' ); params . push ( parent_id || null ); }
if ( sort_order !== undefined ) { updates . push ( 'sort_order = ?' ); params . push ( sort_order ); }
if ( description !== undefined ) { updates . push ( 'description = ?' ); params . push ( description ); }
if ( updates . length === 0 ) {
return res . status ( 400 ). json ({ success : false , message : '没有要更新的字段' });
}
updates . push ( 'updated_at = datetime(\'now\')' );
params . push ( id );
const result = await db . query (
`UPDATE category_tree SET ${ updates . join ( ', ' ) } WHERE id = ?` ,
params
);
if ( result . changes === 0 ) {
return res . status ( 404 ). json ({ success : false , message : '分类不存在' });
}
const updated = await db . query ( 'SELECT * FROM category_tree WHERE id = ?' , [ id ]);
res . json ({ success : true , data : updated . rows [ 0 ], message : '更新成功' });
} catch ( error ) {
console . error ( '更新分类失败:' , error );
res . status ( 500 ). json ({ success : false , message : '更新分类失败' , error : error . message });
}
});
// 删除分类
app . delete ( '/api/categories/:id' , async ( req , res ) => {
try {
const { id } = req . params ;
const productCheck = await db . query ( 'SELECT COUNT(*) as count FROM products WHERE category_id = ?' , [ id ]);
if ( productCheck . rows [ 0 ]. count > 0 ) {
return res . status ( 400 ). json ({ success : false , message : '该分类下还有商品,不能删除' });
}
const result = await db . query ( 'DELETE FROM category_tree WHERE id = ?' , [ id ]);
if ( result . changes === 0 ) {
return res . status ( 404 ). json ({ success : false , message : '分类不存在' });
}
res . json ({ success : true , message : '删除成功' });
} catch ( error ) {
console . error ( '删除分类失败:' , error );
res . status ( 500 ). json ({ success : false , message : '删除分类失败' , error : error . message });
}
});
// ==================== 商品管理API ====================
// 获取商品列表
// ==================== 付款节点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 () }
===========================================
` );
});
}
2026-03-25 23:55:36 +07:00
module . exports = app ;