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
 | 
						mock.Mock
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Create 模拟 DeviceRepository 的 Create 方法
 | 
					// CreateTx 模拟 DeviceRepository 的 CreateTx 方法
 | 
				
			||||||
func (m *MockDeviceRepository) Create(device *models.Device) error {
 | 
					func (m *MockDeviceRepository) Create(device *models.Device) error {
 | 
				
			||||||
	args := m.Called(device)
 | 
						args := m.Called(device)
 | 
				
			||||||
	return args.Error(0)
 | 
						return args.Error(0)
 | 
				
			||||||
@@ -169,7 +169,7 @@ func TestCreateDevice(t *testing.T) {
 | 
				
			|||||||
				Properties: controller.Properties(`{"lora_address":"0x1234"}`),
 | 
									Properties: controller.Properties(`{"lora_address":"0x1234"}`),
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			mockRepoSetup: func(m *MockDeviceRepository) {
 | 
								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 字段
 | 
										// 检查 Name 字段
 | 
				
			||||||
					nameMatch := dev.Name == "主控A"
 | 
										nameMatch := dev.Name == "主控A"
 | 
				
			||||||
					// 检查 Type 字段
 | 
										// 检查 Type 字段
 | 
				
			||||||
@@ -215,7 +215,7 @@ func TestCreateDevice(t *testing.T) {
 | 
				
			|||||||
				Properties: controller.Properties(`{"bus_id":1,"bus_address":10}`),
 | 
									Properties: controller.Properties(`{"bus_id":1,"bus_address":10}`),
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			mockRepoSetup: func(m *MockDeviceRepository) {
 | 
								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 := args.Get(0).(*models.Device)
 | 
				
			||||||
					arg.ID = 2
 | 
										arg.ID = 2
 | 
				
			||||||
					arg.CreatedAt = time.Now()
 | 
										arg.CreatedAt = time.Now()
 | 
				
			||||||
@@ -259,7 +259,7 @@ func TestCreateDevice(t *testing.T) {
 | 
				
			|||||||
				Type: models.DeviceTypeDevice,
 | 
									Type: models.DeviceTypeDevice,
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			mockRepoSetup: func(m *MockDeviceRepository) {
 | 
								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,
 | 
								expectedStatus:   http.StatusOK,
 | 
				
			||||||
			expectedCode:     controller.CodeInternalError,
 | 
								expectedCode:     controller.CodeInternalError,
 | 
				
			||||||
@@ -276,9 +276,9 @@ func TestCreateDevice(t *testing.T) {
 | 
				
			|||||||
				Properties: controller.Properties(`{invalid json}`),
 | 
									Properties: controller.Properties(`{invalid json}`),
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			mockRepoSetup: func(m *MockDeviceRepository) {
 | 
								mockRepoSetup: func(m *MockDeviceRepository) {
 | 
				
			||||||
				// 期望 Create 方法被调用,并返回一个模拟的数据库错误
 | 
									// 期望 CreateTx 方法被调用,并返回一个模拟的数据库错误
 | 
				
			||||||
				// 这个错误模拟的是数据库层因为 Properties 字段的 JSON 格式无效而拒绝保存
 | 
									// 这个错误模拟的是数据库层因为 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)
 | 
										dev := args.Get(0).(*models.Device)
 | 
				
			||||||
					assert.Equal(t, "无效JSON设备", dev.Name)
 | 
										assert.Equal(t, "无效JSON设备", dev.Name)
 | 
				
			||||||
					assert.Equal(t, models.DeviceTypeDevice, dev.Type)
 | 
										assert.Equal(t, models.DeviceTypeDevice, dev.Type)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -25,7 +25,7 @@ type MockUserRepository struct {
 | 
				
			|||||||
	mock.Mock
 | 
						mock.Mock
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Create 模拟 UserRepository 的 Create 方法
 | 
					// CreateTx 模拟 UserRepository 的 CreateTx 方法
 | 
				
			||||||
func (m *MockUserRepository) Create(user *models.User) error {
 | 
					func (m *MockUserRepository) Create(user *models.User) error {
 | 
				
			||||||
	args := m.Called(user)
 | 
						args := m.Called(user)
 | 
				
			||||||
	return args.Error(0)
 | 
						return args.Error(0)
 | 
				
			||||||
@@ -90,8 +90,8 @@ func TestCreateUser(t *testing.T) {
 | 
				
			|||||||
				Password: "password123",
 | 
									Password: "password123",
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			mockRepoSetup: func(m *MockUserRepository) {
 | 
								mockRepoSetup: func(m *MockUserRepository) {
 | 
				
			||||||
				// 模拟 Create 成功
 | 
									// 模拟 CreateTx 成功
 | 
				
			||||||
				m.On("Create", mock.AnythingOfType("*models.User")).Return(nil).Run(func(args mock.Arguments) {
 | 
									m.On("CreateTx", mock.AnythingOfType("*models.User")).Return(nil).Run(func(args mock.Arguments) {
 | 
				
			||||||
					// 模拟数据库自动填充 ID
 | 
										// 模拟数据库自动填充 ID
 | 
				
			||||||
					userArg := args.Get(0).(*models.User)
 | 
										userArg := args.Get(0).(*models.User)
 | 
				
			||||||
					userArg.ID = 1 // 设置一个非零的 ID
 | 
										userArg.ID = 1 // 设置一个非零的 ID
 | 
				
			||||||
@@ -114,7 +114,7 @@ func TestCreateUser(t *testing.T) {
 | 
				
			|||||||
				Password: "123", // 密码少于6位
 | 
									Password: "123", // 密码少于6位
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			mockRepoSetup: func(m *MockUserRepository) {
 | 
								mockRepoSetup: func(m *MockUserRepository) {
 | 
				
			||||||
				// 不会调用 Create 或 FindByUsername
 | 
									// 不会调用 CreateTx 或 FindByUsername
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			expectedResponse: map[string]interface{}{
 | 
								expectedResponse: map[string]interface{}{
 | 
				
			||||||
				"code":    float64(controller.CodeBadRequest),
 | 
									"code":    float64(controller.CodeBadRequest),
 | 
				
			||||||
@@ -128,7 +128,7 @@ func TestCreateUser(t *testing.T) {
 | 
				
			|||||||
				Password: "password123",
 | 
									Password: "password123",
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			mockRepoSetup: func(m *MockUserRepository) {
 | 
								mockRepoSetup: func(m *MockUserRepository) {
 | 
				
			||||||
				// 不会调用 Create 或 FindByUsername
 | 
									// 不会调用 CreateTx 或 FindByUsername
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			expectedResponse: map[string]interface{}{
 | 
								expectedResponse: map[string]interface{}{
 | 
				
			||||||
				"code":    float64(controller.CodeBadRequest),
 | 
									"code":    float64(controller.CodeBadRequest),
 | 
				
			||||||
@@ -143,8 +143,8 @@ func TestCreateUser(t *testing.T) {
 | 
				
			|||||||
				Password: "password123",
 | 
									Password: "password123",
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			mockRepoSetup: func(m *MockUserRepository) {
 | 
								mockRepoSetup: func(m *MockUserRepository) {
 | 
				
			||||||
				// 模拟 Create 失败,因为用户名已存在
 | 
									// 模拟 CreateTx 失败,因为用户名已存在
 | 
				
			||||||
				m.On("Create", mock.AnythingOfType("*models.User")).Return(errors.New("duplicate entry")).Once()
 | 
									m.On("CreateTx", mock.AnythingOfType("*models.User")).Return(errors.New("duplicate entry")).Once()
 | 
				
			||||||
				// 模拟 FindByUsername 找到用户,确认是用户名重复
 | 
									// 模拟 FindByUsername 找到用户,确认是用户名重复
 | 
				
			||||||
				m.On("FindByUsername", "existinguser").Return(&models.User{Username: "existinguser"}, nil).Once()
 | 
									m.On("FindByUsername", "existinguser").Return(&models.User{Username: "existinguser"}, nil).Once()
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
@@ -161,8 +161,8 @@ func TestCreateUser(t *testing.T) {
 | 
				
			|||||||
				Password: "password123",
 | 
									Password: "password123",
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			mockRepoSetup: func(m *MockUserRepository) {
 | 
								mockRepoSetup: func(m *MockUserRepository) {
 | 
				
			||||||
				// 模拟 Create 失败,通用数据库错误
 | 
									// 模拟 CreateTx 失败,通用数据库错误
 | 
				
			||||||
				m.On("Create", mock.AnythingOfType("*models.User")).Return(errors.New("database error")).Once()
 | 
									m.On("CreateTx", mock.AnythingOfType("*models.User")).Return(errors.New("database error")).Once()
 | 
				
			||||||
				// 模拟 FindByUsername 找不到用户,确认不是用户名重复
 | 
									// 模拟 FindByUsername 找不到用户,确认不是用户名重复
 | 
				
			||||||
				m.On("FindByUsername", "db_error_user").Return(nil, gorm.ErrRecordNotFound).Once()
 | 
									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())
 | 
						pigBatchLogRepo := repository.NewGormPigBatchLogRepository(storage.GetDB())
 | 
				
			||||||
	pigFarmRepo := repository.NewGormPigFarmRepository(storage.GetDB())
 | 
						pigFarmRepo := repository.NewGormPigFarmRepository(storage.GetDB())
 | 
				
			||||||
	pigPenRepo := repository.NewGormPigPenRepository(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)
 | 
						unitOfWork := repository.NewGormUnitOfWork(storage.GetDB(), logger)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 初始化猪群管理领域
 | 
						// 初始化猪群管理领域
 | 
				
			||||||
	penTransferManager := pig.NewPenTransferManager(pigPenRepo)
 | 
						pigPenTransferManager := pig.NewPigPenTransferManager(pigPenRepo, pigTransferLogRepo, pigBatchRepo)
 | 
				
			||||||
	pigBatchDomain := pig.NewPigBatchService(pigBatchRepo, pigBatchLogRepo, unitOfWork, penTransferManager)
 | 
						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)
 | 
						pigFarmService := service.NewPigFarmService(pigFarmRepo, pigPenRepo, pigBatchRepo, unitOfWork, logger)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,63 +1,119 @@
 | 
				
			|||||||
package pig
 | 
					package pig
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"errors"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
 | 
						"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
 | 
				
			||||||
	"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
 | 
						"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
 | 
				
			||||||
	"gorm.io/gorm"
 | 
						"gorm.io/gorm"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// PenTransferManager 定义了与猪只位置转移相关的底层数据库操作。
 | 
					// PigPenTransferManager 定义了与猪只位置转移相关的底层数据库操作。
 | 
				
			||||||
// 它是一个内部服务,被主服务 PigBatchService 调用。
 | 
					// 它是一个内部服务,被主服务 PigBatchService 调用。
 | 
				
			||||||
type PenTransferManager interface {
 | 
					type PigPenTransferManager interface {
 | 
				
			||||||
	// LogTransfer 在数据库中创建一条猪只迁移日志。
 | 
						// LogTransfer 在数据库中创建一条猪只迁移日志。
 | 
				
			||||||
	LogTransfer(tx *gorm.DB, log *models.PigTransferLog) error
 | 
						LogTransfer(tx *gorm.DB, log *models.PigTransferLog) error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// GetPenByID 用于获取猪栏的详细信息,供上层服务进行业务校验。
 | 
						// GetPenByID 用于获取猪栏的详细信息,供上层服务进行业务校验。
 | 
				
			||||||
	// 注意: 此方法依赖于您在 PigPenRepository 中添加对应的 GetPenByIDTx 方法。
 | 
					 | 
				
			||||||
	GetPenByID(tx *gorm.DB, penID uint) (*models.Pen, error)
 | 
						GetPenByID(tx *gorm.DB, penID uint) (*models.Pen, error)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// GetPensByBatchID 获取一个猪群当前关联的所有猪栏。
 | 
						// GetPensByBatchID 获取一个猪群当前关联的所有猪栏。
 | 
				
			||||||
	// 注意: 此方法依赖于您在 PigPenRepository 中添加对应的 GetPensByBatchIDTx 方法。
 | 
					 | 
				
			||||||
	GetPensByBatchID(tx *gorm.DB, batchID uint) ([]*models.Pen, error)
 | 
						GetPensByBatchID(tx *gorm.DB, batchID uint) ([]*models.Pen, error)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// UpdatePenFields 更新一个猪栏的指定字段。
 | 
						// UpdatePenFields 更新一个猪栏的指定字段。
 | 
				
			||||||
	// 注意: 此方法依赖于您在 PigPenRepository 中添加对应的 UpdatePenFieldsTx 方法。
 | 
					 | 
				
			||||||
	UpdatePenFields(tx *gorm.DB, penID uint, updates map[string]interface{}) error
 | 
						UpdatePenFields(tx *gorm.DB, penID uint, updates map[string]interface{}) error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// GetCurrentPigsInPen 通过汇总猪只迁移日志,计算给定猪栏中的当前猪只数量。
 | 
				
			||||||
 | 
						GetCurrentPigsInPen(tx *gorm.DB, penID uint) (int, error)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// penTransferManager 是 PenTransferManager 接口的具体实现。
 | 
					// pigPenTransferManager 是 PigPenTransferManager 接口的具体实现。
 | 
				
			||||||
// 它作为调栏管理器,处理底层的数据库交互。
 | 
					// 它作为调栏管理器,处理底层的数据库交互。
 | 
				
			||||||
type penTransferManager struct {
 | 
					type pigPenTransferManager struct {
 | 
				
			||||||
	penRepo      repository.PigPenRepository
 | 
						penRepo      repository.PigPenRepository
 | 
				
			||||||
 | 
						logRepo      repository.PigTransferLogRepository
 | 
				
			||||||
 | 
						pigBatchRepo repository.PigBatchRepository
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// NewPenTransferManager 是 penTransferManager 的构造函数。
 | 
					// NewPigPenTransferManager 是 pigPenTransferManager 的构造函数。
 | 
				
			||||||
func NewPenTransferManager(penRepo repository.PigPenRepository) PenTransferManager {
 | 
					func NewPigPenTransferManager(penRepo repository.PigPenRepository, logRepo repository.PigTransferLogRepository, pigBatchRepo repository.PigBatchRepository) PigPenTransferManager {
 | 
				
			||||||
	return &penTransferManager{
 | 
						return &pigPenTransferManager{
 | 
				
			||||||
		penRepo:      penRepo,
 | 
							penRepo:      penRepo,
 | 
				
			||||||
 | 
							logRepo:      logRepo,
 | 
				
			||||||
 | 
							pigBatchRepo: pigBatchRepo,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// LogTransfer 实现了在数据库中创建迁移日志的逻辑。
 | 
					// LogTransfer 实现了在数据库中创建迁移日志的逻辑。
 | 
				
			||||||
func (s *penTransferManager) LogTransfer(tx *gorm.DB, log *models.PigTransferLog) error {
 | 
					func (s *pigPenTransferManager) LogTransfer(tx *gorm.DB, log *models.PigTransferLog) error {
 | 
				
			||||||
	// 直接使用事务对象创建记录。
 | 
						return s.logRepo.CreatePigTransferLog(tx, log)
 | 
				
			||||||
	return tx.Create(log).Error
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetPenByID 实现了获取猪栏信息的逻辑。
 | 
					// GetPenByID 实现了获取猪栏信息的逻辑。
 | 
				
			||||||
// 注意: 此处调用了一个假设存在的方法 GetPenByIDTx。
 | 
					func (s *pigPenTransferManager) GetPenByID(tx *gorm.DB, penID uint) (*models.Pen, error) {
 | 
				
			||||||
func (s *penTransferManager) GetPenByID(tx *gorm.DB, penID uint) (*models.Pen, error) {
 | 
					 | 
				
			||||||
	return s.penRepo.GetPenByIDTx(tx, penID)
 | 
						return s.penRepo.GetPenByIDTx(tx, penID)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetPensByBatchID 实现了获取猪群关联猪栏列表的逻辑。
 | 
					// GetPensByBatchID 实现了获取猪群关联猪栏列表的逻辑。
 | 
				
			||||||
// 注意: 此处调用了一个假设存在的方法 GetPensByBatchIDTx。
 | 
					func (s *pigPenTransferManager) GetPensByBatchID(tx *gorm.DB, batchID uint) ([]*models.Pen, error) {
 | 
				
			||||||
func (s *penTransferManager) GetPensByBatchID(tx *gorm.DB, batchID uint) ([]*models.Pen, error) {
 | 
					 | 
				
			||||||
	return s.penRepo.GetPensByBatchIDTx(tx, batchID)
 | 
						return s.penRepo.GetPensByBatchIDTx(tx, batchID)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// UpdatePenFields 实现了更新猪栏字段的逻辑。
 | 
					// UpdatePenFields 实现了更新猪栏字段的逻辑。
 | 
				
			||||||
// 注意: 此处调用了一个假设存在的方法 UpdatePenFieldsTx。
 | 
					func (s *pigPenTransferManager) UpdatePenFields(tx *gorm.DB, penID uint, updates map[string]interface{}) error {
 | 
				
			||||||
func (s *penTransferManager) UpdatePenFields(tx *gorm.DB, penID uint, updates map[string]interface{}) error {
 | 
					 | 
				
			||||||
	return s.penRepo.UpdatePenFieldsTx(tx, penID, updates)
 | 
						return s.penRepo.UpdatePenFieldsTx(tx, penID, updates)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetCurrentPigsInPen 实现了计算猪栏当前猪只数量的逻辑。
 | 
				
			||||||
 | 
					func (s *pigPenTransferManager) GetCurrentPigsInPen(tx *gorm.DB, penID uint) (int, error) {
 | 
				
			||||||
 | 
						// 1. 通过猪栏ID查出所属猪群信息
 | 
				
			||||||
 | 
						pen, err := s.penRepo.GetPenByIDTx(tx, penID)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							if errors.Is(err, gorm.ErrRecordNotFound) {
 | 
				
			||||||
 | 
								return 0, ErrPenNotFound
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return 0, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 如果猪栏没有关联任何猪群,那么猪只数必为0
 | 
				
			||||||
 | 
						if pen.PigBatchID == nil || *pen.PigBatchID == 0 {
 | 
				
			||||||
 | 
							return 0, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						currentBatchID := *pen.PigBatchID
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 2. 根据猪群ID获取猪群的起始日期
 | 
				
			||||||
 | 
						batch, err := s.pigBatchRepo.GetPigBatchByIDTx(tx, currentBatchID)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							if errors.Is(err, gorm.ErrRecordNotFound) {
 | 
				
			||||||
 | 
								return 0, ErrPigBatchNotFound
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return 0, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						batchStartDate := batch.StartDate
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 3. 调用仓库方法,获取从猪群开始至今,该猪栏的所有倒序日志
 | 
				
			||||||
 | 
						logs, err := s.logRepo.GetLogsForPenSince(tx, penID, batchStartDate)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return 0, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 如果没有日志,猪只数为0
 | 
				
			||||||
 | 
						if len(logs) == 0 {
 | 
				
			||||||
 | 
							return 0, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 4. 在内存中筛选出最后一段连续日志,并进行计算
 | 
				
			||||||
 | 
						var totalPigs int
 | 
				
			||||||
 | 
						// 再次确认当前猪群ID,以最新的日志为准,防止在极小时间窗口内猪栏被快速切换
 | 
				
			||||||
 | 
						latestBatchID := *pen.PigBatchID
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, log := range logs {
 | 
				
			||||||
 | 
							// 一旦发现日志不属于最新的猪群,立即停止计算
 | 
				
			||||||
 | 
							if log.PigBatchID != latestBatchID {
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							totalPigs += log.Quantity
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return totalPigs, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,8 +2,10 @@ package pig
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"errors"
 | 
						"errors"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
 | 
						"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
 | 
				
			||||||
 | 
						"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// --- 业务错误定义 ---
 | 
					// --- 业务错误定义 ---
 | 
				
			||||||
@@ -23,6 +25,8 @@ var (
 | 
				
			|||||||
	ErrPenNotFound = errors.New("指定的猪栏不存在")
 | 
						ErrPenNotFound = errors.New("指定的猪栏不存在")
 | 
				
			||||||
	// ErrPenNotAssociatedWithBatch 表示猪栏未与该批次关联
 | 
						// ErrPenNotAssociatedWithBatch 表示猪栏未与该批次关联
 | 
				
			||||||
	ErrPenNotAssociatedWithBatch = errors.New("猪栏未与该批次关联")
 | 
						ErrPenNotAssociatedWithBatch = errors.New("猪栏未与该批次关联")
 | 
				
			||||||
 | 
						// ErrInvalidOperation 非法操作
 | 
				
			||||||
 | 
						ErrInvalidOperation = errors.New("非法操作")
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// --- 领域服务接口 ---
 | 
					// --- 领域服务接口 ---
 | 
				
			||||||
@@ -30,29 +34,57 @@ var (
 | 
				
			|||||||
// PigBatchService 定义了猪批次管理的核心业务逻辑接口。
 | 
					// PigBatchService 定义了猪批次管理的核心业务逻辑接口。
 | 
				
			||||||
// 它抽象了所有与猪批次相关的操作,使得应用层可以依赖于此接口,而不是具体的实现。
 | 
					// 它抽象了所有与猪批次相关的操作,使得应用层可以依赖于此接口,而不是具体的实现。
 | 
				
			||||||
type PigBatchService interface {
 | 
					type PigBatchService interface {
 | 
				
			||||||
	// TransferPigsWithinBatch 处理同一个猪群内部的调栏业务。
 | 
						// CreatePigBatch 创建猪批次,并记录初始日志。
 | 
				
			||||||
	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(operatorID uint, batch *models.PigBatch) (*models.PigBatch, error)
 | 
						CreatePigBatch(operatorID uint, batch *models.PigBatch) (*models.PigBatch, error)
 | 
				
			||||||
 | 
						// GetPigBatch 获取单个猪批次。
 | 
				
			||||||
	// GetPigBatch 根据ID获取单个猪批次的详细信息。
 | 
					 | 
				
			||||||
	GetPigBatch(id uint) (*models.PigBatch, error)
 | 
						GetPigBatch(id uint) (*models.PigBatch, error)
 | 
				
			||||||
 | 
						// UpdatePigBatch 更新猪批次信息。
 | 
				
			||||||
	// UpdatePigBatch 更新一个已存在的猪批次信息。
 | 
					 | 
				
			||||||
	UpdatePigBatch(batch *models.PigBatch) (*models.PigBatch, error)
 | 
						UpdatePigBatch(batch *models.PigBatch) (*models.PigBatch, error)
 | 
				
			||||||
 | 
						// DeletePigBatch 删除猪批次,包含业务规则校验。
 | 
				
			||||||
	// DeletePigBatch 删除一个指定的猪批次。
 | 
					 | 
				
			||||||
	// 实现时需要包含业务规则校验,例如,活跃的批次不能被删除。
 | 
					 | 
				
			||||||
	DeletePigBatch(id uint) error
 | 
						DeletePigBatch(id uint) error
 | 
				
			||||||
 | 
						// ListPigBatches 批量查询猪批次。
 | 
				
			||||||
	// ListPigBatches 根据是否活跃的状态,列出所有符合条件的猪批次。
 | 
					 | 
				
			||||||
	ListPigBatches(isActive *bool) ([]*models.PigBatch, error)
 | 
						ListPigBatches(isActive *bool) ([]*models.PigBatch, error)
 | 
				
			||||||
 | 
						// UpdatePigBatchPens 更新猪批次关联的猪栏。
 | 
				
			||||||
	// UpdatePigBatchPens 负责原子性地更新一个猪批次所关联的所有猪栏。
 | 
					 | 
				
			||||||
	// 它会处理猪栏的添加、移除,并确保数据的一致性。
 | 
					 | 
				
			||||||
	UpdatePigBatchPens(batchID uint, desiredPenIDs []uint) error
 | 
						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"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
 | 
						"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
 | 
				
			||||||
	"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
 | 
					 | 
				
			||||||
	"github.com/google/uuid"
 | 
					 | 
				
			||||||
	"gorm.io/gorm"
 | 
						"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 实现了创建猪批次的逻辑,并同时创建初始批次日志。
 | 
					// CreatePigBatch 实现了创建猪批次的逻辑,并同时创建初始批次日志。
 | 
				
			||||||
func (s *pigBatchService) CreatePigBatch(operatorID uint, batch *models.PigBatch) (*models.PigBatch, error) {
 | 
					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,
 | 
								ChangeCount: createdBatch.InitialCount,
 | 
				
			||||||
			Reason:      fmt.Sprintf("创建了新的猪批次 %s,初始数量 %d", createdBatch.BatchNumber, createdBatch.InitialCount),
 | 
								Reason:      fmt.Sprintf("创建了新的猪批次 %s,初始数量 %d", createdBatch.BatchNumber, createdBatch.InitialCount),
 | 
				
			||||||
			BeforeCount: 0, // 初始创建前数量为0
 | 
								BeforeCount: 0, // 初始创建前数量为0
 | 
				
			||||||
			AfterCount:  int(createdBatch.InitialCount),
 | 
								AfterCount:  createdBatch.InitialCount,
 | 
				
			||||||
			OperatorID:  0, // 假设初始创建没有特定操作员ID,或需要从上下文传入
 | 
								OperatorID:  operatorID,
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// 3. 记录批次日志
 | 
							// 3. 记录批次日志
 | 
				
			||||||
		if err := s.pigBatchLogRepo.Create(tx, initialLog); err != nil {
 | 
							if err := s.pigBatchLogRepo.CreateTx(tx, initialLog); err != nil {
 | 
				
			||||||
			return fmt.Errorf("记录初始批次日志失败: %w", err)
 | 
								return fmt.Errorf("记录初始批次日志失败: %w", err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -135,7 +108,7 @@ func (s *pigBatchService) ListPigBatches(isActive *bool) ([]*models.PigBatch, er
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// UpdatePigBatchPens 实现了在事务中更新猪批次关联猪栏的复杂逻辑。
 | 
					// UpdatePigBatchPens 实现了在事务中更新猪批次关联猪栏的复杂逻辑。
 | 
				
			||||||
// 它通过调用底层的 PenTransferManager 来执行数据库操作,从而保持了职责的清晰。
 | 
					// 它通过调用底层的 PigPenTransferManager 来执行数据库操作,从而保持了职责的清晰。
 | 
				
			||||||
func (s *pigBatchService) UpdatePigBatchPens(batchID uint, desiredPenIDs []uint) error {
 | 
					func (s *pigBatchService) UpdatePigBatchPens(batchID uint, desiredPenIDs []uint) error {
 | 
				
			||||||
	// 使用工作单元来确保操作的原子性
 | 
						// 使用工作单元来确保操作的原子性
 | 
				
			||||||
	return s.uow.ExecuteInTransaction(func(tx *gorm.DB) error {
 | 
						return s.uow.ExecuteInTransaction(func(tx *gorm.DB) error {
 | 
				
			||||||
@@ -234,131 +207,71 @@ func (s *pigBatchService) UpdatePigBatchPens(batchID uint, desiredPenIDs []uint)
 | 
				
			|||||||
	})
 | 
						})
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// --- 新增的调栏业务实现 ---
 | 
					// GetCurrentPigQuantity 实现了获取指定猪批次的当前猪只数量的逻辑。
 | 
				
			||||||
 | 
					func (s *pigBatchService) GetCurrentPigQuantity(batchID uint) (int, error) {
 | 
				
			||||||
// executeTransferAndLog 是一个私有辅助方法,用于封装创建和记录迁移日志的通用逻辑。
 | 
						var getErr error
 | 
				
			||||||
func (s *pigBatchService) executeTransferAndLog(tx *gorm.DB, fromBatchID, toBatchID, fromPenID, toPenID uint, quantity int, transferType models.PigTransferType, operatorID uint, remarks string) error {
 | 
						var quantity int
 | 
				
			||||||
	// 1. 生成关联ID
 | 
						err := s.uow.ExecuteInTransaction(func(tx *gorm.DB) error {
 | 
				
			||||||
	correlationID := uuid.New().String()
 | 
							quantity, getErr = s.getCurrentPigQuantityTx(tx, batchID)
 | 
				
			||||||
 | 
							return getErr
 | 
				
			||||||
	// 2. 创建调出日志
 | 
						})
 | 
				
			||||||
	logOut := &models.PigTransferLog{
 | 
						if err != nil {
 | 
				
			||||||
		TransferTime:  time.Now(),
 | 
							return 0, err
 | 
				
			||||||
		PigBatchID:    fromBatchID,
 | 
					 | 
				
			||||||
		PenID:         fromPenID,
 | 
					 | 
				
			||||||
		Quantity:      -quantity, // 调出为负数
 | 
					 | 
				
			||||||
		Type:          transferType,
 | 
					 | 
				
			||||||
		CorrelationID: correlationID,
 | 
					 | 
				
			||||||
		OperatorID:    operatorID,
 | 
					 | 
				
			||||||
		Remarks:       remarks,
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						return quantity, nil
 | 
				
			||||||
	// 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 实现了同一个猪群内部的调栏业务。
 | 
					// getCurrentPigQuantityTx 实现了获取指定猪批次的当前猪只数量的逻辑。
 | 
				
			||||||
func (s *pigBatchService) TransferPigsWithinBatch(batchID uint, fromPenID uint, toPenID uint, quantity uint, operatorID uint, remarks string) error {
 | 
					func (s *pigBatchService) getCurrentPigQuantityTx(tx *gorm.DB, batchID uint) (int, error) {
 | 
				
			||||||
	if fromPenID == toPenID {
 | 
						// 1. 获取猪批次初始信息
 | 
				
			||||||
		return errors.New("源猪栏和目标猪栏不能相同")
 | 
						batch, err := s.pigBatchRepo.GetPigBatchByIDTx(tx, batchID)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							if errors.Is(err, gorm.ErrRecordNotFound) {
 | 
				
			||||||
 | 
								return 0, ErrPigBatchNotFound
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	if quantity == 0 {
 | 
							return 0, fmt.Errorf("获取猪批次 %d 初始信息失败: %w", batchID, err)
 | 
				
			||||||
		return errors.New("迁移数量不能为零")
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 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 {
 | 
						return s.uow.ExecuteInTransaction(func(tx *gorm.DB) error {
 | 
				
			||||||
		// 1. 核心业务规则校验
 | 
							return s.updatePigBatchQuantityTx(tx, operatorID, batchID, changeType, changeAmount, changeReason, happenedAt)
 | 
				
			||||||
		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) updatePigBatchQuantityTx(tx *gorm.DB, operatorID uint, batchID uint, changeType models.LogChangeType, changeAmount int, changeReason string, happenedAt time.Time) error {
 | 
				
			||||||
func (s *pigBatchService) TransferPigsAcrossBatches(sourceBatchID uint, destBatchID uint, fromPenID uint, toPenID uint, quantity uint, operatorID uint, remarks string) error {
 | 
						lastLog, err := s.pigBatchLogRepo.GetLastLogByBatchIDTx(tx, batchID)
 | 
				
			||||||
	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 {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						// 检查数量不应该减到小于零
 | 
				
			||||||
		// 3. 修改本聚合的数据(猪群总数)
 | 
						if changeAmount < 0 {
 | 
				
			||||||
		sourceBatch.InitialCount -= int(quantity)
 | 
							if lastLog.AfterCount+changeAmount < 0 {
 | 
				
			||||||
		destBatch.InitialCount += int(quantity)
 | 
								return ErrInvalidOperation
 | 
				
			||||||
 | 
					 | 
				
			||||||
		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)
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						pigBatchLog := &models.PigBatchLog{
 | 
				
			||||||
		return nil
 | 
							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.RawMaterialPurchase{}, "purchase_date"},
 | 
				
			||||||
		{models.RawMaterialStockLog{}, "happened_at"},
 | 
							{models.RawMaterialStockLog{}, "happened_at"},
 | 
				
			||||||
		{models.FeedUsageRecord{}, "recorded_at"},
 | 
							{models.FeedUsageRecord{}, "recorded_at"},
 | 
				
			||||||
		{models.GroupMedicationLog{}, "happened_at"},
 | 
							{models.MedicationLog{}, "happened_at"},
 | 
				
			||||||
		{models.PigBatchLog{}, "happened_at"},
 | 
							{models.PigBatchLog{}, "happened_at"},
 | 
				
			||||||
		{models.WeighingBatch{}, "weighing_time"},
 | 
							{models.WeighingBatch{}, "weighing_time"},
 | 
				
			||||||
		{models.WeighingRecord{}, "weighing_time"},
 | 
							{models.WeighingRecord{}, "weighing_time"},
 | 
				
			||||||
		{models.PigTransferLog{}, "transfer_time"},
 | 
							{models.PigTransferLog{}, "transfer_time"},
 | 
				
			||||||
		{models.PigBatchSickPigLog{}, "happened_at"},
 | 
							{models.PigSickLog{}, "happened_at"},
 | 
				
			||||||
		{models.PigPurchase{}, "purchase_date"},
 | 
							{models.PigPurchase{}, "purchase_date"},
 | 
				
			||||||
		{models.PigSale{}, "sale_date"},
 | 
							{models.PigSale{}, "sale_date"},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -203,12 +203,12 @@ func (ps *PostgresStorage) applyCompressionPolicies() error {
 | 
				
			|||||||
		{models.RawMaterialPurchase{}, "raw_material_id"},
 | 
							{models.RawMaterialPurchase{}, "raw_material_id"},
 | 
				
			||||||
		{models.RawMaterialStockLog{}, "raw_material_id"},
 | 
							{models.RawMaterialStockLog{}, "raw_material_id"},
 | 
				
			||||||
		{models.FeedUsageRecord{}, "pen_id"},
 | 
							{models.FeedUsageRecord{}, "pen_id"},
 | 
				
			||||||
		{models.GroupMedicationLog{}, "pig_batch_id"},
 | 
							{models.MedicationLog{}, "pig_batch_id"},
 | 
				
			||||||
		{models.PigBatchLog{}, "pig_batch_id"},
 | 
							{models.PigBatchLog{}, "pig_batch_id"},
 | 
				
			||||||
		{models.WeighingBatch{}, "pig_batch_id"},
 | 
							{models.WeighingBatch{}, "pig_batch_id"},
 | 
				
			||||||
		{models.WeighingRecord{}, "weighing_batch_id"},
 | 
							{models.WeighingRecord{}, "weighing_batch_id"},
 | 
				
			||||||
		{models.PigTransferLog{}, "pig_batch_id"},
 | 
							{models.PigTransferLog{}, "pig_batch_id"},
 | 
				
			||||||
		{models.PigBatchSickPigLog{}, "pig_batch_id"},
 | 
							{models.PigSickLog{}, "pig_batch_id"},
 | 
				
			||||||
		{models.PigPurchase{}, "pig_batch_id"},
 | 
							{models.PigPurchase{}, "pig_batch_id"},
 | 
				
			||||||
		{models.PigSale{}, "pig_batch_id"},
 | 
							{models.PigSale{}, "pig_batch_id"},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -80,8 +80,8 @@ const (
 | 
				
			|||||||
	ReasonTypeHealthCare MedicationReasonType = "保健"
 | 
						ReasonTypeHealthCare MedicationReasonType = "保健"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GroupMedicationLog 记录了对整个猪批次的用药情况
 | 
					// MedicationLog 记录了对整个猪批次的用药情况
 | 
				
			||||||
type GroupMedicationLog struct {
 | 
					type MedicationLog struct {
 | 
				
			||||||
	gorm.Model
 | 
						gorm.Model
 | 
				
			||||||
	PigBatchID   uint                 `gorm:"not null;index;comment:关联的猪批次ID"`
 | 
						PigBatchID   uint                 `gorm:"not null;index;comment:关联的猪批次ID"`
 | 
				
			||||||
	MedicationID 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:用药时间"`
 | 
						HappenedAt   time.Time            `gorm:"primaryKey;comment:用药时间"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (GroupMedicationLog) TableName() string {
 | 
					func (MedicationLog) TableName() string {
 | 
				
			||||||
	return "group_medication_logs"
 | 
						return "medication_logs"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -42,7 +42,7 @@ func GetAllModels() []interface{} {
 | 
				
			|||||||
		&WeighingBatch{},
 | 
							&WeighingBatch{},
 | 
				
			||||||
		&WeighingRecord{},
 | 
							&WeighingRecord{},
 | 
				
			||||||
		&PigTransferLog{},
 | 
							&PigTransferLog{},
 | 
				
			||||||
		&PigBatchSickPigLog{},
 | 
							&PigSickLog{},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Pig Buy & Sell
 | 
							// Pig Buy & Sell
 | 
				
			||||||
		&PigPurchase{},
 | 
							&PigPurchase{},
 | 
				
			||||||
@@ -58,7 +58,7 @@ func GetAllModels() []interface{} {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		// Medication Models
 | 
							// Medication Models
 | 
				
			||||||
		&Medication{},
 | 
							&Medication{},
 | 
				
			||||||
		&GroupMedicationLog{},
 | 
							&MedicationLog{},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -57,6 +57,7 @@ const (
 | 
				
			|||||||
	ChangeTypeDeath       LogChangeType = "死亡"
 | 
						ChangeTypeDeath       LogChangeType = "死亡"
 | 
				
			||||||
	ChangeTypeCull        LogChangeType = "淘汰"
 | 
						ChangeTypeCull        LogChangeType = "淘汰"
 | 
				
			||||||
	ChangeTypeSale        LogChangeType = "销售"
 | 
						ChangeTypeSale        LogChangeType = "销售"
 | 
				
			||||||
 | 
						ChangeTypeBuy         LogChangeType = "购买"
 | 
				
			||||||
	ChangeTypeTransferIn  LogChangeType = "转入"
 | 
						ChangeTypeTransferIn  LogChangeType = "转入"
 | 
				
			||||||
	ChangeTypeTransferOut LogChangeType = "转出"
 | 
						ChangeTypeTransferOut LogChangeType = "转出"
 | 
				
			||||||
	ChangeTypeCorrection  LogChangeType = "盘点校正"
 | 
						ChangeTypeCorrection  LogChangeType = "盘点校正"
 | 
				
			||||||
@@ -105,51 +106,3 @@ type WeighingRecord struct {
 | 
				
			|||||||
func (WeighingRecord) TableName() string {
 | 
					func (WeighingRecord) TableName() string {
 | 
				
			||||||
	return "weighing_records"
 | 
						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 {
 | 
						if len(logs) == 0 {
 | 
				
			||||||
		return nil
 | 
							return nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	// GORM 的 Create 传入一个切片指针会执行批量插入。
 | 
						// GORM 的 CreateTx 传入一个切片指针会执行批量插入。
 | 
				
			||||||
	return r.db.Create(&logs).Error
 | 
						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
 | 
					package repository
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"time" // 引入 time 包
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
 | 
						"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
 | 
				
			||||||
	"gorm.io/gorm"
 | 
						"gorm.io/gorm"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// PigBatchLogRepository 定义了与猪批次日志相关的数据库操作接口。
 | 
					// PigBatchLogRepository 定义了与猪批次日志相关的数据库操作接口。
 | 
				
			||||||
type PigBatchLogRepository interface {
 | 
					type PigBatchLogRepository interface {
 | 
				
			||||||
	// Create 在指定的事务中创建一条新的猪批次日志。
 | 
						// CreateTx 在指定的事务中创建一条新的猪批次日志。
 | 
				
			||||||
	Create(tx *gorm.DB, log *models.PigBatchLog) error
 | 
						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 实现。
 | 
					// gormPigBatchLogRepository 是 PigBatchLogRepository 的 GORM 实现。
 | 
				
			||||||
@@ -22,6 +30,26 @@ func NewGormPigBatchLogRepository(db *gorm.DB) PigBatchLogRepository {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Create 实现了创建猪批次日志的逻辑。
 | 
					// 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
 | 
						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