diff --git a/docs/docs.go b/docs/docs.go index 5826ff3..d74aae3 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -947,6 +947,83 @@ const docTemplate = `{ } } }, + "/api/v1/pig-batches/{batchID}/remove-pen/{penID}": { + "delete": { + "description": "将一个空闲猪栏从指定的猪批次中移除", + "produces": [ + "application/json" + ], + "tags": [ + "猪批次管理" + ], + "summary": "从猪批次移除空栏", + "parameters": [ + { + "type": "integer", + "description": "猪批次ID", + "name": "batchID", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "待移除的猪栏ID", + "name": "penID", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "移除成功", + "schema": { + "$ref": "#/definitions/controller.Response" + } + } + } + } + }, + "/api/v1/pig-batches/{fromBatchID}/reclassify-pen": { + "post": { + "description": "将一个猪栏(连同其中的猪只)从一个批次整体划拨到另一个批次", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "猪批次管理" + ], + "summary": "将猪栏划拨到新批次", + "parameters": [ + { + "type": "integer", + "description": "源猪批次ID", + "name": "fromBatchID", + "in": "path", + "required": true + }, + { + "description": "划拨请求信息 (包含目标批次ID、猪栏ID和备注)", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ReclassifyPenToNewBatchRequest" + } + } + ], + "responses": { + "200": { + "description": "划拨成功", + "schema": { + "$ref": "#/definitions/controller.Response" + } + } + } + } + }, "/api/v1/pig-batches/{id}": { "get": { "description": "根据ID获取单个猪批次信息", @@ -1066,9 +1143,9 @@ const docTemplate = `{ } } }, - "/api/v1/pig-batches/{id}/pens": { - "put": { - "description": "更新指定猪批次当前关联的猪栏列表", + "/api/v1/pig-batches/{id}/assign-pens": { + "post": { + "description": "将一个或多个空闲猪栏分配给指定的猪批次", "consumes": [ "application/json" ], @@ -1078,7 +1155,7 @@ const docTemplate = `{ "tags": [ "猪批次管理" ], - "summary": "更新猪批次关联的猪栏", + "summary": "为猪批次分配空栏", "parameters": [ { "type": "integer", @@ -1088,18 +1165,59 @@ const docTemplate = `{ "required": true }, { - "description": "猪批次关联的猪栏ID列表", + "description": "待分配的猪栏ID列表", "name": "body", "in": "body", "required": true, "schema": { - "$ref": "#/definitions/dto.PigBatchUpdatePensRequest" + "$ref": "#/definitions/dto.AssignEmptyPensToBatchRequest" } } ], "responses": { "200": { - "description": "更新成功", + "description": "分配成功", + "schema": { + "$ref": "#/definitions/controller.Response" + } + } + } + } + }, + "/api/v1/pig-batches/{id}/move-pigs-into-pen": { + "post": { + "description": "将指定数量的猪只从批次的“虚拟库存”移入一个已分配的猪栏", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "猪批次管理" + ], + "summary": "将猪只从“虚拟库存”移入指定猪栏", + "parameters": [ + { + "type": "integer", + "description": "猪批次ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "移入猪只请求信息 (包含目标猪栏ID、数量和备注)", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.MovePigsIntoPenRequest" + } + } + ], + "responses": { + "200": { + "description": "移入成功", "schema": { "$ref": "#/definitions/controller.Response" } @@ -1809,6 +1927,9 @@ const docTemplate = `{ } } }, + "dto.AssignEmptyPensToBatchRequest": { + "type": "object" + }, "dto.CreateAreaControllerRequest": { "type": "object", "required": [ @@ -2144,6 +2265,28 @@ const docTemplate = `{ } } }, + "dto.MovePigsIntoPenRequest": { + "type": "object", + "required": [ + "quantity", + "toPenID" + ], + "properties": { + "quantity": { + "description": "移入猪只数量", + "type": "integer", + "minimum": 1 + }, + "remarks": { + "description": "备注", + "type": "string" + }, + "toPenID": { + "description": "目标猪栏ID", + "type": "integer" + } + } + }, "dto.PenResponse": { "type": "object", "properties": { @@ -2298,9 +2441,6 @@ const docTemplate = `{ } } }, - "dto.PigBatchUpdatePensRequest": { - "type": "object" - }, "dto.PigHouseResponse": { "type": "object", "properties": { @@ -2380,6 +2520,27 @@ const docTemplate = `{ } } }, + "dto.ReclassifyPenToNewBatchRequest": { + "type": "object", + "required": [ + "penID", + "toBatchID" + ], + "properties": { + "penID": { + "description": "待划拨的猪栏ID", + "type": "integer" + }, + "remarks": { + "description": "备注", + "type": "string" + }, + "toBatchID": { + "description": "目标猪批次ID", + "type": "integer" + } + } + }, "dto.SubPlanResponse": { "type": "object", "properties": { diff --git a/docs/swagger.json b/docs/swagger.json index 44315f0..3cad7f5 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -936,6 +936,83 @@ } } }, + "/api/v1/pig-batches/{batchID}/remove-pen/{penID}": { + "delete": { + "description": "将一个空闲猪栏从指定的猪批次中移除", + "produces": [ + "application/json" + ], + "tags": [ + "猪批次管理" + ], + "summary": "从猪批次移除空栏", + "parameters": [ + { + "type": "integer", + "description": "猪批次ID", + "name": "batchID", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "待移除的猪栏ID", + "name": "penID", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "移除成功", + "schema": { + "$ref": "#/definitions/controller.Response" + } + } + } + } + }, + "/api/v1/pig-batches/{fromBatchID}/reclassify-pen": { + "post": { + "description": "将一个猪栏(连同其中的猪只)从一个批次整体划拨到另一个批次", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "猪批次管理" + ], + "summary": "将猪栏划拨到新批次", + "parameters": [ + { + "type": "integer", + "description": "源猪批次ID", + "name": "fromBatchID", + "in": "path", + "required": true + }, + { + "description": "划拨请求信息 (包含目标批次ID、猪栏ID和备注)", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ReclassifyPenToNewBatchRequest" + } + } + ], + "responses": { + "200": { + "description": "划拨成功", + "schema": { + "$ref": "#/definitions/controller.Response" + } + } + } + } + }, "/api/v1/pig-batches/{id}": { "get": { "description": "根据ID获取单个猪批次信息", @@ -1055,9 +1132,9 @@ } } }, - "/api/v1/pig-batches/{id}/pens": { - "put": { - "description": "更新指定猪批次当前关联的猪栏列表", + "/api/v1/pig-batches/{id}/assign-pens": { + "post": { + "description": "将一个或多个空闲猪栏分配给指定的猪批次", "consumes": [ "application/json" ], @@ -1067,7 +1144,7 @@ "tags": [ "猪批次管理" ], - "summary": "更新猪批次关联的猪栏", + "summary": "为猪批次分配空栏", "parameters": [ { "type": "integer", @@ -1077,18 +1154,59 @@ "required": true }, { - "description": "猪批次关联的猪栏ID列表", + "description": "待分配的猪栏ID列表", "name": "body", "in": "body", "required": true, "schema": { - "$ref": "#/definitions/dto.PigBatchUpdatePensRequest" + "$ref": "#/definitions/dto.AssignEmptyPensToBatchRequest" } } ], "responses": { "200": { - "description": "更新成功", + "description": "分配成功", + "schema": { + "$ref": "#/definitions/controller.Response" + } + } + } + } + }, + "/api/v1/pig-batches/{id}/move-pigs-into-pen": { + "post": { + "description": "将指定数量的猪只从批次的“虚拟库存”移入一个已分配的猪栏", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "猪批次管理" + ], + "summary": "将猪只从“虚拟库存”移入指定猪栏", + "parameters": [ + { + "type": "integer", + "description": "猪批次ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "移入猪只请求信息 (包含目标猪栏ID、数量和备注)", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.MovePigsIntoPenRequest" + } + } + ], + "responses": { + "200": { + "description": "移入成功", "schema": { "$ref": "#/definitions/controller.Response" } @@ -1798,6 +1916,9 @@ } } }, + "dto.AssignEmptyPensToBatchRequest": { + "type": "object" + }, "dto.CreateAreaControllerRequest": { "type": "object", "required": [ @@ -2133,6 +2254,28 @@ } } }, + "dto.MovePigsIntoPenRequest": { + "type": "object", + "required": [ + "quantity", + "toPenID" + ], + "properties": { + "quantity": { + "description": "移入猪只数量", + "type": "integer", + "minimum": 1 + }, + "remarks": { + "description": "备注", + "type": "string" + }, + "toPenID": { + "description": "目标猪栏ID", + "type": "integer" + } + } + }, "dto.PenResponse": { "type": "object", "properties": { @@ -2287,9 +2430,6 @@ } } }, - "dto.PigBatchUpdatePensRequest": { - "type": "object" - }, "dto.PigHouseResponse": { "type": "object", "properties": { @@ -2369,6 +2509,27 @@ } } }, + "dto.ReclassifyPenToNewBatchRequest": { + "type": "object", + "required": [ + "penID", + "toBatchID" + ], + "properties": { + "penID": { + "description": "待划拨的猪栏ID", + "type": "integer" + }, + "remarks": { + "description": "备注", + "type": "string" + }, + "toBatchID": { + "description": "目标猪批次ID", + "type": "integer" + } + } + }, "dto.SubPlanResponse": { "type": "object", "properties": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index aa93e84..65a1b65 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -69,6 +69,8 @@ definitions: updated_at: type: string type: object + dto.AssignEmptyPensToBatchRequest: + type: object dto.CreateAreaControllerRequest: properties: location: @@ -298,6 +300,22 @@ definitions: example: testuser type: string type: object + dto.MovePigsIntoPenRequest: + properties: + quantity: + description: 移入猪只数量 + minimum: 1 + type: integer + remarks: + description: 备注 + type: string + toPenID: + description: 目标猪栏ID + type: integer + required: + - quantity + - toPenID + type: object dto.PenResponse: properties: capacity: @@ -398,8 +416,6 @@ definitions: - $ref: '#/definitions/models.PigBatchStatus' description: 批次状态,可选 type: object - dto.PigBatchUpdatePensRequest: - type: object dto.PigHouseResponse: properties: description: @@ -450,6 +466,21 @@ definitions: $ref: '#/definitions/dto.TaskResponse' type: array type: object + dto.ReclassifyPenToNewBatchRequest: + properties: + penID: + description: 待划拨的猪栏ID + type: integer + remarks: + description: 备注 + type: string + toBatchID: + description: 目标猪批次ID + type: integer + required: + - penID + - toBatchID + type: object dto.SubPlanResponse: properties: child_plan: @@ -1373,6 +1404,57 @@ paths: summary: 创建猪批次 tags: - 猪批次管理 + /api/v1/pig-batches/{batchID}/remove-pen/{penID}: + delete: + description: 将一个空闲猪栏从指定的猪批次中移除 + parameters: + - description: 猪批次ID + in: path + name: batchID + required: true + type: integer + - description: 待移除的猪栏ID + in: path + name: penID + required: true + type: integer + produces: + - application/json + responses: + "200": + description: 移除成功 + schema: + $ref: '#/definitions/controller.Response' + summary: 从猪批次移除空栏 + tags: + - 猪批次管理 + /api/v1/pig-batches/{fromBatchID}/reclassify-pen: + post: + consumes: + - application/json + description: 将一个猪栏(连同其中的猪只)从一个批次整体划拨到另一个批次 + parameters: + - description: 源猪批次ID + in: path + name: fromBatchID + required: true + type: integer + - description: 划拨请求信息 (包含目标批次ID、猪栏ID和备注) + in: body + name: body + required: true + schema: + $ref: '#/definitions/dto.ReclassifyPenToNewBatchRequest' + produces: + - application/json + responses: + "200": + description: 划拨成功 + schema: + $ref: '#/definitions/controller.Response' + summary: 将猪栏划拨到新批次 + tags: + - 猪批次管理 /api/v1/pig-batches/{id}: delete: description: 根据ID删除一个猪批次 @@ -1446,31 +1528,58 @@ paths: summary: 更新猪批次 tags: - 猪批次管理 - /api/v1/pig-batches/{id}/pens: - put: + /api/v1/pig-batches/{id}/assign-pens: + post: consumes: - application/json - description: 更新指定猪批次当前关联的猪栏列表 + description: 将一个或多个空闲猪栏分配给指定的猪批次 parameters: - description: 猪批次ID in: path name: id required: true type: integer - - description: 猪批次关联的猪栏ID列表 + - description: 待分配的猪栏ID列表 in: body name: body required: true schema: - $ref: '#/definitions/dto.PigBatchUpdatePensRequest' + $ref: '#/definitions/dto.AssignEmptyPensToBatchRequest' produces: - application/json responses: "200": - description: 更新成功 + description: 分配成功 schema: $ref: '#/definitions/controller.Response' - summary: 更新猪批次关联的猪栏 + summary: 为猪批次分配空栏 + tags: + - 猪批次管理 + /api/v1/pig-batches/{id}/move-pigs-into-pen: + post: + consumes: + - application/json + description: 将指定数量的猪只从批次的“虚拟库存”移入一个已分配的猪栏 + parameters: + - description: 猪批次ID + in: path + name: id + required: true + type: integer + - description: 移入猪只请求信息 (包含目标猪栏ID、数量和备注) + in: body + name: body + required: true + schema: + $ref: '#/definitions/dto.MovePigsIntoPenRequest' + produces: + - application/json + responses: + "200": + description: 移入成功 + schema: + $ref: '#/definitions/controller.Response' + summary: 将猪只从“虚拟库存”移入指定猪栏 tags: - 猪批次管理 /api/v1/pig-houses: diff --git a/internal/app/api/api.go b/internal/app/api/api.go index 3533ecc..745a036 100644 --- a/internal/app/api/api.go +++ b/internal/app/api/api.go @@ -234,7 +234,10 @@ func (a *API) setupRoutes() { pigBatchGroup.GET("/:id", a.pigBatchController.GetPigBatch) pigBatchGroup.PUT("/:id", a.pigBatchController.UpdatePigBatch) pigBatchGroup.DELETE("/:id", a.pigBatchController.DeletePigBatch) - pigBatchGroup.PUT("/:id/pens", a.pigBatchController.UpdatePigBatchPens) + pigBatchGroup.POST("/:id/assign-pens", a.pigBatchController.AssignEmptyPensToBatch) + pigBatchGroup.POST("/:fromBatchID/reclassify-pen", a.pigBatchController.ReclassifyPenToNewBatch) + pigBatchGroup.DELETE("/:batchID/remove-pen/:penID", a.pigBatchController.RemoveEmptyPenFromBatch) + pigBatchGroup.POST("/:id/move-pigs-into-pen", a.pigBatchController.MovePigsIntoPen) } a.logger.Info("猪批次相关接口注册成功 (需要认证和审计)") diff --git a/internal/app/controller/management/pig_batch_controller.go b/internal/app/controller/management/pig_batch_controller.go index a08f83d..45d7e00 100644 --- a/internal/app/controller/management/pig_batch_controller.go +++ b/internal/app/controller/management/pig_batch_controller.go @@ -179,43 +179,171 @@ func (c *PigBatchController) ListPigBatches(ctx *gin.Context) { controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取成功", respDTOs, action, "获取成功", respDTOs) } -// UpdatePigBatchPens godoc -// @Summary 更新猪批次关联的猪栏 -// @Description 更新指定猪批次当前关联的猪栏列表 +// AssignEmptyPensToBatch godoc +// @Summary 为猪批次分配空栏 +// @Description 将一个或多个空闲猪栏分配给指定的猪批次 // @Tags 猪批次管理 // @Accept json // @Produce json // @Param id path int true "猪批次ID" -// @Param body body dto.PigBatchUpdatePensRequest true "猪批次关联的猪栏ID列表" -// @Success 200 {object} controller.Response "更新成功" -// @Router /api/v1/pig-batches/{id}/pens [put] -func (c *PigBatchController) UpdatePigBatchPens(ctx *gin.Context) { - const action = "更新猪批次关联猪栏" +// @Param body body dto.AssignEmptyPensToBatchRequest true "待分配的猪栏ID列表" +// @Success 200 {object} controller.Response "分配成功" +// @Router /api/v1/pig-batches/{id}/assign-pens [post] +func (c *PigBatchController) AssignEmptyPensToBatch(ctx *gin.Context) { + const action = "为猪批次分配空栏" batchID, err := strconv.ParseUint(ctx.Param("id"), 10, 32) if err != nil { controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的猪批次ID格式", action, "ID格式错误", ctx.Param("id")) return } - var req dto.PigBatchUpdatePensRequest + var req dto.AssignEmptyPensToBatchRequest if err := ctx.ShouldBindJSON(&req); err != nil { controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的请求体", action, "请求体绑定失败", req) return } - err = c.service.UpdatePigBatchPens(uint(batchID), req.PenIDs) + userID, err := controller.GetOperatorIDFromContext(ctx) + + err = c.service.AssignEmptyPensToBatch(uint(batchID), req.PenIDs, userID) if err != nil { if errors.Is(err, service.ErrPigBatchNotFound) || errors.Is(err, service.ErrPenNotFound) { controller.SendErrorWithAudit(ctx, controller.CodeNotFound, err.Error(), action, err.Error(), batchID) return - } else if errors.Is(err, service.ErrPigBatchNotActive) || errors.Is(err, service.ErrPenOccupiedByOtherBatch) || errors.Is(err, service.ErrPenStatusInvalidForAllocation) || errors.Is(err, service.ErrPenNotAssociatedWithBatch) { + } else if errors.Is(err, service.ErrPigBatchNotActive) || errors.Is(err, service.ErrPenOccupiedByOtherBatch) || errors.Is(err, service.ErrPenStatusInvalidForAllocation) { controller.SendErrorWithAudit(ctx, controller.CodeConflict, err.Error(), action, err.Error(), batchID) return } c.logger.Errorf("%s: 业务逻辑失败: %v", action, err) - controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "更新猪批次关联猪栏失败", action, err.Error(), batchID) + controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "分配空栏失败", action, err.Error(), batchID) return } - controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "更新成功", nil, action, "更新成功", batchID) + controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "分配成功", nil, action, "分配成功", batchID) +} + +// ReclassifyPenToNewBatch godoc +// @Summary 将猪栏划拨到新批次 +// @Description 将一个猪栏(连同其中的猪只)从一个批次整体划拨到另一个批次 +// @Tags 猪批次管理 +// @Accept json +// @Produce json +// @Param fromBatchID path int true "源猪批次ID" +// @Param body body dto.ReclassifyPenToNewBatchRequest true "划拨请求信息 (包含目标批次ID、猪栏ID和备注)" +// @Success 200 {object} controller.Response "划拨成功" +// @Router /api/v1/pig-batches/{fromBatchID}/reclassify-pen [post] +func (c *PigBatchController) ReclassifyPenToNewBatch(ctx *gin.Context) { + const action = "划拨猪栏到新批次" + fromBatchID, err := strconv.ParseUint(ctx.Param("fromBatchID"), 10, 32) + if err != nil { + controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的源猪批次ID格式", action, "ID格式错误", ctx.Param("fromBatchID")) + return + } + + var req dto.ReclassifyPenToNewBatchRequest + if err := ctx.ShouldBindJSON(&req); err != nil { + controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的请求体", action, "请求体绑定失败", req) + return + } + + userID, err := controller.GetOperatorIDFromContext(ctx) + + err = c.service.ReclassifyPenToNewBatch(uint(fromBatchID), req.ToBatchID, req.PenID, userID, req.Remarks) + if err != nil { + if errors.Is(err, service.ErrPigBatchNotFound) || errors.Is(err, service.ErrPenNotFound) { + controller.SendErrorWithAudit(ctx, controller.CodeNotFound, err.Error(), action, err.Error(), fromBatchID) + return + } else if errors.Is(err, service.ErrPigBatchNotActive) || errors.Is(err, service.ErrPenOccupiedByOtherBatch) || errors.Is(err, service.ErrPenNotAssociatedWithBatch) || errors.Is(err, service.ErrInvalidOperation) { + controller.SendErrorWithAudit(ctx, controller.CodeConflict, err.Error(), action, err.Error(), fromBatchID) + return + } + c.logger.Errorf("%s: 业务逻辑失败: %v", action, err) + controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "划拨猪栏失败", action, err.Error(), fromBatchID) + return + } + + controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "划拨成功", nil, action, "划拨成功", fromBatchID) +} + +// RemoveEmptyPenFromBatch godoc +// @Summary 从猪批次移除空栏 +// @Description 将一个空闲猪栏从指定的猪批次中移除 +// @Tags 猪批次管理 +// @Produce json +// @Param batchID path int true "猪批次ID" +// @Param penID path int true "待移除的猪栏ID" +// @Success 200 {object} controller.Response "移除成功" +// @Router /api/v1/pig-batches/{batchID}/remove-pen/{penID} [delete] +func (c *PigBatchController) RemoveEmptyPenFromBatch(ctx *gin.Context) { + const action = "从猪批次移除空栏" + batchID, err := strconv.ParseUint(ctx.Param("batchID"), 10, 32) + if err != nil { + controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的猪批次ID格式", action, "ID格式错误", ctx.Param("batchID")) + return + } + + penID, err := strconv.ParseUint(ctx.Param("penID"), 10, 32) + if err != nil { + controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的猪栏ID格式", action, "ID格式错误", ctx.Param("penID")) + return + } + + err = c.service.RemoveEmptyPenFromBatch(uint(batchID), uint(penID)) + if err != nil { + if errors.Is(err, service.ErrPigBatchNotFound) || errors.Is(err, service.ErrPenNotFound) || errors.Is(err, service.ErrPenNotAssociatedWithBatch) { + controller.SendErrorWithAudit(ctx, controller.CodeNotFound, err.Error(), action, err.Error(), batchID) + return + } else if errors.Is(err, service.ErrPigBatchNotActive) || errors.Is(err, service.ErrPenNotEmpty) { + controller.SendErrorWithAudit(ctx, controller.CodeConflict, err.Error(), action, err.Error(), batchID) + return + } + c.logger.Errorf("%s: 业务逻辑失败: %v", action, err) + controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "移除空栏失败", action, err.Error(), batchID) + return + } + + controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "移除成功", nil, action, "移除成功", batchID) +} + +// MovePigsIntoPen godoc +// @Summary 将猪只从“虚拟库存”移入指定猪栏 +// @Description 将指定数量的猪只从批次的“虚拟库存”移入一个已分配的猪栏 +// @Tags 猪批次管理 +// @Accept json +// @Produce json +// @Param id path int true "猪批次ID" +// @Param body body dto.MovePigsIntoPenRequest true "移入猪只请求信息 (包含目标猪栏ID、数量和备注)" +// @Success 200 {object} controller.Response "移入成功" +// @Router /api/v1/pig-batches/{id}/move-pigs-into-pen [post] +func (c *PigBatchController) MovePigsIntoPen(ctx *gin.Context) { + const action = "将猪只移入猪栏" + batchID, err := strconv.ParseUint(ctx.Param("id"), 10, 32) + if err != nil { + controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的猪批次ID格式", action, "ID格式错误", ctx.Param("id")) + return + } + + var req dto.MovePigsIntoPenRequest + if err := ctx.ShouldBindJSON(&req); err != nil { + controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的请求体", action, "请求体绑定失败", req) + return + } + + userID, err := controller.GetOperatorIDFromContext(ctx) + + err = c.service.MovePigsIntoPen(uint(batchID), req.ToPenID, req.Quantity, userID, req.Remarks) + if err != nil { + if errors.Is(err, service.ErrPigBatchNotFound) || errors.Is(err, service.ErrPenNotFound) { + controller.SendErrorWithAudit(ctx, controller.CodeNotFound, err.Error(), action, err.Error(), batchID) + return + } else if errors.Is(err, service.ErrPigBatchNotActive) || errors.Is(err, service.ErrInvalidOperation) { + controller.SendErrorWithAudit(ctx, controller.CodeConflict, err.Error(), action, err.Error(), batchID) + return + } + c.logger.Errorf("%s: 业务逻辑失败: %v", action, err) + controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "移入猪只失败", action, err.Error(), batchID) + return + } + + controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "移入成功", nil, action, "移入成功", batchID) } diff --git a/internal/app/dto/pig_batch_dto.go b/internal/app/dto/pig_batch_dto.go index 1587bd1..47b539e 100644 --- a/internal/app/dto/pig_batch_dto.go +++ b/internal/app/dto/pig_batch_dto.go @@ -44,7 +44,26 @@ type PigBatchResponseDTO struct { UpdateTime time.Time `json:"update_time"` // 更新时间 } -// PigBatchUpdatePensRequest 用于更新猪批次关联猪栏的请求体 -type PigBatchUpdatePensRequest struct { - PenIDs []uint `json:"penIDs" binding:"required,min=0" example:"[1,2,3]"` +// AssignEmptyPensToBatchRequest 用于为猪批次分配空栏的请求体 +type AssignEmptyPensToBatchRequest struct { + PenIDs []uint `json:"penIDs" binding:"required,min=1" example:"[1,2,3]"` // 待分配的猪栏ID列表 +} + +// ReclassifyPenToNewBatchRequest 用于将猪栏划拨到新批次的请求体 +type ReclassifyPenToNewBatchRequest struct { + ToBatchID uint `json:"toBatchID" binding:"required"` // 目标猪批次ID + PenID uint `json:"penID" binding:"required"` // 待划拨的猪栏ID + Remarks string `json:"remarks"` // 备注 +} + +// RemoveEmptyPenFromBatchRequest 用于从猪批次移除空栏的请求体 +type RemoveEmptyPenFromBatchRequest struct { + PenID uint `json:"penID" binding:"required"` // 待移除的猪栏ID +} + +// MovePigsIntoPenRequest 用于将猪只从“虚拟库存”移入指定猪栏的请求体 +type MovePigsIntoPenRequest struct { + ToPenID uint `json:"toPenID" binding:"required"` // 目标猪栏ID + Quantity int `json:"quantity" binding:"required,min=1"` // 移入猪只数量 + Remarks string `json:"remarks"` // 备注 } diff --git a/internal/app/service/pig_batch_service.go b/internal/app/service/pig_batch_service.go index da50327..32111d4 100644 --- a/internal/app/service/pig_batch_service.go +++ b/internal/app/service/pig_batch_service.go @@ -14,7 +14,10 @@ type PigBatchService interface { UpdatePigBatch(id uint, dto *dto.PigBatchUpdateDTO) (*dto.PigBatchResponseDTO, error) DeletePigBatch(id uint) error ListPigBatches(isActive *bool) ([]*dto.PigBatchResponseDTO, error) - UpdatePigBatchPens(batchID uint, desiredPenIDs []uint) error + AssignEmptyPensToBatch(batchID uint, penIDs []uint, operatorID uint) error + ReclassifyPenToNewBatch(fromBatchID uint, toBatchID uint, penID uint, operatorID uint, remarks string) error + RemoveEmptyPenFromBatch(batchID uint, penID uint) error + MovePigsIntoPen(batchID uint, toPenID uint, quantity int, operatorID uint, remarks string) error } // pigBatchService 的实现现在依赖于领域服务接口。 @@ -65,7 +68,7 @@ func (s *pigBatchService) CreatePigBatch(operatorID uint, dto *dto.PigBatchCreat createdBatch, err := s.domainService.CreatePigBatch(operatorID, batch) if err != nil { s.logger.Errorf("应用层: 创建猪批次失败: %v", err) - return nil, mapDomainError(err) + return nil, MapDomainError(err) } // 3. 领域模型 -> DTO @@ -77,7 +80,7 @@ func (s *pigBatchService) GetPigBatch(id uint) (*dto.PigBatchResponseDTO, error) batch, err := s.domainService.GetPigBatch(id) if err != nil { s.logger.Warnf("应用层: 获取猪批次失败, ID: %d, 错误: %v", id, err) - return nil, mapDomainError(err) + return nil, MapDomainError(err) } return s.toPigBatchResponseDTO(batch), nil @@ -89,7 +92,7 @@ func (s *pigBatchService) UpdatePigBatch(id uint, dto *dto.PigBatchUpdateDTO) (* existingBatch, err := s.domainService.GetPigBatch(id) if err != nil { s.logger.Warnf("应用层: 更新猪批次失败,获取原批次信息错误, ID: %d, 错误: %v", id, err) - return nil, mapDomainError(err) + return nil, MapDomainError(err) } // 2. 将DTO中的变更应用到模型上 @@ -116,7 +119,7 @@ func (s *pigBatchService) UpdatePigBatch(id uint, dto *dto.PigBatchUpdateDTO) (* updatedBatch, err := s.domainService.UpdatePigBatch(existingBatch) if err != nil { s.logger.Errorf("应用层: 更新猪批次失败, ID: %d, 错误: %v", id, err) - return nil, mapDomainError(err) + return nil, MapDomainError(err) } // 4. 转换并返回结果 @@ -128,7 +131,7 @@ func (s *pigBatchService) DeletePigBatch(id uint) error { err := s.domainService.DeletePigBatch(id) if err != nil { s.logger.Errorf("应用层: 删除猪批次失败, ID: %d, 错误: %v", id, err) - return mapDomainError(err) + return MapDomainError(err) } return nil } @@ -138,7 +141,7 @@ func (s *pigBatchService) ListPigBatches(isActive *bool) ([]*dto.PigBatchRespons batches, err := s.domainService.ListPigBatches(isActive) if err != nil { s.logger.Errorf("应用层: 批量查询猪批次失败: %v", err) - return nil, mapDomainError(err) + return nil, MapDomainError(err) } var responseDTOs []*dto.PigBatchResponseDTO @@ -149,12 +152,42 @@ func (s *pigBatchService) ListPigBatches(isActive *bool) ([]*dto.PigBatchRespons return responseDTOs, nil } -// UpdatePigBatchPens 将关联猪栏的复杂操作委托给领域服务,并处理错误转换。 -func (s *pigBatchService) UpdatePigBatchPens(batchID uint, desiredPenIDs []uint) error { - err := s.domainService.UpdatePigBatchPens(batchID, desiredPenIDs) +// AssignEmptyPensToBatch 委托给领域服务 +func (s *pigBatchService) AssignEmptyPensToBatch(batchID uint, penIDs []uint, operatorID uint) error { + err := s.domainService.AssignEmptyPensToBatch(batchID, penIDs, operatorID) if err != nil { - s.logger.Errorf("应用层: 更新猪批次猪栏关联失败, 批次ID: %d, 错误: %v", batchID, err) - return mapDomainError(err) + s.logger.Errorf("应用层: 为猪批次分配空栏失败, 批次ID: %d, 错误: %v", batchID, err) + return MapDomainError(err) + } + return nil +} + +// ReclassifyPenToNewBatch 委托给领域服务 +func (s *pigBatchService) ReclassifyPenToNewBatch(fromBatchID uint, toBatchID uint, penID uint, operatorID uint, remarks string) error { + err := s.domainService.ReclassifyPenToNewBatch(fromBatchID, toBatchID, penID, operatorID, remarks) + if err != nil { + s.logger.Errorf("应用层: 划拨猪栏到新批次失败, 源批次ID: %d, 错误: %v", fromBatchID, err) + return MapDomainError(err) + } + return nil +} + +// RemoveEmptyPenFromBatch 委托给领域服务 +func (s *pigBatchService) RemoveEmptyPenFromBatch(batchID uint, penID uint) error { + err := s.domainService.RemoveEmptyPenFromBatch(batchID, penID) + if err != nil { + s.logger.Errorf("应用层: 从猪批次移除空栏失败, 批次ID: %d, 猪栏ID: %d, 错误: %v", batchID, penID, err) + return MapDomainError(err) + } + return nil +} + +// MovePigsIntoPen 委托给领域服务 +func (s *pigBatchService) MovePigsIntoPen(batchID uint, toPenID uint, quantity int, operatorID uint, remarks string) error { + err := s.domainService.MovePigsIntoPen(batchID, toPenID, quantity, operatorID, remarks) + if err != nil { + s.logger.Errorf("应用层: 将猪只移入猪栏失败, 批次ID: %d, 目标猪栏ID: %d, 错误: %v", batchID, toPenID, err) + return MapDomainError(err) } return nil } diff --git a/internal/app/service/pig_service.go b/internal/app/service/pig_service.go index 8ebe135..37e9dc3 100644 --- a/internal/app/service/pig_service.go +++ b/internal/app/service/pig_service.go @@ -19,10 +19,12 @@ var ( ErrPenOccupiedByOtherBatch = errors.New("猪栏已被其他批次使用") ErrPenStatusInvalidForAllocation = errors.New("猪栏状态不允许分配") ErrPenNotAssociatedWithBatch = errors.New("猪栏未与该批次关联") + ErrPenNotEmpty = errors.New("猪栏内仍有猪只") + ErrInvalidOperation = errors.New("非法操作") ) -// mapDomainError 将领域层的错误转换为应用服务层的公共错误。 -func mapDomainError(err error) error { +// MapDomainError 将领域层的错误转换为应用服务层的公共错误。 +func MapDomainError(err error) error { if err == nil { return nil } @@ -42,6 +44,10 @@ func mapDomainError(err error) error { return ErrPenNotAssociatedWithBatch case errors.Is(err, domain_pig.ErrPenNotFound): return ErrPenNotFound + case errors.Is(err, domain_pig.ErrPenNotEmpty): + return ErrPenNotEmpty + case errors.Is(err, domain_pig.ErrInvalidOperation): + return ErrInvalidOperation // 可以添加更多领域错误到应用层错误的映射 default: return err // 对于未知的领域错误,直接返回