买卖猪要调整猪栏存量

This commit is contained in:
2025-10-06 16:19:48 +08:00
parent 648a790cec
commit 3b109d1547
5 changed files with 153 additions and 38 deletions

View File

@@ -85,7 +85,7 @@ func NewApplication(configPath string) (*Application, error) {
unitOfWork := repository.NewGormUnitOfWork(storage.GetDB(), logger) unitOfWork := repository.NewGormUnitOfWork(storage.GetDB(), logger)
// 初始化猪群管理领域 // 初始化猪群管理领域
pigPenTransferManager := pig.NewPigPenTransferManager(pigPenRepo, pigTransferLogRepo) pigPenTransferManager := pig.NewPigPenTransferManager(pigPenRepo, pigTransferLogRepo, pigBatchRepo)
pigTradeManager := pig.NewPigTradeManager(pigTradeRepo) pigTradeManager := pig.NewPigTradeManager(pigTradeRepo)
pigSickManager := pig.NewSickPigManager(pigSickPigLogRepo, medicationLogRepo) pigSickManager := pig.NewSickPigManager(pigSickPigLogRepo, medicationLogRepo)
pigBatchDomain := pig.NewPigBatchService(pigBatchRepo, pigBatchLogRepo, unitOfWork, pigBatchDomain := pig.NewPigBatchService(pigBatchRepo, pigBatchLogRepo, unitOfWork,

View File

@@ -1,6 +1,8 @@
package pig package pig
import ( import (
"errors"
"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"
"gorm.io/gorm" "gorm.io/gorm"
@@ -13,54 +15,105 @@ type PigPenTransferManager interface {
LogTransfer(tx *gorm.DB, log *models.PigTransferLog) error LogTransfer(tx *gorm.DB, log *models.PigTransferLog) error
// GetPenByID 用于获取猪栏的详细信息,供上层服务进行业务校验。 // GetPenByID 用于获取猪栏的详细信息,供上层服务进行业务校验。
// 注意: 此方法依赖于您在 PigPenRepository 中添加对应的 GetPenByIDTx 方法。
GetPenByID(tx *gorm.DB, penID uint) (*models.Pen, error) GetPenByID(tx *gorm.DB, penID uint) (*models.Pen, error)
// GetPensByBatchID 获取一个猪群当前关联的所有猪栏。 // GetPensByBatchID 获取一个猪群当前关联的所有猪栏。
// 注意: 此方法依赖于您在 PigPenRepository 中添加对应的 GetPensByBatchIDTx 方法。
GetPensByBatchID(tx *gorm.DB, batchID uint) ([]*models.Pen, error) GetPensByBatchID(tx *gorm.DB, batchID uint) ([]*models.Pen, error)
// UpdatePenFields 更新一个猪栏的指定字段。 // UpdatePenFields 更新一个猪栏的指定字段。
// 注意: 此方法依赖于您在 PigPenRepository 中添加对应的 UpdatePenFieldsTx 方法。
UpdatePenFields(tx *gorm.DB, penID uint, updates map[string]interface{}) error UpdatePenFields(tx *gorm.DB, penID uint, updates map[string]interface{}) error
// GetCurrentPigsInPen 通过汇总猪只迁移日志,计算给定猪栏中的当前猪只数量。
GetCurrentPigsInPen(tx *gorm.DB, penID uint) (int, error)
} }
// pigPenTransferManager 是 PigPenTransferManager 接口的具体实现。 // pigPenTransferManager 是 PigPenTransferManager 接口的具体实现。
// 它作为调栏管理器,处理底层的数据库交互。 // 它作为调栏管理器,处理底层的数据库交互。
type pigPenTransferManager struct { type pigPenTransferManager struct {
penRepo repository.PigPenRepository penRepo repository.PigPenRepository
logRepo repository.PigTransferLogRepository logRepo repository.PigTransferLogRepository
pigBatchRepo repository.PigBatchRepository
} }
// NewPigPenTransferManager 是 pigPenTransferManager 的构造函数。 // NewPigPenTransferManager 是 pigPenTransferManager 的构造函数。
// 修改构造函数以接收 PigTransferLogRepository 依赖 func NewPigPenTransferManager(penRepo repository.PigPenRepository, logRepo repository.PigTransferLogRepository, pigBatchRepo repository.PigBatchRepository) PigPenTransferManager {
func NewPigPenTransferManager(penRepo repository.PigPenRepository, logRepo repository.PigTransferLogRepository) PigPenTransferManager {
return &pigPenTransferManager{ return &pigPenTransferManager{
penRepo: penRepo, penRepo: penRepo,
logRepo: logRepo, logRepo: logRepo,
pigBatchRepo: pigBatchRepo,
} }
} }
// LogTransfer 实现了在数据库中创建迁移日志的逻辑。 // LogTransfer 实现了在数据库中创建迁移日志的逻辑。
func (s *pigPenTransferManager) LogTransfer(tx *gorm.DB, log *models.PigTransferLog) error { func (s *pigPenTransferManager) LogTransfer(tx *gorm.DB, log *models.PigTransferLog) error {
// 使用新的仓库接口进行操作
return s.logRepo.CreatePigTransferLog(tx, log) return s.logRepo.CreatePigTransferLog(tx, log)
} }
// GetPenByID 实现了获取猪栏信息的逻辑。 // GetPenByID 实现了获取猪栏信息的逻辑。
// 注意: 此处调用了一个假设存在的方法 GetPenByIDTx。
func (s *pigPenTransferManager) GetPenByID(tx *gorm.DB, penID uint) (*models.Pen, error) { func (s *pigPenTransferManager) GetPenByID(tx *gorm.DB, penID uint) (*models.Pen, error) {
return s.penRepo.GetPenByIDTx(tx, penID) return s.penRepo.GetPenByIDTx(tx, penID)
} }
// GetPensByBatchID 实现了获取猪群关联猪栏列表的逻辑。 // GetPensByBatchID 实现了获取猪群关联猪栏列表的逻辑。
// 注意: 此处调用了一个假设存在的方法 GetPensByBatchIDTx。
func (s *pigPenTransferManager) GetPensByBatchID(tx *gorm.DB, batchID uint) ([]*models.Pen, error) { func (s *pigPenTransferManager) GetPensByBatchID(tx *gorm.DB, batchID uint) ([]*models.Pen, error) {
return s.penRepo.GetPensByBatchIDTx(tx, batchID) return s.penRepo.GetPensByBatchIDTx(tx, batchID)
} }
// UpdatePenFields 实现了更新猪栏字段的逻辑。 // UpdatePenFields 实现了更新猪栏字段的逻辑。
// 注意: 此处调用了一个假设存在的方法 UpdatePenFieldsTx。
func (s *pigPenTransferManager) UpdatePenFields(tx *gorm.DB, penID uint, updates map[string]interface{}) error { func (s *pigPenTransferManager) UpdatePenFields(tx *gorm.DB, penID uint, updates map[string]interface{}) error {
return s.penRepo.UpdatePenFieldsTx(tx, penID, updates) return s.penRepo.UpdatePenFieldsTx(tx, penID, updates)
} }
// GetCurrentPigsInPen 实现了计算猪栏当前猪只数量的逻辑。
func (s *pigPenTransferManager) GetCurrentPigsInPen(tx *gorm.DB, penID uint) (int, error) {
// 1. 通过猪栏ID查出所属猪群信息
pen, err := s.penRepo.GetPenByIDTx(tx, penID)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return 0, ErrPenNotFound
}
return 0, err
}
// 如果猪栏没有关联任何猪群那么猪只数必为0
if pen.PigBatchID == nil || *pen.PigBatchID == 0 {
return 0, nil
}
currentBatchID := *pen.PigBatchID
// 2. 根据猪群ID获取猪群的起始日期
batch, err := s.pigBatchRepo.GetPigBatchByIDTx(tx, currentBatchID)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return 0, ErrPigBatchNotFound
}
return 0, err
}
batchStartDate := batch.StartDate
// 3. 调用仓库方法,获取从猪群开始至今,该猪栏的所有倒序日志
logs, err := s.logRepo.GetLogsForPenSince(tx, penID, batchStartDate)
if err != nil {
return 0, err
}
// 如果没有日志猪只数为0
if len(logs) == 0 {
return 0, nil
}
// 4. 在内存中筛选出最后一段连续日志,并进行计算
var totalPigs int
// 再次确认当前猪群ID以最新的日志为准防止在极小时间窗口内猪栏被快速切换
latestBatchID := *pen.PigBatchID
for _, log := range logs {
// 一旦发现日志不属于最新的猪群,立即停止计算
if log.PigBatchID != latestBatchID {
break
}
totalPigs += log.Quantity
}
return totalPigs, nil
}

