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