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