买卖猪要调整猪栏存量
This commit is contained in:
@@ -85,7 +85,7 @@ func NewApplication(configPath string) (*Application, error) {
|
||||
unitOfWork := repository.NewGormUnitOfWork(storage.GetDB(), logger)
|
||||
|
||||
// 初始化猪群管理领域
|
||||
pigPenTransferManager := pig.NewPigPenTransferManager(pigPenRepo, pigTransferLogRepo)
|
||||
pigPenTransferManager := pig.NewPigPenTransferManager(pigPenRepo, pigTransferLogRepo, pigBatchRepo)
|
||||
pigTradeManager := pig.NewPigTradeManager(pigTradeRepo)
|
||||
pigSickManager := pig.NewSickPigManager(pigSickPigLogRepo, medicationLogRepo)
|
||||
pigBatchDomain := pig.NewPigBatchService(pigBatchRepo, pigBatchLogRepo, unitOfWork,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package pig
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
|
||||
"gorm.io/gorm"
|
||||
@@ -13,54 +15,105 @@ type PigPenTransferManager interface {
|
||||
LogTransfer(tx *gorm.DB, log *models.PigTransferLog) error
|
||||
|
||||
// GetPenByID 用于获取猪栏的详细信息,供上层服务进行业务校验。
|
||||
// 注意: 此方法依赖于您在 PigPenRepository 中添加对应的 GetPenByIDTx 方法。
|
||||
GetPenByID(tx *gorm.DB, penID uint) (*models.Pen, error)
|
||||
|
||||
// GetPensByBatchID 获取一个猪群当前关联的所有猪栏。
|
||||
// 注意: 此方法依赖于您在 PigPenRepository 中添加对应的 GetPensByBatchIDTx 方法。
|
||||
GetPensByBatchID(tx *gorm.DB, batchID uint) ([]*models.Pen, error)
|
||||
|
||||
// UpdatePenFields 更新一个猪栏的指定字段。
|
||||
// 注意: 此方法依赖于您在 PigPenRepository 中添加对应的 UpdatePenFieldsTx 方法。
|
||||
UpdatePenFields(tx *gorm.DB, penID uint, updates map[string]interface{}) error
|
||||
|
||||
// GetCurrentPigsInPen 通过汇总猪只迁移日志,计算给定猪栏中的当前猪只数量。
|
||||
GetCurrentPigsInPen(tx *gorm.DB, penID uint) (int, error)
|
||||
}
|
||||
|
||||
// pigPenTransferManager 是 PigPenTransferManager 接口的具体实现。
|
||||
// 它作为调栏管理器,处理底层的数据库交互。
|
||||
type pigPenTransferManager struct {
|
||||
penRepo repository.PigPenRepository
|
||||
logRepo repository.PigTransferLogRepository
|
||||
penRepo repository.PigPenRepository
|
||||
logRepo repository.PigTransferLogRepository
|
||||
pigBatchRepo repository.PigBatchRepository
|
||||
}
|
||||
|
||||
// NewPigPenTransferManager 是 pigPenTransferManager 的构造函数。
|
||||
// 修改构造函数以接收 PigTransferLogRepository 依赖
|
||||
func NewPigPenTransferManager(penRepo repository.PigPenRepository, logRepo repository.PigTransferLogRepository) PigPenTransferManager {
|
||||
func NewPigPenTransferManager(penRepo repository.PigPenRepository, logRepo repository.PigTransferLogRepository, pigBatchRepo repository.PigBatchRepository) PigPenTransferManager {
|
||||
return &pigPenTransferManager{
|
||||
penRepo: penRepo,
|
||||
logRepo: logRepo,
|
||||
penRepo: penRepo,
|
||||
logRepo: logRepo,
|
||||
pigBatchRepo: pigBatchRepo,
|
||||
}
|
||||
}
|
||||
|
||||
// LogTransfer 实现了在数据库中创建迁移日志的逻辑。
|
||||
func (s *pigPenTransferManager) LogTransfer(tx *gorm.DB, log *models.PigTransferLog) error {
|
||||
// 使用新的仓库接口进行操作
|
||||
return s.logRepo.CreatePigTransferLog(tx, log)
|
||||
}
|
||||
|
||||
// GetPenByID 实现了获取猪栏信息的逻辑。
|
||||
// 注意: 此处调用了一个假设存在的方法 GetPenByIDTx。
|
||||
func (s *pigPenTransferManager) GetPenByID(tx *gorm.DB, penID uint) (*models.Pen, error) {
|
||||
return s.penRepo.GetPenByIDTx(tx, penID)
|
||||
}
|
||||
|
||||
// GetPensByBatchID 实现了获取猪群关联猪栏列表的逻辑。
|
||||
// 注意: 此处调用了一个假设存在的方法 GetPensByBatchIDTx。
|
||||
func (s *pigPenTransferManager) GetPensByBatchID(tx *gorm.DB, batchID uint) ([]*models.Pen, error) {
|
||||
return s.penRepo.GetPensByBatchIDTx(tx, batchID)
|
||||
}
|
||||
|
||||
// UpdatePenFields 实现了更新猪栏字段的逻辑。
|
||||
// 注意: 此处调用了一个假设存在的方法 UpdatePenFieldsTx。
|
||||
func (s *pigPenTransferManager) UpdatePenFields(tx *gorm.DB, penID uint, updates map[string]interface{}) error {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -51,9 +51,9 @@ type PigBatchService interface {
|
||||
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
|
||||
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, 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
|
||||
}
|
||||
|
||||
@@ -10,32 +10,37 @@ import (
|
||||
)
|
||||
|
||||
// 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 {
|
||||
if quantity <= 0 {
|
||||
return errors.New("销售数量必须大于0")
|
||||
}
|
||||
|
||||
// 1. 获取猪批次信息
|
||||
_, err := s.pigBatchRepo.GetPigBatchByIDTx(tx, batchID) // 仅用于校验批次是否存在
|
||||
// 1. 校验猪栏信息
|
||||
pen, err := s.transferSvc.GetPenByID(tx, penID)
|
||||
if err != nil {
|
||||
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 {
|
||||
return fmt.Errorf("获取猪批次 %d 当前数量失败: %w", batchID, err)
|
||||
return fmt.Errorf("获取猪栏 %d 当前猪只数失败: %w", penID, err)
|
||||
}
|
||||
|
||||
if quantity > currentQuantity {
|
||||
return fmt.Errorf("销售数量 %d 超过当前批次 %d 数量 %d", quantity, batchID, currentQuantity)
|
||||
if quantity > currentPigsInPen {
|
||||
return fmt.Errorf("销售数量 %d 超过猪栏 %d 当前猪只数 %d", quantity, penID, currentPigsInPen)
|
||||
}
|
||||
|
||||
// 3. 记录销售交易
|
||||
// 3. 记录销售交易 (财务)
|
||||
sale := &models.PigSale{
|
||||
PigBatchID: batchID,
|
||||
SaleDate: tradeDate,
|
||||
@@ -50,9 +55,23 @@ func (s *pigBatchService) SellPigs(batchID uint, quantity int, unitPrice float64
|
||||
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,
|
||||
fmt.Sprintf("猪批次 %d 销售 %d 头猪给 %s", batchID, quantity, traderName),
|
||||
fmt.Sprintf("猪批次 %d 从猪栏 %d 销售 %d 头猪给 %s", batchID, penID, quantity, traderName),
|
||||
tradeDate); err != nil {
|
||||
return fmt.Errorf("更新猪批次数量失败: %w", err)
|
||||
}
|
||||
@@ -62,22 +81,39 @@ func (s *pigBatchService) SellPigs(batchID uint, quantity int, unitPrice float64
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if quantity <= 0 {
|
||||
return errors.New("采购数量必须大于0")
|
||||
}
|
||||
|
||||
// 1. 获取猪批次信息
|
||||
_, err := s.pigBatchRepo.GetPigBatchByIDTx(tx, batchID) // 仅用于校验批次是否存在
|
||||
// 1. 校验猪栏信息
|
||||
pen, err := s.transferSvc.GetPenByID(tx, penID)
|
||||
if err != nil {
|
||||
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{
|
||||
PigBatchID: batchID,
|
||||
PurchaseDate: tradeDate,
|
||||
@@ -85,16 +121,30 @@ func (s *pigBatchService) BuyPigs(batchID uint, quantity int, unitPrice float64,
|
||||
Quantity: quantity,
|
||||
UnitPrice: unitPrice,
|
||||
TotalPrice: totalPrice, // 总价不一定是单价x数量, 所以要传进来
|
||||
Remarks: remarks,
|
||||
Remarks: remarks, // 用户传入的备注
|
||||
OperatorID: operatorID,
|
||||
}
|
||||
if err := s.tradeSvc.BuyPig(tx, purchase); err != nil {
|
||||
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,
|
||||
fmt.Sprintf("猪批次 %d 采购 %d 头猪从 %s", batchID, quantity, traderName),
|
||||
fmt.Sprintf("猪批次 %d 在猪栏 %d 采购 %d 头猪从 %s", batchID, penID, quantity, traderName),
|
||||
tradeDate); err != nil {
|
||||
return fmt.Errorf("更新猪批次数量失败: %w", err)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
@@ -9,6 +11,9 @@ import (
|
||||
type PigTransferLogRepository interface {
|
||||
// CreatePigTransferLog 在数据库中创建一条猪只迁移日志记录。
|
||||
CreatePigTransferLog(tx *gorm.DB, log *models.PigTransferLog) error
|
||||
|
||||
// GetLogsForPenSince 获取指定猪栏自特定时间点以来的所有迁移日志,按时间倒序排列。
|
||||
GetLogsForPenSince(tx *gorm.DB, penID uint, since time.Time) ([]*models.PigTransferLog, error)
|
||||
}
|
||||
|
||||
// gormPigTransferLogRepository 是 PigTransferLogRepository 接口的 GORM 实现。
|
||||
@@ -25,3 +30,10 @@ func NewGormPigTransferLogRepository(db *gorm.DB) PigTransferLogRepository {
|
||||
func (r *gormPigTransferLogRepository) CreatePigTransferLog(tx *gorm.DB, log *models.PigTransferLog) 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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user