View File

@@ -51,9 +51,9 @@ type PigBatchService interface {
GetCurrentPigQuantity(batchID uint) (int, error) GetCurrentPigQuantity(batchID uint) (int, error)
// SellPigs 处理卖猪的业务逻辑。 // SellPigs 处理卖猪的业务逻辑。
SellPigs(batchID 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, 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
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
} }

View File

@@ -10,32 +10,37 @@ import (
) )
// SellPigs 处理批量销售猪的业务逻辑。 // SellPigs 处理批量销售猪的业务逻辑。
func (s *pigBatchService) SellPigs(batchID uint, quantity int, unitPrice float64, tatalPrice float64, traderName string, tradeDate time.Time, remarks string, operatorID uint) error { func (s *pigBatchService) SellPigs(batchID uint, penID 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 { return s.uow.ExecuteInTransaction(func(tx *gorm.DB) error {
if quantity <= 0 { if quantity <= 0 {
return errors.New("销售数量必须大于0") return errors.New("销售数量必须大于0")
} }
// 1. 获取猪批次信息 // 1. 校验猪栏信息
_, err := s.pigBatchRepo.GetPigBatchByIDTx(tx, batchID) // 仅用于校验批次是否存在 pen, err := s.transferSvc.GetPenByID(tx, penID)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
return ErrPigBatchNotFound return ErrPenNotFound
} }
return fmt.Errorf("获取猪批次 %d 信息失败: %w", batchID, err) return fmt.Errorf("获取猪 %d 信息失败: %w", penID, err)
} }
// 2. 业务校验:检查销售数量是否超过当前批次数量 // 校验猪栏是否属于该批次
currentQuantity, err := s.getCurrentPigQuantityTx(tx, batchID) if pen.PigBatchID == nil || *pen.PigBatchID != batchID {
return ErrPenNotAssociatedWithBatch
}
// 2. 业务校验:检查销售数量是否超过猪栏当前猪只数
currentPigsInPen, err := s.transferSvc.GetCurrentPigsInPen(tx, penID)
if err != nil { if err != nil {
return fmt.Errorf("获取猪批次 %d 当前数失败: %w", batchID, err) return fmt.Errorf("获取猪 %d 当前猪只数失败: %w", penID, err)
} }
if quantity > currentQuantity { if quantity > currentPigsInPen {
return fmt.Errorf("销售数量 %d 超过当前批次 %d 数量 %d", quantity, batchID, currentQuantity) return fmt.Errorf("销售数量 %d 超过猪栏 %d 当前猪只数 %d", quantity, penID, currentPigsInPen)
} }
// 3. 记录销售交易 // 3. 记录销售交易 (财务)
sale := &models.PigSale{ sale := &models.PigSale{
PigBatchID: batchID, PigBatchID: batchID,
SaleDate: tradeDate, SaleDate: tradeDate,
@@ -50,9 +55,23 @@ func (s *pigBatchService) SellPigs(batchID uint, quantity int, unitPrice float64
return fmt.Errorf("记录销售交易失败: %w", err) return fmt.Errorf("记录销售交易失败: %w", err)
} }
// 4. 记录批次日志 // 4. 创建猪只转移日志 (物理)
transferLog := &models.PigTransferLog{
TransferTime: tradeDate,
PigBatchID: batchID,
PenID: penID,
Quantity: -quantity, // 销售导致数量减少
Type: models.PigTransferTypeSale,
OperatorID: operatorID,
Remarks: fmt.Sprintf("销售给 %s", traderName),
}
if err := s.transferSvc.LogTransfer(tx, transferLog); err != nil {
return fmt.Errorf("创建猪只转移日志失败: %w", err)
}
// 5. 记录批次数量变更日志 (逻辑)
if err := s.updatePigBatchQuantityTx(tx, operatorID, batchID, models.ChangeTypeSale, -quantity, if err := s.updatePigBatchQuantityTx(tx, operatorID, batchID, models.ChangeTypeSale, -quantity,
fmt.Sprintf("猪批次 %d 销售 %d 头猪给 %s", batchID, quantity, traderName), fmt.Sprintf("猪批次 %d 从猪栏 %d 销售 %d 头猪给 %s", batchID, penID, quantity, traderName),
tradeDate); err != nil { tradeDate); err != nil {
return fmt.Errorf("更新猪批次数量失败: %w", err) return fmt.Errorf("更新猪批次数量失败: %w", err)
} }
@@ -62,22 +81,39 @@ func (s *pigBatchService) SellPigs(batchID uint, quantity int, unitPrice float64
} }
// BuyPigs 处理批量购买猪的业务逻辑。 // BuyPigs 处理批量购买猪的业务逻辑。
func (s *pigBatchService) BuyPigs(batchID uint, quantity int, unitPrice float64, totalPrice float64, traderName string, tradeDate time.Time, remarks string, operatorID uint) error { func (s *pigBatchService) BuyPigs(batchID uint, penID 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 { return s.uow.ExecuteInTransaction(func(tx *gorm.DB) error {
if quantity <= 0 { if quantity <= 0 {
return errors.New("采购数量必须大于0") return errors.New("采购数量必须大于0")
} }
// 1. 获取猪批次信息 // 1. 校验猪栏信息
_, err := s.pigBatchRepo.GetPigBatchByIDTx(tx, batchID) // 仅用于校验批次是否存在 pen, err := s.transferSvc.GetPenByID(tx, penID)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
return ErrPigBatchNotFound return ErrPenNotFound
} }
return fmt.Errorf("获取猪批次 %d 信息失败: %w", batchID, err) return fmt.Errorf("获取猪 %d 信息失败: %w", penID, err)
} }
// 3. 记录采购交易 // 校验猪栏是否属于该批次
if pen.PigBatchID == nil || *pen.PigBatchID != batchID {
return ErrPenNotAssociatedWithBatch
}
// 2. 业务校验:检查猪栏容量,如果超出,在备注中记录警告
currentPigsInPen, err := s.transferSvc.GetCurrentPigsInPen(tx, penID)
if err != nil {
return fmt.Errorf("获取猪栏 %d 当前猪只数失败: %w", penID, err)
}
transferRemarks := fmt.Sprintf("从 %s 采购", traderName)
if currentPigsInPen+quantity > pen.Capacity {
warning := fmt.Sprintf("[警告]猪栏容量超出: 当前 %d, 采购 %d, 容量 %d.", currentPigsInPen, quantity, pen.Capacity)
transferRemarks = fmt.Sprintf("%s %s", transferRemarks, warning)
}
// 3. 记录采购交易 (财务)
purchase := &models.PigPurchase{ purchase := &models.PigPurchase{
PigBatchID: batchID, PigBatchID: batchID,
PurchaseDate: tradeDate, PurchaseDate: tradeDate,
@@ -85,16 +121,30 @@ func (s *pigBatchService) BuyPigs(batchID uint, quantity int, unitPrice float64,
Quantity: quantity, Quantity: quantity,
UnitPrice: unitPrice, UnitPrice: unitPrice,
TotalPrice: totalPrice, // 总价不一定是单价x数量, 所以要传进来 TotalPrice: totalPrice, // 总价不一定是单价x数量, 所以要传进来
Remarks: remarks, Remarks: remarks, // 用户传入的备注
OperatorID: operatorID, OperatorID: operatorID,
} }
if err := s.tradeSvc.BuyPig(tx, purchase); err != nil { if err := s.tradeSvc.BuyPig(tx, purchase); err != nil {
return fmt.Errorf("记录采购交易失败: %w", err) return fmt.Errorf("记录采购交易失败: %w", err)
} }
// 4. 记录批次日志 // 4. 创建猪只转移日志 (物理)
transferLog := &models.PigTransferLog{
TransferTime: tradeDate,
PigBatchID: batchID,
PenID: penID,
Quantity: quantity, // 采购导致数量增加
Type: models.PigTransferTypePurchase,
OperatorID: operatorID,
Remarks: transferRemarks, // 包含系统生成的备注和潜在的警告
}
if err := s.transferSvc.LogTransfer(tx, transferLog); err != nil {
return fmt.Errorf("创建猪只转移日志失败: %w", err)
}
// 5. 记录批次数量变更日志 (逻辑)
if err := s.updatePigBatchQuantityTx(tx, operatorID, batchID, models.ChangeTypeBuy, quantity, if err := s.updatePigBatchQuantityTx(tx, operatorID, batchID, models.ChangeTypeBuy, quantity,
fmt.Sprintf("猪批次 %d 采购 %d 头猪从 %s", batchID, quantity, traderName), fmt.Sprintf("猪批次 %d 在猪栏 %d 采购 %d 头猪从 %s", batchID, penID, quantity, traderName),
tradeDate); err != nil { tradeDate); err != nil {
return fmt.Errorf("更新猪批次数量失败: %w", err) return fmt.Errorf("更新猪批次数量失败: %w", err)
} }

View File

@@ -1,6 +1,8 @@
package repository package repository
import ( import (
"time"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models" "git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
"gorm.io/gorm" "gorm.io/gorm"
) )
@@ -9,6 +11,9 @@ import (
type PigTransferLogRepository interface { type PigTransferLogRepository interface {
// CreatePigTransferLog 在数据库中创建一条猪只迁移日志记录。 // CreatePigTransferLog 在数据库中创建一条猪只迁移日志记录。
CreatePigTransferLog(tx *gorm.DB, log *models.PigTransferLog) error CreatePigTransferLog(tx *gorm.DB, log *models.PigTransferLog) error
// GetLogsForPenSince 获取指定猪栏自特定时间点以来的所有迁移日志,按时间倒序排列。
GetLogsForPenSince(tx *gorm.DB, penID uint, since time.Time) ([]*models.PigTransferLog, error)
} }
// gormPigTransferLogRepository 是 PigTransferLogRepository 接口的 GORM 实现。 // gormPigTransferLogRepository 是 PigTransferLogRepository 接口的 GORM 实现。
@@ -25,3 +30,10 @@ func NewGormPigTransferLogRepository(db *gorm.DB) PigTransferLogRepository {
func (r *gormPigTransferLogRepository) CreatePigTransferLog(tx *gorm.DB, log *models.PigTransferLog) error { func (r *gormPigTransferLogRepository) CreatePigTransferLog(tx *gorm.DB, log *models.PigTransferLog) error {
return tx.Create(log).Error return tx.Create(log).Error
} }
// GetLogsForPenSince 实现了获取猪栏自特定时间点以来所有迁移日志的逻辑。
func (r *gormPigTransferLogRepository) GetLogsForPenSince(tx *gorm.DB, penID uint, since time.Time) ([]*models.PigTransferLog, error) {
var logs []*models.PigTransferLog
err := tx.Where("pen_id = ? AND transfer_time >= ?", penID, since).Order("transfer_time DESC").Find(&logs).Error
return logs, err
}