From e1399be5385b9273f61d9244c65f8a9e17b31cb0 Mon Sep 17 00:00:00 2001 From: huang <1724659546@qq.com> Date: Tue, 18 Nov 2025 22:22:31 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=A0=E9=99=A4=E5=8E=9F=E6=9C=89=E9=A3=9F?= =?UTF-8?q?=E7=89=A9=E9=80=BB=E8=BE=91=E5=92=8C=E6=A8=A1=E5=9E=8B=20?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=8E=9F=E6=96=99=E5=92=8C=E8=90=A5=E5=85=BB?= =?UTF-8?q?=E4=BB=B7=E5=80=BC=E8=A1=A8=E5=92=8C=E5=8E=9F=E6=96=99=E5=BA=93?= =?UTF-8?q?=E5=AD=98=E6=97=A5=E5=BF=97=E5=92=8C=E8=90=A5=E5=85=BB=E8=A1=A8?= =?UTF-8?q?=E5=AE=9A=E4=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- design/archive/recipe-management/index.md | 47 ++ docs/docs.go | 506 ++---------------- docs/swagger.json | 506 ++---------------- docs/swagger.yaml | 337 ++---------- internal/app/api/router.go | 3 - .../controller/monitor/monitor_controller.go | 102 ---- internal/app/dto/monitor_converter.go | 88 --- internal/app/dto/monitor_dto.go | 114 ---- internal/app/service/monitor_service.go | 68 --- internal/core/component_initializers.go | 3 - internal/infra/database/postgres.go | 17 +- internal/infra/models/feed.go | 111 ---- internal/infra/models/models.go | 6 +- internal/infra/models/raw_material.go | 92 ++++ .../repository/raw_material_repository.go | 187 ------- 15 files changed, 289 insertions(+), 1898 deletions(-) create mode 100644 design/archive/recipe-management/index.md delete mode 100644 internal/infra/models/feed.go create mode 100644 internal/infra/models/raw_material.go delete mode 100644 internal/infra/repository/raw_material_repository.go diff --git a/design/archive/recipe-management/index.md b/design/archive/recipe-management/index.md new file mode 100644 index 0000000..f0a5cf9 --- /dev/null +++ b/design/archive/recipe-management/index.md @@ -0,0 +1,47 @@ +# 需求 + +饲料配方管理及自动生成配方 + +## issue + +http://git.huangwc.com/pig/pig-farm-controller/issues/66 + +# 开发计划 + +1. 原料营养价值管理 + - 增删改查 + - 内置60+条常用原料(玉米、豆粕43、豆粕46、发酵豆粕、麸皮、次粉、DDGS、乳清粉、鱼粉、膨化大豆、各种氨基酸、预混料、油脂等) + - 每种原料固定营养值(消化能、粗蛋白、赖氨酸、钙、磷等15项左右) + +2. 饲料库存管理(代替批次) + - 字段:饲料名、当前原料种类、当前剩余量(吨)、上次入料日期、保质期剩余天数(手动填)、是否发酵料(勾选) + - 发酵料塔额外字段: + - 发酵状态(未发酵 / 正在发酵 / 已发酵可用) + - 发酵开始日期 + - 发酵几天(默认3~7天) + - 水分增加比例(默认+10~20%) + - 营养折损系数(可调,粗蛋白-5%、能量-3%之类) + +3. 猪只阶段营养需求管理 + - 预设10个常用阶段(教槽、仔猪、小猪、中猪、大猪、后备、怀孕前中后、哺乳) + - 每个阶段维护营养需求上下限(消化能、粗蛋白、赖氨酸、钙、有效磷等12项) + +4. 配方管理 + - 按阶段建配方 + - 支持增删改查 + 复制上个配方快速新建 + - 配方明细:原料 + 配比(%) + +5. 自动生成配方(核心功能) + - 选择阶段 → 点击“自动计算最低成本配方” + - 自动读取当前所有料塔的: + - 剩余量(不够的原料自动降配比) + - 保质期剩余天数(越快过期的优先用,权重×1.5) + - 发酵料塔如果状态是“已发酵可用”则按发酵后营养值参与计算 + - 输出:总成本、营养达标情况、发酵料占比、即将过期原料使用提示 + +6. 配方下发与记录 + - 一键下发到喂料站/料线(生成下料曲线) + - 自动记录今天用了哪个配方 + +7. 简单查看功能 + - 两个配方对比页面(营养+成本对比) diff --git a/docs/docs.go b/docs/docs.go index e956e45..dc7ca45 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -56,13 +56,13 @@ const docTemplate = `{ }, { "enum": [ - "Debug", - "Info", - "Warn", - "Error", - "DPanic", - "Panic", - "Fatal" + "debug", + "info", + "warn", + "error", + "dpanic", + "panic", + "fatal" ], "type": "string", "x-enum-varnames": [ @@ -172,13 +172,13 @@ const docTemplate = `{ }, { "enum": [ - "Debug", - "Info", - "Warn", - "Error", - "DPanic", - "Panic", - "Fatal" + "debug", + "info", + "warn", + "error", + "dpanic", + "panic", + "fatal" ], "type": "string", "x-enum-varnames": [ @@ -453,13 +453,13 @@ const docTemplate = `{ }, { "enum": [ - "Debug", - "Info", - "Warn", - "Error", - "DPanic", - "Panic", - "Fatal" + "debug", + "info", + "warn", + "error", + "dpanic", + "panic", + "fatal" ], "type": "string", "x-enum-varnames": [ @@ -740,13 +740,13 @@ const docTemplate = `{ "parameters": [ { "enum": [ - "Debug", - "Info", - "Warn", - "Error", - "DPanic", - "Panic", - "Fatal" + "debug", + "info", + "warn", + "error", + "dpanic", + "panic", + "fatal" ], "type": "string", "x-enum-varnames": [ @@ -1719,85 +1719,6 @@ const docTemplate = `{ } } }, - "/api/v1/monitor/feed-usage-records": { - "get": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "根据提供的过滤条件,分页获取饲料使用记录", - "produces": [ - "application/json" - ], - "tags": [ - "数据监控" - ], - "summary": "获取饲料使用记录列表", - "parameters": [ - { - "type": "string", - "name": "end_time", - "in": "query" - }, - { - "type": "integer", - "name": "feed_formula_id", - "in": "query" - }, - { - "type": "integer", - "name": "operator_id", - "in": "query" - }, - { - "type": "string", - "name": "order_by", - "in": "query" - }, - { - "type": "integer", - "name": "page", - "in": "query" - }, - { - "type": "integer", - "name": "page_size", - "in": "query" - }, - { - "type": "integer", - "name": "pen_id", - "in": "query" - }, - { - "type": "string", - "name": "start_time", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "allOf": [ - { - "$ref": "#/definitions/controller.Response" - }, - { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/dto.ListFeedUsageRecordResponse" - } - } - } - ] - } - } - } - } - }, "/api/v1/monitor/medication-logs": { "get": { "security": [ @@ -1905,6 +1826,7 @@ const docTemplate = `{ }, { "enum": [ + 7, -1, 0, 1, @@ -1914,12 +1836,12 @@ const docTemplate = `{ 5, -1, 5, - 6, - 7 + 6 ], "type": "integer", "format": "int32", "x-enum-varnames": [ + "_numLevels", "DebugLevel", "InfoLevel", "WarnLevel", @@ -1929,8 +1851,7 @@ const docTemplate = `{ "FatalLevel", "_minLevel", "_maxLevel", - "InvalidLevel", - "_numLevels" + "InvalidLevel" ], "name": "level", "in": "query" @@ -2588,159 +2509,6 @@ const docTemplate = `{ } } }, - "/api/v1/monitor/raw-material-purchases": { - "get": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "根据提供的过滤条件,分页获取原料采购记录", - "produces": [ - "application/json" - ], - "tags": [ - "数据监控" - ], - "summary": "获取原料采购记录列表", - "parameters": [ - { - "type": "string", - "name": "end_time", - "in": "query" - }, - { - "type": "string", - "name": "order_by", - "in": "query" - }, - { - "type": "integer", - "name": "page", - "in": "query" - }, - { - "type": "integer", - "name": "page_size", - "in": "query" - }, - { - "type": "integer", - "name": "raw_material_id", - "in": "query" - }, - { - "type": "string", - "name": "start_time", - "in": "query" - }, - { - "type": "string", - "name": "supplier", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "allOf": [ - { - "$ref": "#/definitions/controller.Response" - }, - { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/dto.ListRawMaterialPurchaseResponse" - } - } - } - ] - } - } - } - } - }, - "/api/v1/monitor/raw-material-stock-logs": { - "get": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "根据提供的过滤条件,分页获取原料库存日志", - "produces": [ - "application/json" - ], - "tags": [ - "数据监控" - ], - "summary": "获取原料库存日志列表", - "parameters": [ - { - "type": "string", - "name": "end_time", - "in": "query" - }, - { - "type": "string", - "name": "order_by", - "in": "query" - }, - { - "type": "integer", - "name": "page", - "in": "query" - }, - { - "type": "integer", - "name": "page_size", - "in": "query" - }, - { - "type": "integer", - "name": "raw_material_id", - "in": "query" - }, - { - "type": "integer", - "name": "source_id", - "in": "query" - }, - { - "type": "string", - "name": "source_type", - "in": "query" - }, - { - "type": "string", - "name": "start_time", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "allOf": [ - { - "$ref": "#/definitions/controller.Response" - }, - { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/dto.ListRawMaterialStockLogResponse" - } - } - } - ] - } - } - } - } - }, "/api/v1/monitor/sensor-data": { "get": { "security": [ @@ -5645,49 +5413,6 @@ const docTemplate = `{ } } }, - "dto.FeedFormulaDTO": { - "type": "object", - "properties": { - "id": { - "type": "integer" - }, - "name": { - "type": "string" - } - } - }, - "dto.FeedUsageRecordDTO": { - "type": "object", - "properties": { - "amount": { - "type": "number" - }, - "feed_formula": { - "$ref": "#/definitions/dto.FeedFormulaDTO" - }, - "feed_formula_id": { - "type": "integer" - }, - "id": { - "type": "integer" - }, - "operator_id": { - "type": "integer" - }, - "pen": { - "$ref": "#/definitions/dto.PenDTO" - }, - "pen_id": { - "type": "integer" - }, - "recorded_at": { - "type": "string" - }, - "remarks": { - "type": "string" - } - } - }, "dto.HistoricalAlarmDTO": { "type": "object", "properties": { @@ -5782,20 +5507,6 @@ const docTemplate = `{ } } }, - "dto.ListFeedUsageRecordResponse": { - "type": "object", - "properties": { - "list": { - "type": "array", - "items": { - "$ref": "#/definitions/dto.FeedUsageRecordDTO" - } - }, - "pagination": { - "$ref": "#/definitions/dto.PaginationDTO" - } - } - }, "dto.ListHistoricalAlarmResponse": { "type": "object", "properties": { @@ -5951,34 +5662,6 @@ const docTemplate = `{ } } }, - "dto.ListRawMaterialPurchaseResponse": { - "type": "object", - "properties": { - "list": { - "type": "array", - "items": { - "$ref": "#/definitions/dto.RawMaterialPurchaseDTO" - } - }, - "pagination": { - "$ref": "#/definitions/dto.PaginationDTO" - } - } - }, - "dto.ListRawMaterialStockLogResponse": { - "type": "object", - "properties": { - "list": { - "type": "array", - "items": { - "$ref": "#/definitions/dto.RawMaterialStockLogDTO" - } - }, - "pagination": { - "$ref": "#/definitions/dto.PaginationDTO" - } - } - }, "dto.ListSensorDataResponse": { "type": "object", "properties": { @@ -6216,17 +5899,6 @@ const docTemplate = `{ } } }, - "dto.PenDTO": { - "type": "object", - "properties": { - "id": { - "type": "integer" - }, - "name": { - "type": "string" - } - } - }, "dto.PenResponse": { "type": "object", "properties": { @@ -6733,75 +6405,6 @@ const docTemplate = `{ } } }, - "dto.RawMaterialDTO": { - "type": "object", - "properties": { - "id": { - "type": "integer" - }, - "name": { - "type": "string" - } - } - }, - "dto.RawMaterialPurchaseDTO": { - "type": "object", - "properties": { - "amount": { - "type": "number" - }, - "created_at": { - "type": "string" - }, - "id": { - "type": "integer" - }, - "purchase_date": { - "type": "string" - }, - "raw_material": { - "$ref": "#/definitions/dto.RawMaterialDTO" - }, - "raw_material_id": { - "type": "integer" - }, - "supplier": { - "type": "string" - }, - "total_price": { - "type": "number" - }, - "unit_price": { - "type": "number" - } - } - }, - "dto.RawMaterialStockLogDTO": { - "type": "object", - "properties": { - "change_amount": { - "type": "number" - }, - "happened_at": { - "type": "string" - }, - "id": { - "type": "integer" - }, - "raw_material_id": { - "type": "integer" - }, - "remarks": { - "type": "string" - }, - "source_id": { - "type": "integer" - }, - "source_type": { - "$ref": "#/definitions/models.StockLogSourceType" - } - } - }, "dto.ReclassifyPenToNewBatchRequest": { "type": "object", "required": [ @@ -8140,13 +7743,13 @@ const docTemplate = `{ "models.SeverityLevel": { "type": "string", "enum": [ - "Debug", - "Info", - "Warn", - "Error", - "DPanic", - "Panic", - "Fatal" + "debug", + "info", + "warn", + "error", + "dpanic", + "panic", + "fatal" ], "x-enum-varnames": [ "DebugLevel", @@ -8158,25 +7761,6 @@ const docTemplate = `{ "FatalLevel" ] }, - "models.StockLogSourceType": { - "type": "string", - "enum": [ - "采购入库", - "饲喂出库", - "变质出库", - "售卖出库", - "杂用领取", - "手动盘点" - ], - "x-enum-varnames": [ - "StockLogSourcePurchase", - "StockLogSourceFeeding", - "StockLogSourceDeteriorate", - "StockLogSourceSale", - "StockLogSourceMiscellaneous", - "StockLogSourceManual" - ] - }, "models.TaskType": { "type": "string", "enum": [ @@ -8185,6 +7769,7 @@ const docTemplate = `{ "下料", "全量采集", "告警通知", + "通知刷新", "设备阈值检查", "区域阈值检查" ], @@ -8194,6 +7779,7 @@ const docTemplate = `{ "TaskTypeAreaCollectorThresholdCheck": "区域阈值检查任务", "TaskTypeDeviceThresholdCheck": "设备阈值检查任务", "TaskTypeFullCollection": "新增的全量采集任务", + "TaskTypeNotificationRefresh": "通知刷新任务", "TaskTypeReleaseFeedWeight": "下料口释放指定重量任务", "TaskTypeWaiting": "等待任务" }, @@ -8203,6 +7789,7 @@ const docTemplate = `{ "下料口释放指定重量任务", "新增的全量采集任务", "告警通知任务", + "通知刷新任务", "设备阈值检查任务", "区域阈值检查任务" ], @@ -8212,6 +7799,7 @@ const docTemplate = `{ "TaskTypeReleaseFeedWeight", "TaskTypeFullCollection", "TaskTypeAlarmNotification", + "TaskTypeNotificationRefresh", "TaskTypeDeviceThresholdCheck", "TaskTypeAreaCollectorThresholdCheck" ] @@ -8249,6 +7837,7 @@ const docTemplate = `{ "type": "integer", "format": "int32", "enum": [ + 7, -1, 0, 1, @@ -8258,10 +7847,10 @@ const docTemplate = `{ 5, -1, 5, - 6, - 7 + 6 ], "x-enum-varnames": [ + "_numLevels", "DebugLevel", "InfoLevel", "WarnLevel", @@ -8271,8 +7860,7 @@ const docTemplate = `{ "FatalLevel", "_minLevel", "_maxLevel", - "InvalidLevel", - "_numLevels" + "InvalidLevel" ] } }, diff --git a/docs/swagger.json b/docs/swagger.json index 5edc711..e300abb 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -48,13 +48,13 @@ }, { "enum": [ - "Debug", - "Info", - "Warn", - "Error", - "DPanic", - "Panic", - "Fatal" + "debug", + "info", + "warn", + "error", + "dpanic", + "panic", + "fatal" ], "type": "string", "x-enum-varnames": [ @@ -164,13 +164,13 @@ }, { "enum": [ - "Debug", - "Info", - "Warn", - "Error", - "DPanic", - "Panic", - "Fatal" + "debug", + "info", + "warn", + "error", + "dpanic", + "panic", + "fatal" ], "type": "string", "x-enum-varnames": [ @@ -445,13 +445,13 @@ }, { "enum": [ - "Debug", - "Info", - "Warn", - "Error", - "DPanic", - "Panic", - "Fatal" + "debug", + "info", + "warn", + "error", + "dpanic", + "panic", + "fatal" ], "type": "string", "x-enum-varnames": [ @@ -732,13 +732,13 @@ "parameters": [ { "enum": [ - "Debug", - "Info", - "Warn", - "Error", - "DPanic", - "Panic", - "Fatal" + "debug", + "info", + "warn", + "error", + "dpanic", + "panic", + "fatal" ], "type": "string", "x-enum-varnames": [ @@ -1711,85 +1711,6 @@ } } }, - "/api/v1/monitor/feed-usage-records": { - "get": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "根据提供的过滤条件,分页获取饲料使用记录", - "produces": [ - "application/json" - ], - "tags": [ - "数据监控" - ], - "summary": "获取饲料使用记录列表", - "parameters": [ - { - "type": "string", - "name": "end_time", - "in": "query" - }, - { - "type": "integer", - "name": "feed_formula_id", - "in": "query" - }, - { - "type": "integer", - "name": "operator_id", - "in": "query" - }, - { - "type": "string", - "name": "order_by", - "in": "query" - }, - { - "type": "integer", - "name": "page", - "in": "query" - }, - { - "type": "integer", - "name": "page_size", - "in": "query" - }, - { - "type": "integer", - "name": "pen_id", - "in": "query" - }, - { - "type": "string", - "name": "start_time", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "allOf": [ - { - "$ref": "#/definitions/controller.Response" - }, - { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/dto.ListFeedUsageRecordResponse" - } - } - } - ] - } - } - } - } - }, "/api/v1/monitor/medication-logs": { "get": { "security": [ @@ -1897,6 +1818,7 @@ }, { "enum": [ + 7, -1, 0, 1, @@ -1906,12 +1828,12 @@ 5, -1, 5, - 6, - 7 + 6 ], "type": "integer", "format": "int32", "x-enum-varnames": [ + "_numLevels", "DebugLevel", "InfoLevel", "WarnLevel", @@ -1921,8 +1843,7 @@ "FatalLevel", "_minLevel", "_maxLevel", - "InvalidLevel", - "_numLevels" + "InvalidLevel" ], "name": "level", "in": "query" @@ -2580,159 +2501,6 @@ } } }, - "/api/v1/monitor/raw-material-purchases": { - "get": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "根据提供的过滤条件,分页获取原料采购记录", - "produces": [ - "application/json" - ], - "tags": [ - "数据监控" - ], - "summary": "获取原料采购记录列表", - "parameters": [ - { - "type": "string", - "name": "end_time", - "in": "query" - }, - { - "type": "string", - "name": "order_by", - "in": "query" - }, - { - "type": "integer", - "name": "page", - "in": "query" - }, - { - "type": "integer", - "name": "page_size", - "in": "query" - }, - { - "type": "integer", - "name": "raw_material_id", - "in": "query" - }, - { - "type": "string", - "name": "start_time", - "in": "query" - }, - { - "type": "string", - "name": "supplier", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "allOf": [ - { - "$ref": "#/definitions/controller.Response" - }, - { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/dto.ListRawMaterialPurchaseResponse" - } - } - } - ] - } - } - } - } - }, - "/api/v1/monitor/raw-material-stock-logs": { - "get": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "根据提供的过滤条件,分页获取原料库存日志", - "produces": [ - "application/json" - ], - "tags": [ - "数据监控" - ], - "summary": "获取原料库存日志列表", - "parameters": [ - { - "type": "string", - "name": "end_time", - "in": "query" - }, - { - "type": "string", - "name": "order_by", - "in": "query" - }, - { - "type": "integer", - "name": "page", - "in": "query" - }, - { - "type": "integer", - "name": "page_size", - "in": "query" - }, - { - "type": "integer", - "name": "raw_material_id", - "in": "query" - }, - { - "type": "integer", - "name": "source_id", - "in": "query" - }, - { - "type": "string", - "name": "source_type", - "in": "query" - }, - { - "type": "string", - "name": "start_time", - "in": "query" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "allOf": [ - { - "$ref": "#/definitions/controller.Response" - }, - { - "type": "object", - "properties": { - "data": { - "$ref": "#/definitions/dto.ListRawMaterialStockLogResponse" - } - } - } - ] - } - } - } - } - }, "/api/v1/monitor/sensor-data": { "get": { "security": [ @@ -5637,49 +5405,6 @@ } } }, - "dto.FeedFormulaDTO": { - "type": "object", - "properties": { - "id": { - "type": "integer" - }, - "name": { - "type": "string" - } - } - }, - "dto.FeedUsageRecordDTO": { - "type": "object", - "properties": { - "amount": { - "type": "number" - }, - "feed_formula": { - "$ref": "#/definitions/dto.FeedFormulaDTO" - }, - "feed_formula_id": { - "type": "integer" - }, - "id": { - "type": "integer" - }, - "operator_id": { - "type": "integer" - }, - "pen": { - "$ref": "#/definitions/dto.PenDTO" - }, - "pen_id": { - "type": "integer" - }, - "recorded_at": { - "type": "string" - }, - "remarks": { - "type": "string" - } - } - }, "dto.HistoricalAlarmDTO": { "type": "object", "properties": { @@ -5774,20 +5499,6 @@ } } }, - "dto.ListFeedUsageRecordResponse": { - "type": "object", - "properties": { - "list": { - "type": "array", - "items": { - "$ref": "#/definitions/dto.FeedUsageRecordDTO" - } - }, - "pagination": { - "$ref": "#/definitions/dto.PaginationDTO" - } - } - }, "dto.ListHistoricalAlarmResponse": { "type": "object", "properties": { @@ -5943,34 +5654,6 @@ } } }, - "dto.ListRawMaterialPurchaseResponse": { - "type": "object", - "properties": { - "list": { - "type": "array", - "items": { - "$ref": "#/definitions/dto.RawMaterialPurchaseDTO" - } - }, - "pagination": { - "$ref": "#/definitions/dto.PaginationDTO" - } - } - }, - "dto.ListRawMaterialStockLogResponse": { - "type": "object", - "properties": { - "list": { - "type": "array", - "items": { - "$ref": "#/definitions/dto.RawMaterialStockLogDTO" - } - }, - "pagination": { - "$ref": "#/definitions/dto.PaginationDTO" - } - } - }, "dto.ListSensorDataResponse": { "type": "object", "properties": { @@ -6208,17 +5891,6 @@ } } }, - "dto.PenDTO": { - "type": "object", - "properties": { - "id": { - "type": "integer" - }, - "name": { - "type": "string" - } - } - }, "dto.PenResponse": { "type": "object", "properties": { @@ -6725,75 +6397,6 @@ } } }, - "dto.RawMaterialDTO": { - "type": "object", - "properties": { - "id": { - "type": "integer" - }, - "name": { - "type": "string" - } - } - }, - "dto.RawMaterialPurchaseDTO": { - "type": "object", - "properties": { - "amount": { - "type": "number" - }, - "created_at": { - "type": "string" - }, - "id": { - "type": "integer" - }, - "purchase_date": { - "type": "string" - }, - "raw_material": { - "$ref": "#/definitions/dto.RawMaterialDTO" - }, - "raw_material_id": { - "type": "integer" - }, - "supplier": { - "type": "string" - }, - "total_price": { - "type": "number" - }, - "unit_price": { - "type": "number" - } - } - }, - "dto.RawMaterialStockLogDTO": { - "type": "object", - "properties": { - "change_amount": { - "type": "number" - }, - "happened_at": { - "type": "string" - }, - "id": { - "type": "integer" - }, - "raw_material_id": { - "type": "integer" - }, - "remarks": { - "type": "string" - }, - "source_id": { - "type": "integer" - }, - "source_type": { - "$ref": "#/definitions/models.StockLogSourceType" - } - } - }, "dto.ReclassifyPenToNewBatchRequest": { "type": "object", "required": [ @@ -8132,13 +7735,13 @@ "models.SeverityLevel": { "type": "string", "enum": [ - "Debug", - "Info", - "Warn", - "Error", - "DPanic", - "Panic", - "Fatal" + "debug", + "info", + "warn", + "error", + "dpanic", + "panic", + "fatal" ], "x-enum-varnames": [ "DebugLevel", @@ -8150,25 +7753,6 @@ "FatalLevel" ] }, - "models.StockLogSourceType": { - "type": "string", - "enum": [ - "采购入库", - "饲喂出库", - "变质出库", - "售卖出库", - "杂用领取", - "手动盘点" - ], - "x-enum-varnames": [ - "StockLogSourcePurchase", - "StockLogSourceFeeding", - "StockLogSourceDeteriorate", - "StockLogSourceSale", - "StockLogSourceMiscellaneous", - "StockLogSourceManual" - ] - }, "models.TaskType": { "type": "string", "enum": [ @@ -8177,6 +7761,7 @@ "下料", "全量采集", "告警通知", + "通知刷新", "设备阈值检查", "区域阈值检查" ], @@ -8186,6 +7771,7 @@ "TaskTypeAreaCollectorThresholdCheck": "区域阈值检查任务", "TaskTypeDeviceThresholdCheck": "设备阈值检查任务", "TaskTypeFullCollection": "新增的全量采集任务", + "TaskTypeNotificationRefresh": "通知刷新任务", "TaskTypeReleaseFeedWeight": "下料口释放指定重量任务", "TaskTypeWaiting": "等待任务" }, @@ -8195,6 +7781,7 @@ "下料口释放指定重量任务", "新增的全量采集任务", "告警通知任务", + "通知刷新任务", "设备阈值检查任务", "区域阈值检查任务" ], @@ -8204,6 +7791,7 @@ "TaskTypeReleaseFeedWeight", "TaskTypeFullCollection", "TaskTypeAlarmNotification", + "TaskTypeNotificationRefresh", "TaskTypeDeviceThresholdCheck", "TaskTypeAreaCollectorThresholdCheck" ] @@ -8241,6 +7829,7 @@ "type": "integer", "format": "int32", "enum": [ + 7, -1, 0, 1, @@ -8250,10 +7839,10 @@ 5, -1, 5, - 6, - 7 + 6 ], "x-enum-varnames": [ + "_numLevels", "DebugLevel", "InfoLevel", "WarnLevel", @@ -8263,8 +7852,7 @@ "FatalLevel", "_minLevel", "_maxLevel", - "InvalidLevel", - "_numLevels" + "InvalidLevel" ] } }, diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 74e893d..50e51df 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -432,34 +432,6 @@ definitions: thresholds: type: number type: object - dto.FeedFormulaDTO: - properties: - id: - type: integer - name: - type: string - type: object - dto.FeedUsageRecordDTO: - properties: - amount: - type: number - feed_formula: - $ref: '#/definitions/dto.FeedFormulaDTO' - feed_formula_id: - type: integer - id: - type: integer - operator_id: - type: integer - pen: - $ref: '#/definitions/dto.PenDTO' - pen_id: - type: integer - recorded_at: - type: string - remarks: - type: string - type: object dto.HistoricalAlarmDTO: properties: alarm_code: @@ -521,15 +493,6 @@ definitions: pagination: $ref: '#/definitions/dto.PaginationDTO' type: object - dto.ListFeedUsageRecordResponse: - properties: - list: - items: - $ref: '#/definitions/dto.FeedUsageRecordDTO' - type: array - pagination: - $ref: '#/definitions/dto.PaginationDTO' - type: object dto.ListHistoricalAlarmResponse: properties: list: @@ -630,24 +593,6 @@ definitions: example: 100 type: integer type: object - dto.ListRawMaterialPurchaseResponse: - properties: - list: - items: - $ref: '#/definitions/dto.RawMaterialPurchaseDTO' - type: array - pagination: - $ref: '#/definitions/dto.PaginationDTO' - type: object - dto.ListRawMaterialStockLogResponse: - properties: - list: - items: - $ref: '#/definitions/dto.RawMaterialStockLogDTO' - type: array - pagination: - $ref: '#/definitions/dto.PaginationDTO' - type: object dto.ListSensorDataResponse: properties: list: @@ -806,13 +751,6 @@ definitions: total: type: integer type: object - dto.PenDTO: - properties: - id: - type: integer - name: - type: string - type: object dto.PenResponse: properties: capacity: @@ -1142,51 +1080,6 @@ definitions: $ref: '#/definitions/dto.TaskResponse' type: array type: object - dto.RawMaterialDTO: - properties: - id: - type: integer - name: - type: string - type: object - dto.RawMaterialPurchaseDTO: - properties: - amount: - type: number - created_at: - type: string - id: - type: integer - purchase_date: - type: string - raw_material: - $ref: '#/definitions/dto.RawMaterialDTO' - raw_material_id: - type: integer - supplier: - type: string - total_price: - type: number - unit_price: - type: number - type: object - dto.RawMaterialStockLogDTO: - properties: - change_amount: - type: number - happened_at: - type: string - id: - type: integer - raw_material_id: - type: integer - remarks: - type: string - source_id: - type: integer - source_type: - $ref: '#/definitions/models.StockLogSourceType' - type: object dto.ReclassifyPenToNewBatchRequest: properties: pen_id: @@ -2159,13 +2052,13 @@ definitions: - SensorTypeWeight models.SeverityLevel: enum: - - Debug - - Info - - Warn - - Error - - DPanic - - Panic - - Fatal + - debug + - info + - warn + - error + - dpanic + - panic + - fatal type: string x-enum-varnames: - DebugLevel @@ -2175,22 +2068,6 @@ definitions: - DPanicLevel - PanicLevel - FatalLevel - models.StockLogSourceType: - enum: - - 采购入库 - - 饲喂出库 - - 变质出库 - - 售卖出库 - - 杂用领取 - - 手动盘点 - type: string - x-enum-varnames: - - StockLogSourcePurchase - - StockLogSourceFeeding - - StockLogSourceDeteriorate - - StockLogSourceSale - - StockLogSourceMiscellaneous - - StockLogSourceManual models.TaskType: enum: - 计划分析 @@ -2198,6 +2075,7 @@ definitions: - 下料 - 全量采集 - 告警通知 + - 通知刷新 - 设备阈值检查 - 区域阈值检查 type: string @@ -2207,6 +2085,7 @@ definitions: TaskTypeAreaCollectorThresholdCheck: 区域阈值检查任务 TaskTypeDeviceThresholdCheck: 设备阈值检查任务 TaskTypeFullCollection: 新增的全量采集任务 + TaskTypeNotificationRefresh: 通知刷新任务 TaskTypeReleaseFeedWeight: 下料口释放指定重量任务 TaskTypeWaiting: 等待任务 x-enum-descriptions: @@ -2215,6 +2094,7 @@ definitions: - 下料口释放指定重量任务 - 新增的全量采集任务 - 告警通知任务 + - 通知刷新任务 - 设备阈值检查任务 - 区域阈值检查任务 x-enum-varnames: @@ -2223,6 +2103,7 @@ definitions: - TaskTypeReleaseFeedWeight - TaskTypeFullCollection - TaskTypeAlarmNotification + - TaskTypeNotificationRefresh - TaskTypeDeviceThresholdCheck - TaskTypeAreaCollectorThresholdCheck models.ValueDescriptor: @@ -2248,6 +2129,7 @@ definitions: - PlanTypeFilterSystem zapcore.Level: enum: + - 7 - -1 - 0 - 1 @@ -2258,10 +2140,10 @@ definitions: - -1 - 5 - 6 - - 7 format: int32 type: integer x-enum-varnames: + - _numLevels - DebugLevel - InfoLevel - WarnLevel @@ -2272,7 +2154,6 @@ definitions: - _minLevel - _maxLevel - InvalidLevel - - _numLevels info: contact: email: divano@example.com @@ -2353,13 +2234,13 @@ paths: type: boolean - description: 按告警严重性等级过滤 enum: - - Debug - - Info - - Warn - - Error - - DPanic - - Panic - - Fatal + - debug + - info + - warn + - error + - dpanic + - panic + - fatal in: query name: level type: string @@ -2430,13 +2311,13 @@ paths: type: integer - description: 按告警等级过滤 enum: - - Debug - - Info - - Warn - - Error - - DPanic - - Panic - - Fatal + - debug + - info + - warn + - error + - dpanic + - panic + - fatal in: query name: level type: string @@ -2612,13 +2493,13 @@ paths: type: integer - description: 按告警等级过滤 enum: - - Debug - - Info - - Warn - - Error - - DPanic - - Panic - - Fatal + - debug + - info + - warn + - error + - dpanic + - panic + - fatal in: query name: level type: string @@ -2798,13 +2679,13 @@ paths: parameters: - description: 按告警严重性等级过滤 enum: - - Debug - - Info - - Warn - - Error - - DPanic - - Panic - - Fatal + - debug + - info + - warn + - error + - dpanic + - panic + - fatal in: query name: level type: string @@ -3333,51 +3214,6 @@ paths: summary: 获取设备命令日志列表 tags: - 数据监控 - /api/v1/monitor/feed-usage-records: - get: - description: 根据提供的过滤条件,分页获取饲料使用记录 - parameters: - - in: query - name: end_time - type: string - - in: query - name: feed_formula_id - type: integer - - in: query - name: operator_id - type: integer - - in: query - name: order_by - type: string - - in: query - name: page - type: integer - - in: query - name: page_size - type: integer - - in: query - name: pen_id - type: integer - - in: query - name: start_time - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - allOf: - - $ref: '#/definitions/controller.Response' - - properties: - data: - $ref: '#/definitions/dto.ListFeedUsageRecordResponse' - type: object - security: - - BearerAuth: [] - summary: 获取饲料使用记录列表 - tags: - - 数据监控 /api/v1/monitor/medication-logs: get: description: 根据提供的过滤条件,分页获取用药记录 @@ -3434,6 +3270,7 @@ paths: name: end_time type: string - enum: + - 7 - -1 - 0 - 1 @@ -3444,12 +3281,12 @@ paths: - -1 - 5 - 6 - - 7 format: int32 in: query name: level type: integer x-enum-varnames: + - _numLevels - DebugLevel - InfoLevel - WarnLevel @@ -3460,7 +3297,6 @@ paths: - _minLevel - _maxLevel - InvalidLevel - - _numLevels - enum: - 邮件 - 企业微信 @@ -3846,93 +3682,6 @@ paths: summary: 获取计划执行日志列表 tags: - 数据监控 - /api/v1/monitor/raw-material-purchases: - get: - description: 根据提供的过滤条件,分页获取原料采购记录 - parameters: - - in: query - name: end_time - type: string - - in: query - name: order_by - type: string - - in: query - name: page - type: integer - - in: query - name: page_size - type: integer - - in: query - name: raw_material_id - type: integer - - in: query - name: start_time - type: string - - in: query - name: supplier - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - allOf: - - $ref: '#/definitions/controller.Response' - - properties: - data: - $ref: '#/definitions/dto.ListRawMaterialPurchaseResponse' - type: object - security: - - BearerAuth: [] - summary: 获取原料采购记录列表 - tags: - - 数据监控 - /api/v1/monitor/raw-material-stock-logs: - get: - description: 根据提供的过滤条件,分页获取原料库存日志 - parameters: - - in: query - name: end_time - type: string - - in: query - name: order_by - type: string - - in: query - name: page - type: integer - - in: query - name: page_size - type: integer - - in: query - name: raw_material_id - type: integer - - in: query - name: source_id - type: integer - - in: query - name: source_type - type: string - - in: query - name: start_time - type: string - produces: - - application/json - responses: - "200": - description: OK - schema: - allOf: - - $ref: '#/definitions/controller.Response' - - properties: - data: - $ref: '#/definitions/dto.ListRawMaterialStockLogResponse' - type: object - security: - - BearerAuth: [] - summary: 获取原料库存日志列表 - tags: - - 数据监控 /api/v1/monitor/sensor-data: get: description: 根据提供的过滤条件,分页获取传感器数据 diff --git a/internal/app/api/router.go b/internal/app/api/router.go index 500f495..6560d5b 100644 --- a/internal/app/api/router.go +++ b/internal/app/api/router.go @@ -173,9 +173,6 @@ func (a *API) setupRoutes() { monitorGroup.GET("/task-execution-logs", a.monitorController.ListTaskExecutionLogs) monitorGroup.GET("/pending-collections", a.monitorController.ListPendingCollections) monitorGroup.GET("/user-action-logs", a.monitorController.ListUserActionLogs) - monitorGroup.GET("/raw-material-purchases", a.monitorController.ListRawMaterialPurchases) - monitorGroup.GET("raw-material-stock-logs", a.monitorController.ListRawMaterialStockLogs) - monitorGroup.GET("/feed-usage-records", a.monitorController.ListFeedUsageRecords) monitorGroup.GET("/medication-logs", a.monitorController.ListMedicationLogs) monitorGroup.GET("/pig-batch-logs", a.monitorController.ListPigBatchLogs) monitorGroup.GET("/weighing-batches", a.monitorController.ListWeighingBatches) diff --git a/internal/app/controller/monitor/monitor_controller.go b/internal/app/controller/monitor/monitor_controller.go index ae787ad..52e1816 100644 --- a/internal/app/controller/monitor/monitor_controller.go +++ b/internal/app/controller/monitor/monitor_controller.go @@ -231,108 +231,6 @@ func (c *Controller) ListUserActionLogs(ctx echo.Context) error { return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取用户操作日志成功", resp, actionType, "获取用户操作日志成功", req) } -// ListRawMaterialPurchases godoc -// @Summary 获取原料采购记录列表 -// @Description 根据提供的过滤条件,分页获取原料采购记录 -// @Tags 数据监控 -// @Security BearerAuth -// @Produce json -// @Param query query dto.ListRawMaterialPurchaseRequest true "查询参数" -// @Success 200 {object} controller.Response{data=dto.ListRawMaterialPurchaseResponse} -// @Router /api/v1/monitor/raw-material-purchases [get] -func (c *Controller) ListRawMaterialPurchases(ctx echo.Context) error { - reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "ListRawMaterialPurchases") - const actionType = "获取原料采购记录列表" - - var req dto.ListRawMaterialPurchaseRequest - 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.monitorService.ListRawMaterialPurchases(reqCtx, &req) - if err != nil { - if errors.Is(err, repository.ErrInvalidPagination) { - logger.Warnf("%s: 无效的分页参数: %v", actionType, err) - return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的分页参数: "+err.Error(), actionType, "无效分页参数", req) - } - - logger.Errorf("%s: 服务层查询失败: %v", actionType, err) - return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取原料采购记录失败: "+err.Error(), actionType, "服务层查询失败", req) - } - - logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(resp.List), resp.Pagination.Total) - return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取原料采购记录成功", resp, actionType, "获取原料采购记录成功", req) -} - -// ListRawMaterialStockLogs godoc -// @Summary 获取原料库存日志列表 -// @Description 根据提供的过滤条件,分页获取原料库存日志 -// @Tags 数据监控 -// @Security BearerAuth -// @Produce json -// @Param query query dto.ListRawMaterialStockLogRequest true "查询参数" -// @Success 200 {object} controller.Response{data=dto.ListRawMaterialStockLogResponse} -// @Router /api/v1/monitor/raw-material-stock-logs [get] -func (c *Controller) ListRawMaterialStockLogs(ctx echo.Context) error { - reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "ListRawMaterialStockLogs") - const actionType = "获取原料库存日志列表" - - var req dto.ListRawMaterialStockLogRequest - 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.monitorService.ListRawMaterialStockLogs(reqCtx, &req) - if err != nil { - if errors.Is(err, repository.ErrInvalidPagination) { - logger.Warnf("%s: 无效的分页参数: %v", actionType, err) - return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的分页参数: "+err.Error(), actionType, "无效分页参数", req) - } - - logger.Errorf("%s: 服务层查询失败: %v", actionType, err) - return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取原料库存日志失败: "+err.Error(), actionType, "服务层查询失败", req) - } - - logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(resp.List), resp.Pagination.Total) - return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取原料库存日志成功", resp, actionType, "获取原料库存日志成功", req) -} - -// ListFeedUsageRecords godoc -// @Summary 获取饲料使用记录列表 -// @Description 根据提供的过滤条件,分页获取饲料使用记录 -// @Tags 数据监控 -// @Security BearerAuth -// @Produce json -// @Param query query dto.ListFeedUsageRecordRequest true "查询参数" -// @Success 200 {object} controller.Response{data=dto.ListFeedUsageRecordResponse} -// @Router /api/v1/monitor/feed-usage-records [get] -func (c *Controller) ListFeedUsageRecords(ctx echo.Context) error { - reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "ListFeedUsageRecords") - const actionType = "获取饲料使用记录列表" - - var req dto.ListFeedUsageRecordRequest - 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.monitorService.ListFeedUsageRecords(reqCtx, &req) - if err != nil { - if errors.Is(err, repository.ErrInvalidPagination) { - logger.Warnf("%s: 无效的分页参数: %v", actionType, err) - return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的分页参数: "+err.Error(), actionType, "无效分页参数", req) - } - - logger.Errorf("%s: 服务层查询失败: %v", actionType, err) - return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取饲料使用记录失败: "+err.Error(), actionType, "服务层查询失败", req) - } - - logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(resp.List), resp.Pagination.Total) - return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取饲料使用记录成功", resp, actionType, "获取饲料使用记录成功", req) -} - // ListMedicationLogs godoc // @Summary 获取用药记录列表 // @Description 根据提供的过滤条件,分页获取用药记录 diff --git a/internal/app/dto/monitor_converter.go b/internal/app/dto/monitor_converter.go index 657a4d4..a8d2fd0 100644 --- a/internal/app/dto/monitor_converter.go +++ b/internal/app/dto/monitor_converter.go @@ -170,94 +170,6 @@ func NewListUserActionLogResponse(data []models.UserActionLog, total int64, page } } -// NewListRawMaterialPurchaseResponse 从模型数据创建列表响应 DTO -func NewListRawMaterialPurchaseResponse(data []models.RawMaterialPurchase, total int64, page, pageSize int) *ListRawMaterialPurchaseResponse { - dtos := make([]RawMaterialPurchaseDTO, len(data)) - for i, item := range data { - dtos[i] = RawMaterialPurchaseDTO{ - ID: item.ID, - RawMaterialID: item.RawMaterialID, - RawMaterial: RawMaterialDTO{ - ID: item.RawMaterial.ID, - Name: item.RawMaterial.Name, - }, - Supplier: item.Supplier, - Amount: item.Amount, - UnitPrice: item.UnitPrice, - TotalPrice: item.TotalPrice, - PurchaseDate: item.PurchaseDate, - CreatedAt: item.CreatedAt, - } - } - - return &ListRawMaterialPurchaseResponse{ - List: dtos, - Pagination: PaginationDTO{ - Total: total, - Page: page, - PageSize: pageSize, - }, - } -} - -// NewListRawMaterialStockLogResponse 从模型数据创建列表响应 DTO -func NewListRawMaterialStockLogResponse(data []models.RawMaterialStockLog, total int64, page, pageSize int) *ListRawMaterialStockLogResponse { - dtos := make([]RawMaterialStockLogDTO, len(data)) - for i, item := range data { - dtos[i] = RawMaterialStockLogDTO{ - ID: item.ID, - RawMaterialID: item.RawMaterialID, - ChangeAmount: item.ChangeAmount, - SourceType: item.SourceType, - SourceID: item.SourceID, - HappenedAt: item.HappenedAt, - Remarks: item.Remarks, - } - } - - return &ListRawMaterialStockLogResponse{ - List: dtos, - Pagination: PaginationDTO{ - Total: total, - Page: page, - PageSize: pageSize, - }, - } -} - -// NewListFeedUsageRecordResponse 从模型数据创建列表响应 DTO -func NewListFeedUsageRecordResponse(data []models.FeedUsageRecord, total int64, page, pageSize int) *ListFeedUsageRecordResponse { - dtos := make([]FeedUsageRecordDTO, len(data)) - for i, item := range data { - dtos[i] = FeedUsageRecordDTO{ - ID: item.ID, - PenID: item.PenID, - Pen: PenDTO{ - ID: item.Pen.ID, - Name: item.Pen.PenNumber, - }, - FeedFormulaID: item.FeedFormulaID, - FeedFormula: FeedFormulaDTO{ - ID: item.FeedFormula.ID, - Name: item.FeedFormula.Name, - }, - Amount: item.Amount, - RecordedAt: item.RecordedAt, - OperatorID: item.OperatorID, - Remarks: item.Remarks, - } - } - - return &ListFeedUsageRecordResponse{ - List: dtos, - Pagination: PaginationDTO{ - Total: total, - Page: page, - PageSize: pageSize, - }, - } -} - // NewListMedicationLogResponse 从模型数据创建列表响应 DTO func NewListMedicationLogResponse(data []models.MedicationLog, total int64, page, pageSize int) *ListMedicationLogResponse { dtos := make([]MedicationLogDTO, len(data)) diff --git a/internal/app/dto/monitor_dto.go b/internal/app/dto/monitor_dto.go index 6fcf4b6..a711c2b 100644 --- a/internal/app/dto/monitor_dto.go +++ b/internal/app/dto/monitor_dto.go @@ -202,120 +202,6 @@ type ListUserActionLogResponse struct { Pagination PaginationDTO `json:"pagination"` } -// --- RawMaterialPurchase --- - -// ListRawMaterialPurchaseRequest 定义了获取原料采购列表的请求参数 -type ListRawMaterialPurchaseRequest 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"` - Supplier *string `json:"supplier" query:"supplier"` - StartTime *time.Time `json:"start_time" query:"start_time"` - EndTime *time.Time `json:"end_time" query:"end_time"` - OrderBy string `json:"order_by" query:"order_by"` -} - -// RawMaterialDTO 是用于API响应的简化版原料结构 -type RawMaterialDTO struct { - ID uint32 `json:"id"` - Name string `json:"name"` -} - -// RawMaterialPurchaseDTO 是用于API响应的原料采购结构 -type RawMaterialPurchaseDTO struct { - ID uint32 `json:"id"` - RawMaterialID uint32 `json:"raw_material_id"` - RawMaterial RawMaterialDTO `json:"raw_material"` - Supplier string `json:"supplier"` - Amount float32 `json:"amount"` - UnitPrice float32 `json:"unit_price"` - TotalPrice float32 `json:"total_price"` - PurchaseDate time.Time `json:"purchase_date"` - CreatedAt time.Time `json:"created_at"` -} - -// ListRawMaterialPurchaseResponse 是获取原料采购列表的响应结构 -type ListRawMaterialPurchaseResponse struct { - List []RawMaterialPurchaseDTO `json:"list"` - Pagination PaginationDTO `json:"pagination"` -} - -// --- RawMaterialStockLog --- - -// ListRawMaterialStockLogRequest 定义了获取原料库存日志列表的请求参数 -type ListRawMaterialStockLogRequest 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"` - SourceType *string `json:"source_type" query:"source_type"` - SourceID *uint32 `json:"source_id" query:"source_id"` - StartTime *time.Time `json:"start_time" query:"start_time"` - EndTime *time.Time `json:"end_time" query:"end_time"` - OrderBy string `json:"order_by" query:"order_by"` -} - -// RawMaterialStockLogDTO 是用于API响应的原料库存日志结构 -type RawMaterialStockLogDTO struct { - ID uint32 `json:"id"` - RawMaterialID uint32 `json:"raw_material_id"` - ChangeAmount float32 `json:"change_amount"` - SourceType models.StockLogSourceType `json:"source_type"` - SourceID uint32 `json:"source_id"` - HappenedAt time.Time `json:"happened_at"` - Remarks string `json:"remarks"` -} - -// ListRawMaterialStockLogResponse 是获取原料库存日志列表的响应结构 -type ListRawMaterialStockLogResponse struct { - List []RawMaterialStockLogDTO `json:"list"` - Pagination PaginationDTO `json:"pagination"` -} - -// --- FeedUsageRecord --- - -// ListFeedUsageRecordRequest 定义了获取饲料使用记录列表的请求参数 -type ListFeedUsageRecordRequest struct { - Page int `json:"page" query:"page"` - PageSize int `json:"page_size" query:"page_size"` - PenID *uint32 `json:"pen_id" query:"pen_id"` - FeedFormulaID *uint32 `json:"feed_formula_id" query:"feed_formula_id"` - OperatorID *uint32 `json:"operator_id" query:"operator_id"` - StartTime *time.Time `json:"start_time" query:"start_time"` - EndTime *time.Time `json:"end_time" query:"end_time"` - OrderBy string `json:"order_by" query:"order_by"` -} - -// PenDTO 是用于API响应的简化版猪栏结构 -type PenDTO struct { - ID uint32 `json:"id"` - Name string `json:"name"` -} - -// FeedFormulaDTO 是用于API响应的简化版饲料配方结构 -type FeedFormulaDTO struct { - ID uint32 `json:"id"` - Name string `json:"name"` -} - -// FeedUsageRecordDTO 是用于API响应的饲料使用记录结构 -type FeedUsageRecordDTO struct { - ID uint32 `json:"id"` - PenID uint32 `json:"pen_id"` - Pen PenDTO `json:"pen"` - FeedFormulaID uint32 `json:"feed_formula_id"` - FeedFormula FeedFormulaDTO `json:"feed_formula"` - Amount float32 `json:"amount"` - RecordedAt time.Time `json:"recorded_at"` - OperatorID uint32 `json:"operator_id"` - Remarks string `json:"remarks"` -} - -// ListFeedUsageRecordResponse 是获取饲料使用记录列表的响应结构 -type ListFeedUsageRecordResponse struct { - List []FeedUsageRecordDTO `json:"list"` - Pagination PaginationDTO `json:"pagination"` -} - // --- MedicationLog --- // ListMedicationLogRequest 定义了获取用药记录列表的请求参数 diff --git a/internal/app/service/monitor_service.go b/internal/app/service/monitor_service.go index 64be63e..eeee8c0 100644 --- a/internal/app/service/monitor_service.go +++ b/internal/app/service/monitor_service.go @@ -17,9 +17,6 @@ type MonitorService interface { ListTaskExecutionLogs(ctx context.Context, req *dto.ListTaskExecutionLogRequest) (*dto.ListTaskExecutionLogResponse, error) ListPendingCollections(ctx context.Context, req *dto.ListPendingCollectionRequest) (*dto.ListPendingCollectionResponse, error) ListUserActionLogs(ctx context.Context, req *dto.ListUserActionLogRequest) (*dto.ListUserActionLogResponse, error) - ListRawMaterialPurchases(ctx context.Context, req *dto.ListRawMaterialPurchaseRequest) (*dto.ListRawMaterialPurchaseResponse, error) - ListRawMaterialStockLogs(ctx context.Context, req *dto.ListRawMaterialStockLogRequest) (*dto.ListRawMaterialStockLogResponse, error) - ListFeedUsageRecords(ctx context.Context, req *dto.ListFeedUsageRecordRequest) (*dto.ListFeedUsageRecordResponse, error) ListMedicationLogs(ctx context.Context, req *dto.ListMedicationLogRequest) (*dto.ListMedicationLogResponse, error) ListPigBatchLogs(ctx context.Context, req *dto.ListPigBatchLogRequest) (*dto.ListPigBatchLogResponse, error) ListWeighingBatches(ctx context.Context, req *dto.ListWeighingBatchRequest) (*dto.ListWeighingBatchResponse, error) @@ -40,7 +37,6 @@ type monitorService struct { planRepository repository.PlanRepository pendingCollectionRepo repository.PendingCollectionRepository userActionLogRepo repository.UserActionLogRepository - rawMaterialRepo repository.RawMaterialRepository medicationRepo repository.MedicationLogRepository pigBatchRepo repository.PigBatchRepository pigBatchLogRepo repository.PigBatchLogRepository @@ -59,7 +55,6 @@ func NewMonitorService( planRepository repository.PlanRepository, pendingCollectionRepo repository.PendingCollectionRepository, userActionLogRepo repository.UserActionLogRepository, - rawMaterialRepo repository.RawMaterialRepository, medicationRepo repository.MedicationLogRepository, pigBatchRepo repository.PigBatchRepository, pigBatchLogRepo repository.PigBatchLogRepository, @@ -76,7 +71,6 @@ func NewMonitorService( planRepository: planRepository, pendingCollectionRepo: pendingCollectionRepo, userActionLogRepo: userActionLogRepo, - rawMaterialRepo: rawMaterialRepo, medicationRepo: medicationRepo, pigBatchRepo: pigBatchRepo, pigBatchLogRepo: pigBatchLogRepo, @@ -236,68 +230,6 @@ func (s *monitorService) ListUserActionLogs(ctx context.Context, req *dto.ListUs return dto.NewListUserActionLogResponse(data, total, req.Page, req.PageSize), nil } -// ListRawMaterialPurchases 负责处理查询原料采购记录列表的业务逻辑 -func (s *monitorService) ListRawMaterialPurchases(ctx context.Context, req *dto.ListRawMaterialPurchaseRequest) (*dto.ListRawMaterialPurchaseResponse, error) { - serviceCtx := logs.AddFuncName(ctx, s.ctx, "ListRawMaterialPurchases") - opts := repository.RawMaterialPurchaseListOptions{ - RawMaterialID: req.RawMaterialID, - Supplier: req.Supplier, - OrderBy: req.OrderBy, - StartTime: req.StartTime, - EndTime: req.EndTime, - } - - data, total, err := s.rawMaterialRepo.ListRawMaterialPurchases(serviceCtx, opts, req.Page, req.PageSize) - if err != nil { - return nil, err - } - - return dto.NewListRawMaterialPurchaseResponse(data, total, req.Page, req.PageSize), nil -} - -// ListRawMaterialStockLogs 负责处理查询原料库存日志列表的业务逻辑 -func (s *monitorService) ListRawMaterialStockLogs(ctx context.Context, req *dto.ListRawMaterialStockLogRequest) (*dto.ListRawMaterialStockLogResponse, error) { - serviceCtx := logs.AddFuncName(ctx, s.ctx, "ListRawMaterialStockLogs") - opts := repository.RawMaterialStockLogListOptions{ - RawMaterialID: req.RawMaterialID, - SourceID: req.SourceID, - OrderBy: req.OrderBy, - StartTime: req.StartTime, - EndTime: req.EndTime, - } - if req.SourceType != nil { - sourceType := models.StockLogSourceType(*req.SourceType) - opts.SourceType = &sourceType - } - - data, total, err := s.rawMaterialRepo.ListRawMaterialStockLogs(serviceCtx, opts, req.Page, req.PageSize) - if err != nil { - return nil, err - } - - return dto.NewListRawMaterialStockLogResponse(data, total, req.Page, req.PageSize), nil -} - -// ListFeedUsageRecords 负责处理查询饲料使用记录列表的业务逻辑 -func (s *monitorService) ListFeedUsageRecords(ctx context.Context, req *dto.ListFeedUsageRecordRequest) (*dto.ListFeedUsageRecordResponse, error) { - serviceCtx := logs.AddFuncName(ctx, s.ctx, "ListFeedUsageRecords") - opts := repository.FeedUsageRecordListOptions{ - PenID: req.PenID, - FeedFormulaID: req.FeedFormulaID, - OperatorID: req.OperatorID, - OrderBy: req.OrderBy, - StartTime: req.StartTime, - EndTime: req.EndTime, - } - - data, total, err := s.rawMaterialRepo.ListFeedUsageRecords(serviceCtx, opts, req.Page, req.PageSize) - if err != nil { - return nil, err - } - - return dto.NewListFeedUsageRecordResponse(data, total, req.Page, req.PageSize), nil -} - // ListMedicationLogs 负责处理查询用药记录列表的业务逻辑 func (s *monitorService) ListMedicationLogs(ctx context.Context, req *dto.ListMedicationLogRequest) (*dto.ListMedicationLogResponse, error) { serviceCtx := logs.AddFuncName(ctx, s.ctx, "ListMedicationLogs") diff --git a/internal/core/component_initializers.go b/internal/core/component_initializers.go index 73a9c05..d9c6659 100644 --- a/internal/core/component_initializers.go +++ b/internal/core/component_initializers.go @@ -79,7 +79,6 @@ type Repositories struct { pigTradeRepo repository.PigTradeRepository pigSickPigLogRepo repository.PigSickLogRepository medicationLogRepo repository.MedicationLogRepository - rawMaterialRepo repository.RawMaterialRepository notificationRepo repository.NotificationRepository alarmRepo repository.AlarmRepository unitOfWork repository.UnitOfWork @@ -108,7 +107,6 @@ func initRepositories(ctx context.Context, db *gorm.DB) *Repositories { pigTradeRepo: repository.NewGormPigTradeRepository(logs.AddCompName(baseCtx, "PigTradeRepo"), db), pigSickPigLogRepo: repository.NewGormPigSickLogRepository(logs.AddCompName(baseCtx, "PigSickPigLogRepo"), db), medicationLogRepo: repository.NewGormMedicationLogRepository(logs.AddCompName(baseCtx, "MedicationLogRepo"), db), - rawMaterialRepo: repository.NewGormRawMaterialRepository(logs.AddCompName(baseCtx, "RawMaterialRepo"), db), notificationRepo: repository.NewGormNotificationRepository(logs.AddCompName(baseCtx, "NotificationRepo"), db), alarmRepo: repository.NewGormAlarmRepository(logs.AddCompName(baseCtx, "AlarmRepo"), db), unitOfWork: repository.NewGormUnitOfWork(logs.AddCompName(baseCtx, "UnitOfWork"), db), @@ -247,7 +245,6 @@ func initAppServices(ctx context.Context, infra *Infrastructure, domainServices infra.repos.planRepo, infra.repos.pendingCollectionRepo, infra.repos.userActionLogRepo, - infra.repos.rawMaterialRepo, infra.repos.medicationLogRepo, infra.repos.pigBatchRepo, infra.repos.pigBatchLogRepo, diff --git a/internal/infra/database/postgres.go b/internal/infra/database/postgres.go index d86d578..3ce4a6b 100644 --- a/internal/infra/database/postgres.go +++ b/internal/infra/database/postgres.go @@ -167,9 +167,6 @@ func (ps *PostgresStorage) creatingHyperTable(ctx context.Context) error { {models.TaskExecutionLog{}, "created_at"}, {models.PendingCollection{}, "created_at"}, {models.UserActionLog{}, "time"}, - {models.RawMaterialPurchase{}, "purchase_date"}, - {models.RawMaterialStockLog{}, "happened_at"}, - {models.FeedUsageRecord{}, "recorded_at"}, {models.MedicationLog{}, "happened_at"}, {models.PigBatchLog{}, "happened_at"}, {models.WeighingBatch{}, "weighing_time"}, @@ -180,6 +177,7 @@ func (ps *PostgresStorage) creatingHyperTable(ctx context.Context) error { {models.PigSale{}, "sale_date"}, {models.Notification{}, "alarm_timestamp"}, {models.HistoricalAlarm{}, "trigger_time"}, + {models.RawMaterialStockLog{}, "happened_at"}, } for _, table := range tablesToConvert { @@ -210,9 +208,6 @@ func (ps *PostgresStorage) applyCompressionPolicies(ctx context.Context) error { {models.TaskExecutionLog{}, "task_id"}, {models.PendingCollection{}, "device_id"}, {models.UserActionLog{}, "user_id"}, - {models.RawMaterialPurchase{}, "raw_material_id"}, - {models.RawMaterialStockLog{}, "raw_material_id"}, - {models.FeedUsageRecord{}, "pen_id"}, {models.MedicationLog{}, "pig_batch_id"}, {models.PigBatchLog{}, "pig_batch_id"}, {models.WeighingBatch{}, "pig_batch_id"}, @@ -223,6 +218,7 @@ func (ps *PostgresStorage) applyCompressionPolicies(ctx context.Context) error { {models.PigSale{}, "pig_batch_id"}, {models.Notification{}, "user_id"}, {models.HistoricalAlarm{}, "source_id"}, + {models.RawMaterialStockLog{}, "raw_material_id"}, } for _, policy := range policies { @@ -258,6 +254,15 @@ func (ps *PostgresStorage) creatingIndex(ctx context.Context) error { // 使用 IF NOT EXISTS 保证幂等性 // 如果索引已存在,此命令不会报错 + // 为 raw_material_nutrients 表创建部分唯一索引,以兼容软删除 + logger.Debug("正在为 raw_material_nutrients 表创建部分唯一索引") + partialIndexSQL := "CREATE UNIQUE INDEX IF NOT EXISTS idx_raw_material_nutrients_unique_when_not_deleted ON raw_material_nutrients (raw_material_id, nutrient_id) WHERE deleted_at IS NULL;" + if err := ps.db.WithContext(storageCtx).Exec(partialIndexSQL).Error; err != nil { + logger.Errorw("为 raw_material_nutrients 创建部分唯一索引失败", "error", err) + return fmt.Errorf("为 raw_material_nutrients 创建部分唯一索引失败: %w", err) + } + logger.Debug("成功为 raw_material_nutrients 创建部分唯一索引 (或已存在)") + // 为 sensor_data 表的 data 字段创建 GIN 索引 logger.Debug("正在为 sensor_data 表的 data 字段创建 GIN 索引") ginSensorDataIndexSQL := "CREATE INDEX IF NOT EXISTS idx_sensor_data_data_gin ON sensor_data USING GIN (data);" diff --git a/internal/infra/models/feed.go b/internal/infra/models/feed.go deleted file mode 100644 index 472ba2d..0000000 --- a/internal/infra/models/feed.go +++ /dev/null @@ -1,111 +0,0 @@ -package models - -import ( - "time" -) - -/* - 饲料和饲喂相关的模型 -*/ - -// RawMaterial 代表饲料的原料。 -// 建议:所有重量单位统一存储 (例如, 全部使用 'g'),便于计算和避免转换错误。 -type RawMaterial struct { - Model - Name string `gorm:"size:100;unique;not null;comment:原料名称"` - Description string `gorm:"size:255;comment:描述"` - Quantity float32 `gorm:"not null;comment:库存总量, 单位: g"` -} - -func (RawMaterial) TableName() string { - return "raw_materials" -} - -// RawMaterialPurchase 记录了原料的每一次采购。 -type RawMaterialPurchase struct { - Model - RawMaterialID uint32 `gorm:"not null;index;comment:关联的原料ID"` - RawMaterial RawMaterial `gorm:"foreignKey:RawMaterialID"` - Supplier string `gorm:"size:100;comment:供应商"` - Amount float32 `gorm:"not null;comment:采购数量, 单位: g"` - UnitPrice float32 `gorm:"comment:单价"` - TotalPrice float32 `gorm:"comment:总价"` - PurchaseDate time.Time `gorm:"primaryKey;comment:采购日期"` - CreatedAt time.Time -} - -func (RawMaterialPurchase) TableName() string { - return "raw_material_purchases" -} - -// StockLogSourceType 定义了库存日志来源的类型 -type StockLogSourceType string - -const ( - StockLogSourcePurchase StockLogSourceType = "采购入库" - StockLogSourceFeeding StockLogSourceType = "饲喂出库" - StockLogSourceDeteriorate StockLogSourceType = "变质出库" - StockLogSourceSale StockLogSourceType = "售卖出库" - StockLogSourceMiscellaneous StockLogSourceType = "杂用领取" - StockLogSourceManual StockLogSourceType = "手动盘点" -) - -// RawMaterialStockLog 记录了原料库存的所有变动,提供了完整的追溯链。 -type RawMaterialStockLog struct { - Model - RawMaterialID uint32 `gorm:"not null;index;comment:关联的原料ID"` - ChangeAmount float32 `gorm:"not null;comment:变动数量, 正数为入库, 负数为出库"` - SourceType StockLogSourceType `gorm:"size:50;not null;index;comment:库存变动来源类型"` - SourceID uint32 `gorm:"not null;index;comment:来源记录的ID (如 RawMaterialPurchase.ID 或 FeedUsageRecord.ID)"` - HappenedAt time.Time `gorm:"primaryKey;comment:业务发生时间"` - Remarks string `gorm:"comment:备注, 如主动领取的理由等"` -} - -func (RawMaterialStockLog) TableName() string { - return "raw_material_stock_logs" -} - -// FeedFormula 代表饲料配方。 -// 对于没有配方的外购饲料,可以将其视为一种特殊的 RawMaterial, 并为其创建一个仅包含它自己的 FeedFormula。 -type FeedFormula struct { - Model - Name string `gorm:"size:100;unique;not null;comment:配方名称"` - Description string `gorm:"size:255;comment:描述"` - Components []FeedFormulaComponent `gorm:"foreignKey:FeedFormulaID"` -} - -func (FeedFormula) TableName() string { - return "feed_formulas" -} - -// FeedFormulaComponent 代表配方中的一种原料及其占比。 -type FeedFormulaComponent struct { - Model - FeedFormulaID uint32 `gorm:"not null;index;comment:外键到 FeedFormula"` - RawMaterialID uint32 `gorm:"not null;index;comment:外键到 RawMaterial"` - RawMaterial RawMaterial `gorm:"foreignKey:RawMaterialID"` - Percentage float32 `gorm:"not null;comment:该原料在配方中的百分比 (0-1.0)"` -} - -func (FeedFormulaComponent) TableName() string { - return "feed_formula_components" -} - -// FeedUsageRecord 代表饲料使用记录。 -// 应用层逻辑:当一条使用记录被创建时,应根据其使用的 FeedFormula, -// 计算出每种 RawMaterial 的消耗量,并在 RawMaterialStockLog 中创建对应的出库记录。 -type FeedUsageRecord struct { - Model - PenID uint32 `gorm:"not null;index;comment:关联的猪栏ID"` - Pen Pen `gorm:"foreignKey:PenID"` - FeedFormulaID uint32 `gorm:"not null;index;comment:使用的饲料配方ID"` - FeedFormula FeedFormula `gorm:"foreignKey:FeedFormulaID"` - Amount float32 `gorm:"not null;comment:使用数量, 单位: g"` - RecordedAt time.Time `gorm:"primaryKey;comment:记录时间"` - OperatorID uint32 `gorm:"not null;comment:操作员"` - Remarks string `gorm:"comment:备注, 如 '例行喂料, 弱猪补料' 等"` -} - -func (FeedUsageRecord) TableName() string { - return "feed_usage_records" -} diff --git a/internal/infra/models/models.go b/internal/infra/models/models.go index 152556b..4232d42 100644 --- a/internal/infra/models/models.go +++ b/internal/infra/models/models.go @@ -63,11 +63,9 @@ func GetAllModels() []interface{} { // Feed Models &RawMaterial{}, - &RawMaterialPurchase{}, + &Nutrient{}, + &RawMaterialNutrient{}, &RawMaterialStockLog{}, - &FeedFormula{}, - &FeedFormulaComponent{}, - &FeedUsageRecord{}, // Medication Models &Medication{}, diff --git a/internal/infra/models/raw_material.go b/internal/infra/models/raw_material.go new file mode 100644 index 0000000..d138231 --- /dev/null +++ b/internal/infra/models/raw_material.go @@ -0,0 +1,92 @@ +package models + +import ( + "time" +) + +// StockLogSourceType 定义了库存日志来源的类型 +type StockLogSourceType string + +const ( + StockLogSourcePurchase StockLogSourceType = "采购入库" + StockLogSourceFeeding StockLogSourceType = "饲喂出库" + StockLogSourceDeteriorate StockLogSourceType = "变质出库" + StockLogSourceSale StockLogSourceType = "售卖出库" + StockLogSourceMiscellaneous StockLogSourceType = "杂用领取" + StockLogSourceManual StockLogSourceType = "手动盘点" + StockLogSourceFermentStart StockLogSourceType = "发酵出库" // 原料投入发酵,从库存中扣除 + StockLogSourceFermentEnd StockLogSourceType = "发酵入库" // 发酵料产出,作为新原料计入库存 +) + +// NutrientType 定义了营养素的分类,用于配方优化和成本控制。 +type NutrientType string + +const ( + PositiveNutrient NutrientType = "正面营养" // 希望在配方中最大化的营养素,如蛋白质、能量 + NegativeNutrient NutrientType = "负面营养" // 需要控制上限的营养素,如粗纤维、霉菌毒素 +) + +// RawMaterial 代表一种原料的静态定义,是系统中的原料字典。 +type RawMaterial struct { + Model + Name string `gorm:"size:100;unique;not null;comment:原料名称"` + Description string `gorm:"size:255;comment:描述"` + // Quantity 是当前库存的快照值,用于提供高性能的库存查询。 + // 注意:此字段的值必须在数据库事务中与 RawMaterialStockLog 同步更新,以保证数据一致性。 + Quantity float32 `gorm:"not null;default:0;comment:当前库存快照, 单位: g"` +} + +func (RawMaterial) TableName() string { + return "raw_materials" +} + +// Nutrient 代表一种营养素的静态定义,是系统中的营养素字典。 +// 注意:本系统强制统一营养单位,不再单独设置Unit字段。 +// 约定:宏量营养素(粗蛋白等)单位为百分比(%),微量元素(氨基酸等)单位为毫克/千克(mg/kg)。 +type Nutrient struct { + Model + Name string `gorm:"size:100;unique;not null;comment:营养素名称"` + Type NutrientType `gorm:"size:50;not null;comment:营养素类型 (正面营养/负面营养)"` + Description string `gorm:"size:255;comment:描述"` +} + +func (Nutrient) TableName() string { + return "nutrients" +} + +// RawMaterialNutrient 存储了特定原料的特定营养素的含量值。 +// 这是连接原料和营养素的“营养价值表”。 +// 注意:其唯一性由 postgres.go 中的部分唯一索引保证,以兼容软删除。 +type RawMaterialNutrient struct { + Model + RawMaterialID uint32 `gorm:"not null;comment:关联的原料ID"` + RawMaterial RawMaterial `gorm:"foreignKey:RawMaterialID"` + NutrientID uint32 `gorm:"not null;comment:关联的营养素ID"` + Nutrient Nutrient `gorm:"foreignKey:NutrientID"` + // Value 存储营养价值含量。单位遵循 Nutrient 表中定义的系统级约定。 + Value float32 `gorm:"not null;comment:营养价值含量"` +} + +func (RawMaterialNutrient) TableName() string { + return "raw_material_nutrients" +} + +// RawMaterialStockLog 记录了原料库存的所有变动,提供了完整的追溯链。 +// 它是保证数据一致性和可审计性的核心。 +type RawMaterialStockLog struct { + Model + RawMaterialID uint32 `gorm:"not null;index;comment:关联的原料ID"` + RawMaterial RawMaterial `gorm:"foreignKey:RawMaterialID"` + ChangeAmount float32 `gorm:"not null;comment:变动数量, 正数为入库, 负数为出库, 单位: g"` + // SourceType 告知 SourceID 关联的是哪种类型的业务单据。 + SourceType StockLogSourceType `gorm:"size:50;not null;index;comment:库存变动来源类型"` + // SourceID 是一个多态外键,关联到触发此次变动的业务单据ID (如采购单ID)。 + // 对于无单据的业务(如手动盘点),此字段可为NULL。 + SourceID *uint32 `gorm:"index;comment:来源业务单据的ID"` + HappenedAt time.Time `gorm:"primaryKey;comment:业务发生时间"` + Remarks string `gorm:"comment:备注"` +} + +func (RawMaterialStockLog) TableName() string { + return "raw_material_stock_logs" +} diff --git a/internal/infra/repository/raw_material_repository.go b/internal/infra/repository/raw_material_repository.go deleted file mode 100644 index 2795167..0000000 --- a/internal/infra/repository/raw_material_repository.go +++ /dev/null @@ -1,187 +0,0 @@ -package repository - -import ( - "context" - "time" - - "git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" - "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" - - "gorm.io/gorm" -) - -// RawMaterialPurchaseListOptions 定义了查询原料采购记录时的可选参数 -type RawMaterialPurchaseListOptions struct { - RawMaterialID *uint32 - Supplier *string - StartTime *time.Time // 基于 purchase_date 字段 - EndTime *time.Time // 基于 purchase_date 字段 - OrderBy string // 例如 "purchase_date asc" -} - -// RawMaterialStockLogListOptions 定义了查询原料库存日志时的可选参数 -type RawMaterialStockLogListOptions struct { - RawMaterialID *uint32 - SourceType *models.StockLogSourceType - SourceID *uint32 - StartTime *time.Time // 基于 happened_at 字段 - EndTime *time.Time // 基于 happened_at 字段 - OrderBy string // 例如 "happened_at asc" -} - -// FeedUsageRecordListOptions 定义了查询饲料使用记录时的可选参数 -type FeedUsageRecordListOptions struct { - PenID *uint32 - FeedFormulaID *uint32 - OperatorID *uint32 - StartTime *time.Time // 基于 recorded_at 字段 - EndTime *time.Time // 基于 recorded_at 字段 - OrderBy string // 例如 "recorded_at asc" -} - -// RawMaterialRepository 定义了与原料相关的数据库操作接口 -type RawMaterialRepository interface { - ListRawMaterialPurchases(ctx context.Context, opts RawMaterialPurchaseListOptions, page, pageSize int) ([]models.RawMaterialPurchase, int64, error) - ListRawMaterialStockLogs(ctx context.Context, opts RawMaterialStockLogListOptions, page, pageSize int) ([]models.RawMaterialStockLog, int64, error) - ListFeedUsageRecords(ctx context.Context, opts FeedUsageRecordListOptions, page, pageSize int) ([]models.FeedUsageRecord, int64, error) -} - -// gormRawMaterialRepository 是 RawMaterialRepository 的 GORM 实现 -type gormRawMaterialRepository struct { - ctx context.Context - db *gorm.DB -} - -// NewGormRawMaterialRepository 创建一个新的 RawMaterialRepository GORM 实现实例 -func NewGormRawMaterialRepository(ctx context.Context, db *gorm.DB) RawMaterialRepository { - return &gormRawMaterialRepository{ctx: ctx, db: db} -} - -// ListRawMaterialPurchases 实现了分页和过滤查询原料采购记录的功能 -func (r *gormRawMaterialRepository) ListRawMaterialPurchases(ctx context.Context, opts RawMaterialPurchaseListOptions, page, pageSize int) ([]models.RawMaterialPurchase, int64, error) { - repoCtx := logs.AddFuncName(ctx, r.ctx, "ListRawMaterialPurchases") - if page <= 0 || pageSize <= 0 { - return nil, 0, ErrInvalidPagination - } - - var results []models.RawMaterialPurchase - var total int64 - - query := r.db.WithContext(repoCtx).Model(&models.RawMaterialPurchase{}) - - if opts.RawMaterialID != nil { - query = query.Where("raw_material_id = ?", *opts.RawMaterialID) - } - if opts.Supplier != nil { - query = query.Where("supplier LIKE ?", "%"+*opts.Supplier+"%") - } - if opts.StartTime != nil { - query = query.Where("purchase_date >= ?", *opts.StartTime) - } - if opts.EndTime != nil { - query = query.Where("purchase_date <= ?", *opts.EndTime) - } - - if err := query.Count(&total).Error; err != nil { - return nil, 0, err - } - - orderBy := "purchase_date DESC" - if opts.OrderBy != "" { - orderBy = opts.OrderBy - } - query = query.Order(orderBy).Preload("RawMaterial") - - offset := (page - 1) * pageSize - err := query.Limit(pageSize).Offset(offset).Find(&results).Error - - return results, total, err -} - -// ListRawMaterialStockLogs 实现了分页和过滤查询原料库存日志的功能 -func (r *gormRawMaterialRepository) ListRawMaterialStockLogs(ctx context.Context, opts RawMaterialStockLogListOptions, page, pageSize int) ([]models.RawMaterialStockLog, int64, error) { - repoCtx := logs.AddFuncName(ctx, r.ctx, "ListRawMaterialStockLogs") - if page <= 0 || pageSize <= 0 { - return nil, 0, ErrInvalidPagination - } - - var results []models.RawMaterialStockLog - var total int64 - - query := r.db.WithContext(repoCtx).Model(&models.RawMaterialStockLog{}) - - if opts.RawMaterialID != nil { - query = query.Where("raw_material_id = ?", *opts.RawMaterialID) - } - if opts.SourceType != nil { - query = query.Where("source_type = ?", *opts.SourceType) - } - if opts.SourceID != nil { - query = query.Where("source_id = ?", *opts.SourceID) - } - if opts.StartTime != nil { - query = query.Where("happened_at >= ?", *opts.StartTime) - } - if opts.EndTime != nil { - query = query.Where("happened_at <= ?", *opts.EndTime) - } - - if err := query.Count(&total).Error; err != nil { - return nil, 0, err - } - - orderBy := "happened_at DESC" - if opts.OrderBy != "" { - orderBy = opts.OrderBy - } - query = query.Order(orderBy) - - offset := (page - 1) * pageSize - err := query.Limit(pageSize).Offset(offset).Find(&results).Error - - return results, total, err -} - -// ListFeedUsageRecords 实现了分页和过滤查询饲料使用记录的功能 -func (r *gormRawMaterialRepository) ListFeedUsageRecords(ctx context.Context, opts FeedUsageRecordListOptions, page, pageSize int) ([]models.FeedUsageRecord, int64, error) { - repoCtx := logs.AddFuncName(ctx, r.ctx, "ListFeedUsageRecords") - if page <= 0 || pageSize <= 0 { - return nil, 0, ErrInvalidPagination - } - - var results []models.FeedUsageRecord - var total int64 - - query := r.db.WithContext(repoCtx).Model(&models.FeedUsageRecord{}) - - if opts.PenID != nil { - query = query.Where("pen_id = ?", *opts.PenID) - } - if opts.FeedFormulaID != nil { - query = query.Where("feed_formula_id = ?", *opts.FeedFormulaID) - } - if opts.OperatorID != nil { - query = query.Where("operator_id = ?", *opts.OperatorID) - } - if opts.StartTime != nil { - query = query.Where("recorded_at >= ?", *opts.StartTime) - } - if opts.EndTime != nil { - query = query.Where("recorded_at <= ?", *opts.EndTime) - } - - if err := query.Count(&total).Error; err != nil { - return nil, 0, err - } - - orderBy := "recorded_at DESC" - if opts.OrderBy != "" { - orderBy = opts.OrderBy - } - query = query.Order(orderBy).Preload("Pen").Preload("FeedFormula") - - offset := (page - 1) * pageSize - err := query.Limit(pageSize).Offset(offset).Find(&results).Error - - return results, total, err -}