diff --git a/design/archive/recipe-management/index.md b/design/archive/recipe-management/index.md index 379a678..cc35118 100644 --- a/design/archive/recipe-management/index.md +++ b/design/archive/recipe-management/index.md @@ -55,4 +55,5 @@ http://git.huangwc.com/pig/pig-farm-controller/issues/66 5. 定义猪的模型和营养需求模型 6. 实现从json读取猪营养需求并写入数据库 7. 实现配方领域关于猪模型和营养需求的增删改查 -8. 实现配方领域的web接口 \ No newline at end of file +8. 实现配方领域的web接口 +9. 实现修改原料营养信息 \ No newline at end of file diff --git a/docs/docs.go b/docs/docs.go index 32dfb60..75329e9 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -2905,6 +2905,64 @@ const docTemplate = `{ } } }, + "/api/v1/feed/raw-materials/{id}/nutrients": { + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "根据原料ID,替换其所有的营养成分信息。这是一个覆盖操作。", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "饲料管理" + ], + "summary": "全量更新原料的营养成分", + "parameters": [ + { + "type": "integer", + "description": "原料ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "新的营养成分列表", + "name": "nutrients", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.UpdateRawMaterialNutrientsRequest" + } + } + ], + "responses": { + "200": { + "description": "业务码为200代表更新成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/controller.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/dto.RawMaterialResponse" + } + } + } + ] + } + } + } + } + }, "/api/v1/monitor/device-command-logs": { "get": { "security": [ @@ -7368,9 +7426,6 @@ const docTemplate = `{ "dto.NutrientResponse": { "type": "object", "properties": { - "created_at": { - "type": "string" - }, "description": { "type": "string" }, @@ -7386,9 +7441,6 @@ const docTemplate = `{ "items": { "$ref": "#/definitions/dto.NutrientRawMaterialDTO" } - }, - "updated_at": { - "type": "string" } } }, @@ -7461,9 +7513,6 @@ const docTemplate = `{ "dto.PigAgeStageResponse": { "type": "object", "properties": { - "created_at": { - "type": "string" - }, "description": { "type": "string" }, @@ -7472,9 +7521,6 @@ const docTemplate = `{ }, "name": { "type": "string" - }, - "updated_at": { - "type": "string" } } }, @@ -7667,9 +7713,6 @@ const docTemplate = `{ "breed_disadvantages": { "type": "string" }, - "created_at": { - "type": "string" - }, "description": { "type": "string" }, @@ -7681,9 +7724,6 @@ const docTemplate = `{ }, "parent_info": { "type": "string" - }, - "updated_at": { - "type": "string" } } }, @@ -7704,9 +7744,6 @@ const docTemplate = `{ "dto.PigNutrientRequirementDTO": { "type": "object", "properties": { - "created_at": { - "type": "string" - }, "id": { "type": "integer" }, @@ -7724,9 +7761,6 @@ const docTemplate = `{ "nutrient_name": { "description": "营养素名称", "type": "string" - }, - "updated_at": { - "type": "string" } } }, @@ -7905,9 +7939,6 @@ const docTemplate = `{ "description": "猪品种名称", "type": "string" }, - "created_at": { - "type": "string" - }, "daily_feed_intake": { "type": "number" }, @@ -7938,9 +7969,6 @@ const docTemplate = `{ "items": { "$ref": "#/definitions/dto.PigNutrientRequirementDTO" } - }, - "updated_at": { - "type": "string" } } }, @@ -8052,9 +8080,6 @@ const docTemplate = `{ "dto.RawMaterialNutrientDTO": { "type": "object", "properties": { - "created_at": { - "type": "string" - }, "id": { "type": "integer" }, @@ -8065,21 +8090,32 @@ const docTemplate = `{ "description": "营养素名称", "type": "string" }, - "updated_at": { - "type": "string" - }, "value": { "description": "营养价值含量", "type": "number" } } }, + "dto.RawMaterialNutrientItem": { + "type": "object", + "required": [ + "nutrient_id" + ], + "properties": { + "nutrient_id": { + "description": "营养素ID", + "type": "integer" + }, + "value": { + "description": "含量值,必须大于等于0", + "type": "number", + "minimum": 0 + } + } + }, "dto.RawMaterialResponse": { "type": "object", "properties": { - "created_at": { - "type": "string" - }, "description": { "type": "string" }, @@ -8095,9 +8131,6 @@ const docTemplate = `{ "items": { "$ref": "#/definitions/dto.RawMaterialNutrientDTO" } - }, - "updated_at": { - "type": "string" } } }, @@ -8991,6 +9024,20 @@ const docTemplate = `{ } } }, + "dto.UpdateRawMaterialNutrientsRequest": { + "type": "object", + "required": [ + "nutrients" + ], + "properties": { + "nutrients": { + "type": "array", + "items": { + "$ref": "#/definitions/dto.RawMaterialNutrientItem" + } + } + } + }, "dto.UpdateRawMaterialRequest": { "type": "object", "required": [ diff --git a/docs/swagger.json b/docs/swagger.json index bf084fd..bdf18ce 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -2897,6 +2897,64 @@ } } }, + "/api/v1/feed/raw-materials/{id}/nutrients": { + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "根据原料ID,替换其所有的营养成分信息。这是一个覆盖操作。", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "饲料管理" + ], + "summary": "全量更新原料的营养成分", + "parameters": [ + { + "type": "integer", + "description": "原料ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "新的营养成分列表", + "name": "nutrients", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.UpdateRawMaterialNutrientsRequest" + } + } + ], + "responses": { + "200": { + "description": "业务码为200代表更新成功", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/controller.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/dto.RawMaterialResponse" + } + } + } + ] + } + } + } + } + }, "/api/v1/monitor/device-command-logs": { "get": { "security": [ @@ -7360,9 +7418,6 @@ "dto.NutrientResponse": { "type": "object", "properties": { - "created_at": { - "type": "string" - }, "description": { "type": "string" }, @@ -7378,9 +7433,6 @@ "items": { "$ref": "#/definitions/dto.NutrientRawMaterialDTO" } - }, - "updated_at": { - "type": "string" } } }, @@ -7453,9 +7505,6 @@ "dto.PigAgeStageResponse": { "type": "object", "properties": { - "created_at": { - "type": "string" - }, "description": { "type": "string" }, @@ -7464,9 +7513,6 @@ }, "name": { "type": "string" - }, - "updated_at": { - "type": "string" } } }, @@ -7659,9 +7705,6 @@ "breed_disadvantages": { "type": "string" }, - "created_at": { - "type": "string" - }, "description": { "type": "string" }, @@ -7673,9 +7716,6 @@ }, "parent_info": { "type": "string" - }, - "updated_at": { - "type": "string" } } }, @@ -7696,9 +7736,6 @@ "dto.PigNutrientRequirementDTO": { "type": "object", "properties": { - "created_at": { - "type": "string" - }, "id": { "type": "integer" }, @@ -7716,9 +7753,6 @@ "nutrient_name": { "description": "营养素名称", "type": "string" - }, - "updated_at": { - "type": "string" } } }, @@ -7897,9 +7931,6 @@ "description": "猪品种名称", "type": "string" }, - "created_at": { - "type": "string" - }, "daily_feed_intake": { "type": "number" }, @@ -7930,9 +7961,6 @@ "items": { "$ref": "#/definitions/dto.PigNutrientRequirementDTO" } - }, - "updated_at": { - "type": "string" } } }, @@ -8044,9 +8072,6 @@ "dto.RawMaterialNutrientDTO": { "type": "object", "properties": { - "created_at": { - "type": "string" - }, "id": { "type": "integer" }, @@ -8057,21 +8082,32 @@ "description": "营养素名称", "type": "string" }, - "updated_at": { - "type": "string" - }, "value": { "description": "营养价值含量", "type": "number" } } }, + "dto.RawMaterialNutrientItem": { + "type": "object", + "required": [ + "nutrient_id" + ], + "properties": { + "nutrient_id": { + "description": "营养素ID", + "type": "integer" + }, + "value": { + "description": "含量值,必须大于等于0", + "type": "number", + "minimum": 0 + } + } + }, "dto.RawMaterialResponse": { "type": "object", "properties": { - "created_at": { - "type": "string" - }, "description": { "type": "string" }, @@ -8087,9 +8123,6 @@ "items": { "$ref": "#/definitions/dto.RawMaterialNutrientDTO" } - }, - "updated_at": { - "type": "string" } } }, @@ -8983,6 +9016,20 @@ } } }, + "dto.UpdateRawMaterialNutrientsRequest": { + "type": "object", + "required": [ + "nutrients" + ], + "properties": { + "nutrients": { + "type": "array", + "items": { + "$ref": "#/definitions/dto.RawMaterialNutrientItem" + } + } + } + }, "dto.UpdateRawMaterialRequest": { "type": "object", "required": [ diff --git a/docs/swagger.yaml b/docs/swagger.yaml index e5e8ed7..0bf0acb 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -898,8 +898,6 @@ definitions: type: object dto.NutrientResponse: properties: - created_at: - type: string description: type: string id: @@ -911,8 +909,6 @@ definitions: items: $ref: '#/definitions/dto.NutrientRawMaterialDTO' type: array - updated_at: - type: string type: object dto.PaginationDTO: properties: @@ -959,16 +955,12 @@ definitions: type: object dto.PigAgeStageResponse: properties: - created_at: - type: string description: type: string id: type: integer name: type: string - updated_at: - type: string type: object dto.PigBatchCreateDTO: properties: @@ -1094,8 +1086,6 @@ definitions: type: string breed_disadvantages: type: string - created_at: - type: string description: type: string id: @@ -1104,8 +1094,6 @@ definitions: type: string parent_info: type: string - updated_at: - type: string type: object dto.PigHouseResponse: properties: @@ -1118,8 +1106,6 @@ definitions: type: object dto.PigNutrientRequirementDTO: properties: - created_at: - type: string id: type: integer max_requirement: @@ -1133,8 +1119,6 @@ definitions: nutrient_name: description: 营养素名称 type: string - updated_at: - type: string type: object dto.PigPurchaseDTO: properties: @@ -1252,8 +1236,6 @@ definitions: breed_name: description: 猪品种名称 type: string - created_at: - type: string daily_feed_intake: type: number daily_gain_weight: @@ -1275,8 +1257,6 @@ definitions: items: $ref: '#/definitions/dto.PigNutrientRequirementDTO' type: array - updated_at: - type: string type: object dto.PlanExecutionLogDTO: properties: @@ -1346,8 +1326,6 @@ definitions: type: object dto.RawMaterialNutrientDTO: properties: - created_at: - type: string id: type: integer nutrient_id: @@ -1355,16 +1333,24 @@ definitions: nutrient_name: description: 营养素名称 type: string - updated_at: - type: string value: description: 营养价值含量 type: number type: object + dto.RawMaterialNutrientItem: + properties: + nutrient_id: + description: 营养素ID + type: integer + value: + description: 含量值,必须大于等于0 + minimum: 0 + type: number + required: + - nutrient_id + type: object dto.RawMaterialResponse: properties: - created_at: - type: string description: type: string id: @@ -1376,8 +1362,6 @@ definitions: items: $ref: '#/definitions/dto.RawMaterialNutrientDTO' type: array - updated_at: - type: string type: object dto.ReclassifyPenToNewBatchRequest: properties: @@ -1993,6 +1977,15 @@ definitions: required: - execution_type type: object + dto.UpdateRawMaterialNutrientsRequest: + properties: + nutrients: + items: + $ref: '#/definitions/dto.RawMaterialNutrientItem' + type: array + required: + - nutrients + type: object dto.UpdateRawMaterialRequest: properties: description: @@ -4300,6 +4293,40 @@ paths: summary: 更新原料 tags: - 饲料管理 + /api/v1/feed/raw-materials/{id}/nutrients: + put: + consumes: + - application/json + description: 根据原料ID,替换其所有的营养成分信息。这是一个覆盖操作。 + parameters: + - description: 原料ID + in: path + name: id + required: true + type: integer + - description: 新的营养成分列表 + in: body + name: nutrients + required: true + schema: + $ref: '#/definitions/dto.UpdateRawMaterialNutrientsRequest' + produces: + - application/json + responses: + "200": + description: 业务码为200代表更新成功 + schema: + allOf: + - $ref: '#/definitions/controller.Response' + - properties: + data: + $ref: '#/definitions/dto.RawMaterialResponse' + type: object + security: + - BearerAuth: [] + summary: 全量更新原料的营养成分 + tags: + - 饲料管理 /api/v1/monitor/device-command-logs: get: description: 根据提供的过滤条件,分页获取设备命令日志 diff --git a/internal/app/controller/feed/feed_controller.go b/internal/app/controller/feed/feed_controller.go index 6a5e4f1..260d63f 100644 --- a/internal/app/controller/feed/feed_controller.go +++ b/internal/app/controller/feed/feed_controller.go @@ -365,6 +365,48 @@ func (c *Controller) ListRawMaterials(ctx echo.Context) error { return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取原料列表成功", resp, actionType, "获取原料列表成功", resp) } +// UpdateRawMaterialNutrients godoc +// @Summary 全量更新原料的营养成分 +// @Description 根据原料ID,替换其所有的营养成分信息。这是一个覆盖操作。 +// @Tags 饲料管理 +// @Security BearerAuth +// @Accept json +// @Produce json +// @Param id path int true "原料ID" +// @Param nutrients body dto.UpdateRawMaterialNutrientsRequest true "新的营养成分列表" +// @Success 200 {object} controller.Response{data=dto.RawMaterialResponse} "业务码为200代表更新成功" +// @Router /api/v1/feed/raw-materials/{id}/nutrients [put] +func (c *Controller) UpdateRawMaterialNutrients(ctx echo.Context) error { + reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "UpdateRawMaterialNutrients") + const actionType = "更新原料营养成分" + + idStr := ctx.Param("id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil { + logger.Errorf("%s: 原料ID格式错误: %v, ID: %s", actionType, err, idStr) + return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的原料ID格式", actionType, "原料ID格式错误", idStr) + } + + var req dto.UpdateRawMaterialNutrientsRequest + if err := ctx.Bind(&req); err != nil { + logger.Errorf("%s: 参数绑定失败: %v", actionType, err) + return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的请求体: "+err.Error(), actionType, "请求体绑定失败", req) + } + + resp, err := c.feedManagementService.UpdateRawMaterialNutrients(reqCtx, uint32(id), &req) + if err != nil { + logger.Errorf("%s: 服务层更新原料营养成分失败: %v, ID: %d", actionType, err, id) + if errors.Is(err, service.ErrRawMaterialNotFound) { + return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, err.Error(), actionType, "原料不存在", id) + } + // 这里可以根据未来可能从服务层返回的其他特定错误进行处理 + return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "更新原料营养成分失败: "+err.Error(), actionType, "服务层更新失败", req) + } + + logger.Infof("%s: 原料营养成分更新成功, ID: %d", actionType, resp.ID) + return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "原料营养成分更新成功", resp, actionType, "原料营养成分更新成功", resp) +} + // --- 猪品种 (PigBreed) 接口方法实现 --- // CreatePigBreed godoc diff --git a/internal/app/dto/feed_converter.go b/internal/app/dto/feed_converter.go index e7aabab..bca9492 100644 --- a/internal/app/dto/feed_converter.go +++ b/internal/app/dto/feed_converter.go @@ -22,8 +22,6 @@ func ConvertNutrientToDTO(nutrient *models.Nutrient) *NutrientResponse { return &NutrientResponse{ ID: nutrient.ID, - CreatedAt: nutrient.CreatedAt, - UpdatedAt: nutrient.UpdatedAt, Name: nutrient.Name, Description: nutrient.Description, RawMaterials: rawMaterials, @@ -57,8 +55,6 @@ func ConvertRawMaterialToDTO(rm *models.RawMaterial) *RawMaterialResponse { for i, rmn := range rm.RawMaterialNutrients { rawMaterialNutrientDTOs[i] = RawMaterialNutrientDTO{ ID: rmn.ID, - CreatedAt: rmn.CreatedAt, - UpdatedAt: rmn.UpdatedAt, NutrientID: rmn.NutrientID, Nutrient: rmn.Nutrient.Name, // 假设 Nutrient 已经被预加载 Value: rmn.Value, @@ -67,8 +63,6 @@ func ConvertRawMaterialToDTO(rm *models.RawMaterial) *RawMaterialResponse { return &RawMaterialResponse{ ID: rm.ID, - CreatedAt: rm.CreatedAt, - UpdatedAt: rm.UpdatedAt, Name: rm.Name, Description: rm.Description, RawMaterialNutrients: rawMaterialNutrientDTOs, @@ -99,8 +93,6 @@ func ConvertPigBreedToDTO(breed *models.PigBreed) *PigBreedResponse { } return &PigBreedResponse{ ID: breed.ID, - CreatedAt: breed.CreatedAt, - UpdatedAt: breed.UpdatedAt, Name: breed.Name, Description: breed.Description, ParentInfo: breed.ParentInfo, @@ -134,8 +126,6 @@ func ConvertPigAgeStageToDTO(ageStage *models.PigAgeStage) *PigAgeStageResponse } return &PigAgeStageResponse{ ID: ageStage.ID, - CreatedAt: ageStage.CreatedAt, - UpdatedAt: ageStage.UpdatedAt, Name: ageStage.Name, Description: ageStage.Description, } @@ -168,8 +158,6 @@ func ConvertPigTypeToDTO(pt *models.PigType) *PigTypeResponse { for i, pnr := range pt.PigNutrientRequirements { pigNutrientRequirementDTOs[i] = PigNutrientRequirementDTO{ ID: pnr.ID, - CreatedAt: pnr.CreatedAt, - UpdatedAt: pnr.UpdatedAt, NutrientID: pnr.NutrientID, NutrientName: pnr.Nutrient.Name, // 假设 Nutrient 已经被预加载 MinRequirement: pnr.MinRequirement, @@ -179,8 +167,6 @@ func ConvertPigTypeToDTO(pt *models.PigType) *PigTypeResponse { return &PigTypeResponse{ ID: pt.ID, - CreatedAt: pt.CreatedAt, - UpdatedAt: pt.UpdatedAt, BreedID: pt.BreedID, BreedName: pt.Breed.Name, // 假设 Breed 已经被预加载 AgeStageID: pt.AgeStageID, diff --git a/internal/app/dto/feed_dto.go b/internal/app/dto/feed_dto.go index db30316..57d6f18 100644 --- a/internal/app/dto/feed_dto.go +++ b/internal/app/dto/feed_dto.go @@ -1,9 +1,5 @@ package dto -import ( - "time" -) - // ============================================================================================================= // 营养种类 (Nutrient) 相关 DTO // ============================================================================================================= @@ -30,8 +26,6 @@ type NutrientRawMaterialDTO struct { // NutrientResponse 营养种类响应体 type NutrientResponse struct { ID uint32 `json:"id"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` Name string `json:"name"` Description string `json:"description"` RawMaterials []NutrientRawMaterialDTO `json:"raw_materials"` // 包含此营养的原料列表 @@ -70,19 +64,15 @@ type UpdateRawMaterialRequest struct { // RawMaterialNutrientDTO 原料营养素响应体 type RawMaterialNutrientDTO struct { - ID uint32 `json:"id"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - NutrientID uint32 `json:"nutrient_id"` - Nutrient string `json:"nutrient_name"` // 营养素名称 - Value float32 `json:"value"` // 营养价值含量 + ID uint32 `json:"id"` + NutrientID uint32 `json:"nutrient_id"` + Nutrient string `json:"nutrient_name"` // 营养素名称 + Value float32 `json:"value"` // 营养价值含量 } // RawMaterialResponse 原料响应体 type RawMaterialResponse struct { ID uint32 `json:"id"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` Name string `json:"name"` Description string `json:"description"` RawMaterialNutrients []RawMaterialNutrientDTO `json:"raw_material_nutrients"` // 关联的营养素信息 @@ -103,6 +93,17 @@ type ListRawMaterialResponse struct { Pagination PaginationDTO `json:"pagination"` } +// UpdateRawMaterialNutrientsRequest 更新原料营养成分的请求体 +type UpdateRawMaterialNutrientsRequest struct { + Nutrients []RawMaterialNutrientItem `json:"nutrients" validate:"required,dive"` +} + +// RawMaterialNutrientItem 代表一个营养成分及其含量 +type RawMaterialNutrientItem struct { + NutrientID uint32 `json:"nutrient_id" validate:"required"` // 营养素ID + Value float32 `json:"value" validate:"gte=0"` // 含量值,必须大于等于0 +} + // ============================================================================================================= // 猪品种 (PigBreed) 相关 DTO // ============================================================================================================= @@ -129,15 +130,13 @@ type UpdatePigBreedRequest struct { // PigBreedResponse 猪品种响应体 type PigBreedResponse struct { - ID uint32 `json:"id"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - Name string `json:"name"` - Description string `json:"description"` - ParentInfo string `json:"parent_info"` - AppearanceFeatures string `json:"appearance_features"` - BreedAdvantages string `json:"breed_advantages"` - BreedDisadvantages string `json:"breed_disadvantages"` + ID uint32 `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + ParentInfo string `json:"parent_info"` + AppearanceFeatures string `json:"appearance_features"` + BreedAdvantages string `json:"breed_advantages"` + BreedDisadvantages string `json:"breed_disadvantages"` } // ListPigBreedRequest 定义了获取猪品种列表的请求参数 @@ -172,11 +171,9 @@ type UpdatePigAgeStageRequest struct { // PigAgeStageResponse 猪年龄阶段响应体 type PigAgeStageResponse struct { - ID uint32 `json:"id"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - Name string `json:"name"` - Description string `json:"description"` + ID uint32 `json:"id"` + Name string `json:"name"` + Description string `json:"description"` } // ListPigAgeStageRequest 定义了获取猪年龄阶段列表的请求参数 @@ -225,20 +222,16 @@ type UpdatePigTypeRequest struct { // PigNutrientRequirementDTO 猪营养需求响应体 type PigNutrientRequirementDTO struct { - ID uint32 `json:"id"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - NutrientID uint32 `json:"nutrient_id"` - NutrientName string `json:"nutrient_name"` // 营养素名称 - MinRequirement float32 `json:"min_requirement"` // 最低营养需求量 - MaxRequirement float32 `json:"max_requirement"` // 最高营养需求量 + ID uint32 `json:"id"` + NutrientID uint32 `json:"nutrient_id"` + NutrientName string `json:"nutrient_name"` // 营养素名称 + MinRequirement float32 `json:"min_requirement"` // 最低营养需求量 + MaxRequirement float32 `json:"max_requirement"` // 最高营养需求量 } // PigTypeResponse 猪类型响应体 type PigTypeResponse struct { ID uint32 `json:"id"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` BreedID uint32 `json:"breed_id"` BreedName string `json:"breed_name"` // 猪品种名称 AgeStageID uint32 `json:"age_stage_id"` diff --git a/internal/app/service/feed_management_service.go b/internal/app/service/feed_management_service.go index f00dc46..ba2eb62 100644 --- a/internal/app/service/feed_management_service.go +++ b/internal/app/service/feed_management_service.go @@ -40,6 +40,7 @@ type FeedManagementService interface { DeleteRawMaterial(ctx context.Context, id uint32) error GetRawMaterial(ctx context.Context, id uint32) (*dto.RawMaterialResponse, error) ListRawMaterials(ctx context.Context, req *dto.ListRawMaterialRequest) (*dto.ListRawMaterialResponse, error) + UpdateRawMaterialNutrients(ctx context.Context, id uint32, req *dto.UpdateRawMaterialNutrientsRequest) (*dto.RawMaterialResponse, error) // 新增 // 猪品种相关 CreatePigBreed(ctx context.Context, req *dto.CreatePigBreedRequest) (*dto.PigBreedResponse, error) @@ -236,6 +237,43 @@ func (s *feedManagementServiceImpl) ListRawMaterials(ctx context.Context, req *d return dto.ConvertRawMaterialListToDTO(rawMaterials, total, req.Page, req.PageSize), nil } +// UpdateRawMaterialNutrients 全量更新原料的营养成分 +func (s *feedManagementServiceImpl) UpdateRawMaterialNutrients(ctx context.Context, id uint32, req *dto.UpdateRawMaterialNutrientsRequest) (*dto.RawMaterialResponse, error) { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "UpdateRawMaterialNutrients") + + // 1. 将 DTO 转换为领域模型 + nutrients := make([]models.RawMaterialNutrient, len(req.Nutrients)) + for i, item := range req.Nutrients { + nutrients[i] = models.RawMaterialNutrient{ + NutrientID: item.NutrientID, + Value: item.Value, + } + } + + // 2. 调用领域服务执行更新命令 + err := s.recipeSvc.UpdateRawMaterialNutrients(serviceCtx, id, nutrients) + if err != nil { + if errors.Is(err, recipe.ErrRawMaterialNotFound) { + return nil, ErrRawMaterialNotFound + } + // 此处可以根据领域层可能返回的其他特定错误进行转换 + return nil, fmt.Errorf("更新原料营养成分失败: %w", err) + } + + // 3. 更新成功后,调用查询服务获取最新的原料信息 + updatedRawMaterial, err := s.recipeSvc.GetRawMaterial(serviceCtx, id) + if err != nil { + if errors.Is(err, recipe.ErrRawMaterialNotFound) { + // 理论上不应该发生,因为刚更新成功 + return nil, ErrRawMaterialNotFound + } + return nil, fmt.Errorf("更新后获取原料信息失败: %w", err) + } + + // 4. 将领域模型转换为 DTO 并返回 + return dto.ConvertRawMaterialToDTO(updatedRawMaterial), nil +} + // ===================================================================================================================== // 猪品种 (PigBreed) 实现 // ===================================================================================================================== diff --git a/internal/core/component_initializers.go b/internal/core/component_initializers.go index 10b213d..b354386 100644 --- a/internal/core/component_initializers.go +++ b/internal/core/component_initializers.go @@ -217,6 +217,7 @@ func initDomainServices(ctx context.Context, cfg *config.Config, infra *Infrastr // 配方管理服务 recipeService := recipe.NewRecipeService( logs.AddCompName(baseCtx, "RecipeService"), + infra.repos.unitOfWork, infra.repos.nutrientRepo, infra.repos.rawMaterialRepo, infra.repos.pigTypeRepo, diff --git a/internal/domain/recipe/recipe_service.go b/internal/domain/recipe/recipe_service.go index ff0b66f..3a210df 100644 --- a/internal/domain/recipe/recipe_service.go +++ b/internal/domain/recipe/recipe_service.go @@ -40,6 +40,7 @@ type Service interface { DeleteRawMaterial(ctx context.Context, id uint32) error GetRawMaterial(ctx context.Context, id uint32) (*models.RawMaterial, error) ListRawMaterials(ctx context.Context, opts repository.RawMaterialListOptions, page, pageSize int) ([]models.RawMaterial, int64, error) + UpdateRawMaterialNutrients(ctx context.Context, rawMaterialID uint32, nutrients []models.RawMaterialNutrient) error // 猪品种相关接口 CreatePigBreed(ctx context.Context, breed *models.PigBreed) error @@ -66,15 +67,17 @@ type Service interface { // recipeServiceImpl 是 RecipeService 的实现 type recipeServiceImpl struct { ctx context.Context + uow repository.UnitOfWork nutrientRepo repository.NutrientRepository rawMaterialRepo repository.RawMaterialRepository pigTypeRepo repository.PigTypeRepository } // NewRecipeService 创建一个新的 RecipeService 实例 -func NewRecipeService(ctx context.Context, nutrientRepo repository.NutrientRepository, rawMaterialRepo repository.RawMaterialRepository, pigTypeRepo repository.PigTypeRepository) Service { +func NewRecipeService(ctx context.Context, uow repository.UnitOfWork, nutrientRepo repository.NutrientRepository, rawMaterialRepo repository.RawMaterialRepository, pigTypeRepo repository.PigTypeRepository) Service { return &recipeServiceImpl{ ctx: ctx, + uow: uow, nutrientRepo: nutrientRepo, rawMaterialRepo: rawMaterialRepo, pigTypeRepo: pigTypeRepo, @@ -289,6 +292,41 @@ func (s *recipeServiceImpl) ListRawMaterials(ctx context.Context, opts repositor return rawMaterials, total, nil } +// UpdateRawMaterialNutrients 实现了全量更新原料营养成分的业务逻辑 +func (s *recipeServiceImpl) UpdateRawMaterialNutrients(ctx context.Context, rawMaterialID uint32, nutrients []models.RawMaterialNutrient) error { + serviceCtx := logs.AddFuncName(ctx, s.ctx, "UpdateRawMaterialNutrients") + + // 1. 检查原料是否存在 + if _, err := s.rawMaterialRepo.GetRawMaterialByID(serviceCtx, rawMaterialID); err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return ErrRawMaterialNotFound + } + return fmt.Errorf("获取待更新的原料失败: %w", err) + } + + // 2. 在事务中执行替换操作 + err := s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error { + // 2.1. 删除旧的关联记录 + if err := s.rawMaterialRepo.DeleteNutrientsByRawMaterialIDTx(serviceCtx, tx, rawMaterialID); err != nil { + return err // 错误已在仓库层封装,直接返回 + } + + // 2.2. 创建新的关联记录 + if err := s.rawMaterialRepo.CreateBatchRawMaterialNutrientsTx(serviceCtx, tx, nutrients); err != nil { + return err // 错误已在仓库层封装,直接返回 + } + + return nil + }) + + if err != nil { + return fmt.Errorf("更新原料营养成分事务执行失败: %w", err) + } + + // 3. 操作成功,直接返回 nil + return nil +} + // CreatePigBreed 实现了创建猪品种的核心业务逻辑 func (s *recipeServiceImpl) CreatePigBreed(ctx context.Context, breed *models.PigBreed) error { serviceCtx := logs.AddFuncName(ctx, s.ctx, "CreatePigBreed") diff --git a/internal/infra/repository/raw_material_repository.go b/internal/infra/repository/raw_material_repository.go index efff010..3df2102 100644 --- a/internal/infra/repository/raw_material_repository.go +++ b/internal/infra/repository/raw_material_repository.go @@ -26,6 +26,8 @@ type RawMaterialRepository interface { 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 @@ -162,6 +164,30 @@ func (r *gormRawMaterialRepository) DeleteRawMaterial(ctx context.Context, id ui }) } +// 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 { + // 如果没有要创建的记录,直接返回成功,避免执行空的Create语句 + if len(nutrients) == 0 { + return nil + } + repoCtx := logs.AddFuncName(ctx, r.ctx, "CreateBatchRawMaterialNutrientsTx") + tx := db.WithContext(repoCtx) + if err := tx.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")