Files
pig-farm-controller/internal/app/service/pig_batch_service.go
2025-10-04 01:31:35 +08:00

303 lines
10 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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("猪批次不处于活跃状态,无法修改关联猪栏")
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
})
}