From 34311889e81d18bf81c7bbefa3a2e05860eebf3f Mon Sep 17 00:00:00 2001 From: huang <1724659546@qq.com> Date: Wed, 26 Nov 2025 20:44:41 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E4=BD=BF=E7=94=A8=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F=E4=B8=AD=E6=89=80=E6=9C=89=E5=8F=AF=E7=94=A8=E7=9A=84?= =?UTF-8?q?=E5=8E=9F=E6=96=99=E4=B8=80=E9=94=AE=E7=94=9F=E6=88=90=E9=85=8D?= =?UTF-8?q?=E6=96=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- design/recipe-management/index.md | 3 +- docs/docs.go | 79 +++++++++++++++++-- docs/swagger.json | 79 +++++++++++++++++-- docs/swagger.yaml | 46 ++++++++++- internal/app/api/router.go | 1 + .../app/controller/feed/recipe_controller.go | 31 ++++++++ internal/app/dto/feed_converter.go | 12 +++ internal/app/dto/feed_dto.go | 7 ++ internal/app/service/recipe_service.go | 13 ++- internal/core/component_initializers.go | 2 + .../domain/recipe/recipe_generate_manager.go | 1 + internal/domain/recipe/recipe_service.go | 57 +++++++++++-- 12 files changed, 301 insertions(+), 30 deletions(-) diff --git a/design/recipe-management/index.md b/design/recipe-management/index.md index ce7e430..165ec05 100644 --- a/design/recipe-management/index.md +++ b/design/recipe-management/index.md @@ -63,4 +63,5 @@ http://git.huangwc.com/pig/pig-farm-controller/issues/66 13. 重构配方领域 14. 配方增删改查服务层和控制器 15. 实现库存管理相关逻辑 -16. 实现配方生成器 \ No newline at end of file +16. 实现配方生成器 +17. 实现使用系统中所有可用的原料一键生成配方 \ No newline at end of file diff --git a/docs/docs.go b/docs/docs.go index 1f1cd3e..9155de7 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -3145,6 +3145,52 @@ const docTemplate = `{ } } }, + "/api/v1/feed/recipes/generate-from-all-materials/{pig_type_id}": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "根据指定的猪类型ID,使用系统中所有可用的原料,自动计算并创建一个成本最优的配方。", + "produces": [ + "application/json" + ], + "tags": [ + "饲料管理-配方" + ], + "summary": "使用系统中所有可用的原料一键生成配方", + "parameters": [ + { + "type": "integer", + "description": "猪类型ID", + "name": "pig_type_id", + "in": "path", + "required": true + } + ], + "responses": { + "201": { + "description": "业务码为201代表创建成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/controller.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/dto.GenerateRecipeResponse" + } + } + } + ] + } + } + } + } + }, "/api/v1/feed/recipes/{id}": { "get": { "security": [ @@ -3669,7 +3715,6 @@ const docTemplate = `{ }, { "enum": [ - 7, -1, 0, 1, @@ -3679,12 +3724,12 @@ const docTemplate = `{ 5, -1, 5, - 6 + 6, + 7 ], "type": "integer", "format": "int32", "x-enum-varnames": [ - "_numLevels", "DebugLevel", "InfoLevel", "WarnLevel", @@ -3694,7 +3739,8 @@ const docTemplate = `{ "FatalLevel", "_minLevel", "_maxLevel", - "InvalidLevel" + "InvalidLevel", + "_numLevels" ], "name": "level", "in": "query" @@ -7439,6 +7485,23 @@ const docTemplate = `{ } } }, + "dto.GenerateRecipeResponse": { + "type": "object", + "properties": { + "description": { + "description": "新生成的配方描述", + "type": "string" + }, + "id": { + "description": "新生成的配方ID", + "type": "integer" + }, + "name": { + "description": "新生成的配方名称", + "type": "string" + } + } + }, "dto.HistoricalAlarmDTO": { "type": "object", "properties": { @@ -10523,7 +10586,6 @@ const docTemplate = `{ "type": "integer", "format": "int32", "enum": [ - 7, -1, 0, 1, @@ -10533,10 +10595,10 @@ const docTemplate = `{ 5, -1, 5, - 6 + 6, + 7 ], "x-enum-varnames": [ - "_numLevels", "DebugLevel", "InfoLevel", "WarnLevel", @@ -10546,7 +10608,8 @@ const docTemplate = `{ "FatalLevel", "_minLevel", "_maxLevel", - "InvalidLevel" + "InvalidLevel", + "_numLevels" ] } }, diff --git a/docs/swagger.json b/docs/swagger.json index 9e9a8a8..dd534bf 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -3137,6 +3137,52 @@ } } }, + "/api/v1/feed/recipes/generate-from-all-materials/{pig_type_id}": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "根据指定的猪类型ID,使用系统中所有可用的原料,自动计算并创建一个成本最优的配方。", + "produces": [ + "application/json" + ], + "tags": [ + "饲料管理-配方" + ], + "summary": "使用系统中所有可用的原料一键生成配方", + "parameters": [ + { + "type": "integer", + "description": "猪类型ID", + "name": "pig_type_id", + "in": "path", + "required": true + } + ], + "responses": { + "201": { + "description": "业务码为201代表创建成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/controller.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/dto.GenerateRecipeResponse" + } + } + } + ] + } + } + } + } + }, "/api/v1/feed/recipes/{id}": { "get": { "security": [ @@ -3661,7 +3707,6 @@ }, { "enum": [ - 7, -1, 0, 1, @@ -3671,12 +3716,12 @@ 5, -1, 5, - 6 + 6, + 7 ], "type": "integer", "format": "int32", "x-enum-varnames": [ - "_numLevels", "DebugLevel", "InfoLevel", "WarnLevel", @@ -3686,7 +3731,8 @@ "FatalLevel", "_minLevel", "_maxLevel", - "InvalidLevel" + "InvalidLevel", + "_numLevels" ], "name": "level", "in": "query" @@ -7431,6 +7477,23 @@ } } }, + "dto.GenerateRecipeResponse": { + "type": "object", + "properties": { + "description": { + "description": "新生成的配方描述", + "type": "string" + }, + "id": { + "description": "新生成的配方ID", + "type": "integer" + }, + "name": { + "description": "新生成的配方名称", + "type": "string" + } + } + }, "dto.HistoricalAlarmDTO": { "type": "object", "properties": { @@ -10515,7 +10578,6 @@ "type": "integer", "format": "int32", "enum": [ - 7, -1, 0, 1, @@ -10525,10 +10587,10 @@ 5, -1, 5, - 6 + 6, + 7 ], "x-enum-varnames": [ - "_numLevels", "DebugLevel", "InfoLevel", "WarnLevel", @@ -10538,7 +10600,8 @@ "FatalLevel", "_minLevel", "_maxLevel", - "InvalidLevel" + "InvalidLevel", + "_numLevels" ] } }, diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 1c65e1e..4f31241 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -565,6 +565,18 @@ definitions: thresholds: type: number type: object + dto.GenerateRecipeResponse: + properties: + description: + description: 新生成的配方描述 + type: string + id: + description: 新生成的配方ID + type: integer + name: + description: 新生成的配方名称 + type: string + type: object dto.HistoricalAlarmDTO: properties: alarm_code: @@ -2719,7 +2731,6 @@ definitions: - PlanTypeFilterSystem zapcore.Level: enum: - - 7 - -1 - 0 - 1 @@ -2730,10 +2741,10 @@ definitions: - -1 - 5 - 6 + - 7 format: int32 type: integer x-enum-varnames: - - _numLevels - DebugLevel - InfoLevel - WarnLevel @@ -2744,6 +2755,7 @@ definitions: - _minLevel - _maxLevel - InvalidLevel + - _numLevels info: contact: email: divano@example.com @@ -4722,6 +4734,32 @@ paths: summary: 更新配方 tags: - 饲料管理-配方 + /api/v1/feed/recipes/generate-from-all-materials/{pig_type_id}: + post: + description: 根据指定的猪类型ID,使用系统中所有可用的原料,自动计算并创建一个成本最优的配方。 + parameters: + - description: 猪类型ID + in: path + name: pig_type_id + required: true + type: integer + produces: + - application/json + responses: + "201": + description: 业务码为201代表创建成功 + schema: + allOf: + - $ref: '#/definitions/controller.Response' + - properties: + data: + $ref: '#/definitions/dto.GenerateRecipeResponse' + type: object + security: + - BearerAuth: [] + summary: 使用系统中所有可用的原料一键生成配方 + tags: + - 饲料管理-配方 /api/v1/inventory/stock/adjust: post: consumes: @@ -4947,7 +4985,6 @@ paths: name: end_time type: string - enum: - - 7 - -1 - 0 - 1 @@ -4958,12 +4995,12 @@ paths: - -1 - 5 - 6 + - 7 format: int32 in: query name: level type: integer x-enum-varnames: - - _numLevels - DebugLevel - InfoLevel - WarnLevel @@ -4974,6 +5011,7 @@ paths: - _minLevel - _maxLevel - InvalidLevel + - _numLevels - enum: - 邮件 - 企业微信 diff --git a/internal/app/api/router.go b/internal/app/api/router.go index 4918118..0ad8681 100644 --- a/internal/app/api/router.go +++ b/internal/app/api/router.go @@ -259,6 +259,7 @@ func (a *API) setupRoutes() { feedGroup.DELETE("/recipes/:id", a.recipeController.DeleteRecipe) feedGroup.GET("/recipes/:id", a.recipeController.GetRecipe) feedGroup.GET("/recipes", a.recipeController.ListRecipes) + feedGroup.POST("/recipes/generate-from-all-materials/:pig_type_id", a.recipeController.GenerateFromAllMaterials) } logger.Debug("饲料管理相关接口注册成功 (需要认证和审计)") diff --git a/internal/app/controller/feed/recipe_controller.go b/internal/app/controller/feed/recipe_controller.go index ebe371b..1d44d00 100644 --- a/internal/app/controller/feed/recipe_controller.go +++ b/internal/app/controller/feed/recipe_controller.go @@ -194,3 +194,34 @@ func (c *RecipeController) ListRecipes(ctx echo.Context) error { logger.Infof("%s: 获取配方列表成功, 数量: %d", actionType, len(resp.List)) return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取配方列表成功", resp, actionType, "获取配方列表成功", resp) } + +// GenerateFromAllMaterials godoc +// @Summary 使用系统中所有可用的原料一键生成配方 +// @Description 根据指定的猪类型ID,使用系统中所有可用的原料,自动计算并创建一个成本最优的配方。 +// @Tags 饲料管理-配方 +// @Security BearerAuth +// @Produce json +// @Param pig_type_id path int true "猪类型ID" +// @Success 201 {object} controller.Response{data=dto.GenerateRecipeResponse} "业务码为201代表创建成功" +// @Router /api/v1/feed/recipes/generate-from-all-materials/{pig_type_id} [post] +func (c *RecipeController) GenerateFromAllMaterials(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "GenerateFromAllMaterials") + const actionType = "使用系统中所有可用的原料一键生成配方" + + idStr := ctx.Param("pig_type_id") + id, err := strconv.ParseUint(idStr, 10, 32) + if err != nil { + logger.Errorf("%s: 猪类型ID格式错误: %v, ID: %s", actionType, err, idStr) + return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的猪类型ID格式", actionType, "猪类型ID格式错误", idStr) + } + + recipe, err := c.recipeService.GenerateRecipeWithAllRawMaterials(reqCtx, uint32(id)) + if err != nil { + logger.Errorf("%s: 服务层生成配方失败: %v, PigTypeID: %d", actionType, err, id) + return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "生成配方失败: "+err.Error(), actionType, "服务层生成配方失败", id) + } + + resp := dto.ToGenerateRecipeResponse(recipe) + logger.Infof("%s: 配方生成成功, 新配方ID: %d", actionType, resp.ID) + return controller.SendSuccessWithAudit(ctx, controller.CodeCreated, "配方生成成功", resp, actionType, "配方生成成功", resp) +} diff --git a/internal/app/dto/feed_converter.go b/internal/app/dto/feed_converter.go index 0a8a970..afe931f 100644 --- a/internal/app/dto/feed_converter.go +++ b/internal/app/dto/feed_converter.go @@ -280,3 +280,15 @@ func ConvertUpdateRecipeRequestToModel(req *UpdateRecipeRequest) *models.Recipe RecipeIngredients: ingredients, } } + +// ToGenerateRecipeResponse 将 models.Recipe 转换为 GenerateRecipeResponse DTO +func ToGenerateRecipeResponse(recipe *models.Recipe) *GenerateRecipeResponse { + if recipe == nil { + return nil + } + return &GenerateRecipeResponse{ + ID: recipe.ID, + Name: recipe.Name, + Description: recipe.Description, + } +} diff --git a/internal/app/dto/feed_dto.go b/internal/app/dto/feed_dto.go index d03b8e6..fd8b8a1 100644 --- a/internal/app/dto/feed_dto.go +++ b/internal/app/dto/feed_dto.go @@ -325,3 +325,10 @@ type ListRecipeResponse struct { List []RecipeResponse `json:"list"` Pagination PaginationDTO `json:"pagination"` } + +// GenerateRecipeResponse 是一键生成配方的响应体 +type GenerateRecipeResponse struct { + ID uint32 `json:"id"` // 新生成的配方ID + Name string `json:"name"` // 新生成的配方名称 + Description string `json:"description"` // 新生成的配方描述 +} diff --git a/internal/app/service/recipe_service.go b/internal/app/service/recipe_service.go index 01265e1..0e1cdfe 100644 --- a/internal/app/service/recipe_service.go +++ b/internal/app/service/recipe_service.go @@ -25,22 +25,31 @@ type RecipeService interface { DeleteRecipe(ctx context.Context, id uint32) error GetRecipeByID(ctx context.Context, id uint32) (*dto.RecipeResponse, error) ListRecipes(ctx context.Context, req *dto.ListRecipeRequest) (*dto.ListRecipeResponse, error) + // GenerateRecipeWithAllRawMaterials 添加新方法 + GenerateRecipeWithAllRawMaterials(ctx context.Context, pigTypeID uint32) (*models.Recipe, error) } // recipeServiceImpl 是 RecipeService 接口的实现 type recipeServiceImpl struct { ctx context.Context - recipeSvc recipe.RecipeCoreService + recipeSvc recipe.Service } // NewRecipeService 创建一个新的 RecipeService 实例 -func NewRecipeService(ctx context.Context, recipeSvc recipe.RecipeCoreService) RecipeService { +func NewRecipeService(ctx context.Context, recipeSvc recipe.Service) RecipeService { return &recipeServiceImpl{ ctx: ctx, recipeSvc: recipeSvc, } } +// GenerateRecipeWithAllRawMaterials 实现新方法 +func (s *recipeServiceImpl) GenerateRecipeWithAllRawMaterials(ctx context.Context, pigTypeID uint32) (*models.Recipe, error) { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "GenerateRecipeWithAllRawMaterials") + // 直接调用领域服务的方法 + return s.recipeSvc.GenerateRecipeWithAllRawMaterials(serviceCtx, pigTypeID) +} + // CreateRecipe 创建配方 func (s *recipeServiceImpl) CreateRecipe(ctx context.Context, req *dto.CreateRecipeRequest) (*dto.RecipeResponse, error) { serviceCtx := logs.AddFuncName(ctx, s.ctx, "CreateRecipe") diff --git a/internal/core/component_initializers.go b/internal/core/component_initializers.go index b811295..bb29f09 100644 --- a/internal/core/component_initializers.go +++ b/internal/core/component_initializers.go @@ -228,6 +228,7 @@ func initDomainServices(ctx context.Context, cfg *config.Config, infra *Infrastr pigTypeService := recipe.NewPigTypeService(logs.AddCompName(baseCtx, "PigTypeService"), infra.repos.unitOfWork, infra.repos.pigTypeRepo) rawMaterialService := recipe.NewRawMaterialService(logs.AddCompName(baseCtx, "RawMaterialService"), infra.repos.unitOfWork, infra.repos.rawMaterialRepo, inventoryService) recipeCoreService := recipe.NewRecipeCoreService(logs.AddCompName(baseCtx, "RecipeCoreService"), infra.repos.unitOfWork, infra.repos.recipeRepo) + recipeGenerateManager := recipe.NewRecipeGenerateManager(logs.AddCompName(baseCtx, "RecipeGenerateManager")) recipeService := recipe.NewRecipeService( logs.AddCompName(baseCtx, "RecipeService"), nutrientService, @@ -236,6 +237,7 @@ func initDomainServices(ctx context.Context, cfg *config.Config, infra *Infrastr pigAgeStageService, pigTypeService, recipeCoreService, + recipeGenerateManager, ) return &DomainServices{ diff --git a/internal/domain/recipe/recipe_generate_manager.go b/internal/domain/recipe/recipe_generate_manager.go index c5fa384..745e1ba 100644 --- a/internal/domain/recipe/recipe_generate_manager.go +++ b/internal/domain/recipe/recipe_generate_manager.go @@ -7,6 +7,7 @@ import ( "math" "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" + "gonum.org/v1/gonum/mat" "gonum.org/v1/gonum/optimize/convex/lp" ) diff --git a/internal/domain/recipe/recipe_service.go b/internal/domain/recipe/recipe_service.go index 4ca5032..d8db69b 100644 --- a/internal/domain/recipe/recipe_service.go +++ b/internal/domain/recipe/recipe_service.go @@ -2,6 +2,10 @@ package recipe import ( "context" + "fmt" + + "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/repository" ) // Service 定义了配方与原料领域的核心业务服务接口 @@ -13,6 +17,8 @@ type Service interface { PigAgeStageService PigTypeService RecipeCoreService + RecipeGenerateManager + GenerateRecipeWithAllRawMaterials(ctx context.Context, pigTypeID uint32) (*models.Recipe, error) } // recipeServiceImpl 是 Service 的实现,通过组合各个子服务来实现 @@ -24,6 +30,7 @@ type recipeServiceImpl struct { PigAgeStageService PigTypeService RecipeCoreService + RecipeGenerateManager } // NewRecipeService 创建一个新的 Service 实例 @@ -35,14 +42,50 @@ func NewRecipeService( pigAgeStageService PigAgeStageService, pigTypeService PigTypeService, recipeCoreService RecipeCoreService, + recipeGenerateManager RecipeGenerateManager, ) Service { return &recipeServiceImpl{ - ctx: ctx, - NutrientService: nutrientService, - RawMaterialService: rawMaterialService, - PigBreedService: pigBreedService, - PigAgeStageService: pigAgeStageService, - PigTypeService: pigTypeService, - RecipeCoreService: recipeCoreService, + ctx: ctx, + NutrientService: nutrientService, + RawMaterialService: rawMaterialService, + PigBreedService: pigBreedService, + PigAgeStageService: pigAgeStageService, + PigTypeService: pigTypeService, + RecipeCoreService: recipeCoreService, + RecipeGenerateManager: recipeGenerateManager, } } + +// GenerateRecipeWithAllRawMaterials 使用所有已知原料为特定猪类型生成一个新配方。 +// pigTypeID: 目标猪类型的ID。 +// 返回: 生成的配方对象指针和可能的错误。 +func (r *recipeServiceImpl) GenerateRecipeWithAllRawMaterials(ctx context.Context, pigTypeID uint32) (*models.Recipe, error) { + // 1. 获取猪只类型信息,确保包含了营养需求 + pigType, err := r.GetPigTypeByID(ctx, pigTypeID) + if err != nil { + return nil, fmt.Errorf("获取猪类型信息失败: %w", err) + } + + // 2. 获取所有原料 + // 我们通过传递一个非常大的 pageSize 来获取所有原料,这在大多数情况下是可行的。 + // 对于超大规模系统,可能需要考虑分页迭代,但目前这是一个简单有效的策略。 + materials, _, err := r.ListRawMaterials(ctx, repository.RawMaterialListOptions{}, 1, 9999) + if err != nil { + return nil, fmt.Errorf("获取所有原料列表失败: %w", err) + } + + // 3. 调用生成器生成配方 + recipe, err := r.GenerateRecipe(ctx, *pigType, materials) + if err != nil { + return nil, fmt.Errorf("生成配方失败: %w", err) + } + + // 4. 保存新生成的配方到数据库 + // CreateRecipe 会处理配方及其成分的保存 + if recipe, err = r.CreateRecipe(ctx, recipe); err != nil { + return nil, fmt.Errorf("保存生成的配方失败: %w", err) + } + + // 5. 返回创建的配方 (现在它应该已经有了ID) + return recipe, nil +}