备份:修复前完整项目快照 2026-04-19
This commit is contained in:
@@ -0,0 +1,364 @@
|
||||
/**
|
||||
* 项目材料管理路由
|
||||
* 遵循设计方案:采购-付款-物流-退库一体化流程设计方案.md
|
||||
* 章节:九、项目材料管理
|
||||
*
|
||||
* 功能:
|
||||
* - 材料库存:显示项目当前材料库存
|
||||
* - 采购记录:显示项目关联的所有采购订单
|
||||
* - 退库记录:显示项目材料退库记录
|
||||
* - 材料价格历史:查询材料的历史采购价格
|
||||
*/
|
||||
const express = require('express');
|
||||
const db = require('../db');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
/**
|
||||
* 获取项目材料库存列表
|
||||
*/
|
||||
router.get('/inventory/:projectId', async (req, res) => {
|
||||
try {
|
||||
const { projectId } = req.params;
|
||||
|
||||
const result = await db.query(`
|
||||
SELECT pmi.*, p.name as product_name, p.specification
|
||||
FROM project_material_inventory pmi
|
||||
LEFT JOIN products p ON pmi.product_id = p.id
|
||||
WHERE pmi.project_id = ?
|
||||
ORDER BY p.name
|
||||
`, [projectId]);
|
||||
|
||||
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
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 获取项目材料库存汇总
|
||||
*/
|
||||
router.get('/inventory/:projectId/summary', async (req, res) => {
|
||||
try {
|
||||
const { projectId } = req.params;
|
||||
|
||||
const result = await db.query(`
|
||||
SELECT
|
||||
COUNT(*) as item_count,
|
||||
SUM(purchased_quantity) as total_purchased,
|
||||
SUM(received_quantity) as total_received,
|
||||
SUM(used_quantity) as total_used,
|
||||
SUM(returned_quantity) as total_returned,
|
||||
SUM(current_quantity) as total_current,
|
||||
SUM(total_amount) as total_value
|
||||
FROM project_material_inventory
|
||||
WHERE project_id = ?
|
||||
`, [projectId]);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: result.rows[0]
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取项目材料库存汇总失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取项目材料库存汇总失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 获取项目采购记录
|
||||
*/
|
||||
router.get('/purchases/:projectId', async (req, res) => {
|
||||
try {
|
||||
const { projectId } = req.params;
|
||||
const { status } = req.query;
|
||||
|
||||
let query = `
|
||||
SELECT po.*, s.name as supplier_name,
|
||||
(SELECT SUM(total_price) FROM purchase_order_items WHERE order_id = po.id) as total_amount,
|
||||
(SELECT SUM(CASE WHEN pp.status = 'paid' THEN COALESCE(pp.actual_amount, 0) ELSE 0 END)
|
||||
FROM payment_plans pp WHERE pp.purchase_order_id = po.id) as paid_amount
|
||||
FROM purchase_orders po
|
||||
LEFT JOIN suppliers s ON po.supplier_id = s.id
|
||||
WHERE po.project_id = ?
|
||||
`;
|
||||
const params = [projectId];
|
||||
|
||||
if (status) {
|
||||
query += ' AND po.status = $1';
|
||||
params.push(status);
|
||||
}
|
||||
|
||||
query += ' ORDER BY po.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: '获取项目采购记录失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 获取项目退库记录
|
||||
*/
|
||||
router.get('/returns/:projectId', async (req, res) => {
|
||||
try {
|
||||
const { projectId } = req.params;
|
||||
const { status } = req.query;
|
||||
|
||||
let query = `
|
||||
SELECT * FROM return_records
|
||||
WHERE project_id = ?
|
||||
`;
|
||||
const params = [projectId];
|
||||
|
||||
if (status) {
|
||||
query += ' AND status = $1';
|
||||
params.push(status);
|
||||
}
|
||||
|
||||
query += ' ORDER BY created_at DESC';
|
||||
|
||||
const result = await db.query(query, params);
|
||||
|
||||
const processedResults = result.rows.map(row => {
|
||||
if (row.items) {
|
||||
try {
|
||||
row.items = JSON.parse(row.items);
|
||||
} catch (e) {
|
||||
row.items = [];
|
||||
}
|
||||
}
|
||||
return row;
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: processedResults,
|
||||
count: processedResults.length
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取项目退库记录失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取项目退库记录失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 获取材料价格历史
|
||||
* 遵循设计方案:每次订单确认时自动写入material_price_history表
|
||||
*/
|
||||
router.get('/price-history/:productId', async (req, res) => {
|
||||
try {
|
||||
const { productId } = req.params;
|
||||
const { supplier_id, limit } = req.query;
|
||||
|
||||
let query = `
|
||||
SELECT mph.*, s.name as supplier_name, s.country as supplier_country,
|
||||
po.code as order_code
|
||||
FROM material_price_history mph
|
||||
LEFT JOIN suppliers s ON mph.supplier_id = s.id
|
||||
LEFT JOIN purchase_orders po ON mph.purchase_order_id = po.id
|
||||
WHERE mph.product_id = ?
|
||||
`;
|
||||
const params = [productId];
|
||||
|
||||
if (supplier_id) {
|
||||
query += ' AND mph.supplier_id = $1';
|
||||
params.push(supplier_id);
|
||||
}
|
||||
|
||||
query += ' ORDER BY mph.purchase_date DESC';
|
||||
|
||||
if (limit) {
|
||||
query += ' LIMIT $1';
|
||||
params.push(parseInt(limit));
|
||||
}
|
||||
|
||||
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: '获取材料价格历史失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 获取材料平均采购价格
|
||||
*/
|
||||
router.get('/average-price/:productId', async (req, res) => {
|
||||
try {
|
||||
const { productId } = req.params;
|
||||
|
||||
const result = await db.query(`
|
||||
SELECT
|
||||
AVG(unit_price) as avg_price,
|
||||
MIN(unit_price) as min_price,
|
||||
MAX(unit_price) as max_price,
|
||||
COUNT(*) as purchase_count,
|
||||
SUM(quantity) as total_quantity
|
||||
FROM material_price_history
|
||||
WHERE product_id = ?
|
||||
`, [productId]);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: result.rows[0]
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取材料平均价格失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取材料平均价格失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 获取材料近期价格趋势
|
||||
*/
|
||||
router.get('/price-trend/:productId', async (req, res) => {
|
||||
try {
|
||||
const { productId } = req.params;
|
||||
const { months } = req.query;
|
||||
const monthLimit = parseInt(months) || 6;
|
||||
|
||||
const result = await db.query(`
|
||||
SELECT
|
||||
strftime('%Y-%m', purchase_date) as month,
|
||||
AVG(unit_price) as avg_price,
|
||||
SUM(quantity) as total_quantity,
|
||||
COUNT(*) as purchase_count
|
||||
FROM material_price_history
|
||||
WHERE product_id = ?
|
||||
AND purchase_date >= date('now', '-' || ? || ' months')
|
||||
GROUP BY strftime('%Y-%m', purchase_date)
|
||||
ORDER BY month DESC
|
||||
`, [productId, monthLimit]);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: result.rows
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取材料价格趋势失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取材料价格趋势失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 更新项目材料库存(手动调整)
|
||||
*/
|
||||
router.put('/inventory/:projectId/:productId', async (req, res) => {
|
||||
try {
|
||||
const { projectId, productId } = req.params;
|
||||
const { used_quantity, remark } = req.body;
|
||||
|
||||
const existingResult = await db.query(`
|
||||
SELECT * FROM project_material_inventory
|
||||
WHERE project_id = ? AND product_id = ?
|
||||
`, [projectId, productId]);
|
||||
|
||||
if (existingResult.rows.length === 0) {
|
||||
return res.status(404).json({ success: false, message: '材料库存记录不存在' });
|
||||
}
|
||||
|
||||
const existing = existingResult.rows[0];
|
||||
const newUsedQty = (existing.used_quantity || 0) + (used_quantity || 0);
|
||||
const newCurrentQty = Math.max(0, (existing.current_quantity || 0) - (used_quantity || 0));
|
||||
|
||||
await db.query(`
|
||||
UPDATE project_material_inventory
|
||||
SET used_quantity = ?, current_quantity = ?, updated_at = CURRENT_TIMESTAMP
|
||||
WHERE project_id = ? AND product_id = ?
|
||||
`, [newUsedQty, newCurrentQty, projectId, productId]);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '库存更新成功',
|
||||
data: { used_quantity: newUsedQty, current_quantity: newCurrentQty }
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('更新材料库存失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '更新材料库存失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* 获取所有项目的材料库存汇总
|
||||
*/
|
||||
router.get('/all-projects-summary', async (req, res) => {
|
||||
try {
|
||||
const result = await db.query(`
|
||||
SELECT
|
||||
p.id as project_id,
|
||||
p.name as project_name,
|
||||
COUNT(pmi.id) as item_count,
|
||||
SUM(pmi.current_quantity) as total_quantity,
|
||||
SUM(pmi.total_amount) as total_value
|
||||
FROM projects p
|
||||
LEFT JOIN project_material_inventory pmi ON p.id = pmi.project_id
|
||||
WHERE p.status = 'active'
|
||||
GROUP BY p.id
|
||||
ORDER BY p.name
|
||||
`);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: result.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