853 lines
27 KiB
JavaScript
853 lines
27 KiB
JavaScript
|
|
const { Op } = require('sequelize');
|
|||
|
|
const IotCattle = require('../models/IotCattle');
|
|||
|
|
const Farm = require('../models/Farm');
|
|||
|
|
const CattlePen = require('../models/CattlePen');
|
|||
|
|
const CattleBatch = require('../models/CattleBatch');
|
|||
|
|
const CattleType = require('../models/CattleType');
|
|||
|
|
const CattleUser = require('../models/CattleUser');
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 计算月龄(基于birthday时间戳)
|
|||
|
|
*/
|
|||
|
|
const calculateAgeInMonths = (birthday) => {
|
|||
|
|
if (!birthday) return 0;
|
|||
|
|
|
|||
|
|
const now = Math.floor(Date.now() / 1000); // 当前时间戳(秒)
|
|||
|
|
const birthTimestamp = parseInt(birthday);
|
|||
|
|
|
|||
|
|
if (isNaN(birthTimestamp)) return 0;
|
|||
|
|
|
|||
|
|
const ageInSeconds = now - birthTimestamp;
|
|||
|
|
const ageInMonths = Math.floor(ageInSeconds / (30 * 24 * 60 * 60)); // 按30天一个月计算
|
|||
|
|
|
|||
|
|
return Math.max(0, ageInMonths);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 类别中文映射
|
|||
|
|
*/
|
|||
|
|
const getCategoryName = (cate) => {
|
|||
|
|
const categoryMap = {
|
|||
|
|
1: '犊牛',
|
|||
|
|
2: '繁殖牛',
|
|||
|
|
3: '基础母牛',
|
|||
|
|
4: '隔离牛',
|
|||
|
|
5: '治疗牛',
|
|||
|
|
6: '育肥牛'
|
|||
|
|
};
|
|||
|
|
return categoryMap[cate] || '未知';
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取栏舍、批次、品种和用途名称
|
|||
|
|
*/
|
|||
|
|
const getPenBatchTypeAndUserNames = async (cattleList) => {
|
|||
|
|
// 获取所有唯一的栏舍ID、批次ID、品种ID和品系ID(用途)
|
|||
|
|
const penIds = [...new Set(cattleList.map(cattle => cattle.penId).filter(id => id))];
|
|||
|
|
const batchIds = [...new Set(cattleList.map(cattle => cattle.batchId).filter(id => id && id > 0))];
|
|||
|
|
const typeIds = [...new Set(cattleList.map(cattle => cattle.varieties).filter(id => id))];
|
|||
|
|
const strainIds = [...new Set(cattleList.map(cattle => cattle.strain).filter(id => id))];
|
|||
|
|
|
|||
|
|
// 查询栏舍名称
|
|||
|
|
const penNames = {};
|
|||
|
|
if (penIds.length > 0) {
|
|||
|
|
const pens = await CattlePen.findAll({
|
|||
|
|
where: { id: penIds },
|
|||
|
|
attributes: ['id', 'name']
|
|||
|
|
});
|
|||
|
|
pens.forEach(pen => {
|
|||
|
|
penNames[pen.id] = pen.name;
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 查询批次名称
|
|||
|
|
const batchNames = {};
|
|||
|
|
if (batchIds.length > 0) {
|
|||
|
|
const batches = await CattleBatch.findAll({
|
|||
|
|
where: { id: batchIds },
|
|||
|
|
attributes: ['id', 'name']
|
|||
|
|
});
|
|||
|
|
batches.forEach(batch => {
|
|||
|
|
batchNames[batch.id] = batch.name;
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 查询品种名称
|
|||
|
|
const typeNames = {};
|
|||
|
|
if (typeIds.length > 0) {
|
|||
|
|
const types = await CattleType.findAll({
|
|||
|
|
where: { id: typeIds },
|
|||
|
|
attributes: ['id', 'name']
|
|||
|
|
});
|
|||
|
|
types.forEach(type => {
|
|||
|
|
typeNames[type.id] = type.name;
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 查询用途名称(基于strain字段)
|
|||
|
|
const userNames = {};
|
|||
|
|
if (strainIds.length > 0) {
|
|||
|
|
const users = await CattleUser.findAll({
|
|||
|
|
where: { id: strainIds },
|
|||
|
|
attributes: ['id', 'name']
|
|||
|
|
});
|
|||
|
|
users.forEach(user => {
|
|||
|
|
userNames[user.id] = user.name;
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return { penNames, batchNames, typeNames, userNames };
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 牛只档案控制器 - 基于iot_cattle表
|
|||
|
|
*/
|
|||
|
|
class IotCattleController {
|
|||
|
|
/**
|
|||
|
|
* 获取牛只档案列表
|
|||
|
|
*/
|
|||
|
|
async getCattleArchives(req, res) {
|
|||
|
|
try {
|
|||
|
|
const {
|
|||
|
|
page = 1,
|
|||
|
|
pageSize = 10,
|
|||
|
|
search = '',
|
|||
|
|
farmId = '',
|
|||
|
|
penId = '',
|
|||
|
|
batchId = ''
|
|||
|
|
} = req.query;
|
|||
|
|
|
|||
|
|
console.log('=== 后端接收搜索请求 ===');
|
|||
|
|
console.log('请求时间:', new Date().toISOString());
|
|||
|
|
console.log('请求参数:', { page, pageSize, search, farmId, penId, batchId });
|
|||
|
|
console.log('请求来源:', req.ip);
|
|||
|
|
console.log('User-Agent:', req.get('User-Agent'));
|
|||
|
|
|
|||
|
|
const offset = (page - 1) * pageSize;
|
|||
|
|
const whereConditions = {};
|
|||
|
|
|
|||
|
|
// 搜索条件
|
|||
|
|
if (search) {
|
|||
|
|
whereConditions[Op.or] = [
|
|||
|
|
{ earNumber: { [Op.like]: `%${search}%` } },
|
|||
|
|
{ strain: { [Op.like]: `%${search}%` } }
|
|||
|
|
];
|
|||
|
|
console.log('=== 搜索条件构建 ===');
|
|||
|
|
console.log('搜索关键词:', search);
|
|||
|
|
console.log('搜索条件对象:', JSON.stringify(whereConditions, null, 2));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 农场筛选
|
|||
|
|
if (farmId) {
|
|||
|
|
whereConditions.orgId = farmId;
|
|||
|
|
console.log('添加农场筛选条件:', farmId);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 栏舍筛选
|
|||
|
|
if (penId) {
|
|||
|
|
whereConditions.penId = penId;
|
|||
|
|
console.log('添加栏舍筛选条件:', penId);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 批次筛选
|
|||
|
|
if (batchId) {
|
|||
|
|
whereConditions.batchId = batchId;
|
|||
|
|
console.log('添加批次筛选条件:', batchId);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
console.log('=== 最终查询条件 ===');
|
|||
|
|
console.log('完整查询条件:', JSON.stringify(whereConditions, null, 2));
|
|||
|
|
console.log('分页参数:', { offset, limit: pageSize });
|
|||
|
|
|
|||
|
|
// 先获取总数(严格查询未删除的记录)
|
|||
|
|
console.log('=== 开始数据库查询 ===');
|
|||
|
|
console.log('查询时间:', new Date().toISOString());
|
|||
|
|
|
|||
|
|
const countStartTime = Date.now();
|
|||
|
|
const totalCount = await IotCattle.count({
|
|||
|
|
where: {
|
|||
|
|
...whereConditions,
|
|||
|
|
isDelete: 0 // 确保只统计未删除的记录
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
const countEndTime = Date.now();
|
|||
|
|
|
|||
|
|
console.log('=== 总数查询完成 ===');
|
|||
|
|
console.log('查询耗时:', countEndTime - countStartTime, 'ms');
|
|||
|
|
console.log('总记录数:', totalCount);
|
|||
|
|
|
|||
|
|
// 获取分页数据(严格查询未删除的记录)
|
|||
|
|
const dataStartTime = Date.now();
|
|||
|
|
const rows = await IotCattle.findAll({
|
|||
|
|
where: {
|
|||
|
|
...whereConditions,
|
|||
|
|
isDelete: 0 // 确保只查询未删除的记录
|
|||
|
|
},
|
|||
|
|
attributes: [
|
|||
|
|
'id', 'earNumber', 'sex', 'strain', 'varieties', 'cate',
|
|||
|
|
'birthWeight', 'birthday', 'penId', 'batchId', 'orgId',
|
|||
|
|
'weight', 'level', 'weightCalculateTime', 'dayOfBirthday',
|
|||
|
|
'intoTime', 'parity', 'source', 'sourceDay', 'sourceWeight',
|
|||
|
|
'event', 'eventTime', 'lactationDay', 'semenNum', 'isWear',
|
|||
|
|
'imgs', 'isEleAuth', 'isQuaAuth', 'isDelete', 'isOut',
|
|||
|
|
'createUid', 'createTime', 'algebra', 'colour', 'infoWeight',
|
|||
|
|
'descent', 'isVaccin', 'isInsemination', 'isInsure', 'isMortgage',
|
|||
|
|
'updateTime', 'breedBullTime', 'sixWeight', 'eighteenWeight',
|
|||
|
|
'twelveDayWeight', 'eighteenDayWeight', 'xxivDayWeight',
|
|||
|
|
'semenBreedImgs', 'sellStatus'
|
|||
|
|
],
|
|||
|
|
limit: parseInt(pageSize),
|
|||
|
|
offset: parseInt(offset),
|
|||
|
|
order: [['id', 'ASC']] // 升序排序
|
|||
|
|
});
|
|||
|
|
const dataEndTime = Date.now();
|
|||
|
|
|
|||
|
|
console.log('=== 数据查询完成 ===');
|
|||
|
|
console.log('查询耗时:', dataEndTime - dataStartTime, 'ms');
|
|||
|
|
console.log('查询到记录数:', rows.length);
|
|||
|
|
console.log('记录详情:', rows.map(row => ({
|
|||
|
|
id: row.id,
|
|||
|
|
earNumber: row.earNumber,
|
|||
|
|
sex: row.sex,
|
|||
|
|
varieties: row.varieties
|
|||
|
|
})));
|
|||
|
|
|
|||
|
|
// 获取栏舍、批次、品种和用途名称
|
|||
|
|
const { penNames, batchNames, typeNames, userNames } = await getPenBatchTypeAndUserNames(rows);
|
|||
|
|
|
|||
|
|
// 格式化数据(基于iot_cattle表字段映射)
|
|||
|
|
const formattedData = rows.map(cattle => ({
|
|||
|
|
id: cattle.id,
|
|||
|
|
earNumber: cattle.earNumber, // 映射iot_cattle.ear_number
|
|||
|
|
sex: cattle.sex, // 映射iot_cattle.sex
|
|||
|
|
strain: userNames[cattle.strain] || `品系ID:${cattle.strain}`, // 映射iot_cattle.strain为用途名称
|
|||
|
|
varieties: typeNames[cattle.varieties] || `品种ID:${cattle.varieties}`, // 映射iot_cattle.varieties为品种名称
|
|||
|
|
cate: getCategoryName(cattle.cate), // 映射iot_cattle.cate为中文
|
|||
|
|
birthWeight: cattle.birthWeight, // 映射iot_cattle.birth_weight
|
|||
|
|
birthday: cattle.birthday, // 映射iot_cattle.birthday
|
|||
|
|
intoTime: cattle.intoTime,
|
|||
|
|
parity: cattle.parity,
|
|||
|
|
source: cattle.source,
|
|||
|
|
sourceDay: cattle.sourceDay,
|
|||
|
|
sourceWeight: cattle.sourceWeight,
|
|||
|
|
ageInMonths: calculateAgeInMonths(cattle.birthday), // 从iot_cattle.birthday计算月龄
|
|||
|
|
physiologicalStage: cattle.level || 0, // 使用level作为生理阶段
|
|||
|
|
currentWeight: cattle.weight || 0, // 使用weight作为当前体重
|
|||
|
|
weightCalculateTime: cattle.weightCalculateTime,
|
|||
|
|
dayOfBirthday: cattle.dayOfBirthday,
|
|||
|
|
farmName: `农场ID:${cattle.orgId}`, // 暂时显示ID,后续可优化
|
|||
|
|
penName: cattle.penId ? (penNames[cattle.penId] || `栏舍ID:${cattle.penId}`) : '未分配栏舍', // 映射栏舍名称
|
|||
|
|
batchName: cattle.batchId === 0 ? '未分配批次' : (batchNames[cattle.batchId] || `批次ID:${cattle.batchId}`), // 映射批次名称
|
|||
|
|
farmId: cattle.orgId, // 映射iot_cattle.org_id
|
|||
|
|
penId: cattle.penId, // 映射iot_cattle.pen_id
|
|||
|
|
batchId: cattle.batchId // 映射iot_cattle.batch_id
|
|||
|
|
}));
|
|||
|
|
|
|||
|
|
console.log('=== 数据格式化完成 ===');
|
|||
|
|
console.log('格式化后数据条数:', formattedData.length);
|
|||
|
|
console.log('格式化后数据示例:', formattedData.slice(0, 2));
|
|||
|
|
|
|||
|
|
const responseData = {
|
|||
|
|
success: true,
|
|||
|
|
data: {
|
|||
|
|
list: formattedData,
|
|||
|
|
pagination: {
|
|||
|
|
current: parseInt(page),
|
|||
|
|
pageSize: parseInt(pageSize),
|
|||
|
|
total: totalCount,
|
|||
|
|
pages: Math.ceil(totalCount / parseInt(pageSize))
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
message: '获取牛只档案列表成功'
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
console.log('=== 准备返回响应 ===');
|
|||
|
|
console.log('响应时间:', new Date().toISOString());
|
|||
|
|
console.log('响应数据大小:', JSON.stringify(responseData).length, 'bytes');
|
|||
|
|
console.log('分页信息:', responseData.data.pagination);
|
|||
|
|
|
|||
|
|
res.json(responseData);
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('获取牛只档案列表失败:', error);
|
|||
|
|
res.status(500).json({
|
|||
|
|
success: false,
|
|||
|
|
message: '获取牛只档案列表失败',
|
|||
|
|
error: error.message
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取单个牛只档案详情
|
|||
|
|
*/
|
|||
|
|
async getCattleArchiveById(req, res) {
|
|||
|
|
try {
|
|||
|
|
const { id } = req.params;
|
|||
|
|
|
|||
|
|
const cattle = await IotCattle.findByPk(id, {
|
|||
|
|
include: [
|
|||
|
|
{
|
|||
|
|
model: Farm,
|
|||
|
|
as: 'farm',
|
|||
|
|
attributes: ['id', 'name', 'location']
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
model: CattlePen,
|
|||
|
|
as: 'pen',
|
|||
|
|
attributes: ['id', 'name', 'code']
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
model: CattleBatch,
|
|||
|
|
as: 'batch',
|
|||
|
|
attributes: ['id', 'name', 'code']
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
if (!cattle) {
|
|||
|
|
return res.status(404).json({
|
|||
|
|
success: false,
|
|||
|
|
message: '牛只档案不存在'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 格式化数据(基于iot_cattle表字段映射)
|
|||
|
|
const formattedData = {
|
|||
|
|
id: cattle.id,
|
|||
|
|
earNumber: cattle.earNumber, // 映射iot_cattle.ear_number
|
|||
|
|
sex: cattle.sex, // 映射iot_cattle.sex
|
|||
|
|
strain: cattle.strain, // 映射iot_cattle.strain
|
|||
|
|
varieties: cattle.varieties, // 映射iot_cattle.varieties(单个记录不需要名称映射)
|
|||
|
|
cate: cattle.cate, // 映射iot_cattle.cate
|
|||
|
|
birthWeight: cattle.birthWeight, // 映射iot_cattle.birth_weight
|
|||
|
|
birthday: cattle.birthday, // 映射iot_cattle.birthday
|
|||
|
|
intoTime: cattle.intoTime,
|
|||
|
|
parity: cattle.parity,
|
|||
|
|
source: cattle.source,
|
|||
|
|
sourceDay: cattle.sourceDay,
|
|||
|
|
sourceWeight: cattle.sourceWeight,
|
|||
|
|
ageInMonths: calculateAgeInMonths(cattle.birthday), // 从iot_cattle.birthday计算月龄
|
|||
|
|
physiologicalStage: cattle.level || 0, // 使用level作为生理阶段
|
|||
|
|
currentWeight: cattle.weight || 0, // 使用weight作为当前体重
|
|||
|
|
weightCalculateTime: cattle.weightCalculateTime,
|
|||
|
|
dayOfBirthday: cattle.dayOfBirthday,
|
|||
|
|
farmName: `农场ID:${cattle.orgId}`, // 暂时显示ID,后续可优化
|
|||
|
|
penName: cattle.penId ? `栏舍ID:${cattle.penId}` : '未分配栏舍', // 暂时显示ID,后续可优化
|
|||
|
|
batchName: cattle.batchId === 0 ? '未分配批次' : `批次ID:${cattle.batchId}`, // 暂时显示ID,后续可优化
|
|||
|
|
farmId: cattle.orgId, // 映射iot_cattle.org_id
|
|||
|
|
penId: cattle.penId, // 映射iot_cattle.pen_id
|
|||
|
|
batchId: cattle.batchId // 映射iot_cattle.batch_id
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
res.json({
|
|||
|
|
success: true,
|
|||
|
|
data: formattedData,
|
|||
|
|
message: '获取牛只档案详情成功'
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('获取牛只档案详情失败:', error);
|
|||
|
|
res.status(500).json({
|
|||
|
|
success: false,
|
|||
|
|
message: '获取牛只档案详情失败',
|
|||
|
|
error: error.message
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 创建牛只档案
|
|||
|
|
*/
|
|||
|
|
async createCattleArchive(req, res) {
|
|||
|
|
try {
|
|||
|
|
const {
|
|||
|
|
earNumber,
|
|||
|
|
sex,
|
|||
|
|
strain,
|
|||
|
|
varieties,
|
|||
|
|
cate,
|
|||
|
|
birthWeight,
|
|||
|
|
birthday,
|
|||
|
|
penId,
|
|||
|
|
intoTime,
|
|||
|
|
parity,
|
|||
|
|
source,
|
|||
|
|
sourceDay,
|
|||
|
|
sourceWeight,
|
|||
|
|
orgId,
|
|||
|
|
batchId
|
|||
|
|
} = req.body;
|
|||
|
|
|
|||
|
|
// 验证必填字段
|
|||
|
|
if (!earNumber || !sex || !strain || !varieties || !cate || !birthWeight || !birthday || !orgId) {
|
|||
|
|
return res.status(400).json({
|
|||
|
|
success: false,
|
|||
|
|
message: '缺少必填字段'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查耳标号是否已存在
|
|||
|
|
const existingCattle = await IotCattle.findOne({
|
|||
|
|
where: { earNumber: earNumber }
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
if (existingCattle) {
|
|||
|
|
return res.status(400).json({
|
|||
|
|
success: false,
|
|||
|
|
message: '耳标号已存在'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const cattleData = {
|
|||
|
|
earNumber: parseInt(earNumber),
|
|||
|
|
sex: parseInt(sex),
|
|||
|
|
strain: parseInt(strain),
|
|||
|
|
varieties: parseInt(varieties),
|
|||
|
|
cate: parseInt(cate),
|
|||
|
|
birthWeight: parseFloat(birthWeight),
|
|||
|
|
birthday: parseInt(birthday),
|
|||
|
|
penId: penId ? parseInt(penId) : 0,
|
|||
|
|
intoTime: intoTime ? parseInt(intoTime) : 0,
|
|||
|
|
parity: parity ? parseInt(parity) : 0,
|
|||
|
|
source: source ? parseInt(source) : 0,
|
|||
|
|
sourceDay: sourceDay ? parseInt(sourceDay) : 0,
|
|||
|
|
sourceWeight: sourceWeight ? parseFloat(sourceWeight) : 0,
|
|||
|
|
weight: req.body.currentWeight ? parseFloat(req.body.currentWeight) : 0,
|
|||
|
|
event: req.body.event || 1,
|
|||
|
|
eventTime: req.body.eventTime || Math.floor(Date.now() / 1000),
|
|||
|
|
lactationDay: req.body.lactationDay || 0,
|
|||
|
|
semenNum: req.body.semenNum || '',
|
|||
|
|
isWear: req.body.isWear || 0,
|
|||
|
|
imgs: req.body.imgs || '',
|
|||
|
|
isEleAuth: req.body.isEleAuth || 0,
|
|||
|
|
isQuaAuth: req.body.isQuaAuth || 0,
|
|||
|
|
isDelete: 0,
|
|||
|
|
isOut: 0,
|
|||
|
|
createUid: req.user ? req.user.id : 1,
|
|||
|
|
createTime: Math.floor(Date.now() / 1000),
|
|||
|
|
algebra: req.body.algebra || 0,
|
|||
|
|
colour: req.body.colour || '',
|
|||
|
|
infoWeight: req.body.infoWeight ? parseFloat(req.body.infoWeight) : 0,
|
|||
|
|
descent: req.body.descent || 0,
|
|||
|
|
isVaccin: req.body.isVaccin || 0,
|
|||
|
|
isInsemination: req.body.isInsemination || 0,
|
|||
|
|
isInsure: req.body.isInsure || 0,
|
|||
|
|
isMortgage: req.body.isMortgage || 0,
|
|||
|
|
updateTime: Math.floor(Date.now() / 1000),
|
|||
|
|
breedBullTime: req.body.breedBullTime || 0,
|
|||
|
|
level: req.body.level || 0,
|
|||
|
|
sixWeight: req.body.sixWeight ? parseFloat(req.body.sixWeight) : 0,
|
|||
|
|
eighteenWeight: req.body.eighteenWeight ? parseFloat(req.body.eighteenWeight) : 0,
|
|||
|
|
twelveDayWeight: req.body.twelveDayWeight ? parseFloat(req.body.twelveDayWeight) : 0,
|
|||
|
|
eighteenDayWeight: req.body.eighteenDayWeight ? parseFloat(req.body.eighteenDayWeight) : 0,
|
|||
|
|
xxivDayWeight: req.body.xxivDayWeight ? parseFloat(req.body.xxivDayWeight) : 0,
|
|||
|
|
semenBreedImgs: req.body.semenBreedImgs || '',
|
|||
|
|
sellStatus: req.body.sellStatus || 100,
|
|||
|
|
orgId: parseInt(orgId),
|
|||
|
|
batchId: batchId ? parseInt(batchId) : 0
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const cattle = await IotCattle.create(cattleData);
|
|||
|
|
|
|||
|
|
res.status(201).json({
|
|||
|
|
success: true,
|
|||
|
|
data: cattle,
|
|||
|
|
message: '创建牛只档案成功'
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('创建牛只档案失败:', error);
|
|||
|
|
res.status(500).json({
|
|||
|
|
success: false,
|
|||
|
|
message: '创建牛只档案失败',
|
|||
|
|
error: error.message
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 更新牛只档案
|
|||
|
|
*/
|
|||
|
|
async updateCattleArchive(req, res) {
|
|||
|
|
try {
|
|||
|
|
const { id } = req.params;
|
|||
|
|
const updateData = req.body;
|
|||
|
|
|
|||
|
|
const cattle = await IotCattle.findByPk(id);
|
|||
|
|
if (!cattle) {
|
|||
|
|
return res.status(404).json({
|
|||
|
|
success: false,
|
|||
|
|
message: '牛只档案不存在'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果更新耳标号,检查是否重复
|
|||
|
|
if (updateData.earNumber && updateData.earNumber !== cattle.earNumber) {
|
|||
|
|
const existingCattle = await IotCattle.findOne({
|
|||
|
|
where: {
|
|||
|
|
earNumber: updateData.earNumber,
|
|||
|
|
id: { [Op.ne]: id }
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
if (existingCattle) {
|
|||
|
|
return res.status(400).json({
|
|||
|
|
success: false,
|
|||
|
|
message: '耳标号已存在'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 转换数据类型
|
|||
|
|
const processedData = {};
|
|||
|
|
if (updateData.earNumber) processedData.earNumber = parseInt(updateData.earNumber);
|
|||
|
|
if (updateData.sex) processedData.sex = parseInt(updateData.sex);
|
|||
|
|
if (updateData.strain) processedData.strain = parseInt(updateData.strain);
|
|||
|
|
if (updateData.varieties) processedData.varieties = parseInt(updateData.varieties);
|
|||
|
|
if (updateData.cate) processedData.cate = parseInt(updateData.cate);
|
|||
|
|
if (updateData.birthWeight) processedData.birthWeight = parseFloat(updateData.birthWeight);
|
|||
|
|
if (updateData.birthday) processedData.birthday = parseInt(updateData.birthday);
|
|||
|
|
if (updateData.penId) processedData.penId = parseInt(updateData.penId);
|
|||
|
|
if (updateData.intoTime) processedData.intoTime = parseInt(updateData.intoTime);
|
|||
|
|
if (updateData.parity) processedData.parity = parseInt(updateData.parity);
|
|||
|
|
if (updateData.source) processedData.source = parseInt(updateData.source);
|
|||
|
|
if (updateData.sourceDay) processedData.sourceDay = parseInt(updateData.sourceDay);
|
|||
|
|
if (updateData.sourceWeight) processedData.sourceWeight = parseFloat(updateData.sourceWeight);
|
|||
|
|
if (updateData.orgId) processedData.orgId = parseInt(updateData.orgId);
|
|||
|
|
if (updateData.batchId) processedData.batchId = parseInt(updateData.batchId);
|
|||
|
|
|
|||
|
|
await cattle.update(processedData);
|
|||
|
|
|
|||
|
|
res.json({
|
|||
|
|
success: true,
|
|||
|
|
data: cattle,
|
|||
|
|
message: '更新牛只档案成功'
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('更新牛只档案失败:', error);
|
|||
|
|
res.status(500).json({
|
|||
|
|
success: false,
|
|||
|
|
message: '更新牛只档案失败',
|
|||
|
|
error: error.message
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 删除牛只档案
|
|||
|
|
*/
|
|||
|
|
async deleteCattleArchive(req, res) {
|
|||
|
|
try {
|
|||
|
|
const { id } = req.params;
|
|||
|
|
|
|||
|
|
const cattle = await IotCattle.findByPk(id);
|
|||
|
|
if (!cattle) {
|
|||
|
|
return res.status(404).json({
|
|||
|
|
success: false,
|
|||
|
|
message: '牛只档案不存在'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
await cattle.destroy();
|
|||
|
|
|
|||
|
|
res.json({
|
|||
|
|
success: true,
|
|||
|
|
message: '删除牛只档案成功'
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('删除牛只档案失败:', error);
|
|||
|
|
res.status(500).json({
|
|||
|
|
success: false,
|
|||
|
|
message: '删除牛只档案失败',
|
|||
|
|
error: error.message
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 批量删除牛只档案
|
|||
|
|
*/
|
|||
|
|
async batchDeleteCattleArchives(req, res) {
|
|||
|
|
try {
|
|||
|
|
const { ids } = req.body;
|
|||
|
|
|
|||
|
|
if (!ids || !Array.isArray(ids) || ids.length === 0) {
|
|||
|
|
return res.status(400).json({
|
|||
|
|
success: false,
|
|||
|
|
message: '请选择要删除的牛只档案'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const deletedCount = await IotCattle.destroy({
|
|||
|
|
where: {
|
|||
|
|
id: {
|
|||
|
|
[Op.in]: ids
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
res.json({
|
|||
|
|
success: true,
|
|||
|
|
data: { deletedCount },
|
|||
|
|
message: `成功删除 ${deletedCount} 个牛只档案`
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('批量删除牛只档案失败:', error);
|
|||
|
|
res.status(500).json({
|
|||
|
|
success: false,
|
|||
|
|
message: '批量删除牛只档案失败',
|
|||
|
|
error: error.message
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取农场列表(用于下拉选择)
|
|||
|
|
*/
|
|||
|
|
async getFarms(req, res) {
|
|||
|
|
try {
|
|||
|
|
const farms = await Farm.findAll({
|
|||
|
|
attributes: ['id', 'name', 'location'],
|
|||
|
|
order: [['name', 'ASC']]
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
res.json({
|
|||
|
|
success: true,
|
|||
|
|
data: farms,
|
|||
|
|
message: '获取农场列表成功'
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('获取农场列表失败:', error);
|
|||
|
|
res.status(500).json({
|
|||
|
|
success: false,
|
|||
|
|
message: '获取农场列表失败',
|
|||
|
|
error: error.message
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取栏舍列表(用于下拉选择)
|
|||
|
|
*/
|
|||
|
|
async getPens(req, res) {
|
|||
|
|
try {
|
|||
|
|
const { farmId } = req.query;
|
|||
|
|
const where = {};
|
|||
|
|
|
|||
|
|
if (farmId) {
|
|||
|
|
where.farmId = farmId;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const pens = await CattlePen.findAll({
|
|||
|
|
where,
|
|||
|
|
attributes: ['id', 'name', 'code', 'farmId'],
|
|||
|
|
order: [['name', 'ASC']]
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
res.json({
|
|||
|
|
success: true,
|
|||
|
|
data: pens,
|
|||
|
|
message: '获取栏舍列表成功'
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('获取栏舍列表失败:', error);
|
|||
|
|
res.status(500).json({
|
|||
|
|
success: false,
|
|||
|
|
message: '获取栏舍列表失败',
|
|||
|
|
error: error.message
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取批次列表(用于下拉选择)
|
|||
|
|
*/
|
|||
|
|
async getBatches(req, res) {
|
|||
|
|
try {
|
|||
|
|
const { farmId } = req.query;
|
|||
|
|
const where = {};
|
|||
|
|
|
|||
|
|
if (farmId) {
|
|||
|
|
where.farmId = farmId;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const batches = await CattleBatch.findAll({
|
|||
|
|
where,
|
|||
|
|
attributes: ['id', 'name', 'code', 'farmId'],
|
|||
|
|
order: [['name', 'ASC']]
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
res.json({
|
|||
|
|
success: true,
|
|||
|
|
data: batches,
|
|||
|
|
message: '获取批次列表成功'
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('获取批次列表失败:', error);
|
|||
|
|
res.status(500).json({
|
|||
|
|
success: false,
|
|||
|
|
message: '获取批次列表失败',
|
|||
|
|
error: error.message
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 导入牛只档案数据
|
|||
|
|
*/
|
|||
|
|
async importCattleArchives(req, res) {
|
|||
|
|
try {
|
|||
|
|
console.log('=== 开始导入牛只档案数据 ===');
|
|||
|
|
|
|||
|
|
if (!req.file) {
|
|||
|
|
return res.status(400).json({
|
|||
|
|
success: false,
|
|||
|
|
message: '请选择要导入的文件'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const file = req.file;
|
|||
|
|
console.log('上传文件信息:', {
|
|||
|
|
originalname: file.originalname,
|
|||
|
|
mimetype: file.mimetype,
|
|||
|
|
size: file.size
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 检查文件类型
|
|||
|
|
const allowedTypes = [
|
|||
|
|
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|||
|
|
'application/vnd.ms-excel'
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
if (!allowedTypes.includes(file.mimetype)) {
|
|||
|
|
return res.status(400).json({
|
|||
|
|
success: false,
|
|||
|
|
message: '请上传Excel文件(.xlsx或.xls格式)'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 这里需要添加Excel解析逻辑
|
|||
|
|
// 由于没有安装xlsx库,先返回模拟数据
|
|||
|
|
const importedCount = 0;
|
|||
|
|
const errors = [];
|
|||
|
|
|
|||
|
|
// TODO: 实现Excel文件解析和数据库插入逻辑
|
|||
|
|
// 1. 使用xlsx库解析Excel文件
|
|||
|
|
// 2. 验证数据格式
|
|||
|
|
// 3. 批量插入到数据库
|
|||
|
|
// 4. 返回导入结果
|
|||
|
|
|
|||
|
|
res.json({
|
|||
|
|
success: true,
|
|||
|
|
message: '导入功能开发中',
|
|||
|
|
importedCount,
|
|||
|
|
errors
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('导入牛只档案数据失败:', error);
|
|||
|
|
res.status(500).json({
|
|||
|
|
success: false,
|
|||
|
|
message: '导入失败',
|
|||
|
|
error: error.message
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 下载导入模板
|
|||
|
|
*/
|
|||
|
|
async downloadImportTemplate(req, res) {
|
|||
|
|
try {
|
|||
|
|
console.log('=== 下载牛只档案导入模板 ===');
|
|||
|
|
|
|||
|
|
// 创建模板数据 - 按照截图格式
|
|||
|
|
const templateData = [
|
|||
|
|
{
|
|||
|
|
'耳标编号': '2105523006',
|
|||
|
|
'性别': '1为公牛2为母牛',
|
|||
|
|
'品系': '1:乳肉兼用',
|
|||
|
|
'品种': '1:西藏高山牦牛2:宁夏牛',
|
|||
|
|
'类别': '1:犊牛,2:育成母牛,3:架子牛,4:青年牛,5:基础母牛,6:育肥牛',
|
|||
|
|
'出生体重(kg)': '30',
|
|||
|
|
'出生日期': '格式必须为(2023-1-15)',
|
|||
|
|
'栏舍ID': '1',
|
|||
|
|
'入栏时间': '2023-01-20',
|
|||
|
|
'胎次': '0',
|
|||
|
|
'来源': '1',
|
|||
|
|
'来源天数': '5',
|
|||
|
|
'来源体重': '35.5',
|
|||
|
|
'当前体重': '450.0',
|
|||
|
|
'事件': '正常',
|
|||
|
|
'事件时间': '2023-01-20',
|
|||
|
|
'泌乳天数': '0',
|
|||
|
|
'精液编号': '',
|
|||
|
|
'是否佩戴': '1',
|
|||
|
|
'批次ID': '1'
|
|||
|
|
}
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
// 使用ExportUtils生成Excel文件
|
|||
|
|
const ExportUtils = require('../utils/exportUtils');
|
|||
|
|
const result = ExportUtils.exportToExcel(templateData, [
|
|||
|
|
{ title: '耳标编号', dataIndex: '耳标编号', key: 'earNumber' },
|
|||
|
|
{ title: '性别', dataIndex: '性别', key: 'sex' },
|
|||
|
|
{ title: '品系', dataIndex: '品系', key: 'strain' },
|
|||
|
|
{ title: '品种', dataIndex: '品种', key: 'varieties' },
|
|||
|
|
{ title: '类别', dataIndex: '类别', key: 'cate' },
|
|||
|
|
{ title: '出生体重(kg)', dataIndex: '出生体重(kg)', key: 'birthWeight' },
|
|||
|
|
{ title: '出生日期', dataIndex: '出生日期', key: 'birthday' },
|
|||
|
|
{ title: '栏舍ID', dataIndex: '栏舍ID', key: 'penId' },
|
|||
|
|
{ title: '入栏时间', dataIndex: '入栏时间', key: 'intoTime' },
|
|||
|
|
{ title: '胎次', dataIndex: '胎次', key: 'parity' },
|
|||
|
|
{ title: '来源', dataIndex: '来源', key: 'source' },
|
|||
|
|
{ title: '来源天数', dataIndex: '来源天数', key: 'sourceDay' },
|
|||
|
|
{ title: '来源体重', dataIndex: '来源体重', key: 'sourceWeight' },
|
|||
|
|
{ title: '当前体重', dataIndex: '当前体重', key: 'weight' },
|
|||
|
|
{ title: '事件', dataIndex: '事件', key: 'event' },
|
|||
|
|
{ title: '事件时间', dataIndex: '事件时间', key: 'eventTime' },
|
|||
|
|
{ title: '泌乳天数', dataIndex: '泌乳天数', key: 'lactationDay' },
|
|||
|
|
{ title: '精液编号', dataIndex: '精液编号', key: 'semenNum' },
|
|||
|
|
{ title: '是否佩戴', dataIndex: '是否佩戴', key: 'isWear' },
|
|||
|
|
{ title: '批次ID', dataIndex: '批次ID', key: 'batchId' }
|
|||
|
|
], '牛只档案导入模板');
|
|||
|
|
|
|||
|
|
if (result.success) {
|
|||
|
|
// 使用Express的res.download方法
|
|||
|
|
res.download(result.filePath, '牛只档案导入模板.xlsx', (err) => {
|
|||
|
|
if (err) {
|
|||
|
|
console.error('文件下载失败:', err);
|
|||
|
|
if (!res.headersSent) {
|
|||
|
|
res.status(500).json({
|
|||
|
|
success: false,
|
|||
|
|
message: '文件下载失败',
|
|||
|
|
error: err.message
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
// 下载成功后删除临时文件
|
|||
|
|
const fs = require('fs');
|
|||
|
|
fs.unlink(result.filePath, (err) => {
|
|||
|
|
if (err) console.error('删除临时文件失败:', err);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
} else {
|
|||
|
|
res.status(500).json({
|
|||
|
|
success: false,
|
|||
|
|
message: '生成模板文件失败',
|
|||
|
|
error: result.message
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('下载导入模板失败:', error);
|
|||
|
|
res.status(500).json({
|
|||
|
|
success: false,
|
|||
|
|
message: '下载模板失败',
|
|||
|
|
error: error.message
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
module.exports = new IotCattleController();
|