备份:大改造前的完整版本 - 修复合同细节/付款节点/文件上传/施工管理/项目保存等BUG

This commit is contained in:
root
2026-05-15 12:02:24 +08:00
parent 1cad1e438d
commit fad28741bd
6157 changed files with 5147 additions and 877912 deletions
+40 -52
View File
@@ -1,22 +1,22 @@
const express = require('express');
const db = require('../db');
const { authenticate, requireAdmin } = require('../middleware/auth');
const { body, validationResult } = require('express-validator');
const router = express.Router();
// 验证错误处理中间件
const validate = (req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
success: false,
errors: errors.array()
});
}
next();
};
const express = require('express');
const db = require('../db');
const { authenticate, requireAdmin } = require('../middleware/auth');
const { body, validationResult } = require('express-validator');
const router = express.Router();
// 验证错误处理中间件
const validate = (req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
success: false,
errors: errors.array()
});
}
next();
};
router.get('/', async (req, res) => {
try {
const result = await db.query(`
@@ -47,11 +47,11 @@ router.get('/', async (req, res) => {
res.status(500).json({
success: false,
message: '获取预支款失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
router.post('/', [
body('amount').isFloat({ min: 0.01 }),
body('reason').notEmpty()
@@ -64,30 +64,18 @@ router.post('/', [
const advanceCode = `ADV-${Date.now()}`;
const result = await db.query(
'INSERT INTO advances (applicant_id, project_id, amount, currency, amount_cny, reason, advance_date, advance_code, status, applicant, attachments) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)',
'INSERT INTO advances (applicant_id, project_id, amount, currency, amount_cny, reason, advance_date, advance_code, status, applicant, attachments) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) RETURNING id',
[applicant_id, project_id, amount, currency || 'CNY', amount_cny || 0, reason, advance_date, advanceCode, status || 'pending', applicant, JSON.stringify(attachments || [])]
);
// SQLite不支持RETURNING,所以需要查询刚插入的数据
const lastInsert = await db.query('SELECT * FROM advances ORDER BY id DESC LIMIT 1');
const data = lastInsert.rows[0];
// 解析 attachments 字段为数组
if (data.attachments) {
try {
data.attachments = JSON.parse(data.attachments);
} catch (error) {
data.attachments = [];
}
} else {
data.attachments = [];
}
const data = { id: result.rows[0]?.id, applicant_id, project_id, amount, currency, reason, advance_date, advance_code: advanceCode, status, applicant };
res.json({ success: true, data });
} catch (error) {
console.error('创建预支申请失败:', error);
res.status(500).json({ success: false, message: '创建预支申请失败', error: error.message });
res.status(500).json({ success: false, message: '创建预支申请失败' });
}
});
router.get('/:id', async (req, res) => {
try {
const { id } = req.params;
@@ -112,10 +100,10 @@ router.get('/:id', async (req, res) => {
}
} catch (error) {
console.error('获取预支申请失败:', error);
res.status(500).json({ success: false, message: '获取预支申请失败', error: error.message });
res.status(500).json({ success: false, message: '获取预支申请失败' });
}
});
router.put('/:id', async (req, res) => {
try {
const { id } = req.params;
@@ -133,10 +121,10 @@ router.put('/:id', async (req, res) => {
}
} catch (error) {
console.error('更新预支申请失败:', error);
res.status(500).json({ success: false, message: '更新预支申请失败', error: error.message });
res.status(500).json({ success: false, message: '更新预支申请失败' });
}
});
router.delete('/:id', async (req, res) => {
try {
const { id } = req.params;
@@ -150,10 +138,10 @@ router.delete('/:id', async (req, res) => {
}
} catch (error) {
console.error('删除预支申请失败:', error);
res.status(500).json({ success: false, message: '删除预支申请失败', error: error.message });
res.status(500).json({ success: false, message: '删除预支申请失败' });
}
});
router.post('/:id/submit', async (req, res) => {
try {
const { id } = req.params;
@@ -167,10 +155,10 @@ router.post('/:id/submit', async (req, res) => {
}
} catch (error) {
console.error('提交预支申请失败:', error);
res.status(500).json({ success: false, message: '提交预支申请失败', error: error.message });
res.status(500).json({ success: false, message: '提交预支申请失败' });
}
});
router.post('/:id/withdraw', async (req, res) => {
try {
const { id } = req.params;
@@ -184,10 +172,10 @@ router.post('/:id/withdraw', async (req, res) => {
}
} catch (error) {
console.error('撤回预支申请失败:', error);
res.status(500).json({ success: false, message: '撤回预支申请失败', error: error.message });
res.status(500).json({ success: false, message: '撤回预支申请失败' });
}
});
router.post('/:id/approve', async (req, res) => {
try {
const { id } = req.params;
@@ -202,10 +190,10 @@ router.post('/:id/approve', async (req, res) => {
}
} catch (error) {
console.error('审批预支申请失败:', error);
res.status(500).json({ success: false, message: '审批预支申请失败', error: error.message });
res.status(500).json({ success: false, message: '审批预支申请失败' });
}
});
router.post('/:id/reject', async (req, res) => {
try {
const { id } = req.params;
@@ -220,9 +208,9 @@ router.post('/:id/reject', async (req, res) => {
}
} catch (error) {
console.error('退回预支申请失败:', error);
res.status(500).json({ success: false, message: '退回预支申请失败', error: error.message });
res.status(500).json({ success: false, message: '退回预支申请失败' });
}
});
module.exports = router;
+1 -3
View File
@@ -42,7 +42,6 @@ router.post('/login', async (req, res) => {
role: user.role
});
console.log('用户 ' + username + ' 登录成功');
res.json({
success: true,
@@ -62,7 +61,7 @@ router.post('/login', async (req, res) => {
res.status(500).json({
success: false,
message: '登录失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -77,7 +76,6 @@ router.get('/verify', authenticate, (req, res) => {
});
router.post('/logout', authenticate, (req, res) => {
console.log('用户 ' + req.user.username + ' 登出');
res.json({
success: true,
message: '登出成功'
+349 -28
View File
@@ -4,51 +4,372 @@ const { authenticate, requireAdmin } = require('../middleware/auth');
const router = express.Router();
router.get('/', async (req, res) => {
router.get('/', authenticate, async (req, res) => {
try {
const { customer_id } = req.query;
const { customer_id, status } = req.query;
let query = `
SELECT b.*,
c.name as customer_name,
u.name as manager_name
FROM budget_projects b
LEFT JOIN customers c ON b.customer_id = c.id
LEFT JOIN users u ON b.project_manager_id = u.id
LEFT JOIN users u ON b.business_manager_id = u.id
`;
const params = [];
const conditions = [];
if (customer_id) {
query += ` WHERE b.customer_id = $1`;
params.push(customer_id);
conditions.push(`b.customer_id = $${params.length}`);
}
query += ` ORDER BY b.created_at DESC`;
if (status) {
params.push(status);
conditions.push(`b.status = $${params.length}`);
}
if (conditions.length > 0) {
query += ' WHERE ' + conditions.join(' AND ');
}
query += ' ORDER BY b.created_at DESC';
const result = await db.query(query, params);
const projects = result.rows.map(project => {
const projects = await Promise.all(result.rows.map(async (project) => {
let attachments = [];
let survey_photos = [];
try {
return {
...project,
attachments: project.attachments ? JSON.parse(project.attachments) : [],
survey_photos: project.survey_photos ? JSON.parse(project.survey_photos) : [],
quotations: []
};
} catch (error) {
console.error('解析项目数据失败:', error);
return {
...project,
attachments: [],
survey_photos: [],
quotations: []
};
}
});
const attResult = await db.query(
"SELECT * FROM budget_attachments WHERE budget_project_id = $1 AND file_type = 'attachment'",
[project.id]
);
attachments = attResult.rows.map(a => a.file_url);
} catch (e) {}
try {
const photoResult = await db.query(
"SELECT * FROM budget_attachments WHERE budget_project_id = $1 AND file_type = 'survey_photo'",
[project.id]
);
survey_photos = photoResult.rows.map(a => a.file_url);
} catch (e) {}
let quotations = [];
try {
const qResult = await db.query(
'SELECT * FROM budget_quotations WHERE budget_project_id = $1 ORDER BY version DESC',
[project.id]
);
quotations = qResult.rows;
} catch (e) {}
const { survey_notes, intermediary_name, intermediary_fee, business_manager_id, created_by, ...rest } = project;
return {
...rest,
project_overview: survey_notes || rest.project_overview,
intermediary: intermediary_name,
intermediary_fee_value: intermediary_fee,
manager_id: business_manager_id,
attachments,
survey_photos,
quotations
};
}));
res.json({ success: true, data: projects, count: projects.length });
} catch (error) {
console.error('获取预算项目失败:', error);
res.status(500).json({ success: false, message: error.message });
res.status(500).json({ success: false, message: '获取预算项目失败' });
}
});
router.get('/:id', authenticate, async (req, res) => {
try {
const { id } = req.params;
const result = await db.query(
`SELECT b.*, c.name as customer_name, u.name as manager_name
FROM budget_projects b
LEFT JOIN customers c ON b.customer_id = c.id
LEFT JOIN users u ON b.business_manager_id = u.id
WHERE b.id = $1`,
[id]
);
if (result.rows.length === 0) {
return res.status(404).json({ success: false, message: '项目不存在' });
}
const project = result.rows[0];
let attachments = [];
let survey_photos = [];
try {
const attResult = await db.query(
"SELECT * FROM budget_attachments WHERE budget_project_id = $1 AND file_type = 'attachment'",
[id]
);
attachments = attResult.rows.map(a => a.file_url);
} catch (e) {}
try {
const photoResult = await db.query(
"SELECT * FROM budget_attachments WHERE budget_project_id = $1 AND file_type = 'survey_photo'",
[id]
);
survey_photos = photoResult.rows.map(a => a.file_url);
} catch (e) {}
let quotations = [];
try {
const qResult = await db.query(
'SELECT * FROM budget_quotations WHERE budget_project_id = $1 ORDER BY version DESC',
[id]
);
quotations = qResult.rows;
} catch (e) {}
const { survey_notes, intermediary_name, intermediary_fee, business_manager_id, created_by, ...rest } = project;
res.json({
success: true,
data: {
...rest,
project_overview: survey_notes || rest.project_overview,
intermediary: intermediary_name,
intermediary_fee_value: intermediary_fee,
manager_id: business_manager_id,
attachments,
survey_photos,
quotations
}
});
} catch (error) {
console.error('获取预算项目详情失败:', error);
res.status(500).json({ success: false, message: '获取项目详情失败' });
}
});
router.post('/', authenticate, 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, status
} = req.body;
if (!name) {
return res.status(400).json({ success: false, message: '项目名称不能为空' });
}
const codeResult = await db.query(
"SELECT COUNT(*) as cnt FROM budget_projects WHERE budget_code LIKE 'BJ%'"
);
const codeNum = parseInt(codeResult.rows[0].cnt) + 1;
const budget_code = 'BJ' + String(codeNum).padStart(4, '0');
const result = await db.query(
`INSERT INTO budget_projects
(budget_code, name, customer_id, business_manager_id, location, survey_date,
intermediary_name, intermediary_fee_type, intermediary_fee,
customer_requirements, survey_notes, status, created_by)
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13)
RETURNING *`,
[
budget_code,
name,
customer_id || null,
manager_id || null,
location || null,
survey_date || null,
intermediary || null,
intermediary_fee_type || 'fixed',
intermediary_fee_value || 0,
customer_requirements || null,
project_overview || null,
status || 'negotiating',
req.user?.id || null
]
);
const newProject = result.rows[0];
if (attachments && Array.isArray(attachments)) {
for (const url of attachments) {
await db.query(
'INSERT INTO budget_attachments (budget_project_id, file_url, file_type) VALUES ($1, $2, $3)',
[newProject.id, url, 'attachment']
);
}
}
if (survey_photos && Array.isArray(survey_photos)) {
for (const url of survey_photos) {
await db.query(
'INSERT INTO budget_attachments (budget_project_id, file_url, file_type) VALUES ($1, $2, $3)',
[newProject.id, url, 'survey_photo']
);
}
}
const custResult = await db.query('SELECT name FROM customers WHERE id = $1', [customer_id]);
const mgrResult = await db.query('SELECT name FROM users WHERE id = $1', [manager_id]);
const { survey_notes, intermediary_name, intermediary_fee, business_manager_id, created_by, ...rest } = newProject;
res.json({
success: true,
data: {
...rest,
project_overview: survey_notes || rest.project_overview,
intermediary: intermediary_name,
intermediary_fee_value: intermediary_fee,
manager_id: business_manager_id,
customer_name: custResult.rows[0]?.name || '',
manager_name: mgrResult.rows[0]?.name || '',
attachments: attachments || [],
survey_photos: survey_photos || [],
quotations: []
}
});
} catch (error) {
console.error('创建预算项目失败:', error);
res.status(500).json({ success: false, message: '创建预算项目失败: ' + error.message });
}
});
router.put('/:id/sign', authenticate, async (req, res) => {
try {
const { id } = req.params;
const { contract_code, contract_amount, currency, construction_method, duration_days } = req.body;
const projectResult = await db.query('SELECT * FROM budget_projects WHERE id = $1', [id]);
if (projectResult.rows.length === 0) {
return res.status(404).json({ success: false, message: '项目不存在' });
}
await db.query(
"UPDATE budget_projects SET status = 'signed', updated_at = NOW() WHERE id = $1",
[id]
);
let project_id = projectResult.rows[0].project_id;
if (!project_id) {
const projResult = await db.query(
`INSERT INTO projects (name, customer_id, project_manager_id, status, created_at, updated_at)
SELECT name, customer_id, business_manager_id, 'active', NOW(), NOW()
FROM budget_projects WHERE id = $1
RETURNING id`,
[id]
);
project_id = projResult.rows[0].id;
await db.query('UPDATE budget_projects SET project_id = $1 WHERE id = $2', [project_id, id]);
}
res.json({
success: true,
data: { project_id },
message: '签约成功'
});
} catch (error) {
console.error('签约失败:', error);
res.status(500).json({ success: false, message: '签约失败: ' + error.message });
}
});
router.put('/:id/unsigned', authenticate, async (req, res) => {
try {
const { id } = req.params;
const { reason } = req.body;
const projectResult = await db.query('SELECT * FROM budget_projects WHERE id = $1', [id]);
if (projectResult.rows.length === 0) {
return res.status(404).json({ success: false, message: '项目不存在' });
}
await db.query(
"UPDATE budget_projects SET status = 'unsigned', unsigned_reason = $1, updated_at = NOW() WHERE id = $2",
[reason || '', id]
);
res.json({ success: true, message: '已标记为未签约' });
} catch (error) {
console.error('标记未签约失败:', error);
res.status(500).json({ success: false, message: '操作失败' });
}
});
router.delete('/:id', authenticate, async (req, res) => {
try {
const { id } = req.params;
const projectResult = await db.query('SELECT * FROM budget_projects WHERE id = $1', [id]);
if (projectResult.rows.length === 0) {
return res.status(404).json({ success: false, message: '项目不存在' });
}
await db.query('DELETE FROM budget_attachments WHERE budget_project_id = $1', [id]);
await db.query('DELETE FROM budget_quotations WHERE budget_project_id = $1', [id]);
await db.query('DELETE FROM negotiation_reminders WHERE budget_project_id = $1', [id]);
await db.query('DELETE FROM budget_projects WHERE id = $1', [id]);
res.json({ success: true, message: '删除成功' });
} catch (error) {
console.error('删除预算项目失败:', error);
res.status(500).json({ success: false, message: '删除失败' });
}
});
router.post('/:id/quotations', authenticate, async (req, res) => {
try {
const { id } = req.params;
const { version, quotation_date, amount, currency, file_url, notes, status } = req.body;
const projectResult = await db.query('SELECT * FROM budget_projects WHERE id = $1', [id]);
if (projectResult.rows.length === 0) {
return res.status(404).json({ success: false, message: '项目不存在' });
}
let nextVersion = version;
if (!nextVersion) {
const vResult = await db.query(
'SELECT COALESCE(MAX(version), 0) + 1 as next_ver FROM budget_quotations WHERE budget_project_id = $1',
[id]
);
nextVersion = vResult.rows[0].next_ver;
}
const result = await db.query(
`INSERT INTO budget_quotations (budget_project_id, version, quotation_date, amount, currency, file_url, notes, status)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING *`,
[id, nextVersion, quotation_date || null, amount || 0, currency || 'CNY', file_url || null, notes || null, status || 'draft']
);
await db.query('UPDATE budget_projects SET last_quotation_at = NOW(), updated_at = NOW() WHERE id = $1', [id]);
res.json({ success: true, data: result.rows[0] });
} catch (error) {
console.error('创建报价版本失败:', error);
res.status(500).json({ success: false, message: '创建报价版本失败: ' + error.message });
}
});
router.delete('/:id/quotations/:quotationId', authenticate, async (req, res) => {
try {
const { id, quotationId } = req.params;
const qResult = await db.query('SELECT * FROM budget_quotations WHERE id = $1 AND budget_project_id = $2', [quotationId, id]);
if (qResult.rows.length === 0) {
return res.status(404).json({ success: false, message: '报价版本不存在' });
}
await db.query('DELETE FROM budget_quotations WHERE id = $1', [quotationId]);
res.json({ success: true, message: '删除报价版本成功' });
} catch (error) {
console.error('删除报价版本失败:', error);
res.status(500).json({ success: false, message: '删除报价版本失败' });
}
});
+6 -6
View File
@@ -13,7 +13,7 @@ router.get('/tree', async (req, res) => {
res.json({ success: true, data: buildTree(result.rows) });
} catch (error) {
console.error('获取分类树失败:', error);
res.status(500).json({ success: false, message: '获取分类树失败', error: error.message });
res.status(500).json({ success: false, message: '获取分类树失败' });
}
});
@@ -32,7 +32,7 @@ router.get('/', async (req, res) => {
res.json({ success: true, data: result.rows });
} catch (error) {
console.error('获取分类失败:', error);
res.status(500).json({ success: false, message: '获取分类失败', error: error.message });
res.status(500).json({ success: false, message: '获取分类失败' });
}
});
@@ -46,7 +46,7 @@ router.get('/:id', async (req, res) => {
res.json({ success: true, data: result.rows[0] });
} catch (error) {
console.error('获取分类失败:', error);
res.status(500).json({ success: false, message: '获取分类失败', error: error.message });
res.status(500).json({ success: false, message: '获取分类失败' });
}
});
@@ -63,7 +63,7 @@ router.post('/', async (req, res) => {
res.json({ success: true, data: result.rows[0], message: '创建成功' });
} catch (error) {
console.error('创建分类失败:', error);
res.status(500).json({ success: false, message: '创建分类失败', error: error.message });
res.status(500).json({ success: false, message: '创建分类失败' });
}
});
@@ -88,7 +88,7 @@ router.put('/:id', async (req, res) => {
res.json({ success: true, data: result.rows[0], message: '更新成功' });
} catch (error) {
console.error('更新分类失败:', error);
res.status(500).json({ success: false, message: '更新分类失败', error: error.message });
res.status(500).json({ success: false, message: '更新分类失败' });
}
});
@@ -102,7 +102,7 @@ router.delete('/:id', async (req, res) => {
res.json({ success: true, message: '删除成功' });
} catch (error) {
console.error('删除分类失败:', error);
res.status(500).json({ success: false, message: '删除分类失败', error: error.message });
res.status(500).json({ success: false, message: '删除分类失败' });
}
});
+3 -3
View File
@@ -12,8 +12,8 @@ router.get('/my-projects', async (req, res) => {
u.name as manager_name
FROM projects p
LEFT JOIN customers c ON p.customer_id = c.id
LEFT JOIN users u ON p.manager_id = u.id
WHERE p.status IN ('active', 'pending')
LEFT JOIN users u ON p.project_manager_id = u.id
WHERE p.status IN ('active', 'in_progress', 'planning', 'pending')
ORDER BY p.created_at DESC
`);
@@ -26,7 +26,7 @@ router.get('/my-projects', async (req, res) => {
res.json({ success: true, data: projects, count: projects.length });
} catch (error) {
console.error('获取施工项目失败:', error);
res.status(500).json({ success: false, message: error.message });
res.status(500).json({ success: false, message: '操作失败' });
}
});
+55 -52
View File
@@ -1,10 +1,10 @@
const express = require('express');
const db = require('../db');
const { authenticate, requireAdmin } = require('../middleware/auth');
const LedgerService = require('../services/ledgerService');
const router = express.Router();
const express = require('express');
const db = require('../db');
const { authenticate, requireAdmin } = require('../middleware/auth');
const LedgerService = require('../services/ledgerService');
const router = express.Router();
router.get('/', async (req, res) => {
try {
const result = await db.query(`
@@ -26,22 +26,22 @@ router.get('/', async (req, res) => {
name: contact.name || '未命名',
position: contact.position || '',
phone: contact.phone || '',
is_primary: contact.is_primary === 1
is_primary: contact.is_primary === true
}));
// 获取收款信息
const paymentInfosResult = await db.query(
`SELECT * FROM supplier_payment_infos WHERE supplier_id = $1 ORDER BY is_default DESC`,
`SELECT * FROM customer_payment_infos WHERE customer_id = $1 ORDER BY is_primary DESC`,
[customer.id]
);
const paymentInfos = paymentInfosResult.rows.map(payment => ({
id: payment.id,
account_name: payment.account_name,
bank_account: payment.account_number,
bank_account: payment.bank_account,
bank_name: payment.bank_name,
qr_code: payment.qr_code,
is_primary: payment.is_default === 1
is_primary: payment.is_primary === true
}));
return {
@@ -62,11 +62,11 @@ router.get('/', async (req, res) => {
res.status(500).json({
success: false,
message: '获取客户失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
router.get('/:id', async (req, res) => {
try {
const { id } = req.params;
@@ -74,7 +74,7 @@ router.get('/:id', async (req, res) => {
// 获取客户基本信息
const customerResult = await db.query(`
SELECT * FROM customers
WHERE id = ?
WHERE id = $1
`, [id]);
if (customerResult.rows.length > 0) {
@@ -83,7 +83,7 @@ router.get('/:id', async (req, res) => {
// 获取客户的所有联系人
const contactsResult = await db.query(`
SELECT * FROM contacts
WHERE entity_id = ? AND entity_type = 'customer'
WHERE entity_id = $1 AND entity_type = 'customer'
ORDER BY is_primary DESC
`, [id]);
@@ -92,14 +92,14 @@ router.get('/:id', async (req, res) => {
name: contact.name || '未命名',
position: contact.position || '',
phone: contact.phone || '',
is_primary: contact.is_primary === 1
is_primary: contact.is_primary === true
}));
// 获取客户的所有收款信息
const paymentInfosResult = await db.query(`
SELECT * FROM supplier_payment_infos
WHERE supplier_id = ?
ORDER BY is_default DESC
SELECT * FROM customer_payment_infos
WHERE customer_id = $1
ORDER BY is_primary DESC
`, [id]);
// 转换收款信息数据结构
@@ -107,9 +107,9 @@ router.get('/:id', async (req, res) => {
id: info.id,
account_name: info.account_name || '',
bank_name: info.bank_name || '',
bank_account: info.account_number || '',
bank_account: info.bank_account || '',
qr_code: info.qr_code || '',
is_primary: info.is_default === 1
is_primary: info.is_primary === true
}));
const ledger = await LedgerService.getCustomerLedger(id);
@@ -144,26 +144,26 @@ router.get('/:id', async (req, res) => {
res.status(500).json({
success: false,
message: '获取客户详情失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
router.post('/', async (req, res) => {
try {
const { name, address, remark, contacts, payment_infos } = req.body;
// 从contacts中获取主联系人信息
const primaryContact = contacts?.find(c => c.is_primary) || contacts?.[0];
const contact = primaryContact?.name || '';
const position = primaryContact?.position || '';
const contact_person = primaryContact?.name || '';
const phone = primaryContact?.phone || '';
const email = ''; // 前端没有email字段
const result = await db.query(
`INSERT INTO customers (name, address, contact, position, phone, email, remark, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`,
[name, address, contact, position, phone, email, remark]
`INSERT INTO customers (name, address, remark, contact_person, phone, email, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
RETURNING id`,
[name, address, remark || '', contact_person, phone, email]
);
const customerId = (result.rows[0]?.id || result.rows?.[0]?.id);
@@ -173,8 +173,9 @@ router.post('/', async (req, res) => {
for (const contactItem of contacts) {
await db.query(
`INSERT INTO contacts (entity_id, entity_type, name, position, phone, is_primary, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`,
[customerId, 'customer', contactItem.name, contactItem.position, contactItem.phone, contactItem.is_primary ? 1 : 0]
VALUES ($1, $2, $3, $4, $5, $6, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
RETURNING id`,
[customerId, 'customer', contactItem.name, contactItem.position, contactItem.phone, contactItem.is_primary ? true : false]
);
}
}
@@ -183,9 +184,10 @@ router.post('/', async (req, res) => {
if (payment_infos && payment_infos.length > 0) {
for (const paymentItem of payment_infos) {
await db.query(
`INSERT INTO supplier_payment_infos (supplier_id, account_name, bank_name, bank_account, qr_code, is_primary, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`,
[customerId, paymentItem.account_name, paymentItem.bank_name, paymentItem.bank_account, paymentItem.qr_code, paymentItem.is_primary ? 1 : 0]
`INSERT INTO customer_payment_infos (customer_id, account_name, bank_name, bank_account, qr_code, is_primary, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
RETURNING id`,
[customerId, paymentItem.account_name, paymentItem.bank_name, paymentItem.bank_account, paymentItem.qr_code, paymentItem.is_primary ? true : false]
);
}
}
@@ -212,11 +214,11 @@ router.post('/', async (req, res) => {
res.status(500).json({
success: false,
message: '创建客户失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
router.put('/:id', async (req, res) => {
try {
const { id } = req.params;
@@ -224,16 +226,15 @@ router.put('/:id', async (req, res) => {
// 从contacts中获取主联系人信息
const primaryContact = contacts?.find(c => c.is_primary) || contacts?.[0];
const contact = primaryContact?.name || '';
const position = primaryContact?.position || '';
const contact_person = primaryContact?.name || '';
const phone = primaryContact?.phone || '';
const email = ''; // 前端没有email字段
await db.query(
`UPDATE customers
SET name = ?, address = ?, contact = ?, position = ?, phone = ?, email = ?, remark = ?, updated_at = CURRENT_TIMESTAMP
WHERE id = ?`,
[name, address, contact, position, phone, email, remark, id]
SET name = $1, address = $2, remark = $3, contact_person = $4, phone = $5, email = $6, updated_at = CURRENT_TIMESTAMP
WHERE id = $7`,
[name, address, remark || '', contact_person, phone, email, id]
);
// 删除旧的联系人数据
@@ -244,22 +245,24 @@ router.put('/:id', async (req, res) => {
for (const contactItem of contacts) {
await db.query(
`INSERT INTO contacts (entity_id, entity_type, name, position, phone, is_primary, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`,
[id, 'customer', contactItem.name, contactItem.position, contactItem.phone, contactItem.is_primary ? 1 : 0]
VALUES ($1, $2, $3, $4, $5, $6, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
RETURNING id`,
[id, 'customer', contactItem.name, contactItem.position, contactItem.phone, contactItem.is_primary ? true : false]
);
}
}
// 删除旧的收款信息数据
await db.query(`DELETE FROM supplier_payment_infos WHERE supplier_id = $1`, [id]);
await db.query(`DELETE FROM customer_payment_infos WHERE customer_id = $1`, [id]);
// 插入新的收款信息数据
if (payment_infos && payment_infos.length > 0) {
for (const paymentItem of payment_infos) {
await db.query(
`INSERT INTO supplier_payment_infos (supplier_id, account_name, bank_name, bank_account, qr_code, is_primary, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`,
[id, paymentItem.account_name, paymentItem.bank_name, paymentItem.bank_account, paymentItem.qr_code, paymentItem.is_primary ? 1 : 0]
`INSERT INTO customer_payment_infos (customer_id, account_name, bank_name, bank_account, qr_code, is_primary, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
RETURNING id`,
[id, paymentItem.account_name, paymentItem.bank_name, paymentItem.bank_account, paymentItem.qr_code, paymentItem.is_primary ? true : false]
);
}
}
@@ -286,11 +289,11 @@ router.put('/:id', async (req, res) => {
res.status(500).json({
success: false,
message: '更新客户失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
router.delete('/:id', async (req, res) => {
try {
const { id } = req.params;
@@ -301,7 +304,7 @@ router.delete('/:id', async (req, res) => {
// 再删除客户数据
const result = await db.query(`DELETE FROM customers WHERE id = $1`, [id]);
if (result.changes > 0) {
if (result.rowCount > 0) {
res.json({
success: true,
message: '客户删除成功'
@@ -317,10 +320,10 @@ router.delete('/:id', async (req, res) => {
res.status(500).json({
success: false,
message: '删除客户失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
module.exports = router;
+4 -4
View File
@@ -38,7 +38,7 @@ router.get('/latest', async (req, res) => {
});
} catch (error) {
console.error('获取汇率失败:', error);
res.status(500).json({ success: false, message: '获取汇率失败', error: error.message });
res.status(500).json({ success: false, message: '获取汇率失败' });
}
});
@@ -50,7 +50,7 @@ router.get('/', async (req, res) => {
res.json({ success: true, data: result.rows, count: result.rows.length });
} catch (error) {
console.error('获取汇率失败:', error);
res.status(500).json({ success: false, message: '获取汇率失败', error: error.message });
res.status(500).json({ success: false, message: '获取汇率失败' });
}
});
@@ -69,7 +69,7 @@ router.get('/history', async (req, res) => {
res.json({ success: true, data: formattedData });
} catch (error) {
console.error('获取历史汇率失败:', error);
res.status(500).json({ success: false, message: '获取历史汇率失败', error: error.message });
res.status(500).json({ success: false, message: '获取历史汇率失败' });
}
});
@@ -94,7 +94,7 @@ router.post('/', async (req, res) => {
res.json({ success: true, message: '汇率保存成功', data: result.rows[0] });
} catch (error) {
console.error('保存汇率失败:', error);
res.status(500).json({ success: false, message: '保存汇率失败', error: error.message });
res.status(500).json({ success: false, message: '保存汇率失败' });
}
});
+4 -4
View File
@@ -13,7 +13,7 @@ router.get('/', async (req, res) => {
res.json({ success: true, data: result.rows, count: result.rows.length });
} catch (error) {
console.error('获取执行记录失败:', error);
res.status(500).json({ success: false, message: '获取执行记录失败', error: error.message });
res.status(500).json({ success: false, message: '获取执行记录失败' });
}
});
@@ -36,7 +36,7 @@ router.get('/pending', async (req, res) => {
res.json({ success: true, data: pendingData, count: pendingData.length });
} catch (error) {
console.error('获取待执行列表失败:', error);
res.status(500).json({ success: false, message: '获取待执行列表失败', error: error.message });
res.status(500).json({ success: false, message: '获取待执行列表失败' });
}
});
@@ -68,7 +68,7 @@ router.get('/executed', async (req, res) => {
res.json({ success: true, data: executedData, count: executedData.length });
} catch (error) {
console.error('获取已执行列表失败:', error);
res.status(500).json({ success: false, message: '获取已执行列表失败', error: error.message });
res.status(500).json({ success: false, message: '获取已执行列表失败' });
}
});
@@ -135,7 +135,7 @@ router.post('/', async (req, res) => {
res.json({ success: true, message: '执行操作成功' });
} catch (error) {
console.error('执行操作失败:', error);
res.status(500).json({ success: false, message: '执行操作失败', error: error.message });
res.status(500).json({ success: false, message: '执行操作失败' });
}
});
+1 -1
View File
@@ -28,7 +28,7 @@ router.get('/', async (req, res) => {
}
});
} catch (error) {
res.json({ success: false, message: '获取财务统计失败', error: error.message });
res.json({ success: false, message: '获取财务统计失败' });
}
});
+4 -4
View File
@@ -47,7 +47,7 @@ router.get('/', async (req, res) => {
res.status(500).json({
success: false,
message: '获取库存记录失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -76,7 +76,7 @@ router.get('/summary', async (req, res) => {
res.status(500).json({
success: false,
message: '获取库存汇总失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -88,7 +88,7 @@ router.post('/out', async (req, res) => {
const result = await db.query(`
INSERT INTO inventory_records
(record_type, project_id, product_id, quantity, unit_price, total_amount, record_date, operator, remark)
VALUES (?, ?, ?, ?, ?, ?, CURRENT_DATE, ?, ?)
VALUES ($1, $2, $3, $4, $5, $6, CURRENT_DATE, $7, $8)
`, ['out', project_id || null, product_id, quantity, unit_price || null, total_amount || null, operator || '系统', remark || null]);
res.json({
@@ -101,7 +101,7 @@ router.post('/out', async (req, res) => {
res.status(500).json({
success: false,
message: '出库失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
+52 -144
View File
@@ -11,7 +11,6 @@
*/
const express = require('express');
const db = require('../db');
const LedgerService = require('../services/ledgerService');
const router = express.Router();
@@ -42,7 +41,7 @@ router.get('/', async (req, res) => {
console.error('获取物流公司列表失败:', error);
res.status(500).json({
success: false,
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -69,47 +68,19 @@ router.get('/:id', async (req, res) => {
`, [id]);
company.contacts = contactsResult.rows;
const paymentInfosResult = await db.query(`
SELECT * FROM logistics_company_payment_infos
const bankAccountsResult = await db.query(`
SELECT * FROM logistics_company_bank_accounts
WHERE logistics_company_id = $1
ORDER BY is_default DESC, id
`, [id]);
company.payment_infos = paymentInfosResult.rows;
company.payment_infos = bankAccountsResult.rows;
const ordersResult = await db.query(`
SELECT lr.id, lr.code, lr.purchase_order_id, lr.ship_date, lr.status,
lr.primary_freight, lr.primary_freight_currency, lr.primary_freight_status,
lr.secondary_freight, lr.secondary_freight_currency, lr.secondary_freight_status,
po.code as order_code
FROM logistics_records lr
LEFT JOIN purchase_orders po ON lr.purchase_order_id = po.id
WHERE lr.logistics_company_id = $1
ORDER BY lr.created_at DESC
`, [id]);
company.orders = ordersResult.rows;
const totalFreightResult = await db.query(`
SELECT
COALESCE(SUM(primary_freight), 0) as total_primary_freight,
COALESCE(SUM(secondary_freight), 0) as total_secondary_freight
FROM logistics_records
WHERE logistics_company_id = $1
`, [id]);
company.total_primary_freight = totalFreightResult.rows[0]?.total_primary_freight || 0;
company.total_secondary_freight = totalFreightResult.rows[0]?.total_secondary_freight || 0;
const paidFreightResult = await db.query(`
SELECT
COALESCE(SUM(CASE WHEN primary_freight_status = 'paid' THEN primary_freight ELSE 0 END), 0) as paid_primary,
COALESCE(SUM(CASE WHEN secondary_freight_status = 'paid' THEN secondary_freight ELSE 0 END), 0) as paid_secondary
FROM logistics_records
WHERE logistics_company_id = $1
`, [id]);
company.paid_primary_freight = paidFreightResult.rows[0]?.paid_primary || 0;
company.paid_secondary_freight = paidFreightResult.rows[0]?.paid_secondary || 0;
const ledger = await LedgerService.getLogisticsCompanyLedger(id);
company.ledger = ledger;
company.orders = [];
company.total_primary_freight = 0;
company.total_secondary_freight = 0;
company.paid_primary_freight = 0;
company.paid_secondary_freight = 0;
company.ledger = { summary: { item_count: 0, total_primary_freight: 0, total_secondary_freight: 0, total_freight: 0, paid_primary_freight: 0, paid_secondary_freight: 0, paid_amount: 0, unpaid_amount: 0 }, items: [] };
res.json({
success: true,
@@ -120,7 +91,7 @@ router.get('/:id', async (req, res) => {
res.status(500).json({
success: false,
message: '获取物流公司详情失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -152,7 +123,7 @@ router.post('/', async (req, res) => {
res.status(500).json({
success: false,
message: '创建物流公司失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -163,13 +134,13 @@ router.post('/', async (req, res) => {
router.put('/:id', async (req, res) => {
try {
const { id } = req.params;
const { name, address, phone, email, status, remark } = req.body;
const { name, address, phone, email, remark } = req.body;
const result = await db.query(`
UPDATE logistics_companies
SET name = $1, address = $2, phone = $3, email = $4, status = $5, remark = $6, updated_at = NOW()
WHERE id = $7
`, [name, address, phone, email, status, remark, id]);
SET name = $1, address = $2, phone = $3, email = $4, remark = $5, updated_at = NOW()
WHERE id = $6
`, [name, address, phone, email, remark, id]);
if (result.rowCount === 0) {
return res.status(404).json({ success: false, message: '物流公司不存在' });
@@ -184,7 +155,7 @@ router.put('/:id', async (req, res) => {
res.status(500).json({
success: false,
message: '更新物流公司失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -196,24 +167,10 @@ router.delete('/:id', async (req, res) => {
try {
const { id } = req.params;
const logisticsRecordsResult = await db.query(
'SELECT COUNT(*) as count FROM logistics_records WHERE logistics_company_id = $1',
[id]
);
const hasLogisticsRecords = parseInt(logisticsRecordsResult.rows[0].count) > 0;
if (hasLogisticsRecords) {
return res.status(400).json({
success: false,
message: '该公司已有物流订单关联,无法删除'
});
}
await db.query('BEGIN');
try {
await db.query('DELETE FROM logistics_company_payment_infos WHERE logistics_company_id = $1', [id]);
await db.query('DELETE FROM logistics_company_bank_accounts WHERE logistics_company_id = $1', [id]);
await db.query('DELETE FROM logistics_company_contacts WHERE logistics_company_id = $1', [id]);
await db.query('DELETE FROM logistics_companies WHERE id = $1', [id]);
@@ -229,7 +186,7 @@ router.delete('/:id', async (req, res) => {
res.status(500).json({
success: false,
message: '删除物流公司失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -256,7 +213,7 @@ router.get('/:id/contacts', async (req, res) => {
res.status(500).json({
success: false,
message: '获取联系人列表失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -271,17 +228,17 @@ router.post('/:id/contacts', async (req, res) => {
if (is_primary) {
await db.query(
'UPDATE logistics_company_contacts SET is_primary = 0 WHERE logistics_company_id = $1',
'UPDATE logistics_company_contacts SET is_primary = false WHERE logistics_company_id = $1',
[id]
);
}
const result = await db.query(`
INSERT INTO logistics_company_contacts
(logistics_company_id, name, phone, position, is_primary, created_at)
VALUES ($1, $2, $3, $4, $5, NOW())
(logistics_company_id, name, phone, position, is_primary, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, NOW(), NOW())
RETURNING id
`, [id, name, phone, position, is_primary ? 1 : 0]);
`, [id, name, phone, position, is_primary ? true : false]);
res.json({
success: true,
@@ -293,7 +250,7 @@ router.post('/:id/contacts', async (req, res) => {
res.status(500).json({
success: false,
message: '添加联系人失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -308,16 +265,16 @@ router.put('/:id/contacts/:contactId', async (req, res) => {
if (is_primary) {
await db.query(
'UPDATE logistics_company_contacts SET is_primary = 0 WHERE logistics_company_id = $1',
'UPDATE logistics_company_contacts SET is_primary = false WHERE logistics_company_id = $1',
[id]
);
}
const result = await db.query(`
UPDATE logistics_company_contacts
SET name = $1, phone = $2, position = $3, is_primary = $4
SET name = $1, phone = $2, position = $3, is_primary = $4, updated_at = NOW()
WHERE id = $5 AND logistics_company_id = $6
`, [name, phone, position, is_primary ? 1 : 0, contactId, id]);
`, [name, phone, position, is_primary ? true : false, contactId, id]);
if (result.rowCount === 0) {
return res.status(404).json({ success: false, message: '联系人不存在' });
@@ -329,7 +286,7 @@ router.put('/:id/contacts/:contactId', async (req, res) => {
res.status(500).json({
success: false,
message: '更新联系人失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -356,7 +313,7 @@ router.delete('/:id/contacts/:contactId', async (req, res) => {
res.status(500).json({
success: false,
message: '删除联系人失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -369,7 +326,7 @@ router.get('/:id/payment-infos', async (req, res) => {
const { id } = req.params;
const result = await db.query(`
SELECT * FROM logistics_company_payment_infos
SELECT * FROM logistics_company_bank_accounts
WHERE logistics_company_id = $1
ORDER BY is_default DESC, id
`, [id]);
@@ -383,7 +340,7 @@ router.get('/:id/payment-infos', async (req, res) => {
res.status(500).json({
success: false,
message: '获取收款信息列表失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -398,17 +355,17 @@ router.post('/:id/payment-infos', async (req, res) => {
if (is_default) {
await db.query(
'UPDATE logistics_company_payment_infos SET is_default = 0 WHERE logistics_company_id = $1',
'UPDATE logistics_company_bank_accounts SET is_default = false WHERE logistics_company_id = $1',
[id]
);
}
const result = await db.query(`
INSERT INTO logistics_company_payment_infos
INSERT INTO logistics_company_bank_accounts
(logistics_company_id, account_name, account_number, bank_name, qr_code, is_default, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, NOW(), NOW())
RETURNING id
`, [id, account_name, account_number, bank_name, qr_code, is_default ? 1 : 0]);
`, [id, account_name, account_number, bank_name, qr_code, is_default ? true : false]);
res.json({
success: true,
@@ -420,7 +377,7 @@ router.post('/:id/payment-infos', async (req, res) => {
res.status(500).json({
success: false,
message: '添加收款信息失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -435,16 +392,16 @@ router.put('/:id/payment-infos/:infoId', async (req, res) => {
if (is_default) {
await db.query(
'UPDATE logistics_company_payment_infos SET is_default = 0 WHERE logistics_company_id = $1',
'UPDATE logistics_company_bank_accounts SET is_default = false WHERE logistics_company_id = $1',
[id]
);
}
const result = await db.query(`
UPDATE logistics_company_payment_infos
UPDATE logistics_company_bank_accounts
SET account_name = $1, account_number = $2, bank_name = $3, qr_code = $4, is_default = $5, updated_at = NOW()
WHERE id = $6 AND logistics_company_id = $7
`, [account_name, account_number, bank_name, qr_code, is_default ? 1 : 0, infoId, id]);
`, [account_name, account_number, bank_name, qr_code, is_default ? true : false, infoId, id]);
if (result.rowCount === 0) {
return res.status(404).json({ success: false, message: '收款信息不存在' });
@@ -456,7 +413,7 @@ router.put('/:id/payment-infos/:infoId', async (req, res) => {
res.status(500).json({
success: false,
message: '更新收款信息失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -469,7 +426,7 @@ router.delete('/:id/payment-infos/:infoId', async (req, res) => {
const { id, infoId } = req.params;
const result = await db.query(
'DELETE FROM logistics_company_payment_infos WHERE id = $1 AND logistics_company_id = $2',
'DELETE FROM logistics_company_bank_accounts WHERE id = $1 AND logistics_company_id = $2',
[infoId, id]
);
@@ -483,7 +440,7 @@ router.delete('/:id/payment-infos/:infoId', async (req, res) => {
res.status(500).json({
success: false,
message: '删除收款信息失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -495,28 +452,16 @@ router.get('/:id/orders', async (req, res) => {
try {
const { id } = req.params;
const result = await db.query(`
SELECT lr.id, lr.code, lr.purchase_order_id, lr.ship_date, lr.status,
lr.primary_freight, lr.primary_freight_currency, lr.primary_freight_status,
lr.secondary_freight, lr.secondary_freight_currency, lr.secondary_freight_status,
po.code as order_code, s.name as supplier_name
FROM logistics_records lr
LEFT JOIN purchase_orders po ON lr.purchase_order_id = po.id
LEFT JOIN suppliers s ON po.supplier_id = s.id
WHERE lr.logistics_company_id = $1
ORDER BY lr.created_at DESC
`, [id]);
res.json({
success: true,
data: result.rows
data: []
});
} catch (error) {
console.error('获取业务台账失败:', error);
res.status(500).json({
success: false,
message: '获取业务台账失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -528,55 +473,18 @@ router.get('/:id/ledger', async (req, res) => {
try {
const { id } = req.params;
const summaryResult = await db.query(`
SELECT
COUNT(*) as order_count,
COALESCE(SUM(primary_freight), 0) as total_primary_freight,
COALESCE(SUM(secondary_freight), 0) as total_secondary_freight,
COALESCE(SUM(primary_freight + secondary_freight), 0) as total_freight,
COALESCE(SUM(CASE WHEN primary_freight_status = 'paid' THEN primary_freight ELSE 0 END), 0) as paid_primary_freight,
COALESCE(SUM(CASE WHEN secondary_freight_status = 'paid' THEN secondary_freight ELSE 0 END), 0) as paid_secondary_freight
FROM logistics_records
WHERE logistics_company_id = $1
`, [id]);
const ordersResult = await db.query(`
SELECT lr.id, lr.code, lr.purchase_order_id, lr.ship_date, lr.status,
lr.primary_freight, lr.primary_freight_currency, lr.primary_freight_status,
lr.secondary_freight, lr.secondary_freight_currency, lr.secondary_freight_status,
(COALESCE(lr.primary_freight, 0) + COALESCE(lr.secondary_freight, 0)) as total_freight,
po.code as order_code, p.name as project_name
FROM logistics_records lr
LEFT JOIN purchase_orders po ON lr.purchase_order_id = po.id
LEFT JOIN projects p ON po.project_id = p.id
WHERE lr.logistics_company_id = $1
ORDER BY lr.created_at DESC
`, [id]);
const summary = summaryResult.rows[0] || {
order_count: 0,
total_primary_freight: 0,
total_secondary_freight: 0,
total_freight: 0,
paid_primary_freight: 0,
paid_secondary_freight: 0
};
const totalPaid = (summary.paid_primary_freight || 0) + (summary.paid_secondary_freight || 0);
const totalUnpaid = (summary.total_freight || 0) - totalPaid;
res.json({
success: true,
data: {
summary: {
order_count: summary.order_count || 0,
total_primary_freight: summary.total_primary_freight || 0,
total_secondary_freight: summary.total_secondary_freight || 0,
total_freight: summary.total_freight || 0,
paid_amount: totalPaid,
unpaid_amount: totalUnpaid
order_count: 0,
total_primary_freight: 0,
total_secondary_freight: 0,
total_freight: 0,
paid_amount: 0,
unpaid_amount: 0
},
orders: ordersResult.rows
orders: []
}
});
} catch (error) {
@@ -584,7 +492,7 @@ router.get('/:id/ledger', async (req, res) => {
res.status(500).json({
success: false,
message: '获取物流公司台账失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
+5 -5
View File
@@ -52,7 +52,7 @@ router.get('/', async (req, res) => {
res.status(500).json({
success: false,
message: '获取物流单列表失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -84,7 +84,7 @@ router.get('/:id', async (req, res) => {
res.status(500).json({
success: false,
message: '获取物流单详情失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -127,7 +127,7 @@ router.post('/', async (req, res) => {
res.status(500).json({
success: false,
message: '创建物流单失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -181,7 +181,7 @@ router.put('/:id', async (req, res) => {
res.status(500).json({
success: false,
message: '更新物流单失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -196,7 +196,7 @@ router.delete('/:id', async (req, res) => {
res.json({ success: true, message: '物流单删除成功' });
} catch (error) {
console.error('删除物流单失败:', error);
res.status(500).json({ success: false, message: '删除物流单失败', error: error.message });
res.status(500).json({ success: false, message: '删除物流单失败' });
}
});
+8 -7
View File
@@ -146,7 +146,7 @@ router.get('/pending', async (req, res) => {
res.status(500).json({
success: false,
message: '获取待执行付款列表失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -234,7 +234,7 @@ router.get('/executed', async (req, res) => {
res.status(500).json({
success: false,
message: '获取已执行付款记录失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -328,8 +328,9 @@ router.post('/execute', async (req, res) => {
await db.query(`
INSERT INTO payment_records
(code, payment_type, source_id, amount, currency, payment_date, voucher_url, payee_account, remark, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
`, [recordCode, payment_type, source_id, amount, 'CNY', paymentDate, voucher_url, payee_account, remark]);
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, CURRENT_TIMESTAMP)
RETURNING id`,
[recordCode, payment_type, source_id, amount, 'CNY', paymentDate, voucher_url, payee_account, remark]);
await db.query('COMMIT');
@@ -347,7 +348,7 @@ router.post('/execute', async (req, res) => {
res.status(500).json({
success: false,
message: '执行付款失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -407,7 +408,7 @@ router.get('/detail/:payment_type/:source_id', async (req, res) => {
res.status(500).json({
success: false,
message: '获取付款详情失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -491,7 +492,7 @@ router.get('/statistics', async (req, res) => {
res.status(500).json({
success: false,
message: '获取付款统计失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
+14 -12
View File
@@ -62,7 +62,7 @@ router.get('/', async (req, res) => {
res.status(500).json({
success: false,
message: '获取付款计划列表失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -101,7 +101,7 @@ router.get('/:id', async (req, res) => {
res.status(500).json({
success: false,
message: '获取付款计划详情失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -116,8 +116,9 @@ router.post('/', async (req, res) => {
const result = await db.query(`
INSERT INTO payment_plans
(purchase_order_id, stage, planned_date, planned_amount, planned_percentage, remark, status, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, 'pending', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
`, [purchase_order_id, stage, planned_date, planned_amount, planned_percentage, remark]);
VALUES ($1, $2, $3, $4, $5, $6, 'pending', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
RETURNING id`,
[purchase_order_id, stage, planned_date, planned_amount, planned_percentage, remark]);
res.json({
success: true,
@@ -129,7 +130,7 @@ router.post('/', async (req, res) => {
res.status(500).json({
success: false,
message: '创建付款计划失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -167,7 +168,7 @@ router.put('/:id', async (req, res) => {
res.status(500).json({
success: false,
message: '更新付款计划失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -197,7 +198,7 @@ router.delete('/:id', async (req, res) => {
res.status(500).json({
success: false,
message: '删除付款计划失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -237,8 +238,9 @@ router.post('/:id/create-request', async (req, res) => {
const requestResult = await db.query(`
INSERT INTO payment_requests
(code, payment_type, purchase_order_id, amount, currency, applicant, request_date, status, created_at)
VALUES (?, 'material', ?, ?, ?, '系统管理员', CURRENT_DATE, 'pending', CURRENT_TIMESTAMP)
`, [requestCode, plan.purchase_order_id, plan.planned_amount, plan.currency || 'CNY']);
VALUES ($1, 'material', $2, $3, $4, '系统管理员', CURRENT_DATE, 'pending', CURRENT_TIMESTAMP)
RETURNING id`,
[requestCode, plan.purchase_order_id, plan.planned_amount, plan.currency || 'CNY']);
await db.query(`
UPDATE payment_plans
@@ -262,7 +264,7 @@ router.post('/:id/create-request', async (req, res) => {
res.status(500).json({
success: false,
message: '创建付款申请失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -343,7 +345,7 @@ router.post('/:id/mark-paid', async (req, res) => {
res.status(500).json({
success: false,
message: '标记付款失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -379,7 +381,7 @@ router.get('/reminders/upcoming', async (req, res) => {
res.status(500).json({
success: false,
message: '获取待提醒付款计划失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
+2 -2
View File
@@ -16,7 +16,7 @@ router.get('/', async (req, res) => {
res.json({ success: true, data: result.rows, count: result.rows.length });
} catch (error) {
console.error('获取付款节点失败:', error);
res.status(500).json({ success: false, message: '获取付款节点失败', error: error.message });
res.status(500).json({ success: false, message: '获取付款节点失败' });
}
});
@@ -29,7 +29,7 @@ router.post('/', authenticate, async (req, res) => {
);
res.json({ success: true, data: { id: (result.rows[0]?.id || result.rows?.[0]?.id) }, message: '付款节点创建成功' });
} catch (error) {
res.status(500).json({ success: false, message: '创建付款节点失败', error: error.message });
res.status(500).json({ success: false, message: '创建付款节点失败' });
}
});
+2 -2
View File
@@ -17,7 +17,7 @@ router.get('/', async (req, res) => {
res.json({ success: true, data: result.rows, count: result.rows.length });
} catch (error) {
console.error('获取付款记录失败:', error);
res.status(500).json({ success: false, message: '获取付款记录失败', error: error.message });
res.status(500).json({ success: false, message: '获取付款记录失败' });
}
});
@@ -30,7 +30,7 @@ router.post('/', authenticate, async (req, res) => {
);
res.json({ success: true, data: { id: (result.rows[0]?.id || result.rows?.[0]?.id) }, message: '付款记录创建成功' });
} catch (error) {
res.status(500).json({ success: false, message: '创建付款记录失败', error: error.message });
res.status(500).json({ success: false, message: '创建付款记录失败' });
}
});
+10 -10
View File
@@ -36,7 +36,7 @@ router.get('/', async (req, res) => {
res.json({ success: true, data, count: data.length });
} catch (error) {
console.error('获取付款申请失败:', error);
res.status(500).json({ success: false, message: '获取付款申请失败', error: error.message });
res.status(500).json({ success: false, message: '获取付款申请失败' });
}
});
@@ -61,7 +61,7 @@ router.post('/', async (req, res) => {
payee, bank_account, bank_name, amount, currency, reason, payment_date,
request_code, status, applicant, detail_items, attachments,
payee_type, payee_id, expense_type, expense_category, project_id
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17`,
[
payee, finalBankAccount, finalBankName, finalAmount, currency || 'CNY',
reason, payment_date, requestCode, 'pending', applicant,
@@ -76,7 +76,7 @@ router.post('/', async (req, res) => {
res.json({ success: true, data: lastInsert.rows[0] });
} catch (error) {
console.error('创建付款申请失败:', error);
res.status(500).json({ success: false, message: '创建付款申请失败', error: error.message });
res.status(500).json({ success: false, message: '创建付款申请失败' });
}
});
@@ -112,7 +112,7 @@ router.get('/:id', async (req, res) => {
}
} catch (error) {
console.error('获取付款申请失败:', error);
res.status(500).json({ success: false, message: '获取付款申请失败', error: error.message });
res.status(500).json({ success: false, message: '获取付款申请失败' });
}
});
@@ -164,7 +164,7 @@ router.put('/:id', async (req, res) => {
}
} catch (error) {
console.error('更新付款申请失败:', error);
res.status(500).json({ success: false, message: '更新付款申请失败', error: error.message });
res.status(500).json({ success: false, message: '更新付款申请失败' });
}
});
@@ -181,7 +181,7 @@ router.delete('/:id', async (req, res) => {
}
} catch (error) {
console.error('删除付款申请失败:', error);
res.status(500).json({ success: false, message: '删除付款申请失败', error: error.message });
res.status(500).json({ success: false, message: '删除付款申请失败' });
}
});
@@ -198,7 +198,7 @@ router.post('/:id/submit', async (req, res) => {
}
} catch (error) {
console.error('提交付款申请失败:', error);
res.status(500).json({ success: false, message: '提交付款申请失败', error: error.message });
res.status(500).json({ success: false, message: '提交付款申请失败' });
}
});
@@ -215,7 +215,7 @@ router.post('/:id/withdraw', async (req, res) => {
}
} catch (error) {
console.error('撤回报销申请失败:', error);
res.status(500).json({ success: false, message: '撤回报销申请失败', error: error.message });
res.status(500).json({ success: false, message: '撤回报销申请失败' });
}
});
@@ -233,7 +233,7 @@ router.post('/:id/approve', async (req, res) => {
}
} catch (error) {
console.error('审批付款申请失败:', error);
res.status(500).json({ success: false, message: '审批付款申请失败', error: error.message });
res.status(500).json({ success: false, message: '审批付款申请失败' });
}
});
@@ -251,7 +251,7 @@ router.post('/:id/reject', async (req, res) => {
}
} catch (error) {
console.error('退回报销申请失败:', error);
res.status(500).json({ success: false, message: '退回报销申请失败', error: error.message });
res.status(500).json({ success: false, message: '退回报销申请失败' });
}
});
+10 -10
View File
@@ -47,7 +47,7 @@ router.get('/', async (req, res) => {
res.json({ success: true, data: result.rows, count: result.rows.length });
} catch (error) {
console.error('获取商品失败:', error);
res.status(500).json({ success: false, message: '获取商品失败', error: error.message });
res.status(500).json({ success: false, message: '获取商品失败' });
}
});
@@ -67,7 +67,7 @@ router.get('/template', (req, res) => {
res.send(buffer);
} catch (error) {
console.error('生成模板失败:', error);
res.status(500).json({ success: false, message: '生成模板失败', error: error.message });
res.status(500).json({ success: false, message: '生成模板失败' });
}
});
@@ -111,13 +111,13 @@ router.post('/batch-import', multer().single('file'), async (req, res) => {
);
imported.push({ row: i + 2, name: row['商品名称'], id: result.rows[0].id });
} catch (error) {
errors.push({ row: i + 2, name: row['商品名称'], error: error.message });
errors.push({ row: i + 2, name: row['商品名称'] });
}
}
res.json({ success: true, message: `导入完成,成功 ${imported.length} 条,失败 ${errors.length}`, imported, errors });
} catch (error) {
console.error('批量导入商品失败:', error);
res.status(500).json({ success: false, message: '批量导入商品失败', error: error.message });
res.status(500).json({ success: false, message: '批量导入商品失败' });
}
});
@@ -153,13 +153,13 @@ router.post('/import', multer().single('file'), async (req, res) => {
);
imported.push({ row: i + 2, name: row['商品名称'], id: result.rows[0].id });
} catch (error) {
errors.push({ row: i + 2, name: row['商品名称'], error: error.message });
errors.push({ row: i + 2, name: row['商品名称'] });
}
}
res.json({ success: true, message: `导入完成,成功 ${imported.length} 条,失败 ${errors.length}`, imported, errors });
} catch (error) {
console.error('导入商品失败:', error);
res.status(500).json({ success: false, message: '导入商品失败', error: error.message });
res.status(500).json({ success: false, message: '导入商品失败' });
}
});
@@ -177,7 +177,7 @@ router.get('/:id', async (req, res) => {
res.json({ success: true, data: result.rows[0] });
} catch (error) {
console.error('获取商品失败:', error);
res.status(500).json({ success: false, message: '获取商品失败', error: error.message });
res.status(500).json({ success: false, message: '获取商品失败' });
}
});
@@ -197,7 +197,7 @@ router.post('/', async (req, res) => {
res.json({ success: true, data: result.rows[0], message: '创建成功' });
} catch (error) {
console.error('创建商品失败:', error);
res.status(500).json({ success: false, message: '创建商品失败', error: error.message });
res.status(500).json({ success: false, message: '创建商品失败' });
}
});
@@ -234,7 +234,7 @@ router.put('/:id', async (req, res) => {
res.json({ success: true, data: result.rows[0], message: '更新成功' });
} catch (error) {
console.error('更新商品失败:', error);
res.status(500).json({ success: false, message: '更新商品失败', error: error.message });
res.status(500).json({ success: false, message: '更新商品失败' });
}
});
@@ -246,7 +246,7 @@ router.delete('/:id', async (req, res) => {
res.json({ success: true, message: '删除成功' });
} catch (error) {
console.error('删除商品失败:', error);
res.status(500).json({ success: false, message: '删除商品失败', error: error.message });
res.status(500).json({ success: false, message: '删除商品失败' });
}
});
+9 -9
View File
@@ -39,7 +39,7 @@ router.get('/inventory/:projectId', async (req, res) => {
res.status(500).json({
success: false,
message: '获取项目材料库存失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -73,7 +73,7 @@ router.get('/inventory/:projectId/summary', async (req, res) => {
res.status(500).json({
success: false,
message: '获取项目材料库存汇总失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -116,7 +116,7 @@ router.get('/purchases/:projectId', async (req, res) => {
res.status(500).json({
success: false,
message: '获取项目采购记录失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -165,7 +165,7 @@ router.get('/returns/:projectId', async (req, res) => {
res.status(500).json({
success: false,
message: '获取项目退库记录失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -213,7 +213,7 @@ router.get('/price-history/:productId', async (req, res) => {
res.status(500).json({
success: false,
message: '获取材料价格历史失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -245,7 +245,7 @@ router.get('/average-price/:productId', async (req, res) => {
res.status(500).json({
success: false,
message: '获取材料平均价格失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -281,7 +281,7 @@ router.get('/price-trend/:productId', async (req, res) => {
res.status(500).json({
success: false,
message: '获取材料价格趋势失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -323,7 +323,7 @@ router.put('/inventory/:projectId/:productId', async (req, res) => {
res.status(500).json({
success: false,
message: '更新材料库存失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -356,7 +356,7 @@ router.get('/all-projects-summary', async (req, res) => {
res.status(500).json({
success: false,
message: '获取所有项目材料汇总失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
+73 -33
View File
@@ -37,7 +37,7 @@ router.get('/', async (req, res) => {
res.status(500).json({
success: false,
message: '获取项目失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -130,7 +130,7 @@ router.get('/:id', async (req, res) => {
res.status(500).json({
success: false,
message: '获取项目详情失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -154,7 +154,7 @@ router.get('/:id/contracts', async (req, res) => {
res.status(500).json({
success: false,
message: '获取项目合同失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -192,7 +192,7 @@ router.get('/:id/subcontracts', async (req, res) => {
res.status(500).json({
success: false,
message: '获取项目分包失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -206,7 +206,8 @@ router.post('/:id/subcontracts', async (req, res) => {
const result = await db.query(
`INSERT INTO subcontracts (project_id, subcontractor_id, subcontractor_name, contract_amount, currency, settlement_type, other_terms, payment_description, unit_price_items, start_date, end_date, work_days, paid_amount, status, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`,
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
RETURNING id`,
[id, subcontractor_id, subcontractor_name, contract_amount, currency || 'CNY', settlement_type || 'lump_sum', other_terms, payment_description, unitPriceItemsJson, start_date, end_date, work_days, 0, status || 'active']
);
@@ -239,7 +240,7 @@ router.post('/:id/subcontracts', async (req, res) => {
res.status(500).json({
success: false,
message: '新增项目分包失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -263,7 +264,7 @@ router.get('/:id/materials', async (req, res) => {
res.status(500).json({
success: false,
message: '获取项目材料失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -287,7 +288,7 @@ router.get('/:id/milestones', async (req, res) => {
res.status(500).json({
success: false,
message: '获取项目施工节点失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -311,7 +312,7 @@ router.get('/:id/finances', async (req, res) => {
res.status(500).json({
success: false,
message: '获取项目财务失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -335,7 +336,7 @@ router.get('/:id/warranty-deposits', async (req, res) => {
res.status(500).json({
success: false,
message: '获取项目质保金失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -354,7 +355,7 @@ router.get('/:id/construction-logs', async (req, res) => {
res.status(500).json({
success: false,
message: '获取项目施工日志失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -366,9 +367,10 @@ router.post('/', async (req, res) => {
const projectCode = code || 'PROJ' + new Date().toISOString().slice(0, 10).replace(/-/g, '') +
String(Math.floor(Math.random() * 10000)).padStart(4, '0');
const result = await db.query(
`INSERT INTO projects (name, code, customer_id, manager_id, contract_amount, start_date, end_date, description, status, location, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`,
const result = await db.query(
`INSERT INTO projects (name, code, customer_id, project_manager_id, contract_amount, start_date, end_date, description, status, location, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
RETURNING id`,
[name, projectCode, customer_id || null, manager_id || null, contract_amount || 0, start_date || '', end_date || '', description || '', status || 'planning', location || '']
);
@@ -382,7 +384,7 @@ router.post('/', async (req, res) => {
res.status(500).json({
success: false,
message: '创建项目失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -394,7 +396,7 @@ router.delete('/:id', authenticate, requireAdmin, async (req, res) => {
res.json({ success: true, message: '项目已删除' });
} catch (error) {
console.error('删除项目失败:', error);
res.status(500).json({ success: false, message: error.message });
res.status(500).json({ success: false, message: '操作失败' });
}
});
@@ -403,33 +405,69 @@ router.put('/:id', async (req, res) => {
const { id } = req.params;
const { name, manager_id, location, start_date, end_date, description, status, contract_amount } = req.body;
console.log('收到项目更新请求:', { id, name, manager_id, location, start_date, end_date, description });
const updates = [];
const values = [];
let paramIndex = 1;
if (name !== undefined && name !== null) {
updates.push(`name = $${paramIndex++}`);
values.push(name);
}
if (manager_id !== undefined && manager_id !== null) {
updates.push(`project_manager_id = $${paramIndex++}`);
values.push(manager_id);
}
if (location !== undefined && location !== null) {
updates.push(`location = $${paramIndex++}`);
values.push(location);
}
if (start_date !== undefined && start_date !== null) {
updates.push(`start_date = $${paramIndex++}`);
values.push(start_date);
}
if (end_date !== undefined && end_date !== null) {
updates.push(`end_date = $${paramIndex++}`);
values.push(end_date);
}
if (description !== undefined && description !== null) {
updates.push(`description = $${paramIndex++}`);
values.push(description);
}
if (status !== undefined && status !== null) {
updates.push(`status = $${paramIndex++}`);
values.push(status);
}
if (contract_amount !== undefined && contract_amount !== null) {
updates.push(`contract_amount = $${paramIndex++}`);
values.push(contract_amount);
}
updates.push(`updated_at = CURRENT_TIMESTAMP`);
if (updates.length > 1) {
values.push(id);
await db.query(
`UPDATE projects SET ${updates.join(', ')} WHERE id = $${paramIndex}`,
values
);
}
// 更新项目信息
await db.query(
'UPDATE projects SET name = CASE WHEN $1 IS NOT NULL THEN $2 ELSE name END, manager_id = CASE WHEN $3 IS NOT NULL THEN $4 ELSE manager_id END, location = CASE WHEN $5 IS NOT NULL THEN $6 ELSE location END, start_date = CASE WHEN $7 IS NOT NULL THEN $8 ELSE start_date END, end_date = CASE WHEN $9 IS NOT NULL THEN $10 ELSE end_date END, description = CASE WHEN $11 IS NOT NULL THEN $12 ELSE description END, status = CASE WHEN $13 IS NOT NULL THEN $14 ELSE status END, contract_amount = CASE WHEN $15 IS NOT NULL THEN $16 ELSE contract_amount END, updated_at = CURRENT_TIMESTAMP WHERE id = $17',
[name, name, manager_id, manager_id, location, location, start_date, start_date, end_date, end_date, description, description, status, status, contract_amount, contract_amount, id]
);
// 如果提供了开始和结束日期,更新合同的工期信息
if (start_date && end_date) {
const start = new Date(start_date);
const end = new Date(end_date);
const contractPeriod = Math.floor((end.getTime() - start.getTime()) / (1000 * 60 * 60 * 24)) + 1;
// 更新合同信息
await db.query(
'UPDATE project_contracts SET start_date = $1, end_date = $2, contract_period = $3 WHERE project_id = $4',
[start_date, end_date, contractPeriod, id]
);
}
// 查询更新后的数据
const updatedResult = await db.query('SELECT * FROM projects WHERE id = $1', [id]);
res.json({ success: true, data: updatedResult.rows[0] });
} catch (error) {
console.error('更新项目失败:', error);
res.status(500).json({ success: false, message: error.message });
res.status(500).json({ success: false, message: '操作失败' });
}
});
@@ -447,7 +485,6 @@ router.put('/:id/contract', async (req, res) => {
contract_file
} = req.body;
console.log('收到合同细节保存请求:', { id, project_overview, settlement_type, contract_total, tax_included, contract_file });
// 1. 更新项目基本信息
await db.query(
@@ -476,7 +513,8 @@ router.put('/:id/contract', async (req, res) => {
const contractCode = `CONTRACT-${Date.now()}`;
await db.query(
`INSERT INTO project_contracts (project_id, contract_code, contract_amount, settlement_method, contract_file, other_info, tax_included, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`,
VALUES ($1, $2, $3, $4, $5, $6, $7, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
RETURNING id`,
[id, contractCode, contract_total, settlement_type, contract_file, other_info, tax_included]
);
}
@@ -490,7 +528,8 @@ router.put('/:id/contract', async (req, res) => {
for (const node of payment_nodes) {
await db.query(
`INSERT INTO project_milestones (project_id, milestone_name, condition, percentage, amount, status, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`,
VALUES ($1, $2, $3, $4, $5, $6, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
RETURNING id`,
[id, node.name, node.condition || '', node.percentage, node.amount, 'pending']
);
}
@@ -505,7 +544,8 @@ router.put('/:id/contract', async (req, res) => {
for (const item of unit_price_items) {
await db.query(
`INSERT INTO project_materials (project_id, product_name, unit, budget_quantity, average_price, total_amount, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`,
VALUES ($1, $2, $3, $4, $5, $6, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
RETURNING id`,
[id, item.name, item.unit, item.quantity, item.price, item.total]
);
}
@@ -517,7 +557,7 @@ router.put('/:id/contract', async (req, res) => {
});
} catch (error) {
console.error('保存合同细节失败:', error);
res.status(500).json({ success: false, message: error.message });
res.status(500).json({ success: false, message: '操作失败' });
}
});
@@ -577,7 +617,7 @@ router.get('/:id/cost-summary', async (req, res) => {
res.status(500).json({
success: false,
message: '获取项目成本统计失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
+32 -27
View File
@@ -65,7 +65,7 @@ router.get('/', async (req, res) => {
res.status(500).json({
success: false,
message: '获取采购订单列表失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -152,7 +152,7 @@ router.get('/:id', async (req, res) => {
res.status(500).json({
success: false,
message: '获取采购订单详情失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -170,8 +170,9 @@ router.post('/', async (req, res) => {
const result = await db.query(`
INSERT INTO purchase_orders
(code, purchase_request_id, project_id, supplier_id, currency, status, remark, created_by, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, 'draft', ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
`, [orderCode, purchase_request_id, project_id, supplier_id, currency || 'CNY', remark, created_by]);
VALUES ($1, $2, $3, $4, $5, 'draft', $6, $7, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
RETURNING id`,
[orderCode, purchase_request_id, project_id, supplier_id, currency || 'CNY', remark, created_by]);
res.json({
success: true,
@@ -183,7 +184,7 @@ router.post('/', async (req, res) => {
res.status(500).json({
success: false,
message: '创建采购订单失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -215,7 +216,7 @@ router.put('/:id', async (req, res) => {
res.status(500).json({
success: false,
message: '更新采购订单失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -254,8 +255,9 @@ router.post('/:id/confirm', async (req, res) => {
await db.query(`
INSERT INTO material_price_history
(product_id, purchase_order_id, supplier_id, supplier_country, unit_price, currency, quantity, purchase_date, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, CURRENT_DATE, CURRENT_TIMESTAMP)
`, [item.product_id, id, order.supplier_id, order.supplier_country, item.unit_price, order.currency, item.quantity]);
VALUES ($1, $2, $3, $4, $5, $6, $7, CURRENT_DATE, CURRENT_TIMESTAMP)
RETURNING id`,
[item.product_id, id, order.supplier_id, order.supplier_country, item.unit_price, order.currency, item.quantity]);
}
if (payment_stages && payment_stages.length > 0) {
@@ -263,8 +265,9 @@ router.post('/:id/confirm', async (req, res) => {
await db.query(`
INSERT INTO payment_plans
(purchase_order_id, stage, planned_date, planned_amount, planned_percentage, status, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, 'pending', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
`, [id, stage.name, stage.date, stage.amount, stage.percentage]);
VALUES ($1, $2, $3, $4, $5, 'pending', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
RETURNING id`,
[id, stage.name, stage.date, stage.amount, stage.percentage]);
}
}
@@ -283,7 +286,7 @@ router.post('/:id/confirm', async (req, res) => {
res.status(500).json({
success: false,
message: '确认订单失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -307,7 +310,7 @@ router.post('/:id/cancel', async (req, res) => {
res.json({ success: true, message: '订单已取消' });
} catch (error) {
console.error('取消订单失败:', error);
res.status(500).json({ success: false, message: '取消订单失败', error: error.message });
res.status(500).json({ success: false, message: '取消订单失败' });
}
});
@@ -335,7 +338,7 @@ router.get('/:id/items', async (req, res) => {
res.status(500).json({
success: false,
message: '获取订单商品明细失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -353,8 +356,9 @@ router.post('/:id/items', async (req, res) => {
const result = await db.query(`
INSERT INTO purchase_order_items
(purchase_order_id, product_id, product_name, specification, unit, quantity, unit_price, total_price, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
`, [id, product_id, product_name, specification, unit, quantity, unit_price, total_price]);
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, CURRENT_TIMESTAMP)
RETURNING id`,
[id, product_id, product_name, specification, unit, quantity, unit_price, total_price]);
res.json({
success: true,
@@ -366,7 +370,7 @@ router.post('/:id/items', async (req, res) => {
res.status(500).json({
success: false,
message: '添加商品明细失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -397,7 +401,7 @@ router.put('/:id/items/:itemId', async (req, res) => {
res.status(500).json({
success: false,
message: '更新商品明细失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -424,7 +428,7 @@ router.delete('/:id/items/:itemId', async (req, res) => {
res.status(500).json({
success: false,
message: '删除商品明细失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -453,7 +457,7 @@ router.get('/:id/payment-plans', async (req, res) => {
res.status(500).json({
success: false,
message: '获取付款计划失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -469,8 +473,9 @@ router.post('/:id/payment-plans', async (req, res) => {
const result = await db.query(`
INSERT INTO payment_plans
(purchase_order_id, stage, planned_date, planned_amount, planned_percentage, remark, status, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, 'pending', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
`, [id, stage, planned_date, planned_amount, planned_percentage, remark]);
VALUES ($1, $2, $3, $4, $5, $6, 'pending', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
RETURNING id`,
[id, stage, planned_date, planned_amount, planned_percentage, remark]);
res.json({
success: true,
@@ -482,7 +487,7 @@ router.post('/:id/payment-plans', async (req, res) => {
res.status(500).json({
success: false,
message: '添加付款计划失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -511,7 +516,7 @@ router.put('/:id/payment-plans/:planId', async (req, res) => {
res.status(500).json({
success: false,
message: '更新付款计划失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -538,7 +543,7 @@ router.delete('/:id/payment-plans/:planId', async (req, res) => {
res.status(500).json({
success: false,
message: '删除付款计划失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -567,7 +572,7 @@ router.get('/:id/logistics', async (req, res) => {
res.status(500).json({
success: false,
message: '获取物流信息失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -596,7 +601,7 @@ router.get('/:id/verifications', async (req, res) => {
res.status(500).json({
success: false,
message: '获取验收记录失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -627,7 +632,7 @@ router.delete('/:id', async (req, res) => {
res.status(500).json({
success: false,
message: '删除采购订单失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
+38 -53
View File
@@ -44,7 +44,7 @@ router.get('/', async (req, res) => {
const data = result.rows.map(row => ({
...row,
request_code: row.code
request_code: row.request_code
}));
res.json({
@@ -57,7 +57,7 @@ router.get('/', async (req, res) => {
res.status(500).json({
success: false,
message: '获取采购申请列表失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -74,7 +74,7 @@ router.get('/:id', async (req, res) => {
SELECT pr.*, p.name as project_name
FROM purchase_requests pr
LEFT JOIN projects p ON pr.project_id = p.id
WHERE pr.id = ?
WHERE pr.id = $1
`, [id]);
if (requestResult.rows.length === 0) {
@@ -82,20 +82,7 @@ router.get('/:id', async (req, res) => {
}
const purchaseRequest = requestResult.rows[0];
purchaseRequest.request_code = purchaseRequest.code;
if (purchaseRequest.attachments) {
if (typeof purchaseRequest.attachments === 'string') {
purchaseRequest.attachments = purchaseRequest.attachments.split(',').map((url) => ({
url: url,
name: url.split('/').pop() || '',
uid: url,
status: 'done'
}));
}
} else {
purchaseRequest.attachments = [];
}
// request_code is already in the table, no need to alias from code
res.json({
success: true,
@@ -106,7 +93,7 @@ router.get('/:id', async (req, res) => {
res.status(500).json({
success: false,
message: '获取采购申请详情失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -127,9 +114,8 @@ router.get('/:id', async (req, res) => {
router.post('/', async (req, res) => {
try {
const {
project_id, applicant, request_date,
expense_category, total_amount, currency, remark, attachments,
purchase_type, brief_description, expected_date
project_id, total_amount, currency,
purchase_type, remark
} = req.body;
const date = new Date();
@@ -137,12 +123,12 @@ router.post('/', async (req, res) => {
const result = await db.query(`
INSERT INTO purchase_requests
(code, title, project_id, applicant, request_date, expense_category, total_amount, currency,
status, purchase_type, brief_description, expected_date, remark, attachments, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
`, [requestCode, brief_description || '采购申请', project_id, applicant || '系统管理员',
request_date, expense_category || 'material', total_amount || 0, currency || 'CNY',
'pending_edit', purchase_type || 'inventory', brief_description, expected_date, remark, attachments || '']);
(request_code, project_id, purchase_type, total_amount, currency,
status, remarks, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
RETURNING id`,
[requestCode, project_id, purchase_type || 'inventory', total_amount || 0, currency || 'CNY',
'pending_edit', remark]);
res.json({
success: true,
@@ -154,7 +140,7 @@ router.post('/', async (req, res) => {
res.status(500).json({
success: false,
message: '创建采购申请失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -167,21 +153,19 @@ router.put('/:id', async (req, res) => {
try {
const { id } = req.params;
const {
project_id, applicant, request_date,
expense_category, total_amount, currency, remark, attachments,
purchase_type, brief_description, expected_date
project_id, total_amount, currency,
purchase_type, remark
} = req.body;
const result = await db.query(`
UPDATE purchase_requests
SET project_id = ?, applicant = ?, request_date = ?, expense_category = ?,
total_amount = ?, currency = ?, purchase_type = ?, brief_description = ?,
expected_date = ?, remark = ?, attachments = ?, updated_at = CURRENT_TIMESTAMP
WHERE id = ?
`, [project_id, applicant, request_date, expense_category, total_amount, currency || 'CNY',
purchase_type || 'inventory', brief_description, expected_date, remark, attachments || '', id]);
SET project_id = $1, total_amount = $2, currency = $3,
purchase_type = $4, remarks = $5, updated_at = CURRENT_TIMESTAMP
WHERE id = $6
`, [project_id, total_amount, currency || 'CNY',
purchase_type || 'inventory', remark, id]);
if (result.changes === 0) {
if (result.rowCount === 0) {
return res.status(404).json({ success: false, message: '采购申请不存在' });
}
@@ -194,7 +178,7 @@ router.put('/:id', async (req, res) => {
res.status(500).json({
success: false,
message: '更新采购申请失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -208,7 +192,7 @@ router.delete('/:id', async (req, res) => {
const result = await db.query('DELETE FROM purchase_requests WHERE id = $1', [id]);
if (result.changes === 0) {
if (result.rowCount === 0) {
return res.status(404).json({ success: false, message: '采购申请不存在' });
}
@@ -221,7 +205,7 @@ router.delete('/:id', async (req, res) => {
res.status(500).json({
success: false,
message: '删除采购申请失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -239,14 +223,14 @@ router.post('/:id/submit', async (req, res) => {
['pending', id]
);
if (result.changes === 0) {
if (result.rowCount === 0) {
return res.status(404).json({ success: false, message: '采购申请不存在' });
}
res.json({ success: true, message: '提交成功' });
} catch (error) {
console.error('提交采购申请失败:', error);
res.status(500).json({ success: false, message: '提交采购申请失败', error: error.message });
res.status(500).json({ success: false, message: '提交采购申请失败' });
}
});
@@ -282,16 +266,17 @@ router.post('/:id/approve', async (req, res) => {
const orderResult = await db.query(`
INSERT INTO purchase_orders
(code, purchase_request_id, project_id, estimated_amount, currency, status, created_by, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
`, [orderCode, id, purchaseRequest.project_id, purchaseRequest.total_amount,
purchaseRequest.currency || 'CNY', 'draft', purchaseRequest.applicant || '系统']);
VALUES ($1, $2, $3, $4, $5, $6, $7, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
RETURNING id`,
[orderCode, id, purchaseRequest.project_id, purchaseRequest.total_amount,
purchaseRequest.currency || 'CNY', 'draft', purchaseRequest.created_by || 1]);
await db.query('COMMIT');
res.json({
success: true,
message: '审批通过成功,已自动生成采购订单草稿',
data: { order_id: orderResult.lastID, order_code: orderCode }
data: { order_id: orderResult.rows[0]?.id, order_code: orderCode }
});
} catch (innerError) {
await db.query('ROLLBACK');
@@ -299,7 +284,7 @@ router.post('/:id/approve', async (req, res) => {
}
} catch (error) {
console.error('审批采购申请失败:', error);
res.status(500).json({ success: false, message: '审批采购申请失败', error: error.message });
res.status(500).json({ success: false, message: '审批采购申请失败' });
}
});
@@ -316,14 +301,14 @@ router.post('/:id/reject', async (req, res) => {
['pending_edit', id]
);
if (result.changes === 0) {
if (result.rowCount === 0) {
return res.status(404).json({ success: false, message: '采购申请不存在' });
}
res.json({ success: true, message: '驳回成功' });
} catch (error) {
console.error('驳回采购申请失败:', error);
res.status(500).json({ success: false, message: '驳回采购申请失败', error: error.message });
res.status(500).json({ success: false, message: '驳回采购申请失败' });
}
});
@@ -340,14 +325,14 @@ router.post('/:id/withdraw', async (req, res) => {
['withdrawn', id]
);
if (result.changes === 0) {
if (result.rowCount === 0) {
return res.status(404).json({ success: false, message: '采购申请不存在' });
}
res.json({ success: true, message: '撤回成功' });
} catch (error) {
console.error('撤回采购申请失败:', error);
res.status(500).json({ success: false, message: '撤回采购申请失败', error: error.message });
res.status(500).json({ success: false, message: '撤回采购申请失败' });
}
});
@@ -367,7 +352,7 @@ router.post('/:id/execute', async (req, res) => {
res.json({ success: true, message: '执行成功' });
} catch (error) {
console.error('执行采购申请失败:', error);
res.status(500).json({ success: false, message: '执行采购申请失败', error: error.message });
res.status(500).json({ success: false, message: '执行采购申请失败' });
}
});
+9 -9
View File
@@ -58,7 +58,7 @@ router.get('/', async (req, res) => {
res.status(500).json({
success: false,
message: '获取报销记录失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -85,7 +85,7 @@ router.post('/', [
res.json({ success: true, data: lastInsert.rows[0] });
} catch (error) {
console.error('创建报销申请失败:', error);
res.status(500).json({ success: false, message: '创建报销申请失败', error: error.message });
res.status(500).json({ success: false, message: '创建报销申请失败' });
}
});
@@ -123,7 +123,7 @@ router.get('/:id', async (req, res) => {
}
} catch (error) {
console.error('获取报销申请失败:', error);
res.status(500).json({ success: false, message: '获取报销申请失败', error: error.message });
res.status(500).json({ success: false, message: '获取报销申请失败' });
}
});
@@ -144,7 +144,7 @@ router.put('/:id', async (req, res) => {
}
} catch (error) {
console.error('更新报销申请失败:', error);
res.status(500).json({ success: false, message: '更新报销申请失败', error: error.message });
res.status(500).json({ success: false, message: '更新报销申请失败' });
}
});
@@ -161,7 +161,7 @@ router.delete('/:id', async (req, res) => {
}
} catch (error) {
console.error('删除报销申请失败:', error);
res.status(500).json({ success: false, message: '删除报销申请失败', error: error.message });
res.status(500).json({ success: false, message: '删除报销申请失败' });
}
});
@@ -178,7 +178,7 @@ router.post('/:id/submit', async (req, res) => {
}
} catch (error) {
console.error('提交报销申请失败:', error);
res.status(500).json({ success: false, message: '提交报销申请失败', error: error.message });
res.status(500).json({ success: false, message: '提交报销申请失败' });
}
});
@@ -195,7 +195,7 @@ router.post('/:id/withdraw', async (req, res) => {
}
} catch (error) {
console.error('撤回报销申请失败:', error);
res.status(500).json({ success: false, message: '撤回报销申请失败', error: error.message });
res.status(500).json({ success: false, message: '撤回报销申请失败' });
}
});
@@ -213,7 +213,7 @@ router.post('/:id/approve', async (req, res) => {
}
} catch (error) {
console.error('审批报销申请失败:', error);
res.status(500).json({ success: false, message: '审批报销申请失败', error: error.message });
res.status(500).json({ success: false, message: '审批报销申请失败' });
}
});
@@ -231,7 +231,7 @@ router.post('/:id/reject', async (req, res) => {
}
} catch (error) {
console.error('退回报销申请失败:', error);
res.status(500).json({ success: false, message: '退回报销申请失败', error: error.message });
res.status(500).json({ success: false, message: '退回报销申请失败' });
}
});
+11 -10
View File
@@ -58,7 +58,7 @@ router.get('/', async (req, res) => {
res.status(500).json({
success: false,
message: '获取退库单列表失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -102,7 +102,7 @@ router.get('/:id', async (req, res) => {
res.status(500).json({
success: false,
message: '获取退库单详情失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -137,8 +137,9 @@ router.post('/', async (req, res) => {
INSERT INTO return_records
(code, project_id, return_type, return_date, applicant, items,
total_quantity, total_amount, cost_adjustment, refund_amount, status, remark, attachments, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending', ?, ?, CURRENT_TIMESTAMP)
`, [code, project_id, return_type || 'warehouse', return_date, applicant, itemsJson,
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, 'pending', $11, $12, CURRENT_TIMESTAMP)
RETURNING id`,
[code, project_id, return_type || 'warehouse', return_date, applicant, itemsJson,
total_quantity || calcTotalQty, total_amount || calcTotalAmt, cost_adjustment || 0,
refund_amount || 0, remark, attachments]);
@@ -152,7 +153,7 @@ router.post('/', async (req, res) => {
res.status(500).json({
success: false,
message: '创建退库单失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -228,7 +229,7 @@ router.post('/:id/confirm', async (req, res) => {
res.status(500).json({
success: false,
message: '确认退库失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -254,7 +255,7 @@ router.post('/:id/reject', async (req, res) => {
res.json({ success: true, message: '退库已驳回' });
} catch (error) {
console.error('驳回退库失败:', error);
res.status(500).json({ success: false, message: '驳回退库失败', error: error.message });
res.status(500).json({ success: false, message: '驳回退库失败' });
}
});
@@ -291,7 +292,7 @@ router.put('/:id', async (req, res) => {
res.status(500).json({
success: false,
message: '更新退库单失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -321,7 +322,7 @@ router.delete('/:id', async (req, res) => {
res.status(500).json({
success: false,
message: '删除退库单失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -350,7 +351,7 @@ router.get('/project-materials/:projectId', async (req, res) => {
res.status(500).json({
success: false,
message: '获取项目材料列表失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
+228 -220
View File
@@ -1,10 +1,10 @@
const express = require('express');
const db = require('../db');
const { authenticate, requireAdmin } = require('../middleware/auth');
const LedgerService = require('../services/ledgerService');
const router = express.Router();
const express = require('express');
const db = require('../db');
const { authenticate, requireAdmin } = require('../middleware/auth');
const LedgerService = require('../services/ledgerService');
const router = express.Router();
router.get('/', async (req, res) => {
try {
const result = await db.query(`
@@ -26,7 +26,7 @@ router.get('/', async (req, res) => {
name: contact.name || '未命名',
position: contact.position || '',
phone: contact.phone || '',
is_primary: contact.is_primary === 1
is_primary: contact.is_primary === true
}));
// 获取收款信息
@@ -41,11 +41,15 @@ router.get('/', async (req, res) => {
bank_account: payment.bank_account,
bank_name: payment.bank_name,
qr_code: payment.qr_code,
is_primary: payment.is_primary === 1
is_primary: payment.is_primary === true
}));
return {
...subcontractor,
scope: subcontractor.specialty,
features: subcontractor.category,
country: subcontractor.country,
remark: subcontractor.remark || '',
contacts: contacts.length > 0 ? contacts : [],
payment_infos: paymentInfos.length > 0 ? paymentInfos : []
};
@@ -62,11 +66,11 @@ router.get('/', async (req, res) => {
res.status(500).json({
success: false,
message: '获取分包商失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
router.get('/:id', async (req, res) => {
try {
const { id } = req.params;
@@ -74,7 +78,7 @@ router.get('/:id', async (req, res) => {
// 获取分包商基本信息
const subcontractorResult = await db.query(`
SELECT * FROM subcontractors
WHERE id = ?
WHERE id = $1
`, [id]);
if (subcontractorResult.rows.length > 0) {
@@ -83,7 +87,7 @@ router.get('/:id', async (req, res) => {
// 获取分包商的所有联系人
const contactsResult = await db.query(`
SELECT * FROM contacts
WHERE entity_id = ? AND entity_type = 'subcontractor'
WHERE entity_id = $1 AND entity_type = 'subcontractor'
ORDER BY is_primary DESC
`, [id]);
@@ -92,13 +96,13 @@ router.get('/:id', async (req, res) => {
name: contact.name || '未命名',
position: contact.position || '',
phone: contact.phone || '',
is_primary: contact.is_primary === 1
is_primary: contact.is_primary === true
}));
// 获取分包商的所有收款信息
const paymentInfosResult = await db.query(`
SELECT * FROM subcontractor_payment_infos
WHERE subcontractor_id = ?
WHERE subcontractor_id = $1
ORDER BY is_primary DESC
`, [id]);
@@ -109,7 +113,7 @@ router.get('/:id', async (req, res) => {
bank_account: payment.bank_account,
bank_name: payment.bank_name,
qr_code: payment.qr_code,
is_primary: payment.is_primary === 1
is_primary: payment.is_primary === true
}));
// 获取业务台账
@@ -120,8 +124,8 @@ router.get('/:id', async (req, res) => {
id: subcontractor.id,
code: `SC${String(subcontractor.id).padStart(4, '0')}`,
name: subcontractor.name,
scope: subcontractor.scope || '',
features: subcontractor.features || '',
scope: subcontractor.specialty || '',
features: subcontractor.category || '',
country: subcontractor.country || '',
contacts: contacts.length > 0 ? contacts : [],
payment_infos: paymentInfos.length > 0 ? paymentInfos : [],
@@ -148,27 +152,27 @@ router.get('/:id', async (req, res) => {
res.status(500).json({
success: false,
message: '获取分包商详情失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
router.post('/', async (req, res) => {
try {
const { name, scope, features, country, remark, contacts, payment_infos } = req.body;
// 从contacts中获取主联系人信息
const primaryContact = contacts?.find(c => c.is_primary) || contacts?.[0];
const contact = primaryContact?.name || '';
const position = primaryContact?.position || '';
const contact_person = primaryContact?.name || '';
const phone = primaryContact?.phone || '';
const email = ''; // 前端没有email字段
const address = ''; // 前端没有address字段
const result = await db.query(
`INSERT INTO subcontractors (name, address, contact, position, phone, email, scope, features, country, remark, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`,
[name, address, contact, position, phone, email, scope, features, country, remark]
`INSERT INTO subcontractors (name, address, contact_person, phone, email, specialty, category, country, remark, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
RETURNING id`,
[name, address, contact_person, phone, email, scope, features, country || '', remark || '']
);
const subcontractorId = (result.rows[0]?.id || result.rows?.[0]?.id);
@@ -178,8 +182,9 @@ router.post('/', async (req, res) => {
for (const contactItem of contacts) {
await db.query(
`INSERT INTO contacts (entity_id, entity_type, name, position, phone, is_primary, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`,
[subcontractorId, 'subcontractor', contactItem.name, contactItem.position, contactItem.phone, contactItem.is_primary ? 1 : 0]
VALUES ($1, $2, $3, $4, $5, $6, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
RETURNING id`,
[subcontractorId, 'subcontractor', contactItem.name, contactItem.position, contactItem.phone, contactItem.is_primary ? true : false]
);
}
}
@@ -189,8 +194,9 @@ router.post('/', async (req, res) => {
for (const paymentItem of payment_infos) {
await db.query(
`INSERT INTO subcontractor_payment_infos (subcontractor_id, account_name, bank_name, bank_account, qr_code, is_primary, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`,
[subcontractorId, paymentItem.account_name, paymentItem.bank_name, paymentItem.bank_account, paymentItem.qr_code, paymentItem.is_primary ? 1 : 0]
VALUES ($1, $2, $3, $4, $5, $6, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
RETURNING id`,
[subcontractorId, paymentItem.account_name, paymentItem.bank_name, paymentItem.bank_account, paymentItem.qr_code, paymentItem.is_primary ? true : false]
);
}
}
@@ -219,11 +225,11 @@ router.post('/', async (req, res) => {
res.status(500).json({
success: false,
message: '创建分包商失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
router.put('/:id', async (req, res) => {
try {
const { id } = req.params;
@@ -231,17 +237,16 @@ router.put('/:id', async (req, res) => {
// 从contacts中获取主联系人信息
const primaryContact = contacts?.find(c => c.is_primary) || contacts?.[0];
const contact = primaryContact?.name || '';
const position = primaryContact?.position || '';
const contact_person = primaryContact?.name || '';
const phone = primaryContact?.phone || '';
const email = ''; // 前端没有email字段
const address = ''; // 前端没有address字段
await db.query(
`UPDATE subcontractors
SET name = ?, address = ?, contact = ?, position = ?, phone = ?, email = ?, scope = ?, features = ?, country = ?, remark = ?, updated_at = CURRENT_TIMESTAMP
WHERE id = ?`,
[name, address, contact, position, phone, email, scope, features, country, remark, id]
SET name = $1, address = $2, contact_person = $3, phone = $4, email = $5, specialty = $6, category = $7, country = $8, remark = $9, updated_at = CURRENT_TIMESTAMP
WHERE id = $10`,
[name, address, contact_person, phone, email, scope, features, country || '', remark || '', id]
);
// 删除旧的联系人数据
@@ -252,8 +257,9 @@ router.put('/:id', async (req, res) => {
for (const contactItem of contacts) {
await db.query(
`INSERT INTO contacts (entity_id, entity_type, name, position, phone, is_primary, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`,
[id, 'subcontractor', contactItem.name, contactItem.position, contactItem.phone, contactItem.is_primary ? 1 : 0]
VALUES ($1, $2, $3, $4, $5, $6, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
RETURNING id`,
[id, 'subcontractor', contactItem.name, contactItem.position, contactItem.phone, contactItem.is_primary ? true : false]
);
}
}
@@ -266,8 +272,9 @@ router.put('/:id', async (req, res) => {
for (const paymentItem of payment_infos) {
await db.query(
`INSERT INTO subcontractor_payment_infos (subcontractor_id, account_name, bank_name, bank_account, qr_code, is_primary, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`,
[id, paymentItem.account_name, paymentItem.bank_name, paymentItem.bank_account, paymentItem.qr_code, paymentItem.is_primary ? 1 : 0]
VALUES ($1, $2, $3, $4, $5, $6, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
RETURNING id`,
[id, paymentItem.account_name, paymentItem.bank_name, paymentItem.bank_account, paymentItem.qr_code, paymentItem.is_primary ? true : false]
);
}
}
@@ -296,11 +303,11 @@ router.put('/:id', async (req, res) => {
res.status(500).json({
success: false,
message: '更新分包商失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
router.delete('/:id', async (req, res) => {
try {
const { id } = req.params;
@@ -311,7 +318,7 @@ router.delete('/:id', async (req, res) => {
// 再删除分包商数据
const result = await db.query(`DELETE FROM subcontractors WHERE id = $1`, [id]);
if (result.changes > 0) {
if (result.rowCount > 0) {
res.json({
success: true,
message: '分包商删除成功'
@@ -327,183 +334,184 @@ router.delete('/:id', async (req, res) => {
res.status(500).json({
success: false,
message: '删除分包商失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
// ==================== 分包商收款信息管理接口 ====================
// 获取分包商的所有收款信息
router.get('/:id/payment-infos', async (req, res) => {
try {
const { id } = req.params;
const result = await db.query(`
SELECT * FROM subcontractor_payment_infos
WHERE subcontractor_id = ?
ORDER BY is_primary DESC, created_at DESC
`, [id]);
const paymentInfos = result.rows.map(payment => ({
id: payment.id,
account_name: payment.account_name,
bank_account: payment.bank_account,
bank_name: payment.bank_name,
qr_code: payment.qr_code,
is_primary: payment.is_primary === 1,
created_at: payment.created_at,
updated_at: payment.updated_at
}));
res.json({
success: true,
data: paymentInfos,
count: paymentInfos.length
});
} catch (error) {
console.error('获取分包商收款信息失败:', error);
res.status(500).json({
success: false,
message: '获取分包商收款信息失败',
error: error.message
});
}
});
// 添加分包商收款信息
router.post('/:id/payment-infos', async (req, res) => {
try {
const { id } = req.params;
const { account_name, bank_account, bank_name, qr_code, is_primary } = req.body;
// 如果设置为默认账户,先取消其他账户的默认状态
if (is_primary) {
await db.query(
'UPDATE subcontractor_payment_infos SET is_primary = 0 WHERE subcontractor_id = $1',
[id]
);
}
const result = await db.query(
`INSERT INTO subcontractor_payment_infos (subcontractor_id, account_name, bank_account, bank_name, qr_code, is_primary, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`,
[id, account_name, bank_account, bank_name, qr_code, is_primary ? 1 : 0]
);
const paymentInfoId = (result.rows[0]?.id || result.rows?.[0]?.id);
res.json({
success: true,
message: '收款信息添加成功',
data: {
id: paymentInfoId,
subcontractor_id: id,
account_name,
bank_account,
bank_name,
qr_code,
is_primary: !!is_primary,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
}
});
} catch (error) {
console.error('添加分包商收款信息失败:', error);
res.status(500).json({
success: false,
message: '添加分包商收款信息失败',
error: error.message
});
}
});
// 更新分包商收款信息
router.put('/payment-infos/:infoId', async (req, res) => {
try {
const { infoId } = req.params;
const { account_name, bank_account, bank_name, qr_code, is_primary } = req.body;
// 先获取当前收款信息以获取分包商ID
const currentInfoResult = await db.query(
'SELECT subcontractor_id FROM subcontractor_payment_infos WHERE id = $1',
[infoId]
);
if (currentInfoResult.rows.length === 0) {
return res.status(404).json({
success: false,
message: '收款信息不存在'
});
}
const subcontractorId = currentInfoResult.rows[0].subcontractor_id;
// 如果设置为默认账户,先取消其他账户的默认状态
if (is_primary) {
await db.query(
'UPDATE subcontractor_payment_infos SET is_primary = 0 WHERE subcontractor_id = $1 AND id != $2',
[subcontractorId, infoId]
);
}
await db.query(
`UPDATE subcontractor_payment_infos
SET account_name = ?, bank_account = ?, bank_name = ?, qr_code = ?, is_primary = ?, updated_at = CURRENT_TIMESTAMP
WHERE id = ?`,
[account_name, bank_account, bank_name, qr_code, is_primary ? 1 : 0, infoId]
);
res.json({
success: true,
message: '收款信息更新成功',
data: {
id: infoId,
subcontractor_id: subcontractorId,
account_name,
bank_account,
bank_name,
qr_code,
is_primary: !!is_primary,
updated_at: new Date().toISOString()
}
});
} catch (error) {
console.error('更新分包商收款信息失败:', error);
res.status(500).json({
success: false,
message: '更新分包商收款信息失败',
error: error.message
});
}
});
// 删除分包商收款信息
router.delete('/payment-infos/:infoId', async (req, res) => {
try {
const { infoId } = req.params;
const result = await db.query('DELETE FROM subcontractor_payment_infos WHERE id = $1', [infoId]);
if (result.changes > 0) {
res.json({
success: true,
message: '收款信息删除成功'
});
} else {
res.status(404).json({
success: false,
message: '收款信息不存在'
});
}
} catch (error) {
console.error('删除分包商收款信息失败:', error);
res.status(500).json({
success: false,
message: '删除分包商收款信息失败',
error: error.message
});
}
});
});
// ==================== 分包商收款信息管理接口 ====================
// 获取分包商的所有收款信息
router.get('/:id/payment-infos', async (req, res) => {
try {
const { id } = req.params;
const result = await db.query(`
SELECT * FROM subcontractor_payment_infos
WHERE subcontractor_id = $1
ORDER BY is_primary DESC, created_at DESC
`, [id]);
const paymentInfos = result.rows.map(payment => ({
id: payment.id,
account_name: payment.account_name,
bank_account: payment.bank_account,
bank_name: payment.bank_name,
qr_code: payment.qr_code,
is_primary: payment.is_primary === true,
created_at: payment.created_at,
updated_at: payment.updated_at
}));
res.json({
success: true,
data: paymentInfos,
count: paymentInfos.length
});
} catch (error) {
console.error('获取分包商收款信息失败:', error);
res.status(500).json({
success: false,
message: '获取分包商收款信息失败',
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
// 添加分包商收款信息
router.post('/:id/payment-infos', async (req, res) => {
try {
const { id } = req.params;
const { account_name, bank_account, bank_name, qr_code, is_primary } = req.body;
// 如果设置为默认账户,先取消其他账户的默认状态
if (is_primary) {
await db.query(
'UPDATE subcontractor_payment_infos SET is_primary = 0 WHERE subcontractor_id = $1',
[id]
);
}
const result = await db.query(
`INSERT INTO subcontractor_payment_infos (subcontractor_id, account_name, bank_account, bank_name, qr_code, is_primary, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
RETURNING id`,
[id, account_name, bank_account, bank_name, qr_code, is_primary ? true : false]
);
const paymentInfoId = (result.rows[0]?.id || result.rows?.[0]?.id);
res.json({
success: true,
message: '收款信息添加成功',
data: {
id: paymentInfoId,
subcontractor_id: id,
account_name,
bank_account,
bank_name,
qr_code,
is_primary: !!is_primary,
created_at: new Date().toISOString(),
updated_at: new Date().toISOString()
}
});
} catch (error) {
console.error('添加分包商收款信息失败:', error);
res.status(500).json({
success: false,
message: '添加分包商收款信息失败',
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
// 更新分包商收款信息
router.put('/payment-infos/:infoId', async (req, res) => {
try {
const { infoId } = req.params;
const { account_name, bank_account, bank_name, qr_code, is_primary } = req.body;
// 先获取当前收款信息以获取分包商ID
const currentInfoResult = await db.query(
'SELECT subcontractor_id FROM subcontractor_payment_infos WHERE id = $1',
[infoId]
);
if (currentInfoResult.rows.length === 0) {
return res.status(404).json({
success: false,
message: '收款信息不存在'
});
}
const subcontractorId = currentInfoResult.rows[0].subcontractor_id;
// 如果设置为默认账户,先取消其他账户的默认状态
if (is_primary) {
await db.query(
'UPDATE subcontractor_payment_infos SET is_primary = 0 WHERE subcontractor_id = $1 AND id != $2',
[subcontractorId, infoId]
);
}
await db.query(
`UPDATE subcontractor_payment_infos
SET account_name = $1, bank_account = $2, bank_name = $3, qr_code = $4, is_primary = $5, updated_at = CURRENT_TIMESTAMP
WHERE id = $6`,
[account_name, bank_account, bank_name, qr_code, is_primary ? true : false, infoId]
);
res.json({
success: true,
message: '收款信息更新成功',
data: {
id: infoId,
subcontractor_id: subcontractorId,
account_name,
bank_account,
bank_name,
qr_code,
is_primary: !!is_primary,
updated_at: new Date().toISOString()
}
});
} catch (error) {
console.error('更新分包商收款信息失败:', error);
res.status(500).json({
success: false,
message: '更新分包商收款信息失败',
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
// 删除分包商收款信息
router.delete('/payment-infos/:infoId', async (req, res) => {
try {
const { infoId } = req.params;
const result = await db.query('DELETE FROM subcontractor_payment_infos WHERE id = $1', [infoId]);
if (result.rowCount > 0) {
res.json({
success: true,
message: '收款信息删除成功'
});
} else {
res.status(404).json({
success: false,
message: '收款信息不存在'
});
}
} catch (error) {
console.error('删除分包商收款信息失败:', error);
res.status(500).json({
success: false,
message: '删除分包商收款信息失败',
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
module.exports = router;
+39 -37
View File
@@ -38,14 +38,17 @@ router.get('/', async (req, res) => {
const paymentInfos = paymentInfosResult.rows.map(payment => ({
id: payment.id,
account_name: payment.account_name,
bank_account: payment.account_number,
bank_account: payment.bank_account,
bank_name: payment.bank_name,
qr_code: payment.qr_code,
is_primary: payment.is_default === 1
is_primary: payment.is_default === true
}));
return {
...supplier,
supply_category: supplier.product_category,
country: supplier.supplier_source,
remark: supplier.remark || '',
contacts: contacts.length > 0 ? contacts : [],
payment_infos: paymentInfos.length > 0 ? paymentInfos : []
};
@@ -62,7 +65,7 @@ router.get('/', async (req, res) => {
res.status(500).json({
success: false,
message: '获取供应商失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -74,7 +77,7 @@ router.get('/:id', async (req, res) => {
// 获取供应商基本信息
const supplierResult = await db.query(`
SELECT * FROM suppliers
WHERE id = ?
WHERE id = $1
`, [id]);
if (supplierResult.rows.length > 0) {
@@ -83,7 +86,7 @@ router.get('/:id', async (req, res) => {
// 获取供应商的所有联系人
const contactsResult = await db.query(`
SELECT * FROM contacts
WHERE entity_id = ? AND entity_type = 'supplier'
WHERE entity_id = $1 AND entity_type = 'supplier'
ORDER BY is_primary DESC
`, [id]);
@@ -92,13 +95,13 @@ router.get('/:id', async (req, res) => {
name: contact.name || '未命名',
position: contact.position || '',
phone: contact.phone || '',
is_primary: contact.is_primary === 1
is_primary: contact.is_primary === true
}));
// 获取供应商的所有收款信息
const paymentInfosResult = await db.query(`
SELECT * FROM supplier_payment_infos
WHERE supplier_id = ?
WHERE supplier_id = $1
ORDER BY is_default DESC
`, [id]);
@@ -106,10 +109,10 @@ router.get('/:id', async (req, res) => {
const paymentInfos = paymentInfosResult.rows.map(payment => ({
id: payment.id,
account_name: payment.account_name,
bank_account: payment.account_number,
bank_account: payment.bank_account,
bank_name: payment.bank_name,
qr_code: payment.qr_code,
is_primary: payment.is_default === 1
is_primary: payment.is_default === true
}));
const ledger = await LedgerService.getSupplierLedger(id);
@@ -118,8 +121,8 @@ router.get('/:id', async (req, res) => {
id: supplier.id,
code: `S${String(supplier.id).padStart(4, '0')}`,
name: supplier.name || '未命名',
supply_category: supplier.supply_category || '电力设备',
country: supplier.country || 'Laos',
supply_category: supplier.product_category || '电力设备',
country: supplier.supplier_source || 'Laos',
contacts: contacts.length > 0 ? contacts : [],
payment_infos: paymentInfos.length > 0 ? paymentInfos : [],
remark: supplier.remark || '',
@@ -147,7 +150,7 @@ router.get('/:id', async (req, res) => {
res.status(500).json({
success: false,
message: '获取供应商详情失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -158,27 +161,27 @@ router.post('/', async (req, res) => {
// 从contacts中获取主联系人信息
const primaryContact = contacts?.find(c => c.is_primary) || contacts?.[0];
const contact = primaryContact?.name || '';
const position = primaryContact?.position || '';
const contact_person = primaryContact?.name || '';
const phone = primaryContact?.phone || '';
const email = ''; // 前端没有email字段
const address = ''; // 前端没有address字段
const result = await db.query(
`INSERT INTO suppliers (name, address, contact, position, phone, email, supply_category, country, remark, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`,
[name, address, contact, position, phone, email, supply_category, country, remark]
`INSERT INTO suppliers (name, address, contact_person, phone, email, product_category, supplier_source, remark, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
RETURNING id`,
[name, address, contact_person, phone, email, supply_category, country, remark || '']
);
const supplierId = (result.rows[0]?.id || result.rows?.[0]?.id);
const supplierId = result.rows[0]?.id;
// 插入联系人数据
if (contacts && contacts.length > 0) {
for (const contactItem of contacts) {
await db.query(
`INSERT INTO contacts (entity_id, entity_type, name, position, phone, is_primary, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`,
[supplierId, 'supplier', contactItem.name, contactItem.position, contactItem.phone, contactItem.is_primary ? 1 : 0]
VALUES ($1, $2, $3, $4, $5, $6, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`,
[supplierId, 'supplier', contactItem.name, contactItem.position, contactItem.phone, contactItem.is_primary ? true : false]
);
}
}
@@ -188,8 +191,8 @@ router.post('/', async (req, res) => {
for (const paymentInfo of payment_infos) {
await db.query(
`INSERT INTO supplier_payment_infos (supplier_id, account_name, bank_account, bank_name, qr_code, is_primary, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`,
[supplierId, paymentInfo.account_name, paymentInfo.bank_account, paymentInfo.bank_name, paymentInfo.qr_code, paymentInfo.is_primary ? 1 : 0]
VALUES ($1, $2, $3, $4, $5, $6, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`,
[supplierId, paymentInfo.account_name, paymentInfo.bank_account, paymentInfo.bank_name, paymentInfo.qr_code, paymentInfo.is_primary ? true : false]
);
}
}
@@ -217,7 +220,7 @@ router.post('/', async (req, res) => {
res.status(500).json({
success: false,
message: '创建供应商失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -229,17 +232,16 @@ router.put('/:id', async (req, res) => {
// 从contacts中获取主联系人信息
const primaryContact = contacts?.find(c => c.is_primary) || contacts?.[0];
const contact = primaryContact?.name || '';
const position = primaryContact?.position || '';
const contact_person = primaryContact?.name || '';
const phone = primaryContact?.phone || '';
const email = ''; // 前端没有email字段
const address = ''; // 前端没有address字段
await db.query(
`UPDATE suppliers
SET name = ?, address = ?, contact = ?, position = ?, phone = ?, email = ?, supply_category = ?, country = ?, remark = ?, updated_at = CURRENT_TIMESTAMP
WHERE id = ?`,
[name, address, contact, position, phone, email, supply_category, country, remark, id]
SET name = $1, address = $2, contact_person = $3, phone = $4, email = $5, product_category = $6, supplier_source = $7, remark = $8, updated_at = CURRENT_TIMESTAMP
WHERE id = $9`,
[name, address, contact_person, phone, email, supply_category, country, remark || '', id]
);
// 删除旧的联系人数据
@@ -250,8 +252,8 @@ router.put('/:id', async (req, res) => {
for (const contactItem of contacts) {
await db.query(
`INSERT INTO contacts (entity_id, entity_type, name, position, phone, is_primary, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`,
[id, 'supplier', contactItem.name, contactItem.position, contactItem.phone, contactItem.is_primary ? 1 : 0]
VALUES ($1, $2, $3, $4, $5, $6, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`,
[id, 'supplier', contactItem.name, contactItem.position, contactItem.phone, contactItem.is_primary ? true : false]
);
}
}
@@ -264,8 +266,8 @@ router.put('/:id', async (req, res) => {
for (const paymentInfo of payment_infos) {
await db.query(
`INSERT INTO supplier_payment_infos (supplier_id, account_name, bank_account, bank_name, qr_code, is_primary, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`,
[id, paymentInfo.account_name, paymentInfo.bank_account, paymentInfo.bank_name, paymentInfo.qr_code, paymentInfo.is_primary ? 1 : 0]
VALUES ($1, $2, $3, $4, $5, $6, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`,
[id, paymentInfo.account_name, paymentInfo.bank_account, paymentInfo.bank_name, paymentInfo.qr_code, paymentInfo.is_primary ? true : false]
);
}
}
@@ -293,7 +295,7 @@ router.put('/:id', async (req, res) => {
res.status(500).json({
success: false,
message: '更新供应商失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -308,7 +310,7 @@ router.delete('/:id', async (req, res) => {
// 再删除供应商数据
const result = await db.query(`DELETE FROM suppliers WHERE id = $1`, [id]);
if (result.changes > 0) {
if (result.rowCount > 0) {
res.json({
success: true,
message: '供应商删除成功'
@@ -324,7 +326,7 @@ router.delete('/:id', async (req, res) => {
res.status(500).json({
success: false,
message: '删除供应商失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -361,7 +363,7 @@ router.get('/:id/orders', async (req, res) => {
res.status(500).json({
success: false,
message: '获取供应商订单列表失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -422,7 +424,7 @@ router.get('/:id/ledger', async (req, res) => {
res.status(500).json({
success: false,
message: '获取供应商台账失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
+11 -11
View File
@@ -2,27 +2,30 @@ const express = require('express');
const multer = require('multer');
const path = require('path');
const COS = require('cos-nodejs-sdk-v5');
const { authenticate } = require('../middleware/auth');
const router = express.Router();
// COS配置从环境变量读取,不再硬编码密钥
const cos = new COS({
SecretId: 'AKID8PXTCi2A4vdB6oMitsfM3b1FNQL5kEVZ',
SecretKey: 'vY0OgmhRyKUSClBRXSIySmqkTUZZYdwo'
SecretId: process.env.TENCENT_SECRET_ID,
SecretKey: process.env.TENCENT_SECRET_KEY
});
const cosConfig = {
Bucket: 'qingyuan-erp-files-1310040146',
Region: 'ap-hongkong'
Bucket: process.env.COS_BUCKET || 'qingyuan-erp-files-1310040146',
Region: process.env.COS_REGION || 'ap-hongkong'
};
const imageFormats = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp', 'svg'];
const upload = multer({
storage: multer.memoryStorage(),
limits: { fileSize: 10 * 1024 * 1024 }
limits: { fileSize: 50 * 1024 * 1024 }
});
router.post('/upload/single', upload.single('file'), (req, res) => {
// 所有上传接口都需要认证
router.post('/single', authenticate, upload.single('file'), (req, res) => {
try {
if (!req.file) {
return res.status(400).json({ success: false, error: '没有上传文件' });
@@ -33,7 +36,6 @@ router.post('/upload/single', upload.single('file'), (req, res) => {
const randomStr = Math.random().toString(36).substring(2, 8);
const filename = 'uploads/' + timestamp + '_' + randomStr + '.' + ext;
console.log('开始上传文件到 COS:', filename);
cos.putObject({
Bucket: cosConfig.Bucket,
@@ -45,14 +47,12 @@ router.post('/upload/single', upload.single('file'), (req, res) => {
}, (err, data) => {
if (err) {
console.error('COS 上传失败:', err);
return res.status(500).json({ success: false, error: '上传失败: ' + err.message });
return res.status(500).json({ success: false, error: '上传失败' });
}
console.log('COS 上传成功:', data);
const permanentUrl = 'https://' + cosConfig.Bucket + '.cos.' + cosConfig.Region + '.myqcloud.com/' + filename;
console.log('返回永久 URL:', permanentUrl);
res.json({
success: true,
@@ -73,7 +73,7 @@ router.post('/upload/single', upload.single('file'), (req, res) => {
}
});
router.post('/upload/multiple', upload.array('files', 10), (req, res) => {
router.post('/multiple', authenticate, upload.array('files', 10), (req, res) => {
try {
if (!req.files || req.files.length === 0) {
return res.status(400).json({ success: false, error: '没有上传文件' });
-4
View File
@@ -38,7 +38,6 @@ router.post('/', authenticate, requireAdmin, async (req, res) => {
const newUserResult = await db.query('SELECT id, username, name, email, phone, role, is_active, created_at, updated_at FROM users WHERE id = $1', [result.rows[0].id]);
const newUser = newUserResult.rows[0];
console.log('用户 ' + username + ' 创建成功,操作者: ' + req.user.username);
res.json({ success: true, data: newUser });
} catch (error) {
console.error('创建用户失败:', error);
@@ -71,7 +70,6 @@ router.put('/:id', authenticate, requireAdmin, async (req, res) => {
return res.status(404).json({ success: false, message: '用户不存在' });
}
console.log('用户 ID ' + id + ' 已更新,操作者: ' + req.user.username);
res.json({ success: true, data: updatedUser });
} catch (error) {
console.error('更新用户信息失败:', error);
@@ -112,7 +110,6 @@ router.put('/:id/password', authenticate, async (req, res) => {
[newPasswordHash, id]
);
console.log('用户 ID ' + id + ' 密码已更新,操作者: ' + req.user.username);
res.json({ success: true, message: '密码更新成功' });
} catch (error) {
console.error('更新密码失败:', error);
@@ -142,7 +139,6 @@ router.delete('/:id', authenticate, requireAdmin, async (req, res) => {
await db.query('DELETE FROM users WHERE id = $1', [id]);
console.log('用户 ID ' + id + ' 已删除,操作者: ' + req.user.username);
res.json({ success: true, message: '用户删除成功' });
} catch (error) {
console.error('删除用户失败:', error);
+14 -12
View File
@@ -61,7 +61,7 @@ router.get('/', async (req, res) => {
res.status(500).json({
success: false,
message: '获取验收单列表失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -108,7 +108,7 @@ router.get('/:id', async (req, res) => {
res.status(500).json({
success: false,
message: '获取验收单详情失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -152,8 +152,9 @@ router.post('/', async (req, res) => {
(code, purchase_order_id, logistics_record_id, verification_type,
verification_date, verifier, items, total_ordered, total_received,
total_verified, total_rejected, project_id, storage_type, status, remark, attachments, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending', ?, ?, CURRENT_TIMESTAMP)
`, [code, purchase_order_id, logistics_record_id, verification_type || 'direct',
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, 'pending', $14, $15, CURRENT_TIMESTAMP)
RETURNING id`,
[code, purchase_order_id, logistics_record_id, verification_type || 'direct',
verification_date, verifier, itemsJson, totalOrdered, totalReceived,
totalVerified, totalRejected, project_id, storage_type, remark, attachments]);
@@ -173,7 +174,7 @@ router.post('/', async (req, res) => {
res.status(500).json({
success: false,
message: '创建验收单失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -233,8 +234,9 @@ router.post('/:id/confirm', async (req, res) => {
await db.query(`
INSERT INTO project_material_inventory
(project_id, product_id, product_name, unit, received_quantity, current_quantity, total_amount, average_price, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
`, [verification.project_id, item.product_id, item.product_name, item.unit,
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
RETURNING id`,
[verification.project_id, item.product_id, item.product_name, item.unit,
item.verified_quantity, item.verified_quantity,
item.verified_quantity * item.unit_price || 0, item.unit_price || 0]);
}
@@ -263,7 +265,7 @@ router.post('/:id/confirm', async (req, res) => {
res.status(500).json({
success: false,
message: '确认验收失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -289,7 +291,7 @@ router.post('/:id/reject', async (req, res) => {
res.json({ success: true, message: '验收已驳回' });
} catch (error) {
console.error('驳回验收失败:', error);
res.status(500).json({ success: false, message: '驳回验收失败', error: error.message });
res.status(500).json({ success: false, message: '驳回验收失败' });
}
});
@@ -341,7 +343,7 @@ router.put('/:id', async (req, res) => {
res.status(500).json({
success: false,
message: '更新验收单失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -371,7 +373,7 @@ router.delete('/:id', async (req, res) => {
res.status(500).json({
success: false,
message: '删除验收单失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
@@ -424,7 +426,7 @@ router.get('/order-items/:orderId', async (req, res) => {
res.status(500).json({
success: false,
message: '获取订单商品明细失败',
error: error.message
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
});
}
});
+9 -9
View File
@@ -48,7 +48,7 @@ router.get('/', async (req, res) => {
res.json({ success: true, data, count: data.length });
} catch (error) {
console.error('获取核销记录失败:', error);
res.status(500).json({ success: false, message: '获取核销记录失败', error: error.message });
res.status(500).json({ success: false, message: '获取核销记录失败' });
}
});
@@ -116,7 +116,7 @@ router.post('/', async (req, res) => {
}
} catch (error) {
console.error('创建核销申请失败:', error);
res.status(500).json({ success: false, message: '创建核销申请失败', error: error.message });
res.status(500).json({ success: false, message: '创建核销申请失败' });
}
});
@@ -152,7 +152,7 @@ router.get('/:id', async (req, res) => {
}
} catch (error) {
console.error('获取核销申请失败:', error);
res.status(500).json({ success: false, message: '获取核销申请失败', error: error.message });
res.status(500).json({ success: false, message: '获取核销申请失败' });
}
});
@@ -218,7 +218,7 @@ router.put('/:id', async (req, res) => {
}
} catch (error) {
console.error('更新核销申请失败:', error);
res.status(500).json({ success: false, message: '更新核销申请失败', error: error.message });
res.status(500).json({ success: false, message: '更新核销申请失败' });
}
});
@@ -261,7 +261,7 @@ router.delete('/:id', async (req, res) => {
}
} catch (error) {
console.error('删除核销申请失败:', error);
res.status(500).json({ success: false, message: '删除核销申请失败', error: error.message });
res.status(500).json({ success: false, message: '删除核销申请失败' });
}
});
@@ -278,7 +278,7 @@ router.post('/:id/submit', async (req, res) => {
}
} catch (error) {
console.error('提交核销申请失败:', error);
res.status(500).json({ success: false, message: '提交核销申请失败', error: error.message });
res.status(500).json({ success: false, message: '提交核销申请失败' });
}
});
@@ -295,7 +295,7 @@ router.post('/:id/withdraw', async (req, res) => {
}
} catch (error) {
console.error('撤回核销申请失败:', error);
res.status(500).json({ success: false, message: '撤回核销申请失败', error: error.message });
res.status(500).json({ success: false, message: '撤回核销申请失败' });
}
});
@@ -313,7 +313,7 @@ router.post('/:id/approve', async (req, res) => {
}
} catch (error) {
console.error('审批核销申请失败:', error);
res.status(500).json({ success: false, message: '审批核销申请失败', error: error.message });
res.status(500).json({ success: false, message: '审批核销申请失败' });
}
});
@@ -357,7 +357,7 @@ router.post('/:id/reject', async (req, res) => {
}
} catch (error) {
console.error('退回核销申请失败:', error);
res.status(500).json({ success: false, message: '退回核销申请失败', error: error.message });
res.status(500).json({ success: false, message: '退回核销申请失败' });
}
});