323 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			323 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package pig
 | 
						||
 | 
						||
import (
 | 
						||
	"errors"
 | 
						||
	"fmt"
 | 
						||
	"time"
 | 
						||
 | 
						||
	"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
 | 
						||
	"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
 | 
						||
	"github.com/google/uuid"
 | 
						||
	"gorm.io/gorm"
 | 
						||
)
 | 
						||
 | 
						||
// --- 领域服务实现 ---
 | 
						||
 | 
						||
// pigBatchService 是 PigBatchService 接口的具体实现。
 | 
						||
// 它作为猪群领域的主服务,封装了所有业务逻辑。
 | 
						||
type pigBatchService struct {
 | 
						||
	pigBatchRepo repository.PigBatchRepository // 猪批次仓库
 | 
						||
	uow          repository.UnitOfWork         // 工作单元,用于管理事务
 | 
						||
	transferSvc  PenTransferManager            // 调栏子服务
 | 
						||
}
 | 
						||
 | 
						||
// NewPigBatchService 是 pigBatchService 的构造函数。
 | 
						||
// 它通过依赖注入的方式,创建并返回一个 PigBatchService 接口的实例。
 | 
						||
func NewPigBatchService(
 | 
						||
	pigBatchRepo repository.PigBatchRepository,
 | 
						||
	uow repository.UnitOfWork,
 | 
						||
	transferSvc PenTransferManager,
 | 
						||
) PigBatchService {
 | 
						||
	return &pigBatchService{
 | 
						||
		pigBatchRepo: pigBatchRepo,
 | 
						||
		uow:          uow,
 | 
						||
		transferSvc:  transferSvc,
 | 
						||
	}
 | 
						||
}
 | 
						||
 | 
						||
// CreatePigBatch 实现了创建猪批次的逻辑。
 | 
						||
func (s *pigBatchService) CreatePigBatch(batch *models.PigBatch) (*models.PigBatch, error) {
 | 
						||
	// 业务规则可以在这里添加,例如检查批次号是否唯一等
 | 
						||
	return s.pigBatchRepo.CreatePigBatch(batch)
 | 
						||
}
 | 
						||
 | 
						||
// GetPigBatch 实现了获取单个猪批次的逻辑。
 | 
						||
func (s *pigBatchService) GetPigBatch(id uint) (*models.PigBatch, error) {
 | 
						||
	batch, err := s.pigBatchRepo.GetPigBatchByID(id)
 | 
						||
	if err != nil {
 | 
						||
		if errors.Is(err, gorm.ErrRecordNotFound) {
 | 
						||
			return nil, ErrPigBatchNotFound
 | 
						||
		}
 | 
						||
		return nil, err
 | 
						||
	}
 | 
						||
	return batch, nil
 | 
						||
}
 | 
						||
 | 
						||
// UpdatePigBatch 实现了更新猪批次的逻辑。
 | 
						||
func (s *pigBatchService) UpdatePigBatch(batch *models.PigBatch) (*models.PigBatch, error) {
 | 
						||
	// 可以在这里添加更新前的业务校验
 | 
						||
	updatedBatch, rowsAffected, err := s.pigBatchRepo.UpdatePigBatch(batch)
 | 
						||
	if err != nil {
 | 
						||
		return nil, err
 | 
						||
	}
 | 
						||
	if rowsAffected == 0 {
 | 
						||
		return nil, ErrPigBatchNotFound // 如果没有行被更新,可能意味着记录不存在
 | 
						||
	}
 | 
						||
	return updatedBatch, nil
 | 
						||
}
 | 
						||
 | 
						||
// DeletePigBatch 实现了删除猪批次的逻辑,并包含业务规则校验。
 | 
						||
