Files
pig-farm-controller/internal/infra/repository/raw_material_repository.go

222 lines
9.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package repository
import (
"context"
"errors"
"fmt"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
"gorm.io/gorm"
)
// RawMaterialListOptions 定义了查询原料列表时的筛选条件
type RawMaterialListOptions struct {
Name *string
NutrientName *string
OrderBy string
}
// RawMaterialRepository 定义了与原料相关的数据库操作接口
type RawMaterialRepository interface {
CreateRawMaterial(ctx context.Context, rawMaterial *models.RawMaterial) error
GetRawMaterialByID(ctx context.Context, id uint32) (*models.RawMaterial, error)
GetRawMaterialByName(ctx context.Context, name string) (*models.RawMaterial, error)
ListRawMaterials(ctx context.Context, opts RawMaterialListOptions, page, pageSize int) ([]models.RawMaterial, int64, error)
UpdateRawMaterial(ctx context.Context, rawMaterial *models.RawMaterial) error
DeleteRawMaterial(ctx context.Context, id uint32) error
DeleteNutrientsByRawMaterialIDTx(ctx context.Context, db *gorm.DB, rawMaterialID uint32) error
CreateBatchRawMaterialNutrientsTx(ctx context.Context, db *gorm.DB, nutrients []models.RawMaterialNutrient) error
// 库存日志相关方法
CreateRawMaterialStockLog(ctx context.Context, log *models.RawMaterialStockLog) error
GetLatestRawMaterialStockLog(ctx context.Context, rawMaterialID uint32) (*models.RawMaterialStockLog, error)
}
// gormRawMaterialRepository 是 RawMaterialRepository 的 GORM 实现
type gormRawMaterialRepository struct {
ctx context.Context
db *gorm.DB
}
// NewGormRawMaterialRepository 创建一个新的 RawMaterialRepository GORM 实现实例
func NewGormRawMaterialRepository(ctx context.Context, db *gorm.DB) RawMaterialRepository {
return &gormRawMaterialRepository{ctx: ctx, db: db}
}
// CreateRawMaterial 创建一个新的原料
func (r *gormRawMaterialRepository) CreateRawMaterial(ctx context.Context, rawMaterial *models.RawMaterial) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "CreateRawMaterial")
return r.db.WithContext(repoCtx).Create(rawMaterial).Error
}
// GetRawMaterialByID 根据ID获取单个原料并预加载关联的营养素信息
func (r *gormRawMaterialRepository) GetRawMaterialByID(ctx context.Context, id uint32) (*models.RawMaterial, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "GetRawMaterialByID")
var rawMaterial models.RawMaterial
// 如果记录未找到GORM 会返回 gorm.ErrRecordNotFound 错误
if err := r.db.WithContext(repoCtx).Preload("RawMaterialNutrients.Nutrient").First(&rawMaterial, id).Error; err != nil {
return nil, err
}
return &rawMaterial, nil
}
// GetRawMaterialByName 根据名称获取单个原料,并预加载关联的营养素信息
func (r *gormRawMaterialRepository) GetRawMaterialByName(ctx context.Context, name string) (*models.RawMaterial, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "GetRawMaterialByName")
var rawMaterial models.RawMaterial
// 如果记录未找到GORM 会返回 gorm.ErrRecordNotFound 错误
if err := r.db.WithContext(repoCtx).Preload("RawMaterialNutrients.Nutrient").Where("name = ?", name).First(&rawMaterial).Error; err != nil {
return nil, err
}
return &rawMaterial, nil
}
// ListRawMaterials 列出所有原料(分页),支持按名称和营养名称筛选
func (r *gormRawMaterialRepository) ListRawMaterials(ctx context.Context, opts RawMaterialListOptions, page, pageSize int) ([]models.RawMaterial, int64, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "ListRawMaterials")
var rawMaterials []models.RawMaterial
var total int64
db := r.db.WithContext(repoCtx).Model(&models.RawMaterial{})
// 应用筛选条件
if opts.Name != nil && *opts.Name != "" {
db = db.Where("name LIKE ?", "%"+*opts.Name+"%")
}
// 如果传入了营养名称,则使用子查询进行筛选
if opts.NutrientName != nil && *opts.NutrientName != "" {
// 子查询:从 raw_material_nutrients 和 nutrients 表中找到所有包含该营养的 raw_material_id
subQuery := r.db.Model(&models.RawMaterialNutrient{}).
Select("raw_material_id").
Joins("JOIN nutrients ON nutrients.id = raw_material_nutrients.nutrient_id").
Where("nutrients.name LIKE ?", "%"+*opts.NutrientName+"%")
db = db.Where("id IN (?)", subQuery)
}
// 首先计算总数
if err := db.Count(&total).Error; err != nil {
return nil, 0, err
}
// 然后应用排序、分页并获取数据
if opts.OrderBy != "" {
db = db.Order(opts.OrderBy)
}
offset := (page - 1) * pageSize
if err := db.Preload("RawMaterialNutrients.Nutrient").Offset(offset).Limit(pageSize).Find(&rawMaterials).Error; err != nil {
return nil, 0, err
}
return rawMaterials, total, nil
}
// UpdateRawMaterial 更新一个原料
func (r *gormRawMaterialRepository) UpdateRawMaterial(ctx context.Context, rawMaterial *models.RawMaterial) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "UpdateRawMaterial")
// 使用 map 更新以避免 GORM 的零值问题,并确保只更新指定字段
updateData := map[string]interface{}{
"name": rawMaterial.Name,
"description": rawMaterial.Description,
}
result := r.db.WithContext(repoCtx).Model(&models.RawMaterial{}).Where("id = ?", rawMaterial.ID).Updates(updateData)
if result.Error != nil {
return result.Error
}
if result.RowsAffected == 0 {
return fmt.Errorf("未找到要更新的原料ID: %d", rawMaterial.ID)
}
return nil
}
// DeleteRawMaterial 根据ID删除一个原料并级联软删除关联的 RawMaterialNutrient 和 RawMaterialStockLog 记录
func (r *gormRawMaterialRepository) DeleteRawMaterial(ctx context.Context, id uint32) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "DeleteRawMaterial")
return r.db.WithContext(repoCtx).Transaction(func(tx *gorm.DB) error {
// 1. 查找 RawMaterial 记录,确保其存在
var rawMaterial models.RawMaterial
if err := tx.First(&rawMaterial, id).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return fmt.Errorf("未找到要删除的原料ID: %d", id)
}
return fmt.Errorf("查询原料失败: %w", err)
}
// 2. 软删除所有关联的 RawMaterialNutrient 记录
if err := tx.Where("raw_material_id = ?", id).Delete(&models.RawMaterialNutrient{}).Error; err != nil {
return fmt.Errorf("软删除关联的原料营养素记录失败: %w", err)
}
// 3. 软删除所有关联的 RawMaterialStockLog 记录
if err := tx.Where("raw_material_id = ?", id).Delete(&models.RawMaterialStockLog{}).Error; err != nil {
return fmt.Errorf("软删除关联的原料库存日志记录失败: %w", err)
}
// 4. 软删除 RawMaterial 记录本身
if err := tx.Delete(&rawMaterial).Error; err != nil {
return fmt.Errorf("软删除原料失败: %w", err)
}
return nil
})
}
// DeleteNutrientsByRawMaterialIDTx 在事务中软删除指定原料的所有营养成分
func (r *gormRawMaterialRepository) DeleteNutrientsByRawMaterialIDTx(ctx context.Context, db *gorm.DB, rawMaterialID uint32) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "DeleteNutrientsByRawMaterialIDTx")
tx := db.WithContext(repoCtx)
if err := tx.Where("raw_material_id = ?", rawMaterialID).Delete(&models.RawMaterialNutrient{}).Error; err != nil {
return fmt.Errorf("软删除原料营养成分失败: %w", err)
}
return nil
}
// CreateBatchRawMaterialNutrientsTx 在事务中批量创建原料营养成分
func (r *gormRawMaterialRepository) CreateBatchRawMaterialNutrientsTx(ctx context.Context, db *gorm.DB, nutrients []models.RawMaterialNutrient) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "CreateBatchRawMaterialNutrientsTx")
// 如果没有要创建的记录直接返回成功避免执行空的Create语句
if len(nutrients) == 0 {
return nil
}
// 确保每个营养都关联到正确的原料ID
// 注意:这里假设传入的 nutrients 已经设置了正确的 RawMaterialID
for i := range nutrients {
if nutrients[i].RawMaterialID == 0 {
return fmt.Errorf("创建原料营养时 RecipeID 不能为空")
}
}
if err := db.WithContext(repoCtx).Create(&nutrients).Error; err != nil {
return fmt.Errorf("批量创建原料营养成分失败: %w", err)
}
return nil
}
// CreateRawMaterialStockLog 创建一条新的原料库存日志
func (r *gormRawMaterialRepository) CreateRawMaterialStockLog(ctx context.Context, log *models.RawMaterialStockLog) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "CreateRawMaterialStockLog")
return r.db.WithContext(repoCtx).Create(log).Error
}
// GetLatestRawMaterialStockLog 获取指定原料的最新一条库存日志
func (r *gormRawMaterialRepository) GetLatestRawMaterialStockLog(ctx context.Context, rawMaterialID uint32) (*models.RawMaterialStockLog, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "GetLatestRawMaterialStockLog")
var latestLog models.RawMaterialStockLog
err := r.db.WithContext(repoCtx).
Where("raw_material_id = ?", rawMaterialID).
Order("happened_at DESC, id DESC"). // 优先按时间降序然后按ID降序确保唯一最新
First(&latestLog).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, nil // 如果没有日志记录不视为错误返回nil
}
return nil, err
}
return &latestLog, nil
}