Compare commits

..

3 Commits

Author SHA1 Message Date
0576a790dd 实现 ReclassifyPenToNewBatch 2025-10-06 18:08:56 +08:00
5e49cd3f95 实现MovePigsIntoPen 2025-10-06 17:56:13 +08:00
efbe7d167c 实现AssignEmptyPensToBatch 2025-10-06 17:44:00 +08:00
3 changed files with 257 additions and 4 deletions

View File

@@ -2,6 +2,7 @@ package pig
import ( import (
"errors" "errors"
"fmt"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models" "git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository" "git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
@@ -25,6 +26,9 @@ type PigPenTransferManager interface {
// GetCurrentPigsInPen 通过汇总猪只迁移日志,计算给定猪栏中的当前猪只数量。 // GetCurrentPigsInPen 通过汇总猪只迁移日志,计算给定猪栏中的当前猪只数量。
GetCurrentPigsInPen(tx *gorm.DB, penID uint) (int, error) GetCurrentPigsInPen(tx *gorm.DB, penID uint) (int, error)
// GetTotalPigsInPensForBatchTx 计算指定猪群下所有猪栏的当前总存栏数
GetTotalPigsInPensForBatchTx(tx *gorm.DB, batchID uint) (int, error)
} }
// pigPenTransferManager 是 PigPenTransferManager 接口的具体实现。 // pigPenTransferManager 是 PigPenTransferManager 接口的具体实现。
@@ -117,3 +121,25 @@ func (s *pigPenTransferManager) GetCurrentPigsInPen(tx *gorm.DB, penID uint) (in
return totalPigs, nil return totalPigs, nil
} }
// GetTotalPigsInPensForBatchTx 计算指定猪群下所有猪栏的当前总存栏数
// 该方法通过遍历猪群下的每个猪栏,并调用 GetCurrentPigsInPen 来累加存栏数。
func (s *pigPenTransferManager) GetTotalPigsInPensForBatchTx(tx *gorm.DB, batchID uint) (int, error) {
// 1. 获取该批次下所有猪栏的列表
pensInBatch, err := s.GetPensByBatchID(tx, batchID)
if err != nil {
return 0, fmt.Errorf("获取猪群 %d 下属猪栏失败: %w", batchID, err)
}
totalPigs := 0
// 2. 遍历每个猪栏,累加其存栏数
for _, pen := range pensInBatch {
pigsInPen, err := s.GetCurrentPigsInPen(tx, pen.ID)
if err != nil {
return 0, fmt.Errorf("获取猪栏 %d 存栏数失败: %w", pen.ID, err)
}
totalPigs += pigsInPen
}
return totalPigs, nil
}

View File

@@ -46,21 +46,25 @@ type PigBatchService interface {
ListPigBatches(isActive *bool) ([]*models.PigBatch, error) ListPigBatches(isActive *bool) ([]*models.PigBatch, error)
// UpdatePigBatchPens 更新猪批次关联的猪栏。 // UpdatePigBatchPens 更新猪批次关联的猪栏。
UpdatePigBatchPens(batchID uint, desiredPenIDs []uint) error 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 获取指定猪批次的当前猪只数量。
GetCurrentPigQuantity(batchID uint) (int, error) GetCurrentPigQuantity(batchID uint) (int, error)
UpdatePigBatchQuantity(operatorID uint, batchID uint, changeType models.LogChangeType, changeAmount int, changeReason string, happenedAt time.Time) error UpdatePigBatchQuantity(operatorID uint, batchID uint, changeType models.LogChangeType, changeAmount int, changeReason string, happenedAt time.Time) error
// 交易子服务 // ---交易子服务---
// SellPigs 处理卖猪的业务逻辑。 // SellPigs 处理卖猪的业务逻辑。
SellPigs(batchID uint, penID uint, quantity int, unitPrice float64, tatalPrice float64, traderName string, tradeDate time.Time, remarks string, operatorID uint) error SellPigs(batchID uint, penID uint, quantity int, unitPrice float64, tatalPrice float64, traderName string, tradeDate time.Time, remarks string, operatorID uint) error
// BuyPigs 处理买猪的业务逻辑。 // BuyPigs 处理买猪的业务逻辑。
BuyPigs(batchID uint, penID uint, quantity int, unitPrice float64, tatalPrice float64, traderName string, tradeDate time.Time, remarks string, operatorID uint) error 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 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 TransferPigsWithinBatch(batchID uint, fromPenID uint, toPenID uint, quantity uint, operatorID uint, remarks string) error
} }

View File

@@ -158,3 +158,226 @@ func (s *pigBatchService) TransferPigsAcrossBatches(sourceBatchID uint, destBatc
return nil 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
})
}