package service import ( "errors" "fmt" "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 ( ErrPigBatchNotFound = errors.New("指定的猪批次不存在") ErrPigBatchActive = errors.New("活跃的猪批次不能被删除") ErrPigBatchNotActive = errors.New("猪批次不处于活跃状态,无法修改关联猪栏") ErrPenNotFound = errors.New("指定的猪栏不存在") ErrPenOccupiedByOtherBatch = errors.New("猪栏已被其他批次占用") ErrPenStatusInvalidForAllocation = errors.New("猪栏状态不允许分配") ErrPenNotAssociatedWithBatch = errors.New("猪栏未与该批次关联") ) // 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) // UpdatePigBatchPens 更新猪批次关联的猪栏 UpdatePigBatchPens(batchID uint, desiredPenIDs []uint) error } type pigBatchService struct { logger *logs.Logger pigBatchRepo repository.PigBatchRepository // 猪批次仓库 pigFarmRepo repository.PigFarmRepository // 猪场资产仓库 (包含猪栏操作) uow repository.UnitOfWork // 工作单元,用于事务管理 } // NewPigBatchService 创建一个新的 PigBatchService 实例 func NewPigBatchService(pigBatchRepo repository.PigBatchRepository, pigFarmRepo repository.PigFarmRepository, uow repository.UnitOfWork, logger *logs.Logger) PigBatchService { return &pigBatchService{ logger: logger, pigBatchRepo: pigBatchRepo, pigFarmRepo: pigFarmRepo, uow: uow, } } // 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, } createdBatch, err := s.pigBatchRepo.CreatePigBatch(batch) 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) { batch, err := s.pigBatchRepo.GetPigBatchByID(id) 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) { existingBatch, err := s.pigBatchRepo.GetPigBatchByID(id) 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 } updatedBatch, rowsAffected, err := s.pigBatchRepo.UpdatePigBatch(existingBatch) if err != nil { s.logger.Errorf("更新猪批次失败,ID: %d, 错误: %v", id, err) return nil, err } // 如果没有行受影响,则认为猪批次不存在 if rowsAffected == 0 { return nil, ErrPigBatchNotFound } return s.toPigBatchResponseDTO(updatedBatch), nil } // DeletePigBatch 处理删除猪批次的业务逻辑 func (s *pigBatchService) DeletePigBatch(id uint) error { // 1. 获取猪批次信息 batch, err := s.pigBatchRepo.GetPigBatchByID(id) 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. 执行删除操作 rowsAffected, err := s.pigBatchRepo.DeletePigBatch(id) if err != nil { s.logger.Errorf("删除猪批次失败,ID: %d, 错误: %v", id, err) return err } // 如果没有行受影响,则认为猪批次不存在 if rowsAffected == 0 { return ErrPigBatchNotFound } return nil } // ListPigBatches 处理批量查询猪批次的业务逻辑 func (s *pigBatchService) ListPigBatches(isActive *bool) ([]*dto.PigBatchResponseDTO, error) { batches, err := s.pigBatchRepo.ListPigBatches(isActive) 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 } // 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 // 只有当猪栏当前状态是“占用”时,才将其状态改回“空闲” 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) } // 验证:猪栏必须是“空闲”状态且未被任何批次占用,才能被分配 if actualPen.Status != models.PenStatusEmpty { return fmt.Errorf("猪栏 %s 状态为 %s,无法分配: %w", actualPen.PenNumber, actualPen.Status, ErrPenStatusInvalidForAllocation) } if actualPen.PigBatchID != nil { return fmt.Errorf("猪栏 %s 已被其他批次 %d 占用,无法分配: %w", actualPen.PenNumber, *actualPen.PigBatchID, ErrPenOccupiedByOtherBatch) } updates := map[string]interface{}{ "pig_batch_id": &batchID, // 将 PigBatchID 设为当前批次ID的指针 "status": models.PenStatusOccupied, // 分配后,状态变为“占用” } 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 }) }