Files
pig-farm-controller/internal/domain/pig/pig_batch_service_pen_transfer.go

456 lines
16 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 pig
import (
"errors"
"fmt"
"time"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
"github.com/google/uuid"
"gorm.io/gorm"
)
// executeTransferAndLog 是一个私有辅助方法,用于封装创建和记录迁移日志的通用逻辑。
func (s *pigBatchService) executeTransferAndLog(tx *gorm.DB, fromBatchID, toBatchID, fromPenID, toPenID uint, quantity int, transferType models.PigTransferType, operatorID uint, remarks string) error {
// 通用校验:任何调出操作都不能超过源猪栏的当前存栏数
if quantity < 0 { // 当调出时才需要检查
currentPigsInFromPen, err := s.transferSvc.GetCurrentPigsInPen(tx, fromPenID)
if err != nil {
return fmt.Errorf("获取源猪栏 %d 当前猪只数失败: %w", fromPenID, err)
}
if currentPigsInFromPen+quantity < 0 {
return fmt.Errorf("调出数量 %d 超过源猪栏 %d 当前存栏数 %d", -quantity, fromPenID, currentPigsInFromPen)
}
}
// 1. 生成关联ID
correlationID := uuid.New().String()
// 2. 创建调出日志
logOut := &models.PigTransferLog{
TransferTime: time.Now(),
PigBatchID: fromBatchID,
PenID: fromPenID,
Quantity: -quantity, // 调出为负数
Type: transferType,
CorrelationID: correlationID,
OperatorID: operatorID,
Remarks: remarks,
}
// 3. 创建调入日志
logIn := &models.PigTransferLog{
TransferTime: time.Now(),
PigBatchID: toBatchID,
PenID: toPenID,
Quantity: quantity, // 调入为正数
Type: transferType,
CorrelationID: correlationID,
OperatorID: operatorID,
Remarks: remarks,
}
// 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, operatorID uint, remarks string) 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), "群内调栏", operatorID, remarks)
if err != nil {
return err
}
// 3. 群内调栏,猪群总数不变
return nil
})
}
// TransferPigsAcrossBatches 实现了跨猪群的调栏业务。
func (s *pigBatchService) TransferPigsAcrossBatches(sourceBatchID uint, destBatchID uint, fromPenID uint, toPenID uint, quantity uint, operatorID uint, remarks string) error {
if sourceBatchID == destBatchID {
return errors.New("源猪群和目标猪群不能相同")
}
if quantity == 0 {
return errors.New("迁移数量不能为零")
}
return s.uow.ExecuteInTransaction(func(tx *gorm.DB) error {
// 1. 核心业务规则校验
// 1.1 校验猪群存在
if _, err := s.pigBatchRepo.GetPigBatchByIDTx(tx, sourceBatchID); err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return fmt.Errorf("源猪群 %d 不存在", sourceBatchID)
}
return fmt.Errorf("获取源猪群信息失败: %w", err)
}
if _, err := s.pigBatchRepo.GetPigBatchByIDTx(tx, destBatchID); err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return fmt.Errorf("目标猪群 %d 不存在", destBatchID)
}
return fmt.Errorf("获取目标猪群信息失败: %w", err)
}
// 1.2 校验猪栏归属
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), "跨群调栏", operatorID, remarks)
if err != nil {
return err
}
// 3. 通过创建批次日志来修改猪群总数,确保数据可追溯
now := time.Now()
// 3.1 记录源猪群数量减少
reasonOut := fmt.Sprintf("跨群调栏: %d头猪从批次 %d 调出至批次 %d。备注: %s", quantity, sourceBatchID, destBatchID, remarks)
err = s.updatePigBatchQuantityTx(tx, operatorID, sourceBatchID, models.ChangeTypeTransferOut, -int(quantity), reasonOut, now)
if err != nil {
return fmt.Errorf("更新源猪群 %d 数量失败: %w", sourceBatchID, err)
}
// 3.2 记录目标猪群数量增加
reasonIn := fmt.Sprintf("跨群调栏: %d头猪从批次 %d 调入。备注: %s", quantity, sourceBatchID, remarks)
err = s.updatePigBatchQuantityTx(tx, operatorID, destBatchID, models.ChangeTypeTransferIn, int(quantity), reasonIn, now)
if err != nil {
return fmt.Errorf("更新目标猪群 %d 数量失败: %w", destBatchID, err)
}
return nil
})
}
// AssignEmptyPensToBatch 为猪群分配空栏
func (s *pigBatchService) AssignEmptyPensToBatch(batchID uint, penIDs []uint, operatorID uint) error {
return s.uow.ExecuteInTransaction(func(tx *gorm.DB) error {
// 1. 验证猪批次是否存在且活跃
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. 遍历并校验每一个待分配的猪栏
for _, penID := range penIDs {
pen, 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 pen.Status != models.PenStatusEmpty {
return fmt.Errorf("猪栏 %s 状态不为空 (%s),无法分配", pen.PenNumber, pen.Status)
}
if pen.PigBatchID != nil {
return fmt.Errorf("猪栏 %s 已被其他批次 %d 占用,无法分配", pen.PenNumber, *pen.PigBatchID)
}
// 3. 更新猪栏的归属
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
})
}
// MovePigsIntoPen 将猪只从“虚拟库存”移入指定猪栏
func (s *pigBatchService) MovePigsIntoPen(batchID uint, toPenID uint, quantity int, operatorID uint, remarks string) error {
if quantity <= 0 {
return errors.New("迁移数量必须大于零")
}
return s.uow.ExecuteInTransaction(func(tx *gorm.DB) error {
// 1. 验证猪批次是否存在且活跃
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. 校验目标猪栏
toPen, err := s.transferSvc.GetPenByID(tx, toPenID)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return fmt.Errorf("目标猪栏 %d 不存在: %w", toPenID, ErrPenNotFound)
}
return fmt.Errorf("获取目标猪栏 %d 信息失败: %w", toPenID, err)
}
// 校验目标猪栏的归属和状态
if toPen.PigBatchID == nil {
return fmt.Errorf("目标猪栏 %s 不属于当前批次 %s", toPen.PenNumber, batchID)
}
if toPen.PigBatchID != nil && *toPen.PigBatchID != batchID {
return fmt.Errorf("目标猪栏 %s 已被其他批次 %d 占用,无法移入", toPen.PenNumber, *toPen.PigBatchID)
}
// 3. 校验猪群中有足够的“未分配”猪只
currentBatchTotal, err := s.getCurrentPigQuantityTx(tx, batchID)
if err != nil {
return fmt.Errorf("获取猪群 %d 当前总数量失败: %w", batchID, err)
}
// 获取该批次下所有猪栏的当前总存栏数
totalPigsInPens, err := s.transferSvc.GetTotalPigsInPensForBatchTx(tx, batchID)
if err != nil {
return fmt.Errorf("计算猪群 %d 下属猪栏总存栏失败: %w", batchID, err)
}
unassignedPigs := currentBatchTotal - totalPigsInPens
if unassignedPigs < quantity {
return fmt.Errorf("猪群 %d 未分配猪只不足,当前未分配 %d 头,需要移入 %d 头", batchID, unassignedPigs, quantity)
}
// 4. 记录转移日志
logIn := &models.PigTransferLog{
TransferTime: time.Now(),
PigBatchID: batchID,
PenID: toPenID,
Quantity: quantity, // 调入为正数
Type: models.PigTransferTypeInternal, // 首次入栏
OperatorID: operatorID,
Remarks: remarks,
}
if err := s.transferSvc.LogTransfer(tx, logIn); err != nil {
return fmt.Errorf("记录入栏日志失败: %w", err)
}
return nil
})
}
// ReclassifyPenToNewBatch 连猪带栏,整体划拨到另一个猪群
func (s *pigBatchService) ReclassifyPenToNewBatch(fromBatchID uint, toBatchID uint, penID uint, operatorID uint, remarks string) error {
if fromBatchID == toBatchID {
return errors.New("源猪群和目标猪群不能相同")
}
return s.uow.ExecuteInTransaction(func(tx *gorm.DB) error {
// 1. 核心业务规则校验
// 1.1 校验猪群存在
fromBatch, err := s.pigBatchRepo.GetPigBatchByIDTx(tx, fromBatchID)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return fmt.Errorf("源猪群 %d 不存在", fromBatchID)
}
return fmt.Errorf("获取源猪群信息失败: %w", err)
}
toBatch, err := s.pigBatchRepo.GetPigBatchByIDTx(tx, toBatchID)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return fmt.Errorf("目标猪群 %d 不存在", toBatchID)
}
return fmt.Errorf("获取目标猪群信息失败: %w", err)
}
// 1.2 校验猪栏归属
pen, 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 pen.PigBatchID == nil || *pen.PigBatchID != fromBatchID {
return fmt.Errorf("猪栏 %v 不属于源猪群 %v无法划拨", pen.PenNumber, fromBatch.BatchNumber)
}
// 2. 获取猪栏当前存栏数
quantity, err := s.transferSvc.GetCurrentPigsInPen(tx, penID)
if err != nil {
return fmt.Errorf("获取猪栏 %v 存栏数失败: %w", pen.PenNumber, err)
}
// 3. 更新猪栏的归属
updates := map[string]interface{}{
"pig_batch_id": &toBatchID,
}
if err := s.transferSvc.UpdatePenFields(tx, penID, updates); err != nil {
return fmt.Errorf("更新猪栏 %v 归属失败: %w", pen.PenNumber, err)
}
// 如果猪栏是空的,则只进行归属变更,不影响猪群数量
if quantity == 0 {
return nil // 空栏划拨,不涉及猪只数量变更
}
// 4. 记录猪只从旧批次“迁出”的猪栏日志
correlationID := uuid.New().String()
logOut := &models.PigTransferLog{
TransferTime: time.Now(),
PigBatchID: fromBatchID,
PenID: penID,
Quantity: -quantity, // 迁出为负数
Type: models.PigTransferTypeCrossBatch,
CorrelationID: correlationID,
OperatorID: operatorID,
Remarks: fmt.Sprintf("整栏划拨迁出: %d头猪从批次 %v 随猪栏 %v 划拨至批次 %v。备注: %s", quantity, fromBatch.BatchNumber, pen.PenNumber, toBatch.BatchNumber, remarks),
}
if err := s.transferSvc.LogTransfer(tx, logOut); err != nil {
return fmt.Errorf("记录猪栏 %d 迁出日志失败: %w", penID, err)
}
// 5. 记录猪只到新批次“迁入”的猪栏日志
logIn := &models.PigTransferLog{
TransferTime: time.Now(),
PigBatchID: toBatchID,
PenID: penID,
Quantity: quantity, // 迁入为正数
Type: models.PigTransferTypeCrossBatch,
CorrelationID: correlationID,
OperatorID: operatorID,
Remarks: fmt.Sprintf("整栏划拨迁入: %v头猪随猪栏 %v 从批次 %v 划拨入。备注: %s", quantity, fromBatch.BatchNumber, pen.PenNumber, remarks),
}
if err := s.transferSvc.LogTransfer(tx, logIn); err != nil {
return fmt.Errorf("记录猪栏 %d 迁入日志失败: %w", penID, err)
}
// 7. 通过创建批次日志来修改猪群总数,确保数据可追溯
now := time.Now()
// 7.1 记录源猪群数量减少
reasonOutBatch := fmt.Sprintf("整栏划拨: %d头猪随猪栏 %v 从批次 %v 划拨至批次 %v。备注: %s", quantity, pen.PenNumber, fromBatch.BatchNumber, toBatchID, remarks)
err = s.updatePigBatchQuantityTx(tx, operatorID, fromBatchID, models.ChangeTypeTransferOut, -quantity, reasonOutBatch, now)
if err != nil {
return fmt.Errorf("更新源猪群 %v 数量失败: %w", fromBatch.BatchNumber, err)
}
// 7.2 记录目标猪群数量增加
reasonInBatch := fmt.Sprintf("整栏划拨: %v头猪随猪栏 %v 从批次 %v 划拨入。备注: %s", quantity, pen.PenNumber, fromBatch.BatchNumber, remarks)
err = s.updatePigBatchQuantityTx(tx, operatorID, toBatchID, models.ChangeTypeTransferIn, quantity, reasonInBatch, now)
if err != nil {
return fmt.Errorf("更新目标猪群 %v 数量失败: %w", toBatch.BatchNumber, err)
}
return nil
})
}
func (s *pigBatchService) RemoveEmptyPenFromBatch(batchID uint, penID uint) error {
return s.uow.ExecuteInTransaction(func(tx *gorm.DB) error {
// 1. 检查猪批次是否存在且活跃
batch, err := s.pigBatchRepo.GetPigBatchByIDTx(tx, batchID)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return ErrPigBatchNotFound
}
return err
}
if !batch.IsActive() {
return ErrPigBatchNotActive
}
// 2. 检查猪栏是否存在
pen, err := s.transferSvc.GetPenByID(tx, penID)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return ErrPenNotFound
}
return err
}
// 3. 检查猪栏是否与当前批次关联
if pen.PigBatchID == nil || *pen.PigBatchID != batchID {
return ErrPenNotAssociatedWithBatch
}
// 4. 检查猪栏是否为空
pigsInPen, err := s.transferSvc.GetCurrentPigsInPen(tx, penID)
if err != nil {
return err
}
if pigsInPen > 0 {
return ErrPenNotEmpty
}
// 5. 释放猪栏 (将 pig_batch_id 设置为 nil状态设置为空闲)
if err := s.transferSvc.ReleasePen(tx, penID); err != nil {
return err
}
return nil
})
}
func (s *pigBatchService) GetCurrentPigsInPen(penID uint) (int, error) {
var currentPigs int
err := s.uow.ExecuteInTransaction(func(tx *gorm.DB) error {
pigs, err := s.transferSvc.GetCurrentPigsInPen(tx, penID)
if err != nil {
return err
}
currentPigs = pigs
return nil
})
return currentPigs, err
}
// GetTotalPigsInPensForBatch 实现了获取指定猪群下所有猪栏的当前总存栏数的逻辑。
func (s *pigBatchService) GetTotalPigsInPensForBatch(batchID uint) (int, error) {
var totalPigs int
err := s.uow.ExecuteInTransaction(func(tx *gorm.DB) error {
pigs, err := s.transferSvc.GetTotalPigsInPensForBatchTx(tx, batchID)
if err != nil {
return err
}
totalPigs = pigs
return nil
})
return totalPigs, err
}