func (s *pigBatchService) DeletePigBatch(id uint) error {
 | 
						||
	// 1. 获取猪批次信息
 | 
						||
	batch, err := s.GetPigBatch(id) // 复用 GetPigBatch 方法
 | 
						||
	if err != nil {
 | 
						||
		return err // GetPigBatch 已经处理了 ErrRecordNotFound 的情况
 | 
						||
	}
 | 
						||
 | 
						||
	// 2. 核心业务规则:检查猪批次是否为活跃状态
 | 
						||
	if batch.IsActive() {
 | 
						||
		return ErrPigBatchActive // 如果活跃,则不允许删除
 | 
						||
	}
 | 
						||
 | 
						||
	// 3. 执行删除
 | 
						||
	rowsAffected, err := s.pigBatchRepo.DeletePigBatch(id)
 | 
						||
	if err != nil {
 | 
						||
		return err
 | 
						||
	}
 | 
						||
	if rowsAffected == 0 {
 | 
						||
		return ErrPigBatchNotFound
 | 
						||
	}
 | 
						||
 | 
						||
	return nil
 | 
						||
}
 | 
						||
 | 
						||
// ListPigBatches 实现了批量查询猪批次的逻辑。
 | 
						||
func (s *pigBatchService) ListPigBatches(isActive *bool) ([]*models.PigBatch, error) {
 | 
						||
	return s.pigBatchRepo.ListPigBatches(isActive)
 | 
						||
}
 | 
						||
 | 
						||
// UpdatePigBatchPens 实现了在事务中更新猪批次关联猪栏的复杂逻辑。
 | 
						||
// 它通过调用底层的 PenTransferManager 来执行数据库操作,从而保持了职责的清晰。
 | 
						||
func (s *pigBatchService) UpdatePigBatchPens(batchID uint, desiredPenIDs []uint) error {
 | 
						||
	// 使用工作单元来确保操作的原子性
 | 
						||
	return s.uow.ExecuteInTransaction(func(tx *gorm.DB) error {
 | 
						||
		// 1. 验证猪批次是否存在且活跃
 | 
						||
		// 注意: 此处依赖一个假设存在的 pigBatchRepo.GetPigBatchByIDTx 方法
 | 
						||
		pigBatch, err := s.pigBatchRepo.GetPigBatchByIDTx(tx, batchID)
 | 
						||
		if err != nil {
 | 
						||
			if errors.Is(err, gorm.ErrRecordNotFound) {
 | 
						||
				return ErrPigBatchNotFound
 | 
						||
			}
 | 
						||
			return fmt.Errorf("获取猪批次信息失败: %w", err)
 | 
						||
		}
 | 
						||
 | 
						||
		if !pigBatch.IsActive() {
 | 
						||
			return ErrPigBatchNotActive
 | 
						||
		}
 | 
						||
 | 
						||
		// 2. 获取当前关联的猪栏 (通过子服务)
 | 
						||
		currentPens, err := s.transferSvc.GetPensByBatchID(tx, batchID)
 | 
						||
		if err != nil {
 | 
						||
			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. 构建期望猪栏ID集合
 | 
						||
		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]
 | 
						||
			updates := make(map[string]interface{})
 | 
						||
			updates["pig_batch_id"] = nil
 | 
						||
 | 
						||
			if currentPen.Status == models.PenStatusOccupied {
 | 
						||
				updates["status"] = models.PenStatusEmpty
 | 
						||
			}
 | 
						||
 | 
						||
			if err := s.transferSvc.UpdatePenFields(tx, penID, updates); err != nil {
 | 
						||
				return fmt.Errorf("移除猪栏 %d 失败: %w", penID, err)
 | 
						||
			}
 | 
						||
		}
 | 
						||
 | 
						||
		// 6. 处理添加猪栏的逻辑
 | 
						||
		for _, penID := range pensToAdd {
 | 
						||
			// 通过子服务获取猪栏信息
 | 
						||
			actualPen, err := s.transferSvc.GetPenByID(tx, penID)
 | 
						||
			if err != nil {
 | 
						||
				if errors.Is(err, gorm.ErrRecordNotFound) {
 | 
						||
					return fmt.Errorf("猪栏 %d 不存在: %w", penID, ErrPenNotFound)
 | 
						||
				}
 | 
						||
				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,
 | 
						||
				"status":       models.PenStatusOccupied,
 | 
						||
			}
 | 
						||
			if err := s.transferSvc.UpdatePenFields(tx, penID, updates); err != nil {
 | 
						||
				return fmt.Errorf("添加猪栏 %d 失败: %w", penID, err)
 | 
						||
			}
 | 
						||
		}
 | 
						||
 | 
						||
		return nil
 | 
						||
	})
 | 
						||
}
 | 
						||
 | 
						||
