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