2025-10-03 23:42:14 +08:00
|
|
|
|
package service
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"errors"
|
2025-10-04 00:47:27 +08:00
|
|
|
|
"fmt"
|
2025-10-03 23:42:14 +08:00
|
|
|
|
|
|
|
|
|
|
"git.huangwc.com/pig/pig-farm-controller/internal/app/dto"
|
|
|
|
|
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
|
|
|
|
|
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
|
|
|
|
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
|
|
|
|
|
|
|
|
|
|
|
|
"gorm.io/gorm"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
var (
|
2025-10-04 00:47:27 +08:00
|
|
|
|
ErrPigBatchNotFound = errors.New("指定的猪批次不存在")
|
|
|
|
|
|
ErrPigBatchActive = errors.New("活跃的猪批次不能被删除")
|
|
|
|
|
|
ErrPigBatchNotActive = errors.New("猪批次不处于活跃状态,无法修改关联猪栏")
|
2025-10-04 01:31:35 +08:00
|
|
|
|
ErrPenOccupiedByOtherBatch = errors.New("猪栏已被其他批次使用")
|
2025-10-04 00:47:27 +08:00
|
|
|
|
ErrPenStatusInvalidForAllocation = errors.New("猪栏状态不允许分配")
|
|
|
|
|
|
ErrPenNotAssociatedWithBatch = errors.New("猪栏未与该批次关联")
|
2025-10-03 23:42:14 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// PigBatchService 提供了猪批次管理的业务逻辑
|
|
|
|
|
|
type PigBatchService interface {
|
|
|
|
|
|
CreatePigBatch(dto *dto.PigBatchCreateDTO) (*dto.PigBatchResponseDTO, error)
|
|
|
|
|
|
GetPigBatch(id uint) (*dto.PigBatchResponseDTO, error)
|
|
|
|
|
|
UpdatePigBatch(id uint, dto *dto.PigBatchUpdateDTO) (*dto.PigBatchResponseDTO, error)
|
|
|
|
|
|
DeletePigBatch(id uint) error
|
|
|
|
|
|
ListPigBatches(isActive *bool) ([]*dto.PigBatchResponseDTO, error)
|
2025-10-04 00:47:27 +08:00
|
|
|
|
// UpdatePigBatchPens 更新猪批次关联的猪栏
|
|
|
|
|
|
UpdatePigBatchPens(batchID uint, desiredPenIDs []uint) error
|
2025-10-03 23:42:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
type pigBatchService struct {
|
2025-10-04 00:47:27 +08:00
|
|
|
|
logger *logs.Logger
|
|
|
|
|
|
pigBatchRepo repository.PigBatchRepository // 猪批次仓库
|
|
|
|
|
|
pigFarmRepo repository.PigFarmRepository // 猪场资产仓库 (包含猪栏操作)
|
|
|
|
|
|
uow repository.UnitOfWork // 工作单元,用于事务管理
|
2025-10-03 23:42:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// NewPigBatchService 创建一个新的 PigBatchService 实例
|
2025-10-04 00:47:27 +08:00
|
|
|
|
func NewPigBatchService(pigBatchRepo repository.PigBatchRepository, pigFarmRepo repository.PigFarmRepository, uow repository.UnitOfWork, logger *logs.Logger) PigBatchService {
|
2025-10-03 23:42:14 +08:00
|
|
|
|
return &pigBatchService{
|
2025-10-04 00:47:27 +08:00
|
|
|
|
logger: logger,
|
|
|
|
|
|
pigBatchRepo: pigBatchRepo,
|
|
|
|
|
|
pigFarmRepo: pigFarmRepo,
|
|
|
|
|
|
uow: uow,
|
2025-10-03 23:42:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// toPigBatchResponseDTO 将 models.PigBatch 转换为 dto.PigBatchResponseDTO
|
|
|
|
|
|
func (s *pigBatchService) toPigBatchResponseDTO(batch *models.PigBatch) *dto.PigBatchResponseDTO {
|
|
|
|
|
|
if batch == nil {
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
return &dto.PigBatchResponseDTO{
|
|
|
|
|
|
ID: batch.ID,
|
|
|
|
|
|
BatchNumber: batch.BatchNumber,
|
|
|
|
|
|
OriginType: batch.OriginType,
|
|
|
|
|
|
StartDate: batch.StartDate,
|
|
|
|
|
|
EndDate: batch.EndDate,
|
|
|
|
|
|
InitialCount: batch.InitialCount,
|
|
|
|
|
|
Status: batch.Status,
|
|
|
|
|
|
IsActive: batch.IsActive(), // 使用模型自带的 IsActive 方法
|
|
|
|
|
|
CreateTime: batch.CreatedAt,
|
|
|
|
|
|
UpdateTime: batch.UpdatedAt,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// CreatePigBatch 处理创建猪批次的业务逻辑
|
|
|
|
|
|
func (s *pigBatchService) CreatePigBatch(dto *dto.PigBatchCreateDTO) (*dto.PigBatchResponseDTO, error) {
|
|
|
|
|
|
batch := &models.PigBatch{
|
|
|
|
|
|
BatchNumber: dto.BatchNumber,
|
|
|
|
|
|
OriginType: dto.OriginType,
|
|
|
|
|
|
StartDate: dto.StartDate,
|
|
|
|
|
|
InitialCount: dto.InitialCount,
|
|
|
|
|
|
Status: dto.Status,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-04 00:47:27 +08:00
|
|
|
|
createdBatch, err := s.pigBatchRepo.CreatePigBatch(batch)
|
2025-10-03 23:42:14 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Errorf("创建猪批次失败: %v", err)
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return s.toPigBatchResponseDTO(createdBatch), nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetPigBatch 处理获取单个猪批次的业务逻辑
|
|
|
|
|
|
func (s *pigBatchService) GetPigBatch(id uint) (*dto.PigBatchResponseDTO, error) {
|
2025-10-04 00:47:27 +08:00
|
|
|
|
batch, err := s.pigBatchRepo.GetPigBatchByID(id)
|
2025-10-03 23:42:14 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
|
|
|
|
return nil, ErrPigBatchNotFound
|
|
|
|
|
|
}
|
|
|
|
|
|
s.logger.Errorf("获取猪批次失败,ID: %d, 错误: %v", id, err)
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return s.toPigBatchResponseDTO(batch), nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// UpdatePigBatch 处理更新猪批次的业务逻辑
|
|
|
|
|
|
func (s *pigBatchService) UpdatePigBatch(id uint, dto *dto.PigBatchUpdateDTO) (*dto.PigBatchResponseDTO, error) {
|
2025-10-04 00:47:27 +08:00
|
|
|
|
existingBatch, err := s.pigBatchRepo.GetPigBatchByID(id)
|
2025-10-03 23:42:14 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
|
|
|
|
return nil, ErrPigBatchNotFound
|
|
|
|
|
|
}
|
|
|
|
|
|
s.logger.Errorf("更新猪批次失败,获取原批次信息错误,ID: %d, 错误: %v", id, err)
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 根据 DTO 中的非空字段更新模型
|
|
|
|
|
|
if dto.BatchNumber != nil {
|
|
|
|
|
|
existingBatch.BatchNumber = *dto.BatchNumber
|
|
|
|
|
|
}
|
|
|
|
|
|
if dto.OriginType != nil {
|
|
|
|
|
|
existingBatch.OriginType = *dto.OriginType
|
|
|
|
|
|
}
|
|
|
|
|
|
if dto.StartDate != nil {
|
|
|
|
|
|
existingBatch.StartDate = *dto.StartDate
|
|
|
|
|
|
}
|
|
|
|
|
|
if dto.EndDate != nil {
|
|
|
|
|
|
existingBatch.EndDate = *dto.EndDate
|
|
|
|
|
|
}
|
|
|
|
|
|
if dto.InitialCount != nil {
|
|
|
|
|
|
existingBatch.InitialCount = *dto.InitialCount
|
|
|
|
|
|
}
|
|
|
|
|
|
if dto.Status != nil {
|
|
|
|
|
|
existingBatch.Status = *dto.Status
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-04 00:58:29 +08:00
|
|
|
|
updatedBatch, rowsAffected, err := s.pigBatchRepo.UpdatePigBatch(existingBatch)
|
2025-10-03 23:42:14 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Errorf("更新猪批次失败,ID: %d, 错误: %v", id, err)
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
2025-10-04 00:58:29 +08:00
|
|
|
|
// 如果没有行受影响,则认为猪批次不存在
|
|
|
|
|
|
if rowsAffected == 0 {
|
|
|
|
|
|
return nil, ErrPigBatchNotFound
|
|
|
|
|
|
}
|
2025-10-03 23:42:14 +08:00
|
|
|
|
|
|
|
|
|
|
return s.toPigBatchResponseDTO(updatedBatch), nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// DeletePigBatch 处理删除猪批次的业务逻辑
|
|
|
|
|
|
func (s *pigBatchService) DeletePigBatch(id uint) error {
|
|
|
|
|
|
// 1. 获取猪批次信息
|
2025-10-04 00:47:27 +08:00
|
|
|
|
batch, err := s.pigBatchRepo.GetPigBatchByID(id)
|
2025-10-03 23:42:14 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
|
|
|
|
return ErrPigBatchNotFound
|
|
|
|
|
|
}
|
|
|
|
|
|
s.logger.Errorf("删除猪批次失败,获取批次信息错误,ID: %d, 错误: %v", id, err)
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 检查猪批次是否活跃
|
|
|
|
|
|
if batch.IsActive() {
|
|
|
|
|
|
return ErrPigBatchActive // 如果活跃,则不允许删除
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 执行删除操作
|
2025-10-04 00:58:29 +08:00
|
|
|
|
rowsAffected, err := s.pigBatchRepo.DeletePigBatch(id)
|
2025-10-03 23:42:14 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Errorf("删除猪批次失败,ID: %d, 错误: %v", id, err)
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
2025-10-04 00:58:29 +08:00
|
|
|
|
// 如果没有行受影响,则认为猪批次不存在
|
|
|
|
|
|
if rowsAffected == 0 {
|
|
|
|
|
|
return ErrPigBatchNotFound
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-03 23:42:14 +08:00
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ListPigBatches 处理批量查询猪批次的业务逻辑
|
|
|
|
|
|
func (s *pigBatchService) ListPigBatches(isActive *bool) ([]*dto.PigBatchResponseDTO, error) {
|
2025-10-04 00:47:27 +08:00
|
|
|
|
batches, err := s.pigBatchRepo.ListPigBatches(isActive)
|
2025-10-03 23:42:14 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Errorf("批量查询猪批次失败,错误: %v", err)
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var responseDTOs []*dto.PigBatchResponseDTO
|
|
|
|
|
|
for _, batch := range batches {
|
|
|
|
|
|
responseDTOs = append(responseDTOs, s.toPigBatchResponseDTO(batch))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return responseDTOs, nil
|
|
|
|
|
|
}
|
2025-10-04 00:47:27 +08:00
|
|
|
|
|
|
|
|
|
|
// UpdatePigBatchPens 更新猪批次关联的猪栏
|
|
|
|
|
|
func (s *pigBatchService) UpdatePigBatchPens(batchID uint, desiredPenIDs []uint) error {
|
|
|
|
|
|
// 使用工作单元执行事务
|
|
|
|
|
|
return s.uow.ExecuteInTransaction(func(tx *gorm.DB) error {
|
|
|
|
|
|
// 1. 验证猪批次
|
|
|
|
|
|
pigBatch, err := s.pigFarmRepo.GetPigBatchByIDTx(tx, batchID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
|
|
|
|
return ErrPigBatchNotFound
|
|
|
|
|
|
}
|
|
|
|
|
|
s.logger.Errorf("更新猪批次猪栏失败: 获取猪批次信息错误,ID: %d, 错误: %v", batchID, err)
|
|
|
|
|
|
return fmt.Errorf("获取猪批次信息失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if !pigBatch.IsActive() {
|
|
|
|
|
|
return ErrPigBatchNotActive
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 获取当前关联的猪栏
|
|
|
|
|
|
currentPens, err := s.pigFarmRepo.GetPensByBatchID(tx, batchID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Errorf("更新猪批次猪栏失败: 获取当前关联猪栏错误,批次ID: %d, 错误: %v", batchID, err)
|
|
|
|
|
|
return fmt.Errorf("获取当前关联猪栏失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
currentPenMap := make(map[uint]models.Pen)
|
|
|
|
|
|
currentPenIDsSet := make(map[uint]struct{})
|
|
|
|
|
|
for _, pen := range currentPens {
|
|
|
|
|
|
currentPenMap[pen.ID] = pen
|
|
|
|
|
|
currentPenIDsSet[pen.ID] = struct{}{} // 用于快速查找
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 构建期望猪栏集合
|
|
|
|
|
|
desiredPenIDsSet := make(map[uint]struct{})
|
|
|
|
|
|
for _, penID := range desiredPenIDs {
|
|
|
|
|
|
desiredPenIDsSet[penID] = struct{}{} // 用于快速查找
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 4. 计算需要添加和移除的猪栏
|
|
|
|
|
|
var pensToRemove []uint
|
|
|
|
|
|
for penID := range currentPenIDsSet {
|
|
|
|
|
|
if _, found := desiredPenIDsSet[penID]; !found {
|
|
|
|
|
|
pensToRemove = append(pensToRemove, penID)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var pensToAdd []uint
|
|
|
|
|
|
for _, penID := range desiredPenIDs {
|
|
|
|
|
|
if _, found := currentPenIDsSet[penID]; !found {
|
|
|
|
|
|
pensToAdd = append(pensToAdd, penID)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 5. 处理移除猪栏
|
|
|
|
|
|
for _, penID := range pensToRemove {
|
|
|
|
|
|
currentPen := currentPenMap[penID]
|
|
|
|
|
|
// 验证:确保猪栏确实与当前批次关联
|
|
|
|
|
|
if currentPen.PigBatchID == nil || *currentPen.PigBatchID != batchID {
|
|
|
|
|
|
s.logger.Warnf("尝试移除未与批次 %d 关联的猪栏 %d", batchID, penID)
|
|
|
|
|
|
return fmt.Errorf("猪栏 %d 未与该批次关联,无法移除", penID)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
updates := make(map[string]interface{})
|
|
|
|
|
|
updates["pig_batch_id"] = nil // 总是将 PigBatchID 设为 nil
|
|
|
|
|
|
|
2025-10-04 01:31:35 +08:00
|
|
|
|
// 只有当猪栏当前状态是“使用中”时,才将其状态改回“空闲”
|
2025-10-04 00:47:27 +08:00
|
|
|
|
if currentPen.Status == models.PenStatusOccupied {
|
|
|
|
|
|
updates["status"] = models.PenStatusEmpty
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if err := s.pigFarmRepo.UpdatePenFields(tx, penID, updates); err != nil {
|
|
|
|
|
|
s.logger.Errorf("更新猪批次猪栏失败: 移除猪栏 %d 失败: %v", penID, err)
|
|
|
|
|
|
return fmt.Errorf("移除猪栏 %d 失败: %w", penID, err)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 6. 处理添加猪栏
|
|
|
|
|
|
for _, penID := range pensToAdd {
|
|
|
|
|
|
actualPen, err := s.pigFarmRepo.GetPenByIDTx(tx, penID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
|
|
|
|
return fmt.Errorf("猪栏 %d 不存在: %w", penID, ErrPenNotFound)
|
|
|
|
|
|
}
|
|
|
|
|
|
s.logger.Errorf("更新猪批次猪栏失败: 获取猪栏 %d 信息错误: %v", penID, err)
|
|
|
|
|
|
return fmt.Errorf("获取猪栏 %d 信息失败: %w", penID, err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-04 01:31:35 +08:00
|
|
|
|
// 验证:猪栏必须是“空闲”状态且未被任何批次使用,才能被分配
|
2025-10-04 00:47:27 +08:00
|
|
|
|
if actualPen.Status != models.PenStatusEmpty {
|
|
|
|
|
|
return fmt.Errorf("猪栏 %s 状态为 %s,无法分配: %w", actualPen.PenNumber, actualPen.Status, ErrPenStatusInvalidForAllocation)
|
|
|
|
|
|
}
|
|
|
|
|
|
if actualPen.PigBatchID != nil {
|
2025-10-04 01:31:35 +08:00
|
|
|
|
return fmt.Errorf("猪栏 %s 已被其他批次 %d 使用,无法分配: %w", actualPen.PenNumber, *actualPen.PigBatchID, ErrPenOccupiedByOtherBatch)
|
2025-10-04 00:47:27 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
updates := map[string]interface{}{
|
|
|
|
|
|
"pig_batch_id": &batchID, // 将 PigBatchID 设为当前批次ID的指针
|
2025-10-04 01:31:35 +08:00
|
|
|
|
"status": models.PenStatusOccupied, // 分配后,状态变为“使用中”
|
2025-10-04 00:47:27 +08:00
|
|
|
|
}
|
|
|
|
|
|
if err := s.pigFarmRepo.UpdatePenFields(tx, penID, updates); err != nil {
|
|
|
|
|
|
s.logger.Errorf("更新猪批次猪栏失败: 添加猪栏 %d 失败: %v", penID, err)
|
|
|
|
|
|
return fmt.Errorf("添加猪栏 %d 失败: %w", penID, err)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|