365 lines
10 KiB
JavaScript
365 lines
10 KiB
JavaScript
/**
|
|
* 项目材料管理路由
|
|
* 遵循设计方案:采购-付款-物流-退库一体化流程设计方案.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: process.env.NODE_ENV === 'development' ? 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: process.env.NODE_ENV === 'development' ? 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: process.env.NODE_ENV === 'development' ? 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: process.env.NODE_ENV === 'development' ? 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: process.env.NODE_ENV === 'development' ? 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: process.env.NODE_ENV === 'development' ? 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: process.env.NODE_ENV === 'development' ? 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: process.env.NODE_ENV === 'development' ? 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: process.env.NODE_ENV === 'development' ? error.message : '操作失败'
|
|
});
|
|
}
|
|
});
|
|
|
|
module.exports = router;
|