添加 IntelliJ IDEA 项目配置文件
This commit is contained in:
548
backend/routes/quality.js
Normal file
548
backend/routes/quality.js
Normal file
@@ -0,0 +1,548 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const Joi = require('joi');
|
||||
|
||||
// 模拟质量检测数据
|
||||
let qualityRecords = [
|
||||
{
|
||||
id: 1,
|
||||
orderId: 1,
|
||||
inspectionCode: 'QC001',
|
||||
inspectorName: '张检验员',
|
||||
inspectionDate: '2024-01-15',
|
||||
inspectionLocation: '山东省济南市历城区牲畜养殖基地',
|
||||
cattleCount: 50,
|
||||
samplingCount: 5,
|
||||
inspectionType: 'pre_transport',
|
||||
healthStatus: 'healthy',
|
||||
quarantineCertificate: 'QC001_certificate.pdf',
|
||||
vaccineRecords: [
|
||||
{
|
||||
vaccineName: '口蹄疫疫苗',
|
||||
vaccineDate: '2024-01-01',
|
||||
batchNumber: 'VAC20240101'
|
||||
}
|
||||
],
|
||||
diseaseTests: [
|
||||
{
|
||||
testName: '布鲁氏菌病检测',
|
||||
result: 'negative',
|
||||
testDate: '2024-01-10'
|
||||
},
|
||||
{
|
||||
testName: '结核病检测',
|
||||
result: 'negative',
|
||||
testDate: '2024-01-10'
|
||||
}
|
||||
],
|
||||
weightCheck: {
|
||||
averageWeight: 450,
|
||||
weightRange: '420-480',
|
||||
weightVariance: 15
|
||||
},
|
||||
qualityGrade: 'A',
|
||||
qualityScore: 95,
|
||||
issues: [],
|
||||
recommendations: [
|
||||
'建议继续保持当前饲养标准',
|
||||
'注意观察牲畜健康状况'
|
||||
],
|
||||
photos: [
|
||||
'inspection_001_1.jpg',
|
||||
'inspection_001_2.jpg'
|
||||
],
|
||||
status: 'passed',
|
||||
createdAt: new Date('2024-01-15'),
|
||||
updatedAt: new Date('2024-01-15')
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
orderId: 2,
|
||||
inspectionCode: 'QC002',
|
||||
inspectorName: '李检验员',
|
||||
inspectionDate: '2024-01-16',
|
||||
inspectionLocation: '内蒙古呼和浩特市草原牧场',
|
||||
cattleCount: 80,
|
||||
samplingCount: 8,
|
||||
inspectionType: 'pre_transport',
|
||||
healthStatus: 'healthy',
|
||||
quarantineCertificate: 'QC002_certificate.pdf',
|
||||
vaccineRecords: [
|
||||
{
|
||||
vaccineName: '口蹄疫疫苗',
|
||||
vaccineDate: '2023-12-15',
|
||||
batchNumber: 'VAC20231215'
|
||||
}
|
||||
],
|
||||
diseaseTests: [
|
||||
{
|
||||
testName: '布鲁氏菌病检测',
|
||||
result: 'negative',
|
||||
testDate: '2024-01-12'
|
||||
}
|
||||
],
|
||||
weightCheck: {
|
||||
averageWeight: 480,
|
||||
weightRange: '450-520',
|
||||
weightVariance: 20
|
||||
},
|
||||
qualityGrade: 'A',
|
||||
qualityScore: 92,
|
||||
issues: [
|
||||
{
|
||||
type: 'minor',
|
||||
description: '个别牲畜体重偏轻',
|
||||
solution: '加强营养补充'
|
||||
}
|
||||
],
|
||||
recommendations: [
|
||||
'对体重偏轻的牲畜进行重点关注',
|
||||
'适当调整饲料配比'
|
||||
],
|
||||
photos: [
|
||||
'inspection_002_1.jpg',
|
||||
'inspection_002_2.jpg',
|
||||
'inspection_002_3.jpg'
|
||||
],
|
||||
status: 'passed',
|
||||
createdAt: new Date('2024-01-16'),
|
||||
updatedAt: new Date('2024-01-16')
|
||||
}
|
||||
];
|
||||
|
||||
// 验证schemas
|
||||
const inspectionCreateSchema = Joi.object({
|
||||
orderId: Joi.number().integer().required(),
|
||||
inspectorName: Joi.string().min(2).max(50).required(),
|
||||
inspectionDate: Joi.date().iso().required(),
|
||||
inspectionLocation: Joi.string().min(5).max(200).required(),
|
||||
cattleCount: Joi.number().integer().min(1).required(),
|
||||
samplingCount: Joi.number().integer().min(1).required(),
|
||||
inspectionType: Joi.string().valid('pre_transport', 'during_transport', 'post_transport', 'arrival').required()
|
||||
});
|
||||
|
||||
const qualityResultSchema = Joi.object({
|
||||
healthStatus: Joi.string().valid('healthy', 'sick', 'quarantine').required(),
|
||||
qualityGrade: Joi.string().valid('A+', 'A', 'B+', 'B', 'C', 'D').required(),
|
||||
qualityScore: Joi.number().min(0).max(100).required(),
|
||||
weightCheck: Joi.object({
|
||||
averageWeight: Joi.number().min(0),
|
||||
weightRange: Joi.string(),
|
||||
weightVariance: Joi.number().min(0)
|
||||
}),
|
||||
diseaseTests: Joi.array().items(Joi.object({
|
||||
testName: Joi.string().required(),
|
||||
result: Joi.string().valid('positive', 'negative', 'inconclusive').required(),
|
||||
testDate: Joi.date().iso().required()
|
||||
})),
|
||||
issues: Joi.array().items(Joi.object({
|
||||
type: Joi.string().valid('critical', 'major', 'minor').required(),
|
||||
description: Joi.string().required(),
|
||||
solution: Joi.string()
|
||||
})),
|
||||
recommendations: Joi.array().items(Joi.string())
|
||||
});
|
||||
|
||||
// 获取质量检测列表
|
||||
router.get('/', (req, res) => {
|
||||
try {
|
||||
const {
|
||||
page = 1,
|
||||
pageSize = 20,
|
||||
keyword,
|
||||
inspectionType,
|
||||
qualityGrade,
|
||||
status,
|
||||
startDate,
|
||||
endDate
|
||||
} = req.query;
|
||||
|
||||
let filteredRecords = [...qualityRecords];
|
||||
|
||||
// 关键词搜索
|
||||
if (keyword) {
|
||||
filteredRecords = filteredRecords.filter(record =>
|
||||
record.inspectionCode.includes(keyword) ||
|
||||
record.inspectorName.includes(keyword) ||
|
||||
record.inspectionLocation.includes(keyword)
|
||||
);
|
||||
}
|
||||
|
||||
// 检测类型筛选
|
||||
if (inspectionType) {
|
||||
filteredRecords = filteredRecords.filter(record => record.inspectionType === inspectionType);
|
||||
}
|
||||
|
||||
// 质量等级筛选
|
||||
if (qualityGrade) {
|
||||
filteredRecords = filteredRecords.filter(record => record.qualityGrade === qualityGrade);
|
||||
}
|
||||
|
||||
// 状态筛选
|
||||
if (status) {
|
||||
filteredRecords = filteredRecords.filter(record => record.status === status);
|
||||
}
|
||||
|
||||
// 时间范围筛选
|
||||
if (startDate) {
|
||||
filteredRecords = filteredRecords.filter(record =>
|
||||
new Date(record.inspectionDate) >= new Date(startDate)
|
||||
);
|
||||
}
|
||||
|
||||
if (endDate) {
|
||||
filteredRecords = filteredRecords.filter(record =>
|
||||
new Date(record.inspectionDate) <= new Date(endDate)
|
||||
);
|
||||
}
|
||||
|
||||
// 分页处理
|
||||
const startIndex = (page - 1) * pageSize;
|
||||
const endIndex = startIndex + parseInt(pageSize);
|
||||
const paginatedRecords = filteredRecords.slice(startIndex, endIndex);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
list: paginatedRecords,
|
||||
pagination: {
|
||||
page: parseInt(page),
|
||||
pageSize: parseInt(pageSize),
|
||||
total: filteredRecords.length,
|
||||
totalPages: Math.ceil(filteredRecords.length / pageSize)
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取质量检测列表失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 获取质量检测详情
|
||||
router.get('/:id', (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const record = qualityRecords.find(r => r.id === parseInt(id));
|
||||
|
||||
if (!record) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '质量检测记录不存在'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: record
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取质量检测详情失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 创建质量检测记录
|
||||
router.post('/', (req, res) => {
|
||||
try {
|
||||
const { error, value } = inspectionCreateSchema.validate(req.body);
|
||||
if (error) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '参数验证失败',
|
||||
errors: error.details.map(detail => detail.message)
|
||||
});
|
||||
}
|
||||
|
||||
const inspectionCode = `QC${String(Date.now()).slice(-6)}`;
|
||||
|
||||
const newRecord = {
|
||||
id: Math.max(...qualityRecords.map(r => r.id)) + 1,
|
||||
...value,
|
||||
inspectionCode,
|
||||
healthStatus: 'pending',
|
||||
quarantineCertificate: '',
|
||||
vaccineRecords: [],
|
||||
diseaseTests: [],
|
||||
weightCheck: null,
|
||||
qualityGrade: '',
|
||||
qualityScore: 0,
|
||||
issues: [],
|
||||
recommendations: [],
|
||||
photos: [],
|
||||
status: 'pending',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
};
|
||||
|
||||
qualityRecords.push(newRecord);
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: '质量检测记录创建成功',
|
||||
data: newRecord
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '创建质量检测记录失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 更新质量检测结果
|
||||
router.put('/:id/result', (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { error, value } = qualityResultSchema.validate(req.body);
|
||||
|
||||
if (error) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '参数验证失败',
|
||||
errors: error.details.map(detail => detail.message)
|
||||
});
|
||||
}
|
||||
|
||||
const recordIndex = qualityRecords.findIndex(r => r.id === parseInt(id));
|
||||
if (recordIndex === -1) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '质量检测记录不存在'
|
||||
});
|
||||
}
|
||||
|
||||
// 根据检测结果确定状态
|
||||
let status = 'passed';
|
||||
if (value.healthStatus === 'sick' || value.qualityScore < 60) {
|
||||
status = 'failed';
|
||||
} else if (value.healthStatus === 'quarantine' || value.issues.some(issue => issue.type === 'critical')) {
|
||||
status = 'quarantine';
|
||||
}
|
||||
|
||||
qualityRecords[recordIndex] = {
|
||||
...qualityRecords[recordIndex],
|
||||
...value,
|
||||
status,
|
||||
updatedAt: new Date()
|
||||
};
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '质量检测结果更新成功',
|
||||
data: qualityRecords[recordIndex]
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '更新质量检测结果失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 上传检测照片
|
||||
router.post('/:id/photos', (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { photos } = req.body;
|
||||
|
||||
if (!Array.isArray(photos) || photos.length === 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '照片列表不能为空'
|
||||
});
|
||||
}
|
||||
|
||||
const recordIndex = qualityRecords.findIndex(r => r.id === parseInt(id));
|
||||
if (recordIndex === -1) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '质量检测记录不存在'
|
||||
});
|
||||
}
|
||||
|
||||
qualityRecords[recordIndex].photos = [...qualityRecords[recordIndex].photos, ...photos];
|
||||
qualityRecords[recordIndex].updatedAt = new Date();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '照片上传成功',
|
||||
data: {
|
||||
photos: qualityRecords[recordIndex].photos
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '上传照片失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 获取质量统计
|
||||
router.get('/stats/overview', (req, res) => {
|
||||
try {
|
||||
const totalInspections = qualityRecords.length;
|
||||
const passedCount = qualityRecords.filter(r => r.status === 'passed').length;
|
||||
const failedCount = qualityRecords.filter(r => r.status === 'failed').length;
|
||||
const quarantineCount = qualityRecords.filter(r => r.status === 'quarantine').length;
|
||||
const pendingCount = qualityRecords.filter(r => r.status === 'pending').length;
|
||||
|
||||
// 平均质量分数
|
||||
const completedRecords = qualityRecords.filter(r => r.qualityScore > 0);
|
||||
const averageScore = completedRecords.length > 0
|
||||
? completedRecords.reduce((sum, r) => sum + r.qualityScore, 0) / completedRecords.length
|
||||
: 0;
|
||||
|
||||
// 质量等级分布
|
||||
const gradeDistribution = qualityRecords
|
||||
.filter(r => r.qualityGrade)
|
||||
.reduce((dist, record) => {
|
||||
dist[record.qualityGrade] = (dist[record.qualityGrade] || 0) + 1;
|
||||
return dist;
|
||||
}, {});
|
||||
|
||||
// 检测类型分布
|
||||
const typeDistribution = qualityRecords.reduce((dist, record) => {
|
||||
dist[record.inspectionType] = (dist[record.inspectionType] || 0) + 1;
|
||||
return dist;
|
||||
}, {});
|
||||
|
||||
// 合格率
|
||||
const passRate = totalInspections > 0 ? Math.round((passedCount / totalInspections) * 100) : 0;
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
totalInspections,
|
||||
passedCount,
|
||||
failedCount,
|
||||
quarantineCount,
|
||||
pendingCount,
|
||||
averageScore: Math.round(averageScore * 10) / 10,
|
||||
passRate,
|
||||
gradeDistribution,
|
||||
typeDistribution
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取质量统计失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 获取质量趋势报告
|
||||
router.get('/reports/trend', (req, res) => {
|
||||
try {
|
||||
const { period = 'month' } = req.query;
|
||||
|
||||
// 按时间分组统计
|
||||
const now = new Date();
|
||||
const trends = [];
|
||||
|
||||
if (period === 'month') {
|
||||
// 最近12个月
|
||||
for (let i = 11; i >= 0; i--) {
|
||||
const date = new Date(now.getFullYear(), now.getMonth() - i, 1);
|
||||
const monthRecords = qualityRecords.filter(r => {
|
||||
const recordDate = new Date(r.inspectionDate);
|
||||
return recordDate.getMonth() === date.getMonth() &&
|
||||
recordDate.getFullYear() === date.getFullYear();
|
||||
});
|
||||
|
||||
const passed = monthRecords.filter(r => r.status === 'passed').length;
|
||||
const total = monthRecords.length;
|
||||
const averageScore = monthRecords.length > 0
|
||||
? monthRecords.reduce((sum, r) => sum + (r.qualityScore || 0), 0) / monthRecords.length
|
||||
: 0;
|
||||
|
||||
trends.push({
|
||||
period: `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`,
|
||||
totalInspections: total,
|
||||
passedCount: passed,
|
||||
passRate: total > 0 ? Math.round((passed / total) * 100) : 0,
|
||||
averageScore: Math.round(averageScore * 10) / 10
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
period,
|
||||
trends
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取质量趋势报告失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 获取检测标准配置
|
||||
router.get('/standards', (req, res) => {
|
||||
try {
|
||||
const standards = {
|
||||
weightStandards: {
|
||||
cattle: {
|
||||
min: 400,
|
||||
max: 600,
|
||||
optimal: 500
|
||||
}
|
||||
},
|
||||
healthRequirements: [
|
||||
{
|
||||
name: '口蹄疫疫苗',
|
||||
required: true,
|
||||
validityDays: 365
|
||||
},
|
||||
{
|
||||
name: '布鲁氏菌病检测',
|
||||
required: true,
|
||||
validityDays: 30
|
||||
},
|
||||
{
|
||||
name: '结核病检测',
|
||||
required: true,
|
||||
validityDays: 30
|
||||
}
|
||||
],
|
||||
gradingCriteria: {
|
||||
'A+': { minScore: 95, description: '优质级' },
|
||||
'A': { minScore: 85, description: '良好级' },
|
||||
'B+': { minScore: 75, description: '合格级' },
|
||||
'B': { minScore: 65, description: '基本合格级' },
|
||||
'C': { minScore: 50, description: '待改进级' },
|
||||
'D': { minScore: 0, description: '不合格级' }
|
||||
}
|
||||
};
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: standards
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取检测标准失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
Reference in New Issue
Block a user