Compare commits
	
		
			8 Commits
		
	
	
		
			c76c976cc8
			...
			3b109d1547
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 3b109d1547 | |||
| 648a790cec | |||
| 1b026d6106 | |||
| 91e18c432c | |||
| 59b6977367 | |||
| c49844feea | |||
| 448b721af5 | |||
| 759b31bce3 | 
@@ -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)
 | 
			
		||||
 
 | 
			
		||||
@@ -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()
 | 
			
		||||
			},
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										138
									
								
								internal/domain/pig/pig_batch_service_pen_transfer.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										138
									
								
								internal/domain/pig/pig_batch_service_pen_transfer.go
									
									
									
									
									
										Normal 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
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										154
									
								
								internal/domain/pig/pig_batch_service_pig_trade.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										154
									
								
								internal/domain/pig/pig_batch_service_pig_trade.go
									
									
									
									
									
										Normal 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
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										28
									
								
								internal/domain/pig/pig_sick_manager.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								internal/domain/pig/pig_sick_manager.go
									
									
									
									
									
										Normal 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,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										46
									
								
								internal/domain/pig/pig_trade_manager.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								internal/domain/pig/pig_trade_manager.go
									
									
									
									
									
										Normal 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)
 | 
			
		||||
}
 | 
			
		||||
@@ -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"},
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -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"
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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{},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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"
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										48
									
								
								internal/infra/models/pig_sick.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								internal/infra/models/pig_sick.go
									
									
									
									
									
										Normal 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"
 | 
			
		||||
}
 | 
			
		||||
@@ -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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										26
									
								
								internal/infra/repository/group_medication_log_repository.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								internal/infra/repository/group_medication_log_repository.go
									
									
									
									
									
										Normal 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
 | 
			
		||||
}
 | 
			
		||||
@@ -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
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										26
									
								
								internal/infra/repository/pig_sick_repository.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								internal/infra/repository/pig_sick_repository.go
									
									
									
									
									
										Normal 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
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										36
									
								
								internal/infra/repository/pig_trade_repository.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								internal/infra/repository/pig_trade_repository.go
									
									
									
									
									
										Normal 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
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										39
									
								
								internal/infra/repository/pig_transfer_log_repository.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								internal/infra/repository/pig_transfer_log_repository.go
									
									
									
									
									
										Normal 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
											
										
									
								
							
		Reference in New Issue
	
	Block a user