Compare commits

...

8 Commits

Author SHA1 Message Date
3b109d1547 买卖猪要调整猪栏存量 2025-10-06 16:19:48 +08:00
648a790cec 定义病猪子服务 2025-10-06 15:35:20 +08:00
1b026d6106 定义病猪用药两个repo 2025-10-06 15:26:44 +08:00
91e18c432c 提取修改猪群数量逻辑 2025-10-06 15:08:32 +08:00
59b6977367 调整model位置 2025-10-06 14:48:47 +08:00
c49844feea 实现交易管理器主服务入口 2025-10-06 14:32:52 +08:00
448b721af5 调整方法存放位置 2025-10-06 11:58:26 +08:00
759b31bce3 实现交易子服务 2025-10-06 11:42:56 +08:00
23 changed files with 796 additions and 1491 deletions

View File

@@ -26,7 +26,7 @@ type MockDeviceRepository struct {
mock.Mock
}
// Create 模拟 DeviceRepository 的 Create 方法
// CreateTx 模拟 DeviceRepository 的 CreateTx 方法
func (m *MockDeviceRepository) Create(device *models.Device) error {
args := m.Called(device)
return args.Error(0)
@@ -169,7 +169,7 @@ func TestCreateDevice(t *testing.T) {
Properties: controller.Properties(`{"lora_address":"0x1234"}`),
},
mockRepoSetup: func(m *MockDeviceRepository) {
m.On("Create", mock.MatchedBy(func(dev *models.Device) bool {
m.On("CreateTx", mock.MatchedBy(func(dev *models.Device) bool {
// 检查 Name 字段
nameMatch := dev.Name == "主控A"
// 检查 Type 字段
@@ -215,7 +215,7 @@ func TestCreateDevice(t *testing.T) {
Properties: controller.Properties(`{"bus_id":1,"bus_address":10}`),
},
mockRepoSetup: func(m *MockDeviceRepository) {
m.On("Create", mock.Anything).Return(nil).Run(func(args mock.Arguments) {
m.On("CreateTx", mock.Anything).Return(nil).Run(func(args mock.Arguments) {
arg := args.Get(0).(*models.Device)
arg.ID = 2
arg.CreatedAt = time.Now()
@@ -259,7 +259,7 @@ func TestCreateDevice(t *testing.T) {
Type: models.DeviceTypeDevice,
},
mockRepoSetup: func(m *MockDeviceRepository) {
m.On("Create", mock.Anything).Return(errors.New("db error")).Once()
m.On("CreateTx", mock.Anything).Return(errors.New("db error")).Once()
},
expectedStatus: http.StatusOK,
expectedCode: controller.CodeInternalError,
@@ -276,9 +276,9 @@ func TestCreateDevice(t *testing.T) {
Properties: controller.Properties(`{invalid json}`),
},
mockRepoSetup: func(m *MockDeviceRepository) {
// 期望 Create 方法被调用,并返回一个模拟的数据库错误
// 期望 CreateTx 方法被调用,并返回一个模拟的数据库错误
// 这个错误模拟的是数据库层因为 Properties 字段的 JSON 格式无效而拒绝保存
m.On("Create", mock.Anything).Return(errors.New("database error: invalid json format")).Run(func(args mock.Arguments) {
m.On("CreateTx", mock.Anything).Return(errors.New("database error: invalid json format")).Run(func(args mock.Arguments) {
dev := args.Get(0).(*models.Device)
assert.Equal(t, "无效JSON设备", dev.Name)
assert.Equal(t, models.DeviceTypeDevice, dev.Type)

View File

@@ -25,7 +25,7 @@ type MockUserRepository struct {
mock.Mock
}
// Create 模拟 UserRepository 的 Create 方法
// CreateTx 模拟 UserRepository 的 CreateTx 方法
func (m *MockUserRepository) Create(user *models.User) error {
args := m.Called(user)
return args.Error(0)
@@ -90,8 +90,8 @@ func TestCreateUser(t *testing.T) {
Password: "password123",
},
mockRepoSetup: func(m *MockUserRepository) {
// 模拟 Create 成功
m.On("Create", mock.AnythingOfType("*models.User")).Return(nil).Run(func(args mock.Arguments) {
// 模拟 CreateTx 成功
m.On("CreateTx", mock.AnythingOfType("*models.User")).Return(nil).Run(func(args mock.Arguments) {
// 模拟数据库自动填充 ID
userArg := args.Get(0).(*models.User)
userArg.ID = 1 // 设置一个非零的 ID
@@ -114,7 +114,7 @@ func TestCreateUser(t *testing.T) {
Password: "123", // 密码少于6位
},
mockRepoSetup: func(m *MockUserRepository) {
// 不会调用 Create 或 FindByUsername
// 不会调用 CreateTx 或 FindByUsername
},
expectedResponse: map[string]interface{}{
"code": float64(controller.CodeBadRequest),
@@ -128,7 +128,7 @@ func TestCreateUser(t *testing.T) {
Password: "password123",
},
mockRepoSetup: func(m *MockUserRepository) {
// 不会调用 Create 或 FindByUsername
// 不会调用 CreateTx 或 FindByUsername
},
expectedResponse: map[string]interface{}{
"code": float64(controller.CodeBadRequest),
@@ -143,8 +143,8 @@ func TestCreateUser(t *testing.T) {
Password: "password123",
},
mockRepoSetup: func(m *MockUserRepository) {
// 模拟 Create 失败,因为用户名已存在
m.On("Create", mock.AnythingOfType("*models.User")).Return(errors.New("duplicate entry")).Once()
// 模拟 CreateTx 失败,因为用户名已存在
m.On("CreateTx", mock.AnythingOfType("*models.User")).Return(errors.New("duplicate entry")).Once()
// 模拟 FindByUsername 找到用户,确认是用户名重复
m.On("FindByUsername", "existinguser").Return(&models.User{Username: "existinguser"}, nil).Once()
},
@@ -161,8 +161,8 @@ func TestCreateUser(t *testing.T) {
Password: "password123",
},
mockRepoSetup: func(m *MockUserRepository) {
// 模拟 Create 失败,通用数据库错误
m.On("Create", mock.AnythingOfType("*models.User")).Return(errors.New("database error")).Once()
// 模拟 CreateTx 失败,通用数据库错误
m.On("CreateTx", mock.AnythingOfType("*models.User")).Return(errors.New("database error")).Once()
// 模拟 FindByUsername 找不到用户,确认不是用户名重复
m.On("FindByUsername", "db_error_user").Return(nil, gorm.ErrRecordNotFound).Once()
},

View File

@@ -76,13 +76,20 @@ func NewApplication(configPath string) (*Application, error) {
pigBatchLogRepo := repository.NewGormPigBatchLogRepository(storage.GetDB())
pigFarmRepo := repository.NewGormPigFarmRepository(storage.GetDB())
pigPenRepo := repository.NewGormPigPenRepository(storage.GetDB())
pigTransferLogRepo := repository.NewGormPigTransferLogRepository(storage.GetDB())
pigTradeRepo := repository.NewGormPigTradeRepository(storage.GetDB())
pigSickPigLogRepo := repository.NewGormPigSickLogRepository(storage.GetDB())
medicationLogRepo := repository.NewGormMedicationLogRepository(storage.GetDB())
// 初始化事务管理器
unitOfWork := repository.NewGormUnitOfWork(storage.GetDB(), logger)
// 初始化猪群管理领域
penTransferManager := pig.NewPenTransferManager(pigPenRepo)
pigBatchDomain := pig.NewPigBatchService(pigBatchRepo, pigBatchLogRepo, unitOfWork, penTransferManager)
pigPenTransferManager := pig.NewPigPenTransferManager(pigPenRepo, pigTransferLogRepo, pigBatchRepo)
pigTradeManager := pig.NewPigTradeManager(pigTradeRepo)
pigSickManager := pig.NewSickPigManager(pigSickPigLogRepo, medicationLogRepo)
pigBatchDomain := pig.NewPigBatchService(pigBatchRepo, pigBatchLogRepo, unitOfWork,
pigPenTransferManager, pigTradeManager, pigSickManager)
// --- 业务逻辑处理器初始化 ---
pigFarmService := service.NewPigFarmService(pigFarmRepo, pigPenRepo, pigBatchRepo, unitOfWork, logger)

View File

@@ -1,63 +1,119 @@
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"
)
// PenTransferManager 定义了与猪只位置转移相关的底层数据库操作。
// PigPenTransferManager 定义了与猪只位置转移相关的底层数据库操作。
// 它是一个内部服务,被主服务 PigBatchService 调用。
type PenTransferManager interface {
type PigPenTransferManager interface {
// LogTransfer 在数据库中创建一条猪只迁移日志。
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)
}
// penTransferManager 是 PenTransferManager 接口的具体实现。
// pigPenTransferManager 是 PigPenTransferManager 接口的具体实现。
// 它作为调栏管理器,处理底层的数据库交互。
type penTransferManager struct {
penRepo repository.PigPenRepository
type pigPenTransferManager struct {
penRepo repository.PigPenRepository
logRepo repository.PigTransferLogRepository
pigBatchRepo repository.PigBatchRepository
}
// NewPenTransferManager 是 penTransferManager 的构造函数。
func NewPenTransferManager(penRepo repository.PigPenRepository) PenTransferManager {
return &penTransferManager{
penRepo: penRepo,
// NewPigPenTransferManager 是 pigPenTransferManager 的构造函数。
func NewPigPenTransferManager(penRepo repository.PigPenRepository, logRepo repository.PigTransferLogRepository, pigBatchRepo repository.PigBatchRepository) PigPenTransferManager {
return &pigPenTransferManager{
penRepo: penRepo,
logRepo: logRepo,
pigBatchRepo: pigBatchRepo,
}
}
// LogTransfer 实现了在数据库中创建迁移日志的逻辑。
func (s *penTransferManager) LogTransfer(tx *gorm.DB, log *models.PigTransferLog) error {
// 直接使用事务对象创建记录。
return tx.Create(log).Error
func (s *pigPenTransferManager) LogTransfer(tx *gorm.DB, log *models.PigTransferLog) error {
return s.logRepo.CreatePigTransferLog(tx, log)
}
// GetPenByID 实现了获取猪栏信息的逻辑。
// 注意: 此处调用了一个假设存在的方法 GetPenByIDTx。
func (s *penTransferManager) 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)
}
// GetPensByBatchID 实现了获取猪群关联猪栏列表的逻辑。
// 注意: 此处调用了一个假设存在的方法 GetPensByBatchIDTx。
func (s *penTransferManager) 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)
}
// UpdatePenFields 实现了更新猪栏字段的逻辑。
// 注意: 此处调用了一个假设存在的方法 UpdatePenFieldsTx。
func (s *penTransferManager) 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)
}
// 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

@@ -2,8 +2,10 @@ package pig
import (
"errors"
"time"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
)
// --- 业务错误定义 ---
@@ -23,6 +25,8 @@ var (
ErrPenNotFound = errors.New("指定的猪栏不存在")
// ErrPenNotAssociatedWithBatch 表示猪栏未与该批次关联
ErrPenNotAssociatedWithBatch = errors.New("猪栏未与该批次关联")
// ErrInvalidOperation 非法操作
ErrInvalidOperation = errors.New("非法操作")
)
// --- 领域服务接口 ---
@@ -30,29 +34,57 @@ 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, penID uint, quantity int, unitPrice float64, tatalPrice float64, traderName string, tradeDate time.Time, remarks string, operatorID uint) error
// BuyPigs 处理买猪的业务逻辑。
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
}
// pigBatchService 是 PigBatchService 接口的具体实现。
// 它作为猪群领域的主服务,封装了所有业务逻辑。
type pigBatchService struct {
pigBatchRepo repository.PigBatchRepository // 猪批次仓库
pigBatchLogRepo repository.PigBatchLogRepository // 猪批次日志仓库
uow repository.UnitOfWork // 工作单元,用于管理事务
transferSvc PigPenTransferManager // 调栏子服务
tradeSvc PigTradeManager // 交易子服务
sickSvc SickPigManager // 病猪子服务
}
// NewPigBatchService 是 pigBatchService 的构造函数。
// 它通过依赖注入的方式,创建并返回一个 PigBatchService 接口的实例。
func NewPigBatchService(
pigBatchRepo repository.PigBatchRepository,
pigBatchLogRepo repository.PigBatchLogRepository,
uow repository.UnitOfWork,
transferSvc PigPenTransferManager,
tradeSvc PigTradeManager,
sickSvc SickPigManager,
) PigBatchService {
return &pigBatchService{
pigBatchRepo: pigBatchRepo,
pigBatchLogRepo: pigBatchLogRepo,
uow: uow,
transferSvc: transferSvc,
tradeSvc: tradeSvc,
sickSvc: sickSvc,
}
}

View File

@@ -6,38 +6,11 @@ import (
"time"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
"github.com/google/uuid"
"gorm.io/gorm"
)
// --- 领域服务实现 ---
// pigBatchService 是 PigBatchService 接口的具体实现。
// 它作为猪群领域的主服务,封装了所有业务逻辑。
type pigBatchService struct {
pigBatchRepo repository.PigBatchRepository // 猪批次仓库
pigBatchLogRepo repository.PigBatchLogRepository // 猪批次日志仓库
uow repository.UnitOfWork // 工作单元,用于管理事务
transferSvc PenTransferManager // 调栏子服务
}
// NewPigBatchService 是 pigBatchService 的构造函数。
// 它通过依赖注入的方式,创建并返回一个 PigBatchService 接口的实例。
func NewPigBatchService(
pigBatchRepo repository.PigBatchRepository,
pigBatchLogRepo repository.PigBatchLogRepository,
uow repository.UnitOfWork,
transferSvc PenTransferManager,
) PigBatchService {
return &pigBatchService{
pigBatchRepo: pigBatchRepo,
pigBatchLogRepo: pigBatchLogRepo,
uow: uow,
transferSvc: transferSvc,
}
}
// CreatePigBatch 实现了创建猪批次的逻辑,并同时创建初始批次日志。
func (s *pigBatchService) CreatePigBatch(operatorID uint, batch *models.PigBatch) (*models.PigBatch, error) {
// 业务规则可以在这里添加,例如检查批次号是否唯一等
@@ -60,12 +33,12 @@ func (s *pigBatchService) CreatePigBatch(operatorID uint, batch *models.PigBatch
ChangeCount: createdBatch.InitialCount,
Reason: fmt.Sprintf("创建了新的猪批次 %s初始数量 %d", createdBatch.BatchNumber, createdBatch.InitialCount),
BeforeCount: 0, // 初始创建前数量为0
AfterCount: int(createdBatch.InitialCount),
OperatorID: 0, // 假设初始创建没有特定操作员ID或需要从上下文传入
AfterCount: createdBatch.InitialCount,
OperatorID: operatorID,
}
// 3. 记录批次日志
if err := s.pigBatchLogRepo.Create(tx, initialLog); err != nil {
if err := s.pigBatchLogRepo.CreateTx(tx, initialLog); err != nil {
return fmt.Errorf("记录初始批次日志失败: %w", err)
}
@@ -135,7 +108,7 @@ func (s *pigBatchService) ListPigBatches(isActive *bool) ([]*models.PigBatch, er
}
// UpdatePigBatchPens 实现了在事务中更新猪批次关联猪栏的复杂逻辑。
// 它通过调用底层的 PenTransferManager 来执行数据库操作,从而保持了职责的清晰。
// 它通过调用底层的 PigPenTransferManager 来执行数据库操作,从而保持了职责的清晰。
func (s *pigBatchService) UpdatePigBatchPens(batchID uint, desiredPenIDs []uint) error {
// 使用工作单元来确保操作的原子性
return s.uow.ExecuteInTransaction(func(tx *gorm.DB) error {
@@ -234,131 +207,71 @@ func (s *pigBatchService) UpdatePigBatchPens(batchID uint, desiredPenIDs []uint)
})
}
// --- 新增的调栏业务实现 ---
// executeTransferAndLog 是一个私有辅助方法,用于封装创建和记录迁移日志的通用逻辑。
func (s *pigBatchService) executeTransferAndLog(tx *gorm.DB, fromBatchID, toBatchID, fromPenID, toPenID uint, quantity int, transferType models.PigTransferType, operatorID uint, remarks string) error {
// 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,
// 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
}
// 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
return quantity, 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("迁移数量不能为零")
// 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
}
func (s *pigBatchService) UpdatePigBatchQuantity(operatorID uint, batchID uint, changeType models.LogChangeType, changeAmount int, changeReason string, happenedAt time.Time) error {
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
return s.updatePigBatchQuantityTx(tx, operatorID, batchID, changeType, changeAmount, changeReason, happenedAt)
})
}
// 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("源猪群和目标猪群不能相同")
func (s *pigBatchService) updatePigBatchQuantityTx(tx *gorm.DB, operatorID uint, batchID uint, changeType models.LogChangeType, changeAmount int, changeReason string, happenedAt time.Time) error {
lastLog, err := s.pigBatchLogRepo.GetLastLogByBatchIDTx(tx, batchID)
if err != nil {
return err
}
if quantity == 0 {
return errors.New("迁移数量不能为零")
// 检查数量不应该减到小于零
if changeAmount < 0 {
if lastLog.AfterCount+changeAmount < 0 {
return ErrInvalidOperation
}
}
return s.uow.ExecuteInTransaction(func(tx *gorm.DB) error {
// 1. 核心业务规则校验
sourceBatch, err := s.pigBatchRepo.GetPigBatchByID(sourceBatchID)
if err != nil {
return fmt.Errorf("获取源猪群信息失败: %w", err)
}
destBatch, err := s.pigBatchRepo.GetPigBatchByID(destBatchID)
if err != nil {
return fmt.Errorf("获取目标猪群信息失败: %w", err)
}
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. 修改本聚合的数据(猪群总数)
sourceBatch.InitialCount -= int(quantity)
destBatch.InitialCount += int(quantity)
if _, _, err := s.pigBatchRepo.UpdatePigBatch(sourceBatch); err != nil {
return fmt.Errorf("更新源猪群数量失败: %w", err)
}
if _, _, err := s.pigBatchRepo.UpdatePigBatch(destBatch); err != nil {
return fmt.Errorf("更新目标猪群数量失败: %w", err)
}
return nil
})
pigBatchLog := &models.PigBatchLog{
PigBatchID: batchID,
ChangeType: changeType,
ChangeCount: changeAmount,
Reason: changeReason,
BeforeCount: lastLog.AfterCount,
AfterCount: lastLog.AfterCount + changeAmount,
OperatorID: operatorID,
HappenedAt: happenedAt,
}
return s.pigBatchLogRepo.CreateTx(tx, pigBatchLog)
}

View File

@@ -0,0 +1,138 @@
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 {
// 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. 核心业务规则校验
sourceBatch, err := s.pigBatchRepo.GetPigBatchByID(sourceBatchID)
if err != nil {
return fmt.Errorf("获取源猪群信息失败: %w", err)
}
destBatch, err := s.pigBatchRepo.GetPigBatchByID(destBatchID)
if err != nil {
return fmt.Errorf("获取目标猪群信息失败: %w", err)
}
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. 修改本聚合的数据(猪群总数)
sourceBatch.InitialCount -= int(quantity)
destBatch.InitialCount += int(quantity)
if _, _, err := s.pigBatchRepo.UpdatePigBatch(sourceBatch); err != nil {
return fmt.Errorf("更新源猪群数量失败: %w", err)
}
if _, _, err := s.pigBatchRepo.UpdatePigBatch(destBatch); err != nil {
return fmt.Errorf("更新目标猪群数量失败: %w", err)
}
return nil
})
}

View File

@@ -0,0 +1,154 @@
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, 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. 校验猪栏信息
pen, err := s.transferSvc.GetPenByID(tx, penID)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return ErrPenNotFound
}
return fmt.Errorf("获取猪栏 %d 信息失败: %w", penID, err)
}
// 校验猪栏是否属于该批次
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)
}
if quantity > currentPigsInPen {
return fmt.Errorf("销售数量 %d 超过猪栏 %d 当前猪只数 %d", quantity, penID, currentPigsInPen)
}
// 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. 创建猪只转移日志 (物理)
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 销售 %d 头猪给 %s", batchID, penID, quantity, traderName),
tradeDate); err != nil {
return fmt.Errorf("更新猪批次数量失败: %w", err)
}
return nil
})
}
// BuyPigs 处理批量购买猪的业务逻辑。
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. 校验猪栏信息
pen, err := s.transferSvc.GetPenByID(tx, penID)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return ErrPenNotFound
}
return fmt.Errorf("获取猪栏 %d 信息失败: %w", penID, err)
}
// 校验猪栏是否属于该批次
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,
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. 创建猪只转移日志 (物理)
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 采购 %d 头猪从 %s", batchID, penID, quantity, traderName),
tradeDate); err != nil {
return fmt.Errorf("更新猪批次数量失败: %w", err)
}
return nil
})
}

View File

@@ -0,0 +1,28 @@
package pig
import (
"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
)
// SickPigManager 定义了与病猪管理相关的操作接口。
// 这是一个领域服务,负责协调病猪记录、用药等业务逻辑。
type SickPigManager interface {
}
// sickPigManager 是 SickPigManager 接口的具体实现。
// 它依赖于仓库接口来执行数据持久化操作。
type sickPigManager struct {
sickLogRepo repository.PigSickLogRepository
medicationLogRepo repository.MedicationLogRepository
}
// NewSickPigManager 是 sickPigManager 的构造函数。
func NewSickPigManager(
sickLogRepo repository.PigSickLogRepository,
medicationLogRepo repository.MedicationLogRepository,
) SickPigManager {
return &sickPigManager{
sickLogRepo: sickLogRepo,
medicationLogRepo: medicationLogRepo,
}
}

View File

@@ -0,0 +1,46 @@
package pig
import (
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository" // 引入基础设施层的仓库接口
"gorm.io/gorm"
)
// PigTradeManager 定义了与猪只交易相关的操作接口。
// 这是一个领域服务,负责协调业务逻辑。
type PigTradeManager interface {
// SellPig 处理卖猪的业务逻辑,通过仓库接口创建 PigSale 记录。
SellPig(tx *gorm.DB, sale *models.PigSale) error
// BuyPig 处理买猪的业务逻辑,通过仓库接口创建 PigPurchase 记录。
BuyPig(tx *gorm.DB, purchase *models.PigPurchase) error
}
// pigTradeManager 是 PigTradeManager 接口的具体实现。
// 它依赖于 repository.PigTradeRepository 接口来执行数据持久化操作。
type pigTradeManager struct {
tradeRepo repository.PigTradeRepository // 依赖于基础设施层定义的仓库接口
}
// NewPigTradeManager 是 pigTradeManager 的构造函数。
func NewPigTradeManager(tradeRepo repository.PigTradeRepository) PigTradeManager {
return &pigTradeManager{
tradeRepo: tradeRepo,
}
}
// SellPig 实现了卖猪的逻辑。
// 它通过调用 tradeRepo 来持久化销售记录。
func (s *pigTradeManager) SellPig(tx *gorm.DB, sale *models.PigSale) error {
// 在此处可以添加更复杂的卖猪前置校验或业务逻辑
// 例如:检查猪只库存、更新猪只状态等。
return s.tradeRepo.CreatePigSaleTx(tx, sale)
}
// BuyPig 实现了买猪的逻辑。
// 它通过调用 tradeRepo 来持久化采购记录。
func (s *pigTradeManager) BuyPig(tx *gorm.DB, purchase *models.PigPurchase) error {
// 在此处可以添加更复杂的买猪前置校验或业务逻辑
// 例如:检查资金、更新猪只状态等。
return s.tradeRepo.CreatePigPurchaseTx(tx, purchase)
}

View File

@@ -163,12 +163,12 @@ func (ps *PostgresStorage) creatingHyperTable() error {
{models.RawMaterialPurchase{}, "purchase_date"},
{models.RawMaterialStockLog{}, "happened_at"},
{models.FeedUsageRecord{}, "recorded_at"},
{models.GroupMedicationLog{}, "happened_at"},
{models.MedicationLog{}, "happened_at"},
{models.PigBatchLog{}, "happened_at"},
{models.WeighingBatch{}, "weighing_time"},
{models.WeighingRecord{}, "weighing_time"},
{models.PigTransferLog{}, "transfer_time"},
{models.PigBatchSickPigLog{}, "happened_at"},
{models.PigSickLog{}, "happened_at"},
{models.PigPurchase{}, "purchase_date"},
{models.PigSale{}, "sale_date"},
}
@@ -203,12 +203,12 @@ func (ps *PostgresStorage) applyCompressionPolicies() error {
{models.RawMaterialPurchase{}, "raw_material_id"},
{models.RawMaterialStockLog{}, "raw_material_id"},
{models.FeedUsageRecord{}, "pen_id"},
{models.GroupMedicationLog{}, "pig_batch_id"},
{models.MedicationLog{}, "pig_batch_id"},
{models.PigBatchLog{}, "pig_batch_id"},
{models.WeighingBatch{}, "pig_batch_id"},
{models.WeighingRecord{}, "weighing_batch_id"},
{models.PigTransferLog{}, "pig_batch_id"},
{models.PigBatchSickPigLog{}, "pig_batch_id"},
{models.PigSickLog{}, "pig_batch_id"},
{models.PigPurchase{}, "pig_batch_id"},
{models.PigSale{}, "pig_batch_id"},
}

View File

@@ -80,8 +80,8 @@ const (
ReasonTypeHealthCare MedicationReasonType = "保健"
)
// GroupMedicationLog 记录了对整个猪批次的用药情况
type GroupMedicationLog struct {
// MedicationLog 记录了对整个猪批次的用药情况
type MedicationLog struct {
gorm.Model
PigBatchID uint `gorm:"not null;index;comment:关联的猪批次ID"`
MedicationID uint `gorm:"not null;index;comment:关联的药品ID"`
@@ -94,6 +94,6 @@ type GroupMedicationLog struct {
HappenedAt time.Time `gorm:"primaryKey;comment:用药时间"`
}
func (GroupMedicationLog) TableName() string {
return "group_medication_logs"
func (MedicationLog) TableName() string {
return "medication_logs"
}

View File

@@ -42,7 +42,7 @@ func GetAllModels() []interface{} {
&WeighingBatch{},
&WeighingRecord{},
&PigTransferLog{},
&PigBatchSickPigLog{},
&PigSickLog{},
// Pig Buy & Sell
&PigPurchase{},
@@ -58,7 +58,7 @@ func GetAllModels() []interface{} {
// Medication Models
&Medication{},
&GroupMedicationLog{},
&MedicationLog{},
}
}

View File

@@ -57,6 +57,7 @@ const (
ChangeTypeDeath LogChangeType = "死亡"
ChangeTypeCull LogChangeType = "淘汰"
ChangeTypeSale LogChangeType = "销售"
ChangeTypeBuy LogChangeType = "购买"
ChangeTypeTransferIn LogChangeType = "转入"
ChangeTypeTransferOut LogChangeType = "转出"
ChangeTypeCorrection LogChangeType = "盘点校正"
@@ -105,51 +106,3 @@ type WeighingRecord struct {
func (WeighingRecord) TableName() string {
return "weighing_records"
}
// PigBatchSickPigLogChangeType 定义了病猪变化日志的类型
type PigBatchSickPigLogChangeType string
const (
SickPigChangeTypeAdd PigBatchSickPigLogChangeType = "新增" // 新增病猪
SickPigChangeTypeRemove PigBatchSickPigLogChangeType = "移除" // 移除病猪 (康复, 死亡, 转出等)
)
// PigBatchSickPigTreatmentLocation 定义了病猪治疗地点
type PigBatchSickPigTreatmentLocation string
const (
TreatmentLocationOnSite PigBatchSickPigTreatmentLocation = "原地治疗"
TreatmentLocationSickBay PigBatchSickPigTreatmentLocation = "病猪栏治疗"
)
// PigBatchSickPigReasonType 定义了病猪变化的原因类型
type PigBatchSickPigReasonType string
const (
SickPigReasonTypeIllness PigBatchSickPigReasonType = "患病" // 猪只患病
SickPigReasonTypeRecovery PigBatchSickPigReasonType = "康复" // 猪只康复
SickPigReasonTypeDeath PigBatchSickPigReasonType = "死亡" // 猪只死亡
SickPigReasonTypeEliminate PigBatchSickPigReasonType = "淘汰" // 猪只淘汰
SickPigReasonTypeTransferIn PigBatchSickPigReasonType = "转入" // 病猪转入当前批次
SickPigReasonTypeTransferOut PigBatchSickPigReasonType = "转出" // 病猪转出当前批次 (例如转到其他批次或出售)
SickPigReasonTypeOther PigBatchSickPigReasonType = "其他" // 其他原因
)
// PigBatchSickPigLog 记录了猪批次中病猪数量的变化日志
type PigBatchSickPigLog struct {
gorm.Model
PigBatchID uint `gorm:"primaryKey;comment:关联的猪批次ID"`
PenID uint `gorm:"not null;index;comment:所在猪圈ID"`
PigIDs string `gorm:"size:500;comment:涉及的猪只ID列表逗号分隔"`
ChangeType PigBatchSickPigLogChangeType `gorm:"size:20;not null;comment:变化类型 (新增, 移除)"`
ChangeCount int `gorm:"not null;comment:变化数量, 正数表示新增, 负数表示移除"`
Reason PigBatchSickPigReasonType `gorm:"size:20;not null;comment:变化原因 (如: 患病, 康复, 死亡, 转入, 转出, 其他)"`
Remarks string `gorm:"size:255;comment:备注"`
TreatmentLocation PigBatchSickPigTreatmentLocation `gorm:"size:50;comment:治疗地点"`
OperatorID uint `gorm:"comment:操作员ID"`
HappenedAt time.Time `gorm:"primaryKey;comment:事件发生时间"`
}
func (PigBatchSickPigLog) TableName() string {
return "pig_batch_sick_pig_logs"
}

View File

@@ -0,0 +1,48 @@
package models
import (
"time"
"gorm.io/gorm"
)
// PigBatchSickPigTreatmentLocation 定义了病猪治疗地点
type PigBatchSickPigTreatmentLocation string
const (
TreatmentLocationOnSite PigBatchSickPigTreatmentLocation = "原地治疗"
TreatmentLocationSickBay PigBatchSickPigTreatmentLocation = "病猪栏治疗"
)
// PigBatchSickPigReasonType 定义了病猪变化的原因类型
type PigBatchSickPigReasonType string
const (
SickPigReasonTypeIllness PigBatchSickPigReasonType = "患病" // 猪只患病
SickPigReasonTypeRecovery PigBatchSickPigReasonType = "康复" // 猪只康复
SickPigReasonTypeDeath PigBatchSickPigReasonType = "死亡" // 猪只死亡
SickPigReasonTypeEliminate PigBatchSickPigReasonType = "淘汰" // 猪只淘汰
SickPigReasonTypeTransferIn PigBatchSickPigReasonType = "转入" // 病猪转入当前批次
SickPigReasonTypeTransferOut PigBatchSickPigReasonType = "转出" // 病猪转出当前批次 (例如转到其他批次或出售)
SickPigReasonTypeOther PigBatchSickPigReasonType = "其他" // 其他原因
)
// PigSickLog 记录了猪批次中病猪数量的变化日志
type PigSickLog struct {
gorm.Model
PigBatchID uint `gorm:"primaryKey;comment:关联的猪批次ID"`
PenID uint `gorm:"not null;index;comment:所在猪圈ID"`
PigIDs string `gorm:"size:500;comment:涉及的猪只ID列表逗号分隔"`
ChangeCount int `gorm:"not null;comment:变化数量, 正数表示新增, 负数表示移除"`
Reason PigBatchSickPigReasonType `gorm:"size:20;not null;comment:变化原因 (如: 患病, 康复, 死亡, 转入, 转出, 其他)"`
BeforeCount int `gorm:"comment:变化前的数量"`
AfterCount int `gorm:"comment:变化后的数量"`
Remarks string `gorm:"size:255;comment:备注"`
TreatmentLocation PigBatchSickPigTreatmentLocation `gorm:"size:50;comment:治疗地点"`
OperatorID uint `gorm:"comment:操作员ID"`
HappenedAt time.Time `gorm:"primaryKey;comment:事件发生时间"`
}
func (PigSickLog) TableName() string {
return "pig_sick_logs"
}

View File

@@ -96,7 +96,7 @@ func (r *gormExecutionLogRepository) CreateTaskExecutionLogsInBatch(logs []*mode
if len(logs) == 0 {
return nil
}
// GORM 的 Create 传入一个切片指针会执行批量插入。
// GORM 的 CreateTx 传入一个切片指针会执行批量插入。
return r.db.Create(&logs).Error
}

View File

@@ -0,0 +1,26 @@
package repository
import (
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
"gorm.io/gorm"
)
// MedicationLogRepository 定义了与群体用药日志模型相关的数据库操作接口。
type MedicationLogRepository interface {
CreateMedicationLog(log *models.MedicationLog) error
}
// gormMedicationLogRepository 是 MedicationLogRepository 接口的 GORM 实现。
type gormMedicationLogRepository struct {
db *gorm.DB
}
// NewGormMedicationLogRepository 创建一个新的 MedicationLogRepository GORM 实现实例。
func NewGormMedicationLogRepository(db *gorm.DB) MedicationLogRepository {
return &gormMedicationLogRepository{db: db}
}
// CreateMedicationLog 创建一条新的群体用药日志记录
func (r *gormMedicationLogRepository) CreateMedicationLog(log *models.MedicationLog) error {
return r.db.Create(log).Error
}

View File

@@ -1,14 +1,22 @@
package repository
import (
"time" // 引入 time 包
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
"gorm.io/gorm"
)
// PigBatchLogRepository 定义了与猪批次日志相关的数据库操作接口。
type PigBatchLogRepository interface {
// Create 在指定的事务中创建一条新的猪批次日志。
Create(tx *gorm.DB, log *models.PigBatchLog) error
// CreateTx 在指定的事务中创建一条新的猪批次日志。
CreateTx(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 实现。
@@ -22,6 +30,26 @@ func NewGormPigBatchLogRepository(db *gorm.DB) PigBatchLogRepository {
}
// Create 实现了创建猪批次日志的逻辑。
func (r *gormPigBatchLogRepository) Create(tx *gorm.DB, log *models.PigBatchLog) error {
func (r *gormPigBatchLogRepository) CreateTx(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
}

View File

@@ -0,0 +1,26 @@
package repository
import (
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
"gorm.io/gorm"
)
// PigSickLogRepository 定义了与病猪日志模型相关的数据库操作接口。
type PigSickLogRepository interface {
CreatePigSickLog(log *models.PigSickLog) error
}
// gormPigSickLogRepository 是 PigSickLogRepository 接口的 GORM 实现。
type gormPigSickLogRepository struct {
db *gorm.DB
}
// NewGormPigSickLogRepository 创建一个新的 PigSickLogRepository GORM 实现实例。
func NewGormPigSickLogRepository(db *gorm.DB) PigSickLogRepository {
return &gormPigSickLogRepository{db: db}
}
// CreatePigSickLog 创建一条新的病猪日志记录
func (r *gormPigSickLogRepository) CreatePigSickLog(log *models.PigSickLog) error {
return r.db.Create(log).Error
}

View File

@@ -0,0 +1,36 @@
package repository
import (
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
"gorm.io/gorm"
)
// PigTradeRepository 定义了猪只交易数据持久化的接口。
// 领域服务通过此接口与数据层交互,实现解耦。
type PigTradeRepository interface {
// CreatePigSaleTx 在数据库中创建一条猪只销售记录。
CreatePigSaleTx(tx *gorm.DB, sale *models.PigSale) error
// CreatePigPurchaseTx 在数据库中创建一条猪只采购记录。
CreatePigPurchaseTx(tx *gorm.DB, purchase *models.PigPurchase) error
}
// gormPigTradeRepository 是 PigTradeRepository 接口的 GORM 实现。
type gormPigTradeRepository struct {
db *gorm.DB
}
// NewGormPigTradeRepository 创建一个新的 PigTradeRepository GORM 实现实例。
func NewGormPigTradeRepository(db *gorm.DB) PigTradeRepository {
return &gormPigTradeRepository{db: db}
}
// CreatePigSaleTx 实现了在数据库中创建猪只销售记录的逻辑。
func (r *gormPigTradeRepository) CreatePigSaleTx(tx *gorm.DB, sale *models.PigSale) error {
return tx.Create(sale).Error
}
// CreatePigPurchaseTx 实现了在数据库中创建猪只采购记录的逻辑。
func (r *gormPigTradeRepository) CreatePigPurchaseTx(tx *gorm.DB, purchase *models.PigPurchase) error {
return tx.Create(purchase).Error
}

View File

@@ -0,0 +1,39 @@
package repository
import (
"time"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
"gorm.io/gorm"
)
// PigTransferLogRepository 定义了猪只迁移日志数据持久化的接口。
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 实现。
type gormPigTransferLogRepository struct {
db *gorm.DB
}
// NewGormPigTransferLogRepository 创建一个新的 PigTransferLogRepository GORM 实现实例。
func NewGormPigTransferLogRepository(db *gorm.DB) PigTransferLogRepository {
return &gormPigTransferLogRepository{db: db}
}
// CreatePigTransferLog 实现了在数据库中创建猪只迁移日志记录的逻辑。
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
}

File diff suppressed because it is too large Load Diff