实现库存管理相关逻辑
This commit is contained in:
@@ -61,4 +61,5 @@ http://git.huangwc.com/pig/pig-farm-controller/issues/66
|
|||||||
11. 配方模型定义和仓库层增删改查方法
|
11. 配方模型定义和仓库层增删改查方法
|
||||||
12. 配方领域层方法
|
12. 配方领域层方法
|
||||||
13. 重构配方领域
|
13. 重构配方领域
|
||||||
14. 配方增删改查服务层和控制器
|
14. 配方增删改查服务层和控制器
|
||||||
|
15. 实现库存管理相关逻辑
|
||||||
321
docs/docs.go
321
docs/docs.go
@@ -3267,6 +3267,205 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/v1/inventory/stock/adjust": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "手动调整指定原料的库存量。",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"库存管理"
|
||||||
|
],
|
||||||
|
"summary": "调整原料库存",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "库存调整请求",
|
||||||
|
"name": "request",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.StockAdjustmentRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "业务码为200代表调整成功",
|
||||||
|
"schema": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/controller.Response"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"$ref": "#/definitions/dto.StockLogResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/inventory/stock/current": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "获取所有原料的当前库存列表,支持分页和过滤。",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"库存管理"
|
||||||
|
],
|
||||||
|
"summary": "获取当前库存列表",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "排序字段, 例如 \"stock DESC\"",
|
||||||
|
"name": "order_by",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "页码",
|
||||||
|
"name": "page",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "每页数量",
|
||||||
|
"name": "page_size",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "按原料名称模糊查询",
|
||||||
|
"name": "raw_material_name",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "业务码为200代表成功获取列表",
|
||||||
|
"schema": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/controller.Response"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"$ref": "#/definitions/dto.ListCurrentStockResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/inventory/stock/logs": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "获取原料库存变动历史记录,支持分页、过滤和时间范围查询。",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"库存管理"
|
||||||
|
],
|
||||||
|
"summary": "获取库存变动日志",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "结束时间 (RFC3339格式)",
|
||||||
|
"name": "end_time",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "排序字段",
|
||||||
|
"name": "order_by",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "页码",
|
||||||
|
"name": "page",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "每页数量",
|
||||||
|
"name": "page_size",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "按原料ID精确查询",
|
||||||
|
"name": "raw_material_id",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"collectionFormat": "csv",
|
||||||
|
"description": "按来源类型查询",
|
||||||
|
"name": "source_types",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "开始时间 (RFC3339格式, e.g., \"2023-01-01T00:00:00Z\")",
|
||||||
|
"name": "start_time",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "业务码为200代表成功获取列表",
|
||||||
|
"schema": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/controller.Response"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"$ref": "#/definitions/dto.ListStockLogResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/v1/monitor/device-command-logs": {
|
"/api/v1/monitor/device-command-logs": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
@@ -3448,6 +3647,7 @@ const docTemplate = `{
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"enum": [
|
"enum": [
|
||||||
|
7,
|
||||||
-1,
|
-1,
|
||||||
0,
|
0,
|
||||||
1,
|
1,
|
||||||
@@ -3457,12 +3657,12 @@ const docTemplate = `{
|
|||||||
5,
|
5,
|
||||||
-1,
|
-1,
|
||||||
5,
|
5,
|
||||||
6,
|
6
|
||||||
7
|
|
||||||
],
|
],
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"format": "int32",
|
"format": "int32",
|
||||||
"x-enum-varnames": [
|
"x-enum-varnames": [
|
||||||
|
"_numLevels",
|
||||||
"DebugLevel",
|
"DebugLevel",
|
||||||
"InfoLevel",
|
"InfoLevel",
|
||||||
"WarnLevel",
|
"WarnLevel",
|
||||||
@@ -3472,8 +3672,7 @@ const docTemplate = `{
|
|||||||
"FatalLevel",
|
"FatalLevel",
|
||||||
"_minLevel",
|
"_minLevel",
|
||||||
"_maxLevel",
|
"_maxLevel",
|
||||||
"InvalidLevel",
|
"InvalidLevel"
|
||||||
"_numLevels"
|
|
||||||
],
|
],
|
||||||
"name": "level",
|
"name": "level",
|
||||||
"in": "query"
|
"in": "query"
|
||||||
@@ -7062,6 +7261,27 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"dto.CurrentStockResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"last_updated": {
|
||||||
|
"description": "最后更新时间",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"raw_material_id": {
|
||||||
|
"description": "原料ID",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"raw_material_name": {
|
||||||
|
"description": "原料名称",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"stock": {
|
||||||
|
"description": "当前库存量, 单位: g",
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"dto.DeleteDeviceThresholdAlarmDTO": {
|
"dto.DeleteDeviceThresholdAlarmDTO": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
@@ -7259,6 +7479,20 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"dto.ListCurrentStockResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"list": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/dto.CurrentStockResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pagination": {
|
||||||
|
"$ref": "#/definitions/dto.PaginationDTO"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"dto.ListDeviceCommandLogResponse": {
|
"dto.ListDeviceCommandLogResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -7540,6 +7774,20 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"dto.ListStockLogResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"list": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/dto.StockLogResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pagination": {
|
||||||
|
"$ref": "#/definitions/dto.PaginationDTO"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"dto.ListTaskExecutionLogResponse": {
|
"dto.ListTaskExecutionLogResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -8852,6 +9100,63 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"dto.StockAdjustmentRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"change_amount",
|
||||||
|
"raw_material_id"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"change_amount": {
|
||||||
|
"description": "变动数量, 正数为入库, 负数为出库, 单位: g",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"raw_material_id": {
|
||||||
|
"description": "要调整的原料ID",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"remarks": {
|
||||||
|
"description": "备注",
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 255
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dto.StockLogResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"after_quantity": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"before_quantity": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"change_amount": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"happened_at": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"raw_material_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"raw_material_name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"remarks": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"source_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"source_type": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"dto.SubPlanResponse": {
|
"dto.SubPlanResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -10147,6 +10452,7 @@ const docTemplate = `{
|
|||||||
"type": "integer",
|
"type": "integer",
|
||||||
"format": "int32",
|
"format": "int32",
|
||||||
"enum": [
|
"enum": [
|
||||||
|
7,
|
||||||
-1,
|
-1,
|
||||||
0,
|
0,
|
||||||
1,
|
1,
|
||||||
@@ -10156,10 +10462,10 @@ const docTemplate = `{
|
|||||||
5,
|
5,
|
||||||
-1,
|
-1,
|
||||||
5,
|
5,
|
||||||
6,
|
6
|
||||||
7
|
|
||||||
],
|
],
|
||||||
"x-enum-varnames": [
|
"x-enum-varnames": [
|
||||||
|
"_numLevels",
|
||||||
"DebugLevel",
|
"DebugLevel",
|
||||||
"InfoLevel",
|
"InfoLevel",
|
||||||
"WarnLevel",
|
"WarnLevel",
|
||||||
@@ -10169,8 +10475,7 @@ const docTemplate = `{
|
|||||||
"FatalLevel",
|
"FatalLevel",
|
||||||
"_minLevel",
|
"_minLevel",
|
||||||
"_maxLevel",
|
"_maxLevel",
|
||||||
"InvalidLevel",
|
"InvalidLevel"
|
||||||
"_numLevels"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3259,6 +3259,205 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/v1/inventory/stock/adjust": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "手动调整指定原料的库存量。",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"库存管理"
|
||||||
|
],
|
||||||
|
"summary": "调整原料库存",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "库存调整请求",
|
||||||
|
"name": "request",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/dto.StockAdjustmentRequest"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "业务码为200代表调整成功",
|
||||||
|
"schema": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/controller.Response"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"$ref": "#/definitions/dto.StockLogResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/inventory/stock/current": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "获取所有原料的当前库存列表,支持分页和过滤。",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"库存管理"
|
||||||
|
],
|
||||||
|
"summary": "获取当前库存列表",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "排序字段, 例如 \"stock DESC\"",
|
||||||
|
"name": "order_by",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "页码",
|
||||||
|
"name": "page",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "每页数量",
|
||||||
|
"name": "page_size",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "按原料名称模糊查询",
|
||||||
|
"name": "raw_material_name",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "业务码为200代表成功获取列表",
|
||||||
|
"schema": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/controller.Response"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"$ref": "#/definitions/dto.ListCurrentStockResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/inventory/stock/logs": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"BearerAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "获取原料库存变动历史记录,支持分页、过滤和时间范围查询。",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"库存管理"
|
||||||
|
],
|
||||||
|
"summary": "获取库存变动日志",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "结束时间 (RFC3339格式)",
|
||||||
|
"name": "end_time",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "排序字段",
|
||||||
|
"name": "order_by",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "页码",
|
||||||
|
"name": "page",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "每页数量",
|
||||||
|
"name": "page_size",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "按原料ID精确查询",
|
||||||
|
"name": "raw_material_id",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"collectionFormat": "csv",
|
||||||
|
"description": "按来源类型查询",
|
||||||
|
"name": "source_types",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "开始时间 (RFC3339格式, e.g., \"2023-01-01T00:00:00Z\")",
|
||||||
|
"name": "start_time",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "业务码为200代表成功获取列表",
|
||||||
|
"schema": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/controller.Response"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"$ref": "#/definitions/dto.ListStockLogResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/v1/monitor/device-command-logs": {
|
"/api/v1/monitor/device-command-logs": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
@@ -3440,6 +3639,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"enum": [
|
"enum": [
|
||||||
|
7,
|
||||||
-1,
|
-1,
|
||||||
0,
|
0,
|
||||||
1,
|
1,
|
||||||
@@ -3449,12 +3649,12 @@
|
|||||||
5,
|
5,
|
||||||
-1,
|
-1,
|
||||||
5,
|
5,
|
||||||
6,
|
6
|
||||||
7
|
|
||||||
],
|
],
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"format": "int32",
|
"format": "int32",
|
||||||
"x-enum-varnames": [
|
"x-enum-varnames": [
|
||||||
|
"_numLevels",
|
||||||
"DebugLevel",
|
"DebugLevel",
|
||||||
"InfoLevel",
|
"InfoLevel",
|
||||||
"WarnLevel",
|
"WarnLevel",
|
||||||
@@ -3464,8 +3664,7 @@
|
|||||||
"FatalLevel",
|
"FatalLevel",
|
||||||
"_minLevel",
|
"_minLevel",
|
||||||
"_maxLevel",
|
"_maxLevel",
|
||||||
"InvalidLevel",
|
"InvalidLevel"
|
||||||
"_numLevels"
|
|
||||||
],
|
],
|
||||||
"name": "level",
|
"name": "level",
|
||||||
"in": "query"
|
"in": "query"
|
||||||
@@ -7054,6 +7253,27 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"dto.CurrentStockResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"last_updated": {
|
||||||
|
"description": "最后更新时间",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"raw_material_id": {
|
||||||
|
"description": "原料ID",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"raw_material_name": {
|
||||||
|
"description": "原料名称",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"stock": {
|
||||||
|
"description": "当前库存量, 单位: g",
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"dto.DeleteDeviceThresholdAlarmDTO": {
|
"dto.DeleteDeviceThresholdAlarmDTO": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
@@ -7251,6 +7471,20 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"dto.ListCurrentStockResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"list": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/dto.CurrentStockResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pagination": {
|
||||||
|
"$ref": "#/definitions/dto.PaginationDTO"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"dto.ListDeviceCommandLogResponse": {
|
"dto.ListDeviceCommandLogResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -7532,6 +7766,20 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"dto.ListStockLogResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"list": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/dto.StockLogResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pagination": {
|
||||||
|
"$ref": "#/definitions/dto.PaginationDTO"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"dto.ListTaskExecutionLogResponse": {
|
"dto.ListTaskExecutionLogResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -8844,6 +9092,63 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"dto.StockAdjustmentRequest": {
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"change_amount",
|
||||||
|
"raw_material_id"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"change_amount": {
|
||||||
|
"description": "变动数量, 正数为入库, 负数为出库, 单位: g",
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"raw_material_id": {
|
||||||
|
"description": "要调整的原料ID",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"remarks": {
|
||||||
|
"description": "备注",
|
||||||
|
"type": "string",
|
||||||
|
"maxLength": 255
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dto.StockLogResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"after_quantity": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"before_quantity": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"change_amount": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"happened_at": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"raw_material_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"raw_material_name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"remarks": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"source_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"source_type": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"dto.SubPlanResponse": {
|
"dto.SubPlanResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@@ -10139,6 +10444,7 @@
|
|||||||
"type": "integer",
|
"type": "integer",
|
||||||
"format": "int32",
|
"format": "int32",
|
||||||
"enum": [
|
"enum": [
|
||||||
|
7,
|
||||||
-1,
|
-1,
|
||||||
0,
|
0,
|
||||||
1,
|
1,
|
||||||
@@ -10148,10 +10454,10 @@
|
|||||||
5,
|
5,
|
||||||
-1,
|
-1,
|
||||||
5,
|
5,
|
||||||
6,
|
6
|
||||||
7
|
|
||||||
],
|
],
|
||||||
"x-enum-varnames": [
|
"x-enum-varnames": [
|
||||||
|
"_numLevels",
|
||||||
"DebugLevel",
|
"DebugLevel",
|
||||||
"InfoLevel",
|
"InfoLevel",
|
||||||
"WarnLevel",
|
"WarnLevel",
|
||||||
@@ -10161,8 +10467,7 @@
|
|||||||
"FatalLevel",
|
"FatalLevel",
|
||||||
"_minLevel",
|
"_minLevel",
|
||||||
"_maxLevel",
|
"_maxLevel",
|
||||||
"InvalidLevel",
|
"InvalidLevel"
|
||||||
"_numLevels"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -462,6 +462,21 @@ definitions:
|
|||||||
example: newuser
|
example: newuser
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
|
dto.CurrentStockResponse:
|
||||||
|
properties:
|
||||||
|
last_updated:
|
||||||
|
description: 最后更新时间
|
||||||
|
type: string
|
||||||
|
raw_material_id:
|
||||||
|
description: 原料ID
|
||||||
|
type: integer
|
||||||
|
raw_material_name:
|
||||||
|
description: 原料名称
|
||||||
|
type: string
|
||||||
|
stock:
|
||||||
|
description: '当前库存量, 单位: g'
|
||||||
|
type: number
|
||||||
|
type: object
|
||||||
dto.DeleteDeviceThresholdAlarmDTO:
|
dto.DeleteDeviceThresholdAlarmDTO:
|
||||||
properties:
|
properties:
|
||||||
sensor_type:
|
sensor_type:
|
||||||
@@ -590,6 +605,15 @@ definitions:
|
|||||||
pagination:
|
pagination:
|
||||||
$ref: '#/definitions/dto.PaginationDTO'
|
$ref: '#/definitions/dto.PaginationDTO'
|
||||||
type: object
|
type: object
|
||||||
|
dto.ListCurrentStockResponse:
|
||||||
|
properties:
|
||||||
|
list:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/dto.CurrentStockResponse'
|
||||||
|
type: array
|
||||||
|
pagination:
|
||||||
|
$ref: '#/definitions/dto.PaginationDTO'
|
||||||
|
type: object
|
||||||
dto.ListDeviceCommandLogResponse:
|
dto.ListDeviceCommandLogResponse:
|
||||||
properties:
|
properties:
|
||||||
list:
|
list:
|
||||||
@@ -771,6 +795,15 @@ definitions:
|
|||||||
pagination:
|
pagination:
|
||||||
$ref: '#/definitions/dto.PaginationDTO'
|
$ref: '#/definitions/dto.PaginationDTO'
|
||||||
type: object
|
type: object
|
||||||
|
dto.ListStockLogResponse:
|
||||||
|
properties:
|
||||||
|
list:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/dto.StockLogResponse'
|
||||||
|
type: array
|
||||||
|
pagination:
|
||||||
|
$ref: '#/definitions/dto.PaginationDTO'
|
||||||
|
type: object
|
||||||
dto.ListTaskExecutionLogResponse:
|
dto.ListTaskExecutionLogResponse:
|
||||||
properties:
|
properties:
|
||||||
list:
|
list:
|
||||||
@@ -1654,6 +1687,45 @@ definitions:
|
|||||||
required:
|
required:
|
||||||
- duration_minutes
|
- duration_minutes
|
||||||
type: object
|
type: object
|
||||||
|
dto.StockAdjustmentRequest:
|
||||||
|
properties:
|
||||||
|
change_amount:
|
||||||
|
description: '变动数量, 正数为入库, 负数为出库, 单位: g'
|
||||||
|
type: number
|
||||||
|
raw_material_id:
|
||||||
|
description: 要调整的原料ID
|
||||||
|
type: integer
|
||||||
|
remarks:
|
||||||
|
description: 备注
|
||||||
|
maxLength: 255
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- change_amount
|
||||||
|
- raw_material_id
|
||||||
|
type: object
|
||||||
|
dto.StockLogResponse:
|
||||||
|
properties:
|
||||||
|
after_quantity:
|
||||||
|
type: number
|
||||||
|
before_quantity:
|
||||||
|
type: number
|
||||||
|
change_amount:
|
||||||
|
type: number
|
||||||
|
happened_at:
|
||||||
|
type: string
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
raw_material_id:
|
||||||
|
type: integer
|
||||||
|
raw_material_name:
|
||||||
|
type: string
|
||||||
|
remarks:
|
||||||
|
type: string
|
||||||
|
source_id:
|
||||||
|
type: integer
|
||||||
|
source_type:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
dto.SubPlanResponse:
|
dto.SubPlanResponse:
|
||||||
properties:
|
properties:
|
||||||
child_plan:
|
child_plan:
|
||||||
@@ -2606,6 +2678,7 @@ definitions:
|
|||||||
- PlanTypeFilterSystem
|
- PlanTypeFilterSystem
|
||||||
zapcore.Level:
|
zapcore.Level:
|
||||||
enum:
|
enum:
|
||||||
|
- 7
|
||||||
- -1
|
- -1
|
||||||
- 0
|
- 0
|
||||||
- 1
|
- 1
|
||||||
@@ -2616,10 +2689,10 @@ definitions:
|
|||||||
- -1
|
- -1
|
||||||
- 5
|
- 5
|
||||||
- 6
|
- 6
|
||||||
- 7
|
|
||||||
format: int32
|
format: int32
|
||||||
type: integer
|
type: integer
|
||||||
x-enum-varnames:
|
x-enum-varnames:
|
||||||
|
- _numLevels
|
||||||
- DebugLevel
|
- DebugLevel
|
||||||
- InfoLevel
|
- InfoLevel
|
||||||
- WarnLevel
|
- WarnLevel
|
||||||
@@ -2630,7 +2703,6 @@ definitions:
|
|||||||
- _minLevel
|
- _minLevel
|
||||||
- _maxLevel
|
- _maxLevel
|
||||||
- InvalidLevel
|
- InvalidLevel
|
||||||
- _numLevels
|
|
||||||
info:
|
info:
|
||||||
contact:
|
contact:
|
||||||
email: divano@example.com
|
email: divano@example.com
|
||||||
@@ -4601,6 +4673,124 @@ paths:
|
|||||||
summary: 更新配方
|
summary: 更新配方
|
||||||
tags:
|
tags:
|
||||||
- 饲料管理-配方
|
- 饲料管理-配方
|
||||||
|
/api/v1/inventory/stock/adjust:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: 手动调整指定原料的库存量。
|
||||||
|
parameters:
|
||||||
|
- description: 库存调整请求
|
||||||
|
in: body
|
||||||
|
name: request
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/dto.StockAdjustmentRequest'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: 业务码为200代表调整成功
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/definitions/controller.Response'
|
||||||
|
- properties:
|
||||||
|
data:
|
||||||
|
$ref: '#/definitions/dto.StockLogResponse'
|
||||||
|
type: object
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
summary: 调整原料库存
|
||||||
|
tags:
|
||||||
|
- 库存管理
|
||||||
|
/api/v1/inventory/stock/current:
|
||||||
|
get:
|
||||||
|
description: 获取所有原料的当前库存列表,支持分页和过滤。
|
||||||
|
parameters:
|
||||||
|
- description: 排序字段, 例如 "stock DESC"
|
||||||
|
in: query
|
||||||
|
name: order_by
|
||||||
|
type: string
|
||||||
|
- description: 页码
|
||||||
|
in: query
|
||||||
|
name: page
|
||||||
|
type: integer
|
||||||
|
- description: 每页数量
|
||||||
|
in: query
|
||||||
|
name: page_size
|
||||||
|
type: integer
|
||||||
|
- description: 按原料名称模糊查询
|
||||||
|
in: query
|
||||||
|
name: raw_material_name
|
||||||
|
type: string
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: 业务码为200代表成功获取列表
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/definitions/controller.Response'
|
||||||
|
- properties:
|
||||||
|
data:
|
||||||
|
$ref: '#/definitions/dto.ListCurrentStockResponse'
|
||||||
|
type: object
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
summary: 获取当前库存列表
|
||||||
|
tags:
|
||||||
|
- 库存管理
|
||||||
|
/api/v1/inventory/stock/logs:
|
||||||
|
get:
|
||||||
|
description: 获取原料库存变动历史记录,支持分页、过滤和时间范围查询。
|
||||||
|
parameters:
|
||||||
|
- description: 结束时间 (RFC3339格式)
|
||||||
|
in: query
|
||||||
|
name: end_time
|
||||||
|
type: string
|
||||||
|
- description: 排序字段
|
||||||
|
in: query
|
||||||
|
name: order_by
|
||||||
|
type: string
|
||||||
|
- description: 页码
|
||||||
|
in: query
|
||||||
|
name: page
|
||||||
|
type: integer
|
||||||
|
- description: 每页数量
|
||||||
|
in: query
|
||||||
|
name: page_size
|
||||||
|
type: integer
|
||||||
|
- description: 按原料ID精确查询
|
||||||
|
in: query
|
||||||
|
name: raw_material_id
|
||||||
|
type: integer
|
||||||
|
- collectionFormat: csv
|
||||||
|
description: 按来源类型查询
|
||||||
|
in: query
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
name: source_types
|
||||||
|
type: array
|
||||||
|
- description: 开始时间 (RFC3339格式, e.g., "2023-01-01T00:00:00Z")
|
||||||
|
in: query
|
||||||
|
name: start_time
|
||||||
|
type: string
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: 业务码为200代表成功获取列表
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/definitions/controller.Response'
|
||||||
|
- properties:
|
||||||
|
data:
|
||||||
|
$ref: '#/definitions/dto.ListStockLogResponse'
|
||||||
|
type: object
|
||||||
|
security:
|
||||||
|
- BearerAuth: []
|
||||||
|
summary: 获取库存变动日志
|
||||||
|
tags:
|
||||||
|
- 库存管理
|
||||||
/api/v1/monitor/device-command-logs:
|
/api/v1/monitor/device-command-logs:
|
||||||
get:
|
get:
|
||||||
description: 根据提供的过滤条件,分页获取设备命令日志
|
description: 根据提供的过滤条件,分页获取设备命令日志
|
||||||
@@ -4699,6 +4889,7 @@ paths:
|
|||||||
name: end_time
|
name: end_time
|
||||||
type: string
|
type: string
|
||||||
- enum:
|
- enum:
|
||||||
|
- 7
|
||||||
- -1
|
- -1
|
||||||
- 0
|
- 0
|
||||||
- 1
|
- 1
|
||||||
@@ -4709,12 +4900,12 @@ paths:
|
|||||||
- -1
|
- -1
|
||||||
- 5
|
- 5
|
||||||
- 6
|
- 6
|
||||||
- 7
|
|
||||||
format: int32
|
format: int32
|
||||||
in: query
|
in: query
|
||||||
name: level
|
name: level
|
||||||
type: integer
|
type: integer
|
||||||
x-enum-varnames:
|
x-enum-varnames:
|
||||||
|
- _numLevels
|
||||||
- DebugLevel
|
- DebugLevel
|
||||||
- InfoLevel
|
- InfoLevel
|
||||||
- WarnLevel
|
- WarnLevel
|
||||||
@@ -4725,7 +4916,6 @@ paths:
|
|||||||
- _minLevel
|
- _minLevel
|
||||||
- _maxLevel
|
- _maxLevel
|
||||||
- InvalidLevel
|
- InvalidLevel
|
||||||
- _numLevels
|
|
||||||
- enum:
|
- enum:
|
||||||
- 邮件
|
- 邮件
|
||||||
- 企业微信
|
- 企业微信
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import (
|
|||||||
"git.huangwc.com/pig/pig-farm-controller/internal/app/controller/device"
|
"git.huangwc.com/pig/pig-farm-controller/internal/app/controller/device"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/app/controller/feed"
|
"git.huangwc.com/pig/pig-farm-controller/internal/app/controller/feed"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/app/controller/health"
|
"git.huangwc.com/pig/pig-farm-controller/internal/app/controller/health"
|
||||||
|
"git.huangwc.com/pig/pig-farm-controller/internal/app/controller/inventory"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/app/controller/management"
|
"git.huangwc.com/pig/pig-farm-controller/internal/app/controller/management"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/app/controller/monitor"
|
"git.huangwc.com/pig/pig-farm-controller/internal/app/controller/monitor"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/app/controller/plan"
|
"git.huangwc.com/pig/pig-farm-controller/internal/app/controller/plan"
|
||||||
@@ -62,6 +63,7 @@ type API struct {
|
|||||||
pigTypeController *feed.PigTypeController // 猪种类控制器实例
|
pigTypeController *feed.PigTypeController // 猪种类控制器实例
|
||||||
rawMaterialController *feed.RawMaterialController // 原料控制器实例
|
rawMaterialController *feed.RawMaterialController // 原料控制器实例
|
||||||
recipeController *feed.RecipeController // 配方控制器实例
|
recipeController *feed.RecipeController // 配方控制器实例
|
||||||
|
inventoryController *inventory.InventoryController // 库存控制器实例
|
||||||
listenHandler webhook.ListenHandler // 设备上行事件监听器
|
listenHandler webhook.ListenHandler // 设备上行事件监听器
|
||||||
analysisTaskManager *domain_plan.AnalysisPlanTaskManager // 计划触发器管理器实例
|
analysisTaskManager *domain_plan.AnalysisPlanTaskManager // 计划触发器管理器实例
|
||||||
}
|
}
|
||||||
@@ -85,6 +87,7 @@ func NewAPI(cfg config.ServerConfig,
|
|||||||
pigAgeStageService service.PigAgeStageService,
|
pigAgeStageService service.PigAgeStageService,
|
||||||
pigTypeService service.PigTypeService,
|
pigTypeService service.PigTypeService,
|
||||||
recipeService service.RecipeService,
|
recipeService service.RecipeService,
|
||||||
|
inventoryService service.InventoryService,
|
||||||
tokenGenerator token.Generator,
|
tokenGenerator token.Generator,
|
||||||
listenHandler webhook.ListenHandler,
|
listenHandler webhook.ListenHandler,
|
||||||
) *API {
|
) *API {
|
||||||
@@ -122,6 +125,7 @@ func NewAPI(cfg config.ServerConfig,
|
|||||||
pigTypeController: feed.NewPigTypeController(logs.AddCompName(baseCtx, "PigTypeController"), pigTypeService),
|
pigTypeController: feed.NewPigTypeController(logs.AddCompName(baseCtx, "PigTypeController"), pigTypeService),
|
||||||
rawMaterialController: feed.NewRawMaterialController(logs.AddCompName(baseCtx, "RawMaterialController"), rawMaterialService),
|
rawMaterialController: feed.NewRawMaterialController(logs.AddCompName(baseCtx, "RawMaterialController"), rawMaterialService),
|
||||||
recipeController: feed.NewRecipeController(logs.AddCompName(baseCtx, "RecipeController"), recipeService),
|
recipeController: feed.NewRecipeController(logs.AddCompName(baseCtx, "RecipeController"), recipeService),
|
||||||
|
inventoryController: inventory.NewInventoryController(logs.AddCompName(baseCtx, "InventoryController"), inventoryService),
|
||||||
}
|
}
|
||||||
|
|
||||||
api.setupRoutes() // 设置所有路由
|
api.setupRoutes() // 设置所有路由
|
||||||
|
|||||||
@@ -261,6 +261,15 @@ func (a *API) setupRoutes() {
|
|||||||
feedGroup.GET("/recipes", a.recipeController.ListRecipes)
|
feedGroup.GET("/recipes", a.recipeController.ListRecipes)
|
||||||
}
|
}
|
||||||
logger.Debug("饲料管理相关接口注册成功 (需要认证和审计)")
|
logger.Debug("饲料管理相关接口注册成功 (需要认证和审计)")
|
||||||
|
|
||||||
|
// 库存管理相关路由组
|
||||||
|
inventoryGroup := authGroup.Group("/inventory")
|
||||||
|
{
|
||||||
|
inventoryGroup.POST("/stock/adjust", a.inventoryController.AdjustStock)
|
||||||
|
inventoryGroup.GET("/stock/current", a.inventoryController.ListCurrentStock)
|
||||||
|
inventoryGroup.GET("/stock/logs", a.inventoryController.ListStockLogs)
|
||||||
|
}
|
||||||
|
logger.Debug("库存管理相关接口注册成功 (需要认证和审计)")
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Debug("所有接口注册成功")
|
logger.Debug("所有接口注册成功")
|
||||||
|
|||||||
121
internal/app/controller/inventory/inventory_controller.go
Normal file
121
internal/app/controller/inventory/inventory_controller.go
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
package inventory
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"git.huangwc.com/pig/pig-farm-controller/internal/app/controller"
|
||||||
|
"git.huangwc.com/pig/pig-farm-controller/internal/app/dto"
|
||||||
|
"git.huangwc.com/pig/pig-farm-controller/internal/app/service"
|
||||||
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
// InventoryController 定义了库存相关的控制器
|
||||||
|
type InventoryController struct {
|
||||||
|
ctx context.Context
|
||||||
|
inventoryService service.InventoryService
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInventoryController 创建一个新的 InventoryController 实例
|
||||||
|
func NewInventoryController(ctx context.Context, inventoryService service.InventoryService) *InventoryController {
|
||||||
|
return &InventoryController{
|
||||||
|
ctx: ctx,
|
||||||
|
inventoryService: inventoryService,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AdjustStock godoc
|
||||||
|
// @Summary 调整原料库存
|
||||||
|
// @Description 手动调整指定原料的库存量。
|
||||||
|
// @Tags 库存管理
|
||||||
|
// @Security BearerAuth
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param request body dto.StockAdjustmentRequest true "库存调整请求"
|
||||||
|
// @Success 200 {object} controller.Response{data=dto.StockLogResponse} "业务码为200代表调整成功"
|
||||||
|
// @Router /api/v1/inventory/stock/adjust [post]
|
||||||
|
func (c *InventoryController) AdjustStock(ctx echo.Context) error {
|
||||||
|
reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "AdjustStock")
|
||||||
|
var req dto.StockAdjustmentRequest
|
||||||
|
const actionType = "调整原料库存"
|
||||||
|
|
||||||
|
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.inventoryService.AdjustStock(reqCtx, &req)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("%s: 服务层调整库存失败: %v", actionType, err)
|
||||||
|
if errors.Is(err, service.ErrInventoryRawMaterialNotFound) {
|
||||||
|
return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, err.Error(), actionType, "原料不存在", req)
|
||||||
|
}
|
||||||
|
if errors.Is(err, service.ErrInventoryInsufficientStock) {
|
||||||
|
return controller.SendErrorWithAudit(ctx, controller.CodeConflict, err.Error(), actionType, "原料库存不足", req)
|
||||||
|
}
|
||||||
|
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "调整库存失败: "+err.Error(), actionType, "服务层调整库存失败", req)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Infof("%s: 库存调整成功, 原料ID: %d", actionType, resp.RawMaterialID)
|
||||||
|
return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "库存调整成功", resp, actionType, "库存调整成功", resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListCurrentStock godoc
|
||||||
|
// @Summary 获取当前库存列表
|
||||||
|
// @Description 获取所有原料的当前库存列表,支持分页和过滤。
|
||||||
|
// @Tags 库存管理
|
||||||
|
// @Security BearerAuth
|
||||||
|
// @Produce json
|
||||||
|
// @Param query query dto.ListCurrentStockRequest false "查询参数"
|
||||||
|
// @Success 200 {object} controller.Response{data=dto.ListCurrentStockResponse} "业务码为200代表成功获取列表"
|
||||||
|
// @Router /api/v1/inventory/stock/current [get]
|
||||||
|
func (c *InventoryController) ListCurrentStock(ctx echo.Context) error {
|
||||||
|
reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "ListCurrentStock")
|
||||||
|
const actionType = "获取当前库存列表"
|
||||||
|
var req dto.ListCurrentStockRequest
|
||||||
|
|
||||||
|
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.inventoryService.ListCurrentStock(reqCtx, &req)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("%s: 服务层获取当前库存列表失败: %v", actionType, err)
|
||||||
|
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取当前库存列表失败: "+err.Error(), actionType, "服务层获取当前库存列表失败", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Infof("%s: 获取当前库存列表成功, 数量: %d", actionType, len(resp.List))
|
||||||
|
return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取当前库存列表成功", resp, actionType, "获取当前库存列表成功", resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListStockLogs godoc
|
||||||
|
// @Summary 获取库存变动日志
|
||||||
|
// @Description 获取原料库存变动历史记录,支持分页、过滤和时间范围查询。
|
||||||
|
// @Tags 库存管理
|
||||||
|
// @Security BearerAuth
|
||||||
|
// @Produce json
|
||||||
|
// @Param query query dto.ListStockLogRequest false "查询参数"
|
||||||
|
// @Success 200 {object} controller.Response{data=dto.ListStockLogResponse} "业务码为200代表成功获取列表"
|
||||||
|
// @Router /api/v1/inventory/stock/logs [get]
|
||||||
|
func (c *InventoryController) ListStockLogs(ctx echo.Context) error {
|
||||||
|
reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "ListStockLogs")
|
||||||
|
const actionType = "获取库存变动日志"
|
||||||
|
var req dto.ListStockLogRequest
|
||||||
|
|
||||||
|
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.inventoryService.ListStockLogs(reqCtx, &req)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("%s: 服务层获取库存变动日志失败: %v", actionType, err)
|
||||||
|
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取库存变动日志失败: "+err.Error(), actionType, "服务层获取库存变动日志失败", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Infof("%s: 获取库存变动日志成功, 数量: %d", actionType, len(resp.List))
|
||||||
|
return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取库存变动日志成功", resp, actionType, "获取库存变动日志成功", resp)
|
||||||
|
}
|
||||||
66
internal/app/dto/inventory_converter.go
Normal file
66
internal/app/dto/inventory_converter.go
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
package dto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ConvertCurrentStockToDTO 将原料及其最新库存日志转换为 CurrentStockResponse DTO
|
||||||
|
func ConvertCurrentStockToDTO(material *models.RawMaterial, latestLog *models.RawMaterialStockLog) *CurrentStockResponse {
|
||||||
|
if material == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
stock := float32(0)
|
||||||
|
lastUpdated := material.CreatedAt.Format(time.RFC3339) // 默认使用创建时间
|
||||||
|
|
||||||
|
if latestLog != nil {
|
||||||
|
stock = latestLog.AfterQuantity
|
||||||
|
lastUpdated = latestLog.HappenedAt.Format(time.RFC3339)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &CurrentStockResponse{
|
||||||
|
RawMaterialID: material.ID,
|
||||||
|
RawMaterialName: material.Name,
|
||||||
|
Stock: stock,
|
||||||
|
LastUpdated: lastUpdated,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConvertStockLogToDTO 将 models.RawMaterialStockLog 转换为 StockLogResponse DTO
|
||||||
|
func ConvertStockLogToDTO(log *models.RawMaterialStockLog) *StockLogResponse {
|
||||||
|
if log == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &StockLogResponse{
|
||||||
|
ID: log.ID,
|
||||||
|
RawMaterialID: log.RawMaterialID,
|
||||||
|
RawMaterialName: log.RawMaterial.Name, // 假设 RawMaterial 已被预加载
|
||||||
|
ChangeAmount: log.ChangeAmount,
|
||||||
|
BeforeQuantity: log.BeforeQuantity,
|
||||||
|
AfterQuantity: log.AfterQuantity,
|
||||||
|
SourceType: string(log.SourceType),
|
||||||
|
SourceID: log.SourceID,
|
||||||
|
HappenedAt: log.HappenedAt,
|
||||||
|
Remarks: log.Remarks,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConvertStockLogListToDTO 将 []models.RawMaterialStockLog 转换为 ListStockLogResponse DTO
|
||||||
|
func ConvertStockLogListToDTO(logs []models.RawMaterialStockLog, total int64, page, pageSize int) *ListStockLogResponse {
|
||||||
|
logDTOs := make([]StockLogResponse, len(logs))
|
||||||
|
for i, log := range logs {
|
||||||
|
logDTOs[i] = *ConvertStockLogToDTO(&log)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ListStockLogResponse{
|
||||||
|
List: logDTOs,
|
||||||
|
Pagination: PaginationDTO{
|
||||||
|
Page: page,
|
||||||
|
PageSize: pageSize,
|
||||||
|
Total: total,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
67
internal/app/dto/inventory_dto.go
Normal file
67
internal/app/dto/inventory_dto.go
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
package dto
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// =============================================================================================================
|
||||||
|
// 库存 (Inventory) 相关 DTO
|
||||||
|
// =============================================================================================================
|
||||||
|
|
||||||
|
// StockAdjustmentRequest 手动调整库存的请求体
|
||||||
|
type StockAdjustmentRequest struct {
|
||||||
|
RawMaterialID uint32 `json:"raw_material_id" validate:"required"` // 要调整的原料ID
|
||||||
|
ChangeAmount float32 `json:"change_amount" validate:"required,ne=0"` // 变动数量, 正数为入库, 负数为出库, 单位: g
|
||||||
|
Remarks string `json:"remarks" validate:"max=255"` // 备注
|
||||||
|
}
|
||||||
|
|
||||||
|
// CurrentStockResponse 单个原料及其当前库存的响应体
|
||||||
|
type CurrentStockResponse struct {
|
||||||
|
RawMaterialID uint32 `json:"raw_material_id"` // 原料ID
|
||||||
|
RawMaterialName string `json:"raw_material_name"` // 原料名称
|
||||||
|
Stock float32 `json:"stock"` // 当前库存量, 单位: g
|
||||||
|
LastUpdated string `json:"last_updated"` // 最后更新时间
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListCurrentStockRequest 定义了获取当前库存列表的请求参数
|
||||||
|
type ListCurrentStockRequest struct {
|
||||||
|
Page int `json:"page" query:"page"` // 页码
|
||||||
|
PageSize int `json:"page_size" query:"page_size"` // 每页数量
|
||||||
|
RawMaterialName *string `json:"raw_material_name" query:"raw_material_name"` // 按原料名称模糊查询
|
||||||
|
OrderBy string `json:"order_by" query:"order_by"` // 排序字段, 例如 "stock DESC"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListCurrentStockResponse 是获取当前库存列表的响应结构
|
||||||
|
type ListCurrentStockResponse struct {
|
||||||
|
List []CurrentStockResponse `json:"list"`
|
||||||
|
Pagination PaginationDTO `json:"pagination"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// StockLogResponse 库存变动历史记录的响应体
|
||||||
|
type StockLogResponse struct {
|
||||||
|
ID uint32 `json:"id"`
|
||||||
|
RawMaterialID uint32 `json:"raw_material_id"`
|
||||||
|
RawMaterialName string `json:"raw_material_name"`
|
||||||
|
ChangeAmount float32 `json:"change_amount"`
|
||||||
|
BeforeQuantity float32 `json:"before_quantity"`
|
||||||
|
AfterQuantity float32 `json:"after_quantity"`
|
||||||
|
SourceType string `json:"source_type"`
|
||||||
|
SourceID *uint32 `json:"source_id,omitempty"`
|
||||||
|
HappenedAt time.Time `json:"happened_at"`
|
||||||
|
Remarks string `json:"remarks"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListStockLogRequest 定义了获取库存变动历史的请求参数
|
||||||
|
type ListStockLogRequest struct {
|
||||||
|
Page int `json:"page" query:"page"` // 页码
|
||||||
|
PageSize int `json:"page_size" query:"page_size"` // 每页数量
|
||||||
|
RawMaterialID *uint32 `json:"raw_material_id" query:"raw_material_id"` // 按原料ID精确查询
|
||||||
|
SourceTypes []string `json:"source_types" query:"source_types"` // 按来源类型查询
|
||||||
|
StartTime *string `json:"start_time" query:"start_time"` // 开始时间 (RFC3339格式, e.g., "2023-01-01T00:00:00Z")
|
||||||
|
EndTime *string `json:"end_time" query:"end_time"` // 结束时间 (RFC3339格式)
|
||||||
|
OrderBy string `json:"order_by" query:"order_by"` // 排序字段
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListStockLogResponse 是获取库存变动历史列表的响应结构
|
||||||
|
type ListStockLogResponse struct {
|
||||||
|
List []StockLogResponse `json:"list"`
|
||||||
|
Pagination PaginationDTO `json:"pagination"`
|
||||||
|
}
|
||||||
157
internal/app/service/inventory_service.go
Normal file
157
internal/app/service/inventory_service.go
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.huangwc.com/pig/pig-farm-controller/internal/app/dto"
|
||||||
|
"git.huangwc.com/pig/pig-farm-controller/internal/domain/inventory"
|
||||||
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
|
||||||
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||||
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 定义库存应用服务特定的错误
|
||||||
|
var (
|
||||||
|
ErrInventoryRawMaterialNotFound = errors.New("原料不存在")
|
||||||
|
ErrInventoryInsufficientStock = errors.New("原料库存不足")
|
||||||
|
)
|
||||||
|
|
||||||
|
// InventoryService 定义了库存相关的应用服务接口
|
||||||
|
type InventoryService interface {
|
||||||
|
AdjustStock(ctx context.Context, req *dto.StockAdjustmentRequest) (*dto.StockLogResponse, error)
|
||||||
|
ListCurrentStock(ctx context.Context, req *dto.ListCurrentStockRequest) (*dto.ListCurrentStockResponse, error)
|
||||||
|
ListStockLogs(ctx context.Context, req *dto.ListStockLogRequest) (*dto.ListStockLogResponse, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// inventoryServiceImpl 是 InventoryService 接口的实现
|
||||||
|
type inventoryServiceImpl struct {
|
||||||
|
ctx context.Context
|
||||||
|
invSvc inventory.InventoryCoreService
|
||||||
|
rawMatRepo repository.RawMaterialRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInventoryService 创建一个新的 InventoryService 实例
|
||||||
|
func NewInventoryService(ctx context.Context, invSvc inventory.InventoryCoreService, rawMatRepo repository.RawMaterialRepository) InventoryService {
|
||||||
|
return &inventoryServiceImpl{
|
||||||
|
ctx: ctx,
|
||||||
|
invSvc: invSvc,
|
||||||
|
rawMatRepo: rawMatRepo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AdjustStock 手动调整库存
|
||||||
|
func (s *inventoryServiceImpl) AdjustStock(ctx context.Context, req *dto.StockAdjustmentRequest) (*dto.StockLogResponse, error) {
|
||||||
|
serviceCtx := logs.AddFuncName(ctx, s.ctx, "AdjustStock")
|
||||||
|
|
||||||
|
// 调用领域服务执行核心业务逻辑
|
||||||
|
log, err := s.invSvc.AdjustStock(serviceCtx, req.RawMaterialID, req.ChangeAmount, models.StockLogSourceManual, nil, req.Remarks)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, inventory.ErrRawMaterialNotFound) {
|
||||||
|
return nil, ErrInventoryRawMaterialNotFound
|
||||||
|
}
|
||||||
|
if errors.Is(err, inventory.ErrInsufficientStock) {
|
||||||
|
return nil, ErrInventoryInsufficientStock
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("调整库存失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 手动加载 RawMaterial 信息,因为 CreateRawMaterialStockLog 不会预加载它
|
||||||
|
rawMaterial, err := s.rawMatRepo.GetRawMaterialByID(serviceCtx, log.RawMaterialID)
|
||||||
|
if err != nil {
|
||||||
|
// 理论上不应该发生,因为 AdjustStock 内部已经检查过
|
||||||
|
return nil, fmt.Errorf("获取原料信息失败: %w", err)
|
||||||
|
}
|
||||||
|
log.RawMaterial = *rawMaterial
|
||||||
|
|
||||||
|
return dto.ConvertStockLogToDTO(log), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListCurrentStock 列出所有原料的当前库存
|
||||||
|
func (s *inventoryServiceImpl) ListCurrentStock(ctx context.Context, req *dto.ListCurrentStockRequest) (*dto.ListCurrentStockResponse, error) {
|
||||||
|
serviceCtx := logs.AddFuncName(ctx, s.ctx, "ListCurrentStock")
|
||||||
|
|
||||||
|
// 1. 获取分页的原料列表
|
||||||
|
rawMatOpts := repository.RawMaterialListOptions{
|
||||||
|
Name: req.RawMaterialName,
|
||||||
|
OrderBy: req.OrderBy, // 注意:这里的排序可能需要调整,比如按原料名排序
|
||||||
|
}
|
||||||
|
rawMaterials, total, err := s.rawMatRepo.ListRawMaterials(serviceCtx, rawMatOpts, req.Page, req.PageSize)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("获取原料列表失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(rawMaterials) == 0 {
|
||||||
|
return &dto.ListCurrentStockResponse{
|
||||||
|
List: []dto.CurrentStockResponse{},
|
||||||
|
Pagination: dto.PaginationDTO{Page: req.Page, PageSize: req.PageSize, Total: total},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 提取原料ID并批量获取它们的最新库存日志
|
||||||
|
materialIDs := make([]uint32, len(rawMaterials))
|
||||||
|
for i, rm := range rawMaterials {
|
||||||
|
materialIDs[i] = rm.ID
|
||||||
|
}
|
||||||
|
latestLogMap, err := s.rawMatRepo.BatchGetLatestStockLogsForMaterials(serviceCtx, materialIDs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("批量获取最新库存日志失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 组合原料信息和库存信息
|
||||||
|
stockDTOs := make([]dto.CurrentStockResponse, len(rawMaterials))
|
||||||
|
for i, rm := range rawMaterials {
|
||||||
|
log, _ := latestLogMap[rm.ID] // 如果找不到,log会是零值
|
||||||
|
stockDTOs[i] = *dto.ConvertCurrentStockToDTO(&rm, &log)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &dto.ListCurrentStockResponse{
|
||||||
|
List: stockDTOs,
|
||||||
|
Pagination: dto.PaginationDTO{Page: req.Page, PageSize: req.PageSize, Total: total},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListStockLogs 列出库存变动历史
|
||||||
|
func (s *inventoryServiceImpl) ListStockLogs(ctx context.Context, req *dto.ListStockLogRequest) (*dto.ListStockLogResponse, error) {
|
||||||
|
serviceCtx := logs.AddFuncName(ctx, s.ctx, "ListStockLogs")
|
||||||
|
|
||||||
|
// 解析时间字符串
|
||||||
|
var startTime, endTime *time.Time
|
||||||
|
if req.StartTime != nil && *req.StartTime != "" {
|
||||||
|
t, err := time.Parse(time.RFC3339, *req.StartTime)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("无效的开始时间格式: %w", err)
|
||||||
|
}
|
||||||
|
startTime = &t
|
||||||
|
}
|
||||||
|
if req.EndTime != nil && *req.EndTime != "" {
|
||||||
|
t, err := time.Parse(time.RFC3339, *req.EndTime)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("无效的结束时间格式: %w", err)
|
||||||
|
}
|
||||||
|
endTime = &t
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换 source types
|
||||||
|
sourceTypes := make([]models.StockLogSourceType, len(req.SourceTypes))
|
||||||
|
for i, st := range req.SourceTypes {
|
||||||
|
sourceTypes[i] = models.StockLogSourceType(st)
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := repository.StockLogListOptions{
|
||||||
|
RawMaterialID: req.RawMaterialID,
|
||||||
|
SourceTypes: sourceTypes,
|
||||||
|
StartTime: startTime,
|
||||||
|
EndTime: endTime,
|
||||||
|
OrderBy: req.OrderBy,
|
||||||
|
}
|
||||||
|
|
||||||
|
logs, total, err := s.invSvc.ListStockLogs(serviceCtx, opts, req.Page, req.PageSize)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("获取库存日志列表失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return dto.ConvertStockLogListToDTO(logs, total, req.Page, req.PageSize), nil
|
||||||
|
}
|
||||||
@@ -69,6 +69,7 @@ func NewApplication(configPath string) (*Application, error) {
|
|||||||
appServices.pigAgeStageService,
|
appServices.pigAgeStageService,
|
||||||
appServices.pigTypeService,
|
appServices.pigTypeService,
|
||||||
appServices.recipeService,
|
appServices.recipeService,
|
||||||
|
appServices.inventoryService,
|
||||||
infra.tokenGenerator,
|
infra.tokenGenerator,
|
||||||
infra.lora.listenHandler,
|
infra.lora.listenHandler,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"git.huangwc.com/pig/pig-farm-controller/internal/app/webhook"
|
"git.huangwc.com/pig/pig-farm-controller/internal/app/webhook"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/domain/alarm"
|
"git.huangwc.com/pig/pig-farm-controller/internal/domain/alarm"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/domain/device"
|
"git.huangwc.com/pig/pig-farm-controller/internal/domain/device"
|
||||||
|
"git.huangwc.com/pig/pig-farm-controller/internal/domain/inventory"
|
||||||
domain_notify "git.huangwc.com/pig/pig-farm-controller/internal/domain/notify"
|
domain_notify "git.huangwc.com/pig/pig-farm-controller/internal/domain/notify"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/domain/pig"
|
"git.huangwc.com/pig/pig-farm-controller/internal/domain/pig"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/domain/plan"
|
"git.huangwc.com/pig/pig-farm-controller/internal/domain/plan"
|
||||||
@@ -137,6 +138,7 @@ type DomainServices struct {
|
|||||||
notifyService domain_notify.Service
|
notifyService domain_notify.Service
|
||||||
alarmService alarm.AlarmService
|
alarmService alarm.AlarmService
|
||||||
recipeService recipe.Service
|
recipeService recipe.Service
|
||||||
|
inventoryService inventory.InventoryCoreService
|
||||||
}
|
}
|
||||||
|
|
||||||
// initDomainServices 初始化所有的领域服务。
|
// initDomainServices 初始化所有的领域服务。
|
||||||
@@ -233,6 +235,9 @@ func initDomainServices(ctx context.Context, cfg *config.Config, infra *Infrastr
|
|||||||
recipeCoreService,
|
recipeCoreService,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 库存管理
|
||||||
|
inventoryService := inventory.NewInventoryCoreService(logs.AddCompName(baseCtx, "InventoryCoreService"), infra.repos.unitOfWork, infra.repos.rawMaterialRepo)
|
||||||
|
|
||||||
return &DomainServices{
|
return &DomainServices{
|
||||||
pigPenTransferManager: pigPenTransferManager,
|
pigPenTransferManager: pigPenTransferManager,
|
||||||
pigTradeManager: pigTradeManager,
|
pigTradeManager: pigTradeManager,
|
||||||
@@ -246,6 +251,7 @@ func initDomainServices(ctx context.Context, cfg *config.Config, infra *Infrastr
|
|||||||
notifyService: notifyService,
|
notifyService: notifyService,
|
||||||
alarmService: alarmService,
|
alarmService: alarmService,
|
||||||
recipeService: recipeService,
|
recipeService: recipeService,
|
||||||
|
inventoryService: inventoryService,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -265,6 +271,7 @@ type AppServices struct {
|
|||||||
pigTypeService service.PigTypeService
|
pigTypeService service.PigTypeService
|
||||||
rawMaterialService service.RawMaterialService
|
rawMaterialService service.RawMaterialService
|
||||||
recipeService service.RecipeService
|
recipeService service.RecipeService
|
||||||
|
inventoryService service.InventoryService
|
||||||
}
|
}
|
||||||
|
|
||||||
// initAppServices 初始化所有的应用服务。
|
// initAppServices 初始化所有的应用服务。
|
||||||
@@ -318,6 +325,7 @@ func initAppServices(ctx context.Context, infra *Infrastructure, domainServices
|
|||||||
pigTypeService := service.NewPigTypeService(logs.AddCompName(baseCtx, "PigTypeService"), domainServices.recipeService)
|
pigTypeService := service.NewPigTypeService(logs.AddCompName(baseCtx, "PigTypeService"), domainServices.recipeService)
|
||||||
rawMaterialService := service.NewRawMaterialService(logs.AddCompName(baseCtx, "RawMaterialService"), domainServices.recipeService)
|
rawMaterialService := service.NewRawMaterialService(logs.AddCompName(baseCtx, "RawMaterialService"), domainServices.recipeService)
|
||||||
recipeService := service.NewRecipeService(logs.AddCompName(baseCtx, "RecipeService"), domainServices.recipeService)
|
recipeService := service.NewRecipeService(logs.AddCompName(baseCtx, "RecipeService"), domainServices.recipeService)
|
||||||
|
inventoryService := service.NewInventoryService(logs.AddCompName(baseCtx, "InventoryService"), domainServices.inventoryService, infra.repos.rawMaterialRepo)
|
||||||
|
|
||||||
return &AppServices{
|
return &AppServices{
|
||||||
pigFarmService: pigFarmService,
|
pigFarmService: pigFarmService,
|
||||||
@@ -334,6 +342,7 @@ func initAppServices(ctx context.Context, infra *Infrastructure, domainServices
|
|||||||
pigTypeService: pigTypeService,
|
pigTypeService: pigTypeService,
|
||||||
rawMaterialService: rawMaterialService,
|
rawMaterialService: rawMaterialService,
|
||||||
recipeService: recipeService,
|
recipeService: recipeService,
|
||||||
|
inventoryService: inventoryService,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
170
internal/domain/inventory/inventory_service.go
Normal file
170
internal/domain/inventory/inventory_service.go
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
package inventory
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
|
||||||
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||||
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 定义领域特定的错误
|
||||||
|
var (
|
||||||
|
ErrRawMaterialNotFound = errors.New("原料不存在")
|
||||||
|
ErrInsufficientStock = errors.New("原料库存不足")
|
||||||
|
)
|
||||||
|
|
||||||
|
// InventoryCoreService 定义了库存领域的核心业务服务接口
|
||||||
|
type InventoryCoreService interface {
|
||||||
|
// AdjustStock 调整指定原料的库存
|
||||||
|
AdjustStock(ctx context.Context, rawMaterialID uint32, changeAmount float32, sourceType models.StockLogSourceType, sourceID *uint32, remarks string) (*models.RawMaterialStockLog, error)
|
||||||
|
// GetCurrentStock 获取单个原料的当前库存量
|
||||||
|
GetCurrentStock(ctx context.Context, rawMaterialID uint32) (float32, error)
|
||||||
|
// BatchGetCurrentStock 批量获取多个原料的当前库存量
|
||||||
|
BatchGetCurrentStock(ctx context.Context, rawMaterialIDs []uint32) (map[uint32]float32, error)
|
||||||
|
// ListStockLogs 分页查询库存变动日志
|
||||||
|
ListStockLogs(ctx context.Context, opts repository.StockLogListOptions, page, pageSize int) ([]models.RawMaterialStockLog, int64, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// inventoryCoreServiceImpl 是 InventoryCoreService 的实现
|
||||||
|
type inventoryCoreServiceImpl struct {
|
||||||
|
ctx context.Context
|
||||||
|
uow repository.UnitOfWork
|
||||||
|
rawMatRepo repository.RawMaterialRepository
|
||||||
|
|
||||||
|
// 全局库存调整锁,确保所有 AdjustStock 操作串行执行
|
||||||
|
adjustStockMutex sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInventoryCoreService 创建一个新的 InventoryCoreService 实例
|
||||||
|
func NewInventoryCoreService(ctx context.Context, uow repository.UnitOfWork, rawMatRepo repository.RawMaterialRepository) InventoryCoreService {
|
||||||
|
return &inventoryCoreServiceImpl{
|
||||||
|
ctx: ctx,
|
||||||
|
uow: uow,
|
||||||
|
rawMatRepo: rawMatRepo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AdjustStock 调整指定原料的库存
|
||||||
|
func (s *inventoryCoreServiceImpl) AdjustStock(ctx context.Context, rawMaterialID uint32, changeAmount float32, sourceType models.StockLogSourceType, sourceID *uint32, remarks string) (*models.RawMaterialStockLog, error) {
|
||||||
|
serviceCtx := logs.AddFuncName(ctx, s.ctx, "AdjustStock")
|
||||||
|
|
||||||
|
// 使用全局锁确保所有库存调整操作串行执行
|
||||||
|
s.adjustStockMutex.Lock()
|
||||||
|
defer s.adjustStockMutex.Unlock()
|
||||||
|
|
||||||
|
var createdLog *models.RawMaterialStockLog
|
||||||
|
|
||||||
|
err := s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error {
|
||||||
|
// 在事务中创建 RawMaterialRepository 的新实例
|
||||||
|
txRawMatRepo := repository.NewGormRawMaterialRepository(serviceCtx, tx)
|
||||||
|
|
||||||
|
// 1. 检查原料是否存在
|
||||||
|
_, err := txRawMatRepo.GetRawMaterialByID(serviceCtx, rawMaterialID)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return ErrRawMaterialNotFound
|
||||||
|
}
|
||||||
|
return fmt.Errorf("检查原料是否存在时出错: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 获取当前库存 (在程序锁的保护下,这里是安全的)
|
||||||
|
latestLog, err := txRawMatRepo.GetLatestRawMaterialStockLog(serviceCtx, rawMaterialID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("获取最新库存日志失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var beforeQuantity float32 = 0
|
||||||
|
if latestLog != nil {
|
||||||
|
beforeQuantity = latestLog.AfterQuantity
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 计算新库存并检查是否充足
|
||||||
|
afterQuantity := beforeQuantity + changeAmount
|
||||||
|
if afterQuantity < 0 {
|
||||||
|
return ErrInsufficientStock
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 创建新的库存日志
|
||||||
|
newLog := &models.RawMaterialStockLog{
|
||||||
|
RawMaterialID: rawMaterialID,
|
||||||
|
ChangeAmount: changeAmount,
|
||||||
|
BeforeQuantity: beforeQuantity,
|
||||||
|
AfterQuantity: afterQuantity,
|
||||||
|
SourceType: sourceType,
|
||||||
|
SourceID: sourceID,
|
||||||
|
HappenedAt: time.Now(),
|
||||||
|
Remarks: remarks,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := txRawMatRepo.CreateRawMaterialStockLog(serviceCtx, newLog); err != nil {
|
||||||
|
return fmt.Errorf("创建库存日志失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
createdLog = newLog
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err // 直接返回事务中发生的错误
|
||||||
|
}
|
||||||
|
|
||||||
|
return createdLog, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCurrentStock 获取单个原料的当前库存量
|
||||||
|
func (s *inventoryCoreServiceImpl) GetCurrentStock(ctx context.Context, rawMaterialID uint32) (float32, error) {
|
||||||
|
serviceCtx := logs.AddFuncName(ctx, s.ctx, "GetCurrentStock")
|
||||||
|
|
||||||
|
latestLog, err := s.rawMatRepo.GetLatestRawMaterialStockLog(serviceCtx, rawMaterialID)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("获取最新库存日志失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if latestLog == nil {
|
||||||
|
// 如果没有日志,说明从未入库,库存为0
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return latestLog.AfterQuantity, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BatchGetCurrentStock 批量获取多个原料的当前库存量
|
||||||
|
func (s *inventoryCoreServiceImpl) BatchGetCurrentStock(ctx context.Context, rawMaterialIDs []uint32) (map[uint32]float32, error) {
|
||||||
|
serviceCtx := logs.AddFuncName(ctx, s.ctx, "BatchGetCurrentStock")
|
||||||
|
|
||||||
|
logMap, err := s.rawMatRepo.BatchGetLatestStockLogsForMaterials(serviceCtx, rawMaterialIDs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("批量获取最新库存日志失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
stockMap := make(map[uint32]float32, len(rawMaterialIDs))
|
||||||
|
for _, id := range rawMaterialIDs {
|
||||||
|
if log, ok := logMap[id]; ok {
|
||||||
|
stockMap[id] = log.AfterQuantity
|
||||||
|
} else {
|
||||||
|
// 如果某个原料在 logMap 中不存在,说明它没有任何库存记录,库存为0
|
||||||
|
stockMap[id] = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return stockMap, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListStockLogs 分页查询库存变动日志
|
||||||
|
func (s *inventoryCoreServiceImpl) ListStockLogs(ctx context.Context, opts repository.StockLogListOptions, page, pageSize int) ([]models.RawMaterialStockLog, int64, error) {
|
||||||
|
serviceCtx := logs.AddFuncName(ctx, s.ctx, "ListStockLogs")
|
||||||
|
|
||||||
|
logs, total, err := s.rawMatRepo.ListStockLogs(serviceCtx, opts, page, pageSize)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, fmt.Errorf("获取库存日志列表失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return logs, total, nil
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
|
||||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||||
@@ -18,6 +19,16 @@ type RawMaterialListOptions struct {
|
|||||||
OrderBy string
|
OrderBy string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StockLogListOptions 定义了查询库存日志列表时的筛选条件
|
||||||
|
type StockLogListOptions struct {
|
||||||
|
RawMaterialID *uint32
|
||||||
|
RawMaterialName *string
|
||||||
|
SourceTypes []models.StockLogSourceType
|
||||||
|
StartTime *time.Time
|
||||||
|
EndTime *time.Time
|
||||||
|
OrderBy string
|
||||||
|
}
|
||||||
|
|
||||||
// RawMaterialRepository 定义了与原料相关的数据库操作接口
|
// RawMaterialRepository 定义了与原料相关的数据库操作接口
|
||||||
type RawMaterialRepository interface {
|
type RawMaterialRepository interface {
|
||||||
CreateRawMaterial(ctx context.Context, rawMaterial *models.RawMaterial) error
|
CreateRawMaterial(ctx context.Context, rawMaterial *models.RawMaterial) error
|
||||||
@@ -32,6 +43,10 @@ type RawMaterialRepository interface {
|
|||||||
// 库存日志相关方法
|
// 库存日志相关方法
|
||||||
CreateRawMaterialStockLog(ctx context.Context, log *models.RawMaterialStockLog) error
|
CreateRawMaterialStockLog(ctx context.Context, log *models.RawMaterialStockLog) error
|
||||||
GetLatestRawMaterialStockLog(ctx context.Context, rawMaterialID uint32) (*models.RawMaterialStockLog, error)
|
GetLatestRawMaterialStockLog(ctx context.Context, rawMaterialID uint32) (*models.RawMaterialStockLog, error)
|
||||||
|
// BatchGetLatestStockLogsForMaterials 批量获取一组原料的最新库存日志
|
||||||
|
BatchGetLatestStockLogsForMaterials(ctx context.Context, materialIDs []uint32) (map[uint32]models.RawMaterialStockLog, error)
|
||||||
|
// ListStockLogs 分页列出库存变动日志
|
||||||
|
ListStockLogs(ctx context.Context, opts StockLogListOptions, page, pageSize int) ([]models.RawMaterialStockLog, int64, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// gormRawMaterialRepository 是 RawMaterialRepository 的 GORM 实现
|
// gormRawMaterialRepository 是 RawMaterialRepository 的 GORM 实现
|
||||||
@@ -219,3 +234,87 @@ func (r *gormRawMaterialRepository) GetLatestRawMaterialStockLog(ctx context.Con
|
|||||||
}
|
}
|
||||||
return &latestLog, nil
|
return &latestLog, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BatchGetLatestStockLogsForMaterials 批量获取一组原料的最新库存日志
|
||||||
|
func (r *gormRawMaterialRepository) BatchGetLatestStockLogsForMaterials(ctx context.Context, materialIDs []uint32) (map[uint32]models.RawMaterialStockLog, error) {
|
||||||
|
repoCtx := logs.AddFuncName(ctx, r.ctx, "BatchGetLatestStockLogsForMaterials")
|
||||||
|
if len(materialIDs) == 0 {
|
||||||
|
return make(map[uint32]models.RawMaterialStockLog), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var latestLogs []models.RawMaterialStockLog
|
||||||
|
|
||||||
|
// 使用窗口函数 ROW_NUMBER() 来为每个原料的日志分区,并按时间倒序排名。
|
||||||
|
// 这样可以高效地一次性查询出每个原料的最新一条日志。
|
||||||
|
subQuery := r.db.Model(&models.RawMaterialStockLog{}).
|
||||||
|
Select("*, ROW_NUMBER() OVER(PARTITION BY raw_material_id ORDER BY happened_at DESC, id DESC) as rn").
|
||||||
|
Where("raw_material_id IN ?", materialIDs)
|
||||||
|
|
||||||
|
err := r.db.WithContext(repoCtx).
|
||||||
|
Table("(?) as sub", subQuery).
|
||||||
|
Where("rn = 1").
|
||||||
|
Find(&latestLogs).Error
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("批量获取最新库存日志失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将结果转换为 map[uint32]models.RawMaterialStockLog 以方便查找
|
||||||
|
logMap := make(map[uint32]models.RawMaterialStockLog, len(latestLogs))
|
||||||
|
for _, log := range latestLogs {
|
||||||
|
logMap[log.RawMaterialID] = log
|
||||||
|
}
|
||||||
|
|
||||||
|
return logMap, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListStockLogs 分页列出库存变动日志
|
||||||
|
func (r *gormRawMaterialRepository) ListStockLogs(ctx context.Context, opts StockLogListOptions, page, pageSize int) ([]models.RawMaterialStockLog, int64, error) {
|
||||||
|
repoCtx := logs.AddFuncName(ctx, r.ctx, "ListStockLogs")
|
||||||
|
var logs []models.RawMaterialStockLog
|
||||||
|
var total int64
|
||||||
|
|
||||||
|
db := r.db.WithContext(repoCtx).Model(&models.RawMaterialStockLog{})
|
||||||
|
|
||||||
|
// 应用筛选条件
|
||||||
|
if opts.RawMaterialID != nil {
|
||||||
|
db = db.Where("raw_material_id = ?", *opts.RawMaterialID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增:按原料名称模糊搜索
|
||||||
|
if opts.RawMaterialName != nil && *opts.RawMaterialName != "" {
|
||||||
|
// 使用子查询找到匹配的原料ID
|
||||||
|
subQuery := r.db.Model(&models.RawMaterial{}).Select("id").Where("name LIKE ?", "%"+*opts.RawMaterialName+"%")
|
||||||
|
db = db.Where("raw_material_id IN (?)", subQuery)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(opts.SourceTypes) > 0 {
|
||||||
|
db = db.Where("source_type IN ?", opts.SourceTypes)
|
||||||
|
}
|
||||||
|
if opts.StartTime != nil {
|
||||||
|
db = db.Where("happened_at >= ?", *opts.StartTime)
|
||||||
|
}
|
||||||
|
if opts.EndTime != nil {
|
||||||
|
db = db.Where("happened_at <= ?", *opts.EndTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 首先计算总数
|
||||||
|
if err := db.Count(&total).Error; err != nil {
|
||||||
|
return nil, 0, fmt.Errorf("统计库存日志总数失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 然后应用排序、分页并获取数据
|
||||||
|
if opts.OrderBy != "" {
|
||||||
|
db = db.Order(opts.OrderBy)
|
||||||
|
} else {
|
||||||
|
// 默认排序
|
||||||
|
db = db.Order("happened_at DESC, id DESC")
|
||||||
|
}
|
||||||
|
|
||||||
|
offset := (page - 1) * pageSize
|
||||||
|
if err := db.Preload("RawMaterial").Offset(offset).Limit(pageSize).Find(&logs).Error; err != nil {
|
||||||
|
return nil, 0, fmt.Errorf("查询库存日志列表失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return logs, total, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ design/archive/2025-11-05-provide-logger-with-mothed/task-webhook.md
|
|||||||
design/archive/2025-11-06-health-check-routing/index.md
|
design/archive/2025-11-06-health-check-routing/index.md
|
||||||
design/archive/2025-11-06-system-plan-continuously-triggered/index.md
|
design/archive/2025-11-06-system-plan-continuously-triggered/index.md
|
||||||
design/archive/2025-11-10-exceeding-threshold-alarm/index.md
|
design/archive/2025-11-10-exceeding-threshold-alarm/index.md
|
||||||
design/archive/recipe-management/index.md
|
design/recipe-management/index.md
|
||||||
docs/docs.go
|
docs/docs.go
|
||||||
docs/swagger.json
|
docs/swagger.json
|
||||||
docs/swagger.yaml
|
docs/swagger.yaml
|
||||||
@@ -55,6 +55,7 @@ internal/app/controller/feed/pig_type_controller.go
|
|||||||
internal/app/controller/feed/raw_material_controller.go
|
internal/app/controller/feed/raw_material_controller.go
|
||||||
internal/app/controller/feed/recipe_controller.go
|
internal/app/controller/feed/recipe_controller.go
|
||||||
internal/app/controller/health/health_controller.go
|
internal/app/controller/health/health_controller.go
|
||||||
|
internal/app/controller/inventory/inventory_controller.go
|
||||||
internal/app/controller/management/controller_helpers.go
|
internal/app/controller/management/controller_helpers.go
|
||||||
internal/app/controller/management/pig_batch_controller.go
|
internal/app/controller/management/pig_batch_controller.go
|
||||||
internal/app/controller/management/pig_batch_health_controller.go
|
internal/app/controller/management/pig_batch_health_controller.go
|
||||||
@@ -72,6 +73,8 @@ internal/app/dto/device_dto.go
|
|||||||
internal/app/dto/dto.go
|
internal/app/dto/dto.go
|
||||||
internal/app/dto/feed_converter.go
|
internal/app/dto/feed_converter.go
|
||||||
internal/app/dto/feed_dto.go
|
internal/app/dto/feed_dto.go
|
||||||
|
internal/app/dto/inventory_converter.go
|
||||||
|
internal/app/dto/inventory_dto.go
|
||||||
internal/app/dto/monitor_converter.go
|
internal/app/dto/monitor_converter.go
|
||||||
internal/app/dto/monitor_dto.go
|
internal/app/dto/monitor_dto.go
|
||||||
internal/app/dto/notification_converter.go
|
internal/app/dto/notification_converter.go
|
||||||
@@ -85,6 +88,7 @@ internal/app/middleware/audit.go
|
|||||||
internal/app/middleware/auth.go
|
internal/app/middleware/auth.go
|
||||||
internal/app/service/audit_service.go
|
internal/app/service/audit_service.go
|
||||||
internal/app/service/device_service.go
|
internal/app/service/device_service.go
|
||||||
|
internal/app/service/inventory_service.go
|
||||||
internal/app/service/monitor_service.go
|
internal/app/service/monitor_service.go
|
||||||
internal/app/service/nutrient_service.go
|
internal/app/service/nutrient_service.go
|
||||||
internal/app/service/pig_age_stage_service.go
|
internal/app/service/pig_age_stage_service.go
|
||||||
@@ -108,6 +112,7 @@ internal/core/data_initializer.go
|
|||||||
internal/domain/alarm/alarm_service.go
|
internal/domain/alarm/alarm_service.go
|
||||||
internal/domain/device/device_service.go
|
internal/domain/device/device_service.go
|
||||||
internal/domain/device/general_device_service.go
|
internal/domain/device/general_device_service.go
|
||||||
|
internal/domain/inventory/inventory_service.go
|
||||||
internal/domain/notify/notify.go
|
internal/domain/notify/notify.go
|
||||||
internal/domain/pig/pen_transfer_manager.go
|
internal/domain/pig/pen_transfer_manager.go
|
||||||
internal/domain/pig/pig_batch_service.go
|
internal/domain/pig/pig_batch_service.go
|
||||||
|
|||||||
Reference in New Issue
Block a user