// --- 新增的调栏业务实现 ---
 | 
						||
 | 
						||
// executeTransferAndLog 是一个私有辅助方法,用于封装创建和记录迁移日志的通用逻辑。
 | 
						||
func (s *pigBatchService) executeTransferAndLog(tx *gorm.DB, fromBatchID, toBatchID, fromPenID, toPenID uint, quantity int, transferType string) error {
 | 
						||
	// 1. 生成关联ID
 | 
						||
	correlationID := uuid.New().String()
 | 
						||
 | 
						||
	// 2. 创建调出日志
 | 
						||
	logOut := &models.PigTransferLog{
 | 
						||
		TransferTime:  time.Now(),
 | 
						||
		PigBatchID:    fromBatchID,
 | 
						||
		PenID:         fromPenID,
 | 
						||
		Quantity:      -quantity, // 调出为负数
 | 
						||
		Type:          transferType,
 | 
						||
		CorrelationID: correlationID,
 | 
						||
	}
 | 
						||
 | 
						||
	// 3. 创建调入日志
 | 
						||
	logIn := &models.PigTransferLog{
 | 
						||
		TransferTime:  time.Now(),
 | 
						||
		PigBatchID:    toBatchID,
 | 
						||
		PenID:         toPenID,
 | 
						||
		Quantity:      quantity, // 调入为正数
 | 
						||
		Type:          transferType,
 | 
						||
		CorrelationID: correlationID,
 | 
						||
	}
 | 
						||
 | 
						||
	// 4. 调用子服务记录日志
 | 
						||
	if err := s.transferSvc.LogTransfer(tx, logOut); err != nil {
 | 
						||
		return fmt.Errorf("记录调出日志失败: %w", err)
 | 
						||
	}
 | 
						||
	if err := s.transferSvc.LogTransfer(tx, logIn); err != nil {
 | 
						||
		return fmt.Errorf("记录调入日志失败: %w", err)
 | 
						||
	}
 | 
						||
 | 
						||
	return nil
 | 
						||
}
 | 
						||
 | 
						||
// TransferPigsWithinBatch 实现了同一个猪群内部的调栏业务。
 | 
						||
func (s *pigBatchService) TransferPigsWithinBatch(batchID uint, fromPenID uint, toPenID uint, quantity uint) error {
 | 
						||
	if fromPenID == toPenID {
 | 
						||
		return errors.New("源猪栏和目标猪栏不能相同")
 | 
						||
	}
 | 
						||
	if quantity == 0 {
 | 
						||
		return errors.New("迁移数量不能为零")
 | 
						||
	}
 | 
						||
 | 
						||
	return s.uow.ExecuteInTransaction(func(tx *gorm.DB) error {
 | 
						||
		// 1. 核心业务规则校验
 | 
						||
		fromPen, err := s.transferSvc.GetPenByID(tx, fromPenID)
 | 
						||
		if err != nil {
 | 
						||
			return fmt.Errorf("获取源猪栏信息失败: %w", err)
 | 
						||
		}
 | 
						||
		toPen, err := s.transferSvc.GetPenByID(tx, toPenID)
 | 
						||
		if err != nil {
 | 
						||
			return fmt.Errorf("获取目标猪栏信息失败: %w", err)
 | 
						||
		}
 | 
						||
 | 
						||
		if fromPen.PigBatchID == nil || *fromPen.PigBatchID != batchID {
 | 
						||
			return fmt.Errorf("源猪栏 %d 不属于指定的猪群 %d", fromPenID, batchID)
 | 
						||
		}
 | 
						||
		if toPen.PigBatchID != nil && *toPen.PigBatchID != batchID {
 | 
						||
			return fmt.Errorf("目标猪栏 %d 已被其他猪群占用", toPenID)
 | 
						||
		}
 | 
						||
 | 
						||
		// 2. 调用通用辅助方法执行日志记录
 | 
						||
		err = s.executeTransferAndLog(tx, batchID, batchID, fromPenID, toPenID, int(quantity), "群内调栏")
 | 
						||
		if err != nil {
 | 
						||
			return err
 | 
						||
		}
 | 
						||
 | 
						||
		// 3. 群内调栏,猪群总数不变
 | 
						||
		return nil
 | 
						||
	})
 | 
						||
}
 | 
						||
 | 
						||
