diff --git a/internal/domain/pig/pig_batch.go b/internal/domain/pig/pig_batch.go deleted file mode 100644 index 7d14efd..0000000 --- a/internal/domain/pig/pig_batch.go +++ /dev/null @@ -1,111 +0,0 @@ -package pig - -import ( - "errors" - "time" - - "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" - "git.huangwc.com/pig/pig-farm-controller/internal/infra/repository" -) - -// --- 业务错误定义 --- - -var ( - // ErrPigBatchNotFound 表示当尝试访问一个不存在的猪批次时发生的错误。 - ErrPigBatchNotFound = errors.New("指定的猪批次不存在") - // ErrPigBatchActive 表示当尝试对一个活跃的猪批次执行不允许的操作(如删除)时发生的错误。 - ErrPigBatchActive = errors.New("活跃的猪批次不能被删除") - // ErrPigBatchNotActive 表示当猪批次不处于活跃状态,但执行了需要其活跃的操作时发生的错误。 - ErrPigBatchNotActive = errors.New("猪批次不处于活跃状态,无法修改关联猪栏") - // ErrPenOccupiedByOtherBatch 表示当尝试将一个已经被其他批次占用的猪栏分配给新批次时发生的错误。 - ErrPenOccupiedByOtherBatch = errors.New("猪栏已被其他批次使用") - // ErrPenStatusInvalidForAllocation 表示猪栏的当前状态(例如,'维修中')不允许被分配。 - ErrPenStatusInvalidForAllocation = errors.New("猪栏状态不允许分配") - // ErrPenNotFound 表示猪栏不存在 - ErrPenNotFound = errors.New("指定的猪栏不存在") - // ErrPenNotAssociatedWithBatch 表示猪栏未与该批次关联 - ErrPenNotAssociatedWithBatch = errors.New("猪栏未与该批次关联") - // ErrInvalidOperation 非法操作 - ErrInvalidOperation = errors.New("非法操作") -) - -// --- 领域服务接口 --- - -// PigBatchService 定义了猪批次管理的核心业务逻辑接口。 -// 它抽象了所有与猪批次相关的操作,使得应用层可以依赖于此接口,而不是具体的实现。 -type PigBatchService interface { - // CreatePigBatch 创建猪批次,并记录初始日志。 - CreatePigBatch(operatorID uint, batch *models.PigBatch) (*models.PigBatch, error) - // GetPigBatch 获取单个猪批次。 - GetPigBatch(id uint) (*models.PigBatch, error) - // UpdatePigBatch 更新猪批次信息。 - UpdatePigBatch(batch *models.PigBatch) (*models.PigBatch, error) - // DeletePigBatch 删除猪批次,包含业务规则校验。 - DeletePigBatch(id uint) error - // ListPigBatches 批量查询猪批次。 - ListPigBatches(isActive *bool) ([]*models.PigBatch, error) - // UpdatePigBatchPens 更新猪批次关联的猪栏。 - UpdatePigBatchPens(batchID uint, desiredPenIDs []uint) error - // AssignEmptyPensToBatch 为猪群分配空栏 - AssignEmptyPensToBatch(batchID uint, penIDs []uint, operatorID uint) error - // MovePigsIntoPen 将猪只从“虚拟库存”移入指定猪栏 - MovePigsIntoPen(batchID uint, toPenID uint, quantity int, operatorID uint, remarks string) error - // ReclassifyPenToNewBatch 连猪带栏,整体划拨到另一个猪群 - ReclassifyPenToNewBatch(fromBatchID uint, toBatchID uint, penID uint, operatorID uint, remarks string) error - - // GetCurrentPigQuantity 获取指定猪批次的当前猪只数量。 - GetCurrentPigQuantity(batchID uint) (int, error) - - UpdatePigBatchQuantity(operatorID uint, batchID uint, changeType models.LogChangeType, changeAmount int, changeReason string, happenedAt time.Time) error - - // ---交易子服务--- - // SellPigs 处理卖猪的业务逻辑。 - SellPigs(batchID uint, penID uint, quantity int, unitPrice float64, tatalPrice float64, traderName string, tradeDate time.Time, remarks string, operatorID uint) error - // BuyPigs 处理买猪的业务逻辑。 - BuyPigs(batchID uint, penID uint, quantity int, unitPrice float64, tatalPrice float64, traderName string, tradeDate time.Time, remarks string, operatorID uint) error - - // ---调栏子服务 --- - TransferPigsAcrossBatches(sourceBatchID uint, destBatchID uint, fromPenID uint, toPenID uint, quantity uint, operatorID uint, remarks string) error - TransferPigsWithinBatch(batchID uint, fromPenID uint, toPenID uint, quantity uint, operatorID uint, remarks string) error - - // --- 病猪管理相关方法 --- - // RecordSickPigs 记录新增病猪事件。 - RecordSickPigs(operatorID uint, batchID uint, penID uint, pigIDs string, quantity int, diagnosis string, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error - // RecordSickPigRecovery 记录病猪康复事件。 - RecordSickPigRecovery(operatorID uint, batchID uint, penID uint, pigIDs string, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error - // RecordSickPigDeath 记录病猪死亡事件。 - RecordSickPigDeath(operatorID uint, batchID uint, penID uint, pigIDs string, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error - // RecordSickPigCull 记录病猪淘汰事件。 - RecordSickPigCull(operatorID uint, batchID uint, penID uint, pigIDs string, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error -} - -// pigBatchService 是 PigBatchService 接口的具体实现。 -// 它作为猪群领域的主服务,封装了所有业务逻辑。 -type pigBatchService struct { - pigBatchRepo repository.PigBatchRepository // 猪批次仓库 - pigBatchLogRepo repository.PigBatchLogRepository // 猪批次日志仓库 - uow repository.UnitOfWork // 工作单元,用于管理事务 - transferSvc PigPenTransferManager // 调栏子服务 - tradeSvc PigTradeManager // 交易子服务 - sickSvc SickPigManager // 病猪子服务 -} - -// NewPigBatchService 是 pigBatchService 的构造函数。 -// 它通过依赖注入的方式,创建并返回一个 PigBatchService 接口的实例。 -func NewPigBatchService( - pigBatchRepo repository.PigBatchRepository, - pigBatchLogRepo repository.PigBatchLogRepository, - uow repository.UnitOfWork, - transferSvc PigPenTransferManager, - tradeSvc PigTradeManager, - sickSvc SickPigManager, -) PigBatchService { - return &pigBatchService{ - pigBatchRepo: pigBatchRepo, - pigBatchLogRepo: pigBatchLogRepo, - uow: uow, - transferSvc: transferSvc, - tradeSvc: tradeSvc, - sickSvc: sickSvc, - } -} diff --git a/internal/domain/pig/pig_batch_service.go b/internal/domain/pig/pig_batch_service.go index cd6383e..39c4e92 100644 --- a/internal/domain/pig/pig_batch_service.go +++ b/internal/domain/pig/pig_batch_service.go @@ -2,295 +2,110 @@ package pig import ( "errors" - "fmt" "time" "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" - "gorm.io/gorm" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/repository" ) -// --- 领域服务实现 --- +// --- 业务错误定义 --- -// CreatePigBatch 实现了创建猪批次的逻辑,并同时创建初始批次日志。 -func (s *pigBatchService) CreatePigBatch(operatorID uint, batch *models.PigBatch) (*models.PigBatch, error) { - // 业务规则可以在这里添加,例如检查批次号是否唯一等 +var ( + // ErrPigBatchNotFound 表示当尝试访问一个不存在的猪批次时发生的错误。 + ErrPigBatchNotFound = errors.New("指定的猪批次不存在") + // ErrPigBatchActive 表示当尝试对一个活跃的猪批次执行不允许的操作(如删除)时发生的错误。 + ErrPigBatchActive = errors.New("活跃的猪批次不能被删除") + // ErrPigBatchNotActive 表示当猪批次不处于活跃状态,但执行了需要其活跃的操作时发生的错误。 + ErrPigBatchNotActive = errors.New("猪批次不处于活跃状态,无法修改关联猪栏") + // ErrPenOccupiedByOtherBatch 表示当尝试将一个已经被其他批次占用的猪栏分配给新批次时发生的错误。 + ErrPenOccupiedByOtherBatch = errors.New("猪栏已被其他批次使用") + // ErrPenStatusInvalidForAllocation 表示猪栏的当前状态(例如,'维修中')不允许被分配。 + ErrPenStatusInvalidForAllocation = errors.New("猪栏状态不允许分配") + // ErrPenNotFound 表示猪栏不存在 + ErrPenNotFound = errors.New("指定的猪栏不存在") + // ErrPenNotAssociatedWithBatch 表示猪栏未与该批次关联 + ErrPenNotAssociatedWithBatch = errors.New("猪栏未与该批次关联") + // ErrInvalidOperation 非法操作 + ErrInvalidOperation = errors.New("非法操作") +) - var createdBatch *models.PigBatch - err := s.uow.ExecuteInTransaction(func(tx *gorm.DB) error { - // 1. 创建猪批次 - // 注意: 此处依赖一个假设存在的 pigBatchRepo.CreatePigBatchTx 方法 - var err error - createdBatch, err = s.pigBatchRepo.CreatePigBatchTx(tx, batch) - if err != nil { - return fmt.Errorf("创建猪批次失败: %w", err) - } +// --- 领域服务接口 --- - // 2. 创建初始批次日志 - initialLog := &models.PigBatchLog{ - PigBatchID: createdBatch.ID, - HappenedAt: time.Now(), - ChangeType: models.ChangeTypeCorrection, // 初始创建可视为一种校正 - ChangeCount: createdBatch.InitialCount, - Reason: fmt.Sprintf("创建了新的猪批次 %s,初始数量 %d", createdBatch.BatchNumber, createdBatch.InitialCount), - BeforeCount: 0, // 初始创建前数量为0 - AfterCount: createdBatch.InitialCount, - OperatorID: operatorID, - } +// PigBatchService 定义了猪批次管理的核心业务逻辑接口。 +// 它抽象了所有与猪批次相关的操作,使得应用层可以依赖于此接口,而不是具体的实现。 +type PigBatchService interface { + // CreatePigBatch 创建猪批次,并记录初始日志。 + CreatePigBatch(operatorID uint, batch *models.PigBatch) (*models.PigBatch, error) + // GetPigBatch 获取单个猪批次。 + GetPigBatch(id uint) (*models.PigBatch, error) + // UpdatePigBatch 更新猪批次信息。 + UpdatePigBatch(batch *models.PigBatch) (*models.PigBatch, error) + // DeletePigBatch 删除猪批次,包含业务规则校验。 + DeletePigBatch(id uint) error + // ListPigBatches 批量查询猪批次。 + ListPigBatches(isActive *bool) ([]*models.PigBatch, error) + // UpdatePigBatchPens 更新猪批次关联的猪栏。 + UpdatePigBatchPens(batchID uint, desiredPenIDs []uint) error + // AssignEmptyPensToBatch 为猪群分配空栏 + AssignEmptyPensToBatch(batchID uint, penIDs []uint, operatorID uint) error + // MovePigsIntoPen 将猪只从“虚拟库存”移入指定猪栏 + MovePigsIntoPen(batchID uint, toPenID uint, quantity int, operatorID uint, remarks string) error + // ReclassifyPenToNewBatch 连猪带栏,整体划拨到另一个猪群 + ReclassifyPenToNewBatch(fromBatchID uint, toBatchID uint, penID uint, operatorID uint, remarks string) error - // 3. 记录批次日志 - if err := s.pigBatchLogRepo.CreateTx(tx, initialLog); err != nil { - return fmt.Errorf("记录初始批次日志失败: %w", err) - } + // GetCurrentPigQuantity 获取指定猪批次的当前猪只数量。 + GetCurrentPigQuantity(batchID uint) (int, error) - return nil - }) + UpdatePigBatchQuantity(operatorID uint, batchID uint, changeType models.LogChangeType, changeAmount int, changeReason string, happenedAt time.Time) error - if err != nil { - return nil, err - } + // ---交易子服务--- + // SellPigs 处理卖猪的业务逻辑。 + SellPigs(batchID uint, penID uint, quantity int, unitPrice float64, tatalPrice float64, traderName string, tradeDate time.Time, remarks string, operatorID uint) error + // BuyPigs 处理买猪的业务逻辑。 + BuyPigs(batchID uint, penID uint, quantity int, unitPrice float64, tatalPrice float64, traderName string, tradeDate time.Time, remarks string, operatorID uint) error - return createdBatch, nil + // ---调栏子服务 --- + TransferPigsAcrossBatches(sourceBatchID uint, destBatchID uint, fromPenID uint, toPenID uint, quantity uint, operatorID uint, remarks string) error + TransferPigsWithinBatch(batchID uint, fromPenID uint, toPenID uint, quantity uint, operatorID uint, remarks string) error + + // --- 病猪管理相关方法 --- + // RecordSickPigs 记录新增病猪事件。 + RecordSickPigs(operatorID uint, batchID uint, penID uint, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error + // RecordSickPigRecovery 记录病猪康复事件。 + RecordSickPigRecovery(operatorID uint, batchID uint, penID uint, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error + // RecordSickPigDeath 记录病猪死亡事件。 + RecordSickPigDeath(operatorID uint, batchID uint, penID uint, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error + // RecordSickPigCull 记录病猪淘汰事件。 + RecordSickPigCull(operatorID uint, batchID uint, penID uint, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error } -// 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 +// pigBatchService 是 PigBatchService 接口的具体实现。 +// 它作为猪群领域的主服务,封装了所有业务逻辑。 +type pigBatchService struct { + pigBatchRepo repository.PigBatchRepository // 猪批次仓库 + pigBatchLogRepo repository.PigBatchLogRepository // 猪批次日志仓库 + uow repository.UnitOfWork // 工作单元,用于管理事务 + transferSvc PigPenTransferManager // 调栏子服务 + tradeSvc PigTradeManager // 交易子服务 + sickSvc SickPigManager // 病猪子服务 } -// UpdatePigBatch 实现了更新猪批次的逻辑。 -func (s *pigBatchService) UpdatePigBatch(batch *models.PigBatch) (*models.PigBatch, error) { - // 可以在这里添加更新前的业务校验 - updatedBatch, rowsAffected, err := s.pigBatchRepo.UpdatePigBatch(batch) - if err != nil { - return nil, err +// NewPigBatchService 是 pigBatchService 的构造函数。 +// 它通过依赖注入的方式,创建并返回一个 PigBatchService 接口的实例。 +func NewPigBatchService( + pigBatchRepo repository.PigBatchRepository, + pigBatchLogRepo repository.PigBatchLogRepository, + uow repository.UnitOfWork, + transferSvc PigPenTransferManager, + tradeSvc PigTradeManager, + sickSvc SickPigManager, +) PigBatchService { + return &pigBatchService{ + pigBatchRepo: pigBatchRepo, + pigBatchLogRepo: pigBatchLogRepo, + uow: uow, + transferSvc: transferSvc, + tradeSvc: tradeSvc, + sickSvc: sickSvc, } - if rowsAffected == 0 { - return nil, ErrPigBatchNotFound // 如果没有行被更新,可能意味着记录不存在 - } - return updatedBatch, nil -} - -// DeletePigBatch 实现了删除猪批次的逻辑,并包含业务规则校验。 -func (s *pigBatchService) DeletePigBatch(id uint) error { - return s.uow.ExecuteInTransaction(func(tx *gorm.DB) error { - // 1. 获取猪批次信息 - batch, err := s.pigBatchRepo.GetPigBatchByIDTx(tx, id) // 使用事务内方法 - if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return ErrPigBatchNotFound - } - return err - } - - // 2. 核心业务规则:检查猪批次是否为活跃状态 - if batch.IsActive() { - return ErrPigBatchActive // 如果活跃,则不允许删除 - } - - // 3. 释放所有关联的猪栏 - // 获取该批次下所有猪栏 - pensInBatch, err := s.transferSvc.GetPensByBatchID(tx, id) - if err != nil { - return fmt.Errorf("获取猪批次 %d 关联猪栏失败: %w", id, err) - } - - // 逐一释放猪栏 - for _, pen := range pensInBatch { - if err := s.transferSvc.ReleasePen(tx, pen.ID); err != nil { - return fmt.Errorf("释放猪栏 %d 失败: %w", pen.ID, err) - } - } - - // 4. 执行删除猪批次 - rowsAffected, err := s.pigBatchRepo.DeletePigBatchTx(tx, 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 实现了在事务中更新猪批次关联猪栏的复杂逻辑。 -// 它通过调用底层的 PigPenTransferManager 来执行数据库操作,从而保持了职责的清晰。 -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 - }) -} - -// GetCurrentPigQuantity 实现了获取指定猪批次的当前猪只数量的逻辑。 -func (s *pigBatchService) GetCurrentPigQuantity(batchID uint) (int, error) { - var getErr error - var quantity int - err := s.uow.ExecuteInTransaction(func(tx *gorm.DB) error { - quantity, getErr = s.getCurrentPigQuantityTx(tx, batchID) - return getErr - }) - if err != nil { - return 0, err - } - return quantity, nil -} - -// getCurrentPigQuantityTx 实现了获取指定猪批次的当前猪只数量的逻辑。 -func (s *pigBatchService) getCurrentPigQuantityTx(tx *gorm.DB, batchID uint) (int, error) { - // 1. 获取猪批次初始信息 - batch, err := s.pigBatchRepo.GetPigBatchByIDTx(tx, batchID) - if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - return 0, ErrPigBatchNotFound - } - return 0, fmt.Errorf("获取猪批次 %d 初始信息失败: %w", batchID, err) - } - - // 2. 尝试获取该批次的最后一条日志记录 - lastLog, err := s.pigBatchLogRepo.GetLastLogByBatchIDTx(tx, batchID) - if err != nil { - if errors.Is(err, gorm.ErrRecordNotFound) { - // 如果没有找到任何日志记录(除了初始创建),则当前数量就是初始数量 - return batch.InitialCount, nil - } - return 0, fmt.Errorf("获取猪批次 %d 最后一条日志失败: %w", batchID, err) - } - - // 3. 如果找到最后一条日志,则当前数量为该日志的 AfterCount - return lastLog.AfterCount, nil -} - -func (s *pigBatchService) UpdatePigBatchQuantity(operatorID uint, batchID uint, changeType models.LogChangeType, changeAmount int, changeReason string, happenedAt time.Time) error { - return s.uow.ExecuteInTransaction(func(tx *gorm.DB) error { - return s.updatePigBatchQuantityTx(tx, operatorID, batchID, changeType, changeAmount, changeReason, happenedAt) - }) -} - -func (s *pigBatchService) updatePigBatchQuantityTx(tx *gorm.DB, operatorID uint, batchID uint, changeType models.LogChangeType, changeAmount int, changeReason string, happenedAt time.Time) error { - lastLog, err := s.pigBatchLogRepo.GetLastLogByBatchIDTx(tx, batchID) - if err != nil { - return err - } - // 检查数量不应该减到小于零 - if changeAmount < 0 { - if lastLog.AfterCount+changeAmount < 0 { - return ErrInvalidOperation - } - } - pigBatchLog := &models.PigBatchLog{ - PigBatchID: batchID, - ChangeType: changeType, - ChangeCount: changeAmount, - Reason: changeReason, - BeforeCount: lastLog.AfterCount, - AfterCount: lastLog.AfterCount + changeAmount, - OperatorID: operatorID, - HappenedAt: happenedAt, - } - return s.pigBatchLogRepo.CreateTx(tx, pigBatchLog) } diff --git a/internal/domain/pig/pig_batch_service_method.go b/internal/domain/pig/pig_batch_service_method.go new file mode 100644 index 0000000..cd6383e --- /dev/null +++ b/internal/domain/pig/pig_batch_service_method.go @@ -0,0 +1,296 @@ +package pig + +import ( + "errors" + "fmt" + "time" + + "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" + "gorm.io/gorm" +) + +// --- 领域服务实现 --- + +// CreatePigBatch 实现了创建猪批次的逻辑,并同时创建初始批次日志。 +func (s *pigBatchService) CreatePigBatch(operatorID uint, batch *models.PigBatch) (*models.PigBatch, error) { + // 业务规则可以在这里添加,例如检查批次号是否唯一等 + + var createdBatch *models.PigBatch + err := s.uow.ExecuteInTransaction(func(tx *gorm.DB) error { + // 1. 创建猪批次 + // 注意: 此处依赖一个假设存在的 pigBatchRepo.CreatePigBatchTx 方法 + var err error + createdBatch, err = s.pigBatchRepo.CreatePigBatchTx(tx, batch) + if err != nil { + return fmt.Errorf("创建猪批次失败: %w", err) + } + + // 2. 创建初始批次日志 + initialLog := &models.PigBatchLog{ + PigBatchID: createdBatch.ID, + HappenedAt: time.Now(), + ChangeType: models.ChangeTypeCorrection, // 初始创建可视为一种校正 + ChangeCount: createdBatch.InitialCount, + Reason: fmt.Sprintf("创建了新的猪批次 %s,初始数量 %d", createdBatch.BatchNumber, createdBatch.InitialCount), + BeforeCount: 0, // 初始创建前数量为0 + AfterCount: createdBatch.InitialCount, + OperatorID: operatorID, + } + + // 3. 记录批次日志 + if err := s.pigBatchLogRepo.CreateTx(tx, initialLog); err != nil { + return fmt.Errorf("记录初始批次日志失败: %w", err) + } + + return nil + }) + + if err != nil { + return nil, err + } + + return createdBatch, nil +} + +// 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 { + return s.uow.ExecuteInTransaction(func(tx *gorm.DB) error { + // 1. 获取猪批次信息 + batch, err := s.pigBatchRepo.GetPigBatchByIDTx(tx, id) // 使用事务内方法 + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return ErrPigBatchNotFound + } + return err + } + + // 2. 核心业务规则:检查猪批次是否为活跃状态 + if batch.IsActive() { + return ErrPigBatchActive // 如果活跃,则不允许删除 + } + + // 3. 释放所有关联的猪栏 + // 获取该批次下所有猪栏 + pensInBatch, err := s.transferSvc.GetPensByBatchID(tx, id) + if err != nil { + return fmt.Errorf("获取猪批次 %d 关联猪栏失败: %w", id, err) + } + + // 逐一释放猪栏 + for _, pen := range pensInBatch { + if err := s.transferSvc.ReleasePen(tx, pen.ID); err != nil { + return fmt.Errorf("释放猪栏 %d 失败: %w", pen.ID, err) + } + } + + // 4. 执行删除猪批次 + rowsAffected, err := s.pigBatchRepo.DeletePigBatchTx(tx, 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 实现了在事务中更新猪批次关联猪栏的复杂逻辑。 +// 它通过调用底层的 PigPenTransferManager 来执行数据库操作,从而保持了职责的清晰。 +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 + }) +} + +// GetCurrentPigQuantity 实现了获取指定猪批次的当前猪只数量的逻辑。 +func (s *pigBatchService) GetCurrentPigQuantity(batchID uint) (int, error) { + var getErr error + var quantity int + err := s.uow.ExecuteInTransaction(func(tx *gorm.DB) error { + quantity, getErr = s.getCurrentPigQuantityTx(tx, batchID) + return getErr + }) + if err != nil { + return 0, err + } + return quantity, nil +} + +// getCurrentPigQuantityTx 实现了获取指定猪批次的当前猪只数量的逻辑。 +func (s *pigBatchService) getCurrentPigQuantityTx(tx *gorm.DB, batchID uint) (int, error) { + // 1. 获取猪批次初始信息 + batch, err := s.pigBatchRepo.GetPigBatchByIDTx(tx, batchID) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return 0, ErrPigBatchNotFound + } + return 0, fmt.Errorf("获取猪批次 %d 初始信息失败: %w", batchID, err) + } + + // 2. 尝试获取该批次的最后一条日志记录 + lastLog, err := s.pigBatchLogRepo.GetLastLogByBatchIDTx(tx, batchID) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + // 如果没有找到任何日志记录(除了初始创建),则当前数量就是初始数量 + return batch.InitialCount, nil + } + return 0, fmt.Errorf("获取猪批次 %d 最后一条日志失败: %w", batchID, err) + } + + // 3. 如果找到最后一条日志,则当前数量为该日志的 AfterCount + return lastLog.AfterCount, nil +} + +func (s *pigBatchService) UpdatePigBatchQuantity(operatorID uint, batchID uint, changeType models.LogChangeType, changeAmount int, changeReason string, happenedAt time.Time) error { + return s.uow.ExecuteInTransaction(func(tx *gorm.DB) error { + return s.updatePigBatchQuantityTx(tx, operatorID, batchID, changeType, changeAmount, changeReason, happenedAt) + }) +} + +func (s *pigBatchService) updatePigBatchQuantityTx(tx *gorm.DB, operatorID uint, batchID uint, changeType models.LogChangeType, changeAmount int, changeReason string, happenedAt time.Time) error { + lastLog, err := s.pigBatchLogRepo.GetLastLogByBatchIDTx(tx, batchID) + if err != nil { + return err + } + // 检查数量不应该减到小于零 + if changeAmount < 0 { + if lastLog.AfterCount+changeAmount < 0 { + return ErrInvalidOperation + } + } + pigBatchLog := &models.PigBatchLog{ + PigBatchID: batchID, + ChangeType: changeType, + ChangeCount: changeAmount, + Reason: changeReason, + BeforeCount: lastLog.AfterCount, + AfterCount: lastLog.AfterCount + changeAmount, + OperatorID: operatorID, + HappenedAt: happenedAt, + } + return s.pigBatchLogRepo.CreateTx(tx, pigBatchLog) +} diff --git a/internal/domain/pig/pig_batch_service_pig_sick.go b/internal/domain/pig/pig_batch_service_pig_sick.go index f088a06..fecac92 100644 --- a/internal/domain/pig/pig_batch_service_pig_sick.go +++ b/internal/domain/pig/pig_batch_service_pig_sick.go @@ -7,21 +7,27 @@ import ( ) // RecordSickPigs 记录新增病猪事件。 -func (s *pigBatchService) RecordSickPigs(operatorID uint, batchID uint, penID uint, pigIDs string, quantity int, diagnosis string, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error { - panic("implement me") +func (s *pigBatchService) RecordSickPigs(operatorID uint, batchID uint, penID uint, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error { + // 1. 检查批次是否活跃 + + // 2. 检查猪栏是否关联 + + // 3. 检查剩余健康猪不能少于即将转化的病猪数量 + + // 4. 创建病猪日志 } // RecordSickPigRecovery 记录病猪康复事件。 -func (s *pigBatchService) RecordSickPigRecovery(operatorID uint, batchID uint, penID uint, pigIDs string, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error { +func (s *pigBatchService) RecordSickPigRecovery(operatorID uint, batchID uint, penID uint, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error { panic("implement me") } // RecordSickPigDeath 记录病猪死亡事件。 -func (s *pigBatchService) RecordSickPigDeath(operatorID uint, batchID uint, penID uint, pigIDs string, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error { +func (s *pigBatchService) RecordSickPigDeath(operatorID uint, batchID uint, penID uint, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error { panic("implement me") } // RecordSickPigCull 记录病猪淘汰事件。 -func (s *pigBatchService) RecordSickPigCull(operatorID uint, batchID uint, penID uint, pigIDs string, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error { +func (s *pigBatchService) RecordSickPigCull(operatorID uint, batchID uint, penID uint, quantity int, treatmentLocation models.PigBatchSickPigTreatmentLocation, happenedAt time.Time, remarks string) error { panic("implement me") }