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 // 库存日志相关方法 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 }) } // 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 }