// TransferPigsAcrossBatches 实现了跨猪群的调栏业务。
 | 
						||
func (s *pigBatchService) TransferPigsAcrossBatches(sourceBatchID uint, destBatchID uint, fromPenID uint, toPenID uint, quantity uint) error {
 | 
						||
	if sourceBatchID == destBatchID {
 | 
						||
		return errors.New("源猪群和目标猪群不能相同")
 | 
						||
	}
 | 
						||
	if quantity == 0 {
 | 
						||
		return errors.New("迁移数量不能为零")
 | 
						||
	}
 | 
						||
 | 
						||
	return s.uow.ExecuteInTransaction(func(tx *gorm.DB) error {
 | 
						||
		// 1. 核心业务规则校验
 | 
						||
		sourceBatch, err := s.pigBatchRepo.GetPigBatchByID(sourceBatchID)
 | 
						||
		if err != nil {
 | 
						||
			return fmt.Errorf("获取源猪群信息失败: %w", err)
 | 
						||
		}
 | 
						||
		destBatch, err := s.pigBatchRepo.GetPigBatchByID(destBatchID)
 | 
						||
		if err != nil {
 | 
						||
			return fmt.Errorf("获取目标猪群信息失败: %w", err)
 | 
						||
		}
 | 
						||
 | 
						||
		fromPen, err := s.transferSvc.GetPenByID(tx, fromPenID)
 | 
						||
		if err != nil {
 | 
						||
			return fmt.Errorf("获取源猪栏信息失败: %w", err)
 | 
						||
		}
 | 
						||
		if fromPen.PigBatchID == nil || *fromPen.PigBatchID != sourceBatchID {
 | 
						||
			return fmt.Errorf("源猪栏 %d 不属于源猪群 %d", fromPenID, sourceBatchID)
 | 
						||
		}
 | 
						||
 | 
						||
		// 2. 调用通用辅助方法执行日志记录
 | 
						||
		err = s.executeTransferAndLog(tx, sourceBatchID, destBatchID, fromPenID, toPenID, int(quantity), "跨群调栏")
 | 
						||
		if err != nil {
 | 
						||
			return err
 | 
						||
		}
 | 
						||
 | 
						||
		// 3. 修改本聚合的数据(猪群总数)
 | 
						||
		sourceBatch.InitialCount -= int(quantity)
 | 
						||
		destBatch.InitialCount += int(quantity)
 | 
						||
 | 
						||
		if _, _, err := s.pigBatchRepo.UpdatePigBatch(sourceBatch); err != nil {
 | 
						||
			return fmt.Errorf("更新源猪群数量失败: %w", err)
 | 
						||
		}
 | 
						||
		if _, _, err := s.pigBatchRepo.UpdatePigBatch(destBatch); err != nil {
 | 
						||
			return fmt.Errorf("更新目标猪群数量失败: %w", err)
 | 
						||
		}
 | 
						||
 | 
						||
		return nil
 | 
						||
	})
 | 
						||
}
 |