2026-04-19 19:15:01 +08:00
|
|
|
/**
|
|
|
|
|
* 物流管理路由 - PostgreSQL版本
|
|
|
|
|
*/
|
|
|
|
|
const express = require('express');
|
|
|
|
|
const db = require('../db');
|
|
|
|
|
|
|
|
|
|
const router = express.Router();
|
|
|
|
|
|
|
|
|
|
router.get('/', async (req, res) => {
|
|
|
|
|
try {
|
|
|
|
|
const { purchase_order_id, status, ship_from } = req.query;
|
|
|
|
|
let query = `
|
|
|
|
|
SELECT lr.*,
|
|
|
|
|
po.code as order_code,
|
|
|
|
|
lc.name as logistics_company_name
|
|
|
|
|
FROM logistics_records lr
|
|
|
|
|
LEFT JOIN purchase_orders po ON lr.purchase_order_id = po.id
|
|
|
|
|
LEFT JOIN logistics_companies lc ON lr.logistics_company_id = lc.id
|
|
|
|
|
`;
|
|
|
|
|
const params = [];
|
|
|
|
|
const conditions = [];
|
|
|
|
|
let paramIndex = 1;
|
|
|
|
|
|
|
|
|
|
if (purchase_order_id) {
|
|
|
|
|
conditions.push('lr.purchase_order_id = $' + paramIndex++);
|
|
|
|
|
params.push(purchase_order_id);
|
|
|
|
|
}
|
|
|
|
|
if (status) {
|
|
|
|
|
conditions.push('lr.status = $' + paramIndex++);
|
|
|
|
|
params.push(status);
|
|
|
|
|
}
|
|
|
|
|
if (ship_from) {
|
|
|
|
|
conditions.push('lr.ship_from = $' + paramIndex++);
|
|
|
|
|
params.push(ship_from);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (conditions.length > 0) {
|
|
|
|
|
query += ' WHERE ' + conditions.join(' AND ');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
query += ' ORDER BY lr.created_at DESC';
|
|
|
|
|
|
|
|
|
|
const result = await db.query(query, params);
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
data: result.rows,
|
|
|
|
|
count: result.rows.length
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('获取物流单列表失败:', error);
|
|
|
|
|
res.status(500).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: '获取物流单列表失败',
|
2026-05-15 12:02:24 +08:00
|
|
|
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
|
2026-04-19 19:15:01 +08:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
router.get('/:id', async (req, res) => {
|
|
|
|
|
try {
|
|
|
|
|
const { id } = req.params;
|
|
|
|
|
|
|
|
|
|
const result = await db.query(`
|
|
|
|
|
SELECT lr.*,
|
|
|
|
|
po.code as order_code,
|
|
|
|
|
lc.name as logistics_company_name
|
|
|
|
|
FROM logistics_records lr
|
|
|
|
|
LEFT JOIN purchase_orders po ON lr.purchase_order_id = po.id
|
|
|
|
|
LEFT JOIN logistics_companies lc ON lr.logistics_company_id = lc.id
|
|
|
|
|
WHERE lr.id = $1
|
|
|
|
|
`, [id]);
|
|
|
|
|
|
|
|
|
|
if (result.rows.length === 0) {
|
|
|
|
|
return res.status(404).json({ success: false, message: '物流单不存在' });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
data: result.rows[0]
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('获取物流单详情失败:', error);
|
|
|
|
|
res.status(500).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: '获取物流单详情失败',
|
2026-05-15 12:02:24 +08:00
|
|
|
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
|
2026-04-19 19:15:01 +08:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
router.post('/', async (req, res) => {
|
|
|
|
|
try {
|
|
|
|
|
const {
|
|
|
|
|
purchase_order_id, ship_from, logistics_company_id, logistics_company,
|
|
|
|
|
tracking_number, ship_date, ship_location, estimated_arrival_date,
|
|
|
|
|
use_hub, primary_freight, primary_freight_currency,
|
|
|
|
|
secondary_freight, secondary_freight_currency, driver_phone,
|
|
|
|
|
cargo_weight, transport_distance, remark, created_by
|
|
|
|
|
} = req.body;
|
|
|
|
|
|
|
|
|
|
const code = 'LR' + 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_records
|
|
|
|
|
(code, purchase_order_id, ship_from, logistics_company_id, logistics_company,
|
|
|
|
|
tracking_number, ship_date, ship_location, estimated_arrival_date,
|
|
|
|
|
use_hub, primary_freight, primary_freight_currency, primary_freight_status,
|
|
|
|
|
secondary_freight, secondary_freight_currency, secondary_freight_status,
|
|
|
|
|
driver_phone, cargo_weight, transport_distance, status, remark, created_by, created_at, updated_at)
|
|
|
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, 'pending', $13, $14, 'pending', $15, $16, $17, 'pending', $18, $19, NOW(), NOW())
|
|
|
|
|
RETURNING id
|
|
|
|
|
`, [code, purchase_order_id, ship_from || 'Laos', logistics_company_id, logistics_company,
|
|
|
|
|
tracking_number, ship_date, ship_location, estimated_arrival_date,
|
|
|
|
|
use_hub ? 1 : 0, primary_freight || 0, primary_freight_currency || 'CNY',
|
|
|
|
|
secondary_freight || 0, secondary_freight_currency || 'LAK',
|
|
|
|
|
driver_phone, cargo_weight, transport_distance, remark, created_by]);
|
|
|
|
|
|
|
|
|
|
res.json({
|
|
|
|
|
success: true,
|
|
|
|
|
message: '物流单创建成功',
|
|
|
|
|
data: { id: result.rows[0].id, code }
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('创建物流单失败:', error);
|
|
|
|
|
res.status(500).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: '创建物流单失败',
|
2026-05-15 12:02:24 +08:00
|
|
|
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
|
2026-04-19 19:15:01 +08:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
router.put('/:id', async (req, res) => {
|
|
|
|
|
try {
|
|
|
|
|
const { id } = req.params;
|
|
|
|
|
const updateFields = req.body;
|
|
|
|
|
|
|
|
|
|
const fields = [];
|
|
|
|
|
const values = [];
|
|
|
|
|
let paramIndex = 1;
|
|
|
|
|
|
|
|
|
|
const allowedFields = [
|
|
|
|
|
'ship_from', 'logistics_company_id', 'logistics_company', 'tracking_number',
|
|
|
|
|
'ship_date', 'ship_location', 'estimated_arrival_date',
|
|
|
|
|
'customs_arrival_date', 'customs_clearance_date',
|
|
|
|
|
'use_hub', 'hub_arrival_date', 'hub_receiver', 'hub_verified_quantity', 'second_ship_date',
|
|
|
|
|
'primary_freight', 'primary_freight_currency', 'primary_freight_status', 'primary_freight_document',
|
|
|
|
|
'secondary_freight', 'secondary_freight_currency', 'secondary_freight_status',
|
|
|
|
|
'driver_phone', 'cargo_weight', 'transport_distance',
|
|
|
|
|
'final_arrival_date', 'final_location', 'status', 'remark'
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
for (const field of allowedFields) {
|
|
|
|
|
if (updateFields[field] !== undefined) {
|
|
|
|
|
fields.push(field + ' = $' + paramIndex++);
|
|
|
|
|
values.push(updateFields[field]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (fields.length === 0) {
|
|
|
|
|
return res.status(400).json({ success: false, message: '没有要更新的字段' });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fields.push('updated_at = NOW()');
|
|
|
|
|
values.push(id);
|
|
|
|
|
|
|
|
|
|
const result = await db.query(
|
|
|
|
|
'UPDATE logistics_records SET ' + fields.join(', ') + ' WHERE id = $' + paramIndex,
|
|
|
|
|
values
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
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: '更新物流单失败',
|
2026-05-15 12:02:24 +08:00
|
|
|
error: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
|
2026-04-19 19:15:01 +08:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
router.delete('/:id', async (req, res) => {
|
|
|
|
|
try {
|
|
|
|
|
const { id } = req.params;
|
|
|
|
|
const result = await db.query('DELETE FROM logistics_records WHERE id = $1', [id]);
|
|
|
|
|
if (result.rowCount === 0) {
|
|
|
|
|
return res.status(404).json({ success: false, message: '物流单不存在' });
|
|
|
|
|
}
|
|
|
|
|
res.json({ success: true, message: '物流单删除成功' });
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('删除物流单失败:', error);
|
2026-05-15 12:02:24 +08:00
|
|
|
res.status(500).json({ success: false, message: '删除物流单失败' });
|
2026-04-19 19:15:01 +08:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
module.exports = router;
|