From c49844feea58932ec011439f6761ad995f98a45a Mon Sep 17 00:00:00 2001 From: huang <1724659546@qq.com> Date: Mon, 6 Oct 2025 14:32:52 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E4=BA=A4=E6=98=93=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E5=99=A8=E4=B8=BB=E6=9C=8D=E5=8A=A1=E5=85=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/domain/pig/pig_batch.go | 34 +++-- internal/domain/pig/pig_batch_service.go | 39 ++++++ .../domain/pig/pig_batch_service_pig_trade.go | 125 ++++++++++++++++++ internal/infra/models/pig_batch.go | 1 + .../repository/pig_batch_log_repository.go | 28 ++++ 5 files changed, 208 insertions(+), 19 deletions(-) diff --git a/internal/domain/pig/pig_batch.go b/internal/domain/pig/pig_batch.go index 53ffa8f..63a2d93 100644 --- a/internal/domain/pig/pig_batch.go +++ b/internal/domain/pig/pig_batch.go @@ -2,6 +2,7 @@ package pig import ( "errors" + "time" "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" ) @@ -30,29 +31,24 @@ var ( // PigBatchService 定义了猪批次管理的核心业务逻辑接口。 // 它抽象了所有与猪批次相关的操作,使得应用层可以依赖于此接口,而不是具体的实现。 type PigBatchService interface { - // TransferPigsWithinBatch 处理同一个猪群内部的调栏业务。 - TransferPigsWithinBatch(batchID uint, fromPenID uint, toPenID uint, quantity uint, operatorID uint, remarks string) error - - // TransferPigsAcrossBatches 处理跨猪群的调栏业务。 - TransferPigsAcrossBatches(sourceBatchID uint, destBatchID uint, fromPenID uint, toPenID uint, quantity uint, operatorID uint, remarks string) error - - // CreatePigBatch 创建一个新的猪批次。 + // CreatePigBatch 创建猪批次,并记录初始日志。 CreatePigBatch(operatorID uint, batch *models.PigBatch) (*models.PigBatch, error) - - // GetPigBatch 根据ID获取单个猪批次的详细信息。 + // GetPigBatch 获取单个猪批次。 GetPigBatch(id uint) (*models.PigBatch, error) - - // UpdatePigBatch 更新一个已存在的猪批次信息。 + // UpdatePigBatch 更新猪批次信息。 UpdatePigBatch(batch *models.PigBatch) (*models.PigBatch, error) - - // DeletePigBatch 删除一个指定的猪批次。 - // 实现时需要包含业务规则校验,例如,活跃的批次不能被删除。 + // DeletePigBatch 删除猪批次,包含业务规则校验。 DeletePigBatch(id uint) error - - // ListPigBatches 根据是否活跃的状态,列出所有符合条件的猪批次。 + // ListPigBatches 批量查询猪批次。 ListPigBatches(isActive *bool) ([]*models.PigBatch, error) - - // UpdatePigBatchPens 负责原子性地更新一个猪批次所关联的所有猪栏。 - // 它会处理猪栏的添加、移除,并确保数据的一致性。 + // UpdatePigBatchPens 更新猪批次关联的猪栏。 UpdatePigBatchPens(batchID uint, desiredPenIDs []uint) error + + // GetCurrentPigQuantity 获取指定猪批次的当前猪只数量。 + GetCurrentPigQuantity(batchID uint) (int, error) + + // SellPigs 处理卖猪的业务逻辑。 + SellPigs(batchID uint, quantity int, unitPrice float64, tatalPrice float64, traderName string, tradeDate time.Time, remarks string, operatorID uint) error + // BuyPigs 处理买猪的业务逻辑。 + BuyPigs(batchID uint, quantity int, unitPrice float64, tatalPrice float64, traderName string, tradeDate time.Time, remarks string, operatorID uint) error } diff --git a/internal/domain/pig/pig_batch_service.go b/internal/domain/pig/pig_batch_service.go index 7b711d4..110c0cb 100644 --- a/internal/domain/pig/pig_batch_service.go +++ b/internal/domain/pig/pig_batch_service.go @@ -235,3 +235,42 @@ func (s *pigBatchService) UpdatePigBatchPens(batchID uint, desiredPenIDs []uint) 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 +} diff --git a/internal/domain/pig/pig_batch_service_pig_trade.go b/internal/domain/pig/pig_batch_service_pig_trade.go index 467de02..48603e1 100644 --- a/internal/domain/pig/pig_batch_service_pig_trade.go +++ b/internal/domain/pig/pig_batch_service_pig_trade.go @@ -1 +1,126 @@ package pig + +import ( + "errors" + "fmt" + "time" + + "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" + "gorm.io/gorm" +) + +// SellPigs 处理批量销售猪的业务逻辑。 +func (s *pigBatchService) SellPigs(batchID uint, quantity int, unitPrice float64, tatalPrice float64, traderName string, tradeDate time.Time, remarks string, operatorID uint) error { + return s.uow.ExecuteInTransaction(func(tx *gorm.DB) error { + if quantity <= 0 { + return errors.New("销售数量必须大于0") + } + + // 1. 获取猪批次信息 + _, err := s.pigBatchRepo.GetPigBatchByIDTx(tx, batchID) // 仅用于校验批次是否存在 + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return ErrPigBatchNotFound + } + return fmt.Errorf("获取猪批次 %d 信息失败: %w", batchID, err) + } + + // 2. 业务校验:检查销售数量是否超过当前批次数量 + currentQuantity, err := s.getCurrentPigQuantityTx(tx, batchID) + if err != nil { + return fmt.Errorf("获取猪批次 %d 当前数量失败: %w", batchID, err) + } + + if quantity > currentQuantity { + return fmt.Errorf("销售数量 %d 超过当前批次 %d 数量 %d", quantity, batchID, currentQuantity) + } + + // 3. 记录销售交易 + sale := &models.PigSale{ + PigBatchID: batchID, + SaleDate: tradeDate, + Buyer: traderName, + Quantity: quantity, + UnitPrice: unitPrice, + TotalPrice: tatalPrice, // 总价不一定是单价x数量, 所以要传进来 + Remarks: remarks, + OperatorID: operatorID, + } + if err := s.tradeSvc.SellPig(tx, sale); err != nil { + return fmt.Errorf("记录销售交易失败: %w", err) + } + + // 4. 记录批次日志 + log := &models.PigBatchLog{ + PigBatchID: batchID, + HappenedAt: time.Now(), + ChangeType: models.ChangeTypeSale, + ChangeCount: -quantity, + Reason: fmt.Sprintf("猪批次 %d 销售 %d 头猪给 %s", batchID, quantity, traderName), + BeforeCount: currentQuantity, + AfterCount: currentQuantity - quantity, + OperatorID: operatorID, + } + if err := s.pigBatchLogRepo.Create(tx, log); err != nil { + return fmt.Errorf("记录销售批次日志失败: %w", err) + } + + return nil + }) +} + +// BuyPigs 处理批量购买猪的业务逻辑。 +func (s *pigBatchService) BuyPigs(batchID uint, quantity int, unitPrice float64, totalPrice float64, traderName string, tradeDate time.Time, remarks string, operatorID uint) error { + return s.uow.ExecuteInTransaction(func(tx *gorm.DB) error { + if quantity <= 0 { + return errors.New("采购数量必须大于0") + } + + // 1. 获取猪批次信息 + _, err := s.pigBatchRepo.GetPigBatchByIDTx(tx, batchID) // 仅用于校验批次是否存在 + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return ErrPigBatchNotFound + } + return fmt.Errorf("获取猪批次 %d 信息失败: %w", batchID, err) + } + + // 2. 获取当前猪批次数量 + currentQuantity, err := s.getCurrentPigQuantityTx(tx, batchID) + if err != nil { + return fmt.Errorf("获取猪批次 %d 当前数量失败: %w", batchID, err) + } + + // 3. 记录采购交易 + purchase := &models.PigPurchase{ + PigBatchID: batchID, + PurchaseDate: tradeDate, + Supplier: traderName, + Quantity: quantity, + UnitPrice: unitPrice, + TotalPrice: totalPrice, // 总价不一定是单价x数量, 所以要传进来 + Remarks: remarks, + OperatorID: operatorID, + } + if err := s.tradeSvc.BuyPig(tx, purchase); err != nil { + return fmt.Errorf("记录采购交易失败: %w", err) + } + + // 4. 记录批次日志 + log := &models.PigBatchLog{ + PigBatchID: batchID, + HappenedAt: time.Now(), + ChangeType: models.ChangeTypeBuy, + ChangeCount: quantity, + Reason: fmt.Sprintf("猪批次 %d 采购 %d 头猪从 %s", batchID, quantity, traderName), + BeforeCount: currentQuantity, + AfterCount: currentQuantity + quantity, + OperatorID: operatorID, + } + if err := s.pigBatchLogRepo.Create(tx, log); err != nil { + return fmt.Errorf("记录采购批次日志失败: %w", err) + } + + return nil + }) +} diff --git a/internal/infra/models/pig_batch.go b/internal/infra/models/pig_batch.go index 8357e3e..db17bd3 100644 --- a/internal/infra/models/pig_batch.go +++ b/internal/infra/models/pig_batch.go @@ -57,6 +57,7 @@ const ( ChangeTypeDeath LogChangeType = "死亡" ChangeTypeCull LogChangeType = "淘汰" ChangeTypeSale LogChangeType = "销售" + ChangeTypeBuy LogChangeType = "购买" ChangeTypeTransferIn LogChangeType = "转入" ChangeTypeTransferOut LogChangeType = "转出" ChangeTypeCorrection LogChangeType = "盘点校正" diff --git a/internal/infra/repository/pig_batch_log_repository.go b/internal/infra/repository/pig_batch_log_repository.go index aaed13d..283731f 100644 --- a/internal/infra/repository/pig_batch_log_repository.go +++ b/internal/infra/repository/pig_batch_log_repository.go @@ -1,6 +1,8 @@ package repository import ( + "time" // 引入 time 包 + "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" "gorm.io/gorm" ) @@ -9,6 +11,12 @@ import ( type PigBatchLogRepository interface { // Create 在指定的事务中创建一条新的猪批次日志。 Create(tx *gorm.DB, log *models.PigBatchLog) error + + // GetLogsByBatchIDAndDateRangeTx 在指定的事务中,获取指定批次在特定时间范围内的所有日志记录。 + GetLogsByBatchIDAndDateRangeTx(tx *gorm.DB, batchID uint, startDate, endDate time.Time) ([]*models.PigBatchLog, error) + + // GetLastLogByBatchIDTx 在指定的事务中,获取某批次的最后一条日志记录。 + GetLastLogByBatchIDTx(tx *gorm.DB, batchID uint) (*models.PigBatchLog, error) } // gormPigBatchLogRepository 是 PigBatchLogRepository 的 GORM 实现。 @@ -25,3 +33,23 @@ func NewGormPigBatchLogRepository(db *gorm.DB) PigBatchLogRepository { func (r *gormPigBatchLogRepository) Create(tx *gorm.DB, log *models.PigBatchLog) error { return tx.Create(log).Error } + +// GetLogsByBatchIDAndDateRangeTx 实现了在指定的事务中,获取指定批次在特定时间范围内的所有日志记录的逻辑。 +func (r *gormPigBatchLogRepository) GetLogsByBatchIDAndDateRangeTx(tx *gorm.DB, batchID uint, startDate, endDate time.Time) ([]*models.PigBatchLog, error) { + var logs []*models.PigBatchLog + err := tx.Where("pig_batch_id = ? AND created_at >= ? AND created_at <= ?", batchID, startDate, endDate).Find(&logs).Error + if err != nil { + return nil, err + } + return logs, nil +} + +// GetLastLogByBatchIDTx 实现了在指定的事务中,获取某批次的最后一条日志记录的逻辑。 +func (r *gormPigBatchLogRepository) GetLastLogByBatchIDTx(tx *gorm.DB, batchID uint) (*models.PigBatchLog, error) { + var log models.PigBatchLog + err := tx.Where("pig_batch_id = ?", batchID).Order("id DESC").First(&log).Error + if err != nil { + return nil, err + } + return &log, nil +}