备份:修复前完整项目快照 2026-04-19
This commit is contained in:
@@ -0,0 +1,592 @@
|
||||
/**
|
||||
* 物流管理路由
|
||||
* 遵循设计方案:采购-付款-物流-退库一体化流程设计方案.md
|
||||
* 章节:四、物流管理
|
||||
*
|
||||
* 统一合作伙伴界面规范:
|
||||
* - 基本信息:公司名称、地址、联系方式、报价描述
|
||||
* - 联系人:支持多个联系人,标记主联系人
|
||||
* - 收款信息:支持多个银行账户,标记默认账户
|
||||
* - 业务台账:订单列表、运费总额、已付/未付金额
|
||||
*/
|
||||
const express = require('express');
|
||||
const db = require('../db');
|
||||
const LedgerService = require('../services/ledgerService');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
/**
|
||||
* 获取物流公司列表
|
||||
*/
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const { status } = req.query;
|
||||
let query = 'SELECT id, code, name, address, phone, email, status, remark, created_at FROM logistics_companies';
|
||||
const params = [];
|
||||
|
||||
if (status) {
|
||||
query += ' WHERE status = $1';
|
||||
params.push(status);
|
||||
}
|
||||
|
||||
query += ' ORDER BY created_at DESC';
|
||||
|
||||
const result = await db.query(query, params);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: result.rows,
|
||||
count: result.rows.length
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取物流公司列表失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 获取物流公司详情(包含所有TAB数据)
|
||||
*/
|
||||
router.get('/:id', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const companyResult = await db.query('SELECT id, code, name, address, phone, email, status, remark, created_at FROM logistics_companies WHERE id = $1', [id]);
|
||||
|
||||
if (companyResult.rows.length === 0) {
|
||||
return res.status(404).json({ success: false, message: '物流公司不存在' });
|
||||
}
|
||||
|
||||
const company = companyResult.rows[0];
|
||||
|
||||
const contactsResult = await db.query(`
|
||||
SELECT * FROM logistics_company_contacts
|
||||
WHERE logistics_company_id = $1
|
||||
ORDER BY is_primary DESC, id
|
||||
`, [id]);
|
||||
company.contacts = contactsResult.rows;
|
||||
|
||||
const paymentInfosResult = await db.query(`
|
||||
SELECT * FROM logistics_company_payment_infos
|
||||
WHERE logistics_company_id = $1
|
||||
ORDER BY is_default DESC, id
|
||||
`, [id]);
|
||||
company.payment_infos = paymentInfosResult.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;
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: company
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取物流公司详情失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取物流公司详情失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 创建物流公司
|
||||
*/
|
||||
router.post('/', async (req, res) => {
|
||||
try {
|
||||
const { code, name, address, phone, email, remark } = req.body;
|
||||
|
||||
const companyCode = code || 'LC' + new Date().toISOString().slice(0, 10).replace(/-/g, '') +
|
||||
String(Math.floor(Math.random() * 10000)).padStart(4, '0');
|
||||
|
||||
const result = await db.query(`
|
||||
INSERT INTO logistics_companies
|
||||
(code, name, address, phone, email, status, remark, created_at, updated_at)
|
||||
VALUES ($1, $2, $3, $4, $5, 'active', $6, NOW(), NOW())
|
||||
RETURNING id
|
||||
`, [companyCode, name, address, phone, email, remark]);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '物流公司创建成功',
|
||||
data: { id: result.rows[0].id, code: companyCode }
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('创建物流公司失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '创建物流公司失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 更新物流公司基本信息
|
||||
*/
|
||||
router.put('/:id', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { name, address, phone, email, status, 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]);
|
||||
|
||||
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
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 删除物流公司
|
||||
*/
|
||||
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_contacts WHERE logistics_company_id = $1', [id]);
|
||||
await db.query('DELETE FROM logistics_companies WHERE id = $1', [id]);
|
||||
|
||||
await db.query('COMMIT');
|
||||
|
||||
res.json({ success: true, message: '物流公司删除成功' });
|
||||
} catch (innerError) {
|
||||
await db.query('ROLLBACK');
|
||||
throw innerError;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('删除物流公司失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '删除物流公司失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 获取物流公司联系人列表
|
||||
*/
|
||||
router.get('/:id/contacts', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const result = await db.query(`
|
||||
SELECT * FROM logistics_company_contacts
|
||||
WHERE logistics_company_id = $1
|
||||
ORDER BY is_primary DESC, id
|
||||
`, [id]);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: result.rows
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取联系人列表失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取联系人列表失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 添加联系人
|
||||
*/
|
||||
router.post('/:id/contacts', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { name, phone, position, is_primary } = req.body;
|
||||
|
||||
if (is_primary) {
|
||||
await db.query(
|
||||
'UPDATE logistics_company_contacts SET is_primary = 0 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())
|
||||
RETURNING id
|
||||
`, [id, name, phone, position, is_primary ? 1 : 0]);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '联系人添加成功',
|
||||
data: { id: result.rows[0].id }
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('添加联系人失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '添加联系人失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 更新联系人
|
||||
*/
|
||||
router.put('/:id/contacts/:contactId', async (req, res) => {
|
||||
try {
|
||||
const { id, contactId } = req.params;
|
||||
const { name, phone, position, is_primary } = req.body;
|
||||
|
||||
if (is_primary) {
|
||||
await db.query(
|
||||
'UPDATE logistics_company_contacts SET is_primary = 0 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
|
||||
WHERE id = $5 AND logistics_company_id = $6
|
||||
`, [name, phone, position, is_primary ? 1 : 0, contactId, id]);
|
||||
|
||||
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
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 删除联系人
|
||||
*/
|
||||
router.delete('/:id/contacts/:contactId', async (req, res) => {
|
||||
try {
|
||||
const { id, contactId } = req.params;
|
||||
|
||||
const result = await db.query(
|
||||
'DELETE FROM logistics_company_contacts WHERE id = $1 AND logistics_company_id = $2',
|
||||
[contactId, id]
|
||||
);
|
||||
|
||||
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
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 获取物流公司收款信息列表
|
||||
*/
|
||||
router.get('/:id/payment-infos', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const result = await db.query(`
|
||||
SELECT * FROM logistics_company_payment_infos
|
||||
WHERE logistics_company_id = $1
|
||||
ORDER BY is_default DESC, id
|
||||
`, [id]);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: result.rows
|
||||
});
|
||||
} 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, account_number, bank_name, qr_code, is_default } = req.body;
|
||||
|
||||
if (is_default) {
|
||||
await db.query(
|
||||
'UPDATE logistics_company_payment_infos SET is_default = 0 WHERE logistics_company_id = $1',
|
||||
[id]
|
||||
);
|
||||
}
|
||||
|
||||
const result = await db.query(`
|
||||
INSERT INTO logistics_company_payment_infos
|
||||
(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]);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '收款信息添加成功',
|
||||
data: { id: result.rows[0].id }
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('添加收款信息失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '添加收款信息失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 更新收款信息
|
||||
*/
|
||||
router.put('/:id/payment-infos/:infoId', async (req, res) => {
|
||||
try {
|
||||
const { id, infoId } = req.params;
|
||||
const { account_name, account_number, bank_name, qr_code, is_default } = req.body;
|
||||
|
||||
if (is_default) {
|
||||
await db.query(
|
||||
'UPDATE logistics_company_payment_infos SET is_default = 0 WHERE logistics_company_id = $1',
|
||||
[id]
|
||||
);
|
||||
}
|
||||
|
||||
const result = await db.query(`
|
||||
UPDATE logistics_company_payment_infos
|
||||
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]);
|
||||
|
||||
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
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 删除收款信息
|
||||
*/
|
||||
router.delete('/:id/payment-infos/:infoId', async (req, res) => {
|
||||
try {
|
||||
const { id, infoId } = req.params;
|
||||
|
||||
const result = await db.query(
|
||||
'DELETE FROM logistics_company_payment_infos WHERE id = $1 AND logistics_company_id = $2',
|
||||
[infoId, id]
|
||||
);
|
||||
|
||||
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
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 获取物流公司业务台账
|
||||
*/
|
||||
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
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取业务台账失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取业务台账失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 获取物流公司财务台账(汇总+订单列表)
|
||||
*/
|
||||
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
|
||||
},
|
||||
orders: ordersResult.rows
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取物流公司台账失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取物流公司台账失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
Reference in New Issue
Block a user