Browse Source

Merge branch 'feature/stroage-manage' of xuyiping/kpt-tmr-group into develop

xuyiping 1 year ago
parent
commit
bc3ee5f50a
97 changed files with 14220 additions and 720 deletions
  1. 38 7
      .drone.yml
  2. 1 1
      .gitignore
  3. 24 0
      Dockerfile
  4. 9 0
      README.md
  5. 69 0
      backend/operation/enum.proto
  6. 51 0
      backend/operation/feed_formula.proto
  7. 35 0
      backend/operation/mobile.proto
  8. 225 11
      backend/operation/pasture.proto
  9. 136 0
      backend/operation/statistic.proto
  10. 111 27
      backend/operation/system.proto
  11. 15 1
      config/app.go
  12. 9 1
      config/app.test.yaml
  13. 1 0
      config/load_config.go
  14. 2 0
      dep/dep.go
  15. 17 9
      go.mod
  16. 51 14
      go.sum
  17. 2 0
      http/debug/debug.go
  18. 22 0
      http/debug/debug_test.go
  19. 46 0
      http/debug/x_main_test.go
  20. 250 0
      http/handler/feed/feed_formula.go
  21. 33 0
      http/handler/mobile/mobile_list.go
  22. 278 0
      http/handler/pasture/cattle_forage_category.go
  23. 250 0
      http/handler/pasture/forage_list.go
  24. 0 39
      http/handler/pasture/pasture.go
  25. 161 0
      http/handler/pasture/pasture_list.go
  26. 59 0
      http/handler/statistic/analysis.go
  27. 27 13
      http/handler/system/menu.go
  28. 39 8
      http/handler/system/role.go
  29. 95 15
      http/handler/system/user.go
  30. 1 0
      http/middleware/cors.go
  31. 3 3
      http/middleware/pagination.go
  32. 9 2
      http/middleware/sso.go
  33. 1 0
      http/route/api_debug_route.go
  34. 62 3
      http/route/app_api.go
  35. 70 0
      http/util/httptt/http.go
  36. 100 0
      http/util/httptt/query_encoder.go
  37. 0 3
      logger/zap-2023-05-06.log
  38. 78 0
      model/analysis_accuracy.go
  39. 70 0
      model/cattle_category.go
  40. 81 0
      model/feed_formula.go
  41. 106 0
      model/forage.go
  42. 70 0
      model/forage_category.go
  43. 132 0
      model/formula_estimate.go
  44. 31 49
      model/group_pasture.go
  45. 7 7
      model/system_group_pasture_permissions.go
  46. 68 44
      model/system_menu.go
  47. 2 2
      model/system_menu_permissions.go
  48. 19 1
      model/system_mobile.go
  49. 2 2
      model/system_mobile_permissions.go
  50. 13 26
      model/system_role.go
  51. 51 34
      model/system_user.go
  52. 9 0
      model/wechat.go
  53. 288 0
      module/backend/feed_service.go
  54. 82 13
      module/backend/interface.go
  55. 870 0
      module/backend/mock/kptservice.go
  56. 0 48
      module/backend/ops_service.go
  57. 822 0
      module/backend/pasture_service.go
  58. 177 0
      module/backend/statistic_service.go
  59. 122 48
      module/backend/system_permissions.go
  60. 245 39
      module/backend/system_service.go
  61. 34 0
      module/backend/wx_applet_service.go
  62. 56 0
      module/backend/x_suite_test.go
  63. 2 2
      pkg/apiok/common.go
  64. 4 4
      pkg/di/xreflect/reflect.go
  65. 2 2
      pkg/di/xreflect/reflect_test.go
  66. 11 11
      pkg/di/xreflect/stack_test.go
  67. 68 0
      pkg/ginutil/bind.go
  68. 34 0
      pkg/ginutil/bind_proto.go
  69. 19 0
      pkg/ginutil/json_proto_response.go
  70. 322 0
      pkg/ginutil/setter.go
  71. 30 0
      pkg/jsonpb/decode.go
  72. 33 0
      pkg/jsonpb/decode_test.go
  73. 52 0
      pkg/jsonpb/encode.go
  74. 37 0
      pkg/jsonpb/encode_test.go
  75. 108 0
      pkg/jsonpb/query_decode.go
  76. 9 0
      pkg/jsonpb/query_decode_test.go
  77. 2 3
      pkg/jwt/jwt.go
  78. 2 1
      pkg/valid/is/rule.go
  79. 1 1
      pkg/valid/is/rule_test.go
  80. 38 0
      pkg/xreflect/reflect.go
  81. 111 0
      pkg/xreflect/xreflect_test.go
  82. 1 0
      pkg/xstore/database/dbtest/mysql.go
  83. 50 0
      pkg/xstore/database/gorm.go
  84. 1 0
      pkg/xstore/database/migrator/migrate.go
  85. 173 0
      proto/go/backend/operation/common.pb.go
  86. 788 8
      proto/go/backend/operation/enum.pb.go
  87. 643 0
      proto/go/backend/operation/feed_formula.pb.go
  88. 449 0
      proto/go/backend/operation/mobile.pb.go
  89. 2987 137
      proto/go/backend/operation/pasture.pb.go
  90. 1702 0
      proto/go/backend/operation/statistic.pb.go
  91. 648 79
      proto/go/backend/operation/system.pb.go
  92. 36 0
      service/excel/interface.go
  93. 1 1
      service/sso/cache.go
  94. 1 1
      service/sso/sso.go
  95. 77 0
      service/wechat/http.go
  96. 19 0
      service/wechat/interface.go
  97. 22 0
      test/mock/mock.go

+ 38 - 7
.drone.yml

@@ -1,12 +1,43 @@
 kind: pipeline
 type: docker
-name: default
+name: kptTmrGroup-server-publish
 
 steps:
-  - name: build
-    image: golang:1.17.1
-    environment:
-      GOPROXY: "https://goproxy.cn,direct"
+  - name: test
+    image: golang:1.17
     commands:
-      - env
-      - CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o kpt-gogs-demo
+      - pwd
+      - ls -l
+  - name: build
+    image: plugins/docker
+    volumes:
+      - name: hosts
+        path: /etc/hosts
+      - name: docker-ca
+        path: /etc/docker
+      - name: dockersock
+        path: /var/run/docker.sock
+    settings:
+      dockerfile: /drone/src/kpt-tmr-group/Dockerfile
+      username:
+        from_secret: aliyuncs_username
+      password:
+        from_secret: aliyuncs_password
+      repo: registry.cn-hangzhou.aliyuncs.com/kpt-event/kpt-tmr-group
+      registry: registry.cn-hangzhou.aliyuncs.com
+      tags: [1.0.0,latest]
+trigger:
+  branch:
+   include:
+     - develop
+  event:
+    - push
+    - merge
+
+volumes:
+  - name: docker-ca
+    host:
+      path: /etc/docker
+  - name: dockersock
+    host:
+      path: /var/run/docker.sock

+ 1 - 1
.gitignore

@@ -46,4 +46,4 @@ _testmain.go
 .idea/
 bin/
 .vscode/
-logger
+logger/

+ 24 - 0
Dockerfile

@@ -0,0 +1,24 @@
+FROM golang:1.17-alpine3.16 as builder
+WORKDIR /go/src/kpt-tmr-group
+
+LABEL name="kpt-event" \
+description="pt service" \
+owner="yiping.xu"
+
+COPY ./* .
+
+RUN ls -l && pwd
+
+RUN go env -w GO111MODULE=on \
+    && go env -w GOPROXY=https://goproxy.cn,direct \
+    && go env -w CGO_ENABLED=0 \
+    && go env \
+    && go mod tidy -compat=1.17 \
+    && go build -o ./bin/kptTmrGroup \
+    && ls -l ./bin
+
+EXPOSE 8080
+
+VOLUME /var/Logger
+
+CMD ["/go/src/kpt-tmr-group/bin/kptTmrGroup"]

+ 9 - 0
README.md

@@ -30,3 +30,12 @@ lint:
 需要更新 gomock:
 - 生成 mock 前,请确保你能够编译 & 编译完成
 - make generate
+
+TODO 列表
+- proto3 int64 jsonpb处理后自动转成string
+  * 现在处理的方式是把int64类型改成int32类型
+- 用户登出没有用redis做缓存,所以后端没有提供登出接口,有以下弊端
+  * 用户的token没有过期,如被人劫持,会被使用到直到token过期
+  
+- 饲料列表--小料车下拉框问题
+- 权限列表排序的问题

+ 69 - 0
backend/operation/enum.proto

@@ -12,3 +12,72 @@ message IsShow {
     NO = 2;       // 否
   }
 }
+
+message CattleCategoryParent {
+  enum Kind {
+    INVALID = 0;           // 无效
+    LACTATION_CAW = 1;     // 泌乳牛
+    FATTEN_CAW = 2;        // 育肥牛
+    RESERVE_CAW  = 3;      // 后备牛
+    DRY_CAW = 4;           // 干奶牛
+    PERINATAL_CAW  = 5;    // 围产牛
+    OTHER_CAW = 6;         // 其他
+  }
+}
+
+// 饲料类别
+message ForageCategoryParent {
+  enum Kind {
+    INVALID = 0;           // 无效
+    ROUGHAGE = 1;          // 粗料
+    CONCENTRATE  = 2;      // 精料(浓缩料)
+    HALF_ROUGHAGE_HALF_CONCENTRATE = 3;   // 粗料精料各半
+    OTHER = 4;        // 其他
+  }
+}
+
+// 饲料来源
+message ForageSource {
+  enum Kind {
+    INVALID = 0;           // 无效
+    SYSTEM_BUILT_IN = 1;   // 系统内置
+    USER_DEFINED = 2;      // 用户自定义
+  }
+}
+
+// 跳转延迟
+message JumpDelaType {
+  enum Kind {
+    INVALID = 0;           // 禁用
+    THREE = 1;             // 3秒
+    SIX = 2;               // 6秒
+    NINE = 3;              // 9秒
+  }
+}
+
+// 计划类型
+message ForagePlanType {
+  enum Kind {
+    INVALID = 0;           // 无
+    FORKLIFT = 1;          // 铲车
+    CONCENTRATE = 2;       // 精料
+  }
+}
+
+message DataSource {
+  enum Kind {
+    INVALID = 0;           // 无
+    BACKGROUND_ADD = 1;    // 后台手动添加
+    EXCEL_IMPORT = 2;     // excel报表导入
+    FROM_PASTURE = 3;     // 来自牧场
+  }
+}
+
+message FormulaType {
+  enum Kind {
+    INVALID = 0;                    // 无
+    FEED_FORMULA = 1;              // 饲喂配方
+    PREMIXED_FORMULA = 2;          // 预混配方
+    SUPPLEMENTARY_FORMULA = 3;     // 补料配方
+  }
+}

+ 51 - 0
backend/operation/feed_formula.proto

@@ -0,0 +1,51 @@
+syntax = "proto3";
+package backend.operation;
+
+option go_package = ".;operationPb";
+
+import "backend/operation/pagination.proto";
+import "backend/operation/enum.proto";
+
+message AddFeedFormulaRequest {
+  int32 id = 1;
+  string name = 2;                   // 名称
+  string  encode_number = 3;         // 编码
+  string colour = 4;                 // 颜色
+  CattleCategoryParent.Kind cattle_category_id = 5;      // 畜牧类别id
+  string cattle_category_name = 6;   // 畜牧类型名称
+  int32 formula_type_id = 7;         // 配方类型id
+  string formula_type_name = 8;      // 配方类型名称
+  DataSource.Kind data_source_id = 9;   // 数据来源
+  string data_source_name = 10;   // 数据来源
+  string remarks = 11;               // 备注
+  int32 version = 12;                // 版本号
+  IsShow.Kind is_show = 13;          // 是否启用
+  IsShow.Kind is_modify = 14;        // 是否可修改
+  int32 created_at = 15;             // 创建时间
+  string created_at_format = 16;     // 创建时间格式化
+  string pasture_name = 17;          // 牧场名称
+}
+
+message SearchFeedFormulaRequest {
+  int32 cattle_category_id = 1;    // 分类id
+  int32 formula_type_id = 2;       // 配方类型id
+  int32 data_source = 3;           // 饲料来源
+  IsShow.Kind is_show = 4;         // 是否启用
+  string name = 5;                 // 配方名称
+  string remarks = 6;              // 备注
+  PaginationModel pagination = 7;  // 分页
+}
+
+message SearchFeedFormulaListResponse {
+  int32 page = 1;
+  int32 page_size = 2;
+  int32 total = 3;
+  repeated AddFeedFormulaRequest list = 4;
+}
+
+// 是否启用
+message IsShowModifyFeedFormula {
+  int32 feed_formula_id = 1;
+  IsShow.Kind is_show = 2;
+  int32 edit_type = 3;                  // 1 更新是否启用 2 更新 modify
+}

+ 35 - 0
backend/operation/mobile.proto

@@ -0,0 +1,35 @@
+syntax = "proto3";
+package backend.operation;
+
+option go_package = ".;operationPb";
+
+import "backend/operation/pagination.proto";
+import "backend/operation/system.proto";
+
+message SearchMobileRequest {
+  string name = 1;       // 名称
+  PaginationModel pagination = 2; // 分页
+}
+
+message SearchMobileResponse {
+  int32 code = 1;
+  string msg = 2;
+  SearchMobileData data = 3;
+}
+
+message SearchMobileData {
+  int32 page = 1;
+  int32 total = 2;
+  int32 page_size = 3;
+  repeated  MobileData list = 4;
+}
+
+message MobileData {
+  uint32 id = 1;
+  string name = 2;
+  uint32 created_at = 3;
+  string created_at_format = 4;
+  repeated AddMenuRequest children = 15;   // 子分类
+}
+
+

+ 225 - 11
backend/operation/pasture.proto

@@ -4,29 +4,243 @@ package backend.operation;
 option go_package = ".;operationPb";
 
 import "backend/operation/enum.proto";
+import "backend/operation/pagination.proto";
 
 message AddPastureRequest {
-  int64 id = 1;
+  int32 id = 1;
   string name = 2; // 牧场名称
-  string manager_user = 3; // 牧场负责人名称
+  string account = 3;   // 登录账号名称
+  string manager_user = 4; // 牧场负责人名称
   string manager_phone = 5;   // 牧场负责人手机号
   string address = 6;   // 牧场地址
   IsShow.Kind is_show = 7;    // 是否启用
-  int64 created_at = 8;    // 创建时间
+  int32 created_at = 8;    // 创建时间
+  string created_at_format = 9;    // 创建时间格式化
 }
 
 message SearchPastureRequest {
-  int32 page = 1;        // 第几页,从1开始
-  int32 page_size = 2;   // 每页size,一般为20
-  string name = 3;       // 牧场名称
-  string manager_user = 4; // 牧场负责人名称
-  string manager_phone = 5;   // 牧场负责人手机号
-  int64 start_time = 6;       // 开始时间
-  int64 end_time = 7;    // 结束时间
+  string name = 1;       // 牧场名称
+  string manager_user = 2; // 牧场负责人名称
+  string manager_phone = 3;   // 牧场负责人手机号
+  uint32 start_time = 4;       // 开始时间
+  uint32 end_time = 5;    // 结束时间
+  PaginationModel pagination = 6; // 分页
 }
 
 message SearchPastureResponse {
+  int32 code = 1;
+  string msg = 2;
+  SearchPastureData data = 3;
+}
+
+message SearchPastureData {
+  int32 page = 1;
+  int32 total = 2;
+  int32 page_size = 3;
+  repeated AddPastureRequest list = 4;
+}
+
+message UserPasture {
+  int32 id = 1;
+  string name = 2;
+}
+
+message IsShowGroupPasture {
+  int32 pasture_id = 1;    // 牧场id
+  IsShow.Kind is_show = 2;     // 是否开启
+}
+
+// 添加牧畜分类
+message AddCattleCategoryRequest {
+  uint32 id = 1;
+  CattleCategoryParent.Kind parent_id = 2;  // 父类id
+  string parent_name = 3;        // 父类名称
+  string name = 4;               // 牧畜分类名称
+  string number = 5;            // 畜牧类别编号
+  IsShow.Kind is_show = 6;      // 是否启用
+  uint32 created_at = 7;         // 创建时间
+  string created_at_format = 8;   // 创建时间格式
+}
+
+// 是否启用
+message IsShowCattleCategory {
+  int32 cattle_category_id = 1;
+  IsShow.Kind is_show = 2;
+}
+
+// 畜牧分类查询列表
+message SearchCattleCategoryRequest {
+  string parent_name = 1;
+  IsShow.Kind is_show = 2;
+  string name = 3;
+  PaginationModel pagination = 4; // 分页
+}
+
+message SearchCattleCategoryResponse {
+  int32 code = 1;
+  string msg = 2;
+  SearchCattleCategoryData data = 3;
+}
+
+message SearchCattleCategoryData {
+  int32 page = 1;
+  int32 total = 2;
+  int32 page_size = 3;
+  repeated AddCattleCategoryRequest list = 4;
+}
+
+// 添加饲料分类
+message AddForageCategoryRequest {
+  uint32 id = 1;
+  ForageCategoryParent.Kind parent_id = 2;  // 父类id
+  string parent_name = 3;        // 父类名称
+  string name = 4;               // 牧畜分类名称
+  string number = 5;            // 畜牧类别编号
+  IsShow.Kind is_show = 6;      // 是否启用
+  uint32 created_at = 7;         // 创建时间
+  string created_at_format = 8;   // 创建时间格式
+}
+
+// 是否启用
+message IsShowForageCategory {
+  int32 forage_category_id = 1;
+  IsShow.Kind is_show = 2;
+}
+
+// 饲料分类查询列表
+message SearchForageCategoryRequest {
+  string parent_name = 1;
+  IsShow.Kind is_show = 2;
+  string name = 3;
+  PaginationModel pagination = 4; // 分页
+}
+
+message SearchForageCategoryResponse {
+  int32 code = 1;
+  string msg = 2;
+  SearchForageCategoryData data = 3;
+}
+
+message SearchForageCategoryData {
   int32 page = 1;
   int32 total = 2;
-  repeated AddPastureRequest list = 3;
+  int32 page_size = 3;
+  repeated AddForageCategoryRequest list = 4;
+}
+
+// 饲料列表
+message AddForageRequest {
+  int32 id = 1;
+  string name = 2;                // 饲料名称
+  int32 category_id = 3;          // 饲料分类id
+  string category_name = 4;       // 饲料分类名称
+  int32 material_type = 5;        // 物料类型
+  string unique_encode = 7;       // 唯一编码
+  ForageSource.Kind forage_source_id = 8;       // 饲料来源id
+  string forage_source_name = 9;                // 饲料来源名称
+  ForagePlanType.Kind plan_type_id = 10;       // 计划类型id
+  string plan_type_name = 11;                  // 计划类型名称
+  string  small_material_scale = 12;           // 小料称
+  int32 allow_error = 13;                     // 允许误差 (单位kg)
+  int32 package_weight = 14;                  // 包装重量 (单位kg)
+  int32 price = 15;                          // 单价(单位分)
+  int32 jump_weight = 16;                    // 跳转重量域(单位kg)
+  JumpDelaType.Kind jump_delay = 17;         // 跳转延迟
+  IsShow.Kind confirm_start = 18;            // 确认开始
+  int32  relay_locations = 19;               // 继电器位置
+  IsShow.Kind jmp = 20;                     // 无上域
+  string backup1 = 21;                      // 备用字段1
+  string backup2 = 22;                      // 备用字段2
+  string backup3 = 23;                      // 备用字段3
+  int32 created_at = 24;                    // 创建时间
+  string created_at_format = 25;            // 创建时间格式化
+  IsShow.Kind is_show = 26;                  // 是否启用
+}
+
+message SearchForageListRequest {
+  string name = 1;   // 饲料名称
+  string category_id = 2;   // 饲料分类id
+  uint32 forage_source_id = 3;   // 饲料来源
+  IsShow.Kind is_show = 4;    // 是否启用
+  uint32 allow_error = 5;      // 允许误差
+  uint32 jump_weight = 6;      // 跳转重量域
+  JumpDelaType.Kind  jump_delay = 7;   // 跳转延迟
+  PaginationModel pagination = 8; // 分页
+}
+
+message SearchForageListResponse {
+ int32 code = 1;
+ string msg = 2;
+ SearchForageList data = 3;
+}
+
+message SearchForageList {
+  int32 page = 1;
+  int32 page_size = 2;
+  int32 total = 3;
+  repeated AddForageRequest list = 4;
+}
+
+// 是否启用
+message IsShowForage {
+  int32 forage_id = 1;
+  IsShow.Kind is_show = 2;
+}
+
+message ForageEnumListResponse {
+  int32 code = 1;
+  string msg = 2;
+  ForageEnumList data = 3;
+}
+
+message ForageEnumList {
+  repeated ForageSourceEnum forage_source = 1;
+  repeated ForagePlanTypeEnum forage_plan_type = 2;
+  repeated JumpDelaTypeEnum jump_dela_type = 3;
+  repeated CattleParentCategoryEnum cattle_parent_category = 4;
+  repeated ForageParentCategoryEnum forage_parent_category = 5;
+  repeated IsShowEnum is_show = 6;
+  repeated FormulaTypeEnum formula_type = 7;
+  repeated FormulaOptionEnum formula_list = 8;
+  repeated IsShowEnum confirm_start = 9;
+}
+
+message ForageSourceEnum {
+  ForageSource.Kind value = 1;
+  string label = 2;
+}
+
+message ForagePlanTypeEnum {
+  ForagePlanType.Kind value = 1;
+  string label = 2;
+}
+
+message JumpDelaTypeEnum {
+  JumpDelaType.Kind value = 1;
+  string label = 2;
+}
+
+message CattleParentCategoryEnum {
+  CattleCategoryParent.Kind value = 1;
+  string label = 2;
+}
+
+message ForageParentCategoryEnum {
+  ForageCategoryParent.Kind value = 1;
+  string label = 2;
+}
+
+message IsShowEnum {
+  IsShow.Kind value = 1;
+  string label = 2;
+}
+
+message FormulaTypeEnum {
+  FormulaType.Kind value = 1;
+  string label = 2;
+}
+
+message FormulaOptionEnum {
+  int32 value = 1;
+  string label = 2;
 }

+ 136 - 0
backend/operation/statistic.proto

@@ -0,0 +1,136 @@
+syntax = "proto3";
+package backend.operation;
+
+option go_package = ".;operationPb";
+
+
+import "backend/operation/pagination.proto";
+import "backend/operation/enum.proto";
+
+// 添加配方评估 具体字段含义参照formula_estimate表对应的字段
+message AddFormulaEstimateRequest {
+  int32 id = 1;
+  int32 pasture_id = 2;
+  string pasture_name = 3;
+  int32 barn_id = 4;
+  int32 feed_formula_id = 5;
+  string feed_formula_name = 6;
+  int32 cow_number = 7;
+  int32 dry_formula_number = 8;
+  int32 dry_tmr_feed = 9;
+  int32 dry_food_intake = 10;
+  int32 mj_formula_number = 11;
+  int32 mj_tmr_feed = 12;
+  int32 mj_food_intake = 13;
+  int32 nnd_formula_number = 14;
+  int32 nnd_tmr_feed = 15;
+  int32 nnd_food_intake = 16;
+  int32 cpg_formula_number = 17;
+  int32 cpg_tmr_feed = 18;
+  int32 cpg_food_intake = 19;
+  int32 pg_formula_number = 20;
+  int32 pg_tmr_feed = 21;
+  int32 pg_food_intake = 22;
+  int32 dm_formula_number = 23;
+  int32 dm_tmr_feed = 24;
+  int32 dm_food_intake = 25;
+  int32 cpdm_formula_number = 26;
+  int32 cpdm_tmr_feed = 27;
+  int32 cpdm_food_intake = 28;
+  int32 fat_formula_number = 29;
+  int32 fat_tmr_feed = 30;
+  int32 fat_food_intake = 31;
+  int32 starch_formula_number = 32;
+  int32 starch_tmr_feed = 33;
+  int32 starch_food_intake = 34;
+  int32 ndf_formula_number = 35;
+  int32 ndf_tmr_feed = 36;
+  int32 ndf_food_intake = 37;
+  int32 cp_ndf_formula_number = 38;
+  int32 cp_ndf_tmr_feed = 39;
+  int32 cp_ndf_food_intake = 40;
+  int32 adf_formula_number = 41;
+  int32 adf_tmr_feed = 42;
+  int32 adf_food_intake = 43;
+  int32 calcium_formula_number = 44;
+  int32 calcium_tmr_feed = 45;
+  int32 calcium_food_intake = 46;
+  int32 pdm_formula_number = 47;
+  int32 pdm_tmr_feed = 48;
+  int32 pdm_food_intake = 49;
+  int32 cf_ratio_formula_number = 50;
+  int32 cf_ratio_tmr_feed = 51;
+  int32 cf_ratio_food_intake = 52;
+  int32 created_at = 53;
+  string created_at_format = 54;
+}
+
+message SearchFormulaEstimateRequest {
+  string start_time  = 1;     // 开始时间
+  string end_time    = 2;     // 结束时间
+  int32 search_type = 3;      // 查询方式  1 安照配方 2 按照栏舍
+  string name = 4;            // 名称
+  PaginationModel pagination = 5; // 分页
+}
+
+message SearchFormulaEstimateResponse {
+  int32 code = 1;
+  string msg = 2;
+  SearchFormulaEstimate data = 3;
+}
+
+message SearchFormulaEstimate {
+  int32 page = 1;
+  int32 total = 2;
+  int32 page_size = 3;
+  repeated AddFormulaEstimateRequest list = 4;
+}
+
+// 首页 dashboard 准确性分析
+message SearchAnalysisAccuracyRequest {
+  CattleCategoryParent.Kind cattle_parent_category_id = 1;   // 牧畜分类id 泌乳牛
+  int32 feed_formula_id = 2;    // 配方id
+  string start_date = 3;       // 开始时间
+  string end_date = 4;         // 结束时间
+  repeated int32 pasture_ids = 5;   //牧场ids
+}
+
+message SearchAnalysisAccuracyResponse {
+  int32 code = 1;
+  string msg = 2;
+  AnalysisAccuracy data = 3;
+}
+
+message AnalysisAccuracy {
+  Chart chart = 1;
+  Table table = 2;
+}
+
+message Chart {
+  CommonValueRatio mixed_fodder_accurate_ratio = 4;              // 混料准确率
+  CommonValueRatio mixed_fodder_correct_ratio = 5;               // 混料正确率
+  CommonValueRatio sprinkle_fodder_accurate_ratio = 6;           // 撒料准确率
+  CommonValueRatio sprinkle_fodder_correct_ratio = 7;            // 撒料正确率
+}
+
+message Table {
+  message TableList {
+    int32 id = 1;
+    string name = 2;
+  }
+
+  repeated TableList table_list = 1;
+}
+
+message CommonValueRatio {
+  string max_value = 1;                  // 最高值
+  string middle_value = 2;               // 中位值
+  string min_value = 3;                  // 最低值
+  repeated ValueRatio data_list = 4;     // 数据集合
+  repeated string pasture_name = 5;      // 牧场名称集合
+  repeated string date_day = 6;          // 日期集合
+}
+
+message ValueRatio {
+  repeated string value_ratio = 1;
+}

+ 111 - 27
backend/operation/system.proto

@@ -6,18 +6,33 @@ option go_package = ".;operationPb";
 import "backend/operation/enum.proto";
 import "backend/operation/pagination.proto";
 import "backend/operation/pasture.proto";
+
+message CommonOK {
+  int32 code = 1;
+  string msg = 2;
+  Success data = 3;
+}
+
+message Success {
+  bool success = 1;
+}
+
+message WxOpenId {
+  string openid = 1;
+}
+
 // 用户角色
 message AddRoleRequest {
-  int64 id = 1;
+  uint32 id = 1;
   string name = 2;        // 角色名称
   string remarks = 3;     // 角色备注
   IsShow.Kind is_show = 4;     // 是否启用
-  repeated int64 pasture_id = 5;    // 牧场id
-  repeated int64 menu_id = 6;       // 菜单id
-  repeated int64 mobile_id = 7;     // 移动端id
+  repeated uint32 pasture_id = 5;    // 牧场id
+  repeated uint32 menu_id = 6;       // 菜单id
+  repeated uint32 mobile_id = 7;     // 移动端id
   string create_user = 8;          // 创建用户
-  int64 created_at = 9;             // 创建时间
-  string crated_at_format = 10;     // 创建时间格式化
+  uint32 created_at = 9;             // 创建时间
+  string created_at_format = 10;     // 创建时间格式化
 }
 
 message SearchRoleRequest {
@@ -26,40 +41,83 @@ message SearchRoleRequest {
 }
 
 message SearchRoleResponse {
+  uint32 code = 1;
+  string msg = 2;
+  SearchRoleData data = 3;
+}
+
+message SearchRoleData {
   int32 page = 1;
   int32 total = 2;
-  repeated AddRoleRequest list = 3;
+  int32 page_size = 3;
+  repeated AddRoleRequest list = 4;
+}
+
+// 角色对应权限列表
+message RolePermissionsList {
+  int32 code = 1;
+  string msg = 2;
+  RolePermissionsData data = 3;
+}
+
+message RolePermissionsData {
+  repeated uint32 mobile_list = 1;    // 移动端权限id
+  repeated uint32 pasture_list = 2;    // 牧场端列表
+  repeated uint32 menu_list = 3;      // 后端权限列表
 }
 
 // 用户token
 message SystemToken {
+  int32 code =  1;
+  string msg = 2;
+  TokenData data = 3;
+}
+
+message TokenData {
   string token = 1;
 }
 
 // 用户登录
 message UserAuth {
+  int32 code = 1;
+  string msg = 2;
+  UserAuthData data = 3;
+}
+
+message UserAuthData {
   string user_name = 1;   // 用户名称
   string password = 2;    // 用户密码
   string phone = 3;       // 用户手机号
   repeated UserRole roles  = 4;    // 用户角色
   string employee_name = 5;    // 员工名称
+  repeated UserPasture pastures = 6; // 用户角色牧场牧场
 }
 
 message UserRole {
-  int64 id = 1;      // 角色id
+  int32 id = 1;      // 角色id
   string name = 2;   // 角色名称
 }
 
+// 用户详情
+message UserDetails {
+  int32 code = 1;
+  string msg = 2;
+  AddSystemUser data = 3;
+}
+
+
 message AddSystemUser {
-  int64 id = 1;                    // 用户id
+  int32 id = 1;                    // 用户id
   string name = 2;                 // 用户名称
   string phone = 3;                // 用户手机号
   repeated UserRole roles = 4;      // 角色
   IsShow.Kind is_show = 5;         // 是否开启
   string employee_name = 6;        // 员工姓名
   string create_user = 7;          // 创建人
-  int64 created_at = 8;            // 创建时间
-  string crated_at_format = 9;      // 创建时间格式化
+  int32 created_at = 8;            // 创建时间
+  string created_at_format = 9;      // 创建时间格式化
+  string role_name = 10;            // 角色名称
+  repeated int32 role_ids = 11;     // 角色ids
 }
 
 // 查询用户
@@ -67,43 +125,51 @@ message SearchUserRequest {
   string name = 1;               // 用户名称
   string employee_name = 2;       // 员工姓名
   IsShow.Kind is_show = 3;        // 是否启用
-  int64 created_start_time = 4;   // 开始时间
-  int64 created_end_time = 5;     // 结束时间
+  uint32 created_start_time = 4;   // 开始时间
+  uint32 created_end_time = 5;     // 结束时间
   PaginationModel pagination = 6; // 分页
 }
 
 message SearchUserResponse {
+    int32 code = 1;
+    string msg = 2;
+    SearchUserData data = 3;
+}
+
+message SearchUserData  {
   int32 page = 1;
   int32 total = 2;
-  repeated AddSystemUser list = 3;
+  int32 page_size = 3;
+  repeated AddSystemUser list = 4;
 }
 
 message IsShowSystemUserRequest {
-  int64 user_id = 1;  // 用户id
+  int32 user_id = 1;  // 用户id
   IsShow.Kind is_show = 2;   // is_show
 }
-
 // 系统菜单权限
 message AddMenuRequest {
-  int64 id = 1;
-  string name = 2;       // 名称
-  int64 parent_id = 3;   // 父id
-  int32 menu_type = 4;  // 菜单类型 1 菜单 2 按钮
-  string title = 5;      // 标题
-  string path = 6;       // 路径 path
+  int32 id = 1;
+  string name = 2;           // 名称
+  int32 parent_id = 3;       // 父id
+  int32 menu_type = 4;       // 菜单类型 1 菜单 2 按钮
+  string title = 5;          // 标题
+  string path = 6;           // 路径 path
   IsShow.Kind is_show = 7;   // 是否显示
   string component = 8;      // 组件
   string icon = 9;           // 图标
   int32 sort = 10;           // 排序
   string redirect = 11;      // 重定向
-  int64 created_at = 12;     // 创建时间
-  string crated_at_format = 13;      // 创建时间格式化
-  int32 level = 14;      // 菜单等级
+  int32 created_at = 12;     // 创建时间
+  string created_at_format = 13;      // 创建时间格式化
+  int32 level = 14;                   // 菜单等级
   repeated AddMenuRequest children = 15;   // 子分类
+  bool affix = 16;
+  bool keepAlive = 17;
 }
 
 message IsShowSystemMenuRequest {
-  int64 menu_id = 1;  // 角色id
+  int32 menu_id = 1;  // 角色id
   IsShow.Kind is_show = 2;   // is_show
 }
 
@@ -114,6 +180,12 @@ message SearchMenuRequest {
 }
 
 message SearchMenuResponse {
+  int32 code = 1;
+  string msg = 2;
+  SearchMenuData data = 3;
+}
+
+message SearchMenuData {
   int32 page = 1;
   int32 total = 2;
   repeated AddMenuRequest list = 3;
@@ -121,13 +193,25 @@ message SearchMenuResponse {
 
 // 系统用户权限相关
 message SystemUserMenuPermissions {
+  int32 code = 1;
+  string msg = 2;
+  SystemUserMenuData data = 3;
+}
+
+message SystemUserMenuData {
   repeated AddPastureRequest pasture_list = 1;    // 牧场列表
   repeated AddMenuRequest menu_list = 2;          // 菜单列表
   repeated AddMobileRequest mobile_list = 3;      // 移动端权限
+  repeated MenuButtonsPath menu_buttons_path = 4;  // 按钮级别权限path,前端需要特别处理
+}
+
+message MenuButtonsPath {
+  string path = 1;
+  int32 menu_id = 2;
 }
 
 // 移动端
 message AddMobileRequest {
-  int64 id = 1;     // id
+  uint32 id = 1;     // id
   string name = 2;  // 名称
 }

+ 15 - 1
config/app.go

@@ -7,7 +7,7 @@ import (
 	"sync"
 )
 
-const AppName = "kptEvent"
+const AppName = "kptTmrGroup"
 
 var (
 	Module   = di.Provide(Options)
@@ -28,6 +28,20 @@ type AppConfig struct {
 	// redis 配置
 	RedisSetting RedisSetting `json:"RedisSetting" yaml:"redis_setting"`
 	JwtSecret    string       `json:"jwtSecret" yaml:"jwt_secret"`
+
+	ExcelSetting ExcelSetting `json:"excelSetting" yaml:"excel_setting"`
+
+	WechatSetting WechatSetting `json:"wechatSetting" yaml:"wechat_setting"`
+}
+
+type WechatSetting struct {
+	Appid  string `yaml:"appid"`
+	Secret string `yaml:"secret"`
+}
+
+type ExcelSetting struct {
+	SheetName string  `yaml:"sheet_name"` // = "Sheet1" //默认Sheet名称
+	Height    float64 `yaml:"height"`     // = 25.0     //默认行高度
 }
 
 // StoreSetting 数据库配置

+ 9 - 1
config/app.test.yaml

@@ -17,4 +17,12 @@ redis_setting:
     requirepass: ""
     expiry: 120
 
-jwt_secret: "sUd7j%UfJMt59ywh"
+jwt_secret: "sUd7j%UfJMt59ywh"
+
+excel_setting:
+  sheet_name: "Sheet1"
+  height: 25.0
+
+wechat_setting:
+  appid: wxd6e17d5709ce9c80
+  secret: 13b93e6c1cda8b6e46adb358a04f9f8f

+ 1 - 0
config/load_config.go

@@ -9,6 +9,7 @@ import (
 
 func Initialize(path string, cfgStruct interface{}) error {
 	dir := fmt.Sprintf("./config/%s", path)
+	// dir = fmt.Sprintf("D:\\project\\golangNew\\kpt-tmr-group\\config\\%s", path)
 	viper.SetConfigType("yaml")
 	viper.SetConfigFile(dir)
 	if err := viper.ReadInConfig(); err != nil {

+ 2 - 0
dep/dep.go

@@ -5,6 +5,7 @@ import (
 	"kpt-tmr-group/module/backend"
 	"kpt-tmr-group/pkg/di"
 	"kpt-tmr-group/service/sso"
+	"kpt-tmr-group/service/wechat"
 	"kpt-tmr-group/store/kptstore"
 )
 
@@ -25,5 +26,6 @@ func Options() []di.HubOption {
 		kptstore.Module,
 		backend.Module,
 		sso.Module,
+		wechat.Module,
 	}
 }

+ 17 - 9
go.mod

@@ -3,6 +3,7 @@ module kpt-tmr-group
 go 1.17
 
 require (
+	github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2
 	github.com/dgrijalva/jwt-go v3.2.0+incompatible
 	github.com/getsentry/sentry-go v0.20.0
 	github.com/gin-contrib/cors v1.4.0
@@ -11,20 +12,25 @@ require (
 	github.com/gin-gonic/gin v1.9.0
 	github.com/go-redis/redis v6.15.9+incompatible
 	github.com/go-redis/redis/v7 v7.4.1
+	github.com/golang/mock v1.4.4
+	github.com/golang/protobuf v1.5.3
 	github.com/google/go-cmp v0.5.9
 	github.com/grpc-ecosystem/go-grpc-middleware v1.4.0
 	github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
+	github.com/huandu/xstrings v1.4.0
 	github.com/jinzhu/copier v0.3.5
 	github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible
+	github.com/magiconair/properties v1.8.7
 	github.com/mitchellh/mapstructure v1.5.0
 	github.com/natefinch/lumberjack v2.0.0+incompatible
+	github.com/nyaruka/phonenumbers v1.1.7
 	github.com/sirupsen/logrus v1.9.0
 	github.com/spf13/cobra v1.7.0
 	github.com/spf13/viper v1.15.0
 	github.com/stretchr/testify v1.8.2
+	github.com/xuri/excelize/v2 v2.7.1
 	go.uber.org/dig v1.15.0
 	go.uber.org/zap v1.21.0
-	golang.org/x/oauth2 v0.5.0
 	google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef
 	google.golang.org/grpc v1.52.0
 	google.golang.org/protobuf v1.30.0
@@ -45,7 +51,6 @@ require (
 	github.com/go-playground/validator/v10 v10.11.2 // indirect
 	github.com/go-sql-driver/mysql v1.7.0 // indirect
 	github.com/goccy/go-json v0.10.0 // indirect
-	github.com/golang/protobuf v1.5.3 // indirect
 	github.com/google/uuid v1.3.0 // indirect
 	github.com/hashicorp/hcl v1.0.0 // indirect
 	github.com/inconshreveable/mousetrap v1.1.0 // indirect
@@ -56,18 +61,20 @@ require (
 	github.com/klauspost/cpuid/v2 v2.0.9 // indirect
 	github.com/leodido/go-urn v1.2.1 // indirect
 	github.com/lestrrat-go/strftime v1.0.6 // indirect
-	github.com/magiconair/properties v1.8.7 // indirect
 	github.com/mattn/go-isatty v0.0.17 // indirect
 	github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
 	github.com/modern-go/reflect2 v1.0.2 // indirect
+	github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
 	github.com/pelletier/go-toml/v2 v2.0.6 // indirect
 	github.com/pkg/errors v0.9.1 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
-	github.com/prometheus/client_golang v1.15.1 // indirect
+	github.com/prometheus/client_golang v1.14.0 // indirect
 	github.com/prometheus/client_model v0.3.0 // indirect
 	github.com/prometheus/common v0.42.0 // indirect
 	github.com/prometheus/procfs v0.9.0 // indirect
+	github.com/richardlehane/mscfb v1.0.4 // indirect
+	github.com/richardlehane/msoleps v1.0.3 // indirect
 	github.com/spf13/afero v1.9.3 // indirect
 	github.com/spf13/cast v1.5.0 // indirect
 	github.com/spf13/jwalterweatherman v1.1.0 // indirect
@@ -75,14 +82,15 @@ require (
 	github.com/subosito/gotenv v1.4.2 // indirect
 	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
 	github.com/ugorji/go/codec v1.2.9 // indirect
+	github.com/xuri/efp v0.0.0-20220603152613-6918739fd470 // indirect
+	github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22 // indirect
 	go.uber.org/atomic v1.9.0 // indirect
 	go.uber.org/multierr v1.8.0 // indirect
 	golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
-	golang.org/x/crypto v0.7.0 // indirect
-	golang.org/x/net v0.8.0 // indirect
-	golang.org/x/sys v0.6.0 // indirect
-	golang.org/x/text v0.8.0 // indirect
-	google.golang.org/appengine v1.6.7 // indirect
+	golang.org/x/crypto v0.8.0 // indirect
+	golang.org/x/net v0.9.0 // indirect
+	golang.org/x/sys v0.7.0 // indirect
+	golang.org/x/text v0.9.0 // indirect
 	gopkg.in/ini.v1 v1.67.0 // indirect
 	gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect

+ 51 - 14
go.sum

@@ -39,6 +39,8 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
+github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
+github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
 github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
 github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
 github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
@@ -127,6 +129,7 @@ github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFU
 github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
 github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
 github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
 github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -190,6 +193,8 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
 github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
 github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
 github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU=
+github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
 github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
@@ -214,8 +219,8 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv
 github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
 github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
-github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@@ -242,8 +247,12 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
 github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
+github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
 github.com/natefinch/lumberjack v2.0.0+incompatible h1:4QJd3OLAMgj7ph+yZTuX13Ld4UpgHp07nNdFX7mqFfM=
 github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk=
+github.com/nyaruka/phonenumbers v1.1.7 h1:5UUI9hE79Kk0dymSquXbMYB7IlNDNhvu2aNlJpm9et8=
+github.com/nyaruka/phonenumbers v1.1.7/go.mod h1:DC7jZd321FqUe+qWSNcHi10tyIyGNXGcNbfkPvdp1Vs=
 github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo=
 github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@@ -261,8 +270,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
 github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/prometheus/client_golang v1.15.1 h1:8tXpTmJbyH5lydzFPoxSIJ0J46jdh3tylbvM1xCv0LI=
-github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk=
+github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw=
+github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y=
 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
 github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
@@ -270,6 +279,11 @@ github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI
 github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
 github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI=
 github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY=
+github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM=
+github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk=
+github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
+github.com/richardlehane/msoleps v1.0.3 h1:aznSZzrwYRl3rLKRT3gUk9am7T/mLNSnJINvN0AQoVM=
+github.com/richardlehane/msoleps v1.0.3/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
 github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
@@ -313,11 +327,18 @@ github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6
 github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
 github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU=
 github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
+github.com/xuri/efp v0.0.0-20220603152613-6918739fd470 h1:6932x8ltq1w4utjmfMPVj09jdMlkY0aiA6+Skbtl3/c=
+github.com/xuri/efp v0.0.0-20220603152613-6918739fd470/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
+github.com/xuri/excelize/v2 v2.7.1 h1:gm8q0UCAyaTt3MEF5wWMjVdmthm2EHAWesGSKS9tdVI=
+github.com/xuri/excelize/v2 v2.7.1/go.mod h1:qc0+2j4TvAUrBw36ATtcTeC1VCM0fFdAXZOmcF4nTpY=
+github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22 h1:OAmKAfT06//esDdpi/DZ8Qsdt4+M5+ltca05dA5bG2M=
+github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
 github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
 go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
 go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
 go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
@@ -347,9 +368,10 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
 golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
-golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
+golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
+golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -362,6 +384,8 @@ golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EH
 golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/image v0.5.0 h1:5JMiNunQeQw++mMOz48/ISeNu3Iweh/JaZU8ZLqHRrI=
+golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
 golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -384,6 +408,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -418,8 +444,10 @@ golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v
 golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
-golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
-golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
+golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -429,8 +457,6 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ
 golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.5.0 h1:HuArIo48skDwlrvM3sEdHXElYslAMsf3KwRkkW4MC4s=
-golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -442,6 +468,8 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -484,12 +512,18 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
-golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
+golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
+golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -497,8 +531,10 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
-golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
+golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -553,6 +589,8 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f
 golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
 golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -582,7 +620,6 @@ google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
 google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
 google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
-google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
 google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
 google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=

+ 2 - 0
http/debug/debug.go

@@ -7,5 +7,7 @@ import (
 )
 
 func HelloOk(c *gin.Context) {
+	// redis
+	// kafka
 	c.JSON(http.StatusOK, gin.H{"result": "ok"})
 }

+ 22 - 0
http/debug/debug_test.go

@@ -0,0 +1,22 @@
+package debug_test
+
+import (
+	"kpt-tmr-group/http/util/httptt"
+	"net/http"
+	"net/http/httptest"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestHelloOk(t *testing.T) {
+	t.Run("ok", func(t *testing.T) {
+		g, ctrl, _ := fakeServer(t)
+		defer ctrl.Finish()
+		req := httptt.NewRequest(t, http.MethodGet, "/debug/test", nil)
+		rw := httptest.NewRecorder()
+
+		g.ServeHTTP(rw, req)
+		assert.Equal(t, http.StatusOK, rw.Code)
+	})
+}

+ 46 - 0
http/debug/x_main_test.go

@@ -0,0 +1,46 @@
+package debug_test
+
+import (
+	"io/ioutil"
+	"kpt-tmr-group/dep"
+	"kpt-tmr-group/http/middleware"
+	"kpt-tmr-group/http/route"
+	"kpt-tmr-group/module/backend"
+	kptservicemock "kpt-tmr-group/module/backend/mock"
+	"kpt-tmr-group/test/mock"
+	"os"
+	"testing"
+
+	"go.uber.org/dig"
+
+	"github.com/gin-gonic/gin"
+	"github.com/golang/mock/gomock"
+)
+
+func TestMain(m *testing.M) {
+	gin.SetMode(gin.ReleaseMode)
+	gin.DefaultWriter = ioutil.Discard
+	os.Exit(m.Run())
+}
+
+type Mock struct {
+	dig.In
+
+	KptService *kptservicemock.MockKptService
+}
+
+func fakeServer(t *testing.T) (*gin.Engine, *gomock.Controller, *Mock) {
+	ctrl := gomock.NewController(t)
+	var currMock *Mock
+	mock.GetMock(ctrl, func(entry Mock) { currMock = &entry })
+
+	g := gin.New()
+	route.HTTPServerRoute(func(engine *gin.Engine) {
+		engine.Use(middleware.WithDependency(&dep.HttpDependency{
+			StoreEventHub: backend.Hub{
+				OpsService: currMock.KptService,
+			},
+		}))
+	})(g)
+	return g, ctrl, currMock
+}

+ 250 - 0
http/handler/feed/feed_formula.go

@@ -0,0 +1,250 @@
+package feed
+
+import (
+	"fmt"
+	"kpt-tmr-group/http/middleware"
+	"kpt-tmr-group/pkg/apierr"
+	"kpt-tmr-group/pkg/ginutil"
+	"kpt-tmr-group/pkg/valid"
+	"kpt-tmr-group/pkg/xerr"
+	operationPb "kpt-tmr-group/proto/go/backend/operation"
+	"mime/multipart"
+	"net/http"
+	"path"
+	"strconv"
+	"time"
+
+	"github.com/gin-gonic/gin"
+)
+
+func AddFeedFormula(c *gin.Context) {
+	var req operationPb.AddFeedFormulaRequest
+	if err := c.BindJSON(&req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := valid.ValidateStruct(&req,
+		valid.Field(&req.Name, valid.Required),
+		valid.Field(&req.EncodeNumber, valid.Required, valid.Length(1, 30)),
+		valid.Field(&req.Colour, valid.Required),
+		valid.Field(&req.CattleCategoryId, valid.Required, valid.Min(1)),
+		valid.Field(&req.CattleCategoryName, valid.Required),
+		valid.Field(&req.FormulaTypeId, valid.Required, valid.Min(1)),
+		valid.Field(&req.FormulaTypeName, valid.Required),
+		valid.Field(&req.DataSourceId, valid.Required, valid.Min(1)),
+		valid.Field(&req.DataSourceName, valid.Required),
+		valid.Field(&req.Remarks, valid.Required),
+		valid.Field(&req.IsShow, valid.Required, valid.Min(1), valid.Max(2)),
+		valid.Field(&req.IsModify, valid.Required, valid.Min(1), valid.Max(2)),
+	); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := middleware.BackendOperation(c).OpsService.CreateFeedFormula(c, &req); err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+	ginutil.JSONResp(c, &operationPb.CommonOK{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.Success{Success: true},
+	})
+}
+
+func EditFeedFormula(c *gin.Context) {
+	var req operationPb.AddFeedFormulaRequest
+	if err := c.BindJSON(&req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := valid.ValidateStruct(&req,
+		valid.Field(&req.Id, valid.Required, valid.Min(1)),
+		valid.Field(&req.Name, valid.Required),
+		valid.Field(&req.EncodeNumber, valid.Required, valid.Length(1, 30)),
+		valid.Field(&req.Colour, valid.Required),
+		valid.Field(&req.CattleCategoryId, valid.Required, valid.Min(1)),
+		valid.Field(&req.CattleCategoryName, valid.Required),
+		valid.Field(&req.FormulaTypeId, valid.Required, valid.Min(1)),
+		valid.Field(&req.FormulaTypeName, valid.Required),
+		valid.Field(&req.DataSourceId, valid.Required, valid.Min(1)),
+		valid.Field(&req.DataSourceName, valid.Required),
+		valid.Field(&req.Remarks, valid.Required),
+		valid.Field(&req.IsShow, valid.Required, valid.Min(1), valid.Max(2)),
+		valid.Field(&req.IsModify, valid.Required, valid.Min(1), valid.Max(2)),
+	); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := middleware.BackendOperation(c).OpsService.EditFeedFormula(c, &req); err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+	ginutil.JSONResp(c, &operationPb.CommonOK{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.Success{Success: true},
+	})
+}
+
+func SearchFeedFormulaList(c *gin.Context) {
+	req := &operationPb.SearchFeedFormulaRequest{}
+	if err := ginutil.BindProto(c, req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	req.Pagination = &operationPb.PaginationModel{
+		Page:       int32(c.GetInt(middleware.Page)),
+		PageSize:   int32(c.GetInt(middleware.PageSize)),
+		PageOffset: int32(c.GetInt(middleware.PageOffset)),
+	}
+
+	res, err := middleware.BackendOperation(c).OpsService.SearchFeedFormulaList(c, req)
+	if err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+	ginutil.JSONResp(c, res)
+}
+
+func DeleteFeedFormula(c *gin.Context) {
+	feedFormulaIdStr := c.Param("feed_formula_id")
+	feedFormulaId, _ := strconv.Atoi(feedFormulaIdStr)
+
+	if err := valid.Validate(feedFormulaId, valid.Required, valid.Min(1)); err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+
+	if err := middleware.BackendOperation(c).OpsService.DeleteFeedFormula(c, int64(feedFormulaId)); err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+	ginutil.JSONResp(c, &operationPb.CommonOK{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.Success{Success: true},
+	})
+}
+
+func IsShowModifyFeedFormula(c *gin.Context) {
+	var req operationPb.IsShowModifyFeedFormula
+	if err := c.BindJSON(&req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := valid.ValidateStruct(&req,
+		valid.Field(&req.FeedFormulaId, valid.Required, valid.Min(1)),
+		valid.Field(&req.IsShow, valid.Required, valid.Min(1), valid.Max(2)),
+	); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := middleware.BackendOperation(c).OpsService.IsShowFeedFormula(c, &req); err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+	ginutil.JSONResp(c, &operationPb.CommonOK{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.Success{Success: true},
+	})
+}
+
+func ExcelImportFeedFormula(c *gin.Context) {
+	file, err := func(c *gin.Context) (multipart.File, error) {
+		if c.ContentType() != "multipart/form-data" {
+			return nil, xerr.Custom("invalid Content-Type")
+		}
+		if c.Request.Body == nil || int(c.Request.ContentLength) <= 0 {
+			return nil, xerr.Custom("invalid body")
+		}
+
+		file, fileHeader, err := c.Request.FormFile("file")
+		if err != nil {
+			return nil, xerr.WithStack(err)
+		}
+
+		fileSuffix := path.Ext(path.Base(fileHeader.Filename))
+		if fileSuffix != ".xlsx" {
+			return nil, xerr.Custom("invalid file suffix")
+		}
+
+		return file, nil
+	}(c)
+
+	if err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err = middleware.BackendOperation(c).OpsService.ExcelImportFeedFormula(c, file); err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+	ginutil.JSONResp(c, &operationPb.CommonOK{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.Success{Success: true},
+	})
+}
+
+func ExcelExportFeedFormula(c *gin.Context) {
+	req := &operationPb.SearchFeedFormulaRequest{}
+	if err := c.BindJSON(req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	req.Pagination = &operationPb.PaginationModel{
+		Page:       int32(c.GetInt(middleware.Page)),
+		PageSize:   int32(c.GetInt(middleware.PageSize)),
+		PageOffset: int32(c.GetInt(middleware.PageOffset)),
+	}
+
+	buffer, err := middleware.BackendOperation(c).OpsService.ExcelExportFeedFormula(c, req)
+	if err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+
+	c.Header("Content-Type", "application/octet-stream")
+	c.Header("Content-Disposition", "attachment; filename="+(fmt.Sprintf("饲料表 %s.xlsx", time.Now().Format("200601021504"))))
+	if _, err = c.Writer.Write(buffer.Bytes()); err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+
+	ginutil.JSONResp(c, &operationPb.CommonOK{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.Success{Success: true},
+	})
+}
+
+func ExcelTemplateFeedFormula(c *gin.Context) {
+	buffer, err := middleware.BackendOperation(c).OpsService.ExcelTemplateFeedFormula(c)
+	if err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+
+	c.Header("Content-Type", "application/octet-stream")
+	c.Header("Content-Disposition", "attachment; filename="+(fmt.Sprintf("饲料表 %s.xlsx", time.Now().Format("200601021504"))))
+	if _, err = c.Writer.Write(buffer.Bytes()); err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+
+	ginutil.JSONResp(c, &operationPb.CommonOK{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.Success{Success: true},
+	})
+}

+ 33 - 0
http/handler/mobile/mobile_list.go

@@ -0,0 +1,33 @@
+package mobile
+
+import (
+	"kpt-tmr-group/http/middleware"
+	"kpt-tmr-group/pkg/apierr"
+	"kpt-tmr-group/pkg/ginutil"
+	operationPb "kpt-tmr-group/proto/go/backend/operation"
+	"net/http"
+
+	"github.com/gin-gonic/gin"
+)
+
+func SearchMobileList(c *gin.Context) {
+	var req operationPb.SearchMobileRequest
+	if err := ginutil.BindProto(c, &req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	req.Pagination = &operationPb.PaginationModel{
+		Page:       int32(c.GetInt(middleware.Page)),
+		PageSize:   int32(c.GetInt(middleware.PageSize)),
+		PageOffset: int32(c.GetInt(middleware.PageOffset)),
+	}
+
+	res, err := middleware.BackendOperation(c).OpsService.SearchMobileList(c, &req)
+	if err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+
+	ginutil.JSONResp(c, res)
+}

+ 278 - 0
http/handler/pasture/cattle_forage_category.go

@@ -0,0 +1,278 @@
+package pasture
+
+import (
+	"kpt-tmr-group/http/middleware"
+	"kpt-tmr-group/pkg/apierr"
+	"kpt-tmr-group/pkg/ginutil"
+	"kpt-tmr-group/pkg/valid"
+	operationPb "kpt-tmr-group/proto/go/backend/operation"
+	"net/http"
+	"strconv"
+
+	"github.com/gin-gonic/gin"
+)
+
+/*// ParentCattleCategoryList 牲畜父类列表
+func ParentCattleCategoryList(c *gin.Context) {
+	res := middleware.BackendOperation(c).OpsService.ParentCattleCategoryList(c)
+	c.JSON(http.StatusOK, apiok.CommonResponse(res))
+}
+*/
+
+func AddCattleCategory(c *gin.Context) {
+	var req operationPb.AddCattleCategoryRequest
+	if err := ginutil.BindProto(c, &req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := valid.ValidateStruct(&req,
+		valid.Field(&req.Name, valid.Required),
+		valid.Field(&req.ParentId, valid.Required, valid.Min(1), valid.Max(6)),
+		valid.Field(&req.ParentName, valid.Required),
+		valid.Field(&req.Number, valid.Required),
+	); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := middleware.BackendOperation(c).OpsService.AddCattleCategory(c, &req); err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+	ginutil.JSONResp(c, &operationPb.CommonOK{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.Success{Success: true},
+	})
+}
+
+func EditCattleCategory(c *gin.Context) {
+	var req operationPb.AddCattleCategoryRequest
+	if err := ginutil.BindProto(c, &req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := valid.ValidateStruct(&req,
+		valid.Field(&req.Id, valid.Required),
+		valid.Field(&req.Name, valid.Required),
+		valid.Field(&req.ParentId, valid.Required, valid.Min(1), valid.Max(6)),
+		valid.Field(&req.ParentName, valid.Required),
+		valid.Field(&req.Number, valid.Required),
+	); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := middleware.BackendOperation(c).OpsService.EditCattleCategory(c, &req); err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+	ginutil.JSONResp(c, &operationPb.CommonOK{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.Success{Success: true},
+	})
+}
+
+func IsShowCattleCategory(c *gin.Context) {
+	var req operationPb.IsShowCattleCategory
+	if err := ginutil.BindProto(c, &req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+	if err := valid.ValidateStruct(&req,
+		valid.Field(&req.CattleCategoryId, valid.Required, valid.Min(1)),
+		valid.Field(&req.IsShow, valid.Required, valid.Min(1), valid.Max(2)),
+	); err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+
+	if err := middleware.BackendOperation(c).OpsService.IsShowCattleCategory(c, &req); err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+
+	ginutil.JSONResp(c, &operationPb.CommonOK{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.Success{Success: true},
+	})
+}
+
+func DeleteCattleCategory(c *gin.Context) {
+	cattleCategoryIdStr := c.Param("cattle_category_id")
+	cattleCategoryId, _ := strconv.Atoi(cattleCategoryIdStr)
+
+	if err := valid.Validate(cattleCategoryId, valid.Required, valid.Min(1)); err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+
+	if err := middleware.BackendOperation(c).OpsService.DeleteCattleCategory(c, int64(cattleCategoryId)); err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+
+	ginutil.JSONResp(c, &operationPb.CommonOK{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.Success{Success: true},
+	})
+}
+
+func SearchCattleCategory(c *gin.Context) {
+	req := &operationPb.SearchCattleCategoryRequest{}
+	if err := ginutil.BindProto(c, req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	req.Pagination = &operationPb.PaginationModel{
+		Page:       int32(c.GetInt(middleware.Page)),
+		PageSize:   int32(c.GetInt(middleware.PageSize)),
+		PageOffset: int32(c.GetInt(middleware.PageOffset)),
+	}
+
+	res, err := middleware.BackendOperation(c).OpsService.SearchCattleCategoryList(c, req)
+	if err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+
+	ginutil.JSONResp(c, res)
+}
+
+/*// ParentForageCategoryList 饲料父类列表
+func ParentForageCategoryList(c *gin.Context) {
+	res := middleware.BackendOperation(c).OpsService.ParentForageCategoryList(c)
+	c.JSON(http.StatusOK, apiok.CommonResponse(res))
+}*/
+
+func AddForageCategory(c *gin.Context) {
+	var req operationPb.AddForageCategoryRequest
+	if err := ginutil.BindProto(c, &req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := valid.ValidateStruct(&req,
+		valid.Field(&req.Name, valid.Required),
+		valid.Field(&req.ParentId, valid.Required, valid.Min(1), valid.Max(6)),
+		valid.Field(&req.ParentName, valid.Required),
+		valid.Field(&req.Number, valid.Required),
+	); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := middleware.BackendOperation(c).OpsService.AddForageCategory(c, &req); err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+	ginutil.JSONResp(c, &operationPb.CommonOK{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.Success{Success: true},
+	})
+}
+
+func EditForageCategory(c *gin.Context) {
+	var req operationPb.AddForageCategoryRequest
+	if err := ginutil.BindProto(c, &req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := valid.ValidateStruct(&req,
+		valid.Field(&req.Id, valid.Required),
+		valid.Field(&req.Name, valid.Required),
+		valid.Field(&req.ParentId, valid.Required, valid.Min(1), valid.Max(6)),
+		valid.Field(&req.ParentName, valid.Required),
+		valid.Field(&req.Number, valid.Required),
+	); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := middleware.BackendOperation(c).OpsService.EditForageCategory(c, &req); err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+	ginutil.JSONResp(c, &operationPb.CommonOK{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.Success{Success: true},
+	})
+}
+
+func IsShowForageCategory(c *gin.Context) {
+	var req operationPb.IsShowForageCategory
+	if err := ginutil.BindProto(c, &req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+	if err := valid.ValidateStruct(&req,
+		valid.Field(&req.ForageCategoryId, valid.Required, valid.Min(1)),
+		valid.Field(&req.IsShow, valid.Required, valid.Min(1), valid.Max(2)),
+	); err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+
+	if err := middleware.BackendOperation(c).OpsService.IsShowForageCategory(c, &req); err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+
+	ginutil.JSONResp(c, &operationPb.CommonOK{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.Success{Success: true},
+	})
+}
+
+func DeleteForageCategory(c *gin.Context) {
+	forageCategoryIdStr := c.Param("forage_category_id")
+	forageCategoryId, _ := strconv.Atoi(forageCategoryIdStr)
+
+	if err := valid.Validate(forageCategoryId, valid.Required, valid.Min(1)); err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+
+	if err := middleware.BackendOperation(c).OpsService.DeleteForageCategory(c, int64(forageCategoryId)); err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+
+	ginutil.JSONResp(c, &operationPb.CommonOK{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.Success{Success: true},
+	})
+}
+
+func SearchForageCategory(c *gin.Context) {
+	req := &operationPb.SearchForageCategoryRequest{}
+	if err := c.BindJSON(req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	req.Pagination = &operationPb.PaginationModel{
+		Page:       int32(c.GetInt(middleware.Page)),
+		PageSize:   int32(c.GetInt(middleware.PageSize)),
+		PageOffset: int32(c.GetInt(middleware.PageOffset)),
+	}
+
+	res, err := middleware.BackendOperation(c).OpsService.SearchForageCategoryList(c, req)
+	if err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+
+	ginutil.JSONResp(c, res)
+}

+ 250 - 0
http/handler/pasture/forage_list.go

@@ -0,0 +1,250 @@
+package pasture
+
+import (
+	"fmt"
+	"kpt-tmr-group/http/middleware"
+	"kpt-tmr-group/pkg/apierr"
+	"kpt-tmr-group/pkg/ginutil"
+	"kpt-tmr-group/pkg/valid"
+	"kpt-tmr-group/pkg/xerr"
+	operationPb "kpt-tmr-group/proto/go/backend/operation"
+	"mime/multipart"
+	"net/http"
+	"path"
+	"time"
+
+	"github.com/gin-gonic/gin"
+)
+
+func AddForage(c *gin.Context) {
+	var req operationPb.AddForageRequest
+	if err := ginutil.BindProto(c, &req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := valid.ValidateStruct(&req,
+		valid.Field(&req.Name, valid.Required),
+		valid.Field(&req.CategoryId, valid.Required),
+		valid.Field(&req.UniqueEncode, valid.Required),
+		valid.Field(&req.ForageSourceId, valid.Required),
+		valid.Field(&req.ForageSourceName, valid.Required),
+		//valid.Field(&req.PlanTypeId, valid.Required, valid.Min(0), valid.Max(2)),
+		valid.Field(&req.PlanTypeName, valid.Required),
+		//valid.Field(&req.JumpWeight, valid.Required, valid.Min(0), valid.Max(50)),
+	); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := middleware.BackendOperation(c).OpsService.CreateForage(c, &req); err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+	ginutil.JSONResp(c, &operationPb.CommonOK{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.Success{Success: true},
+	})
+}
+
+func EditForage(c *gin.Context) {
+	var req operationPb.AddForageRequest
+	if err := ginutil.BindProto(c, &req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := valid.ValidateStruct(&req,
+		valid.Field(&req.Id, valid.Required, valid.Min(1)),
+		valid.Field(&req.Name, valid.Required),
+		valid.Field(&req.CategoryId, valid.Required),
+		valid.Field(&req.CategoryName, valid.Required),
+		valid.Field(&req.UniqueEncode, valid.Required),
+		valid.Field(&req.ForageSourceId, valid.Required),
+	); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := middleware.BackendOperation(c).OpsService.EditForage(c, &req); err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+	ginutil.JSONResp(c, &operationPb.CommonOK{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.Success{Success: true},
+	})
+}
+
+func SearchForageList(c *gin.Context) {
+	req := &operationPb.SearchForageListRequest{}
+	if err := ginutil.BindProto(c, req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	req.Pagination = &operationPb.PaginationModel{
+		Page:       int32(c.GetInt(middleware.Page)),
+		PageSize:   int32(c.GetInt(middleware.PageSize)),
+		PageOffset: int32(c.GetInt(middleware.PageOffset)),
+	}
+
+	res, err := middleware.BackendOperation(c).OpsService.SearchForageList(c, req)
+	if err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+	ginutil.JSONResp(c, res)
+}
+
+// SearchForageEnumList 饲料列表公共枚举
+func SearchForageEnumList(c *gin.Context) {
+	res := middleware.BackendOperation(c).OpsService.ForageEnumList(c)
+	ginutil.JSONResp(c, res)
+}
+
+func DeleteForageList(c *gin.Context) {
+	ids := make([]int64, 0)
+	if err := c.BindJSON(&ids); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if len(ids) <= 0 {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, xerr.Custom("参数错误"))
+		return
+	}
+
+	if err := middleware.BackendOperation(c).OpsService.DeleteForageList(c, ids); err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+	ginutil.JSONResp(c, &operationPb.CommonOK{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.Success{Success: true},
+	})
+}
+
+func IsShowForage(c *gin.Context) {
+	var req operationPb.IsShowForage
+	if err := ginutil.BindProto(c, &req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := valid.ValidateStruct(&req,
+		valid.Field(&req.ForageId, valid.Required, valid.Min(1)),
+		valid.Field(&req.IsShow, valid.Required, valid.Min(1), valid.Max(2)),
+	); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := middleware.BackendOperation(c).OpsService.IsShowForage(c, &req); err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+	ginutil.JSONResp(c, &operationPb.CommonOK{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.Success{Success: true},
+	})
+}
+
+func ExcelImportForage(c *gin.Context) {
+
+	file, err := func(c *gin.Context) (multipart.File, error) {
+		if c.ContentType() != "multipart/form-data" {
+			return nil, xerr.Custom("invalid Content-Type")
+		}
+		if c.Request.Body == nil || int(c.Request.ContentLength) <= 0 {
+			return nil, xerr.Custom("invalid body")
+		}
+
+		file, fileHeader, err := c.Request.FormFile("file")
+		if err != nil {
+			return nil, xerr.WithStack(err)
+		}
+
+		fileSuffix := path.Ext(path.Base(fileHeader.Filename))
+		if fileSuffix != ".xlsx" {
+			return nil, xerr.Custom("invalid file suffix")
+		}
+
+		return file, nil
+	}(c)
+
+	if err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err = middleware.BackendOperation(c).OpsService.ExcelImportForage(c, file); err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+	ginutil.JSONResp(c, &operationPb.CommonOK{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.Success{Success: true},
+	})
+}
+
+func ExcelExportForage(c *gin.Context) {
+
+	req := &operationPb.SearchForageListRequest{}
+	if err := ginutil.BindProto(c, req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	req.Pagination = &operationPb.PaginationModel{
+		Page:       int32(c.GetInt(middleware.Page)),
+		PageSize:   int32(c.GetInt(middleware.PageSize)),
+		PageOffset: int32(c.GetInt(middleware.PageOffset)),
+	}
+
+	buffer, err := middleware.BackendOperation(c).OpsService.ExcelExportForage(c, req)
+	if err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+
+	c.Header("Content-Type", "application/octet-stream")
+	c.Header("Content-Disposition", "attachment; filename="+(fmt.Sprintf("饲料表 %s.xlsx", time.Now().Format("200601021504"))))
+	if _, err = c.Writer.Write(buffer.Bytes()); err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+
+	ginutil.JSONResp(c, &operationPb.CommonOK{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.Success{Success: true},
+	})
+}
+
+func ExcelTemplateForage(c *gin.Context) {
+
+	buffer, err := middleware.BackendOperation(c).OpsService.ExcelTemplateForage(c)
+	if err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+
+	c.Header("Content-Type", "application/octet-stream")
+	c.Header("Content-Disposition", "attachment; filename="+(fmt.Sprintf("饲料表 %s.xlsx", time.Now().Format("200601021504"))))
+	if _, err = c.Writer.Write(buffer.Bytes()); err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+
+	ginutil.JSONResp(c, &operationPb.CommonOK{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.Success{Success: true},
+	})
+}

+ 0 - 39
http/handler/pasture/pasture.go

@@ -1,39 +0,0 @@
-package pasture
-
-import (
-	"kpt-tmr-group/http/middleware"
-	"kpt-tmr-group/pkg/apierr"
-	"kpt-tmr-group/pkg/apiok"
-	operationPb "kpt-tmr-group/proto/go/backend/operation"
-	"net/http"
-
-	"github.com/gin-gonic/gin"
-)
-
-func AddSystemPasture(c *gin.Context) {
-	var req operationPb.AddPastureRequest
-	if err := c.BindJSON(&req); err != nil {
-		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
-		return
-	}
-	if err := middleware.BackendOperation(c).OpsService.CreatePasture(c, &req); err != nil {
-		apierr.ClassifiedAbort(c, err)
-		return
-	}
-	c.JSON(http.StatusOK, apiok.CommonResponse(apiok.NewApiOk(true)))
-}
-
-func SearchPastureList(c *gin.Context) {
-	req := &operationPb.SearchPastureRequest{}
-	if err := c.BindJSON(req); err != nil {
-		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
-		return
-	}
-	res, err := middleware.BackendOperation(c).OpsService.SearchPastureList(c, req)
-	if err != nil {
-		apierr.ClassifiedAbort(c, err)
-		return
-	}
-
-	c.JSON(http.StatusOK, apiok.CommonResponse(res.ToPB()))
-}

+ 161 - 0
http/handler/pasture/pasture_list.go

@@ -0,0 +1,161 @@
+package pasture
+
+import (
+	"kpt-tmr-group/http/middleware"
+	"kpt-tmr-group/pkg/apierr"
+	"kpt-tmr-group/pkg/ginutil"
+	"kpt-tmr-group/pkg/valid"
+	operationPb "kpt-tmr-group/proto/go/backend/operation"
+	"net/http"
+	"strconv"
+
+	"github.com/gin-gonic/gin"
+)
+
+func AddGroupPasture(c *gin.Context) {
+	var req operationPb.AddPastureRequest
+	if err := ginutil.BindProto(c, &req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := valid.ValidateStruct(&req,
+		valid.Field(&req.Name, valid.Required),
+		valid.Field(&req.ManagerUser, valid.Required),
+		valid.Field(&req.ManagerPhone, valid.Required),
+		valid.Field(&req.Account, valid.Required),
+		valid.Field(&req.Address, valid.Required),
+	); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := middleware.BackendOperation(c).OpsService.CreateGroupPasture(c, &req); err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+	ginutil.JSONResp(c, &operationPb.CommonOK{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.Success{Success: true},
+	})
+}
+
+func EditGroupPasture(c *gin.Context) {
+	var req operationPb.AddPastureRequest
+	if err := ginutil.BindProto(c, &req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := valid.ValidateStruct(&req,
+		valid.Field(&req.Id, valid.Required, valid.Min(1)),
+		valid.Field(&req.Name, valid.Required),
+		valid.Field(&req.ManagerUser, valid.Required),
+		valid.Field(&req.ManagerPhone, valid.Required),
+		valid.Field(&req.Account, valid.Required),
+		valid.Field(&req.Address, valid.Required),
+	); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := middleware.BackendOperation(c).OpsService.EditGroupPasture(c, &req); err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+	ginutil.JSONResp(c, &operationPb.CommonOK{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.Success{Success: true},
+	})
+}
+
+func SearchGroupPastureList(c *gin.Context) {
+	req := &operationPb.SearchPastureRequest{}
+	if err := c.BindJSON(req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	req.Pagination = &operationPb.PaginationModel{
+		Page:       int32(c.GetInt(middleware.Page)),
+		PageSize:   int32(c.GetInt(middleware.PageSize)),
+		PageOffset: int32(c.GetInt(middleware.PageOffset)),
+	}
+
+	res, err := middleware.BackendOperation(c).OpsService.SearchGroupPastureList(c, req)
+	if err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+	ginutil.JSONResp(c, res)
+}
+
+func DeleteGroupPasture(c *gin.Context) {
+	pastureIdStr := c.Param("pasture_id")
+	pastureId, _ := strconv.Atoi(pastureIdStr)
+
+	if err := valid.Validate(pastureId, valid.Required, valid.Min(1)); err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+
+	if err := middleware.BackendOperation(c).OpsService.DeleteGroupPasture(c, int64(pastureId)); err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+
+	ginutil.JSONResp(c, &operationPb.CommonOK{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.Success{Success: true},
+	})
+}
+
+func ResetPasswordGroupPasture(c *gin.Context) {
+	pastureIdStr := c.Param("pasture_id")
+	pastureId, _ := strconv.Atoi(pastureIdStr)
+
+	if err := valid.Validate(pastureId, valid.Required, valid.Min(1)); err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+
+	if err := middleware.BackendOperation(c).OpsService.ResetPasswordGroupPasture(c, int64(pastureId)); err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+
+	ginutil.JSONResp(c, &operationPb.CommonOK{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.Success{Success: true},
+	})
+}
+
+func IsShowGroupPasture(c *gin.Context) {
+	var req operationPb.IsShowGroupPasture
+	if err := ginutil.BindProto(c, &req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+	if err := valid.ValidateStruct(&req,
+		valid.Field(&req.PastureId, valid.Required, valid.Min(1)),
+		valid.Field(&req.IsShow, valid.Required, valid.Min(1), valid.Max(2)),
+	); err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+
+	if err := middleware.BackendOperation(c).OpsService.IsShowGroupPasture(c, &req); err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+
+	ginutil.JSONResp(c, &operationPb.CommonOK{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.Success{Success: true},
+	})
+}

+ 59 - 0
http/handler/statistic/analysis.go

@@ -0,0 +1,59 @@
+package statistic
+
+import (
+	"kpt-tmr-group/http/middleware"
+	"kpt-tmr-group/pkg/apierr"
+	"kpt-tmr-group/pkg/ginutil"
+	"kpt-tmr-group/pkg/valid"
+	operationPb "kpt-tmr-group/proto/go/backend/operation"
+	"net/http"
+
+	"github.com/gin-gonic/gin"
+)
+
+// SearchFormulaEstimateList 配方评估
+func SearchFormulaEstimateList(c *gin.Context) {
+	req := &operationPb.SearchFormulaEstimateRequest{}
+	if err := c.BindJSON(req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	req.Pagination = &operationPb.PaginationModel{
+		Page:       int32(c.GetInt(middleware.Page)),
+		PageSize:   int32(c.GetInt(middleware.PageSize)),
+		PageOffset: int32(c.GetInt(middleware.PageOffset)),
+	}
+
+	res, err := middleware.BackendOperation(c).OpsService.SearchFormulaEstimateList(c, req)
+	if err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+	ginutil.JSONResp(c, res)
+}
+
+// AnalysisAccuracy 准备性分析
+func AnalysisAccuracy(c *gin.Context) {
+	var req operationPb.SearchAnalysisAccuracyRequest
+	if err := c.BindJSON(&req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := valid.ValidateStruct(&req,
+		valid.Field(&req.PastureIds, valid.Required),
+		valid.Field(&req.CattleParentCategoryId, valid.Required),
+	); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	res, err := middleware.BackendOperation(c).OpsService.SearchAnalysisAccuracy(c, &req)
+	if err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+	c.JSON(http.StatusOK, res)
+	//ginutil.JSONResp(c, res)
+}

+ 27 - 13
http/handler/system/menu.go

@@ -3,7 +3,7 @@ package system
 import (
 	"kpt-tmr-group/http/middleware"
 	"kpt-tmr-group/pkg/apierr"
-	"kpt-tmr-group/pkg/apiok"
+	"kpt-tmr-group/pkg/ginutil"
 	"kpt-tmr-group/pkg/valid"
 	operationPb "kpt-tmr-group/proto/go/backend/operation"
 	"net/http"
@@ -15,7 +15,7 @@ import (
 // AddSystemMenu 添加系统菜单权限
 func AddSystemMenu(c *gin.Context) {
 	var req operationPb.AddMenuRequest
-	if err := c.BindJSON(&req); err != nil {
+	if err := ginutil.BindProto(c, &req); err != nil {
 		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
 		return
 	}
@@ -43,13 +43,17 @@ func AddSystemMenu(c *gin.Context) {
 		return
 	}
 
-	c.JSON(http.StatusOK, apiok.CommonResponse(apiok.NewApiOk(true)))
+	ginutil.JSONResp(c, &operationPb.CommonOK{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.Success{Success: true},
+	})
 }
 
 // EditSystemMenu 编辑系统菜单权限
 func EditSystemMenu(c *gin.Context) {
 	var req operationPb.AddMenuRequest
-	if err := c.BindJSON(&req); err != nil {
+	if err := ginutil.BindProto(c, &req); err != nil {
 		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
 		return
 	}
@@ -57,7 +61,6 @@ func EditSystemMenu(c *gin.Context) {
 	if err := valid.ValidateStruct(&req,
 		valid.Field(&req.Id, valid.Required, valid.Min(1)),
 		valid.Field(&req.Name, valid.Required),
-		valid.Field(&req.ParentId, valid.Required, valid.Min(0)),
 		valid.Field(&req.MenuType, valid.Required, valid.Min(0), valid.Max(2)),
 		valid.Field(&req.Path, valid.Required),
 	); err != nil {
@@ -76,14 +79,17 @@ func EditSystemMenu(c *gin.Context) {
 		apierr.ClassifiedAbort(c, err)
 		return
 	}
-
-	c.JSON(http.StatusOK, apiok.CommonResponse(apiok.NewApiOk(true)))
+	ginutil.JSONResp(c, &operationPb.CommonOK{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.Success{Success: true},
+	})
 }
 
 // IsShowSystemMenu 是否启动
 func IsShowSystemMenu(c *gin.Context) {
 	var req operationPb.IsShowSystemMenuRequest
-	if err := c.BindJSON(&req); err != nil {
+	if err := ginutil.BindProto(c, &req); err != nil {
 		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
 		return
 	}
@@ -100,13 +106,17 @@ func IsShowSystemMenu(c *gin.Context) {
 		apierr.ClassifiedAbort(c, err)
 		return
 	}
-	c.JSON(http.StatusOK, apiok.CommonResponse(apiok.NewApiOk(true)))
+	ginutil.JSONResp(c, &operationPb.CommonOK{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.Success{Success: true},
+	})
 }
 
 // SearchSystemMenuList 菜单列表查询
 func SearchSystemMenuList(c *gin.Context) {
 	var req operationPb.SearchMenuRequest
-	if err := c.BindJSON(&req); err != nil {
+	if err := ginutil.BindProto(c, &req); err != nil {
 		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
 		return
 	}
@@ -122,7 +132,7 @@ func SearchSystemMenuList(c *gin.Context) {
 		apierr.ClassifiedAbort(c, err)
 		return
 	}
-	c.JSON(http.StatusOK, apiok.CommonResponse(res))
+	ginutil.JSONResp(c, res)
 }
 
 // DeleteSystemMenu 删除菜单
@@ -135,10 +145,14 @@ func DeleteSystemMenu(c *gin.Context) {
 		return
 	}
 
-	if err := middleware.BackendOperation(c).OpsService.DeleteSystemRole(c, int64(menuId)); err != nil {
+	if err := middleware.BackendOperation(c).OpsService.DeleteSystemMenu(c, int64(menuId)); err != nil {
 		apierr.ClassifiedAbort(c, err)
 		return
 	}
 
-	c.JSON(http.StatusOK, apiok.CommonResponse(apiok.NewApiOk(true)))
+	ginutil.JSONResp(c, &operationPb.CommonOK{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.Success{Success: true},
+	})
 }

+ 39 - 8
http/handler/system/role.go

@@ -3,7 +3,7 @@ package system
 import (
 	"kpt-tmr-group/http/middleware"
 	"kpt-tmr-group/pkg/apierr"
-	"kpt-tmr-group/pkg/apiok"
+	"kpt-tmr-group/pkg/ginutil"
 	"kpt-tmr-group/pkg/valid"
 	operationPb "kpt-tmr-group/proto/go/backend/operation"
 	"net/http"
@@ -15,7 +15,7 @@ import (
 // AddSystemRole 添加角色
 func AddSystemRole(c *gin.Context) {
 	var req operationPb.AddRoleRequest
-	if err := c.BindJSON(&req); err != nil {
+	if err := ginutil.BindProto(c, &req); err != nil {
 		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
 		return
 	}
@@ -34,13 +34,17 @@ func AddSystemRole(c *gin.Context) {
 		apierr.ClassifiedAbort(c, err)
 		return
 	}
-	c.JSON(http.StatusOK, apiok.CommonResponse(apiok.NewApiOk(true)))
+	ginutil.JSONResp(c, &operationPb.CommonOK{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.Success{Success: true},
+	})
 }
 
 // EditSystemRole 编辑角色
 func EditSystemRole(c *gin.Context) {
 	var req operationPb.AddRoleRequest
-	if err := c.BindJSON(&req); err != nil {
+	if err := ginutil.BindProto(c, &req); err != nil {
 		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
 		return
 	}
@@ -59,7 +63,30 @@ func EditSystemRole(c *gin.Context) {
 		apierr.ClassifiedAbort(c, err)
 		return
 	}
-	c.JSON(http.StatusOK, apiok.CommonResponse(apiok.NewApiOk(true)))
+	ginutil.JSONResp(c, &operationPb.CommonOK{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.Success{Success: true},
+	})
+}
+
+// GetRolePermissions 获取角色对应的权限
+func GetRolePermissions(c *gin.Context) {
+	roleIdStr := c.Param("role_id")
+	roleId, _ := strconv.Atoi(roleIdStr)
+
+	if err := valid.Validate(roleId, valid.Required, valid.Min(1)); err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+
+	res, err := middleware.BackendOperation(c).OpsService.GetRolePermissions(c, int64(roleId))
+	if err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+
+	ginutil.JSONResp(c, res)
 }
 
 // DeleteSystemRole 删除角色
@@ -77,13 +104,17 @@ func DeleteSystemRole(c *gin.Context) {
 		return
 	}
 
-	c.JSON(http.StatusOK, apiok.CommonResponse(apiok.NewApiOk(true)))
+	ginutil.JSONResp(c, &operationPb.CommonOK{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.Success{Success: true},
+	})
 }
 
 // SearchSystemRoleList 角色列表
 func SearchSystemRoleList(c *gin.Context) {
 	var req operationPb.SearchRoleRequest
-	if err := c.BindJSON(&req); err != nil {
+	if err := ginutil.BindProto(c, &req); err != nil {
 		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
 		return
 	}
@@ -99,5 +130,5 @@ func SearchSystemRoleList(c *gin.Context) {
 		apierr.ClassifiedAbort(c, err)
 		return
 	}
-	c.JSON(http.StatusOK, apiok.CommonResponse(res))
+	ginutil.JSONResp(c, res)
 }

+ 95 - 15
http/handler/system/user.go

@@ -3,7 +3,7 @@ package system
 import (
 	"kpt-tmr-group/http/middleware"
 	"kpt-tmr-group/pkg/apierr"
-	"kpt-tmr-group/pkg/apiok"
+	"kpt-tmr-group/pkg/ginutil"
 	"kpt-tmr-group/pkg/valid"
 	operationPb "kpt-tmr-group/proto/go/backend/operation"
 	"net/http"
@@ -14,8 +14,8 @@ import (
 
 // Auth 用户登录
 func Auth(c *gin.Context) {
-	var req operationPb.UserAuth
-	if err := c.BindJSON(&req); err != nil {
+	var req operationPb.UserAuthData
+	if err := ginutil.BindProto(c, &req); err != nil {
 		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
 		return
 	}
@@ -33,13 +33,28 @@ func Auth(c *gin.Context) {
 		apierr.ClassifiedAbort(c, err)
 		return
 	}
-	c.JSON(http.StatusOK, apiok.CommonResponse(res))
+	ginutil.JSONResp(c, res)
+}
+
+func GetWxAppletOpenId(c *gin.Context) {
+	jsCode := c.Param("js_code")
+	if err := valid.Validate(jsCode, valid.Required, valid.Length(1, 100)); err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+	res, err := middleware.BackendOperation(c).OpsService.GetOpenId(c, jsCode)
+	if err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+
+	ginutil.JSONResp(c, res)
 }
 
 // AddSystemUser 创建系统用户
 func AddSystemUser(c *gin.Context) {
 	var req operationPb.AddSystemUser
-	if err := c.BindJSON(&req); err != nil {
+	if err := ginutil.BindProto(c, &req); err != nil {
 		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
 		return
 	}
@@ -60,7 +75,30 @@ func AddSystemUser(c *gin.Context) {
 		apierr.ClassifiedAbort(c, err)
 		return
 	}
-	c.JSON(http.StatusOK, apiok.CommonResponse(apiok.NewApiOk(true)))
+	ginutil.JSONResp(c, &operationPb.CommonOK{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.Success{Success: true},
+	})
+}
+
+// DetailsSystemUser 系统用户详情
+func DetailsSystemUser(c *gin.Context) {
+	userIdStr := c.Param("user_id")
+	userId, _ := strconv.Atoi(userIdStr)
+
+	if err := valid.Validate(userId, valid.Required, valid.Min(1)); err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+
+	res, err := middleware.BackendOperation(c).OpsService.DetailsSystemUser(c, int64(userId))
+	if err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+
+	ginutil.JSONResp(c, res)
 }
 
 // GetUserInfo 获取用户信息
@@ -75,13 +113,13 @@ func GetUserInfo(c *gin.Context) {
 		apierr.ClassifiedAbort(c, err)
 		return
 	}
-	c.JSON(http.StatusOK, apiok.CommonResponse(res))
+	ginutil.JSONResp(c, res)
 }
 
 // SearchSystemUserList 查询系统用户列表
 func SearchSystemUserList(c *gin.Context) {
 	var req operationPb.SearchUserRequest
-	if err := c.BindJSON(&req); err != nil {
+	if err := ginutil.BindProto(c, &req); err != nil {
 		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
 		return
 	}
@@ -104,13 +142,13 @@ func SearchSystemUserList(c *gin.Context) {
 		apierr.ClassifiedAbort(c, err)
 		return
 	}
-	c.JSON(http.StatusOK, apiok.CommonResponse(res))
+	ginutil.JSONResp(c, res)
 }
 
 // EditSystemUser 编辑系统用户
 func EditSystemUser(c *gin.Context) {
 	var req operationPb.AddSystemUser
-	if err := c.BindJSON(&req); err != nil {
+	if err := ginutil.BindProto(c, &req); err != nil {
 		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
 		return
 	}
@@ -131,7 +169,11 @@ func EditSystemUser(c *gin.Context) {
 		apierr.ClassifiedAbort(c, err)
 		return
 	}
-	c.JSON(http.StatusOK, apiok.CommonResponse(apiok.NewApiOk(true)))
+	ginutil.JSONResp(c, &operationPb.CommonOK{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.Success{Success: true},
+	})
 }
 
 // DeleteUser 删除系统用户
@@ -149,13 +191,17 @@ func DeleteUser(c *gin.Context) {
 		return
 	}
 
-	c.JSON(http.StatusOK, apiok.CommonResponse(apiok.NewApiOk(true)))
+	ginutil.JSONResp(c, &operationPb.CommonOK{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.Success{Success: true},
+	})
 }
 
 // IsShowSystemUser 系统用户启动开关
 func IsShowSystemUser(c *gin.Context) {
 	var req operationPb.IsShowSystemUserRequest
-	if err := c.BindJSON(&req); err != nil {
+	if err := ginutil.BindProto(c, &req); err != nil {
 		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
 		return
 	}
@@ -172,7 +218,11 @@ func IsShowSystemUser(c *gin.Context) {
 		apierr.ClassifiedAbort(c, err)
 		return
 	}
-	c.JSON(http.StatusOK, apiok.CommonResponse(apiok.NewApiOk(true)))
+	ginutil.JSONResp(c, &operationPb.CommonOK{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.Success{Success: true},
+	})
 }
 
 // GetSystemUserPermissions 获取系统用户菜单权限
@@ -188,6 +238,36 @@ func GetSystemUserPermissions(c *gin.Context) {
 		apierr.ClassifiedAbort(c, err)
 		return
 	}
+	ginutil.JSONResp(c, res)
+}
+
+// ResetPasswordSystemUser 用户密码重置
+func ResetPasswordSystemUser(c *gin.Context) {
+	userIdStr := c.Param("user_id")
+	userId, _ := strconv.Atoi(userIdStr)
+
+	if err := valid.Validate(userId, valid.Required, valid.Min(1)); err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+
+	if err := middleware.BackendOperation(c).OpsService.ResetPasswordSystemUser(c, int64(userId)); err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+
+	ginutil.JSONResp(c, &operationPb.CommonOK{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.Success{Success: true},
+	})
+}
 
-	c.JSON(http.StatusOK, apiok.CommonResponse(res))
+// LogoutSystemUser 用户登出,
+func LogoutSystemUser(c *gin.Context) {
+	ginutil.JSONResp(c, &operationPb.CommonOK{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.Success{Success: true},
+	})
 }

+ 1 - 0
http/middleware/cors.go

@@ -36,6 +36,7 @@ func CORS(configs ...cors.Config) gin.HandlerFunc {
 		//允许类型校验
 		if method == "OPTIONS" {
 			c.JSON(http.StatusOK, "ok!")
+			return
 		}
 
 		defer func() {

+ 3 - 3
http/middleware/pagination.go

@@ -9,8 +9,8 @@ import (
 
 const (
 	Page       = "page"
-	PageSize   = "pageSize"
-	PageOffset = "pageOffset"
+	PageSize   = "page_size"
+	PageOffset = "page_offset"
 )
 
 // Pagination sets page, pageSize and pageOffset to *gin.Context
@@ -25,7 +25,7 @@ func Pagination() gin.HandlerFunc {
 
 func getSetItem(c *gin.Context, k string, d int) int {
 	var n int
-	if v := c.Request.Header.Get(k); v != "" {
+	if v := c.Query(k); v != "" {
 		if i, err := strconv.Atoi(v); err == nil {
 			if i > 0 {
 				n = i

+ 9 - 2
http/middleware/sso.go

@@ -15,6 +15,7 @@ const (
 	Authorization = "Authorization"
 	ToKenPrefix   = "Bearer "
 	UserName      = "userName"
+	XRequestId    = "X-Request-Id"
 )
 
 func GetToken(c *gin.Context) string {
@@ -25,6 +26,11 @@ func GetToken(c *gin.Context) string {
 	return ""
 }
 
+func GetXRequestId(c *gin.Context) string {
+	item := c.Request.Header.Get(XRequestId)
+	return item
+}
+
 func unauthorized(c *gin.Context) {
 	c.AbortWithStatusJSON(http.StatusBadRequest, apierr.WithContext(c, commonPb.Error_UNAUTHORIZED))
 }
@@ -35,16 +41,17 @@ func RequireAdmin() gin.HandlerFunc {
 		token := GetToken(c)
 		if token == "" {
 			unauthorized(c)
-			return
+			c.Abort()
 		}
 
 		claims, err := jwt.ParseToken(token)
 		if err != nil || claims == nil || claims.Username == "" {
 			unauthorized(c)
-			return
+			c.Abort()
 		}
 
 		c.Set(UserName, claims.Username)
+		c.Set(XRequestId, GetXRequestId(c))
 		c.Next()
 	}
 }

+ 1 - 0
http/route/api_debug_route.go

@@ -15,6 +15,7 @@ func DebugAPI(opts ...func(engine *gin.Engine)) func(s *gin.Engine) {
 
 		// Not Found
 		s.NoRoute(handler.Handle404)
+		s.GET("/debug/test", debug.HelloOk)
 		debugRoute := authRouteGroup(s, "/api/v1/kpt/debug/")
 		// kpt debug api
 		debugRoute.GET("hello", debug.HelloOk)

+ 62 - 3
http/route/app_api.go

@@ -2,7 +2,10 @@ package route
 
 import (
 	"kpt-tmr-group/http/handler"
+	"kpt-tmr-group/http/handler/feed"
+	"kpt-tmr-group/http/handler/mobile"
 	"kpt-tmr-group/http/handler/pasture"
+	"kpt-tmr-group/http/handler/statistic"
 	"kpt-tmr-group/http/handler/system"
 	"kpt-tmr-group/http/middleware"
 
@@ -20,20 +23,25 @@ func AppAPI(opts ...func(engine *gin.Engine)) func(s *gin.Engine) {
 		s.GET("/check", handler.Health)
 
 		s.POST("/auth", system.Auth)
+		s.GET("/wx_applet/openid/:js_code", system.GetWxAppletOpenId)
 
 		// system API 组
 		// 系统用户
 		systemRoute := authRouteGroup(s, "/api/v1/system/")
 		systemRoute.POST("/user_info", system.GetUserInfo)
 		systemRoute.POST("/user/add", system.AddSystemUser)
+		systemRoute.GET("/user/details/:user_id", system.DetailsSystemUser)
 		systemRoute.POST("/user/list", system.SearchSystemUserList)
 		systemRoute.POST("/user/edit", system.EditSystemUser)
 		systemRoute.POST("/user/is_show", system.IsShowSystemUser)
 		systemRoute.DELETE("/user/:user_id", system.DeleteUser)
 		systemRoute.POST("/user/permissions", system.GetSystemUserPermissions)
+		systemRoute.POST("/user/rest_password/:user_id", system.ResetPasswordSystemUser)
+		systemRoute.POST("/user/logout", system.LogoutSystemUser)
 
 		// 系统角色
 		systemRoute.POST("/role/add", system.AddSystemRole)
+		systemRoute.GET("/role/permissions/:role_id", system.GetRolePermissions)
 		systemRoute.POST("/role/edit", system.EditSystemRole)
 		systemRoute.DELETE("/role/:role_id", system.DeleteSystemRole)
 		systemRoute.POST("/role/list", system.SearchSystemRoleList)
@@ -45,16 +53,67 @@ func AppAPI(opts ...func(engine *gin.Engine)) func(s *gin.Engine) {
 		systemRoute.POST("/menu/list", system.SearchSystemMenuList)
 		systemRoute.DELETE("/menu/:menu_id", system.DeleteSystemMenu)
 
+		// 移动端
+		systemRoute.POST("/mobile/list", mobile.SearchMobileList)
+
 		// 牧场管理
 		opsRoute := authRouteGroup(s, "/api/v1/ops/")
-		opsRoute.POST("/pasture/add", pasture.AddSystemPasture)
-		opsRoute.POST("/pasture/list", pasture.SearchPastureList)
+		opsRoute.POST("/pasture/add", pasture.AddGroupPasture)
+		opsRoute.POST("/pasture/edit", pasture.EditGroupPasture)
+		opsRoute.POST("/pasture/list", pasture.SearchGroupPastureList)
+		opsRoute.DELETE("/pasture/:pasture_id", pasture.DeleteGroupPasture)
+		opsRoute.POST("/pasture/rest_password/:pasture_id", pasture.ResetPasswordGroupPasture)
+		opsRoute.POST("/pasture/is_show", pasture.IsShowGroupPasture)
+
+		// 牲牧类型
+		// opsRoute.GET("/cattle/category/parent_list", pasture.ParentCattleCategoryList)
+		opsRoute.POST("/cattle/category/add", pasture.AddCattleCategory)
+		opsRoute.POST("/cattle/category/edit", pasture.EditCattleCategory)
+		opsRoute.POST("/cattle/category/is_show", pasture.IsShowCattleCategory)
+		opsRoute.DELETE("/cattle/category/:cattle_category_id", pasture.DeleteCattleCategory)
+		opsRoute.POST("/cattle/category/list", pasture.SearchCattleCategory)
+
+		// 饲料类别
+		// opsRoute.GET("/forage/category/parent_list", pasture.ParentForageCategoryList)
+		opsRoute.POST("/forage/category/add", pasture.AddForageCategory)
+		opsRoute.POST("/forage/category/edit", pasture.EditForageCategory)
+		opsRoute.POST("/forage/category/is_show", pasture.IsShowForageCategory)
+		opsRoute.DELETE("/forage/category/:forage_category_id", pasture.DeleteForageCategory)
+		opsRoute.POST("/forage/category/list", pasture.SearchForageCategory)
+
+		// 饲料列表
+		opsRoute.POST("/forage/add", pasture.AddForage)
+		opsRoute.POST("/forage/edit", pasture.EditForage)
+		opsRoute.POST("/forage/list", pasture.SearchForageList)
+		opsRoute.POST("forage/delete", pasture.DeleteForageList)
+		opsRoute.POST("forage/is_show", pasture.IsShowForage)
+		opsRoute.POST("forage/excel_import", pasture.ExcelImportForage)
+		opsRoute.POST("forage/excel_export", pasture.ExcelExportForage)
+		opsRoute.POST("forage/excel_template", pasture.ExcelTemplateForage)
+		opsRoute.GET("/forage/enum/list", pasture.SearchForageEnumList)
+
+		// 饲料配方
+		opsRoute.POST("/feed_formula/add", feed.AddFeedFormula)
+		opsRoute.POST("/feed_formula/edit", feed.EditFeedFormula)
+		opsRoute.POST("/feed_formula/list", feed.SearchFeedFormulaList)
+		opsRoute.DELETE("/feed_formula/delete/:feed_formula_id", feed.DeleteFeedFormula)
+		opsRoute.POST("/feed_formula/is_modify_show", feed.IsShowModifyFeedFormula)
+		opsRoute.POST("/feed_formula/excel_export", feed.ExcelExportFeedFormula)
+		opsRoute.POST("/feed_formula/excel_import", feed.ExcelImportFeedFormula)
+		opsRoute.POST("/feed_formula/excel_template", feed.ExcelTemplateFeedFormula)
+
+		//统计分析 statistic analysis
+		opsRoute.POST("/feed_estimate/list", statistic.SearchFormulaEstimateList)
+
+		opsRoute.GET("/analysis/accuracy", statistic.AnalysisAccuracy)
+
+		// 其他
 	}
 }
 
 func authRouteGroup(s *gin.Engine, relativePath string) *gin.RouterGroup {
 	group := s.Group(relativePath)
 	// 中间件鉴权
-	group.Use(middleware.RequireAdmin(), middleware.Pagination(), middleware.CORS())
+	group.Use(middleware.RequireAdmin(), middleware.CORS())
 	return group
 }

+ 70 - 0
http/util/httptt/http.go

@@ -0,0 +1,70 @@
+package httptt
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"testing"
+
+	"kpt-tmr-group/pkg/jsonpb"
+
+	"github.com/golang/protobuf/proto"
+	"github.com/stretchr/testify/require"
+)
+
+func NewRequest(t *testing.T, method, path string, body proto.Message) *http.Request {
+	var (
+		req *http.Request
+		err error
+	)
+	switch method {
+	case http.MethodGet, http.MethodDelete:
+		var url string
+		if body != nil {
+			v := MarshalQueryPB(body)
+			query := v.Encode()
+			url = fmt.Sprintf("%s?%s", path, query)
+		} else {
+			url = path
+		}
+		req, err = http.NewRequest(method, url, nil)
+		require.NoError(t, err)
+	default:
+		if body != nil {
+			bs, err := jsonpb.MarshalBytes(body)
+			require.NoError(t, err)
+			req, err = http.NewRequest(method, path, bytes.NewReader(bs))
+			require.NoError(t, err)
+		} else {
+			req, err = http.NewRequest(method, path, nil)
+			require.NoError(t, err)
+		}
+	}
+	return req
+}
+
+func NewRequest2(t *testing.T, method, path string, body interface{}) *http.Request {
+	var (
+		req *http.Request
+		err error
+	)
+	switch method {
+	case http.MethodGet:
+		var url string
+		url = path
+		req, err = http.NewRequest(http.MethodGet, url, nil)
+		require.NoError(t, err)
+	default:
+		if body != nil {
+			bs, err := json.Marshal(body)
+			require.NoError(t, err)
+			req, err = http.NewRequest(method, path, bytes.NewReader(bs))
+			require.NoError(t, err)
+		} else {
+			req, err = http.NewRequest(method, path, nil)
+			require.NoError(t, err)
+		}
+	}
+	return req
+}

+ 100 - 0
http/util/httptt/query_encoder.go

@@ -0,0 +1,100 @@
+package httptt
+
+import (
+	"encoding/json"
+	"fmt"
+	"math"
+	"net/url"
+	"reflect"
+	"strings"
+
+	"kpt-tmr-group/pkg/xerr"
+
+	"github.com/golang/protobuf/proto"
+)
+
+type wkt interface {
+	XXX_WellKnownType() string
+}
+
+var wktType = reflect.TypeOf((*wkt)(nil)).Elem()
+
+func MarshalQueryPB(pb proto.Message) *url.Values {
+	fields := make(url.Values)
+	target := reflect.ValueOf(pb).Elem()
+	props := proto.GetProperties(target.Type())
+	for i := 0; i < target.NumField(); i++ {
+		ft := target.Type().Field(i)
+
+		if strings.HasPrefix(ft.Name, "XXX_") || ft.Anonymous {
+			continue
+		}
+		// unexported field
+		if strings.ToUpper(ft.Name[:1]) != ft.Name[:1] {
+			continue
+		}
+
+		if err := setFields(&fields, target.Field(i), props.Prop[i]); err != nil {
+			panic(err)
+		}
+	}
+	return &fields
+}
+
+func setFields(fields *url.Values, target reflect.Value, prop *proto.Properties) error {
+	if target.Kind() == reflect.Slice && target.Type().Elem().Kind() != reflect.Uint8 {
+		for i := 0; i < target.Len(); i++ {
+			if err := setFields(fields, target.Index(i), prop); err != nil {
+				return xerr.WithStack(err)
+			}
+		}
+		return nil
+	}
+	// Handle well-known types.
+	// Most are handled up in marshalObject (because 99% are messages).
+	if target.Type().Implements(wktType) {
+		wkt := target.Interface().(wkt)
+		switch wkt.XXX_WellKnownType() {
+		case "NullValue":
+			appendFields(fields, prop, "null")
+			return nil
+		}
+	}
+
+	if prop.Enum != "" {
+		appendFields(fields, prop, target.Interface().(fmt.Stringer).String())
+		return nil
+	}
+
+	if target.Kind() == reflect.Float32 || target.Kind() == reflect.Float64 {
+		f := target.Float()
+		var sval string
+		switch {
+		case math.IsInf(f, 1):
+			sval = `"Infinity"`
+		case math.IsInf(f, -1):
+			sval = `"-Infinity"`
+		case math.IsNaN(f):
+			sval = `"NaN"`
+		}
+		if sval != "" {
+			appendFields(fields, prop, sval)
+			return nil
+		}
+	}
+
+	b, err := json.Marshal(target.Interface())
+	if err != nil {
+		return xerr.WithStack(err)
+	}
+	appendFields(fields, prop, string(b))
+	return nil
+}
+
+func appendFields(v *url.Values, prop *proto.Properties, value string) {
+	key := prop.JSONName
+	if key == "" {
+		key = prop.OrigName
+	}
+	v.Add(key, value)
+}

+ 0 - 3
logger/zap-2023-05-06.log

@@ -1,3 +0,0 @@
-{"L":"ERROR","T":"2023-05-06T16:51:38.918+0800","M":"MustNewStore","Err":"Error 1045 (28000): Access denied for user 'root'@'172.19.0.1' (using password: NO)","KptEventDSNRW":"root@tcp(127.0.0.1:3306)/kpt_tmr_group?charset=utf8mb4&parseTime=true&loc=Local&allowNativePasswords=true&timeout=300s&readTimeout=300s&writeTimeout=300s","func":"kptstore.MustNewStore","file":"D:/project/golangNew/kpt-tmr-group/store/kptstore/rw_store.go","line":53}
-{"L":"ERROR","T":"2023-05-06T16:54:30.471+0800","M":"MustNewStore","Err":"Error 1049 (42000): Unknown database 'kpt_tmr-group'","KptEventDSNRW":"root:123456@tcp(192.168.1.70:3306)/kpt_tmr-group?charset=utf8mb4&parseTime=true&loc=Local&allowNativePasswords=true&timeout=300s&readTimeout=300s&writeTimeout=300s","func":"kptstore.MustNewStore","file":"D:/project/golangNew/kpt-tmr-group/store/kptstore/rw_store.go","line":53}
-{"L":"ERROR","T":"2023-05-06T16:54:56.679+0800","M":"MustNewStore","Err":"Error 1049 (42000): Unknown database 'kpt_tmr-group'","KptEventDSNRW":"root:123456@tcp(127.0.0.1:3306)/kpt_tmr-group?charset=utf8mb4&parseTime=true&loc=Local&allowNativePasswords=true&timeout=300s&readTimeout=300s&writeTimeout=300s","func":"kptstore.MustNewStore","file":"D:/project/golangNew/kpt-tmr-group/store/kptstore/rw_store.go","line":53}

+ 78 - 0
model/analysis_accuracy.go

@@ -0,0 +1,78 @@
+package model
+
+import (
+	operationPb "kpt-tmr-group/proto/go/backend/operation"
+)
+
+type AnalysisAccuracy struct {
+	Id                       int64                                 `json:"id"`
+	PastureId                int64                                 `json:"pasture_id"`
+	PastureName              string                                `json:"pasture_name"`
+	FeedFormulaId            int64                                 `json:"feed_formula_id"`
+	FeedFormulaName          string                                `json:"feed_formula_name"`
+	CattleParentCategoryId   operationPb.CattleCategoryParent_Kind `json:"cattle_parent_category_id"`
+	CattleParentCategoryName string                                `json:"cattle_parent_category_name"`
+	IWeight                  int64                                 `json:"iweight"`
+	LWeight                  int64                                 `json:"lweight"`
+	OWeight                  int64                                 `json:"oweight"`
+	ActualWeightMinus        int64                                 `json:"actual_weight_minus"`
+	AllowRatio               int64                                 `json:"allow_ratio"`
+	Alweight                 int64                                 `json:"alweight"`
+	DateDay                  string                                `json:"date_day"`
+	CreatedAt                int64                                 `json:"created_at"`
+	UpdateAt                 int64                                 `json:"update_at"`
+}
+
+func (c *AnalysisAccuracy) TableName() string {
+	return "analysis_accuracy"
+}
+
+type OptionsAnalysisAccuracy struct {
+	PastureId            int64  `json:"pasture_id"`
+	PastureName          string `json:"pasture_name"`
+	AllIweight           int64  `json:"all_iweight"`
+	AllLweight           int64  `json:"all_lweight"`
+	AllOweight           int64  `json:"all_oweight"`
+	AllActualWeightMinus int64  `json:"all_actual_weight_minus"`
+	AllAllowRatio        int64  `json:"all_allow_ratio"`
+	AllAlweight          int64  `json:"all_alweight"`
+	DateDay              string `json:"date_day"`
+}
+
+type SearchAnalysisAccuracyResponse struct {
+	Code int32                 `json:"code"`
+	Msg  string                `json:"msg"`
+	Data *AnalysisAccuracyData `json:"data"`
+}
+
+type AnalysisAccuracyData struct {
+	Chart *Chart `json:"chart"`
+	Table *Table `json:"table"`
+}
+
+type Table struct {
+	TitleList []*TableList   `json:"title_list"`
+	DataList  []*interface{} `json:"data_list"`
+}
+
+type TableList struct {
+	Name  string `json:"name"`
+	Value string `json:"value"`
+}
+
+type Chart struct {
+	MixedFodderAccurateRatio    *CommonValueRatio `json:"mixed_fodder_accurate_ratio"`
+	MixedFodderCorrectRatio     *CommonValueRatio `json:"mixed_fodder_correct_ratio"`
+	SprinkleFodderAccurateRatio *CommonValueRatio `json:"sprinkle_fodder_accurate_ratio"`
+	SprinkleFodderCorrectRatio  *CommonValueRatio `json:"sprinkle_fodder_correct_ratio"`
+}
+
+type CommonValueRatio struct {
+	MaxValue    string     `json:"max_value"`    // 最高值
+	MiddleValue string     `json:"middle_value"` // 中位值
+	MinValue    string     `json:"min_value"`    // 最低值
+	DataList    [][]string `json:"data_list"`    // 数据集合
+	PastureName []string   `json:"pasture_name"` // 牧场名称集合
+	PastureIds  []int32    `json:"pasture_ids"`
+	DateDay     []string   `json:"date_day"` // 日期集合
+}

+ 70 - 0
model/cattle_category.go

@@ -0,0 +1,70 @@
+package model
+
+import (
+	operationPb "kpt-tmr-group/proto/go/backend/operation"
+	"time"
+)
+
+type CattleCategory struct {
+	Id          int64                                 `json:"id"`
+	ParentId    operationPb.CattleCategoryParent_Kind `json:"parent_id"`
+	ParentName  string                                `json:"parent_name"`
+	PastureId   int64                                 `json:"pasture_id"`
+	PastureName string                                `json:"pasture_name"`
+	Name        string                                `json:"name"`
+	Number      string                                `json:"number"`
+	IsShow      operationPb.IsShow_Kind               `json:"is_show"`
+	IsDelete    operationPb.IsShow_Kind               `json:"is_delete"`
+	DataSource  operationPb.DataSource_Kind           `json:"data_source"`
+	Remarks     string                                `json:"remarks"`
+	CreatedAt   int64                                 `json:"created_at"`
+	UpdatedAt   int64                                 `json:"updated_at"`
+}
+
+func (c *CattleCategory) TableName() string {
+	return "cattle_category"
+}
+
+func NewCattleCategory(req *operationPb.AddCattleCategoryRequest) *CattleCategory {
+	return &CattleCategory{
+		ParentId:   req.ParentId,
+		ParentName: req.ParentName,
+		Name:       req.Name,
+		Number:     req.Number,
+		IsShow:     operationPb.IsShow_OK,
+		IsDelete:   operationPb.IsShow_OK,
+		DataSource: operationPb.DataSource_BACKGROUND_ADD,
+		Remarks:    "集团新增数据",
+	}
+}
+
+type CattleCategorySlice []*CattleCategory
+
+func (c CattleCategorySlice) ToPB() []*operationPb.AddCattleCategoryRequest {
+	res := make([]*operationPb.AddCattleCategoryRequest, len(c))
+	for i, v := range c {
+		res[i] = &operationPb.AddCattleCategoryRequest{
+			Id:              uint32(v.Id),
+			Name:            v.Name,
+			ParentId:        v.ParentId,
+			ParentName:      v.ParentName,
+			Number:          v.Number,
+			IsShow:          v.IsShow,
+			CreatedAt:       uint32(v.CreatedAt),
+			CreatedAtFormat: time.Unix(v.CreatedAt, 0).Format(LayoutTime),
+		}
+	}
+	return res
+}
+
+func (c *CattleCategory) ToPb() *operationPb.AddCattleCategoryRequest {
+	return &operationPb.AddCattleCategoryRequest{
+		Id:         uint32(c.Id),
+		Name:       c.Name,
+		Number:     c.Number,
+		ParentId:   c.ParentId,
+		ParentName: c.ParentName,
+		IsShow:     c.IsShow,
+		CreatedAt:  uint32(c.CreatedAt),
+	}
+}

+ 81 - 0
model/feed_formula.go

@@ -0,0 +1,81 @@
+package model
+
+import (
+	operationPb "kpt-tmr-group/proto/go/backend/operation"
+	"time"
+)
+
+type FeedFormula struct {
+	Id                 int64                                 `json:"id"`
+	Name               string                                `json:"name"`
+	Colour             string                                `json:"colour"`
+	EncodeNumber       string                                `json:"encode_number"`
+	CattleCategoryId   operationPb.CattleCategoryParent_Kind `json:"cattle_category_id"`
+	CattleCategoryName string                                `json:"cattle_category_name"`
+	FormulaTypeId      int32                                 `json:"formula_type_id"`
+	FormulaTypeName    string                                `json:"formula_type_name"`
+	DataSourceId       operationPb.DataSource_Kind           `json:"data_source_id"`
+	DataSourceName     string                                `json:"data_source_name"`
+	Remarks            string                                `json:"remarks"`
+	Version            int64                                 `json:"version"`
+	PastureId          int64                                 `json:"pasture_id"`
+	PastureName        string                                `json:"pasture_name"`
+	IsShow             operationPb.IsShow_Kind               `json:"is_show"`
+	IsModify           operationPb.IsShow_Kind               `json:"is_modify"`
+	IsDelete           operationPb.IsShow_Kind               `json:"is_delete"`
+	CreatedAt          int64                                 `json:"created_at"`
+	UpdatedAt          int64                                 `json:"updated_at"`
+}
+
+func (f *FeedFormula) TableName() string {
+	return "feed_formula"
+}
+
+func NewFeedFormula(req *operationPb.AddFeedFormulaRequest) *FeedFormula {
+	return &FeedFormula{
+		Name:               req.Name,
+		Colour:             req.Colour,
+		EncodeNumber:       req.EncodeNumber,
+		CattleCategoryId:   req.CattleCategoryId,
+		CattleCategoryName: req.CattleCategoryName,
+		FormulaTypeId:      req.FormulaTypeId,
+		FormulaTypeName:    req.FormulaTypeName,
+		DataSourceId:       req.DataSourceId,
+		DataSourceName:     req.DataSourceName,
+		Remarks:            req.Remarks,
+		Version:            0,
+		PastureId:          0,
+		PastureName:        "",
+		IsShow:             req.IsShow,
+		IsDelete:           operationPb.IsShow_OK,
+		IsModify:           operationPb.IsShow_OK,
+	}
+}
+
+type FeedFormulaSlice []*FeedFormula
+
+func (f FeedFormulaSlice) ToPB() []*operationPb.AddFeedFormulaRequest {
+	res := make([]*operationPb.AddFeedFormulaRequest, len(f))
+	for i, v := range f {
+		res[i] = &operationPb.AddFeedFormulaRequest{
+			Id:                 int32(v.Id),
+			Name:               v.Name,
+			Colour:             v.Colour,
+			EncodeNumber:       v.EncodeNumber,
+			CattleCategoryId:   v.CattleCategoryId,
+			CattleCategoryName: v.CattleCategoryName,
+			FormulaTypeId:      v.FormulaTypeId,
+			FormulaTypeName:    v.FormulaTypeName,
+			DataSourceName:     v.DataSourceName,
+			DataSourceId:       v.DataSourceId,
+			Remarks:            v.Remarks,
+			Version:            int32(v.Version),
+			PastureName:        v.PastureName,
+			IsShow:             v.IsShow,
+			IsModify:           v.IsModify,
+			CreatedAt:          int32(v.CreatedAt),
+			CreatedAtFormat:    time.Unix(v.CreatedAt, 0).Format(LayoutTime),
+		}
+	}
+	return res
+}

+ 106 - 0
model/forage.go

@@ -0,0 +1,106 @@
+package model
+
+import (
+	operationPb "kpt-tmr-group/proto/go/backend/operation"
+	"time"
+)
+
+type Forage struct {
+	Id                 int64                           `json:"id"`
+	PastureId          int64                           `json:"pasture_id"`
+	PastureName        string                          `json:"pasture_name"`
+	Name               string                          `json:"name"`
+	CategoryId         int64                           `json:"category_id"`
+	CategoryName       string                          `json:"category_name"`
+	MaterialType       int64                           `json:"material_type"`
+	UniqueEncode       string                          `json:"unique_encode"`
+	ForageSourceId     operationPb.ForageSource_Kind   `json:"forage_source_id"`
+	ForageSourceName   string                          `json:"forage_source_name"`
+	PlanTypeId         operationPb.ForagePlanType_Kind `json:"plan_type_id"`
+	PlanTypeName       string                          `json:"plan_type_name"`
+	SmallMaterialScale string                          `json:"small_material_scale"`
+	AllowError         int64                           `json:"allow_error"`
+	PackageWeight      int64                           `json:"package_weight"`
+	Price              int64                           `json:"price"`
+	JumpWeight         int64                           `json:"jump_weight"`
+	JumpDelay          operationPb.JumpDelaType_Kind   `json:"jump_delay"`
+	ConfirmStart       operationPb.IsShow_Kind         `json:"confirm_start"`
+	RelayLocations     int64                           `json:"relay_locations"`
+	Jmp                operationPb.IsShow_Kind         `json:"jmp"`
+	DataSource         operationPb.DataSource_Kind     `json:"data_source"`
+	Backup1            string                          `json:"backup1"`
+	Backup2            string                          `json:"backup2"`
+	Backup3            string                          `json:"backup3"`
+	IsShow             operationPb.IsShow_Kind         `json:"is_show"`
+	IsDelete           operationPb.IsShow_Kind         `json:"is_delete"`
+	CreatedAt          int64                           `json:"created_at"`
+	UpdatedAt          int64                           `json:"updated_at"`
+}
+
+func (c *Forage) TableName() string {
+	return "forage"
+}
+
+func NewForage(req *operationPb.AddForageRequest) *Forage {
+	return &Forage{
+		Name:               req.Name,
+		CategoryId:         int64(req.CategoryId),
+		CategoryName:       req.CategoryName,
+		UniqueEncode:       req.UniqueEncode,
+		ForageSourceId:     req.ForageSourceId,
+		ForageSourceName:   req.ForageSourceName,
+		PlanTypeId:         req.PlanTypeId,
+		PlanTypeName:       req.PlanTypeName,
+		SmallMaterialScale: req.SmallMaterialScale,
+		AllowError:         int64(req.AllowError),
+		PackageWeight:      int64(req.PackageWeight),
+		Price:              int64(req.Price),
+		JumpWeight:         int64(req.JumpWeight),
+		JumpDelay:          req.JumpDelay,
+		ConfirmStart:       req.ConfirmStart,
+		RelayLocations:     int64(req.RelayLocations),
+		IsShow:             operationPb.IsShow_OK,
+		IsDelete:           operationPb.IsShow_OK,
+		DataSource:         operationPb.DataSource_BACKGROUND_ADD,
+		Jmp:                req.Jmp,
+		Backup1:            req.Backup1,
+		Backup2:            req.Backup2,
+		Backup3:            req.Backup3,
+	}
+}
+
+type ForageSlice []*Forage
+
+func (f ForageSlice) ToPB() []*operationPb.AddForageRequest {
+	res := make([]*operationPb.AddForageRequest, len(f))
+	for i, v := range f {
+		res[i] = &operationPb.AddForageRequest{
+			Id:                 int32(v.Id),
+			Name:               v.Name,
+			CategoryId:         int32(v.CategoryId),
+			CategoryName:       v.CategoryName,
+			MaterialType:       int32(v.MaterialType),
+			UniqueEncode:       v.UniqueEncode,
+			ForageSourceId:     v.ForageSourceId,
+			ForageSourceName:   v.ForageSourceName,
+			PlanTypeId:         v.PlanTypeId,
+			PlanTypeName:       v.PlanTypeName,
+			SmallMaterialScale: v.SmallMaterialScale,
+			AllowError:         int32(v.AllowError),
+			PackageWeight:      int32(v.PackageWeight),
+			Price:              int32(v.Price),
+			JumpWeight:         int32(v.JumpWeight),
+			JumpDelay:          v.JumpDelay,
+			ConfirmStart:       v.ConfirmStart,
+			RelayLocations:     int32(v.RelayLocations),
+			Jmp:                v.Jmp,
+			IsShow:             v.IsShow,
+			Backup1:            v.Backup1,
+			Backup2:            v.Backup2,
+			Backup3:            v.Backup3,
+			CreatedAt:          int32(v.CreatedAt),
+			CreatedAtFormat:    time.Unix(v.CreatedAt, 0).Format(LayoutTime),
+		}
+	}
+	return res
+}

+ 70 - 0
model/forage_category.go

@@ -0,0 +1,70 @@
+package model
+
+import (
+	operationPb "kpt-tmr-group/proto/go/backend/operation"
+	"time"
+)
+
+type ForageCategory struct {
+	Id          int64                                 `json:"id"`
+	ParentId    operationPb.ForageCategoryParent_Kind `json:"parent_id"`
+	ParentName  string                                `json:"parent_name"`
+	PastureId   int64                                 `json:"pasture_id"`
+	PastureName string                                `json:"pasture_name"`
+	Name        string                                `json:"name"`
+	Number      string                                `json:"number"`
+	IsShow      operationPb.IsShow_Kind               `json:"is_show"`
+	IsDelete    operationPb.IsShow_Kind               `json:"is_delete"`
+	DataSource  operationPb.DataSource_Kind           `json:"data_source"`
+	Remarks     string                                `json:"remarks"`
+	CreatedAt   int64                                 `json:"created_at"`
+	UpdatedAt   int64                                 `json:"updated_at"`
+}
+
+func (s *ForageCategory) TableName() string {
+	return "forage_category"
+}
+
+func NewForageCategory(req *operationPb.AddForageCategoryRequest) *ForageCategory {
+	return &ForageCategory{
+		ParentId:   req.ParentId,
+		ParentName: req.ParentName,
+		Name:       req.Name,
+		Number:     req.Number,
+		IsShow:     operationPb.IsShow_OK,
+		IsDelete:   operationPb.IsShow_OK,
+		DataSource: operationPb.DataSource_BACKGROUND_ADD,
+		Remarks:    "集团新增数据",
+	}
+}
+
+type ForageCategorySlice []*ForageCategory
+
+func (f ForageCategorySlice) ToPB() []*operationPb.AddForageCategoryRequest {
+	res := make([]*operationPb.AddForageCategoryRequest, len(f))
+	for i, v := range f {
+		res[i] = &operationPb.AddForageCategoryRequest{
+			Id:              uint32(v.Id),
+			Name:            v.Name,
+			ParentId:        v.ParentId,
+			ParentName:      v.ParentName,
+			Number:          v.Number,
+			IsShow:          v.IsShow,
+			CreatedAt:       uint32(v.CreatedAt),
+			CreatedAtFormat: time.Unix(v.CreatedAt, 0).Format(LayoutTime),
+		}
+	}
+	return res
+}
+
+func (c *ForageCategory) ToPb() *operationPb.AddForageCategoryRequest {
+	return &operationPb.AddForageCategoryRequest{
+		Id:         uint32(c.Id),
+		Name:       c.Name,
+		Number:     c.Number,
+		ParentId:   c.ParentId,
+		ParentName: c.ParentName,
+		IsShow:     c.IsShow,
+		CreatedAt:  uint32(c.CreatedAt),
+	}
+}

+ 132 - 0
model/formula_estimate.go

@@ -0,0 +1,132 @@
+package model
+
+import (
+	operationPb "kpt-tmr-group/proto/go/backend/operation"
+	"time"
+)
+
+type FormulaEstimate struct {
+	Id                   int32  `json:"id"`
+	PastureId            int32  `json:"pasture_id"`
+	PastureName          string `json:"pasture_name"`
+	BarnId               int32  `json:"barn_id"`
+	FeedFormulaId        int32  `json:"feed_formula_id"`
+	FeedFormulaName      string `json:"feed_formula_name"`
+	CowNumber            int32  `json:"cow_number"`
+	DryFormulaNumber     int32  `json:"dry_formula_number"`
+	DryTmrFeed           int32  `json:"dry_tmr_feed"`
+	DryFoodIntake        int32  `json:"dry_food_intake"`
+	MjFormulaNumber      int32  `json:"mj_formula_number"`
+	MjTmrFeed            int32  `json:"mj_tmr_feed"`
+	MjFoodIntake         int32  `json:"mj_food_intake"`
+	NndFormulaNumber     int32  `json:"nnd_formula_number"`
+	NndTmrFeed           int32  `json:"nnd_tmr_feed"`
+	NndFoodIntake        int32  `json:"nnd_food_intake"`
+	CpgFormulaNumber     int32  `json:"cpg_formula_number"`
+	CpgTmrFeed           int32  `json:"cpg_tmr_feed"`
+	CpgFoodIntake        int32  `json:"cpg_food_intake"`
+	PgFormulaNumber      int32  `json:"pg_formula_number"`
+	PgTmrFeed            int32  `json:"pg_tmr_feed"`
+	PgFoodIntake         int32  `json:"pg_food_intake"`
+	DmFormulaNumber      int32  `json:"dm_formula_number"`
+	DmTmrFeed            int32  `json:"dm_tmr_feed"`
+	DmFoodIntake         int32  `json:"dm_food_intake"`
+	CpdmFormulaNumber    int32  `json:"cpdm_formula_number"`
+	CpdmTmrFeed          int32  `json:"cpdm_tmr_feed"`
+	CpdmFoodIntake       int32  `json:"cpdm_food_intake"`
+	FatFormulaNumber     int32  `json:"fat_formula_number"`
+	FatTmrFeed           int32  `json:"fat_tmr_feed"`
+	FatFoodIntake        int32  `json:"fat_food_intake"`
+	StarchFormulaNumber  int32  `json:"starch_formula_number"`
+	StarchTmrFeed        int32  `json:"starch_tmr_feed"`
+	StarchFoodIntake     int32  `json:"starch_food_intake"`
+	NdfFormulaNumber     int32  `json:"ndf_formula_number"`
+	NdfTmrFeed           int32  `json:"ndf_tmr_feed"`
+	NdfFoodIntake        int32  `json:"ndf_food_intake"`
+	CpNdfFormulaNumber   int32  `json:"cp_ndf_formula_number"`
+	CpNdfTmrFeed         int32  `json:"cp_ndf_tmr_feed"`
+	CpNdfFoodIntake      int32  `json:"cp_ndf_food_intake"`
+	AdfFormulaNumber     int32  `json:"adf_formula_number"`
+	AdfTmrFeed           int32  `json:"adf_tmr_feed"`
+	AdfFoodIntake        int32  `json:"adf_food_intake"`
+	CalciumFormulaNumber int32  `json:"calcium_formula_number"`
+	CalciumTmrFeed       int32  `json:"calcium_tmr_feed"`
+	CalciumFoodIntake    int32  `json:"calcium_food_intake"`
+	PdmFormulaNumber     int32  `json:"pdm_formula_number"`
+	PdmTmrFeed           int32  `json:"pdm_tmr_feed"`
+	PdmFoodIntake        int32  `json:"pdm_food_intake"`
+	CfRatioFormulaNumber int32  `json:"cf_ratio_formula_number"`
+	CfRatioTmrFeed       int32  `json:"cf_ratio_tmr_feed"`
+	CfRatioFoodIntake    int32  `json:"cf_ratio_food_intake"`
+	CreatedAt            int32  `json:"created_at"`
+	UpdatedAt            int32  `json:"updated_at"`
+}
+
+func (f *FormulaEstimate) TableName() string {
+	return "formula_estimate"
+}
+
+type FormulaEstimateSlice []*FormulaEstimate
+
+func (f FormulaEstimateSlice) ToPB() []*operationPb.AddFormulaEstimateRequest {
+	res := make([]*operationPb.AddFormulaEstimateRequest, len(f))
+	for i, v := range f {
+		res[i] = &operationPb.AddFormulaEstimateRequest{
+			Id:                   v.Id,
+			PastureId:            v.PastureId,
+			PastureName:          v.PastureName,
+			BarnId:               v.BarnId,
+			FeedFormulaId:        v.FeedFormulaId,
+			FeedFormulaName:      v.FeedFormulaName,
+			CowNumber:            v.CowNumber,
+			DryFoodIntake:        v.DryFoodIntake,
+			DryFormulaNumber:     v.DryFormulaNumber,
+			DryTmrFeed:           v.DryTmrFeed,
+			MjFoodIntake:         v.MjFoodIntake,
+			MjFormulaNumber:      v.MjFormulaNumber,
+			MjTmrFeed:            v.MjTmrFeed,
+			NndFoodIntake:        v.NndFoodIntake,
+			NndFormulaNumber:     v.NndFormulaNumber,
+			NndTmrFeed:           v.NndTmrFeed,
+			CpgFoodIntake:        v.CpgFoodIntake,
+			CpgFormulaNumber:     v.CpgFormulaNumber,
+			CpgTmrFeed:           v.CpgTmrFeed,
+			PgFoodIntake:         v.PgFoodIntake,
+			PgFormulaNumber:      v.PgFormulaNumber,
+			PgTmrFeed:            v.PgTmrFeed,
+			DmFoodIntake:         v.DmFoodIntake,
+			DmFormulaNumber:      v.DmFormulaNumber,
+			DmTmrFeed:            v.DmTmrFeed,
+			CpdmFoodIntake:       v.CpdmFoodIntake,
+			CpdmFormulaNumber:    v.CpdmFormulaNumber,
+			CpdmTmrFeed:          v.CpdmTmrFeed,
+			FatFoodIntake:        v.FatFoodIntake,
+			FatFormulaNumber:     v.FatFormulaNumber,
+			FatTmrFeed:           v.FatTmrFeed,
+			StarchFoodIntake:     v.StarchFoodIntake,
+			StarchFormulaNumber:  v.StarchFormulaNumber,
+			StarchTmrFeed:        v.StarchTmrFeed,
+			NdfFoodIntake:        v.NdfFoodIntake,
+			NdfFormulaNumber:     v.NdfFormulaNumber,
+			NdfTmrFeed:           v.NdfTmrFeed,
+			CpNdfFoodIntake:      v.CpNdfFoodIntake,
+			CpNdfFormulaNumber:   v.CpNdfFormulaNumber,
+			CpNdfTmrFeed:         v.CpNdfTmrFeed,
+			AdfFoodIntake:        v.AdfFoodIntake,
+			AdfFormulaNumber:     v.AdfFormulaNumber,
+			AdfTmrFeed:           v.AdfTmrFeed,
+			CalciumFoodIntake:    v.CalciumFoodIntake,
+			CalciumFormulaNumber: v.CalciumFormulaNumber,
+			CalciumTmrFeed:       v.CalciumTmrFeed,
+			PdmFoodIntake:        v.PdmFoodIntake,
+			PdmFormulaNumber:     v.PdmFormulaNumber,
+			PdmTmrFeed:           v.PdmTmrFeed,
+			CfRatioFoodIntake:    v.CfRatioFoodIntake,
+			CfRatioFormulaNumber: v.CfRatioFormulaNumber,
+			CfRatioTmrFeed:       v.CfRatioTmrFeed,
+			CreatedAt:            v.CreatedAt,
+			CreatedAtFormat:      time.Unix(int64(v.CreatedAt), 0).Format(LayoutTime),
+		}
+	}
+	return res
+}

+ 31 - 49
model/group_pasture.go

@@ -3,18 +3,21 @@ package model
 import (
 	"kpt-tmr-group/pkg/tool"
 	operationPb "kpt-tmr-group/proto/go/backend/operation"
+	"time"
 )
 
 type GroupPasture struct {
-	Id              int64                   `json:"id,omitempty"`
-	Name            string                  `json:"name,omitempty"`
-	ManagerUser     string                  `json:"manager_user"`
-	ManagerPassword string                  `json:"manager_password"`
-	ManagerPhone    string                  `json:"manager_phone"`
-	IsShow          operationPb.IsShow_Kind `json:"is_show,omitempty"`
-	Address         string                  `json:"address"`
-	CreatedAt       int64                   `json:"created_at,omitempty"`
-	UpdatedAt       int64                   `json:"updated_at,omitempty"`
+	Id           int64                   `json:"id,omitempty"`
+	Name         string                  `json:"name,omitempty"`
+	Account      string                  `json:"account,omitempty"`
+	Password     string                  `json:"password"`
+	ManagerUser  string                  `json:"manager_user"`
+	ManagerPhone string                  `json:"manager_phone"`
+	IsShow       operationPb.IsShow_Kind `json:"is_show,omitempty"`
+	IsDelete     operationPb.IsShow_Kind `json:"is_delete,omitempty"`
+	Address      string                  `json:"address"`
+	CreatedAt    int64                   `json:"created_at,omitempty"`
+	UpdatedAt    int64                   `json:"updated_at,omitempty"`
 }
 
 func (s *GroupPasture) TableName() string {
@@ -25,12 +28,14 @@ const InitManagerPassword = "123456"
 
 func NewGroupPasture(req *operationPb.AddPastureRequest) *GroupPasture {
 	groupPasture := &GroupPasture{
-		Name:            req.Name,
-		ManagerUser:     req.ManagerUser,
-		ManagerPassword: tool.Md5String(InitManagerPassword),
-		ManagerPhone:    req.ManagerPhone,
-		IsShow:          operationPb.IsShow_OK,
-		Address:         req.Address,
+		Name:         req.Name,
+		Account:      req.Account,
+		Password:     tool.Md5String(InitManagerPassword),
+		ManagerUser:  req.ManagerUser,
+		ManagerPhone: req.ManagerPhone,
+		IsShow:       operationPb.IsShow_OK,
+		IsDelete:     operationPb.IsShow_OK,
+		Address:      req.Address,
 	}
 	return groupPasture
 }
@@ -41,24 +46,15 @@ func (g GroupPastureSlice) ToPB() []*operationPb.AddPastureRequest {
 	res := make([]*operationPb.AddPastureRequest, len(g))
 	for i, v := range g {
 		res[i] = &operationPb.AddPastureRequest{
-			Id:           v.Id,
-			Name:         v.Name,
-			ManagerUser:  v.ManagerUser,
-			ManagerPhone: v.ManagerPhone,
-			Address:      v.Address,
-			IsShow:       v.IsShow,
-			CreatedAt:    v.CreatedAt,
-		}
-	}
-	return res
-}
-
-func (g GroupPastureSlice) ToPermissionsPB() []*operationPb.AddPastureRequest {
-	res := make([]*operationPb.AddPastureRequest, len(g))
-	for i, v := range g {
-		res[i] = &operationPb.AddPastureRequest{
-			Id:   v.Id,
-			Name: v.Name,
+			Id:              int32(v.Id),
+			Name:            v.Name,
+			Account:         v.Account,
+			ManagerUser:     v.ManagerUser,
+			ManagerPhone:    v.ManagerPhone,
+			Address:         v.Address,
+			IsShow:          v.IsShow,
+			CreatedAt:       int32(v.CreatedAt),
+			CreatedAtFormat: time.Unix(v.CreatedAt, 0).Format(LayoutTime),
 		}
 	}
 	return res
@@ -66,26 +62,12 @@ func (g GroupPastureSlice) ToPermissionsPB() []*operationPb.AddPastureRequest {
 
 func (g *GroupPasture) ToPb() *operationPb.AddPastureRequest {
 	return &operationPb.AddPastureRequest{
-		Id:           g.Id,
+		Id:           int32(g.Id),
 		Name:         g.Name,
 		ManagerUser:  g.ManagerUser,
 		ManagerPhone: g.ManagerPhone,
 		Address:      g.Address,
 		IsShow:       g.IsShow,
-		CreatedAt:    g.CreatedAt,
-	}
-}
-
-type GroupPastureResponse struct {
-	Page  int32                            `json:"page"`
-	Total int32                            `json:"total"`
-	List  []*operationPb.AddPastureRequest `json:"list"`
-}
-
-func (g *GroupPastureResponse) ToPB() *operationPb.SearchPastureResponse {
-	return &operationPb.SearchPastureResponse{
-		Page:  g.Page,
-		Total: g.Total,
-		List:  g.List,
+		CreatedAt:    int32(g.CreatedAt),
 	}
 }

+ 7 - 7
model/system_pasture_permissions.go → model/system_group_pasture_permissions.go

@@ -2,7 +2,7 @@ package model
 
 import operationPb "kpt-tmr-group/proto/go/backend/operation"
 
-type SystemPasturePermissions struct {
+type SystemGroupPasturePermissions struct {
 	Id        int64                   `json:"id,omitempty"`
 	RoleId    int64                   `json:"role_id"`
 	PastureId int64                   `json:"pasture_id"`
@@ -11,16 +11,16 @@ type SystemPasturePermissions struct {
 	UpdatedAt int64                   `json:"updated_at"`
 }
 
-func (s *SystemPasturePermissions) TableName() string {
-	return "system_pasture_permissions"
+func (s *SystemGroupPasturePermissions) TableName() string {
+	return "system_group_pasture_permissions"
 }
 
-func NewSystemPasturePermissions(roleID int64, pastureIds []int64) []*SystemPasturePermissions {
-	systemPasturePermissions := make([]*SystemPasturePermissions, len(pastureIds))
+func NewSystemGroupPasturePermissions(roleID int64, pastureIds []uint32) []*SystemGroupPasturePermissions {
+	systemPasturePermissions := make([]*SystemGroupPasturePermissions, len(pastureIds))
 	for i, v := range pastureIds {
-		systemPasturePermissions[i] = &SystemPasturePermissions{
+		systemPasturePermissions[i] = &SystemGroupPasturePermissions{
 			RoleId:    roleID,
-			PastureId: v,
+			PastureId: int64(v),
 			IsShow:    operationPb.IsShow_OK,
 		}
 	}

+ 68 - 44
model/system_menu.go

@@ -5,6 +5,12 @@ import (
 	"time"
 )
 
+const (
+	Level1 = iota + 1
+	Level2
+	Level3
+)
+
 type SystemMenu struct {
 	Id        int64                   `json:"id,omitempty"`
 	Name      string                  `json:"name,omitempty"`
@@ -31,13 +37,14 @@ func NewSystemMenu(req *operationPb.AddMenuRequest) *SystemMenu {
 	return &SystemMenu{
 		Name:      req.Name,
 		MenuType:  req.MenuType,
+		Level:     req.Level,
 		Title:     req.Title,
 		Path:      req.Path,
 		Component: req.Component,
 		Icon:      req.Icon,
 		Sort:      req.Sort,
 		Redirect:  req.Redirect,
-		ParentId:  req.ParentId,
+		ParentId:  int64(req.ParentId),
 		IsShow:    operationPb.IsShow_OK,
 		IsDelete:  operationPb.IsShow_OK,
 	}
@@ -46,55 +53,72 @@ func NewSystemMenu(req *operationPb.AddMenuRequest) *SystemMenu {
 type SystemMenuSlice []*SystemMenu
 
 func (s SystemMenuSlice) ToPB() []*operationPb.AddMenuRequest {
-	res := make([]*operationPb.AddMenuRequest, len(s))
-	for i, v := range s {
-		res[i] = &operationPb.AddMenuRequest{
-			Id:             v.Id,
-			Name:           v.Name,
-			MenuType:       v.MenuType,
-			Level:          v.Level,
-			Title:          v.Title,
-			IsShow:         v.IsShow,
-			Component:      v.Component,
-			Icon:           v.Icon,
-			Sort:           v.Sort,
-			Redirect:       v.Redirect,
-			ParentId:       v.ParentId,
-			CreatedAt:      v.CreatedAt,
-			CratedAtFormat: time.Unix(v.CreatedAt, 0).Format(LayoutTime),
+	level := make(map[int32][]*operationPb.AddMenuRequest, 0)
+	for _, menu := range s {
+		if _, ok := level[menu.Level]; !ok {
+			level[menu.Level] = make([]*operationPb.AddMenuRequest, 0)
 		}
+		level[menu.Level] = append(level[menu.Level], &operationPb.AddMenuRequest{
+			Id:              int32(menu.Id),
+			Name:            menu.Name,
+			ParentId:        int32(menu.ParentId),
+			MenuType:        menu.MenuType,
+			Title:           menu.Title,
+			Path:            menu.Path,
+			IsShow:          menu.IsShow,
+			Component:       menu.Component,
+			Icon:            menu.Icon,
+			Sort:            menu.Sort,
+			Redirect:        menu.Redirect,
+			CreatedAt:       int32(menu.CreatedAt),
+			CreatedAtFormat: time.Unix(menu.CreatedAt, 0).Format(LayoutTime),
+			Level:           menu.Level,
+			Affix:           true,
+			KeepAlive:       true,
+			Children:        make([]*operationPb.AddMenuRequest, 0),
+		})
 	}
-	return res
-}
 
-func (s *SystemMenu) ToPb() *operationPb.AddMenuRequest {
-	return &operationPb.AddMenuRequest{
-		Id:        s.Id,
-		Name:      s.Name,
-		MenuType:  s.MenuType,
-		Level:     s.Level,
-		Title:     s.Title,
-		Path:      s.Path,
-		Component: s.Component,
-		Icon:      s.Icon,
-		Sort:      s.Sort,
-		Redirect:  s.Redirect,
-		ParentId:  s.ParentId,
-		IsShow:    s.IsShow,
-		CreatedAt: s.CreatedAt,
+	for _, leve3Data := range level[Level3] {
+		for _, leve2Data := range level[Level2] {
+			if leve3Data.ParentId == leve2Data.Id {
+				if leve2Data.Children == nil {
+					leve2Data.Children = make([]*operationPb.AddMenuRequest, 0)
+				}
+				leve2Data.Children = append(leve2Data.Children, leve3Data)
+			}
+		}
+	}
+
+	for _, leve2Data := range level[Level2] {
+		for _, leve1Data := range level[Level1] {
+			if leve2Data.ParentId == leve1Data.Id {
+				if leve1Data.Children == nil {
+					leve1Data.Children = make([]*operationPb.AddMenuRequest, 0)
+				}
+				leve1Data.Children = append(leve1Data.Children, leve2Data)
+			}
+		}
 	}
-}
 
-type SystemMenuResponse struct {
-	Page  int32                         `json:"page"`
-	Total int32                         `json:"total"`
-	List  []*operationPb.AddMenuRequest `json:"list"`
+	return level[Level1]
 }
 
-func (s *SystemMenuResponse) ToPB() *operationPb.SearchMenuResponse {
-	return &operationPb.SearchMenuResponse{
-		Page:  s.Page,
-		Total: s.Total,
-		List:  s.List,
+func (s *SystemMenu) ToPb() *operationPb.AddMenuRequest {
+	return &operationPb.AddMenuRequest{
+		Id:              int32(s.Id),
+		Name:            s.Name,
+		MenuType:        s.MenuType,
+		Level:           s.Level,
+		Title:           s.Title,
+		Path:            s.Path,
+		Component:       s.Component,
+		Icon:            s.Icon,
+		Sort:            s.Sort,
+		Redirect:        s.Redirect,
+		ParentId:        int32(s.ParentId),
+		IsShow:          s.IsShow,
+		CreatedAt:       int32(s.CreatedAt),
+		CreatedAtFormat: time.Unix(s.CreatedAt, 0).Format(LayoutTime),
 	}
 }

+ 2 - 2
model/system_menu_permissions.go

@@ -15,12 +15,12 @@ func (s *SystemMenuPermissions) TableName() string {
 	return "system_menu_permissions"
 }
 
-func NewSystemMenuPermissions(roleID int64, menuIds []int64) []*SystemMenuPermissions {
+func NewSystemMenuPermissions(roleID int64, menuIds []uint32) []*SystemMenuPermissions {
 	systemMenuPermissions := make([]*SystemMenuPermissions, len(menuIds))
 	for i, v := range menuIds {
 		systemMenuPermissions[i] = &SystemMenuPermissions{
 			RoleId: roleID,
-			MenuId: v,
+			MenuId: int64(v),
 			IsShow: operationPb.IsShow_OK,
 		}
 	}

+ 19 - 1
model/system_mobile.go

@@ -1,6 +1,9 @@
 package model
 
-import operationPb "kpt-tmr-group/proto/go/backend/operation"
+import (
+	operationPb "kpt-tmr-group/proto/go/backend/operation"
+	"time"
+)
 
 type SystemMobile struct {
 	Id        int64                   `json:"id,omitempty"`
@@ -13,3 +16,18 @@ type SystemMobile struct {
 func (s *SystemMobile) TableName() string {
 	return "system_mobile"
 }
+
+type SystemMobileSlice []*SystemMobile
+
+func (s SystemMobileSlice) ToPB() []*operationPb.MobileData {
+	res := make([]*operationPb.MobileData, len(s))
+	for i, v := range s {
+		res[i] = &operationPb.MobileData{
+			Id:              uint32(v.Id),
+			Name:            v.Name,
+			CreatedAt:       uint32(v.CreatedAt),
+			CreatedAtFormat: time.Unix(v.CreatedAt, 0).Format(LayoutTime),
+		}
+	}
+	return res
+}

+ 2 - 2
model/system_mobile_permissions.go

@@ -15,12 +15,12 @@ func (s *SystemMobilePermissions) TableName() string {
 	return "system_mobile_permissions"
 }
 
-func NewSystemMobilePermissions(roleID int64, mobileIds []int64) []*SystemMobilePermissions {
+func NewSystemMobilePermissions(roleID int64, mobileIds []uint32) []*SystemMobilePermissions {
 	systemMobilePermissions := make([]*SystemMobilePermissions, len(mobileIds))
 	for i, v := range mobileIds {
 		systemMobilePermissions[i] = &SystemMobilePermissions{
 			RoleId:   roleID,
-			MobileId: v,
+			MobileId: int64(v),
 			IsShow:   operationPb.IsShow_OK,
 		}
 	}

+ 13 - 26
model/system_role.go

@@ -37,13 +37,13 @@ func (s SystemRoleSlice) ToPB() []*operationPb.AddRoleRequest {
 	res := make([]*operationPb.AddRoleRequest, len(s))
 	for i, v := range s {
 		res[i] = &operationPb.AddRoleRequest{
-			Id:             v.Id,
-			Name:           v.Name,
-			Remarks:        v.Remarks,
-			CreateUser:     v.CreateUser,
-			IsShow:         v.IsShow,
-			CreatedAt:      v.CreatedAt,
-			CratedAtFormat: time.Unix(v.CreatedAt, 0).Format(LayoutTime),
+			Id:              uint32(v.Id),
+			Name:            v.Name,
+			Remarks:         v.Remarks,
+			CreateUser:      v.CreateUser,
+			IsShow:          v.IsShow,
+			CreatedAt:       uint32(v.CreatedAt),
+			CreatedAtFormat: time.Unix(v.CreatedAt, 0).Format(LayoutTime),
 		}
 	}
 	return res
@@ -51,24 +51,11 @@ func (s SystemRoleSlice) ToPB() []*operationPb.AddRoleRequest {
 
 func (s *SystemRole) ToPb() *operationPb.AddRoleRequest {
 	return &operationPb.AddRoleRequest{
-		Id:         s.Id,
-		Name:       s.Name,
-		CreateUser: s.CreateUser,
-		IsShow:     s.IsShow,
-		CreatedAt:  s.CreatedAt,
-	}
-}
-
-type SystemRoleResponse struct {
-	Page  int32                         `json:"page"`
-	Total int32                         `json:"total"`
-	List  []*operationPb.AddRoleRequest `json:"list"`
-}
-
-func (s *SystemRoleResponse) ToPB() *operationPb.SearchRoleResponse {
-	return &operationPb.SearchRoleResponse{
-		Page:  s.Page,
-		Total: s.Total,
-		List:  s.List,
+		Id:              uint32(s.Id),
+		Name:            s.Name,
+		CreateUser:      s.CreateUser,
+		IsShow:          s.IsShow,
+		CreatedAt:       uint32(s.CreatedAt),
+		CreatedAtFormat: time.Unix(s.CreatedAt, 0).Format(LayoutTime),
 	}
 }

+ 51 - 34
model/system_user.go

@@ -3,6 +3,7 @@ package model
 import (
 	"fmt"
 	operationPb "kpt-tmr-group/proto/go/backend/operation"
+	"net/http"
 	"strconv"
 	"strings"
 	"time"
@@ -26,20 +27,25 @@ func (s *SystemUser) TableName() string {
 	return "system_user"
 }
 
-func (s *SystemUser) SystemUserFormat(userRoles []*SystemRole) *operationPb.UserAuth {
+func (s *SystemUser) SystemUserFormat(userRoles []*SystemRole, pastures []*operationPb.UserPasture) *operationPb.UserAuth {
 	roles := make([]*operationPb.UserRole, len(userRoles))
 	for k, v := range userRoles {
 		roles[k] = &operationPb.UserRole{
-			Id:   v.Id,
+			Id:   int32(v.Id),
 			Name: v.Name,
 		}
 	}
 
 	return &operationPb.UserAuth{
-		UserName:     s.Name,
-		Phone:        s.Phone,
-		EmployeeName: s.EmployeeName,
-		Roles:        roles,
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.UserAuthData{
+			UserName:     s.Name,
+			Phone:        s.Phone,
+			EmployeeName: s.EmployeeName,
+			Roles:        roles,
+			Pastures:     pastures,
+		},
 	}
 }
 
@@ -68,45 +74,56 @@ func (s *SystemUser) SystemUserRoleToSlice() []int {
 
 type SystemUserSlice []*SystemUser
 
-func (s SystemUserSlice) ToPB() []*operationPb.AddSystemUser {
+func (s SystemUserSlice) ToPB(roleList []*SystemRole) []*operationPb.AddSystemUser {
 	res := make([]*operationPb.AddSystemUser, len(s))
 	for i, v := range s {
 		res[i] = &operationPb.AddSystemUser{
-			Id:             v.Id,
-			Name:           v.Name,
-			Phone:          v.Phone,
-			EmployeeName:   v.EmployeeName,
-			CreateUser:     v.CreateUser,
-			IsShow:         v.IsShow,
-			CreatedAt:      v.CreatedAt,
-			CratedAtFormat: time.Unix(v.CreatedAt, 0).Format(LayoutTime),
+			Id:              int32(v.Id),
+			Name:            v.Name,
+			Phone:           v.Phone,
+			EmployeeName:    v.EmployeeName,
+			CreateUser:      v.CreateUser,
+			IsShow:          v.IsShow,
+			CreatedAt:       int32(v.CreatedAt),
+			CreatedAtFormat: time.Unix(v.CreatedAt, 0).Format(LayoutTime),
+			RoleName:        strings.TrimRight(v.UserRoleFormat(roleList), ","),
 		}
 	}
 	return res
 }
 
-func (s *SystemUser) ToPb() *operationPb.AddSystemUser {
-	return &operationPb.AddSystemUser{
-		Id:           s.Id,
-		Name:         s.Name,
-		Phone:        s.Phone,
-		CreateUser:   s.CreateUser,
-		EmployeeName: s.EmployeeName,
-		IsShow:       s.IsShow,
-		CreatedAt:    s.CreatedAt,
+func (s *SystemUser) UserRoleFormat(roleList []*SystemRole) string {
+	ids := strings.Split(s.RoleIds, ",")
+	roleListName := ""
+	for _, id := range ids {
+		for _, r := range roleList {
+			if fmt.Sprintf("%d", r.Id) != id {
+				continue
+			}
+			roleListName += fmt.Sprintf("%s,", r.Name)
+		}
 	}
+	return roleListName
 }
 
-type SystemUserResponse struct {
-	Page  int32                        `json:"page"`
-	Total int32                        `json:"total"`
-	List  []*operationPb.AddSystemUser `json:"list"`
-}
+func (s *SystemUser) ToPb() *operationPb.AddSystemUser {
+
+	roles := make([]int32, 0)
+	roleIds := strings.Split(s.RoleIds, ",")
+	for _, roleId := range roleIds {
+		id, _ := strconv.Atoi(roleId)
+		roles = append(roles, int32(id))
+	}
 
-func (s *SystemUserResponse) ToPB() *operationPb.SearchUserResponse {
-	return &operationPb.SearchUserResponse{
-		Page:  s.Page,
-		Total: s.Total,
-		List:  s.List,
+	return &operationPb.AddSystemUser{
+		Id:              int32(s.Id),
+		Name:            s.Name,
+		Phone:           s.Phone,
+		CreateUser:      s.CreateUser,
+		EmployeeName:    s.EmployeeName,
+		IsShow:          s.IsShow,
+		CreatedAt:       int32(s.CreatedAt),
+		CreatedAtFormat: time.Unix(s.CreatedAt, 0).Format(LayoutTime),
+		RoleIds:         roles,
 	}
 }

+ 9 - 0
model/wechat.go

@@ -0,0 +1,9 @@
+package model
+
+type JsCode struct {
+	SessionKey string `json:"session_key"`
+	UnionId    string `json:"unionid"`
+	ErrMsg     string `json:"errmsg"`
+	OpenId     string `json:"openid"`
+	ErrCode    int32  `json:"errcode"`
+}

+ 288 - 0
module/backend/feed_service.go

@@ -0,0 +1,288 @@
+package backend
+
+import (
+	"bytes"
+	"context"
+	"errors"
+	"fmt"
+	"io"
+	"kpt-tmr-group/model"
+	"kpt-tmr-group/pkg/logger/zaplog"
+	"kpt-tmr-group/pkg/xerr"
+	operationPb "kpt-tmr-group/proto/go/backend/operation"
+
+	"github.com/xuri/excelize/v2"
+	"go.uber.org/zap"
+
+	"gorm.io/gorm"
+)
+
+// CreateFeedFormula 添加数据
+func (s *StoreEntry) CreateFeedFormula(ctx context.Context, req *operationPb.AddFeedFormulaRequest) error {
+	forage := model.NewFeedFormula(req)
+	if err := s.DB.Create(forage).Error; err != nil {
+		return xerr.WithStack(err)
+	}
+	return nil
+}
+
+// EditFeedFormula 编辑数据
+func (s *StoreEntry) EditFeedFormula(ctx context.Context, req *operationPb.AddFeedFormulaRequest) error {
+
+	forage := model.FeedFormula{Id: int64(req.Id)}
+	if err := s.DB.Where("is_delete = ?", operationPb.IsShow_OK).First(&forage).Error; err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			return xerr.Custom("该数据不存在")
+		}
+		return xerr.WithStack(err)
+	}
+
+	updateData := &model.FeedFormula{
+		Name:               req.Name,
+		Colour:             req.Colour,
+		CattleCategoryId:   req.CattleCategoryId,
+		CattleCategoryName: req.CattleCategoryName,
+		FormulaTypeId:      req.FormulaTypeId,
+		FormulaTypeName:    req.FormulaTypeName,
+		DataSourceId:       req.DataSourceId,
+		DataSourceName:     req.DataSourceName,
+		Remarks:            req.Remarks,
+		IsShow:             req.IsShow,
+	}
+
+	if err := s.DB.Model(new(model.FeedFormula)).
+		Omit("is_show", "is_delete", "encode_number", "formula_type_id", "formula_type_name", "data_source", "version", "is_modify").
+		Where("id = ?", req.Id).
+		Updates(updateData).Error; err != nil {
+		return xerr.WithStack(err)
+	}
+
+	return nil
+}
+
+// SearchFeedFormulaList 查询数据列表
+func (s *StoreEntry) SearchFeedFormulaList(ctx context.Context, req *operationPb.SearchFeedFormulaRequest) (*operationPb.SearchFeedFormulaListResponse, error) {
+	feedFormula := make([]*model.FeedFormula, 0)
+	var count int64 = 0
+
+	pref := s.DB.Model(new(model.FeedFormula)).Where("is_delete = ?", operationPb.IsShow_OK)
+	if req.Name != "" {
+		pref.Where("name like ?", fmt.Sprintf("%s%s%s", "%", req.Name, "%"))
+	}
+
+	if req.CattleCategoryId > 0 {
+		pref.Where("cattle_category_id = ?", req.CattleCategoryId)
+	}
+
+	if req.FormulaTypeId > 0 {
+		pref.Where("formula_type_id = ?", req.FormulaTypeId)
+	}
+
+	if req.IsShow > 0 {
+		pref.Where("is_show = ?", req.IsShow)
+	}
+
+	if req.DataSource > 0 {
+		pref.Where("data_source = ?", req.DataSource)
+	}
+
+	if req.Remarks != "" {
+		pref.Where("remarks = ?", req.Remarks)
+	}
+
+	if err := pref.Order("id desc").Count(&count).Limit(int(req.Pagination.PageSize)).Offset(int(req.Pagination.PageOffset)).
+		Find(&feedFormula).Error; err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	return &operationPb.SearchFeedFormulaListResponse{
+		Page:     req.Pagination.Page,
+		PageSize: req.Pagination.PageSize,
+		Total:    int32(count),
+		List:     model.FeedFormulaSlice(feedFormula).ToPB(),
+	}, nil
+}
+
+// IsShowFeedFormula 是否启用和是否可修改
+func (s *StoreEntry) IsShowFeedFormula(ctx context.Context, req *operationPb.IsShowModifyFeedFormula) error {
+	feedFormula := &model.FeedFormula{Id: int64(req.FeedFormulaId)}
+	if err := s.DB.First(feedFormula).Error; err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			return xerr.Custom("该数据不存在")
+		}
+		return xerr.WithStack(err)
+	}
+
+	if req.EditType == 1 {
+		if err := s.DB.Model(new(model.FeedFormula)).Where("id = ?", req.FeedFormulaId).Update("is_show", req.IsShow).Error; err != nil {
+			return xerr.WithStack(err)
+		}
+	}
+
+	if req.EditType == 2 {
+		if err := s.DB.Model(new(model.FeedFormula)).Where("id = ?", req.FeedFormulaId).Update("is_modify", req.IsShow).Error; err != nil {
+			return xerr.WithStack(err)
+		}
+	}
+	return nil
+}
+
+// DeleteFeedFormula 是否删除
+func (s *StoreEntry) DeleteFeedFormula(ctx context.Context, feedFormulaId int64) error {
+	feedFormula := &model.FeedFormula{Id: feedFormulaId}
+	if err := s.DB.First(feedFormula).Error; err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			return xerr.Custom("该数据不存在")
+		}
+		return xerr.WithStack(err)
+	}
+
+	if err := s.DB.Model(new(model.FeedFormula)).Where("id = ?", feedFormula.Id).Update("is_delete", operationPb.IsShow_NO).Error; err != nil {
+		return xerr.WithStack(err)
+	}
+	return nil
+}
+
+// ExcelImportFeedFormula 导入excel
+func (s *StoreEntry) ExcelImportFeedFormula(ctx context.Context, req io.Reader) error {
+	xlsx, err := excelize.OpenReader(req)
+	if err != nil {
+		return xerr.WithStack(err)
+	}
+	defer xlsx.Close()
+
+	rows, err := xlsx.GetRows(xlsx.GetSheetName(xlsx.GetActiveSheetIndex()))
+	if err != nil {
+		return xerr.WithStack(err)
+	}
+
+	if len(rows) > 10000 {
+		rows = rows[:10000]
+	}
+	feedFormulaList := make([]*model.FeedFormula, 0)
+	for i, row := range rows {
+		if i == 0 {
+			continue
+		}
+		var (
+			name, encodeNumber, cattleCategoryName, formulaTypeName, dataSourceName, remarks, isShowStr string
+			isShow                                                                                      operationPb.IsShow_Kind
+		)
+
+		for k, v := range row {
+			if k == 0 {
+				name = v
+			}
+			if k == 1 {
+				encodeNumber = v
+			}
+			if k == 2 {
+				cattleCategoryName = v
+			}
+			if k == 3 {
+				formulaTypeName = v
+			}
+			if k == 4 {
+				dataSourceName = v
+			}
+			if k == 5 {
+				remarks = v
+			}
+			if k == 6 {
+				isShowStr = v
+			}
+		}
+
+		if isShowStr == "是" {
+			isShow = operationPb.IsShow_OK
+		} else {
+			isShow = operationPb.IsShow_NO
+		}
+
+		feedFormulaItem := &model.FeedFormula{
+			Name:               name,
+			EncodeNumber:       encodeNumber,
+			CattleCategoryName: cattleCategoryName,
+			FormulaTypeName:    formulaTypeName,
+			Remarks:            remarks,
+			IsShow:             isShow,
+			IsDelete:           operationPb.IsShow_OK,
+			DataSourceId:       operationPb.DataSource_EXCEL_IMPORT,
+			DataSourceName:     dataSourceName,
+		}
+		feedFormulaList = append(feedFormulaList, feedFormulaItem)
+	}
+
+	if len(feedFormulaList) > 0 {
+		if err = s.DB.Create(feedFormulaList).Error; err != nil {
+			return xerr.WithStack(err)
+		}
+	}
+
+	return nil
+}
+
+// ExcelExportFeedFormula 流式导出excel
+func (s *StoreEntry) ExcelExportFeedFormula(ctx context.Context, req *operationPb.SearchFeedFormulaRequest) (*bytes.Buffer, error) {
+	res, err := s.SearchFeedFormulaList(ctx, req)
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+	if len(res.List) <= 0 {
+		return nil, xerr.Custom("数据为空")
+	}
+
+	file := excelize.NewFile()
+	defer file.Close()
+
+	streamWriter, err := file.NewStreamWriter("Sheet1")
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+	titles := []interface{}{"配方名称", "配方编码", "畜牧类别", "配方类别", "来源", "备注", "是否启用",
+		"饲料组", "饲料名称", "重量(kg)", "搅拌延迟(min)", "是否锁定牛头数比例", "顺序"}
+	if err = streamWriter.SetRow("A1", titles); err != nil {
+		return nil, xerr.WithStack(err)
+	}
+	for i, item := range res.List {
+		cell, err := excelize.CoordinatesToCellName(1, i+2)
+		if err != nil {
+			zaplog.Error("excelize.CoordinatesToCellName", zap.Any("Err", err))
+			continue
+		}
+		row := make([]interface{}, 0)
+		row = append(row, item.Name, item.EncodeNumber, item.CattleCategoryName, item.FormulaTypeName, item.DataSourceName,
+			item.Remarks, item.IsShow)
+
+		if err = streamWriter.SetRow(cell, row); err != nil {
+			return nil, xerr.WithStack(err)
+		}
+	}
+
+	if err = streamWriter.Flush(); err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	return file.WriteToBuffer()
+}
+
+// ExcelTemplateFeedFormula 导出模板
+func (s *StoreEntry) ExcelTemplateFeedFormula(ctx context.Context) (*bytes.Buffer, error) {
+	file := excelize.NewFile()
+	defer file.Close()
+
+	streamWriter, err := file.NewStreamWriter("Sheet1")
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+	titles := []interface{}{"配方名称", "配方编码", "畜牧类别", "配方类别", "来源", "备注", "是否启用",
+		"饲料组", "饲料名称", "重量(kg)", "搅拌延迟(min)", "是否锁定牛头数比例", "顺序"}
+	if err = streamWriter.SetRow("A1", titles); err != nil {
+		return nil, xerr.WithStack(err)
+	}
+	if err = streamWriter.Flush(); err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	return file.WriteToBuffer()
+}

+ 82 - 13
module/backend/interface.go

@@ -1,11 +1,14 @@
 package backend
 
 import (
+	"bytes"
 	"context"
+	"io"
 	"kpt-tmr-group/config"
 	"kpt-tmr-group/model"
 	"kpt-tmr-group/pkg/di"
 	operationPb "kpt-tmr-group/proto/go/backend/operation"
+	"kpt-tmr-group/service/wechat"
 	"kpt-tmr-group/store/kptstore"
 
 	"go.uber.org/dig"
@@ -23,36 +26,89 @@ type StoreEntry struct {
 
 	Cfg *config.AppConfig
 	DB  *kptstore.DB
-	//SSO *sso.Cache
+	// SSO *sso.Cache
 	// AsynqClient asynqsvc.Client
 	// Cache *redis.Client
+	WxClient *wechat.ClientService
 }
 
 func NewStore(store StoreEntry) KptService {
 	return &store
 }
 
+func NewStoreEntry(cfg *config.AppConfig, Db *kptstore.DB) *StoreEntry {
+	return &StoreEntry{
+		Cfg:      cfg,
+		DB:       Db,
+		WxClient: nil,
+	}
+}
+
+//go:generate mockgen -destination mock/kptservice.go -package kptservicemock kpt-tmr-group/module/backend KptService
 type KptService interface {
-	Operation
-	SystemOperation
+	PastureService   // 牧场相关操作
+	SystemService    // 系统相关操作
+	WxAppletService  // 小程序相关
+	StatisticService // 统计分析
 }
 
-type Operation interface {
+type PastureService interface {
+	// CreateGroupPasture 牧场管理相关
+	CreateGroupPasture(ctx context.Context, req *operationPb.AddPastureRequest) error
+	EditGroupPasture(ctx context.Context, req *operationPb.AddPastureRequest) error
+	SearchGroupPastureList(ctx context.Context, req *operationPb.SearchPastureRequest) (*operationPb.SearchPastureResponse, error)
+	DeleteGroupPasture(ctx context.Context, pastureId int64) error
+	ResetPasswordGroupPasture(ctx context.Context, pastureId int64) error
+	IsShowGroupPasture(ctx context.Context, req *operationPb.IsShowGroupPasture) error
 
-	// CreatePasture 牧场管理相关
-	CreatePasture(ctx context.Context, req *operationPb.AddPastureRequest) error
-	SearchPastureList(ctx context.Context, req *operationPb.SearchPastureRequest) (*model.GroupPastureResponse, error)
-}
+	// ParentCattleCategoryList 牧畜类别
+	ParentCattleCategoryList(ctx context.Context) map[operationPb.CattleCategoryParent_Kind]string
+	AddCattleCategory(ctx context.Context, req *operationPb.AddCattleCategoryRequest) error
+	EditCattleCategory(ctx context.Context, req *operationPb.AddCattleCategoryRequest) error
+	IsShowCattleCategory(ctx context.Context, req *operationPb.IsShowCattleCategory) error
+	DeleteCattleCategory(ctx context.Context, cattleCategoryId int64) error
+	SearchCattleCategoryList(ctx context.Context, req *operationPb.SearchCattleCategoryRequest) (*operationPb.SearchCattleCategoryResponse, error)
 
-type SystemOperation interface {
+	// ParentForageCategoryList 饲料类别相关
+	ParentForageCategoryList(ctx context.Context) map[operationPb.ForageCategoryParent_Kind]string
+	AddForageCategory(ctx context.Context, req *operationPb.AddForageCategoryRequest) error
+	EditForageCategory(ctx context.Context, req *operationPb.AddForageCategoryRequest) error
+	IsShowForageCategory(ctx context.Context, req *operationPb.IsShowForageCategory) error
+	DeleteForageCategory(ctx context.Context, cattleCategoryId int64) error
+	SearchForageCategoryList(ctx context.Context, req *operationPb.SearchForageCategoryRequest) (*operationPb.SearchForageCategoryResponse, error)
+
+	// CreateForage 饲料相关
+	CreateForage(ctx context.Context, req *operationPb.AddForageRequest) error
+	EditForage(ctx context.Context, req *operationPb.AddForageRequest) error
+	SearchForageList(ctx context.Context, req *operationPb.SearchForageListRequest) (*operationPb.SearchForageListResponse, error)
+	ForageEnumList(ctx context.Context) *operationPb.ForageEnumListResponse
+	DeleteForageList(ctx context.Context, ids []int64) error
+	IsShowForage(ctx context.Context, req *operationPb.IsShowForage) error
+	ExcelImportForage(ctx context.Context, req io.Reader) error
+	ExcelExportForage(ctx context.Context, req *operationPb.SearchForageListRequest) (*bytes.Buffer, error)
+	ExcelTemplateForage(ctx context.Context) (*bytes.Buffer, error)
+
+	// CreateFeedFormula 饲料配方
+	CreateFeedFormula(ctx context.Context, req *operationPb.AddFeedFormulaRequest) error
+	EditFeedFormula(ctx context.Context, req *operationPb.AddFeedFormulaRequest) error
+	SearchFeedFormulaList(ctx context.Context, req *operationPb.SearchFeedFormulaRequest) (*operationPb.SearchFeedFormulaListResponse, error)
+	IsShowFeedFormula(ctx context.Context, req *operationPb.IsShowModifyFeedFormula) error
+	DeleteFeedFormula(ctx context.Context, feedFormulaId int64) error
+	ExcelImportFeedFormula(ctx context.Context, req io.Reader) error
+	ExcelExportFeedFormula(ctx context.Context, req *operationPb.SearchFeedFormulaRequest) (*bytes.Buffer, error)
+	ExcelTemplateFeedFormula(ctx context.Context) (*bytes.Buffer, error)
+}
 
+type SystemService interface {
 	// Auth 系统用户相关
-	Auth(ctx context.Context, auth *operationPb.UserAuth) (*operationPb.SystemToken, error)
+	Auth(ctx context.Context, auth *operationPb.UserAuthData) (*operationPb.SystemToken, error)
 	GetUserInfo(ctx context.Context, token string) (*operationPb.UserAuth, error)
 	CreateSystemUser(ctx context.Context, req *operationPb.AddSystemUser) error
-	SearchSystemUserList(ctx context.Context, req *operationPb.SearchUserRequest) (*model.SystemUserResponse, error)
+	SearchSystemUserList(ctx context.Context, req *operationPb.SearchUserRequest) (*operationPb.SearchUserResponse, error)
 	EditSystemUser(ctx context.Context, req *operationPb.AddSystemUser) error
 	DeleteSystemUser(ctx context.Context, userId int64) error
+	ResetPasswordSystemUser(ctx context.Context, userId int64) error
+	DetailsSystemUser(ctx context.Context, userId int64) (*operationPb.UserDetails, error)
 	IsShowSystemUser(ctx context.Context, req *operationPb.IsShowSystemUserRequest) error
 	GetSystemUserPermissions(ctx context.Context, token string) (*operationPb.SystemUserMenuPermissions, error)
 
@@ -60,12 +116,25 @@ type SystemOperation interface {
 	CreateSystemRole(ctx context.Context, req *operationPb.AddRoleRequest) error
 	EditSystemRole(ctx context.Context, req *operationPb.AddRoleRequest) error
 	DeleteSystemRole(ctx context.Context, roleId int64) error
-	SearchSystemRoleList(ctx context.Context, req *operationPb.SearchRoleRequest) (*model.SystemRoleResponse, error)
+	GetRolePermissions(ctx context.Context, roleId int64) (*operationPb.RolePermissionsList, error)
+	SearchSystemRoleList(ctx context.Context, req *operationPb.SearchRoleRequest) (*operationPb.SearchRoleResponse, error)
 
 	// CreateSystemMenu 系统菜单权限
 	CreateSystemMenu(ctx context.Context, req *operationPb.AddMenuRequest) error
 	EditSystemMenu(ctx context.Context, req *operationPb.AddMenuRequest) error
 	IsShowSystemMenu(ctx context.Context, req *operationPb.IsShowSystemMenuRequest) error
-	SearchSystemMenuList(ctx context.Context, req *operationPb.SearchMenuRequest) (*model.SystemMenuResponse, error)
+	SearchSystemMenuList(ctx context.Context, req *operationPb.SearchMenuRequest) (*operationPb.SearchMenuResponse, error)
 	DeleteSystemMenu(ctx context.Context, menuId int64) error
+
+	// SearchMobileList 移动端
+	SearchMobileList(ctx context.Context, req *operationPb.SearchMobileRequest) (*operationPb.SearchMobileResponse, error)
+}
+
+type StatisticService interface {
+	SearchFormulaEstimateList(ctx context.Context, req *operationPb.SearchFormulaEstimateRequest) (*operationPb.SearchFormulaEstimateResponse, error)
+	SearchAnalysisAccuracy(ctx context.Context, req *operationPb.SearchAnalysisAccuracyRequest) (*model.SearchAnalysisAccuracyResponse, error)
+}
+
+type WxAppletService interface {
+	GetOpenId(ctx context.Context, jsCode string) (*operationPb.WxOpenId, error)
 }

+ 870 - 0
module/backend/mock/kptservice.go

@@ -0,0 +1,870 @@
+// Code generated by MockGen. DO NOT EDIT.
+// Source: kpt-tmr-group/module/backend (interfaces: KptService)
+
+// Package kptservicemock is a generated GoMock package.
+package kptservicemock
+
+import (
+	bytes "bytes"
+	context "context"
+	io "io"
+	operationPb "kpt-tmr-group/proto/go/backend/operation"
+	reflect "reflect"
+
+	gomock "github.com/golang/mock/gomock"
+)
+
+// MockKptService is a mock of KptService interface.
+type MockKptService struct {
+	ctrl     *gomock.Controller
+	recorder *MockKptServiceMockRecorder
+}
+
+// MockKptServiceMockRecorder is the mock recorder for MockKptService.
+type MockKptServiceMockRecorder struct {
+	mock *MockKptService
+}
+
+// NewMockKptService creates a new mock instance.
+func NewMockKptService(ctrl *gomock.Controller) *MockKptService {
+	mock := &MockKptService{ctrl: ctrl}
+	mock.recorder = &MockKptServiceMockRecorder{mock}
+	return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use.
+func (m *MockKptService) EXPECT() *MockKptServiceMockRecorder {
+	return m.recorder
+}
+
+// AddCattleCategory mocks base method.
+func (m *MockKptService) AddCattleCategory(arg0 context.Context, arg1 *operationPb.AddCattleCategoryRequest) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "AddCattleCategory", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// AddCattleCategory indicates an expected call of AddCattleCategory.
+func (mr *MockKptServiceMockRecorder) AddCattleCategory(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddCattleCategory", reflect.TypeOf((*MockKptService)(nil).AddCattleCategory), arg0, arg1)
+}
+
+// AddForageCategory mocks base method.
+func (m *MockKptService) AddForageCategory(arg0 context.Context, arg1 *operationPb.AddForageCategoryRequest) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "AddForageCategory", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// AddForageCategory indicates an expected call of AddForageCategory.
+func (mr *MockKptServiceMockRecorder) AddForageCategory(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddForageCategory", reflect.TypeOf((*MockKptService)(nil).AddForageCategory), arg0, arg1)
+}
+
+// Auth mocks base method.
+func (m *MockKptService) Auth(arg0 context.Context, arg1 *operationPb.UserAuthData) (*operationPb.SystemToken, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "Auth", arg0, arg1)
+	ret0, _ := ret[0].(*operationPb.SystemToken)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// Auth indicates an expected call of Auth.
+func (mr *MockKptServiceMockRecorder) Auth(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Auth", reflect.TypeOf((*MockKptService)(nil).Auth), arg0, arg1)
+}
+
+// CreateFeedFormula mocks base method.
+func (m *MockKptService) CreateFeedFormula(arg0 context.Context, arg1 *operationPb.AddFeedFormulaRequest) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "CreateFeedFormula", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// CreateFeedFormula indicates an expected call of CreateFeedFormula.
+func (mr *MockKptServiceMockRecorder) CreateFeedFormula(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateFeedFormula", reflect.TypeOf((*MockKptService)(nil).CreateFeedFormula), arg0, arg1)
+}
+
+// CreateForage mocks base method.
+func (m *MockKptService) CreateForage(arg0 context.Context, arg1 *operationPb.AddForageRequest) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "CreateForage", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// CreateForage indicates an expected call of CreateForage.
+func (mr *MockKptServiceMockRecorder) CreateForage(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateForage", reflect.TypeOf((*MockKptService)(nil).CreateForage), arg0, arg1)
+}
+
+// CreateGroupPasture mocks base method.
+func (m *MockKptService) CreateGroupPasture(arg0 context.Context, arg1 *operationPb.AddPastureRequest) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "CreateGroupPasture", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// CreateGroupPasture indicates an expected call of CreateGroupPasture.
+func (mr *MockKptServiceMockRecorder) CreateGroupPasture(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateGroupPasture", reflect.TypeOf((*MockKptService)(nil).CreateGroupPasture), arg0, arg1)
+}
+
+// CreateSystemMenu mocks base method.
+func (m *MockKptService) CreateSystemMenu(arg0 context.Context, arg1 *operationPb.AddMenuRequest) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "CreateSystemMenu", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// CreateSystemMenu indicates an expected call of CreateSystemMenu.
+func (mr *MockKptServiceMockRecorder) CreateSystemMenu(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateSystemMenu", reflect.TypeOf((*MockKptService)(nil).CreateSystemMenu), arg0, arg1)
+}
+
+// CreateSystemRole mocks base method.
+func (m *MockKptService) CreateSystemRole(arg0 context.Context, arg1 *operationPb.AddRoleRequest) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "CreateSystemRole", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// CreateSystemRole indicates an expected call of CreateSystemRole.
+func (mr *MockKptServiceMockRecorder) CreateSystemRole(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateSystemRole", reflect.TypeOf((*MockKptService)(nil).CreateSystemRole), arg0, arg1)
+}
+
+// CreateSystemUser mocks base method.
+func (m *MockKptService) CreateSystemUser(arg0 context.Context, arg1 *operationPb.AddSystemUser) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "CreateSystemUser", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// CreateSystemUser indicates an expected call of CreateSystemUser.
+func (mr *MockKptServiceMockRecorder) CreateSystemUser(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateSystemUser", reflect.TypeOf((*MockKptService)(nil).CreateSystemUser), arg0, arg1)
+}
+
+// DeleteCattleCategory mocks base method.
+func (m *MockKptService) DeleteCattleCategory(arg0 context.Context, arg1 int64) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "DeleteCattleCategory", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// DeleteCattleCategory indicates an expected call of DeleteCattleCategory.
+func (mr *MockKptServiceMockRecorder) DeleteCattleCategory(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteCattleCategory", reflect.TypeOf((*MockKptService)(nil).DeleteCattleCategory), arg0, arg1)
+}
+
+// DeleteFeedFormula mocks base method.
+func (m *MockKptService) DeleteFeedFormula(arg0 context.Context, arg1 int64) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "DeleteFeedFormula", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// DeleteFeedFormula indicates an expected call of DeleteFeedFormula.
+func (mr *MockKptServiceMockRecorder) DeleteFeedFormula(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteFeedFormula", reflect.TypeOf((*MockKptService)(nil).DeleteFeedFormula), arg0, arg1)
+}
+
+// DeleteForageCategory mocks base method.
+func (m *MockKptService) DeleteForageCategory(arg0 context.Context, arg1 int64) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "DeleteForageCategory", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// DeleteForageCategory indicates an expected call of DeleteForageCategory.
+func (mr *MockKptServiceMockRecorder) DeleteForageCategory(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteForageCategory", reflect.TypeOf((*MockKptService)(nil).DeleteForageCategory), arg0, arg1)
+}
+
+// DeleteForageList mocks base method.
+func (m *MockKptService) DeleteForageList(arg0 context.Context, arg1 []int64) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "DeleteForageList", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// DeleteForageList indicates an expected call of DeleteForageList.
+func (mr *MockKptServiceMockRecorder) DeleteForageList(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteForageList", reflect.TypeOf((*MockKptService)(nil).DeleteForageList), arg0, arg1)
+}
+
+// DeleteGroupPasture mocks base method.
+func (m *MockKptService) DeleteGroupPasture(arg0 context.Context, arg1 int64) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "DeleteGroupPasture", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// DeleteGroupPasture indicates an expected call of DeleteGroupPasture.
+func (mr *MockKptServiceMockRecorder) DeleteGroupPasture(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteGroupPasture", reflect.TypeOf((*MockKptService)(nil).DeleteGroupPasture), arg0, arg1)
+}
+
+// DeleteSystemMenu mocks base method.
+func (m *MockKptService) DeleteSystemMenu(arg0 context.Context, arg1 int64) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "DeleteSystemMenu", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// DeleteSystemMenu indicates an expected call of DeleteSystemMenu.
+func (mr *MockKptServiceMockRecorder) DeleteSystemMenu(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteSystemMenu", reflect.TypeOf((*MockKptService)(nil).DeleteSystemMenu), arg0, arg1)
+}
+
+// DeleteSystemRole mocks base method.
+func (m *MockKptService) DeleteSystemRole(arg0 context.Context, arg1 int64) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "DeleteSystemRole", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// DeleteSystemRole indicates an expected call of DeleteSystemRole.
+func (mr *MockKptServiceMockRecorder) DeleteSystemRole(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteSystemRole", reflect.TypeOf((*MockKptService)(nil).DeleteSystemRole), arg0, arg1)
+}
+
+// DeleteSystemUser mocks base method.
+func (m *MockKptService) DeleteSystemUser(arg0 context.Context, arg1 int64) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "DeleteSystemUser", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// DeleteSystemUser indicates an expected call of DeleteSystemUser.
+func (mr *MockKptServiceMockRecorder) DeleteSystemUser(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteSystemUser", reflect.TypeOf((*MockKptService)(nil).DeleteSystemUser), arg0, arg1)
+}
+
+// DetailsSystemUser mocks base method.
+func (m *MockKptService) DetailsSystemUser(arg0 context.Context, arg1 int64) (*operationPb.UserDetails, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "DetailsSystemUser", arg0, arg1)
+	ret0, _ := ret[0].(*operationPb.UserDetails)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// DetailsSystemUser indicates an expected call of DetailsSystemUser.
+func (mr *MockKptServiceMockRecorder) DetailsSystemUser(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DetailsSystemUser", reflect.TypeOf((*MockKptService)(nil).DetailsSystemUser), arg0, arg1)
+}
+
+// EditCattleCategory mocks base method.
+func (m *MockKptService) EditCattleCategory(arg0 context.Context, arg1 *operationPb.AddCattleCategoryRequest) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "EditCattleCategory", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// EditCattleCategory indicates an expected call of EditCattleCategory.
+func (mr *MockKptServiceMockRecorder) EditCattleCategory(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EditCattleCategory", reflect.TypeOf((*MockKptService)(nil).EditCattleCategory), arg0, arg1)
+}
+
+// EditFeedFormula mocks base method.
+func (m *MockKptService) EditFeedFormula(arg0 context.Context, arg1 *operationPb.AddFeedFormulaRequest) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "EditFeedFormula", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// EditFeedFormula indicates an expected call of EditFeedFormula.
+func (mr *MockKptServiceMockRecorder) EditFeedFormula(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EditFeedFormula", reflect.TypeOf((*MockKptService)(nil).EditFeedFormula), arg0, arg1)
+}
+
+// EditForage mocks base method.
+func (m *MockKptService) EditForage(arg0 context.Context, arg1 *operationPb.AddForageRequest) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "EditForage", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// EditForage indicates an expected call of EditForage.
+func (mr *MockKptServiceMockRecorder) EditForage(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EditForage", reflect.TypeOf((*MockKptService)(nil).EditForage), arg0, arg1)
+}
+
+// EditForageCategory mocks base method.
+func (m *MockKptService) EditForageCategory(arg0 context.Context, arg1 *operationPb.AddForageCategoryRequest) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "EditForageCategory", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// EditForageCategory indicates an expected call of EditForageCategory.
+func (mr *MockKptServiceMockRecorder) EditForageCategory(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EditForageCategory", reflect.TypeOf((*MockKptService)(nil).EditForageCategory), arg0, arg1)
+}
+
+// EditGroupPasture mocks base method.
+func (m *MockKptService) EditGroupPasture(arg0 context.Context, arg1 *operationPb.AddPastureRequest) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "EditGroupPasture", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// EditGroupPasture indicates an expected call of EditGroupPasture.
+func (mr *MockKptServiceMockRecorder) EditGroupPasture(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EditGroupPasture", reflect.TypeOf((*MockKptService)(nil).EditGroupPasture), arg0, arg1)
+}
+
+// EditSystemMenu mocks base method.
+func (m *MockKptService) EditSystemMenu(arg0 context.Context, arg1 *operationPb.AddMenuRequest) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "EditSystemMenu", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// EditSystemMenu indicates an expected call of EditSystemMenu.
+func (mr *MockKptServiceMockRecorder) EditSystemMenu(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EditSystemMenu", reflect.TypeOf((*MockKptService)(nil).EditSystemMenu), arg0, arg1)
+}
+
+// EditSystemRole mocks base method.
+func (m *MockKptService) EditSystemRole(arg0 context.Context, arg1 *operationPb.AddRoleRequest) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "EditSystemRole", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// EditSystemRole indicates an expected call of EditSystemRole.
+func (mr *MockKptServiceMockRecorder) EditSystemRole(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EditSystemRole", reflect.TypeOf((*MockKptService)(nil).EditSystemRole), arg0, arg1)
+}
+
+// EditSystemUser mocks base method.
+func (m *MockKptService) EditSystemUser(arg0 context.Context, arg1 *operationPb.AddSystemUser) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "EditSystemUser", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// EditSystemUser indicates an expected call of EditSystemUser.
+func (mr *MockKptServiceMockRecorder) EditSystemUser(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EditSystemUser", reflect.TypeOf((*MockKptService)(nil).EditSystemUser), arg0, arg1)
+}
+
+// ExcelExportFeedFormula mocks base method.
+func (m *MockKptService) ExcelExportFeedFormula(arg0 context.Context, arg1 *operationPb.SearchFeedFormulaRequest) (*bytes.Buffer, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "ExcelExportFeedFormula", arg0, arg1)
+	ret0, _ := ret[0].(*bytes.Buffer)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// ExcelExportFeedFormula indicates an expected call of ExcelExportFeedFormula.
+func (mr *MockKptServiceMockRecorder) ExcelExportFeedFormula(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExcelExportFeedFormula", reflect.TypeOf((*MockKptService)(nil).ExcelExportFeedFormula), arg0, arg1)
+}
+
+// ExcelExportForage mocks base method.
+func (m *MockKptService) ExcelExportForage(arg0 context.Context, arg1 *operationPb.SearchForageListRequest) (*bytes.Buffer, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "ExcelExportForage", arg0, arg1)
+	ret0, _ := ret[0].(*bytes.Buffer)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// ExcelExportForage indicates an expected call of ExcelExportForage.
+func (mr *MockKptServiceMockRecorder) ExcelExportForage(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExcelExportForage", reflect.TypeOf((*MockKptService)(nil).ExcelExportForage), arg0, arg1)
+}
+
+// ExcelImportFeedFormula mocks base method.
+func (m *MockKptService) ExcelImportFeedFormula(arg0 context.Context, arg1 io.Reader) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "ExcelImportFeedFormula", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// ExcelImportFeedFormula indicates an expected call of ExcelImportFeedFormula.
+func (mr *MockKptServiceMockRecorder) ExcelImportFeedFormula(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExcelImportFeedFormula", reflect.TypeOf((*MockKptService)(nil).ExcelImportFeedFormula), arg0, arg1)
+}
+
+// ExcelImportForage mocks base method.
+func (m *MockKptService) ExcelImportForage(arg0 context.Context, arg1 io.Reader) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "ExcelImportForage", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// ExcelImportForage indicates an expected call of ExcelImportForage.
+func (mr *MockKptServiceMockRecorder) ExcelImportForage(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExcelImportForage", reflect.TypeOf((*MockKptService)(nil).ExcelImportForage), arg0, arg1)
+}
+
+// ExcelTemplateFeedFormula mocks base method.
+func (m *MockKptService) ExcelTemplateFeedFormula(arg0 context.Context) (*bytes.Buffer, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "ExcelTemplateFeedFormula", arg0)
+	ret0, _ := ret[0].(*bytes.Buffer)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// ExcelTemplateFeedFormula indicates an expected call of ExcelTemplateFeedFormula.
+func (mr *MockKptServiceMockRecorder) ExcelTemplateFeedFormula(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExcelTemplateFeedFormula", reflect.TypeOf((*MockKptService)(nil).ExcelTemplateFeedFormula), arg0)
+}
+
+// ExcelTemplateForage mocks base method.
+func (m *MockKptService) ExcelTemplateForage(arg0 context.Context) (*bytes.Buffer, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "ExcelTemplateForage", arg0)
+	ret0, _ := ret[0].(*bytes.Buffer)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// ExcelTemplateForage indicates an expected call of ExcelTemplateForage.
+func (mr *MockKptServiceMockRecorder) ExcelTemplateForage(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExcelTemplateForage", reflect.TypeOf((*MockKptService)(nil).ExcelTemplateForage), arg0)
+}
+
+// ForageEnumList mocks base method.
+func (m *MockKptService) ForageEnumList(arg0 context.Context) *operationPb.ForageEnumListResponse {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "ForageEnumList", arg0)
+	ret0, _ := ret[0].(*operationPb.ForageEnumListResponse)
+	return ret0
+}
+
+// ForageEnumList indicates an expected call of ForageEnumList.
+func (mr *MockKptServiceMockRecorder) ForageEnumList(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ForageEnumList", reflect.TypeOf((*MockKptService)(nil).ForageEnumList), arg0)
+}
+
+// GetOpenId mocks base method.
+func (m *MockKptService) GetOpenId(arg0 context.Context, arg1 string) (*operationPb.WxOpenId, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetOpenId", arg0, arg1)
+	ret0, _ := ret[0].(*operationPb.WxOpenId)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// GetOpenId indicates an expected call of GetOpenId.
+func (mr *MockKptServiceMockRecorder) GetOpenId(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetOpenId", reflect.TypeOf((*MockKptService)(nil).GetOpenId), arg0, arg1)
+}
+
+// GetRolePermissions mocks base method.
+func (m *MockKptService) GetRolePermissions(arg0 context.Context, arg1 int64) (*operationPb.RolePermissionsList, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetRolePermissions", arg0, arg1)
+	ret0, _ := ret[0].(*operationPb.RolePermissionsList)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// GetRolePermissions indicates an expected call of GetRolePermissions.
+func (mr *MockKptServiceMockRecorder) GetRolePermissions(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRolePermissions", reflect.TypeOf((*MockKptService)(nil).GetRolePermissions), arg0, arg1)
+}
+
+// GetSystemUserPermissions mocks base method.
+func (m *MockKptService) GetSystemUserPermissions(arg0 context.Context, arg1 string) (*operationPb.SystemUserMenuPermissions, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetSystemUserPermissions", arg0, arg1)
+	ret0, _ := ret[0].(*operationPb.SystemUserMenuPermissions)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// GetSystemUserPermissions indicates an expected call of GetSystemUserPermissions.
+func (mr *MockKptServiceMockRecorder) GetSystemUserPermissions(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSystemUserPermissions", reflect.TypeOf((*MockKptService)(nil).GetSystemUserPermissions), arg0, arg1)
+}
+
+// GetUserInfo mocks base method.
+func (m *MockKptService) GetUserInfo(arg0 context.Context, arg1 string) (*operationPb.UserAuth, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetUserInfo", arg0, arg1)
+	ret0, _ := ret[0].(*operationPb.UserAuth)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// GetUserInfo indicates an expected call of GetUserInfo.
+func (mr *MockKptServiceMockRecorder) GetUserInfo(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserInfo", reflect.TypeOf((*MockKptService)(nil).GetUserInfo), arg0, arg1)
+}
+
+// IsShowCattleCategory mocks base method.
+func (m *MockKptService) IsShowCattleCategory(arg0 context.Context, arg1 *operationPb.IsShowCattleCategory) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "IsShowCattleCategory", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// IsShowCattleCategory indicates an expected call of IsShowCattleCategory.
+func (mr *MockKptServiceMockRecorder) IsShowCattleCategory(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsShowCattleCategory", reflect.TypeOf((*MockKptService)(nil).IsShowCattleCategory), arg0, arg1)
+}
+
+// IsShowFeedFormula mocks base method.
+func (m *MockKptService) IsShowFeedFormula(arg0 context.Context, arg1 *operationPb.IsShowModifyFeedFormula) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "IsShowFeedFormula", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// IsShowFeedFormula indicates an expected call of IsShowFeedFormula.
+func (mr *MockKptServiceMockRecorder) IsShowFeedFormula(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsShowFeedFormula", reflect.TypeOf((*MockKptService)(nil).IsShowFeedFormula), arg0, arg1)
+}
+
+// IsShowForage mocks base method.
+func (m *MockKptService) IsShowForage(arg0 context.Context, arg1 *operationPb.IsShowForage) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "IsShowForage", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// IsShowForage indicates an expected call of IsShowForage.
+func (mr *MockKptServiceMockRecorder) IsShowForage(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsShowForage", reflect.TypeOf((*MockKptService)(nil).IsShowForage), arg0, arg1)
+}
+
+// IsShowForageCategory mocks base method.
+func (m *MockKptService) IsShowForageCategory(arg0 context.Context, arg1 *operationPb.IsShowForageCategory) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "IsShowForageCategory", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// IsShowForageCategory indicates an expected call of IsShowForageCategory.
+func (mr *MockKptServiceMockRecorder) IsShowForageCategory(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsShowForageCategory", reflect.TypeOf((*MockKptService)(nil).IsShowForageCategory), arg0, arg1)
+}
+
+// IsShowGroupPasture mocks base method.
+func (m *MockKptService) IsShowGroupPasture(arg0 context.Context, arg1 *operationPb.IsShowGroupPasture) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "IsShowGroupPasture", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// IsShowGroupPasture indicates an expected call of IsShowGroupPasture.
+func (mr *MockKptServiceMockRecorder) IsShowGroupPasture(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsShowGroupPasture", reflect.TypeOf((*MockKptService)(nil).IsShowGroupPasture), arg0, arg1)
+}
+
+// IsShowSystemMenu mocks base method.
+func (m *MockKptService) IsShowSystemMenu(arg0 context.Context, arg1 *operationPb.IsShowSystemMenuRequest) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "IsShowSystemMenu", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// IsShowSystemMenu indicates an expected call of IsShowSystemMenu.
+func (mr *MockKptServiceMockRecorder) IsShowSystemMenu(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsShowSystemMenu", reflect.TypeOf((*MockKptService)(nil).IsShowSystemMenu), arg0, arg1)
+}
+
+// IsShowSystemUser mocks base method.
+func (m *MockKptService) IsShowSystemUser(arg0 context.Context, arg1 *operationPb.IsShowSystemUserRequest) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "IsShowSystemUser", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// IsShowSystemUser indicates an expected call of IsShowSystemUser.
+func (mr *MockKptServiceMockRecorder) IsShowSystemUser(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsShowSystemUser", reflect.TypeOf((*MockKptService)(nil).IsShowSystemUser), arg0, arg1)
+}
+
+// ParentCattleCategoryList mocks base method.
+func (m *MockKptService) ParentCattleCategoryList(arg0 context.Context) map[operationPb.CattleCategoryParent_Kind]string {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "ParentCattleCategoryList", arg0)
+	ret0, _ := ret[0].(map[operationPb.CattleCategoryParent_Kind]string)
+	return ret0
+}
+
+// ParentCattleCategoryList indicates an expected call of ParentCattleCategoryList.
+func (mr *MockKptServiceMockRecorder) ParentCattleCategoryList(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParentCattleCategoryList", reflect.TypeOf((*MockKptService)(nil).ParentCattleCategoryList), arg0)
+}
+
+// ParentForageCategoryList mocks base method.
+func (m *MockKptService) ParentForageCategoryList(arg0 context.Context) map[operationPb.ForageCategoryParent_Kind]string {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "ParentForageCategoryList", arg0)
+	ret0, _ := ret[0].(map[operationPb.ForageCategoryParent_Kind]string)
+	return ret0
+}
+
+// ParentForageCategoryList indicates an expected call of ParentForageCategoryList.
+func (mr *MockKptServiceMockRecorder) ParentForageCategoryList(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ParentForageCategoryList", reflect.TypeOf((*MockKptService)(nil).ParentForageCategoryList), arg0)
+}
+
+// ResetPasswordGroupPasture mocks base method.
+func (m *MockKptService) ResetPasswordGroupPasture(arg0 context.Context, arg1 int64) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "ResetPasswordGroupPasture", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// ResetPasswordGroupPasture indicates an expected call of ResetPasswordGroupPasture.
+func (mr *MockKptServiceMockRecorder) ResetPasswordGroupPasture(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResetPasswordGroupPasture", reflect.TypeOf((*MockKptService)(nil).ResetPasswordGroupPasture), arg0, arg1)
+}
+
+// ResetPasswordSystemUser mocks base method.
+func (m *MockKptService) ResetPasswordSystemUser(arg0 context.Context, arg1 int64) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "ResetPasswordSystemUser", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// ResetPasswordSystemUser indicates an expected call of ResetPasswordSystemUser.
+func (mr *MockKptServiceMockRecorder) ResetPasswordSystemUser(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ResetPasswordSystemUser", reflect.TypeOf((*MockKptService)(nil).ResetPasswordSystemUser), arg0, arg1)
+}
+
+// SearchCattleCategoryList mocks base method.
+func (m *MockKptService) SearchCattleCategoryList(arg0 context.Context, arg1 *operationPb.SearchCattleCategoryRequest) (*operationPb.SearchCattleCategoryResponse, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "SearchCattleCategoryList", arg0, arg1)
+	ret0, _ := ret[0].(*operationPb.SearchCattleCategoryResponse)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// SearchCattleCategoryList indicates an expected call of SearchCattleCategoryList.
+func (mr *MockKptServiceMockRecorder) SearchCattleCategoryList(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SearchCattleCategoryList", reflect.TypeOf((*MockKptService)(nil).SearchCattleCategoryList), arg0, arg1)
+}
+
+// SearchFeedFormulaList mocks base method.
+func (m *MockKptService) SearchFeedFormulaList(arg0 context.Context, arg1 *operationPb.SearchFeedFormulaRequest) (*operationPb.SearchFeedFormulaListResponse, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "SearchFeedFormulaList", arg0, arg1)
+	ret0, _ := ret[0].(*operationPb.SearchFeedFormulaListResponse)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// SearchFeedFormulaList indicates an expected call of SearchFeedFormulaList.
+func (mr *MockKptServiceMockRecorder) SearchFeedFormulaList(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SearchFeedFormulaList", reflect.TypeOf((*MockKptService)(nil).SearchFeedFormulaList), arg0, arg1)
+}
+
+// SearchForageCategoryList mocks base method.
+func (m *MockKptService) SearchForageCategoryList(arg0 context.Context, arg1 *operationPb.SearchForageCategoryRequest) (*operationPb.SearchForageCategoryResponse, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "SearchForageCategoryList", arg0, arg1)
+	ret0, _ := ret[0].(*operationPb.SearchForageCategoryResponse)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// SearchForageCategoryList indicates an expected call of SearchForageCategoryList.
+func (mr *MockKptServiceMockRecorder) SearchForageCategoryList(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SearchForageCategoryList", reflect.TypeOf((*MockKptService)(nil).SearchForageCategoryList), arg0, arg1)
+}
+
+// SearchForageList mocks base method.
+func (m *MockKptService) SearchForageList(arg0 context.Context, arg1 *operationPb.SearchForageListRequest) (*operationPb.SearchForageListResponse, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "SearchForageList", arg0, arg1)
+	ret0, _ := ret[0].(*operationPb.SearchForageListResponse)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// SearchForageList indicates an expected call of SearchForageList.
+func (mr *MockKptServiceMockRecorder) SearchForageList(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SearchForageList", reflect.TypeOf((*MockKptService)(nil).SearchForageList), arg0, arg1)
+}
+
+// SearchFormulaEstimateList mocks base method.
+func (m *MockKptService) SearchFormulaEstimateList(arg0 context.Context, arg1 *operationPb.SearchFormulaEstimateRequest) (*operationPb.SearchFormulaEstimateResponse, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "SearchFormulaEstimateList", arg0, arg1)
+	ret0, _ := ret[0].(*operationPb.SearchFormulaEstimateResponse)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// SearchFormulaEstimateList indicates an expected call of SearchFormulaEstimateList.
+func (mr *MockKptServiceMockRecorder) SearchFormulaEstimateList(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SearchFormulaEstimateList", reflect.TypeOf((*MockKptService)(nil).SearchFormulaEstimateList), arg0, arg1)
+}
+
+// SearchGroupPastureList mocks base method.
+func (m *MockKptService) SearchGroupPastureList(arg0 context.Context, arg1 *operationPb.SearchPastureRequest) (*operationPb.SearchPastureResponse, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "SearchGroupPastureList", arg0, arg1)
+	ret0, _ := ret[0].(*operationPb.SearchPastureResponse)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// SearchGroupPastureList indicates an expected call of SearchGroupPastureList.
+func (mr *MockKptServiceMockRecorder) SearchGroupPastureList(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SearchGroupPastureList", reflect.TypeOf((*MockKptService)(nil).SearchGroupPastureList), arg0, arg1)
+}
+
+// SearchMobileList mocks base method.
+func (m *MockKptService) SearchMobileList(arg0 context.Context, arg1 *operationPb.SearchMobileRequest) (*operationPb.SearchMobileResponse, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "SearchMobileList", arg0, arg1)
+	ret0, _ := ret[0].(*operationPb.SearchMobileResponse)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// SearchMobileList indicates an expected call of SearchMobileList.
+func (mr *MockKptServiceMockRecorder) SearchMobileList(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SearchMobileList", reflect.TypeOf((*MockKptService)(nil).SearchMobileList), arg0, arg1)
+}
+
+// SearchSystemMenuList mocks base method.
+func (m *MockKptService) SearchSystemMenuList(arg0 context.Context, arg1 *operationPb.SearchMenuRequest) (*operationPb.SearchMenuResponse, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "SearchSystemMenuList", arg0, arg1)
+	ret0, _ := ret[0].(*operationPb.SearchMenuResponse)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// SearchSystemMenuList indicates an expected call of SearchSystemMenuList.
+func (mr *MockKptServiceMockRecorder) SearchSystemMenuList(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SearchSystemMenuList", reflect.TypeOf((*MockKptService)(nil).SearchSystemMenuList), arg0, arg1)
+}
+
+// SearchSystemRoleList mocks base method.
+func (m *MockKptService) SearchSystemRoleList(arg0 context.Context, arg1 *operationPb.SearchRoleRequest) (*operationPb.SearchRoleResponse, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "SearchSystemRoleList", arg0, arg1)
+	ret0, _ := ret[0].(*operationPb.SearchRoleResponse)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// SearchSystemRoleList indicates an expected call of SearchSystemRoleList.
+func (mr *MockKptServiceMockRecorder) SearchSystemRoleList(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SearchSystemRoleList", reflect.TypeOf((*MockKptService)(nil).SearchSystemRoleList), arg0, arg1)
+}
+
+// SearchSystemUserList mocks base method.
+func (m *MockKptService) SearchSystemUserList(arg0 context.Context, arg1 *operationPb.SearchUserRequest) (*operationPb.SearchUserResponse, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "SearchSystemUserList", arg0, arg1)
+	ret0, _ := ret[0].(*operationPb.SearchUserResponse)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// SearchSystemUserList indicates an expected call of SearchSystemUserList.
+func (mr *MockKptServiceMockRecorder) SearchSystemUserList(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SearchSystemUserList", reflect.TypeOf((*MockKptService)(nil).SearchSystemUserList), arg0, arg1)
+}

+ 0 - 48
module/backend/ops_service.go

@@ -1,48 +0,0 @@
-package backend
-
-import (
-	"context"
-	"fmt"
-	"kpt-tmr-group/model"
-	"kpt-tmr-group/pkg/xerr"
-	operationPb "kpt-tmr-group/proto/go/backend/operation"
-)
-
-// CreatePasture 创建牧场
-func (s *StoreEntry) CreatePasture(ctx context.Context, req *operationPb.AddPastureRequest) error {
-	pastureList := model.NewGroupPasture(req)
-	if err := s.DB.Create(pastureList).Error; err != nil {
-		return xerr.WithStack(err)
-	}
-	return nil
-}
-
-// SearchPastureList 查询牧场列表
-func (s *StoreEntry) SearchPastureList(ctx context.Context, req *operationPb.SearchPastureRequest) (*model.GroupPastureResponse, error) {
-
-	groupPasture := make([]*model.GroupPasture, 0)
-	var count int64 = 0
-
-	pref := s.DB.Model(new(model.GroupPasture)).Where("is_show = ? ", operationPb.IsShow_OK)
-	if req.Name != "" {
-		pref.Where("name like ?", fmt.Sprintf("%s%s%s", "%", req.Name, "%"))
-	}
-	if req.ManagerPhone != "" {
-		pref.Where("manager_phone like ?", fmt.Sprintf("%s%s%s", "%", req.ManagerPhone, "%"))
-	}
-
-	if req.ManagerUser != "" {
-		pref.Where("manager_user like ?", fmt.Sprintf("%s%s%s", "%", req.ManagerUser, "%"))
-	}
-
-	if err := pref.Order("id desc").Count(&count).Limit(int(req.GetPageSize())).Offset(int((req.GetPage() - 1) * req.GetPageSize())).
-		Find(&groupPasture).Error; err != nil {
-		return nil, xerr.WithStack(err)
-	}
-
-	return &model.GroupPastureResponse{
-		Page:  req.Page,
-		Total: int32(count),
-		List:  model.GroupPastureSlice(groupPasture).ToPB(),
-	}, nil
-}

+ 822 - 0
module/backend/pasture_service.go

@@ -0,0 +1,822 @@
+package backend
+
+import (
+	"bytes"
+	"context"
+	"errors"
+	"fmt"
+	"io"
+	"kpt-tmr-group/model"
+	"kpt-tmr-group/pkg/logger/zaplog"
+	"kpt-tmr-group/pkg/tool"
+	"kpt-tmr-group/pkg/xerr"
+	operationPb "kpt-tmr-group/proto/go/backend/operation"
+	"net/http"
+	"strconv"
+
+	"go.uber.org/zap"
+
+	"github.com/xuri/excelize/v2"
+
+	"gorm.io/gorm"
+)
+
+var (
+	CattleParentCategoryMap = map[operationPb.CattleCategoryParent_Kind]string{
+		operationPb.CattleCategoryParent_LACTATION_CAW: "泌乳牛",
+		operationPb.CattleCategoryParent_FATTEN_CAW:    "育肥牛",
+		operationPb.CattleCategoryParent_RESERVE_CAW:   "后备牛",
+		operationPb.CattleCategoryParent_DRY_CAW:       "干奶牛",
+		operationPb.CattleCategoryParent_PERINATAL_CAW: "围产牛",
+		operationPb.CattleCategoryParent_OTHER_CAW:     "其他",
+	}
+	ForageParentCategoryMap = map[operationPb.ForageCategoryParent_Kind]string{
+		operationPb.ForageCategoryParent_ROUGHAGE:                       "粗料",
+		operationPb.ForageCategoryParent_CONCENTRATE:                    "精料",
+		operationPb.ForageCategoryParent_HALF_ROUGHAGE_HALF_CONCENTRATE: "粗料精料各半",
+		operationPb.ForageCategoryParent_OTHER:                          "其他",
+	}
+	ForageSourceMap = map[operationPb.ForageSource_Kind]string{
+		operationPb.ForageSource_SYSTEM_BUILT_IN: "系统内置",
+		operationPb.ForageSource_USER_DEFINED:    "用户自定义",
+	}
+	ForagePlanTypeMap = map[operationPb.ForagePlanType_Kind]string{
+		operationPb.ForagePlanType_INVALID:     "无",
+		operationPb.ForagePlanType_FORKLIFT:    "铲车",
+		operationPb.ForagePlanType_CONCENTRATE: "精料",
+	}
+	JumpDelaTypeMap = map[operationPb.JumpDelaType_Kind]string{
+		operationPb.JumpDelaType_INVALID: "禁用",
+		operationPb.JumpDelaType_THREE:   "3秒",
+		operationPb.JumpDelaType_SIX:     "6秒",
+		operationPb.JumpDelaType_NINE:    "9秒",
+	}
+	IsShowMap = map[operationPb.IsShow_Kind]string{
+		operationPb.IsShow_OK: "是",
+		operationPb.IsShow_NO: "否",
+	}
+)
+
+// CreateGroupPasture 创建集团牧场
+func (s *StoreEntry) CreateGroupPasture(ctx context.Context, req *operationPb.AddPastureRequest) error {
+	pastureList := model.NewGroupPasture(req)
+	if err := s.DB.Create(pastureList).Error; err != nil {
+		return xerr.WithStack(err)
+	}
+	return nil
+}
+
+// EditGroupPasture 创建集团牧场
+func (s *StoreEntry) EditGroupPasture(ctx context.Context, req *operationPb.AddPastureRequest) error {
+	groupPasture := &model.GroupPasture{Id: int64(req.Id)}
+	if err := s.DB.First(groupPasture).Error; err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			return xerr.Custom("该数据不存在!")
+		}
+		return xerr.WithStack(err)
+	}
+
+	updateData := &model.GroupPasture{
+		Name:         req.Name,
+		Account:      req.Account,
+		ManagerUser:  req.ManagerUser,
+		ManagerPhone: req.ManagerPhone,
+		Address:      req.Address,
+	}
+
+	if err := s.DB.Model(new(model.GroupPasture)).Omit("is_show", "password").
+		Where("id = ?", req.Id).
+		Updates(updateData).Error; err != nil {
+		return xerr.WithStack(err)
+	}
+	return nil
+}
+
+// SearchGroupPastureList 查询牧场列表
+func (s *StoreEntry) SearchGroupPastureList(ctx context.Context, req *operationPb.SearchPastureRequest) (*operationPb.SearchPastureResponse, error) {
+	groupPasture := make([]*model.GroupPasture, 0)
+	var count int64 = 0
+
+	pref := s.DB.Model(new(model.GroupPasture)).Where("is_delete = ? ", operationPb.IsShow_OK)
+	if req.Name != "" {
+		pref.Where("name like ?", fmt.Sprintf("%s%s%s", "%", req.Name, "%"))
+	}
+	if req.ManagerPhone != "" {
+		pref.Where("manager_phone like ?", fmt.Sprintf("%s%s%s", "%", req.ManagerPhone, "%"))
+	}
+
+	if req.ManagerUser != "" {
+		pref.Where("manager_user like ?", fmt.Sprintf("%s%s%s", "%", req.ManagerUser, "%"))
+	}
+
+	if req.StartTime > 0 && req.EndTime > 0 && req.EndTime >= req.StartTime {
+		pref.Where("created_at BETWEEN  ? AND ? ", req.StartTime, req.EndTime)
+	}
+
+	if err := pref.Order("id desc").Count(&count).Limit(int(req.Pagination.PageSize)).Offset(int(req.Pagination.PageOffset)).
+		Find(&groupPasture).Debug().Error; err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	return &operationPb.SearchPastureResponse{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.SearchPastureData{
+			Page:     req.Pagination.Page,
+			Total:    int32(count),
+			PageSize: req.Pagination.PageSize,
+			List:     model.GroupPastureSlice(groupPasture).ToPB(),
+		},
+	}, nil
+}
+
+func (s *StoreEntry) DeleteGroupPasture(ctx context.Context, pastureId int64) error {
+	groupPasture := &model.GroupPasture{
+		Id: pastureId,
+	}
+	if err := s.DB.First(groupPasture).Error; err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			return xerr.Custom("该数据不存在")
+		}
+		return xerr.WithStack(err)
+	}
+
+	if err := s.DB.Model(groupPasture).Update("is_delete", operationPb.IsShow_NO).Error; err != nil {
+		return xerr.WithStack(err)
+	}
+	return nil
+}
+
+func (s *StoreEntry) ResetPasswordGroupPasture(ctx context.Context, pastureId int64) error {
+	groupPasture := &model.GroupPasture{
+		Id: pastureId,
+	}
+	if err := s.DB.Where("is_delete = ?", operationPb.IsShow_OK).First(groupPasture).Error; err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			return xerr.Custom("该数据不存在")
+		}
+		return xerr.WithStack(err)
+	}
+
+	if err := s.DB.Model(groupPasture).Update("password", tool.Md5String(model.InitManagerPassword)).Error; err != nil {
+		return xerr.WithStack(err)
+	}
+	return nil
+}
+
+func (s *StoreEntry) IsShowGroupPasture(ctx context.Context, req *operationPb.IsShowGroupPasture) error {
+	groupPasture := &model.GroupPasture{
+		Id: int64(req.PastureId),
+	}
+	if err := s.DB.First(groupPasture).Error; err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			return xerr.Custom("该数据不存在")
+		}
+		return xerr.WithStack(err)
+	}
+
+	if err := s.DB.Model(groupPasture).Where("is_delete = ?", operationPb.IsShow_OK).Update("is_show", req.IsShow).Error; err != nil {
+		return xerr.WithStack(err)
+	}
+	return nil
+}
+
+// ParentCattleCategoryList 畜牧类别父类列表
+func (s *StoreEntry) ParentCattleCategoryList(ctx context.Context) map[operationPb.CattleCategoryParent_Kind]string {
+	return CattleParentCategoryMap
+}
+
+// AddCattleCategory 添加畜牧分类
+func (s *StoreEntry) AddCattleCategory(ctx context.Context, req *operationPb.AddCattleCategoryRequest) error {
+	cattleCategory := model.NewCattleCategory(req)
+	if err := s.DB.Create(cattleCategory).Error; err != nil {
+		return xerr.WithStack(err)
+	}
+	return nil
+}
+
+// EditCattleCategory 编辑畜牧分类
+func (s *StoreEntry) EditCattleCategory(ctx context.Context, req *operationPb.AddCattleCategoryRequest) error {
+	cattleCategory := &model.CattleCategory{Id: int64(req.Id)}
+	if err := s.DB.First(cattleCategory).Error; err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			return xerr.Custom("该数据不存在")
+		}
+		return xerr.WithStack(err)
+	}
+	updateData := &model.CattleCategory{
+		ParentName: req.ParentName,
+		Name:       req.Name,
+		Number:     req.Number,
+		ParentId:   req.ParentId,
+	}
+
+	if err := s.DB.Model(new(model.CattleCategory)).Omit("is_show", "is_delete").
+		Where("id = ?", req.Id).
+		Updates(updateData).Error; err != nil {
+		return xerr.WithStack(err)
+	}
+	return nil
+}
+
+// IsShowCattleCategory 是否启用
+func (s *StoreEntry) IsShowCattleCategory(ctx context.Context, req *operationPb.IsShowCattleCategory) error {
+	cattleCategory := &model.CattleCategory{Id: int64(req.CattleCategoryId)}
+	if err := s.DB.First(cattleCategory).Error; err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			return xerr.Custom("该数据不存在")
+		}
+		return xerr.WithStack(err)
+	}
+
+	if err := s.DB.Model(new(model.CattleCategory)).Where("id = ?", req.CattleCategoryId).Update("is_show", req.IsShow).Error; err != nil {
+		return xerr.WithStack(err)
+	}
+	return nil
+}
+
+// DeleteCattleCategory 是否删除
+func (s *StoreEntry) DeleteCattleCategory(ctx context.Context, cattleCategoryId int64) error {
+	cattleCategory := &model.CattleCategory{Id: cattleCategoryId}
+	if err := s.DB.First(cattleCategory).Error; err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			return xerr.Custom("该数据不存在")
+		}
+		return xerr.WithStack(err)
+	}
+
+	if err := s.DB.Model(new(model.CattleCategory)).Where("id = ?", cattleCategoryId).Update("is_delete", operationPb.IsShow_NO).Error; err != nil {
+		return xerr.WithStack(err)
+	}
+	return nil
+}
+
+// SearchCattleCategoryList 牧畜分类类别列表
+func (s *StoreEntry) SearchCattleCategoryList(ctx context.Context, req *operationPb.SearchCattleCategoryRequest) (*operationPb.SearchCattleCategoryResponse, error) {
+	cattleCategory := make([]*model.CattleCategory, 0)
+	var count int64 = 0
+
+	pref := s.DB.Model(new(model.CattleCategory)).Where("is_delete = ?", operationPb.IsShow_OK)
+	if req.Name != "" {
+		pref.Where("name like ?", fmt.Sprintf("%s%s%s", "%", req.Name, "%"))
+	}
+
+	if req.ParentName != "" {
+		pref.Where("parent_name like ?", fmt.Sprintf("%s%s%s", "%", req.ParentName, "%"))
+	}
+
+	if req.IsShow > 0 {
+		pref.Where("is_show = ?", req.IsShow)
+	}
+
+	if err := pref.Order("id desc").Count(&count).Limit(int(req.Pagination.PageSize)).Offset(int(req.Pagination.PageOffset)).
+		Find(&cattleCategory).Debug().Error; err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	return &operationPb.SearchCattleCategoryResponse{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.SearchCattleCategoryData{
+			Page:     req.Pagination.Page,
+			PageSize: req.Pagination.PageSize,
+			Total:    int32(count),
+			List:     model.CattleCategorySlice(cattleCategory).ToPB(),
+		},
+	}, nil
+}
+
+// ParentForageCategoryList 饲料类别父类列表
+func (s *StoreEntry) ParentForageCategoryList(ctx context.Context) map[operationPb.ForageCategoryParent_Kind]string {
+	return ForageParentCategoryMap
+}
+
+// AddForageCategory 添加饲料分类
+func (s *StoreEntry) AddForageCategory(ctx context.Context, req *operationPb.AddForageCategoryRequest) error {
+	forageCategory := model.NewForageCategory(req)
+	if err := s.DB.Create(forageCategory).Error; err != nil {
+		return xerr.WithStack(err)
+	}
+	return nil
+}
+
+// EditForageCategory 编辑饲料分类
+func (s *StoreEntry) EditForageCategory(ctx context.Context, req *operationPb.AddForageCategoryRequest) error {
+	forageCategory := &model.ForageCategory{Id: int64(req.Id)}
+	if err := s.DB.First(forageCategory).Error; err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			return xerr.Custom("该数据不存在")
+		}
+		return xerr.WithStack(err)
+	}
+	updateData := &model.ForageCategory{
+		ParentName: req.ParentName,
+		Name:       req.Name,
+		Number:     req.Number,
+		ParentId:   req.ParentId,
+	}
+
+	if err := s.DB.Model(new(model.ForageCategory)).Omit("is_show", "is_delete").
+		Where("id = ?", req.Id).
+		Updates(updateData).Error; err != nil {
+		return xerr.WithStack(err)
+	}
+	return nil
+}
+
+// IsShowForageCategory 是否启用
+func (s *StoreEntry) IsShowForageCategory(ctx context.Context, req *operationPb.IsShowForageCategory) error {
+	forageCategory := &model.ForageCategory{Id: int64(req.ForageCategoryId)}
+	if err := s.DB.First(forageCategory).Error; err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			return xerr.Custom("该数据不存在")
+		}
+		return xerr.WithStack(err)
+	}
+
+	if err := s.DB.Model(new(model.ForageCategory)).Where("id = ?", req.ForageCategoryId).Update("is_show", req.IsShow).Error; err != nil {
+		return xerr.WithStack(err)
+	}
+	return nil
+}
+
+// DeleteForageCategory 是否删除
+func (s *StoreEntry) DeleteForageCategory(ctx context.Context, forageCategoryId int64) error {
+	forageCategory := &model.ForageCategory{Id: forageCategoryId}
+	if err := s.DB.First(forageCategory).Error; err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			return xerr.Custom("该数据不存在")
+		}
+		return xerr.WithStack(err)
+	}
+
+	if err := s.DB.Model(new(model.ForageCategory)).Where("id = ?", forageCategoryId).Update("is_delete", operationPb.IsShow_NO).Error; err != nil {
+		return xerr.WithStack(err)
+	}
+	return nil
+}
+
+// SearchForageCategoryList 饲料分类类别列表
+func (s *StoreEntry) SearchForageCategoryList(ctx context.Context, req *operationPb.SearchForageCategoryRequest) (*operationPb.SearchForageCategoryResponse, error) {
+	forageCategory := make([]*model.ForageCategory, 0)
+	var count int64 = 0
+
+	pref := s.DB.Model(new(model.ForageCategory)).Where("is_delete = ?", operationPb.IsShow_OK)
+	if req.Name != "" {
+		pref.Where("name like ?", fmt.Sprintf("%s%s%s", "%", req.Name, "%"))
+	}
+
+	if req.ParentName != "" {
+		pref.Where("parent_name like ?", fmt.Sprintf("%s%s%s", "%", req.ParentName, "%"))
+	}
+
+	if req.IsShow > 0 {
+		pref.Where("is_show = ?", req.IsShow)
+	}
+
+	if err := pref.Order("id desc").Count(&count).Limit(int(req.Pagination.PageSize)).Offset(int(req.Pagination.PageOffset)).
+		Find(&forageCategory).Debug().Error; err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	return &operationPb.SearchForageCategoryResponse{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.SearchForageCategoryData{
+			Page:     req.Pagination.Page,
+			PageSize: req.Pagination.PageSize,
+			Total:    int32(count),
+			List:     model.ForageCategorySlice(forageCategory).ToPB(),
+		},
+	}, nil
+}
+
+// CreateForage 创建饲料
+func (s *StoreEntry) CreateForage(ctx context.Context, req *operationPb.AddForageRequest) error {
+	forage := model.NewForage(req)
+	if err := s.DB.Create(forage).Error; err != nil {
+		return xerr.WithStack(err)
+	}
+	return nil
+}
+
+// EditForage 编辑饲料
+func (s *StoreEntry) EditForage(ctx context.Context, req *operationPb.AddForageRequest) error {
+	forage := model.Forage{Id: int64(req.Id)}
+	if err := s.DB.Where("is_delete = ?", operationPb.IsShow_OK).First(&forage).Error; err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			return xerr.Custom("该数据不存在")
+		}
+		return xerr.WithStack(err)
+	}
+
+	updateData := &model.Forage{
+		Name:               req.Name,
+		CategoryId:         int64(req.CategoryId),
+		UniqueEncode:       req.UniqueEncode,
+		ForageSourceId:     req.ForageSourceId,
+		PlanTypeId:         req.PlanTypeId,
+		SmallMaterialScale: req.SmallMaterialScale,
+		AllowError:         int64(req.AllowError),
+		PackageWeight:      int64(req.PackageWeight),
+		Price:              int64(req.Price),
+		JumpWeight:         int64(req.JumpWeight),
+		JumpDelay:          req.JumpDelay,
+		ConfirmStart:       req.ConfirmStart,
+		RelayLocations:     int64(req.RelayLocations),
+		Jmp:                req.Jmp,
+		Backup1:            req.Backup1,
+		Backup2:            req.Backup2,
+		Backup3:            req.Backup3,
+	}
+
+	if err := s.DB.Model(new(model.Forage)).Omit("is_show", "is_delete").Where("id = ?", req.Id).
+		Updates(updateData).Error; err != nil {
+		return xerr.WithStack(err)
+	}
+
+	return nil
+}
+
+func (s *StoreEntry) SearchForageList(ctx context.Context, req *operationPb.SearchForageListRequest) (*operationPb.SearchForageListResponse, error) {
+	forage := make([]*model.Forage, 0)
+	var count int64 = 0
+
+	pref := s.DB.Model(new(model.Forage)).Where("is_delete = ?", operationPb.IsShow_OK)
+	if req.Name != "" {
+		pref.Where("name like ?", fmt.Sprintf("%s%s%s", "%", req.Name, "%"))
+	}
+
+	if req.CategoryId != "" {
+		pref.Where("category_id = ?", req.CategoryId)
+	}
+
+	if req.ForageSourceId > 0 {
+		pref.Where("forage_source_id = ?", req.ForageSourceId)
+	}
+
+	if req.IsShow > 0 {
+		pref.Where("is_show = ?", req.IsShow)
+	}
+
+	if req.AllowError > 0 {
+		pref.Where("allow_error = ?", req.AllowError)
+	}
+
+	if req.JumpWeight > 0 {
+		pref.Where("jump_weight = ?", req.JumpWeight)
+	}
+
+	if req.JumpDelay > 0 {
+		pref.Where("jump_delay = ?", req.JumpDelay)
+	}
+
+	if err := pref.Order("id desc").Count(&count).Limit(int(req.Pagination.PageSize)).Offset(int(req.Pagination.PageOffset)).
+		Find(&forage).Error; err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	return &operationPb.SearchForageListResponse{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.SearchForageList{
+			Page:     req.Pagination.Page,
+			PageSize: req.Pagination.PageSize,
+			Total:    int32(count),
+			List:     model.ForageSlice(forage).ToPB(),
+		},
+	}, nil
+}
+
+func (s *StoreEntry) ForageEnumList(ctx context.Context) *operationPb.ForageEnumListResponse {
+	res := &operationPb.ForageEnumListResponse{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.ForageEnumList{
+			JumpDelaType:         make([]*operationPb.JumpDelaTypeEnum, 0),
+			ForageSource:         make([]*operationPb.ForageSourceEnum, 0),
+			ForagePlanType:       make([]*operationPb.ForagePlanTypeEnum, 0),
+			CattleParentCategory: make([]*operationPb.CattleParentCategoryEnum, 0),
+			ForageParentCategory: make([]*operationPb.ForageParentCategoryEnum, 0),
+			IsShow:               make([]*operationPb.IsShowEnum, 0),
+			FormulaType:          make([]*operationPb.FormulaTypeEnum, 0),
+			FormulaList:          make([]*operationPb.FormulaOptionEnum, 0),
+			ConfirmStart:         make([]*operationPb.IsShowEnum, 0),
+		},
+	}
+
+	// 跳转延迟
+	res.Data.JumpDelaType = append(res.Data.JumpDelaType, &operationPb.JumpDelaTypeEnum{
+		Value: operationPb.JumpDelaType_INVALID,
+		Label: "禁用",
+	}, &operationPb.JumpDelaTypeEnum{
+		Value: operationPb.JumpDelaType_THREE,
+		Label: "3秒",
+	}, &operationPb.JumpDelaTypeEnum{
+		Value: operationPb.JumpDelaType_SIX,
+		Label: "6秒",
+	})
+
+	// 饲料来源
+	res.Data.ForageSource = append(res.Data.ForageSource, &operationPb.ForageSourceEnum{
+		Value: operationPb.ForageSource_SYSTEM_BUILT_IN,
+		Label: "系统内置",
+	}, &operationPb.ForageSourceEnum{
+		Value: operationPb.ForageSource_USER_DEFINED,
+		Label: "用户自定义",
+	})
+
+	// 计划类型
+	res.Data.ForagePlanType = append(res.Data.ForagePlanType,
+		&operationPb.ForagePlanTypeEnum{
+			Value: operationPb.ForagePlanType_INVALID,
+			Label: "无",
+		},
+		&operationPb.ForagePlanTypeEnum{
+			Value: operationPb.ForagePlanType_FORKLIFT,
+			Label: "铲车",
+		}, &operationPb.ForagePlanTypeEnum{
+			Value: operationPb.ForagePlanType_CONCENTRATE,
+			Label: "精料",
+		})
+
+	// 畜牧类别
+	res.Data.CattleParentCategory = append(res.Data.CattleParentCategory, &operationPb.CattleParentCategoryEnum{
+		Value: operationPb.CattleCategoryParent_LACTATION_CAW,
+		Label: "泌乳牛",
+	}, &operationPb.CattleParentCategoryEnum{
+		Value: operationPb.CattleCategoryParent_FATTEN_CAW,
+		Label: "育肥牛",
+	}, &operationPb.CattleParentCategoryEnum{
+		Value: operationPb.CattleCategoryParent_RESERVE_CAW,
+		Label: "后备牛",
+	}, &operationPb.CattleParentCategoryEnum{
+		Value: operationPb.CattleCategoryParent_DRY_CAW,
+		Label: "干奶牛",
+	}, &operationPb.CattleParentCategoryEnum{
+		Value: operationPb.CattleCategoryParent_PERINATAL_CAW,
+		Label: "围产牛",
+	}, &operationPb.CattleParentCategoryEnum{
+		Value: operationPb.CattleCategoryParent_OTHER_CAW,
+		Label: "其他",
+	})
+
+	// 饲料类别
+	res.Data.ForageParentCategory = append(res.Data.ForageParentCategory, &operationPb.ForageParentCategoryEnum{
+		Value: operationPb.ForageCategoryParent_ROUGHAGE,
+		Label: "粗料",
+	}, &operationPb.ForageParentCategoryEnum{
+		Value: operationPb.ForageCategoryParent_CONCENTRATE,
+		Label: "精料",
+	}, &operationPb.ForageParentCategoryEnum{
+		Value: operationPb.ForageCategoryParent_HALF_ROUGHAGE_HALF_CONCENTRATE,
+		Label: "粗料精料参半",
+	}, &operationPb.ForageParentCategoryEnum{
+		Value: operationPb.ForageCategoryParent_OTHER,
+		Label: "其他",
+	})
+
+	res.Data.IsShow = append(res.Data.IsShow, &operationPb.IsShowEnum{
+		Value: operationPb.IsShow_OK,
+		Label: "是",
+	}, &operationPb.IsShowEnum{
+		Value: operationPb.IsShow_NO,
+		Label: "否",
+	})
+
+	res.Data.FormulaType = append(res.Data.FormulaType, &operationPb.FormulaTypeEnum{
+		Value: operationPb.FormulaType_FEED_FORMULA,
+		Label: "饲喂配方",
+	}, &operationPb.FormulaTypeEnum{
+		Value: operationPb.FormulaType_PREMIXED_FORMULA,
+		Label: "预混配方",
+	}, &operationPb.FormulaTypeEnum{
+		Value: operationPb.FormulaType_SUPPLEMENTARY_FORMULA,
+		Label: "补料配方",
+	})
+
+	res.Data.ConfirmStart = append(res.Data.ConfirmStart, &operationPb.IsShowEnum{
+		Value: operationPb.IsShow_OK,
+		Label: "启用",
+	}, &operationPb.IsShowEnum{
+		Value: operationPb.IsShow_NO,
+		Label: "禁用",
+	})
+
+	res.Data.FormulaList = append(res.Data.FormulaList, &operationPb.FormulaOptionEnum{
+		Value: 0,
+		Label: "所有",
+	})
+	formulaList := make([]*model.FeedFormula, 0)
+	if err := s.DB.Where("is_delete = ?", operationPb.IsShow_OK).Find(&formulaList).Error; err != nil {
+		zaplog.Error("OptionFormula", zap.Any("Err", err))
+		return res
+	}
+
+	for _, v := range formulaList {
+		res.Data.FormulaList = append(res.Data.FormulaList, &operationPb.FormulaOptionEnum{
+			Value: int32(v.Id),
+			Label: v.Name,
+		})
+	}
+
+	return res
+}
+
+func (s *StoreEntry) DeleteForageList(ctx context.Context, ids []int64) error {
+	if len(ids) == 0 {
+		return xerr.Custom("参数错误")
+	}
+	if err := s.DB.Model(new(model.Forage)).Where("id IN ?", ids).Update("is_delete", operationPb.IsShow_NO).Error; err != nil {
+		return xerr.WithStack(err)
+	}
+	return nil
+}
+
+func (s *StoreEntry) IsShowForage(ctx context.Context, req *operationPb.IsShowForage) error {
+	forage := &model.Forage{Id: int64(req.ForageId)}
+	if err := s.DB.First(forage).Error; err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			return xerr.Custom("该数据不存在")
+		}
+		return xerr.WithStack(err)
+	}
+
+	if err := s.DB.Model(new(model.Forage)).Where("id = ?", req.ForageId).Update("is_show", req.IsShow).Error; err != nil {
+		return xerr.WithStack(err)
+	}
+	return nil
+}
+
+// ExcelImportForage 导入excel
+func (s *StoreEntry) ExcelImportForage(ctx context.Context, req io.Reader) error {
+	xlsx, err := excelize.OpenReader(req)
+	if err != nil {
+		return xerr.WithStack(err)
+	}
+	defer xlsx.Close()
+
+	rows, err := xlsx.GetRows(xlsx.GetSheetName(xlsx.GetActiveSheetIndex()))
+	if err != nil {
+		return xerr.WithStack(err)
+	}
+
+	if len(rows) > 10000 {
+		rows = rows[:10000]
+	}
+	forageList := make([]*model.Forage, 0)
+	for i, row := range rows {
+		if i == 0 {
+			continue
+		}
+		var (
+			forageSourceId                                int32
+			categoryId, price, jumpWeight, relayLocations int
+			allowError, packageWeight                     int
+			name, uniqueEncode, backup1, backup2, backup3 string
+		)
+
+		for k, v := range row {
+			if k == 0 {
+				name = v
+			}
+			if k == 2 {
+				uniqueEncode = v
+			}
+			if k == 3 {
+				forageSourceId = operationPb.ForageSource_Kind_value[v]
+			}
+			if k == 5 {
+				allowError, _ = strconv.Atoi(v)
+			}
+
+			if k == 6 {
+				packageWeight, _ = strconv.Atoi(v)
+			}
+
+			if k == 7 {
+				price, _ = strconv.Atoi(v)
+			}
+
+			if k == 8 {
+				jumpWeight, _ = strconv.Atoi(v)
+			}
+
+			if k == 11 {
+				relayLocations, _ = strconv.Atoi(v)
+			}
+
+			if k == 13 {
+				backup1 = v
+			}
+
+			if k == 14 {
+				backup2 = v
+			}
+
+			if k == 15 {
+				backup3 = v
+			}
+		}
+
+		forageItem := &model.Forage{
+			Name:               name,
+			CategoryId:         int64(categoryId),
+			MaterialType:       0,
+			UniqueEncode:       uniqueEncode,
+			ForageSourceId:     operationPb.ForageSource_Kind(forageSourceId),
+			PlanTypeId:         0,
+			SmallMaterialScale: "",
+			AllowError:         int64(allowError),
+			PackageWeight:      int64(packageWeight),
+			Price:              int64(price),
+			JumpWeight:         int64(jumpWeight),
+			JumpDelay:          0,
+			ConfirmStart:       0,
+			RelayLocations:     int64(relayLocations),
+			Jmp:                0,
+			Backup1:            backup1,
+			Backup2:            backup2,
+			Backup3:            backup3,
+			IsShow:             operationPb.IsShow_OK,
+			IsDelete:           operationPb.IsShow_OK,
+			DataSource:         operationPb.DataSource_EXCEL_IMPORT,
+		}
+
+		forageList = append(forageList, forageItem)
+	}
+
+	if len(forageList) > 0 {
+		if err = s.DB.Create(forageList).Error; err != nil {
+			return xerr.WithStack(err)
+		}
+	}
+
+	return nil
+}
+
+// ExcelExportForage 流式导出excel
+func (s *StoreEntry) ExcelExportForage(ctx context.Context, req *operationPb.SearchForageListRequest) (*bytes.Buffer, error) {
+	res, err := s.SearchForageList(ctx, req)
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+	if len(res.Data.List) <= 0 {
+		return nil, xerr.Custom("数据为空")
+	}
+
+	file := excelize.NewFile()
+	defer file.Close()
+
+	streamWriter, err := file.NewStreamWriter("Sheet1")
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+	titles := []interface{}{"饲料名称", "饲料分类", "唯一编码", "饲料来源", "计划类型", "允许误差(kg)", "包装单位重量(kg)",
+		"单价", "跳转重量域(kg)", "跳转延迟", "确认开始", "继电器位置", "无上域", "备用字段01", "备用字段02", "备用字段03"}
+	if err = streamWriter.SetRow("A1", titles); err != nil {
+		return nil, xerr.WithStack(err)
+	}
+	for i, item := range res.Data.List {
+		cell, err := excelize.CoordinatesToCellName(1, i+2)
+		if err != nil {
+			zaplog.Error("excelize.CoordinatesToCellName", zap.Any("Err", err))
+			continue
+		}
+		row := make([]interface{}, 0)
+		row = append(row, item.Name, item.CategoryName, item.UniqueEncode, item.ForageSourceId, item.PlanTypeId,
+			item.AllowError, item.PackageWeight, float64(item.Price/100.00), item.JumpWeight, item.JumpDelay,
+			item.ConfirmStart, item.RelayLocations, item.Jmp, item.Backup1, item.Backup2, item.Backup3)
+
+		if err = streamWriter.SetRow(cell, row); err != nil {
+			return nil, xerr.WithStack(err)
+		}
+	}
+
+	if err = streamWriter.Flush(); err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	return file.WriteToBuffer()
+}
+
+// ExcelTemplateForage 导出模板
+func (s *StoreEntry) ExcelTemplateForage(ctx context.Context) (*bytes.Buffer, error) {
+	file := excelize.NewFile()
+	defer file.Close()
+
+	streamWriter, err := file.NewStreamWriter("Sheet1")
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+	titles := []interface{}{"饲料名称", "饲料分类", "唯一编码", "饲料来源", "计划类型", "允许误差(kg)", "包装单位重量(kg)",
+		"单价", "跳转重量域(kg)", "跳转延迟", "确认开始", "继电器位置", "无上域", "备用字段01", "备用字段02", "备用字段03"}
+	if err = streamWriter.SetRow("A1", titles); err != nil {
+		return nil, xerr.WithStack(err)
+	}
+	if err = streamWriter.Flush(); err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	return file.WriteToBuffer()
+}

+ 177 - 0
module/backend/statistic_service.go

@@ -0,0 +1,177 @@
+package backend
+
+import (
+	"context"
+	"fmt"
+	"kpt-tmr-group/model"
+	"kpt-tmr-group/pkg/xerr"
+	operationPb "kpt-tmr-group/proto/go/backend/operation"
+	"net/http"
+	"strconv"
+	"strings"
+	"time"
+)
+
+func (s *StoreEntry) SearchFormulaEstimateList(ctx context.Context, req *operationPb.SearchFormulaEstimateRequest) (*operationPb.SearchFormulaEstimateResponse, error) {
+	startTime, err := time.Parse(model.LayoutTime, req.StartTime)
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	endTime, err := time.Parse(model.LayoutTime, req.EndTime)
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	startTimeUnix := startTime.Unix()
+	endTimeUnix := endTime.Unix()
+
+	formulaEstimate := make([]*model.FormulaEstimate, 0)
+	var count int64 = 0
+
+	pref := s.DB.Model(new(model.FormulaEstimate))
+	if req.Name != "" {
+		pref.Where("name like ?", fmt.Sprintf("%s%s%s", "%", req.Name, "%"))
+	}
+	if startTimeUnix > 0 && endTimeUnix > 0 && endTimeUnix >= startTimeUnix {
+		pref.Where("created_at BETWEEN ? AND ?", startTimeUnix, endTimeUnix)
+	}
+
+	if req.SearchType == 1 {
+		pref.Where("feed_formula_name = ?", req.Name)
+	} else {
+		pref.Where("barn_id = ?", req.Name)
+	}
+
+	if err = pref.Order("id desc").Count(&count).Limit(int(req.Pagination.PageSize)).Offset(int(req.Pagination.PageOffset)).
+		Find(&formulaEstimate).Error; err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	return &operationPb.SearchFormulaEstimateResponse{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.SearchFormulaEstimate{
+			Page:     req.Pagination.Page,
+			Total:    int32(count),
+			PageSize: req.Pagination.PageSize,
+			List:     model.FormulaEstimateSlice(formulaEstimate).ToPB(),
+		},
+	}, nil
+}
+
+func (s *StoreEntry) SearchAnalysisAccuracy(ctx context.Context, req *operationPb.SearchAnalysisAccuracyRequest) (*model.SearchAnalysisAccuracyResponse, error) {
+
+	dataList := &model.CommonValueRatio{
+		MaxValue:    "",
+		MiddleValue: "",
+		MinValue:    "",
+		PastureName: make([]string, 0),
+		DateDay:     make([]string, 0),
+		DataList:    make([][]string, 0),
+		PastureIds:  make([]int32, 0),
+	}
+	res := &model.SearchAnalysisAccuracyResponse{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &model.AnalysisAccuracyData{
+			Chart: &model.Chart{
+				MixedFodderAccurateRatio:    dataList,
+				MixedFodderCorrectRatio:     dataList,
+				SprinkleFodderAccurateRatio: dataList,
+				SprinkleFodderCorrectRatio:  dataList,
+			},
+			Table: &model.Table{
+				TitleList: make([]*model.TableList, 0),
+				DataList:  make([]*interface{}, 0),
+			},
+		},
+	}
+	res.Data.Table.TitleList = append(res.Data.Table.TitleList, &model.TableList{
+		Name:  "title",
+		Value: "牧场",
+	})
+
+	analysisAccuracy := make([]*model.OptionsAnalysisAccuracy, 0)
+	pref := s.DB.Model(new(model.AnalysisAccuracy))
+	if req.EndDate != "" && req.StartDate != "" {
+		pref.Where("date_day BETWEEN ? AND ?", req.StartDate, req.EndDate)
+	}
+
+	if req.CattleParentCategoryId > 0 {
+		pref.Where("cattle_parent_category_id = ?", req.CattleParentCategoryId)
+	}
+	if req.FeedFormulaId > 0 {
+		pref.Where("feed_formula_id = ?", req.FeedFormulaId)
+	}
+
+	if len(req.PastureIds) > 0 {
+		pref.Where("pasture_id IN ?", req.PastureIds)
+	}
+
+	if err := pref.Select("pasture_id,pasture_name,date_day,sum(iweight) as all_iweight,sum(lweight) as all_lweight,sum(oweight) as all_oweight,sum(actual_weight_minus) as all_actual_weight_minus,sum(allow_ratio) as all_allow_ratio,sum(alweight) as all_alweight").
+		Group("pasture_id,pasture_name,date_day").Order("pasture_name,date_day").Find(&analysisAccuracy).Debug().Error; err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	mixedFodderAccurateRatio := make([]float64, 0)
+	mapPastureName := make(map[string]bool, 0)
+	mapDateDay := make(map[string]bool, 0)
+	mapRatio := make(map[string]bool, 0)
+	tableList := make([]*model.TableList, 0)
+	for k, v := range analysisAccuracy {
+		if _, ok := mapPastureName[v.PastureName]; !ok {
+			res.Data.Chart.MixedFodderAccurateRatio.PastureName = append(res.Data.Chart.MixedFodderAccurateRatio.PastureName, v.PastureName)
+			res.Data.Chart.MixedFodderAccurateRatio.PastureIds = append(res.Data.Chart.MixedFodderAccurateRatio.PastureIds, int32(v.PastureId))
+			mapPastureName[v.PastureName] = true
+		}
+
+		dataDayFormat := strings.TrimRight(v.DateDay, "T00:00:00+08:00")
+		if _, ok := mapDateDay[v.DateDay]; !ok {
+			res.Data.Chart.MixedFodderAccurateRatio.DateDay = append(res.Data.Chart.MixedFodderAccurateRatio.DateDay, dataDayFormat)
+			mapDateDay[v.DateDay] = true
+		}
+
+		valueRatio1 := float64(v.AllIweight/v.AllLweight) / 100.0
+		if _, ok := mapRatio[v.DateDay]; !ok {
+			valueRatio := make([]string, 0)
+			for _, a := range analysisAccuracy {
+				if a.DateDay == v.DateDay {
+					valueRatio = append(valueRatio, strconv.FormatFloat(float64(a.AllIweight/a.AllLweight)/100.0, 'f', 2, 64))
+				}
+			}
+			res.Data.Chart.MixedFodderAccurateRatio.DataList = append(res.Data.Chart.MixedFodderAccurateRatio.DataList, valueRatio)
+			mapRatio[v.DateDay] = true
+			tableList = append(tableList, &model.TableList{
+				Name:  fmt.Sprintf("date%d", k),
+				Value: dataDayFormat,
+			})
+		}
+		mixedFodderAccurateRatio = append(mixedFodderAccurateRatio, valueRatio1)
+	}
+
+	mixedFodderAccurateRatioMaxValue, mixedFodderAccurateRatioMiddleValue, mixedFodderAccurateRatioMinValue := calculateRatio(mixedFodderAccurateRatio)
+	res.Data.Chart.MixedFodderAccurateRatio.MaxValue = strconv.FormatFloat(mixedFodderAccurateRatioMaxValue, 'f', 2, 64)
+	res.Data.Chart.MixedFodderAccurateRatio.MiddleValue = strconv.FormatFloat(mixedFodderAccurateRatioMiddleValue, 'f', 2, 64)
+	res.Data.Chart.MixedFodderAccurateRatio.MinValue = strconv.FormatFloat(mixedFodderAccurateRatioMinValue, 'f', 2, 64)
+
+	res.Data.Table.TitleList = append(res.Data.Table.TitleList, tableList...)
+
+	return res, nil
+}
+
+func calculateRatio(res []float64) (float64, float64, float64) {
+	var maxValue, middleValue, minValue, allValue float64 = 0, 0, 0, 0
+	for _, v := range res {
+		if v > maxValue {
+			maxValue = v
+		}
+		if v <= maxValue {
+			minValue = v
+		}
+
+		allValue += v
+	}
+	middleValue = allValue / float64(len(res))
+	return maxValue, middleValue, minValue
+}

+ 122 - 48
module/backend/system_permissions.go

@@ -6,6 +6,9 @@ import (
 	"kpt-tmr-group/pkg/logger/zaplog"
 	"kpt-tmr-group/pkg/xerr"
 	operationPb "kpt-tmr-group/proto/go/backend/operation"
+	"net/http"
+	"sort"
+	"strings"
 	"sync"
 	"time"
 
@@ -13,16 +16,47 @@ import (
 )
 
 type SystemAllPermissionsList struct {
-	PastureList []*model.SystemPasturePermissions
+	PastureList []*model.SystemGroupPasturePermissions
 	MenuList    []*model.SystemMenuPermissions
 	MobileList  []*model.SystemMobilePermissions
 }
 
-const (
-	Level1 = iota + 1
-	Level2
-	Level3
-)
+func (s *StoreEntry) AllPermissionsListToRolePermissions(req *SystemAllPermissionsList) *operationPb.RolePermissionsList {
+	res := &operationPb.RolePermissionsList{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.RolePermissionsData{
+			MobileList:  make([]uint32, 0),
+			MenuList:    make([]uint32, 0),
+			PastureList: make([]uint32, 0),
+		},
+	}
+	wg := sync.WaitGroup{}
+	wg.Add(3)
+	go func() {
+		for _, v := range req.MobileList {
+			res.Data.MobileList = append(res.Data.MobileList, uint32(v.MobileId))
+		}
+		wg.Done()
+	}()
+
+	go func() {
+		for _, v := range req.MenuList {
+			res.Data.MenuList = append(res.Data.MenuList, uint32(v.MenuId))
+		}
+		wg.Done()
+	}()
+
+	go func() {
+		for _, v := range req.PastureList {
+			res.Data.PastureList = append(res.Data.PastureList, uint32(v.PastureId))
+		}
+		wg.Done()
+	}()
+
+	wg.Wait()
+	return res
+}
 
 // SystemUserMenuPermissionsUnDuplicate 角色权限去重
 func (s *SystemAllPermissionsList) SystemUserMenuPermissionsUnDuplicate() {
@@ -38,9 +72,14 @@ func (s *SystemAllPermissionsList) SystemUserMenuPermissionsUnDuplicate() {
 
 func (s *StoreEntry) SystemPermissionsFormatPb(pastureList []*model.GroupPasture, mobileList []*model.SystemMobile, menuList []*model.SystemMenu) *operationPb.SystemUserMenuPermissions {
 	systemUserMenuPermissions := &operationPb.SystemUserMenuPermissions{
-		PastureList: make([]*operationPb.AddPastureRequest, 0),
-		MenuList:    make([]*operationPb.AddMenuRequest, 0),
-		MobileList:  make([]*operationPb.AddMobileRequest, 0),
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.SystemUserMenuData{
+			PastureList:     make([]*operationPb.AddPastureRequest, 0),
+			MenuList:        make([]*operationPb.AddMenuRequest, 0),
+			MobileList:      make([]*operationPb.AddMobileRequest, 0),
+			MenuButtonsPath: make([]*operationPb.MenuButtonsPath, 0),
+		},
 	}
 
 	wg := sync.WaitGroup{}
@@ -48,9 +87,9 @@ func (s *StoreEntry) SystemPermissionsFormatPb(pastureList []*model.GroupPasture
 
 	go func() {
 		for _, v := range pastureList {
-			systemUserMenuPermissions.PastureList = append(systemUserMenuPermissions.PastureList,
+			systemUserMenuPermissions.Data.PastureList = append(systemUserMenuPermissions.Data.PastureList,
 				&operationPb.AddPastureRequest{
-					Id:   v.Id,
+					Id:   int32(v.Id),
 					Name: v.Name,
 				},
 			)
@@ -60,33 +99,45 @@ func (s *StoreEntry) SystemPermissionsFormatPb(pastureList []*model.GroupPasture
 
 	// TODO 后面优化成递归算法
 	go func() {
-
 		level := make(map[int32][]*operationPb.AddMenuRequest, 0)
 		for _, menu := range menuList {
 			if _, ok := level[menu.Level]; !ok {
 				level[menu.Level] = make([]*operationPb.AddMenuRequest, 0)
 			}
 			level[menu.Level] = append(level[menu.Level], &operationPb.AddMenuRequest{
-				Id:             menu.Id,
-				Name:           menu.Name,
-				ParentId:       menu.ParentId,
-				MenuType:       menu.MenuType,
-				Title:          menu.Title,
-				Path:           menu.Path,
-				IsShow:         menu.IsShow,
-				Component:      menu.Component,
-				Icon:           menu.Icon,
-				Sort:           menu.Sort,
-				Redirect:       menu.Redirect,
-				CreatedAt:      menu.CreatedAt,
-				CratedAtFormat: time.Unix(menu.CreatedAt, 0).Format(model.LayoutTime),
-				Level:          menu.Level,
-				Children:       make([]*operationPb.AddMenuRequest, 0),
+				Id:              int32(menu.Id),
+				Name:            menu.Name,
+				ParentId:        int32(menu.ParentId),
+				MenuType:        menu.MenuType,
+				Title:           menu.Title,
+				Path:            menu.Path,
+				IsShow:          menu.IsShow,
+				Component:       menu.Component,
+				Icon:            menu.Icon,
+				Sort:            menu.Sort,
+				Redirect:        menu.Redirect,
+				CreatedAt:       int32(menu.CreatedAt),
+				CreatedAtFormat: time.Unix(menu.CreatedAt, 0).Format(model.LayoutTime),
+				Level:           menu.Level,
+				Affix:           true,
+				KeepAlive:       true,
+				Children:        make([]*operationPb.AddMenuRequest, 0),
 			})
+			if menu.Level == model.Level3 {
+				systemUserMenuPermissions.Data.MenuButtonsPath = append(systemUserMenuPermissions.Data.MenuButtonsPath, &operationPb.MenuButtonsPath{
+					Path:   menu.Path,
+					MenuId: int32(menu.Id),
+				})
+			}
 		}
 
-		for _, leve3Data := range level[Level3] {
-			for _, leve2Data := range level[Level2] {
+		level2IsShow := make([]int32, 0)
+		for _, leve2Data := range level[model.Level2] {
+			for _, leve3Data := range level[model.Level3] {
+				if leve3Data.ParentId == leve2Data.Id && strings.Contains(leve3Data.Path, ":page") {
+					level2IsShow = append(level2IsShow, leve2Data.Id)
+				}
+
 				if leve3Data.ParentId == leve2Data.Id {
 					if leve2Data.Children == nil {
 						leve2Data.Children = make([]*operationPb.AddMenuRequest, 0)
@@ -96,8 +147,20 @@ func (s *StoreEntry) SystemPermissionsFormatPb(pastureList []*model.GroupPasture
 			}
 		}
 
-		for _, leve2Data := range level[Level2] {
-			for _, leve1Data := range level[Level1] {
+		sort.SliceStable(level[model.Level2], func(i, j int) bool {
+			return level[model.Level2][i].Sort > level[model.Level2][j].Sort
+		})
+		for _, leve2Data := range level[model.Level2] {
+			var info bool
+			for _, v := range level2IsShow {
+				if v == leve2Data.Id {
+					info = true
+				}
+			}
+			if !info {
+				continue
+			}
+			for _, leve1Data := range level[model.Level1] {
 				if leve2Data.ParentId == leve1Data.Id {
 					if leve1Data.Children == nil {
 						leve1Data.Children = make([]*operationPb.AddMenuRequest, 0)
@@ -107,15 +170,19 @@ func (s *StoreEntry) SystemPermissionsFormatPb(pastureList []*model.GroupPasture
 			}
 		}
 
-		systemUserMenuPermissions.MenuList = level[Level1]
+		sort.SliceStable(level[model.Level1], func(i, j int) bool {
+			return level[model.Level1][i].Sort > level[model.Level1][j].Sort
+		})
+
+		systemUserMenuPermissions.Data.MenuList = level[model.Level1]
 		wg.Done()
 	}()
 
 	go func() {
 		for _, v := range mobileList {
-			systemUserMenuPermissions.MobileList = append(systemUserMenuPermissions.MobileList,
+			systemUserMenuPermissions.Data.MobileList = append(systemUserMenuPermissions.Data.MobileList,
 				&operationPb.AddMobileRequest{
-					Id:   v.Id,
+					Id:   uint32(v.Id),
 					Name: v.Name,
 				},
 			)
@@ -130,7 +197,7 @@ func (s *StoreEntry) SystemPermissionsFormatPb(pastureList []*model.GroupPasture
 // GetSystemAllPermissionsList 获取用户相关权限
 func (s *StoreEntry) GetSystemAllPermissionsList(ctx context.Context, roleId int64) *SystemAllPermissionsList {
 	systemAllPermissionsList := &SystemAllPermissionsList{
-		PastureList: make([]*model.SystemPasturePermissions, 0),
+		PastureList: make([]*model.SystemGroupPasturePermissions, 0),
 		MenuList:    make([]*model.SystemMenuPermissions, 0),
 		MobileList:  make([]*model.SystemMobilePermissions, 0),
 	}
@@ -138,8 +205,9 @@ func (s *StoreEntry) GetSystemAllPermissionsList(ctx context.Context, roleId int
 	wg := sync.WaitGroup{}
 	wg.Add(3)
 	go func() {
-		pastureList := make([]*model.SystemPasturePermissions, 0)
-		if err := s.DB.Where("role_id = ?", roleId).Find(&pastureList).Error; err != nil {
+
+		pastureList := make([]*model.SystemGroupPasturePermissions, 0)
+		if err := s.DB.Where("role_id = ? and is_show = ?", roleId, operationPb.IsShow_OK).Find(&pastureList).Error; err != nil {
 			zaplog.Error("SystemPasturePermissions", zap.Any("Err", err))
 		}
 		systemAllPermissionsList.PastureList = pastureList
@@ -148,7 +216,7 @@ func (s *StoreEntry) GetSystemAllPermissionsList(ctx context.Context, roleId int
 
 	go func() {
 		menuList := make([]*model.SystemMenuPermissions, 0)
-		if err := s.DB.Where("role_id = ?", roleId).Find(&menuList).Error; err != nil {
+		if err := s.DB.Where("role_id = ? and is_show = ?", roleId, operationPb.IsShow_OK).Find(&menuList).Error; err != nil {
 			zaplog.Error("SystemMenuPermissions", zap.Any("Err", err))
 		}
 		systemAllPermissionsList.MenuList = menuList
@@ -158,7 +226,7 @@ func (s *StoreEntry) GetSystemAllPermissionsList(ctx context.Context, roleId int
 	// 获取供应商数据
 	go func() {
 		mobileList := make([]*model.SystemMobilePermissions, 0)
-		if err := s.DB.Where("role_id = ?", roleId).Find(&mobileList).Error; err != nil {
+		if err := s.DB.Where("role_id = ? and is_show = ?", roleId, operationPb.IsShow_OK).Find(&mobileList).Error; err != nil {
 			zaplog.Error("SystemMobilePermissions", zap.Any("Err", err))
 		}
 		systemAllPermissionsList.MobileList = mobileList
@@ -169,15 +237,16 @@ func (s *StoreEntry) GetSystemAllPermissionsList(ctx context.Context, roleId int
 }
 
 // GetPastureList 获取******
-func (s *StoreEntry) GetPastureList(ctx context.Context, req []*model.SystemPasturePermissions) ([]*model.GroupPasture, error) {
+func (s *StoreEntry) GetPastureList(ctx context.Context, req []*model.SystemGroupPasturePermissions) ([]*model.GroupPasture, error) {
 	ids := make([]int64, 0)
 	for _, v := range req {
 		ids = append(ids, v.PastureId)
 	}
-
 	groupPastureList := make([]*model.GroupPasture, 0)
-	if err := s.DB.Find(&groupPastureList, ids).Error; err != nil {
-		return nil, xerr.WithStack(err)
+	if len(ids) > 0 {
+		if err := s.DB.Where("is_show = ?", operationPb.IsShow_OK).Find(&groupPastureList, ids).Error; err != nil {
+			return nil, xerr.WithStack(err)
+		}
 	}
 	return groupPastureList, nil
 }
@@ -190,9 +259,12 @@ func (s *StoreEntry) GetMenuList(ctx context.Context, req []*model.SystemMenuPer
 	}
 
 	menuList := make([]*model.SystemMenu, 0)
-	if err := s.DB.Find(&menuList, ids).Error; err != nil {
-		return nil, xerr.WithStack(err)
+	if len(ids) > 0 {
+		if err := s.DB.Where("is_show = ?", operationPb.IsShow_OK).Find(&menuList, ids).Error; err != nil {
+			return nil, xerr.WithStack(err)
+		}
 	}
+
 	return menuList, nil
 }
 
@@ -202,10 +274,12 @@ func (s *StoreEntry) GetMobileList(ctx context.Context, req []*model.SystemMobil
 	for _, v := range req {
 		ids = append(ids, v.MobileId)
 	}
-
 	mobileList := make([]*model.SystemMobile, 0)
-	if err := s.DB.Find(&mobileList, ids).Error; err != nil {
-		return nil, xerr.WithStack(err)
+	if len(ids) > 0 {
+		if err := s.DB.Where("is_show = ?", operationPb.IsShow_OK).Find(&mobileList, ids).Error; err != nil {
+			return nil, xerr.WithStack(err)
+		}
 	}
+
 	return mobileList, nil
 }

+ 245 - 39
module/backend/system_service.go

@@ -9,6 +9,7 @@ import (
 	"kpt-tmr-group/pkg/tool"
 	"kpt-tmr-group/pkg/xerr"
 	operationPb "kpt-tmr-group/proto/go/backend/operation"
+	"net/http"
 	"strconv"
 	"strings"
 
@@ -16,7 +17,7 @@ import (
 )
 
 // Auth 用户登录
-func (s *StoreEntry) Auth(ctx context.Context, auth *operationPb.UserAuth) (*operationPb.SystemToken, error) {
+func (s *StoreEntry) Auth(ctx context.Context, auth *operationPb.UserAuthData) (*operationPb.SystemToken, error) {
 	systemUser := &model.SystemUser{}
 
 	if err := s.DB.Where("name = ?", auth.UserName).Find(systemUser).Error; err != nil {
@@ -35,7 +36,9 @@ func (s *StoreEntry) Auth(ctx context.Context, auth *operationPb.UserAuth) (*ope
 	}
 
 	return &operationPb.SystemToken{
-		Token: token,
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.TokenData{Token: token},
 	}, nil
 }
 
@@ -55,7 +58,7 @@ func (s *StoreEntry) GetUserInfo(ctx context.Context, token string) (*operationP
 	}
 
 	systemRole := make([]*model.SystemRole, 0)
-
+	pastureList := make([]*operationPb.UserPasture, 0)
 	roleIdStr := strings.Split(systemUser.RoleIds, ",")
 	if len(roleIdStr) > 0 {
 		roleIds := make([]int, 0)
@@ -64,12 +67,17 @@ func (s *StoreEntry) GetUserInfo(ctx context.Context, token string) (*operationP
 			roleIds = append(roleIds, roleId)
 		}
 
+		if err = s.DB.Model(new(model.SystemGroupPasturePermissions)).Select("DISTINCT(group_pasture.id) AS did, group_pasture.id as id,group_pasture.name as name").
+			Where("system_group_pasture_permissions.role_id IN ?", roleIds).Where("system_group_pasture_permissions.is_show = ?", operationPb.IsShow_OK).
+			Joins("right join group_pasture  on system_group_pasture_permissions.pasture_id = group_pasture.id").Find(&pastureList).Debug().Error; err != nil {
+		}
+
 		if err = s.DB.Find(&systemRole, roleIds).Error; err != nil {
 			return nil, xerr.WithStack(err)
 		}
 	}
 
-	return systemUser.SystemUserFormat(systemRole), nil
+	return systemUser.SystemUserFormat(systemRole, pastureList), nil
 }
 
 // CreateSystemUser 创建系统用户
@@ -81,7 +89,8 @@ func (s *StoreEntry) CreateSystemUser(ctx context.Context, req *operationPb.AddS
 		Phone:        req.Phone,
 		Password:     tool.Md5String(model.InitManagerPassword),
 		CreateUser:   req.CreateUser,
-		IsShow:       req.IsShow,
+		IsShow:       operationPb.IsShow_OK,
+		IsDelete:     operationPb.IsShow_OK,
 	}
 	systemUsers.SystemUserRoleFormat(req)
 
@@ -92,8 +101,8 @@ func (s *StoreEntry) CreateSystemUser(ctx context.Context, req *operationPb.AddS
 }
 
 // SearchSystemUserList 查询系统用户
-func (s *StoreEntry) SearchSystemUserList(ctx context.Context, req *operationPb.SearchUserRequest) (*model.SystemUserResponse, error) {
-	systemUser := make([]*model.SystemUser, 0)
+func (s *StoreEntry) SearchSystemUserList(ctx context.Context, req *operationPb.SearchUserRequest) (*operationPb.SearchUserResponse, error) {
+	systemUserList := make([]*model.SystemUser, 0)
 	var count int64 = 0
 
 	pref := s.DB.Model(new(model.SystemUser)).Where("is_delete = ?", operationPb.IsShow_OK)
@@ -108,21 +117,35 @@ func (s *StoreEntry) SearchSystemUserList(ctx context.Context, req *operationPb.
 		pref.Where("is_show = ?", req.IsShow)
 	}
 
+	if req.CreatedStartTime > 0 && req.CreatedEndTime > 0 && req.CreatedStartTime <= req.CreatedEndTime {
+		pref.Where("created_at  BETWEEN  ? AND ?", req.CreatedStartTime, req.CreatedEndTime)
+	}
+
 	if err := pref.Order("id desc").Count(&count).Limit(int(req.Pagination.PageSize)).Offset(int(req.Pagination.PageOffset)).
-		Find(&systemUser).Debug().Error; err != nil {
+		Find(&systemUserList).Debug().Error; err != nil {
 		return nil, xerr.WithStack(err)
 	}
 
-	return &model.SystemUserResponse{
-		Page:  req.Pagination.Page,
-		Total: int32(count),
-		List:  model.SystemUserSlice(systemUser).ToPB(),
+	roleList, err := s.SearchSystemRoleListByIds(ctx, []int64{})
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	return &operationPb.SearchUserResponse{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.SearchUserData{
+			Page:     req.Pagination.Page,
+			Total:    int32(count),
+			PageSize: req.Pagination.PageSize,
+			List:     model.SystemUserSlice(systemUserList).ToPB(roleList),
+		},
 	}, nil
 }
 
 // EditSystemUser 编辑用户
 func (s *StoreEntry) EditSystemUser(ctx context.Context, req *operationPb.AddSystemUser) error {
-	systemUser := &model.SystemUser{Id: req.Id}
+	systemUser := &model.SystemUser{Id: int64(req.Id)}
 	if err := s.DB.First(systemUser).Error; err != nil {
 		if errors.Is(err, gorm.ErrRecordNotFound) {
 			return xerr.Custom("该数据不存在!")
@@ -165,10 +188,47 @@ func (s *StoreEntry) DeleteSystemUser(ctx context.Context, userId int64) error {
 	return nil
 }
 
+// ResetPasswordSystemUser 重置系统用户密码
+func (s *StoreEntry) ResetPasswordSystemUser(ctx context.Context, userId int64) error {
+	systemUser := &model.SystemUser{
+		Id: userId,
+	}
+	if err := s.DB.First(systemUser).Error; err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			return xerr.Custom("该用户不存在")
+		}
+		return xerr.WithStack(err)
+	}
+
+	if err := s.DB.Model(systemUser).Update("password", tool.Md5String(model.InitManagerPassword)).Error; err != nil {
+		return xerr.WithStack(err)
+	}
+	return nil
+}
+
+// DetailsSystemUser 系统用户详情
+func (s *StoreEntry) DetailsSystemUser(ctx context.Context, userId int64) (*operationPb.UserDetails, error) {
+	systemUser := &model.SystemUser{
+		Id: userId,
+	}
+	if err := s.DB.First(systemUser).Error; err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			return nil, xerr.Custom("该用户不存在")
+		}
+		return nil, xerr.WithStack(err)
+	}
+
+	return &operationPb.UserDetails{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: systemUser.ToPb(),
+	}, nil
+}
+
 // IsShowSystemUser 用户是否启用
 func (s *StoreEntry) IsShowSystemUser(ctx context.Context, req *operationPb.IsShowSystemUserRequest) error {
 	systemUser := &model.SystemUser{
-		Id: req.UserId,
+		Id: int64(req.UserId),
 	}
 	if err := s.DB.First(systemUser).Error; err != nil {
 		if errors.Is(err, gorm.ErrRecordNotFound) {
@@ -207,12 +267,12 @@ func (s *StoreEntry) GetSystemUserPermissions(ctx context.Context, token string)
 
 	// 获取用户角色数据
 	systemRoles := make([]*model.SystemRole, 0)
-	if err = s.DB.Find(&systemRoles, roleIds).Error; err != nil {
+	if err = s.DB.Where("is_show = ?", operationPb.IsShow_OK).Find(&systemRoles, roleIds).Error; err != nil {
 		return nil, xerr.WithStack(err)
 	}
 
 	systemAllPermissionsList := &SystemAllPermissionsList{
-		PastureList: make([]*model.SystemPasturePermissions, 0),
+		PastureList: make([]*model.SystemGroupPasturePermissions, 0),
 		MenuList:    make([]*model.SystemMenuPermissions, 0),
 		MobileList:  make([]*model.SystemMobilePermissions, 0),
 	}
@@ -269,7 +329,7 @@ func (s *StoreEntry) CreateSystemRole(ctx context.Context, req *operationPb.AddR
 
 		// 创建角色牧场端权限数据
 		if len(req.PastureId) > 0 {
-			pasturePermissions := model.NewSystemPasturePermissions(role.Id, req.PastureId)
+			pasturePermissions := model.NewSystemGroupPasturePermissions(role.Id, req.PastureId)
 			if err := tx.Create(pasturePermissions).Error; err != nil {
 				return xerr.WithStack(err)
 			}
@@ -283,7 +343,7 @@ func (s *StoreEntry) CreateSystemRole(ctx context.Context, req *operationPb.AddR
 
 // EditSystemRole 编辑角色
 func (s *StoreEntry) EditSystemRole(ctx context.Context, req *operationPb.AddRoleRequest) error {
-	role := &model.SystemRole{Id: req.Id}
+	role := &model.SystemRole{Id: int64(req.Id)}
 	if err := s.DB.First(role).Error; err != nil {
 		if errors.Is(err, gorm.ErrRecordNotFound) {
 			return xerr.Custom("该数据不存在")
@@ -291,10 +351,50 @@ func (s *StoreEntry) EditSystemRole(ctx context.Context, req *operationPb.AddRol
 		return xerr.WithStack(err)
 	}
 
-	updateSystemRole := model.NewSystemRole(req)
-	if err := s.DB.Omit("is_show").
-		Where("id = ?", role.Id).
-		Updates(updateSystemRole).Error; err != nil {
+	if err := s.DB.Transaction(func(tx *gorm.DB) error {
+		updateSystemRole := model.NewSystemRole(req)
+		if err := tx.Omit("is_show").
+			Where("id = ?", role.Id).
+			Updates(updateSystemRole).Error; err != nil {
+			return xerr.WithStack(err)
+		}
+
+		// 更新牧场权限关系表
+		pastureIds := req.PastureId
+		if err := tx.Model(new(model.SystemGroupPasturePermissions)).
+			Where("role_id = ?", req.Id).Update("is_show", operationPb.IsShow_NO).Error; err != nil {
+			return xerr.WithStack(err)
+		}
+
+		newSystemGroupPasturePermissions := model.NewSystemGroupPasturePermissions(int64(req.Id), pastureIds)
+		if err := tx.Create(newSystemGroupPasturePermissions).Error; err != nil {
+			return xerr.WithStack(err)
+		}
+
+		// 更新菜单权限关系表
+		menuIds := req.MenuId
+		if err := tx.Model(new(model.SystemMenuPermissions)).
+			Where("role_id = ?", req.Id).Update("is_show", operationPb.IsShow_NO).Error; err != nil {
+			return xerr.WithStack(err)
+		}
+
+		newSystemMenuPermissions := model.NewSystemMenuPermissions(int64(req.Id), menuIds)
+		if err := tx.Create(newSystemMenuPermissions).Error; err != nil {
+			return xerr.WithStack(err)
+		}
+		// 更新移动端权限关系表
+		mobileIds := req.MobileId
+		if err := tx.Model(new(model.SystemMobilePermissions)).
+			Where("role_id = ?", req.Id).Update("is_show", operationPb.IsShow_NO).Error; err != nil {
+			return xerr.WithStack(err)
+		}
+
+		newSystemMobilePermissions := model.NewSystemMobilePermissions(int64(req.Id), mobileIds)
+		if err := tx.Create(newSystemMobilePermissions).Error; err != nil {
+			return xerr.WithStack(err)
+		}
+		return nil
+	}); err != nil {
 		return xerr.WithStack(err)
 	}
 
@@ -320,7 +420,7 @@ func (s *StoreEntry) DeleteSystemRole(ctx context.Context, roleId int64) error {
 }
 
 // SearchSystemRoleList 查询系统角色
-func (s *StoreEntry) SearchSystemRoleList(ctx context.Context, req *operationPb.SearchRoleRequest) (*model.SystemRoleResponse, error) {
+func (s *StoreEntry) SearchSystemRoleList(ctx context.Context, req *operationPb.SearchRoleRequest) (*operationPb.SearchRoleResponse, error) {
 	systemRole := make([]*model.SystemRole, 0)
 	var count int64 = 0
 
@@ -334,13 +434,54 @@ func (s *StoreEntry) SearchSystemRoleList(ctx context.Context, req *operationPb.
 		return nil, xerr.WithStack(err)
 	}
 
-	return &model.SystemRoleResponse{
-		Page:  req.Pagination.Page,
-		Total: int32(count),
-		List:  model.SystemRoleSlice(systemRole).ToPB(),
+	return &operationPb.SearchRoleResponse{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.SearchRoleData{
+			Page:     req.Pagination.Page,
+			Total:    int32(count),
+			PageSize: req.Pagination.PageSize,
+			List:     model.SystemRoleSlice(systemRole).ToPB(),
+		},
 	}, nil
 }
 
+// SearchSystemRoleListByIds 根据id查询角色列表
+func (s *StoreEntry) SearchSystemRoleListByIds(ctx context.Context, ids []int64) ([]*model.SystemRole, error) {
+	systemRoleList := make([]*model.SystemRole, 0)
+	if err := s.DB.Model(new(model.SystemRole)).Where("is_show = ?", operationPb.IsShow_OK).Find(&systemRoleList, ids).Error; err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	return systemRoleList, nil
+}
+
+// GetRolePermissions 查询系统角色权限
+func (s *StoreEntry) GetRolePermissions(ctx context.Context, roleId int64) (*operationPb.RolePermissionsList, error) {
+	systemRole := &model.SystemRole{
+		Id: roleId,
+	}
+	if err := s.DB.First(systemRole).Error; err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			return nil, xerr.Custom("该数据不存在")
+		}
+		return nil, xerr.WithStack(err)
+	}
+
+	systemAllPermissionsList := &SystemAllPermissionsList{
+		PastureList: make([]*model.SystemGroupPasturePermissions, 0),
+		MenuList:    make([]*model.SystemMenuPermissions, 0),
+		MobileList:  make([]*model.SystemMobilePermissions, 0),
+	}
+
+	item := s.GetSystemAllPermissionsList(ctx, systemRole.Id)
+	systemAllPermissionsList.PastureList = append(systemAllPermissionsList.PastureList, item.PastureList...)
+	systemAllPermissionsList.MenuList = append(systemAllPermissionsList.MenuList, item.MenuList...)
+	systemAllPermissionsList.MobileList = append(systemAllPermissionsList.MobileList, item.MobileList...)
+
+	return s.AllPermissionsListToRolePermissions(systemAllPermissionsList), nil
+}
+
 // CreateSystemMenu 添加系统菜单权限
 func (s *StoreEntry) CreateSystemMenu(ctx context.Context, req *operationPb.AddMenuRequest) error {
 	systemMenu := model.NewSystemMenu(req)
@@ -352,7 +493,7 @@ func (s *StoreEntry) CreateSystemMenu(ctx context.Context, req *operationPb.AddM
 
 // EditSystemMenu 编辑系统菜单权限
 func (s *StoreEntry) EditSystemMenu(ctx context.Context, req *operationPb.AddMenuRequest) error {
-	systemMenu := &model.SystemMenu{Id: req.Id}
+	systemMenu := &model.SystemMenu{Id: int64(req.Id)}
 	if err := s.DB.First(systemMenu).Error; err != nil {
 		if errors.Is(err, gorm.ErrRecordNotFound) {
 			return xerr.Custom("该数据不存在!")
@@ -369,7 +510,7 @@ func (s *StoreEntry) EditSystemMenu(ctx context.Context, req *operationPb.AddMen
 		Icon:      req.Icon,
 		Sort:      req.Sort,
 		Redirect:  req.Redirect,
-		ParentId:  req.ParentId,
+		ParentId:  int64(req.ParentId),
 	}
 
 	if err := s.DB.Model(new(model.SystemMenu)).Omit("is_show").
@@ -383,7 +524,7 @@ func (s *StoreEntry) EditSystemMenu(ctx context.Context, req *operationPb.AddMen
 
 // IsShowSystemMenu 菜单是否启用
 func (s *StoreEntry) IsShowSystemMenu(ctx context.Context, req *operationPb.IsShowSystemMenuRequest) error {
-	systemMenu := &model.SystemMenu{Id: req.MenuId}
+	systemMenu := &model.SystemMenu{Id: int64(req.MenuId)}
 	if err := s.DB.First(systemMenu).Error; err != nil {
 		if errors.Is(err, gorm.ErrRecordNotFound) {
 			return xerr.Custom("该数据不存在")
@@ -398,27 +539,65 @@ func (s *StoreEntry) IsShowSystemMenu(ctx context.Context, req *operationPb.IsSh
 }
 
 // SearchSystemMenuList 菜单列表查询
-func (s *StoreEntry) SearchSystemMenuList(ctx context.Context, req *operationPb.SearchMenuRequest) (*model.SystemMenuResponse, error) {
-	systemMenu := make([]*model.SystemMenu, 0)
+func (s *StoreEntry) SearchSystemMenuList(ctx context.Context, req *operationPb.SearchMenuRequest) (*operationPb.SearchMenuResponse, error) {
+	systemMenuLevel1 := make([]*model.SystemMenu, 0)
 	var count int64 = 0
 
-	pref := s.DB.Model(new(model.SystemMenu)).Where("is_delete = ?", operationPb.IsShow_OK)
+	pref := s.DB.Model(new(model.SystemMenu)).Where("is_delete = ? and level = ?", operationPb.IsShow_OK, model.Level1)
 	if req.Name != "" {
 		pref.Where("name like ?", fmt.Sprintf("%s%s%s", "%", req.Name, "%"))
 	}
 
-	if err := pref.Order("id desc").Count(&count).Limit(int(req.Pagination.PageSize)).Offset(int(req.Pagination.PageOffset)).
-		Find(&systemMenu).Debug().Error; err != nil {
+	if err := pref.Order("sort desc").Count(&count).Limit(int(req.Pagination.PageSize)).Offset(int(req.Pagination.PageOffset)).
+		Find(&systemMenuLevel1).Debug().Error; err != nil {
 		return nil, xerr.WithStack(err)
 	}
 
-	return &model.SystemMenuResponse{
-		Page:  req.Pagination.Page,
-		Total: int32(count),
-		List:  model.SystemMenuSlice(systemMenu).ToPB(),
+	systemMenuLevel1 = append(systemMenuLevel1, s.searchMenuLevel23ByLevel1(ctx, systemMenuLevel1)...)
+	return &operationPb.SearchMenuResponse{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.SearchMenuData{
+			Page:  req.Pagination.Page,
+			Total: int32(count),
+			List:  model.SystemMenuSlice(systemMenuLevel1).ToPB(),
+		},
 	}, nil
 }
 
+// searchMenuLevel23ByLevel1 根据一级菜单返回对应二三级菜单
+func (s *StoreEntry) searchMenuLevel23ByLevel1(ctx context.Context, res []*model.SystemMenu) []*model.SystemMenu {
+	systemMenuLevel23 := make([]*model.SystemMenu, 0)
+	if len(res) <= 0 {
+		return systemMenuLevel23
+	}
+
+	ids1 := make([]int64, 0)
+	for _, v := range res {
+		ids1 = append(ids1, v.Id)
+	}
+	if err := s.DB.Model(new(model.SystemMenu)).Where("is_delete = ? and level = ?", operationPb.IsShow_OK, model.Level2).
+		Where("parent_id IN ?", ids1).Order("sort desc").
+		Find(&systemMenuLevel23).Error; err != nil {
+		return systemMenuLevel23
+	}
+	ids2 := make([]int64, 0)
+	for _, v := range systemMenuLevel23 {
+		ids2 = append(ids2, v.Id)
+	}
+	systemMenuLevel3 := make([]*model.SystemMenu, 0)
+	if err := s.DB.Model(new(model.SystemMenu)).Where("is_delete = ? and level = ?", operationPb.IsShow_OK, model.Level3).
+		Where("parent_id IN ?", ids2).Order("sort desc").
+		Find(&systemMenuLevel3).Error; err != nil {
+		return systemMenuLevel23
+	}
+	if len(systemMenuLevel3) > 0 {
+		systemMenuLevel23 = append(systemMenuLevel23, systemMenuLevel3...)
+	}
+
+	return systemMenuLevel23
+}
+
 // DeleteSystemMenu 删除系统菜单
 func (s *StoreEntry) DeleteSystemMenu(ctx context.Context, menuId int64) error {
 	systemMenu := &model.SystemMenu{Id: menuId}
@@ -434,3 +613,30 @@ func (s *StoreEntry) DeleteSystemMenu(ctx context.Context, menuId int64) error {
 	}
 	return nil
 }
+
+// SearchMobileList 查询移动端角色
+func (s *StoreEntry) SearchMobileList(ctx context.Context, req *operationPb.SearchMobileRequest) (*operationPb.SearchMobileResponse, error) {
+	systemMobile := make([]*model.SystemMobile, 0)
+	var count int64 = 0
+
+	pref := s.DB.Model(new(model.SystemMobile)).Where("is_show = ?", operationPb.IsShow_OK)
+	if req.Name != "" {
+		pref.Where("name like ?", fmt.Sprintf("%s%s%s", "%", req.Name, "%"))
+	}
+
+	if err := pref.Order("id desc").Count(&count).Limit(int(req.Pagination.PageSize)).Offset(int(req.Pagination.PageOffset)).
+		Find(&systemMobile).Debug().Error; err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	return &operationPb.SearchMobileResponse{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.SearchMobileData{
+			Page:     req.Pagination.Page,
+			Total:    int32(count),
+			PageSize: req.Pagination.PageSize,
+			List:     model.SystemMobileSlice(systemMobile).ToPB(),
+		},
+	}, nil
+}

+ 34 - 0
module/backend/wx_applet_service.go

@@ -0,0 +1,34 @@
+package backend
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"kpt-tmr-group/model"
+	"kpt-tmr-group/pkg/logger/zaplog"
+	"kpt-tmr-group/pkg/xerr"
+	operationPb "kpt-tmr-group/proto/go/backend/operation"
+
+	"go.uber.org/zap"
+)
+
+const (
+	GRANT_TYPE = "authorization_code"
+	OPENID_URL = "https://api.weixin.qq.com/sns/jscode2session"
+)
+
+func (s *StoreEntry) GetOpenId(ctx context.Context, jsCode string) (*operationPb.WxOpenId, error) {
+	url := fmt.Sprintf("%s?appid=%s&secret=%s&js_code=%s&grant_type=%s", OPENID_URL, s.WxClient.AppID, s.WxClient.Secret, jsCode, GRANT_TYPE)
+	res, err := s.WxClient.DoGet(url)
+	if err != nil {
+		zaplog.Error("GetOpenId", zap.Any("DoGet", err), zap.String("url", url))
+		return nil, xerr.WithStack(err)
+	}
+	data := &model.JsCode{}
+	if err = json.Unmarshal(res, data); err != nil {
+		zaplog.Error("GetOpenId", zap.Any("Unmarshal", err), zap.String("url", url))
+		return nil, xerr.WithStack(err)
+	}
+	zaplog.Info("GetOpenId", zap.Any("ok", data))
+	return &operationPb.WxOpenId{Openid: data.OpenId}, nil
+}

+ 56 - 0
module/backend/x_suite_test.go

@@ -0,0 +1,56 @@
+package backend
+
+import (
+	"context"
+	"kpt-tmr-group/config"
+	"kpt-tmr-group/store/kptstore"
+	"kpt-tmr-group/test/mock"
+
+	"golang.org/x/sync/errgroup"
+
+	"github.com/golang/mock/gomock"
+	"github.com/stretchr/testify/suite"
+	"go.uber.org/dig"
+)
+
+var dbName = "kpt_tmr_group_test"
+
+type Suite struct {
+	suite.Suite
+
+	ctx  context.Context
+	cfg  *config.AppConfig
+	db   *kptstore.DB
+	ctrl *gomock.Controller
+	m    Mock
+
+	storeEntry *StoreEntry
+}
+
+type Mock struct {
+	dig.In
+}
+
+func (s *Suite) SetupSuite() {
+	s.cfg = config.Options()
+}
+
+func (s *Suite) SetupTest() {
+	s.ctx = context.Background()
+	s.ctrl = gomock.NewController(s.T())
+
+	mock.GetMock(s.ctrl, func(m Mock) { s.m = m })
+	s.storeEntry = NewStoreEntry(s.cfg, s.db)
+}
+
+func (s *Suite) prepareDB() {
+	s.cfg = config.Options()
+	g, _ := errgroup.WithContext(context.Background())
+	g.Go(func() error { // todo 连接数据库
+
+		return nil
+	})
+	if err := g.Wait(); err != nil {
+		panic(err)
+	}
+}

+ 2 - 2
pkg/apiok/common.go

@@ -1,6 +1,6 @@
 package apiok
 
-type OkResponse struct {
+/*type OkResponse struct {
 	Code int         `json:"code"`
 	Msg  string      `json:"msg"`
 	Data interface{} `json:"data"`
@@ -13,7 +13,7 @@ func CommonResponse(data interface{}) *OkResponse {
 		Data: data,
 	}
 }
-
+*/
 type ApiOk struct {
 	Success bool `json:"success"`
 }

+ 4 - 4
pkg/di/xreflect/reflect.go

@@ -144,12 +144,12 @@ func shouldIgnoreFrame(f Frame) bool {
 
 	// The unique, fully-qualified name for all functions begins with
 	// "{{importPath}}.". We'll ignore di and its subpackages.
-	s := strings.TrimPrefix(f.Function, "git.llsapp.com/zhenghe/pkg/di")
+	s := strings.TrimPrefix(f.Function, "kpt-tmr-group/pkg/di")
 	if len(s) > 0 && s[0] == '.' || s[0] == '/' {
 		// We want to match,
-		//   git.llsapp.com/zhenghe/pkg/di.Foo
-		//   git.llsapp.com/zhenghe/pkg/di/something.Foo
-		// But not, git.llsapp.com/zhenghe/pkg/difoo
+		//   kpt-tmr-group/pkg/di.Foo
+		//   kpt-tmr-group/pkg/di/something.Foo
+		// But not, kpt-tmr-group/pkg/difoo
 		return true
 	}
 

+ 2 - 2
pkg/di/xreflect/reflect_test.go

@@ -83,7 +83,7 @@ func TestReturnTypes(t *testing.T) {
 }
 
 func TestCaller(t *testing.T) {
-	assert.Equal(t, "git.llsapp.com/zhenghe/pkg/di/xreflect.TestCaller", Caller())
+	assert.Equal(t, "kpt-tmr-group/pkg/di/xreflect.TestCaller", Caller())
 }
 
 func someFunc() {}
@@ -97,7 +97,7 @@ func TestFuncName(t *testing.T) {
 		{
 			desc: "function",
 			give: someFunc,
-			want: "git.llsapp.com/zhenghe/pkg/di/xreflect.someFunc()",
+			want: "kpt-tmr-group/pkg/di/xreflect.someFunc()",
 		},
 		{
 			desc: "not a function",

+ 11 - 11
pkg/di/xreflect/stack_test.go

@@ -38,7 +38,7 @@ func TestStack(t *testing.T) {
 		frames := CallerStack(0, 0)
 		require.NotEmpty(t, frames)
 		f := frames[0]
-		assert.Equal(t, "git.llsapp.com/zhenghe/pkg/di/xreflect.TestStack.func1", f.Function)
+		assert.Equal(t, "kpt-tmr-group/pkg/di/xreflect.TestStack.func1", f.Function)
 		assert.Contains(t, f.File, "xreflect/stack_test.go")
 		assert.NotZero(t, f.Line)
 	})
@@ -54,7 +54,7 @@ func TestStack(t *testing.T) {
 		require.True(t, len(frames) > 3, "expected at least three frames")
 		for i, name := range []string{"func2.1.1", "func2.1", "func2"} {
 			f := frames[i]
-			assert.Equal(t, "git.llsapp.com/zhenghe/pkg/di/xreflect.TestStack."+name, f.Function)
+			assert.Equal(t, "kpt-tmr-group/pkg/di/xreflect.TestStack."+name, f.Function)
 			assert.Contains(t, f.File, "xreflect/stack_test.go")
 			assert.NotZero(t, f.Line)
 		}
@@ -70,7 +70,7 @@ func TestStack(t *testing.T) {
 
 		require.NotEmpty(t, frames)
 		f := frames[0]
-		assert.Equal(t, "git.llsapp.com/zhenghe/pkg/di/xreflect.TestStack.func3", f.Function)
+		assert.Equal(t, "kpt-tmr-group/pkg/di/xreflect.TestStack.func3", f.Function)
 		assert.Contains(t, f.File, "xreflect/stack_test.go")
 		assert.NotZero(t, f.Line)
 	})
@@ -87,8 +87,8 @@ func TestStackCallerName(t *testing.T) {
 			desc: "skip di components",
 			give: Stack{
 				{
-					Function: "git.llsapp.com/zhenghe/pkg/di.Foo()",
-					File:     "git.llsapp.com/zhenghe/pkg/di/foo.go",
+					Function: "kpt-tmr-group/pkg/di.Foo()",
+					File:     "kpt-tmr-group/pkg/di/foo.go",
 				},
 				{
 					Function: "foo/bar.Baz()",
@@ -101,7 +101,7 @@ func TestStackCallerName(t *testing.T) {
 			desc: "skip di in wrong directory",
 			give: Stack{
 				{
-					Function: "git.llsapp.com/zhenghe/pkg/di/di.Foo()",
+					Function: "kpt-tmr-group/pkg/di/di.Foo()",
 					File:     "di/foo.go",
 				},
 				{
@@ -115,7 +115,7 @@ func TestStackCallerName(t *testing.T) {
 			desc: "skip di subpackage",
 			give: Stack{
 				{
-					Function: "git.llsapp.com/zhenghe/pkg/di/xreflect.Foo()",
+					Function: "kpt-tmr-group/pkg/di/xreflect.Foo()",
 					File:     "di/internal/xreflect/foo.go",
 				},
 				{
@@ -130,7 +130,7 @@ func TestStackCallerName(t *testing.T) {
 			give: Stack{
 				{
 					Function: "some/thing.Foo()",
-					File:     "git.llsapp.com/zhenghe/pkg/di/foo_test.go",
+					File:     "kpt-tmr-group/pkg/di/foo_test.go",
 				},
 			},
 			want: "some/thing.Foo()",
@@ -139,11 +139,11 @@ func TestStackCallerName(t *testing.T) {
 			desc: "don't skip di prefix",
 			give: Stack{
 				{
-					Function: "git.llsapp.com/zhenghe/pkg/difoo.Bar()",
-					File:     "git.llsapp.com/zhenghe/pkg/difoo/bar.go",
+					Function: "kpt-tmr-group/pkg/difoo.Bar()",
+					File:     "kpt-tmr-group/pkg/difoo/bar.go",
 				},
 			},
-			want: "git.llsapp.com/zhenghe/pkg/difoo.Bar()",
+			want: "kpt-tmr-group/pkg/difoo.Bar()",
 		},
 	}
 

+ 68 - 0
pkg/ginutil/bind.go

@@ -0,0 +1,68 @@
+package ginutil
+
+import (
+	"net/http"
+	"reflect"
+
+	"kpt-tmr-group/pkg/jsonpb"
+	"kpt-tmr-group/pkg/xerr"
+
+	"github.com/gin-gonic/gin"
+	"github.com/gin-gonic/gin/binding"
+	"github.com/golang/protobuf/proto"
+	"github.com/huandu/xstrings"
+)
+
+var camelQuery = &CamelQueryBinding{}
+
+// BindQuery with query params
+func BindQuery(c *gin.Context, obj interface{}) error {
+	return c.ShouldBindWith(obj, camelQuery)
+}
+
+type CamelQueryBinding struct{}
+
+func (*CamelQueryBinding) Name() string {
+	return "camel_query"
+}
+
+func (*CamelQueryBinding) Bind(req *http.Request, obj interface{}) error {
+	values := req.URL.Query()
+	if err := mapFormByTag(obj, values, "json"); err != nil {
+		return err
+	}
+	return binding.Validator.ValidateStruct(obj)
+}
+
+type camelFormSource map[string][]string
+
+// TrySet tries to set a value by request's form source (like map[string][]string)
+func (form camelFormSource) TrySet(value reflect.Value, field reflect.StructField, tagValue string, opt setOptions) (isSetted bool, err error) {
+	return setByForm(value, field, form, xstrings.FirstRuneToLower(xstrings.ToCamelCase(tagValue)), opt)
+}
+
+// BindProtoMessage with proto message from json body
+func BindProtoMessage(c *gin.Context, obj proto.Message) error {
+	return c.ShouldBindWith(obj, protoMessageBindingFromBody)
+}
+
+var protoMessageBindingFromBody = &ProtoMessageBinding{}
+
+type ProtoMessageBinding struct{}
+
+func (*ProtoMessageBinding) Name() string {
+	return "proto_message_binding"
+}
+
+func (*ProtoMessageBinding) Bind(req *http.Request, obj interface{}) error {
+	pbMessage, ok := obj.(proto.Message)
+	if !ok {
+		return xerr.New("bind obj should be proto.Message")
+	}
+
+	if err := jsonpb.Unmarshal(req.Body, pbMessage); err != nil {
+		return xerr.WithStack(err)
+	}
+
+	return binding.Validator.ValidateStruct(obj)
+}

+ 34 - 0
pkg/ginutil/bind_proto.go

@@ -0,0 +1,34 @@
+package ginutil
+
+import (
+	"net/http"
+
+	"kpt-tmr-group/pkg/jsonpb"
+	"kpt-tmr-group/pkg/xerr"
+
+	"github.com/gin-gonic/gin"
+	"github.com/golang/protobuf/proto"
+)
+
+type ProtoMessageQueryBinding struct{}
+
+func BindQueryProto(c *gin.Context, pb proto.Message) error {
+	values := c.Request.URL.Query()
+	if err := jsonpb.UnmarshalQuery(values, pb); err != nil {
+		return xerr.WithStack(err)
+	}
+	return nil
+}
+
+func BindProto(c *gin.Context, pb proto.Message) (err error) {
+	switch c.Request.Method {
+	case http.MethodGet, http.MethodDelete:
+		err = BindQueryProto(c, pb)
+	default:
+		err = BindProtoMessage(c, pb)
+	}
+	if err != nil {
+		return xerr.WithStack(err)
+	}
+	return
+}

+ 19 - 0
pkg/ginutil/json_proto_response.go

@@ -0,0 +1,19 @@
+package ginutil
+
+import (
+	"kpt-tmr-group/pkg/apierr"
+	"kpt-tmr-group/pkg/jsonpb"
+	"net/http"
+
+	"github.com/gin-gonic/gin"
+	"github.com/golang/protobuf/proto"
+)
+
+func JSONResp(c *gin.Context, pb proto.Message) {
+	bs, err := jsonpb.MarshalBytes(pb)
+	if err != nil {
+		apierr.AbortInternalError(c, http.StatusInternalServerError, err)
+		return
+	}
+	c.Data(http.StatusOK, "application/json", bs)
+}

+ 322 - 0
pkg/ginutil/setter.go

@@ -0,0 +1,322 @@
+package ginutil
+
+import (
+	"encoding/json"
+	"errors"
+	"reflect"
+	"strconv"
+	"strings"
+	"time"
+
+	"kpt-tmr-group/pkg/xerr"
+)
+
+var _ setter = camelFormSource(nil)
+
+var emptyField = reflect.StructField{}
+
+// setter tries to set value on a walking by fields of a struct
+type setter interface {
+	TrySet(value reflect.Value, field reflect.StructField, key string, opt setOptions) (isSetted bool, err error)
+}
+
+type setOptions struct {
+	isDefaultExists bool
+	defaultValue    string
+}
+
+func mapFormByTag(ptr interface{}, form map[string][]string, tag string) error {
+	return mappingByPtr(ptr, camelFormSource(form), tag)
+}
+
+func mappingByPtr(ptr interface{}, setter setter, tag string) error {
+	_, err := mapping(reflect.ValueOf(ptr), emptyField, setter, tag)
+	return err
+}
+
+func mapping(value reflect.Value, field reflect.StructField, setter setter, tag string) (bool, error) {
+	var vKind = value.Kind()
+
+	if vKind == reflect.Ptr {
+		var isNew bool
+		vPtr := value
+		if value.IsNil() {
+			isNew = true
+			vPtr = reflect.New(value.Type().Elem())
+		}
+		isSetted, err := mapping(vPtr.Elem(), field, setter, tag)
+		if err != nil {
+			return false, err
+		}
+		if isNew && isSetted {
+			value.Set(vPtr)
+		}
+		return isSetted, nil
+	}
+
+	ok, err := tryToSetValue(value, field, setter, tag)
+	if err != nil {
+		return false, err
+	}
+	if ok {
+		return true, nil
+	}
+
+	if vKind == reflect.Struct {
+		tValue := value.Type()
+
+		var isSetted bool
+		for i := 0; i < value.NumField(); i++ {
+			if !value.Field(i).CanSet() {
+				continue
+			}
+			ok, err := mapping(value.Field(i), tValue.Field(i), setter, tag)
+			if err != nil {
+				return false, err
+			}
+			isSetted = isSetted || ok
+		}
+		return isSetted, nil
+	}
+	return false, nil
+}
+
+func tryToSetValue(value reflect.Value, field reflect.StructField, setter setter, tag string) (bool, error) {
+	var tagValue string
+	var setOpt setOptions
+
+	tagValue = field.Tag.Get(tag)
+	tagValue, opts := head(tagValue, ",")
+
+	if tagValue == "-" { // just ignoring this field
+		return false, nil
+	}
+	if tagValue == "" { // when field is "emptyField" variable
+		return false, nil
+	}
+
+	var opt string
+	for len(opts) > 0 {
+		opt, opts = head(opts, ",")
+
+		k, v := head(opt, "=")
+		switch k {
+		case "default":
+			setOpt.isDefaultExists = true
+			setOpt.defaultValue = v
+		}
+	}
+
+	return setter.TrySet(value, field, tagValue, setOpt)
+}
+
+func setByForm(value reflect.Value, field reflect.StructField, form map[string][]string, tagValue string, opt setOptions) (isSetted bool, err error) {
+	vs, ok := form[tagValue]
+	if !ok && !opt.isDefaultExists {
+		return false, nil
+	}
+
+	switch value.Kind() {
+	case reflect.Slice:
+		if !ok {
+			vs = []string{opt.defaultValue}
+		}
+		return true, setSlice(vs, value, field)
+	case reflect.Array:
+		if !ok {
+			vs = []string{opt.defaultValue}
+		}
+		if len(vs) != value.Len() {
+			return false, xerr.Errorf("%q is not valid value for %s", vs, value.Type().String())
+		}
+		return true, setArray(vs, value, field)
+	default:
+		var val string
+		if !ok {
+			val = opt.defaultValue
+		}
+
+		if len(vs) > 0 {
+			val = vs[0]
+		}
+		return true, setWithProperType(val, value, field)
+	}
+}
+
+var errUnknownType = errors.New("unknown type")
+
+func setWithProperType(val string, value reflect.Value, field reflect.StructField) error {
+	switch value.Kind() {
+	case reflect.Int:
+		return setIntField(val, 0, value)
+	case reflect.Int8:
+		return setIntField(val, 8, value)
+	case reflect.Int16:
+		return setIntField(val, 16, value)
+	case reflect.Int32:
+		return setIntField(val, 32, value)
+	case reflect.Int64:
+		switch value.Interface().(type) {
+		case time.Duration:
+			return setTimeDuration(val, value, field)
+		}
+		return setIntField(val, 64, value)
+	case reflect.Uint:
+		return setUintField(val, 0, value)
+	case reflect.Uint8:
+		return setUintField(val, 8, value)
+	case reflect.Uint16:
+		return setUintField(val, 16, value)
+	case reflect.Uint32:
+		return setUintField(val, 32, value)
+	case reflect.Uint64:
+		return setUintField(val, 64, value)
+	case reflect.Bool:
+		return setBoolField(val, value)
+	case reflect.Float32:
+		return setFloatField(val, 32, value)
+	case reflect.Float64:
+		return setFloatField(val, 64, value)
+	case reflect.String:
+		value.SetString(val)
+	case reflect.Struct:
+		switch value.Interface().(type) {
+		case time.Time:
+			return setTimeField(val, field, value)
+		}
+		return json.Unmarshal([]byte(val), value.Addr().Interface())
+	case reflect.Map:
+		return json.Unmarshal([]byte(val), value.Addr().Interface())
+	default:
+		return errUnknownType
+	}
+	return nil
+}
+
+func setIntField(val string, bitSize int, field reflect.Value) error {
+	if val == "" {
+		val = "0"
+	}
+	intVal, err := strconv.ParseInt(val, 10, bitSize)
+	if err == nil {
+		field.SetInt(intVal)
+	}
+	return xerr.WithStack(err)
+}
+
+func setUintField(val string, bitSize int, field reflect.Value) error {
+	if val == "" {
+		val = "0"
+	}
+	uintVal, err := strconv.ParseUint(val, 10, bitSize)
+	if err == nil {
+		field.SetUint(uintVal)
+	}
+	return xerr.WithStack(err)
+}
+
+func setBoolField(val string, field reflect.Value) error {
+	if val == "" {
+		val = "false"
+	}
+	boolVal, err := strconv.ParseBool(val)
+	if err == nil {
+		field.SetBool(boolVal)
+	}
+	return xerr.WithStack(err)
+}
+
+func setFloatField(val string, bitSize int, field reflect.Value) error {
+	if val == "" {
+		val = "0.0"
+	}
+	floatVal, err := strconv.ParseFloat(val, bitSize)
+	if err == nil {
+		field.SetFloat(floatVal)
+	}
+	return xerr.WithStack(err)
+}
+
+func setTimeField(val string, structField reflect.StructField, value reflect.Value) error {
+	timeFormat := structField.Tag.Get("time_format")
+	if timeFormat == "" {
+		timeFormat = time.RFC3339
+	}
+
+	if val == "" {
+		value.Set(reflect.ValueOf(time.Time{}))
+		return nil
+	}
+
+	l := time.Local
+	if isUTC, _ := strconv.ParseBool(structField.Tag.Get("time_utc")); isUTC {
+		l = time.UTC
+	}
+
+	if locTag := structField.Tag.Get("time_location"); locTag != "" {
+		loc, err := time.LoadLocation(locTag)
+		if err != nil {
+			return xerr.WithStack(err)
+		}
+		l = loc
+	}
+
+	var (
+		t   time.Time
+		err error
+	)
+
+	if isTS, _ := strconv.ParseBool(structField.Tag.Get("time_ts")); isTS {
+		sec, e := strconv.ParseInt(val, 10, 64)
+		if e != nil {
+			return xerr.WithStack(e)
+		}
+		t = time.Unix(sec, 0).In(l)
+	} else {
+		t, err = time.ParseInLocation(timeFormat, val, l)
+	}
+	if err != nil {
+		return xerr.WithStack(err)
+	}
+
+	value.Set(reflect.ValueOf(t))
+	return nil
+}
+
+// TODO: should support array case. eg. myArray[]=a&myArray[]=b
+func setArray(vals []string, value reflect.Value, field reflect.StructField) error {
+	for i, s := range vals {
+		err := setWithProperType(s, value.Index(i), field)
+		if err != nil {
+			return xerr.WithStack(err)
+		}
+	}
+	return nil
+}
+
+func setSlice(vals []string, value reflect.Value, field reflect.StructField) error {
+	slice := reflect.MakeSlice(value.Type(), len(vals), len(vals))
+	err := setArray(vals, slice, field)
+	if err != nil {
+		return xerr.WithStack(err)
+	}
+	value.Set(slice)
+	return nil
+}
+
+func setTimeDuration(val string, value reflect.Value, _ reflect.StructField) error {
+	d, err := time.ParseDuration(val)
+	if err != nil {
+		return xerr.WithStack(err)
+	}
+	value.Set(reflect.ValueOf(d))
+	return nil
+}
+
+func head(str, sep string) (head string, tail string) {
+	idx := strings.Index(str, sep)
+	if idx < 0 {
+		return str, ""
+	}
+	return str[:idx], str[idx+len(sep):]
+}

+ 30 - 0
pkg/jsonpb/decode.go

@@ -0,0 +1,30 @@
+package jsonpb
+
+import (
+	"io"
+
+	"kpt-tmr-group/pkg/xerr"
+
+	"github.com/golang/protobuf/jsonpb"
+	"github.com/golang/protobuf/proto"
+)
+
+var unmarshaler = &jsonpb.Unmarshaler{AllowUnknownFields: true}
+
+func Unmarshal(r io.Reader, pb proto.Message) (err error) {
+	defer func() {
+		if e := recover(); e != nil {
+			err = e.(error)
+		}
+	}()
+
+	if err := unmarshaler.Unmarshal(r, pb); err != nil {
+		return xerr.WithStack(err)
+	}
+	return nil
+}
+
+// Unmarshaler jsonpb unmarshaler
+type Unmarshaler interface {
+	Unmarshal(r io.Reader, pb proto.Message) error
+}

+ 33 - 0
pkg/jsonpb/decode_test.go

@@ -0,0 +1,33 @@
+package jsonpb
+
+import (
+	"testing"
+)
+
+var bs = `{
+  "status": {
+    "code": "SUCCESS"
+  },
+  "done": true,
+  "result": {
+    "ptTimestampUsec": "1558687721507006",
+    "level": 2,
+    "fluencyLevel": 0,
+    "pronunciationLevel": 0
+  },
+  "suggestedActivityId": [],
+  "preloadContent": {
+    "activityContent": []
+  },
+  "cbParam": {
+    "previousPart": 4,
+    "warmed": false,
+    "ptSubPart": 0
+  },
+  "IsWarmup": false
+}
+`
+
+func TestUnmarshal(t *testing.T) {
+
+}

+ 52 - 0
pkg/jsonpb/encode.go

@@ -0,0 +1,52 @@
+package jsonpb
+
+import (
+	"bytes"
+	"sync"
+
+	"github.com/golang/protobuf/jsonpb"
+	"github.com/golang/protobuf/proto"
+)
+
+var marshaller = &jsonpb.Marshaler{EmitDefaults: true, OrigName: true, EnumsAsInts: true}
+
+func Marshal(pb proto.Message) (string, error) {
+	e := newEncodeState()
+	if err := marshaller.Marshal(e, pb); err != nil {
+		return "", err
+	}
+
+	s := e.String()
+	e.Reset()
+	encodeStatePool.Put(e)
+
+	return s, nil
+}
+
+func MarshalBytes(pb proto.Message) ([]byte, error) {
+	e := newEncodeState()
+	if err := marshaller.Marshal(e, pb); err != nil {
+		return nil, err
+	}
+
+	buf := append([]byte(nil), e.Bytes()...)
+	e.Reset()
+	encodeStatePool.Put(e)
+	return buf, nil
+}
+
+// An encodeState encodes proto into a bytes.Buffer.
+type encodeState struct {
+	*bytes.Buffer
+}
+
+var encodeStatePool sync.Pool
+
+func newEncodeState() *encodeState {
+	if v := encodeStatePool.Get(); v != nil {
+		e := v.(*encodeState)
+		e.Reset()
+		return e
+	}
+	return &encodeState{Buffer: bytes.NewBuffer(make([]byte, 0, 2048))}
+}

+ 37 - 0
pkg/jsonpb/encode_test.go

@@ -0,0 +1,37 @@
+package jsonpb
+
+import (
+	"testing"
+
+	"github.com/golang/protobuf/proto"
+)
+
+var message proto.Message
+
+func BenchmarkMarshalWithPool(b *testing.B) {
+	b.ResetTimer()
+
+	for i := 0; i < b.N; i++ {
+		Marshal(message)
+	}
+}
+
+func BenchmarkMarshalBytesWithPool(b *testing.B) {
+	b.ResetTimer()
+
+	for i := 0; i < b.N; i++ {
+		Marshal(message)
+	}
+}
+
+func BenchmarkMarshalToPBString(b *testing.B) {
+	b.ResetTimer()
+
+	for i := 0; i < b.N; i++ {
+		marshalToPBString(message)
+	}
+}
+
+func marshalToPBString(pb proto.Message) (string, error) {
+	return marshaller.MarshalToString(pb)
+}

+ 108 - 0
pkg/jsonpb/query_decode.go

@@ -0,0 +1,108 @@
+package jsonpb
+
+import (
+	"net/url"
+	"reflect"
+	"strings"
+
+	"kpt-tmr-group/pkg/xerr"
+	"kpt-tmr-group/pkg/xreflect"
+
+	"github.com/golang/protobuf/proto"
+)
+
+var emptyField = reflect.StructField{}
+
+func UnmarshalQuery(values url.Values, pb proto.Message) error {
+	target := reflect.ValueOf(pb).Elem()
+	targetType := target.Type()
+	if targetType.Kind() != reflect.Struct {
+		return xerr.New("target should be struct")
+	}
+	sprops := proto.GetProperties(targetType)
+	for i := 0; i < target.NumField(); i++ {
+		ft := target.Type().Field(i)
+		if strings.HasPrefix(ft.Name, "XXX_") {
+			continue
+		}
+		getField := func(prop *proto.Properties) ([]string, bool) {
+			// Be liberal in what names we accept; both orig_name and camelName are okay.
+			camel, orig := prop.JSONName, prop.OrigName
+
+			keys := []string{camel, orig}
+			// handle condition xxx[]=1&xxx[]=2
+			if prop.Repeated {
+				keys = append(keys, camel+"[]", orig+"[]")
+			}
+			for _, key := range keys {
+				v, ok := values[key]
+				if ok {
+					return v, true
+				}
+			}
+			return nil, false
+		}
+
+		valueForField, ok := getField(sprops.Prop[i])
+		if !ok {
+			continue
+		}
+		err := setProtoByQueryValue(target.Field(i), valueForField, sprops.Prop[i])
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func setProtoByQueryValue(target reflect.Value, queryValue []string, prop *proto.Properties) (err error) {
+	if len(queryValue) == 0 {
+		if prop.HasDefault {
+			queryValue = []string{prop.Default}
+		} else {
+			return nil
+		}
+	}
+	if target.Kind() == reflect.Slice {
+		slice := reflect.MakeSlice(target.Type(), len(queryValue), len(queryValue))
+		for i, s := range queryValue {
+			err := setProtoByQueryValue(slice.Index(i), []string{s}, prop)
+			if err != nil {
+				return err
+			}
+		}
+		target.Set(slice)
+		return nil
+	}
+
+	// Handle enums, which have an underlying type of int32,
+	// and may appear as strings.
+	// The case of an enum appearing as a number is handled
+	// at the bottom of this function.
+	if prop != nil && prop.Enum != "" {
+		vmap := proto.EnumValueMap(prop.Enum)
+		if len(queryValue) == 1 {
+			// Don't need to do unquoting; valid enum names
+			// are from a limited character set.
+			s := queryValue[0]
+			n, ok := vmap[s]
+			if !ok {
+				return xerr.Errorf("unknown value %q for enum %s", s, prop.Enum)
+			}
+			return setProtoEnum(n, target, prop)
+		}
+	}
+	return xreflect.SetString(target, queryValue[0])
+}
+
+func setProtoEnum(val int32, target reflect.Value, prop *proto.Properties) error {
+	if target.Kind() == reflect.Ptr { // proto2
+		target.Set(reflect.New(target.Type().Elem()))
+		target = target.Elem()
+	}
+	if target.Kind() != reflect.Int32 {
+		return xerr.Errorf("invalid target %q for enum %s", target.Kind(), prop.Enum)
+	}
+	target.SetInt(int64(val))
+	return nil
+}

+ 9 - 0
pkg/jsonpb/query_decode_test.go

@@ -0,0 +1,9 @@
+package jsonpb
+
+import (
+	"testing"
+)
+
+func TestUnmarshalQuery(t *testing.T) {
+
+}

+ 2 - 3
pkg/jwt/jwt.go

@@ -3,7 +3,6 @@ package jwt
 import (
 	"fmt"
 	"kpt-tmr-group/config"
-	"kpt-tmr-group/pkg/tool"
 	"reflect"
 	"time"
 
@@ -20,11 +19,11 @@ type Claims struct {
 
 func GenerateToken(username, password string) (string, error) {
 	nowTime := time.Now()
-	expireTime := nowTime.Add(2 * time.Hour)
+	expireTime := nowTime.Add(4 * time.Hour)
 
 	claims := Claims{
 		username,
-		tool.Md5String(password),
+		password,
 		jwt.StandardClaims{
 			ExpiresAt: expireTime.Unix(),
 			Issuer:    "https://github.com/kptyun/go-admin/",

+ 2 - 1
pkg/valid/is/rule.go

@@ -4,7 +4,8 @@ import (
 	"regexp"
 	"unicode"
 
-	"git.llsapp.com/zhenghe/pkg/valid"
+	"kpt-tmr-group/pkg/valid"
+
 	"github.com/asaskevich/govalidator"
 	"github.com/nyaruka/phonenumbers"
 )

+ 1 - 1
pkg/valid/is/rule_test.go

@@ -1,10 +1,10 @@
 package is
 
 import (
+	"kpt-tmr-group/pkg/valid"
 	"strings"
 	"testing"
 
-	"git.llsapp.com/zhenghe/pkg/valid"
 	"github.com/stretchr/testify/assert"
 )
 

+ 38 - 0
pkg/xreflect/reflect.go

@@ -0,0 +1,38 @@
+package xreflect
+
+import (
+	"encoding/json"
+	"reflect"
+	"strconv"
+
+	"kpt-tmr-group/pkg/xerr"
+)
+
+var ErrUnkownType = xerr.New("unknown type")
+
+func SetString(target reflect.Value, val string) error {
+	if target.Kind() == reflect.String {
+		target.SetString(val)
+		return nil
+	}
+	return json.Unmarshal([]byte(val), target.Addr().Interface())
+}
+
+func ToString(target reflect.Value) (string, error) {
+	switch target.Kind() {
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		return strconv.FormatInt(target.Int(), 10), nil
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+		return strconv.FormatUint(target.Uint(), 10), nil
+	case reflect.Float32:
+		return strconv.FormatFloat(target.Float(), 'f', -1, 32), nil
+	case reflect.Float64:
+		return strconv.FormatFloat(target.Float(), 'f', -1, 64), nil
+	case reflect.Bool:
+		return strconv.FormatBool(target.Bool()), nil
+	case reflect.String:
+		return target.String(), nil
+	default:
+		return "", ErrUnkownType
+	}
+}

+ 111 - 0
pkg/xreflect/xreflect_test.go

@@ -0,0 +1,111 @@
+package xreflect
+
+import (
+	"reflect"
+	"testing"
+
+	"github.com/magiconair/properties/assert"
+	"github.com/stretchr/testify/require"
+)
+
+type TestStruct struct {
+	StringVal  string
+	IntVal     int
+	Int8Val    int8
+	Int16Val   int16
+	Int32Val   int32
+	Int64Val   int64
+	UintVal    uint
+	Uint8Val   uint8
+	Uint16Val  uint16
+	Uint32Val  uint32
+	Uint64Val  uint64
+	Float32Val float32
+	Float64Val float64
+}
+
+type UnknownTypeStruct struct {
+	UnknownType struct{}
+}
+
+func TestSetString(t *testing.T) {
+	t.Run("set success", func(t *testing.T) {
+		stringValues := []string{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11.123", "12.567"}
+		v := &TestStruct{}
+		refV := reflect.ValueOf(v).Elem()
+		for i := 0; i < refV.NumField(); i++ {
+			err := SetString(refV.Field(i), stringValues[i])
+			require.NoError(t, err)
+		}
+		assert.Equal(t, "0", v.StringVal)
+		assert.Equal(t, 1, v.IntVal)
+		assert.Equal(t, int8(2), v.Int8Val)
+		assert.Equal(t, int16(3), v.Int16Val)
+		assert.Equal(t, int32(4), v.Int32Val)
+		assert.Equal(t, int64(5), v.Int64Val)
+		assert.Equal(t, uint(6), v.UintVal)
+		assert.Equal(t, uint8(7), v.Uint8Val)
+		assert.Equal(t, uint16(8), v.Uint16Val)
+		assert.Equal(t, uint32(9), v.Uint32Val)
+		assert.Equal(t, uint64(10), v.Uint64Val)
+		assert.Equal(t, float32(11.123), v.Float32Val)
+		assert.Equal(t, 12.567, v.Float64Val)
+	})
+
+	t.Run("format error", func(t *testing.T) {
+		stringValues := []string{"0", "1.5", "2.5", "3.5", "4.5", "5.5", "6.5", "7.5", "8.5", "9.5", "10.5", "xxx", "xxx"}
+		v := &TestStruct{}
+		refV := reflect.ValueOf(v).Elem()
+		for i := 1; i < refV.NumField(); i++ {
+			err := SetString(refV.Field(i), stringValues[i])
+			assert.Equal(t, true, err != nil)
+		}
+	})
+
+	t.Run("unknown type", func(t *testing.T) {
+		stringValues := []string{"0"}
+		v := &UnknownTypeStruct{}
+		refV := reflect.ValueOf(v).Elem()
+		for i := 1; i < refV.NumField(); i++ {
+			err := SetString(refV.Field(i), stringValues[i])
+			assert.Equal(t, true, err != nil)
+		}
+	})
+}
+
+func TestToString(t *testing.T) {
+	t.Run("format success", func(t *testing.T) {
+		stringValues := []string{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11.123", "12.567"}
+		v := &TestStruct{
+			StringVal:  "0",
+			IntVal:     1,
+			Int8Val:    2,
+			Int16Val:   3,
+			Int32Val:   4,
+			Int64Val:   5,
+			UintVal:    6,
+			Uint8Val:   7,
+			Uint16Val:  8,
+			Uint32Val:  9,
+			Uint64Val:  10,
+			Float32Val: 11.123,
+			Float64Val: 12.567,
+		}
+		refV := reflect.ValueOf(v).Elem()
+		for i := 1; i < refV.NumField(); i++ {
+			str, err := ToString(refV.Field(i))
+			require.NoError(t, err)
+			assert.Equal(t, stringValues[i], str)
+		}
+	})
+
+	t.Run("unknown type", func(t *testing.T) {
+		v := &UnknownTypeStruct{}
+		refV := reflect.ValueOf(v).Elem()
+		for i := 1; i < refV.NumField(); i++ {
+			_, err := ToString(refV.Field(i))
+			assert.Equal(t, ErrUnkownType, err)
+		}
+	})
+
+}

+ 1 - 0
pkg/xstore/database/dbtest/mysql.go

@@ -0,0 +1 @@
+package dbtest

+ 50 - 0
pkg/xstore/database/gorm.go

@@ -0,0 +1,50 @@
+package database
+
+import (
+	"kpt-tmr-group/config"
+	KptLogger "kpt-tmr-group/pkg/logger/logrus"
+	"kpt-tmr-group/pkg/xerr"
+	"time"
+
+	"gorm.io/gorm"
+
+	"gorm.io/driver/mysql"
+
+	"gorm.io/gorm/logger"
+)
+
+type goRmLog struct {
+}
+
+func (g goRmLog) Printf(s string, i ...interface{}) {
+	KptLogger.Infof(s, i...)
+
+}
+
+// NewDatabase return xorm engine
+// with some default params
+func NewDatabase(cfg *config.AppConfig, drivers ...string) (*gorm.DB, error) {
+
+	newLogger := logger.New(
+		goRmLog{},
+		logger.Config{
+			SlowThreshold: 5 * time.Second,
+			LogLevel:      logger.Info,
+		},
+	)
+
+	db, err := gorm.Open(mysql.New(mysql.Config{
+		DriverName: cfg.StoreSetting.DriverName,
+		DSN:        cfg.StoreSetting.KptEventDSNRW}),
+		&gorm.Config{Logger: newLogger},
+	)
+	if err != nil {
+		panic(xerr.WithStack(err))
+	}
+
+	if cfg.StoreSetting.ShowSQL {
+		db.Logger.LogMode(logger.Info)
+	}
+
+	return db, err
+}

+ 1 - 0
pkg/xstore/database/migrator/migrate.go

@@ -0,0 +1 @@
+package migrator

+ 173 - 0
proto/go/backend/operation/common.pb.go

@@ -0,0 +1,173 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.28.1
+// 	protoc        v3.21.9
+// source: backend/operation/common.proto
+
+package operationPb
+
+import (
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	reflect "reflect"
+	sync "sync"
+)
+
+const (
+	// Verify that this generated code is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+	// Verify that runtime/protoimpl is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type ListCommon struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	NoEdit       bool `protobuf:"varint,1,opt,name=NoEdit,proto3" json:"NoEdit,omitempty"`
+	IsCreate     bool `protobuf:"varint,2,opt,name=isCreate,proto3" json:"isCreate,omitempty"`
+	IsUpdate     bool `protobuf:"varint,3,opt,name=isUpdate,proto3" json:"isUpdate,omitempty"`
+	IsUpdateSave bool `protobuf:"varint,4,opt,name=isUpdateSave,proto3" json:"isUpdateSave,omitempty"`
+}
+
+func (x *ListCommon) Reset() {
+	*x = ListCommon{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_backend_operation_common_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *ListCommon) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ListCommon) ProtoMessage() {}
+
+func (x *ListCommon) ProtoReflect() protoreflect.Message {
+	mi := &file_backend_operation_common_proto_msgTypes[0]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use ListCommon.ProtoReflect.Descriptor instead.
+func (*ListCommon) Descriptor() ([]byte, []int) {
+	return file_backend_operation_common_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *ListCommon) GetNoEdit() bool {
+	if x != nil {
+		return x.NoEdit
+	}
+	return false
+}
+
+func (x *ListCommon) GetIsCreate() bool {
+	if x != nil {
+		return x.IsCreate
+	}
+	return false
+}
+
+func (x *ListCommon) GetIsUpdate() bool {
+	if x != nil {
+		return x.IsUpdate
+	}
+	return false
+}
+
+func (x *ListCommon) GetIsUpdateSave() bool {
+	if x != nil {
+		return x.IsUpdateSave
+	}
+	return false
+}
+
+var File_backend_operation_common_proto protoreflect.FileDescriptor
+
+var file_backend_operation_common_proto_rawDesc = []byte{
+	0x0a, 0x1e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74,
+	0x69, 0x6f, 0x6e, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+	0x12, 0x11, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74,
+	0x69, 0x6f, 0x6e, 0x22, 0x80, 0x01, 0x0a, 0x0a, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6d, 0x6d,
+	0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x4e, 0x6f, 0x45, 0x64, 0x69, 0x74, 0x18, 0x01, 0x20, 0x01,
+	0x28, 0x08, 0x52, 0x06, 0x4e, 0x6f, 0x45, 0x64, 0x69, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x73,
+	0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x69, 0x73,
+	0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x73, 0x55, 0x70, 0x64, 0x61,
+	0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x69, 0x73, 0x55, 0x70, 0x64, 0x61,
+	0x74, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x69, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x61,
+	0x76, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x69, 0x73, 0x55, 0x70, 0x64, 0x61,
+	0x74, 0x65, 0x53, 0x61, 0x76, 0x65, 0x42, 0x0f, 0x5a, 0x0d, 0x2e, 0x3b, 0x6f, 0x70, 0x65, 0x72,
+	0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+	file_backend_operation_common_proto_rawDescOnce sync.Once
+	file_backend_operation_common_proto_rawDescData = file_backend_operation_common_proto_rawDesc
+)
+
+func file_backend_operation_common_proto_rawDescGZIP() []byte {
+	file_backend_operation_common_proto_rawDescOnce.Do(func() {
+		file_backend_operation_common_proto_rawDescData = protoimpl.X.CompressGZIP(file_backend_operation_common_proto_rawDescData)
+	})
+	return file_backend_operation_common_proto_rawDescData
+}
+
+var file_backend_operation_common_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
+var file_backend_operation_common_proto_goTypes = []interface{}{
+	(*ListCommon)(nil), // 0: backend.operation.ListCommon
+}
+var file_backend_operation_common_proto_depIdxs = []int32{
+	0, // [0:0] is the sub-list for method output_type
+	0, // [0:0] is the sub-list for method input_type
+	0, // [0:0] is the sub-list for extension type_name
+	0, // [0:0] is the sub-list for extension extendee
+	0, // [0:0] is the sub-list for field type_name
+}
+
+func init() { file_backend_operation_common_proto_init() }
+func file_backend_operation_common_proto_init() {
+	if File_backend_operation_common_proto != nil {
+		return
+	}
+	if !protoimpl.UnsafeEnabled {
+		file_backend_operation_common_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*ListCommon); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+	}
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_backend_operation_common_proto_rawDesc,
+			NumEnums:      0,
+			NumMessages:   1,
+			NumExtensions: 0,
+			NumServices:   0,
+		},
+		GoTypes:           file_backend_operation_common_proto_goTypes,
+		DependencyIndexes: file_backend_operation_common_proto_depIdxs,
+		MessageInfos:      file_backend_operation_common_proto_msgTypes,
+	}.Build()
+	File_backend_operation_common_proto = out.File
+	file_backend_operation_common_proto_rawDesc = nil
+	file_backend_operation_common_proto_goTypes = nil
+	file_backend_operation_common_proto_depIdxs = nil
+}

+ 788 - 8
proto/go/backend/operation/enum.pb.go

@@ -69,6 +69,376 @@ func (IsShow_Kind) EnumDescriptor() ([]byte, []int) {
 	return file_backend_operation_enum_proto_rawDescGZIP(), []int{0, 0}
 }
 
+type CattleCategoryParent_Kind int32
+
+const (
+	CattleCategoryParent_INVALID       CattleCategoryParent_Kind = 0 // 无效
+	CattleCategoryParent_LACTATION_CAW CattleCategoryParent_Kind = 1 // 泌乳牛
+	CattleCategoryParent_FATTEN_CAW    CattleCategoryParent_Kind = 2 // 育肥牛
+	CattleCategoryParent_RESERVE_CAW   CattleCategoryParent_Kind = 3 // 后备牛
+	CattleCategoryParent_DRY_CAW       CattleCategoryParent_Kind = 4 // 干奶牛
+	CattleCategoryParent_PERINATAL_CAW CattleCategoryParent_Kind = 5 // 围产牛
+	CattleCategoryParent_OTHER_CAW     CattleCategoryParent_Kind = 6 // 其他
+)
+
+// Enum value maps for CattleCategoryParent_Kind.
+var (
+	CattleCategoryParent_Kind_name = map[int32]string{
+		0: "INVALID",
+		1: "LACTATION_CAW",
+		2: "FATTEN_CAW",
+		3: "RESERVE_CAW",
+		4: "DRY_CAW",
+		5: "PERINATAL_CAW",
+		6: "OTHER_CAW",
+	}
+	CattleCategoryParent_Kind_value = map[string]int32{
+		"INVALID":       0,
+		"LACTATION_CAW": 1,
+		"FATTEN_CAW":    2,
+		"RESERVE_CAW":   3,
+		"DRY_CAW":       4,
+		"PERINATAL_CAW": 5,
+		"OTHER_CAW":     6,
+	}
+)
+
+func (x CattleCategoryParent_Kind) Enum() *CattleCategoryParent_Kind {
+	p := new(CattleCategoryParent_Kind)
+	*p = x
+	return p
+}
+
+func (x CattleCategoryParent_Kind) String() string {
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (CattleCategoryParent_Kind) Descriptor() protoreflect.EnumDescriptor {
+	return file_backend_operation_enum_proto_enumTypes[1].Descriptor()
+}
+
+func (CattleCategoryParent_Kind) Type() protoreflect.EnumType {
+	return &file_backend_operation_enum_proto_enumTypes[1]
+}
+
+func (x CattleCategoryParent_Kind) Number() protoreflect.EnumNumber {
+	return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Use CattleCategoryParent_Kind.Descriptor instead.
+func (CattleCategoryParent_Kind) EnumDescriptor() ([]byte, []int) {
+	return file_backend_operation_enum_proto_rawDescGZIP(), []int{1, 0}
+}
+
+type ForageCategoryParent_Kind int32
+
+const (
+	ForageCategoryParent_INVALID                        ForageCategoryParent_Kind = 0 // 无效
+	ForageCategoryParent_ROUGHAGE                       ForageCategoryParent_Kind = 1 // 粗料
+	ForageCategoryParent_CONCENTRATE                    ForageCategoryParent_Kind = 2 // 精料(浓缩料)
+	ForageCategoryParent_HALF_ROUGHAGE_HALF_CONCENTRATE ForageCategoryParent_Kind = 3 // 粗料精料各半
+	ForageCategoryParent_OTHER                          ForageCategoryParent_Kind = 4 // 其他
+)
+
+// Enum value maps for ForageCategoryParent_Kind.
+var (
+	ForageCategoryParent_Kind_name = map[int32]string{
+		0: "INVALID",
+		1: "ROUGHAGE",
+		2: "CONCENTRATE",
+		3: "HALF_ROUGHAGE_HALF_CONCENTRATE",
+		4: "OTHER",
+	}
+	ForageCategoryParent_Kind_value = map[string]int32{
+		"INVALID":                        0,
+		"ROUGHAGE":                       1,
+		"CONCENTRATE":                    2,
+		"HALF_ROUGHAGE_HALF_CONCENTRATE": 3,
+		"OTHER":                          4,
+	}
+)
+
+func (x ForageCategoryParent_Kind) Enum() *ForageCategoryParent_Kind {
+	p := new(ForageCategoryParent_Kind)
+	*p = x
+	return p
+}
+
+func (x ForageCategoryParent_Kind) String() string {
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (ForageCategoryParent_Kind) Descriptor() protoreflect.EnumDescriptor {
+	return file_backend_operation_enum_proto_enumTypes[2].Descriptor()
+}
+
+func (ForageCategoryParent_Kind) Type() protoreflect.EnumType {
+	return &file_backend_operation_enum_proto_enumTypes[2]
+}
+
+func (x ForageCategoryParent_Kind) Number() protoreflect.EnumNumber {
+	return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Use ForageCategoryParent_Kind.Descriptor instead.
+func (ForageCategoryParent_Kind) EnumDescriptor() ([]byte, []int) {
+	return file_backend_operation_enum_proto_rawDescGZIP(), []int{2, 0}
+}
+
+type ForageSource_Kind int32
+
+const (
+	ForageSource_INVALID         ForageSource_Kind = 0 // 无效
+	ForageSource_SYSTEM_BUILT_IN ForageSource_Kind = 1 // 系统内置
+	ForageSource_USER_DEFINED    ForageSource_Kind = 2 // 用户自定义
+)
+
+// Enum value maps for ForageSource_Kind.
+var (
+	ForageSource_Kind_name = map[int32]string{
+		0: "INVALID",
+		1: "SYSTEM_BUILT_IN",
+		2: "USER_DEFINED",
+	}
+	ForageSource_Kind_value = map[string]int32{
+		"INVALID":         0,
+		"SYSTEM_BUILT_IN": 1,
+		"USER_DEFINED":    2,
+	}
+)
+
+func (x ForageSource_Kind) Enum() *ForageSource_Kind {
+	p := new(ForageSource_Kind)
+	*p = x
+	return p
+}
+
+func (x ForageSource_Kind) String() string {
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (ForageSource_Kind) Descriptor() protoreflect.EnumDescriptor {
+	return file_backend_operation_enum_proto_enumTypes[3].Descriptor()
+}
+
+func (ForageSource_Kind) Type() protoreflect.EnumType {
+	return &file_backend_operation_enum_proto_enumTypes[3]
+}
+
+func (x ForageSource_Kind) Number() protoreflect.EnumNumber {
+	return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Use ForageSource_Kind.Descriptor instead.
+func (ForageSource_Kind) EnumDescriptor() ([]byte, []int) {
+	return file_backend_operation_enum_proto_rawDescGZIP(), []int{3, 0}
+}
+
+type JumpDelaType_Kind int32
+
+const (
+	JumpDelaType_INVALID JumpDelaType_Kind = 0 // 禁用
+	JumpDelaType_THREE   JumpDelaType_Kind = 1 // 3秒
+	JumpDelaType_SIX     JumpDelaType_Kind = 2 // 6秒
+	JumpDelaType_NINE    JumpDelaType_Kind = 3 // 9秒
+)
+
+// Enum value maps for JumpDelaType_Kind.
+var (
+	JumpDelaType_Kind_name = map[int32]string{
+		0: "INVALID",
+		1: "THREE",
+		2: "SIX",
+		3: "NINE",
+	}
+	JumpDelaType_Kind_value = map[string]int32{
+		"INVALID": 0,
+		"THREE":   1,
+		"SIX":     2,
+		"NINE":    3,
+	}
+)
+
+func (x JumpDelaType_Kind) Enum() *JumpDelaType_Kind {
+	p := new(JumpDelaType_Kind)
+	*p = x
+	return p
+}
+
+func (x JumpDelaType_Kind) String() string {
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (JumpDelaType_Kind) Descriptor() protoreflect.EnumDescriptor {
+	return file_backend_operation_enum_proto_enumTypes[4].Descriptor()
+}
+
+func (JumpDelaType_Kind) Type() protoreflect.EnumType {
+	return &file_backend_operation_enum_proto_enumTypes[4]
+}
+
+func (x JumpDelaType_Kind) Number() protoreflect.EnumNumber {
+	return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Use JumpDelaType_Kind.Descriptor instead.
+func (JumpDelaType_Kind) EnumDescriptor() ([]byte, []int) {
+	return file_backend_operation_enum_proto_rawDescGZIP(), []int{4, 0}
+}
+
+type ForagePlanType_Kind int32
+
+const (
+	ForagePlanType_INVALID     ForagePlanType_Kind = 0 // 无
+	ForagePlanType_FORKLIFT    ForagePlanType_Kind = 1 // 铲车
+	ForagePlanType_CONCENTRATE ForagePlanType_Kind = 2 // 精料
+)
+
+// Enum value maps for ForagePlanType_Kind.
+var (
+	ForagePlanType_Kind_name = map[int32]string{
+		0: "INVALID",
+		1: "FORKLIFT",
+		2: "CONCENTRATE",
+	}
+	ForagePlanType_Kind_value = map[string]int32{
+		"INVALID":     0,
+		"FORKLIFT":    1,
+		"CONCENTRATE": 2,
+	}
+)
+
+func (x ForagePlanType_Kind) Enum() *ForagePlanType_Kind {
+	p := new(ForagePlanType_Kind)
+	*p = x
+	return p
+}
+
+func (x ForagePlanType_Kind) String() string {
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (ForagePlanType_Kind) Descriptor() protoreflect.EnumDescriptor {
+	return file_backend_operation_enum_proto_enumTypes[5].Descriptor()
+}
+
+func (ForagePlanType_Kind) Type() protoreflect.EnumType {
+	return &file_backend_operation_enum_proto_enumTypes[5]
+}
+
+func (x ForagePlanType_Kind) Number() protoreflect.EnumNumber {
+	return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Use ForagePlanType_Kind.Descriptor instead.
+func (ForagePlanType_Kind) EnumDescriptor() ([]byte, []int) {
+	return file_backend_operation_enum_proto_rawDescGZIP(), []int{5, 0}
+}
+
+type DataSource_Kind int32
+
+const (
+	DataSource_INVALID        DataSource_Kind = 0 // 无
+	DataSource_BACKGROUND_ADD DataSource_Kind = 1 // 后台手动添加
+	DataSource_EXCEL_IMPORT   DataSource_Kind = 2 // excel报表导入
+	DataSource_FROM_PASTURE   DataSource_Kind = 3 // 来自牧场
+)
+
+// Enum value maps for DataSource_Kind.
+var (
+	DataSource_Kind_name = map[int32]string{
+		0: "INVALID",
+		1: "BACKGROUND_ADD",
+		2: "EXCEL_IMPORT",
+		3: "FROM_PASTURE",
+	}
+	DataSource_Kind_value = map[string]int32{
+		"INVALID":        0,
+		"BACKGROUND_ADD": 1,
+		"EXCEL_IMPORT":   2,
+		"FROM_PASTURE":   3,
+	}
+)
+
+func (x DataSource_Kind) Enum() *DataSource_Kind {
+	p := new(DataSource_Kind)
+	*p = x
+	return p
+}
+
+func (x DataSource_Kind) String() string {
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (DataSource_Kind) Descriptor() protoreflect.EnumDescriptor {
+	return file_backend_operation_enum_proto_enumTypes[6].Descriptor()
+}
+
+func (DataSource_Kind) Type() protoreflect.EnumType {
+	return &file_backend_operation_enum_proto_enumTypes[6]
+}
+
+func (x DataSource_Kind) Number() protoreflect.EnumNumber {
+	return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Use DataSource_Kind.Descriptor instead.
+func (DataSource_Kind) EnumDescriptor() ([]byte, []int) {
+	return file_backend_operation_enum_proto_rawDescGZIP(), []int{6, 0}
+}
+
+type FormulaType_Kind int32
+
+const (
+	FormulaType_INVALID               FormulaType_Kind = 0 // 无
+	FormulaType_FEED_FORMULA          FormulaType_Kind = 1 // 饲喂配方
+	FormulaType_PREMIXED_FORMULA      FormulaType_Kind = 2 // 预混配方
+	FormulaType_SUPPLEMENTARY_FORMULA FormulaType_Kind = 3 // 补料配方
+)
+
+// Enum value maps for FormulaType_Kind.
+var (
+	FormulaType_Kind_name = map[int32]string{
+		0: "INVALID",
+		1: "FEED_FORMULA",
+		2: "PREMIXED_FORMULA",
+		3: "SUPPLEMENTARY_FORMULA",
+	}
+	FormulaType_Kind_value = map[string]int32{
+		"INVALID":               0,
+		"FEED_FORMULA":          1,
+		"PREMIXED_FORMULA":      2,
+		"SUPPLEMENTARY_FORMULA": 3,
+	}
+)
+
+func (x FormulaType_Kind) Enum() *FormulaType_Kind {
+	p := new(FormulaType_Kind)
+	*p = x
+	return p
+}
+
+func (x FormulaType_Kind) String() string {
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (FormulaType_Kind) Descriptor() protoreflect.EnumDescriptor {
+	return file_backend_operation_enum_proto_enumTypes[7].Descriptor()
+}
+
+func (FormulaType_Kind) Type() protoreflect.EnumType {
+	return &file_backend_operation_enum_proto_enumTypes[7]
+}
+
+func (x FormulaType_Kind) Number() protoreflect.EnumNumber {
+	return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Use FormulaType_Kind.Descriptor instead.
+func (FormulaType_Kind) EnumDescriptor() ([]byte, []int) {
+	return file_backend_operation_enum_proto_rawDescGZIP(), []int{7, 0}
+}
+
 // 字段类型
 type IsShow struct {
 	state         protoimpl.MessageState
@@ -108,6 +478,276 @@ func (*IsShow) Descriptor() ([]byte, []int) {
 	return file_backend_operation_enum_proto_rawDescGZIP(), []int{0}
 }
 
+type CattleCategoryParent struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+}
+
+func (x *CattleCategoryParent) Reset() {
+	*x = CattleCategoryParent{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_backend_operation_enum_proto_msgTypes[1]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *CattleCategoryParent) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*CattleCategoryParent) ProtoMessage() {}
+
+func (x *CattleCategoryParent) ProtoReflect() protoreflect.Message {
+	mi := &file_backend_operation_enum_proto_msgTypes[1]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use CattleCategoryParent.ProtoReflect.Descriptor instead.
+func (*CattleCategoryParent) Descriptor() ([]byte, []int) {
+	return file_backend_operation_enum_proto_rawDescGZIP(), []int{1}
+}
+
+// 饲料类别
+type ForageCategoryParent struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+}
+
+func (x *ForageCategoryParent) Reset() {
+	*x = ForageCategoryParent{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_backend_operation_enum_proto_msgTypes[2]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *ForageCategoryParent) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ForageCategoryParent) ProtoMessage() {}
+
+func (x *ForageCategoryParent) ProtoReflect() protoreflect.Message {
+	mi := &file_backend_operation_enum_proto_msgTypes[2]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use ForageCategoryParent.ProtoReflect.Descriptor instead.
+func (*ForageCategoryParent) Descriptor() ([]byte, []int) {
+	return file_backend_operation_enum_proto_rawDescGZIP(), []int{2}
+}
+
+// 饲料来源
+type ForageSource struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+}
+
+func (x *ForageSource) Reset() {
+	*x = ForageSource{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_backend_operation_enum_proto_msgTypes[3]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *ForageSource) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ForageSource) ProtoMessage() {}
+
+func (x *ForageSource) ProtoReflect() protoreflect.Message {
+	mi := &file_backend_operation_enum_proto_msgTypes[3]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use ForageSource.ProtoReflect.Descriptor instead.
+func (*ForageSource) Descriptor() ([]byte, []int) {
+	return file_backend_operation_enum_proto_rawDescGZIP(), []int{3}
+}
+
+// 跳转延迟
+type JumpDelaType struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+}
+
+func (x *JumpDelaType) Reset() {
+	*x = JumpDelaType{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_backend_operation_enum_proto_msgTypes[4]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *JumpDelaType) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*JumpDelaType) ProtoMessage() {}
+
+func (x *JumpDelaType) ProtoReflect() protoreflect.Message {
+	mi := &file_backend_operation_enum_proto_msgTypes[4]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use JumpDelaType.ProtoReflect.Descriptor instead.
+func (*JumpDelaType) Descriptor() ([]byte, []int) {
+	return file_backend_operation_enum_proto_rawDescGZIP(), []int{4}
+}
+
+// 计划类型
+type ForagePlanType struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+}
+
+func (x *ForagePlanType) Reset() {
+	*x = ForagePlanType{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_backend_operation_enum_proto_msgTypes[5]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *ForagePlanType) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ForagePlanType) ProtoMessage() {}
+
+func (x *ForagePlanType) ProtoReflect() protoreflect.Message {
+	mi := &file_backend_operation_enum_proto_msgTypes[5]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use ForagePlanType.ProtoReflect.Descriptor instead.
+func (*ForagePlanType) Descriptor() ([]byte, []int) {
+	return file_backend_operation_enum_proto_rawDescGZIP(), []int{5}
+}
+
+type DataSource struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+}
+
+func (x *DataSource) Reset() {
+	*x = DataSource{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_backend_operation_enum_proto_msgTypes[6]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *DataSource) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*DataSource) ProtoMessage() {}
+
+func (x *DataSource) ProtoReflect() protoreflect.Message {
+	mi := &file_backend_operation_enum_proto_msgTypes[6]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use DataSource.ProtoReflect.Descriptor instead.
+func (*DataSource) Descriptor() ([]byte, []int) {
+	return file_backend_operation_enum_proto_rawDescGZIP(), []int{6}
+}
+
+type FormulaType struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+}
+
+func (x *FormulaType) Reset() {
+	*x = FormulaType{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_backend_operation_enum_proto_msgTypes[7]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *FormulaType) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*FormulaType) ProtoMessage() {}
+
+func (x *FormulaType) ProtoReflect() protoreflect.Message {
+	mi := &file_backend_operation_enum_proto_msgTypes[7]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use FormulaType.ProtoReflect.Descriptor instead.
+func (*FormulaType) Descriptor() ([]byte, []int) {
+	return file_backend_operation_enum_proto_rawDescGZIP(), []int{7}
+}
+
 var File_backend_operation_enum_proto protoreflect.FileDescriptor
 
 var file_backend_operation_enum_proto_rawDesc = []byte{
@@ -117,8 +757,50 @@ var file_backend_operation_enum_proto_rawDesc = []byte{
 	0x6e, 0x22, 0x2d, 0x0a, 0x06, 0x49, 0x73, 0x53, 0x68, 0x6f, 0x77, 0x22, 0x23, 0x0a, 0x04, 0x4b,
 	0x69, 0x6e, 0x64, 0x12, 0x0b, 0x0a, 0x07, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x10, 0x00,
 	0x12, 0x06, 0x0a, 0x02, 0x4f, 0x4b, 0x10, 0x01, 0x12, 0x06, 0x0a, 0x02, 0x4e, 0x4f, 0x10, 0x02,
-	0x42, 0x0f, 0x5a, 0x0d, 0x2e, 0x3b, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50,
-	0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+	0x22, 0x8e, 0x01, 0x0a, 0x14, 0x43, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x43, 0x61, 0x74, 0x65, 0x67,
+	0x6f, 0x72, 0x79, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x22, 0x76, 0x0a, 0x04, 0x4b, 0x69, 0x6e,
+	0x64, 0x12, 0x0b, 0x0a, 0x07, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x10, 0x00, 0x12, 0x11,
+	0x0a, 0x0d, 0x4c, 0x41, 0x43, 0x54, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x43, 0x41, 0x57, 0x10,
+	0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x46, 0x41, 0x54, 0x54, 0x45, 0x4e, 0x5f, 0x43, 0x41, 0x57, 0x10,
+	0x02, 0x12, 0x0f, 0x0a, 0x0b, 0x52, 0x45, 0x53, 0x45, 0x52, 0x56, 0x45, 0x5f, 0x43, 0x41, 0x57,
+	0x10, 0x03, 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x52, 0x59, 0x5f, 0x43, 0x41, 0x57, 0x10, 0x04, 0x12,
+	0x11, 0x0a, 0x0d, 0x50, 0x45, 0x52, 0x49, 0x4e, 0x41, 0x54, 0x41, 0x4c, 0x5f, 0x43, 0x41, 0x57,
+	0x10, 0x05, 0x12, 0x0d, 0x0a, 0x09, 0x4f, 0x54, 0x48, 0x45, 0x52, 0x5f, 0x43, 0x41, 0x57, 0x10,
+	0x06, 0x22, 0x79, 0x0a, 0x14, 0x46, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x43, 0x61, 0x74, 0x65, 0x67,
+	0x6f, 0x72, 0x79, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x22, 0x61, 0x0a, 0x04, 0x4b, 0x69, 0x6e,
+	0x64, 0x12, 0x0b, 0x0a, 0x07, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x10, 0x00, 0x12, 0x0c,
+	0x0a, 0x08, 0x52, 0x4f, 0x55, 0x47, 0x48, 0x41, 0x47, 0x45, 0x10, 0x01, 0x12, 0x0f, 0x0a, 0x0b,
+	0x43, 0x4f, 0x4e, 0x43, 0x45, 0x4e, 0x54, 0x52, 0x41, 0x54, 0x45, 0x10, 0x02, 0x12, 0x22, 0x0a,
+	0x1e, 0x48, 0x41, 0x4c, 0x46, 0x5f, 0x52, 0x4f, 0x55, 0x47, 0x48, 0x41, 0x47, 0x45, 0x5f, 0x48,
+	0x41, 0x4c, 0x46, 0x5f, 0x43, 0x4f, 0x4e, 0x43, 0x45, 0x4e, 0x54, 0x52, 0x41, 0x54, 0x45, 0x10,
+	0x03, 0x12, 0x09, 0x0a, 0x05, 0x4f, 0x54, 0x48, 0x45, 0x52, 0x10, 0x04, 0x22, 0x4a, 0x0a, 0x0c,
+	0x46, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x22, 0x3a, 0x0a, 0x04,
+	0x4b, 0x69, 0x6e, 0x64, 0x12, 0x0b, 0x0a, 0x07, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x10,
+	0x00, 0x12, 0x13, 0x0a, 0x0f, 0x53, 0x59, 0x53, 0x54, 0x45, 0x4d, 0x5f, 0x42, 0x55, 0x49, 0x4c,
+	0x54, 0x5f, 0x49, 0x4e, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x55, 0x53, 0x45, 0x52, 0x5f, 0x44,
+	0x45, 0x46, 0x49, 0x4e, 0x45, 0x44, 0x10, 0x02, 0x22, 0x41, 0x0a, 0x0c, 0x4a, 0x75, 0x6d, 0x70,
+	0x44, 0x65, 0x6c, 0x61, 0x54, 0x79, 0x70, 0x65, 0x22, 0x31, 0x0a, 0x04, 0x4b, 0x69, 0x6e, 0x64,
+	0x12, 0x0b, 0x0a, 0x07, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x10, 0x00, 0x12, 0x09, 0x0a,
+	0x05, 0x54, 0x48, 0x52, 0x45, 0x45, 0x10, 0x01, 0x12, 0x07, 0x0a, 0x03, 0x53, 0x49, 0x58, 0x10,
+	0x02, 0x12, 0x08, 0x0a, 0x04, 0x4e, 0x49, 0x4e, 0x45, 0x10, 0x03, 0x22, 0x44, 0x0a, 0x0e, 0x46,
+	0x6f, 0x72, 0x61, 0x67, 0x65, 0x50, 0x6c, 0x61, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x22, 0x32, 0x0a,
+	0x04, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x0b, 0x0a, 0x07, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44,
+	0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x46, 0x4f, 0x52, 0x4b, 0x4c, 0x49, 0x46, 0x54, 0x10, 0x01,
+	0x12, 0x0f, 0x0a, 0x0b, 0x43, 0x4f, 0x4e, 0x43, 0x45, 0x4e, 0x54, 0x52, 0x41, 0x54, 0x45, 0x10,
+	0x02, 0x22, 0x59, 0x0a, 0x0a, 0x44, 0x61, 0x74, 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x22,
+	0x4b, 0x0a, 0x04, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x0b, 0x0a, 0x07, 0x49, 0x4e, 0x56, 0x41, 0x4c,
+	0x49, 0x44, 0x10, 0x00, 0x12, 0x12, 0x0a, 0x0e, 0x42, 0x41, 0x43, 0x4b, 0x47, 0x52, 0x4f, 0x55,
+	0x4e, 0x44, 0x5f, 0x41, 0x44, 0x44, 0x10, 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x45, 0x58, 0x43, 0x45,
+	0x4c, 0x5f, 0x49, 0x4d, 0x50, 0x4f, 0x52, 0x54, 0x10, 0x02, 0x12, 0x10, 0x0a, 0x0c, 0x46, 0x52,
+	0x4f, 0x4d, 0x5f, 0x50, 0x41, 0x53, 0x54, 0x55, 0x52, 0x45, 0x10, 0x03, 0x22, 0x65, 0x0a, 0x0b,
+	0x46, 0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x54, 0x79, 0x70, 0x65, 0x22, 0x56, 0x0a, 0x04, 0x4b,
+	0x69, 0x6e, 0x64, 0x12, 0x0b, 0x0a, 0x07, 0x49, 0x4e, 0x56, 0x41, 0x4c, 0x49, 0x44, 0x10, 0x00,
+	0x12, 0x10, 0x0a, 0x0c, 0x46, 0x45, 0x45, 0x44, 0x5f, 0x46, 0x4f, 0x52, 0x4d, 0x55, 0x4c, 0x41,
+	0x10, 0x01, 0x12, 0x14, 0x0a, 0x10, 0x50, 0x52, 0x45, 0x4d, 0x49, 0x58, 0x45, 0x44, 0x5f, 0x46,
+	0x4f, 0x52, 0x4d, 0x55, 0x4c, 0x41, 0x10, 0x02, 0x12, 0x19, 0x0a, 0x15, 0x53, 0x55, 0x50, 0x50,
+	0x4c, 0x45, 0x4d, 0x45, 0x4e, 0x54, 0x41, 0x52, 0x59, 0x5f, 0x46, 0x4f, 0x52, 0x4d, 0x55, 0x4c,
+	0x41, 0x10, 0x03, 0x42, 0x0f, 0x5a, 0x0d, 0x2e, 0x3b, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69,
+	0x6f, 0x6e, 0x50, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
 }
 
 var (
@@ -133,11 +815,25 @@ func file_backend_operation_enum_proto_rawDescGZIP() []byte {
 	return file_backend_operation_enum_proto_rawDescData
 }
 
-var file_backend_operation_enum_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
-var file_backend_operation_enum_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
+var file_backend_operation_enum_proto_enumTypes = make([]protoimpl.EnumInfo, 8)
+var file_backend_operation_enum_proto_msgTypes = make([]protoimpl.MessageInfo, 8)
 var file_backend_operation_enum_proto_goTypes = []interface{}{
-	(IsShow_Kind)(0), // 0: backend.operation.IsShow.Kind
-	(*IsShow)(nil),   // 1: backend.operation.IsShow
+	(IsShow_Kind)(0),               // 0: backend.operation.IsShow.Kind
+	(CattleCategoryParent_Kind)(0), // 1: backend.operation.CattleCategoryParent.Kind
+	(ForageCategoryParent_Kind)(0), // 2: backend.operation.ForageCategoryParent.Kind
+	(ForageSource_Kind)(0),         // 3: backend.operation.ForageSource.Kind
+	(JumpDelaType_Kind)(0),         // 4: backend.operation.JumpDelaType.Kind
+	(ForagePlanType_Kind)(0),       // 5: backend.operation.ForagePlanType.Kind
+	(DataSource_Kind)(0),           // 6: backend.operation.DataSource.Kind
+	(FormulaType_Kind)(0),          // 7: backend.operation.FormulaType.Kind
+	(*IsShow)(nil),                 // 8: backend.operation.IsShow
+	(*CattleCategoryParent)(nil),   // 9: backend.operation.CattleCategoryParent
+	(*ForageCategoryParent)(nil),   // 10: backend.operation.ForageCategoryParent
+	(*ForageSource)(nil),           // 11: backend.operation.ForageSource
+	(*JumpDelaType)(nil),           // 12: backend.operation.JumpDelaType
+	(*ForagePlanType)(nil),         // 13: backend.operation.ForagePlanType
+	(*DataSource)(nil),             // 14: backend.operation.DataSource
+	(*FormulaType)(nil),            // 15: backend.operation.FormulaType
 }
 var file_backend_operation_enum_proto_depIdxs = []int32{
 	0, // [0:0] is the sub-list for method output_type
@@ -165,14 +861,98 @@ func file_backend_operation_enum_proto_init() {
 				return nil
 			}
 		}
+		file_backend_operation_enum_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*CattleCategoryParent); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_backend_operation_enum_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*ForageCategoryParent); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_backend_operation_enum_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*ForageSource); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_backend_operation_enum_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*JumpDelaType); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_backend_operation_enum_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*ForagePlanType); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_backend_operation_enum_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*DataSource); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_backend_operation_enum_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*FormulaType); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
 	}
 	type x struct{}
 	out := protoimpl.TypeBuilder{
 		File: protoimpl.DescBuilder{
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			RawDescriptor: file_backend_operation_enum_proto_rawDesc,
-			NumEnums:      1,
-			NumMessages:   1,
+			NumEnums:      8,
+			NumMessages:   8,
 			NumExtensions: 0,
 			NumServices:   0,
 		},

+ 643 - 0
proto/go/backend/operation/feed_formula.pb.go

@@ -0,0 +1,643 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.28.1
+// 	protoc        v3.21.9
+// source: backend/operation/feed_formula.proto
+
+package operationPb
+
+import (
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	reflect "reflect"
+	sync "sync"
+)
+
+const (
+	// Verify that this generated code is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+	// Verify that runtime/protoimpl is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type AddFeedFormulaRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Id                 int32                     `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
+	Name               string                    `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`                                                                                                     // 名称
+	EncodeNumber       string                    `protobuf:"bytes,3,opt,name=encode_number,json=encodeNumber,proto3" json:"encode_number,omitempty"`                                                                 // 编码
+	Colour             string                    `protobuf:"bytes,4,opt,name=colour,proto3" json:"colour,omitempty"`                                                                                                 // 颜色
+	CattleCategoryId   CattleCategoryParent_Kind `protobuf:"varint,5,opt,name=cattle_category_id,json=cattleCategoryId,proto3,enum=backend.operation.CattleCategoryParent_Kind" json:"cattle_category_id,omitempty"` // 畜牧类别id
+	CattleCategoryName string                    `protobuf:"bytes,6,opt,name=cattle_category_name,json=cattleCategoryName,proto3" json:"cattle_category_name,omitempty"`                                             // 畜牧类型名称
+	FormulaTypeId      int32                     `protobuf:"varint,7,opt,name=formula_type_id,json=formulaTypeId,proto3" json:"formula_type_id,omitempty"`                                                           // 配方类型id
+	FormulaTypeName    string                    `protobuf:"bytes,8,opt,name=formula_type_name,json=formulaTypeName,proto3" json:"formula_type_name,omitempty"`                                                      // 配方类型名称
+	DataSourceId       DataSource_Kind           `protobuf:"varint,9,opt,name=data_source_id,json=dataSourceId,proto3,enum=backend.operation.DataSource_Kind" json:"data_source_id,omitempty"`                       // 数据来源
+	DataSourceName     string                    `protobuf:"bytes,10,opt,name=data_source_name,json=dataSourceName,proto3" json:"data_source_name,omitempty"`                                                        // 数据来源
+	Remarks            string                    `protobuf:"bytes,11,opt,name=remarks,proto3" json:"remarks,omitempty"`                                                                                              // 备注
+	Version            int32                     `protobuf:"varint,12,opt,name=version,proto3" json:"version,omitempty"`                                                                                             // 版本号
+	IsShow             IsShow_Kind               `protobuf:"varint,13,opt,name=is_show,json=isShow,proto3,enum=backend.operation.IsShow_Kind" json:"is_show,omitempty"`                                              // 是否启用
+	IsModify           IsShow_Kind               `protobuf:"varint,14,opt,name=is_modify,json=isModify,proto3,enum=backend.operation.IsShow_Kind" json:"is_modify,omitempty"`                                        // 是否可修改
+	CreatedAt          int32                     `protobuf:"varint,15,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"`                                                                        // 创建时间
+	CreatedAtFormat    string                    `protobuf:"bytes,16,opt,name=created_at_format,json=createdAtFormat,proto3" json:"created_at_format,omitempty"`                                                     // 创建时间格式化
+	PastureName        string                    `protobuf:"bytes,17,opt,name=pasture_name,json=pastureName,proto3" json:"pasture_name,omitempty"`                                                                   // 牧场名称
+}
+
+func (x *AddFeedFormulaRequest) Reset() {
+	*x = AddFeedFormulaRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_backend_operation_feed_formula_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *AddFeedFormulaRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*AddFeedFormulaRequest) ProtoMessage() {}
+
+func (x *AddFeedFormulaRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_backend_operation_feed_formula_proto_msgTypes[0]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use AddFeedFormulaRequest.ProtoReflect.Descriptor instead.
+func (*AddFeedFormulaRequest) Descriptor() ([]byte, []int) {
+	return file_backend_operation_feed_formula_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *AddFeedFormulaRequest) GetId() int32 {
+	if x != nil {
+		return x.Id
+	}
+	return 0
+}
+
+func (x *AddFeedFormulaRequest) GetName() string {
+	if x != nil {
+		return x.Name
+	}
+	return ""
+}
+
+func (x *AddFeedFormulaRequest) GetEncodeNumber() string {
+	if x != nil {
+		return x.EncodeNumber
+	}
+	return ""
+}
+
+func (x *AddFeedFormulaRequest) GetColour() string {
+	if x != nil {
+		return x.Colour
+	}
+	return ""
+}
+
+func (x *AddFeedFormulaRequest) GetCattleCategoryId() CattleCategoryParent_Kind {
+	if x != nil {
+		return x.CattleCategoryId
+	}
+	return CattleCategoryParent_INVALID
+}
+
+func (x *AddFeedFormulaRequest) GetCattleCategoryName() string {
+	if x != nil {
+		return x.CattleCategoryName
+	}
+	return ""
+}
+
+func (x *AddFeedFormulaRequest) GetFormulaTypeId() int32 {
+	if x != nil {
+		return x.FormulaTypeId
+	}
+	return 0
+}
+
+func (x *AddFeedFormulaRequest) GetFormulaTypeName() string {
+	if x != nil {
+		return x.FormulaTypeName
+	}
+	return ""
+}
+
+func (x *AddFeedFormulaRequest) GetDataSourceId() DataSource_Kind {
+	if x != nil {
+		return x.DataSourceId
+	}
+	return DataSource_INVALID
+}
+
+func (x *AddFeedFormulaRequest) GetDataSourceName() string {
+	if x != nil {
+		return x.DataSourceName
+	}
+	return ""
+}
+
+func (x *AddFeedFormulaRequest) GetRemarks() string {
+	if x != nil {
+		return x.Remarks
+	}
+	return ""
+}
+
+func (x *AddFeedFormulaRequest) GetVersion() int32 {
+	if x != nil {
+		return x.Version
+	}
+	return 0
+}
+
+func (x *AddFeedFormulaRequest) GetIsShow() IsShow_Kind {
+	if x != nil {
+		return x.IsShow
+	}
+	return IsShow_INVALID
+}
+
+func (x *AddFeedFormulaRequest) GetIsModify() IsShow_Kind {
+	if x != nil {
+		return x.IsModify
+	}
+	return IsShow_INVALID
+}
+
+func (x *AddFeedFormulaRequest) GetCreatedAt() int32 {
+	if x != nil {
+		return x.CreatedAt
+	}
+	return 0
+}
+
+func (x *AddFeedFormulaRequest) GetCreatedAtFormat() string {
+	if x != nil {
+		return x.CreatedAtFormat
+	}
+	return ""
+}
+
+func (x *AddFeedFormulaRequest) GetPastureName() string {
+	if x != nil {
+		return x.PastureName
+	}
+	return ""
+}
+
+type SearchFeedFormulaRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	CattleCategoryId int32            `protobuf:"varint,1,opt,name=cattle_category_id,json=cattleCategoryId,proto3" json:"cattle_category_id,omitempty"`    // 分类id
+	FormulaTypeId    int32            `protobuf:"varint,2,opt,name=formula_type_id,json=formulaTypeId,proto3" json:"formula_type_id,omitempty"`             // 配方类型id
+	DataSource       int32            `protobuf:"varint,3,opt,name=data_source,json=dataSource,proto3" json:"data_source,omitempty"`                        // 饲料来源
+	IsShow           IsShow_Kind      `protobuf:"varint,4,opt,name=is_show,json=isShow,proto3,enum=backend.operation.IsShow_Kind" json:"is_show,omitempty"` // 是否启用
+	Name             string           `protobuf:"bytes,5,opt,name=name,proto3" json:"name,omitempty"`                                                       // 配方名称
+	Remarks          string           `protobuf:"bytes,6,opt,name=remarks,proto3" json:"remarks,omitempty"`                                                 // 备注
+	Pagination       *PaginationModel `protobuf:"bytes,7,opt,name=pagination,proto3" json:"pagination,omitempty"`                                           // 分页
+}
+
+func (x *SearchFeedFormulaRequest) Reset() {
+	*x = SearchFeedFormulaRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_backend_operation_feed_formula_proto_msgTypes[1]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *SearchFeedFormulaRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*SearchFeedFormulaRequest) ProtoMessage() {}
+
+func (x *SearchFeedFormulaRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_backend_operation_feed_formula_proto_msgTypes[1]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use SearchFeedFormulaRequest.ProtoReflect.Descriptor instead.
+func (*SearchFeedFormulaRequest) Descriptor() ([]byte, []int) {
+	return file_backend_operation_feed_formula_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *SearchFeedFormulaRequest) GetCattleCategoryId() int32 {
+	if x != nil {
+		return x.CattleCategoryId
+	}
+	return 0
+}
+
+func (x *SearchFeedFormulaRequest) GetFormulaTypeId() int32 {
+	if x != nil {
+		return x.FormulaTypeId
+	}
+	return 0
+}
+
+func (x *SearchFeedFormulaRequest) GetDataSource() int32 {
+	if x != nil {
+		return x.DataSource
+	}
+	return 0
+}
+
+func (x *SearchFeedFormulaRequest) GetIsShow() IsShow_Kind {
+	if x != nil {
+		return x.IsShow
+	}
+	return IsShow_INVALID
+}
+
+func (x *SearchFeedFormulaRequest) GetName() string {
+	if x != nil {
+		return x.Name
+	}
+	return ""
+}
+
+func (x *SearchFeedFormulaRequest) GetRemarks() string {
+	if x != nil {
+		return x.Remarks
+	}
+	return ""
+}
+
+func (x *SearchFeedFormulaRequest) GetPagination() *PaginationModel {
+	if x != nil {
+		return x.Pagination
+	}
+	return nil
+}
+
+type SearchFeedFormulaListResponse struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Page     int32                    `protobuf:"varint,1,opt,name=page,proto3" json:"page,omitempty"`
+	PageSize int32                    `protobuf:"varint,2,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"`
+	Total    int32                    `protobuf:"varint,3,opt,name=total,proto3" json:"total,omitempty"`
+	List     []*AddFeedFormulaRequest `protobuf:"bytes,4,rep,name=list,proto3" json:"list,omitempty"`
+}
+
+func (x *SearchFeedFormulaListResponse) Reset() {
+	*x = SearchFeedFormulaListResponse{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_backend_operation_feed_formula_proto_msgTypes[2]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *SearchFeedFormulaListResponse) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*SearchFeedFormulaListResponse) ProtoMessage() {}
+
+func (x *SearchFeedFormulaListResponse) ProtoReflect() protoreflect.Message {
+	mi := &file_backend_operation_feed_formula_proto_msgTypes[2]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use SearchFeedFormulaListResponse.ProtoReflect.Descriptor instead.
+func (*SearchFeedFormulaListResponse) Descriptor() ([]byte, []int) {
+	return file_backend_operation_feed_formula_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *SearchFeedFormulaListResponse) GetPage() int32 {
+	if x != nil {
+		return x.Page
+	}
+	return 0
+}
+
+func (x *SearchFeedFormulaListResponse) GetPageSize() int32 {
+	if x != nil {
+		return x.PageSize
+	}
+	return 0
+}
+
+func (x *SearchFeedFormulaListResponse) GetTotal() int32 {
+	if x != nil {
+		return x.Total
+	}
+	return 0
+}
+
+func (x *SearchFeedFormulaListResponse) GetList() []*AddFeedFormulaRequest {
+	if x != nil {
+		return x.List
+	}
+	return nil
+}
+
+// 是否启用
+type IsShowModifyFeedFormula struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	FeedFormulaId int32       `protobuf:"varint,1,opt,name=feed_formula_id,json=feedFormulaId,proto3" json:"feed_formula_id,omitempty"`
+	IsShow        IsShow_Kind `protobuf:"varint,2,opt,name=is_show,json=isShow,proto3,enum=backend.operation.IsShow_Kind" json:"is_show,omitempty"`
+	EditType      int32       `protobuf:"varint,3,opt,name=edit_type,json=editType,proto3" json:"edit_type,omitempty"` // 1 更新是否启用 2 更新 modify
+}
+
+func (x *IsShowModifyFeedFormula) Reset() {
+	*x = IsShowModifyFeedFormula{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_backend_operation_feed_formula_proto_msgTypes[3]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *IsShowModifyFeedFormula) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*IsShowModifyFeedFormula) ProtoMessage() {}
+
+func (x *IsShowModifyFeedFormula) ProtoReflect() protoreflect.Message {
+	mi := &file_backend_operation_feed_formula_proto_msgTypes[3]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use IsShowModifyFeedFormula.ProtoReflect.Descriptor instead.
+func (*IsShowModifyFeedFormula) Descriptor() ([]byte, []int) {
+	return file_backend_operation_feed_formula_proto_rawDescGZIP(), []int{3}
+}
+
+func (x *IsShowModifyFeedFormula) GetFeedFormulaId() int32 {
+	if x != nil {
+		return x.FeedFormulaId
+	}
+	return 0
+}
+
+func (x *IsShowModifyFeedFormula) GetIsShow() IsShow_Kind {
+	if x != nil {
+		return x.IsShow
+	}
+	return IsShow_INVALID
+}
+
+func (x *IsShowModifyFeedFormula) GetEditType() int32 {
+	if x != nil {
+		return x.EditType
+	}
+	return 0
+}
+
+var File_backend_operation_feed_formula_proto protoreflect.FileDescriptor
+
+var file_backend_operation_feed_formula_proto_rawDesc = []byte{
+	0x0a, 0x24, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74,
+	0x69, 0x6f, 0x6e, 0x2f, 0x66, 0x65, 0x65, 0x64, 0x5f, 0x66, 0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61,
+	0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x11, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e,
+	0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x22, 0x62, 0x61, 0x63, 0x6b, 0x65,
+	0x6e, 0x64, 0x2f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x70, 0x61, 0x67,
+	0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x62,
+	0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e,
+	0x2f, 0x65, 0x6e, 0x75, 0x6d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xe6, 0x05, 0x0a, 0x15,
+	0x41, 0x64, 0x64, 0x46, 0x65, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x52, 0x65,
+	0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
+	0x05, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20,
+	0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x65, 0x6e, 0x63,
+	0x6f, 0x64, 0x65, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x0c, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x16,
+	0x0a, 0x06, 0x63, 0x6f, 0x6c, 0x6f, 0x75, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06,
+	0x63, 0x6f, 0x6c, 0x6f, 0x75, 0x72, 0x12, 0x5a, 0x0a, 0x12, 0x63, 0x61, 0x74, 0x74, 0x6c, 0x65,
+	0x5f, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01,
+	0x28, 0x0e, 0x32, 0x2c, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x6f, 0x70, 0x65,
+	0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x43, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x43, 0x61, 0x74,
+	0x65, 0x67, 0x6f, 0x72, 0x79, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x2e, 0x4b, 0x69, 0x6e, 0x64,
+	0x52, 0x10, 0x63, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79,
+	0x49, 0x64, 0x12, 0x30, 0x0a, 0x14, 0x63, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x5f, 0x63, 0x61, 0x74,
+	0x65, 0x67, 0x6f, 0x72, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x12, 0x63, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79,
+	0x4e, 0x61, 0x6d, 0x65, 0x12, 0x26, 0x0a, 0x0f, 0x66, 0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x5f,
+	0x74, 0x79, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x66,
+	0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x54, 0x79, 0x70, 0x65, 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x11,
+	0x66, 0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x6e, 0x61, 0x6d,
+	0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x66, 0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61,
+	0x54, 0x79, 0x70, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x48, 0x0a, 0x0e, 0x64, 0x61, 0x74, 0x61,
+	0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0e,
+	0x32, 0x22, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x6f, 0x70, 0x65, 0x72, 0x61,
+	0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e,
+	0x4b, 0x69, 0x6e, 0x64, 0x52, 0x0c, 0x64, 0x61, 0x74, 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65,
+	0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63,
+	0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x64, 0x61,
+	0x74, 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07,
+	0x72, 0x65, 0x6d, 0x61, 0x72, 0x6b, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72,
+	0x65, 0x6d, 0x61, 0x72, 0x6b, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f,
+	0x6e, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e,
+	0x12, 0x37, 0x0a, 0x07, 0x69, 0x73, 0x5f, 0x73, 0x68, 0x6f, 0x77, 0x18, 0x0d, 0x20, 0x01, 0x28,
+	0x0e, 0x32, 0x1e, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x6f, 0x70, 0x65, 0x72,
+	0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x49, 0x73, 0x53, 0x68, 0x6f, 0x77, 0x2e, 0x4b, 0x69, 0x6e,
+	0x64, 0x52, 0x06, 0x69, 0x73, 0x53, 0x68, 0x6f, 0x77, 0x12, 0x3b, 0x0a, 0x09, 0x69, 0x73, 0x5f,
+	0x6d, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1e, 0x2e, 0x62,
+	0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e,
+	0x2e, 0x49, 0x73, 0x53, 0x68, 0x6f, 0x77, 0x2e, 0x4b, 0x69, 0x6e, 0x64, 0x52, 0x08, 0x69, 0x73,
+	0x4d, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65,
+	0x64, 0x5f, 0x61, 0x74, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61,
+	0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x2a, 0x0a, 0x11, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64,
+	0x5f, 0x61, 0x74, 0x5f, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x18, 0x10, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x0f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x46, 0x6f, 0x72, 0x6d, 0x61,
+	0x74, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x73, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x6e, 0x61, 0x6d,
+	0x65, 0x18, 0x11, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x61, 0x73, 0x74, 0x75, 0x72, 0x65,
+	0x4e, 0x61, 0x6d, 0x65, 0x22, 0xbc, 0x02, 0x0a, 0x18, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x46,
+	0x65, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
+	0x74, 0x12, 0x2c, 0x0a, 0x12, 0x63, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x5f, 0x63, 0x61, 0x74, 0x65,
+	0x67, 0x6f, 0x72, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x10, 0x63,
+	0x61, 0x74, 0x74, 0x6c, 0x65, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x49, 0x64, 0x12,
+	0x26, 0x0a, 0x0f, 0x66, 0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x5f,
+	0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x66, 0x6f, 0x72, 0x6d, 0x75, 0x6c,
+	0x61, 0x54, 0x79, 0x70, 0x65, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x64, 0x61, 0x74, 0x61, 0x5f,
+	0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x64, 0x61,
+	0x74, 0x61, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x37, 0x0a, 0x07, 0x69, 0x73, 0x5f, 0x73,
+	0x68, 0x6f, 0x77, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1e, 0x2e, 0x62, 0x61, 0x63, 0x6b,
+	0x65, 0x6e, 0x64, 0x2e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x49, 0x73,
+	0x53, 0x68, 0x6f, 0x77, 0x2e, 0x4b, 0x69, 0x6e, 0x64, 0x52, 0x06, 0x69, 0x73, 0x53, 0x68, 0x6f,
+	0x77, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52,
+	0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x72, 0x65, 0x6d, 0x61, 0x72, 0x6b, 0x73,
+	0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x65, 0x6d, 0x61, 0x72, 0x6b, 0x73, 0x12,
+	0x42, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20,
+	0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x6f, 0x70,
+	0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x50, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69,
+	0x6f, 0x6e, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x52, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74,
+	0x69, 0x6f, 0x6e, 0x22, 0xa4, 0x01, 0x0a, 0x1d, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x46, 0x65,
+	0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73,
+	0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20,
+	0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67,
+	0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61,
+	0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18,
+	0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x12, 0x3c, 0x0a, 0x04,
+	0x6c, 0x69, 0x73, 0x74, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x62, 0x61, 0x63,
+	0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x41,
+	0x64, 0x64, 0x46, 0x65, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x52, 0x65, 0x71,
+	0x75, 0x65, 0x73, 0x74, 0x52, 0x04, 0x6c, 0x69, 0x73, 0x74, 0x22, 0x97, 0x01, 0x0a, 0x17, 0x49,
+	0x73, 0x53, 0x68, 0x6f, 0x77, 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x46, 0x65, 0x65, 0x64, 0x46,
+	0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x12, 0x26, 0x0a, 0x0f, 0x66, 0x65, 0x65, 0x64, 0x5f, 0x66,
+	0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52,
+	0x0d, 0x66, 0x65, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x49, 0x64, 0x12, 0x37,
+	0x0a, 0x07, 0x69, 0x73, 0x5f, 0x73, 0x68, 0x6f, 0x77, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32,
+	0x1e, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74,
+	0x69, 0x6f, 0x6e, 0x2e, 0x49, 0x73, 0x53, 0x68, 0x6f, 0x77, 0x2e, 0x4b, 0x69, 0x6e, 0x64, 0x52,
+	0x06, 0x69, 0x73, 0x53, 0x68, 0x6f, 0x77, 0x12, 0x1b, 0x0a, 0x09, 0x65, 0x64, 0x69, 0x74, 0x5f,
+	0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x65, 0x64, 0x69, 0x74,
+	0x54, 0x79, 0x70, 0x65, 0x42, 0x0f, 0x5a, 0x0d, 0x2e, 0x3b, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74,
+	0x69, 0x6f, 0x6e, 0x50, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+	file_backend_operation_feed_formula_proto_rawDescOnce sync.Once
+	file_backend_operation_feed_formula_proto_rawDescData = file_backend_operation_feed_formula_proto_rawDesc
+)
+
+func file_backend_operation_feed_formula_proto_rawDescGZIP() []byte {
+	file_backend_operation_feed_formula_proto_rawDescOnce.Do(func() {
+		file_backend_operation_feed_formula_proto_rawDescData = protoimpl.X.CompressGZIP(file_backend_operation_feed_formula_proto_rawDescData)
+	})
+	return file_backend_operation_feed_formula_proto_rawDescData
+}
+
+var file_backend_operation_feed_formula_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
+var file_backend_operation_feed_formula_proto_goTypes = []interface{}{
+	(*AddFeedFormulaRequest)(nil),         // 0: backend.operation.AddFeedFormulaRequest
+	(*SearchFeedFormulaRequest)(nil),      // 1: backend.operation.SearchFeedFormulaRequest
+	(*SearchFeedFormulaListResponse)(nil), // 2: backend.operation.SearchFeedFormulaListResponse
+	(*IsShowModifyFeedFormula)(nil),       // 3: backend.operation.IsShowModifyFeedFormula
+	(CattleCategoryParent_Kind)(0),        // 4: backend.operation.CattleCategoryParent.Kind
+	(DataSource_Kind)(0),                  // 5: backend.operation.DataSource.Kind
+	(IsShow_Kind)(0),                      // 6: backend.operation.IsShow.Kind
+	(*PaginationModel)(nil),               // 7: backend.operation.PaginationModel
+}
+var file_backend_operation_feed_formula_proto_depIdxs = []int32{
+	4, // 0: backend.operation.AddFeedFormulaRequest.cattle_category_id:type_name -> backend.operation.CattleCategoryParent.Kind
+	5, // 1: backend.operation.AddFeedFormulaRequest.data_source_id:type_name -> backend.operation.DataSource.Kind
+	6, // 2: backend.operation.AddFeedFormulaRequest.is_show:type_name -> backend.operation.IsShow.Kind
+	6, // 3: backend.operation.AddFeedFormulaRequest.is_modify:type_name -> backend.operation.IsShow.Kind
+	6, // 4: backend.operation.SearchFeedFormulaRequest.is_show:type_name -> backend.operation.IsShow.Kind
+	7, // 5: backend.operation.SearchFeedFormulaRequest.pagination:type_name -> backend.operation.PaginationModel
+	0, // 6: backend.operation.SearchFeedFormulaListResponse.list:type_name -> backend.operation.AddFeedFormulaRequest
+	6, // 7: backend.operation.IsShowModifyFeedFormula.is_show:type_name -> backend.operation.IsShow.Kind
+	8, // [8:8] is the sub-list for method output_type
+	8, // [8:8] is the sub-list for method input_type
+	8, // [8:8] is the sub-list for extension type_name
+	8, // [8:8] is the sub-list for extension extendee
+	0, // [0:8] is the sub-list for field type_name
+}
+
+func init() { file_backend_operation_feed_formula_proto_init() }
+func file_backend_operation_feed_formula_proto_init() {
+	if File_backend_operation_feed_formula_proto != nil {
+		return
+	}
+	file_backend_operation_pagination_proto_init()
+	file_backend_operation_enum_proto_init()
+	if !protoimpl.UnsafeEnabled {
+		file_backend_operation_feed_formula_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*AddFeedFormulaRequest); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_backend_operation_feed_formula_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*SearchFeedFormulaRequest); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_backend_operation_feed_formula_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*SearchFeedFormulaListResponse); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_backend_operation_feed_formula_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*IsShowModifyFeedFormula); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+	}
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_backend_operation_feed_formula_proto_rawDesc,
+			NumEnums:      0,
+			NumMessages:   4,
+			NumExtensions: 0,
+			NumServices:   0,
+		},
+		GoTypes:           file_backend_operation_feed_formula_proto_goTypes,
+		DependencyIndexes: file_backend_operation_feed_formula_proto_depIdxs,
+		MessageInfos:      file_backend_operation_feed_formula_proto_msgTypes,
+	}.Build()
+	File_backend_operation_feed_formula_proto = out.File
+	file_backend_operation_feed_formula_proto_rawDesc = nil
+	file_backend_operation_feed_formula_proto_goTypes = nil
+	file_backend_operation_feed_formula_proto_depIdxs = nil
+}

+ 449 - 0
proto/go/backend/operation/mobile.pb.go

@@ -0,0 +1,449 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.28.1
+// 	protoc        v3.21.9
+// source: backend/operation/mobile.proto
+
+package operationPb
+
+import (
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	reflect "reflect"
+	sync "sync"
+)
+
+const (
+	// Verify that this generated code is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+	// Verify that runtime/protoimpl is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type SearchMobileRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Name       string           `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`             // 名称
+	Pagination *PaginationModel `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` // 分页
+}
+
+func (x *SearchMobileRequest) Reset() {
+	*x = SearchMobileRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_backend_operation_mobile_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *SearchMobileRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*SearchMobileRequest) ProtoMessage() {}
+
+func (x *SearchMobileRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_backend_operation_mobile_proto_msgTypes[0]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use SearchMobileRequest.ProtoReflect.Descriptor instead.
+func (*SearchMobileRequest) Descriptor() ([]byte, []int) {
+	return file_backend_operation_mobile_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *SearchMobileRequest) GetName() string {
+	if x != nil {
+		return x.Name
+	}
+	return ""
+}
+
+func (x *SearchMobileRequest) GetPagination() *PaginationModel {
+	if x != nil {
+		return x.Pagination
+	}
+	return nil
+}
+
+type SearchMobileResponse struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Code int32             `protobuf:"varint,1,opt,name=code,proto3" json:"code,omitempty"`
+	Msg  string            `protobuf:"bytes,2,opt,name=msg,proto3" json:"msg,omitempty"`
+	Data *SearchMobileData `protobuf:"bytes,3,opt,name=data,proto3" json:"data,omitempty"`
+}
+
+func (x *SearchMobileResponse) Reset() {
+	*x = SearchMobileResponse{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_backend_operation_mobile_proto_msgTypes[1]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *SearchMobileResponse) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*SearchMobileResponse) ProtoMessage() {}
+
+func (x *SearchMobileResponse) ProtoReflect() protoreflect.Message {
+	mi := &file_backend_operation_mobile_proto_msgTypes[1]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use SearchMobileResponse.ProtoReflect.Descriptor instead.
+func (*SearchMobileResponse) Descriptor() ([]byte, []int) {
+	return file_backend_operation_mobile_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *SearchMobileResponse) GetCode() int32 {
+	if x != nil {
+		return x.Code
+	}
+	return 0
+}
+
+func (x *SearchMobileResponse) GetMsg() string {
+	if x != nil {
+		return x.Msg
+	}
+	return ""
+}
+
+func (x *SearchMobileResponse) GetData() *SearchMobileData {
+	if x != nil {
+		return x.Data
+	}
+	return nil
+}
+
+type SearchMobileData struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Page     int32         `protobuf:"varint,1,opt,name=page,proto3" json:"page,omitempty"`
+	Total    int32         `protobuf:"varint,2,opt,name=total,proto3" json:"total,omitempty"`
+	PageSize int32         `protobuf:"varint,3,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"`
+	List     []*MobileData `protobuf:"bytes,4,rep,name=list,proto3" json:"list,omitempty"`
+}
+
+func (x *SearchMobileData) Reset() {
+	*x = SearchMobileData{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_backend_operation_mobile_proto_msgTypes[2]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *SearchMobileData) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*SearchMobileData) ProtoMessage() {}
+
+func (x *SearchMobileData) ProtoReflect() protoreflect.Message {
+	mi := &file_backend_operation_mobile_proto_msgTypes[2]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use SearchMobileData.ProtoReflect.Descriptor instead.
+func (*SearchMobileData) Descriptor() ([]byte, []int) {
+	return file_backend_operation_mobile_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *SearchMobileData) GetPage() int32 {
+	if x != nil {
+		return x.Page
+	}
+	return 0
+}
+
+func (x *SearchMobileData) GetTotal() int32 {
+	if x != nil {
+		return x.Total
+	}
+	return 0
+}
+
+func (x *SearchMobileData) GetPageSize() int32 {
+	if x != nil {
+		return x.PageSize
+	}
+	return 0
+}
+
+func (x *SearchMobileData) GetList() []*MobileData {
+	if x != nil {
+		return x.List
+	}
+	return nil
+}
+
+type MobileData struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Id              uint32            `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
+	Name            string            `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
+	CreatedAt       uint32            `protobuf:"varint,3,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"`
+	CreatedAtFormat string            `protobuf:"bytes,4,opt,name=created_at_format,json=createdAtFormat,proto3" json:"created_at_format,omitempty"`
+	Children        []*AddMenuRequest `protobuf:"bytes,15,rep,name=children,proto3" json:"children,omitempty"` // 子分类
+}
+
+func (x *MobileData) Reset() {
+	*x = MobileData{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_backend_operation_mobile_proto_msgTypes[3]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *MobileData) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*MobileData) ProtoMessage() {}
+
+func (x *MobileData) ProtoReflect() protoreflect.Message {
+	mi := &file_backend_operation_mobile_proto_msgTypes[3]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use MobileData.ProtoReflect.Descriptor instead.
+func (*MobileData) Descriptor() ([]byte, []int) {
+	return file_backend_operation_mobile_proto_rawDescGZIP(), []int{3}
+}
+
+func (x *MobileData) GetId() uint32 {
+	if x != nil {
+		return x.Id
+	}
+	return 0
+}
+
+func (x *MobileData) GetName() string {
+	if x != nil {
+		return x.Name
+	}
+	return ""
+}
+
+func (x *MobileData) GetCreatedAt() uint32 {
+	if x != nil {
+		return x.CreatedAt
+	}
+	return 0
+}
+
+func (x *MobileData) GetCreatedAtFormat() string {
+	if x != nil {
+		return x.CreatedAtFormat
+	}
+	return ""
+}
+
+func (x *MobileData) GetChildren() []*AddMenuRequest {
+	if x != nil {
+		return x.Children
+	}
+	return nil
+}
+
+var File_backend_operation_mobile_proto protoreflect.FileDescriptor
+
+var file_backend_operation_mobile_proto_rawDesc = []byte{
+	0x0a, 0x1e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74,
+	0x69, 0x6f, 0x6e, 0x2f, 0x6d, 0x6f, 0x62, 0x69, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+	0x12, 0x11, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74,
+	0x69, 0x6f, 0x6e, 0x1a, 0x22, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2f, 0x6f, 0x70, 0x65,
+	0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f,
+	0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64,
+	0x2f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x73, 0x79, 0x73, 0x74, 0x65,
+	0x6d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x6d, 0x0a, 0x13, 0x53, 0x65, 0x61, 0x72, 0x63,
+	0x68, 0x4d, 0x6f, 0x62, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12,
+	0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61,
+	0x6d, 0x65, 0x12, 0x42, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e,
+	0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64,
+	0x2e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x50, 0x61, 0x67, 0x69, 0x6e,
+	0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x52, 0x0a, 0x70, 0x61, 0x67, 0x69,
+	0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x75, 0x0a, 0x14, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68,
+	0x4d, 0x6f, 0x62, 0x69, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12,
+	0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x63, 0x6f,
+	0x64, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x73, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
+	0x03, 0x6d, 0x73, 0x67, 0x12, 0x37, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01,
+	0x28, 0x0b, 0x32, 0x23, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x6f, 0x70, 0x65,
+	0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x4d, 0x6f, 0x62,
+	0x69, 0x6c, 0x65, 0x44, 0x61, 0x74, 0x61, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x8c, 0x01,
+	0x0a, 0x10, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x4d, 0x6f, 0x62, 0x69, 0x6c, 0x65, 0x44, 0x61,
+	0x74, 0x61, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05,
+	0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18,
+	0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x12, 0x1b, 0x0a, 0x09,
+	0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52,
+	0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x31, 0x0a, 0x04, 0x6c, 0x69, 0x73,
+	0x74, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e,
+	0x64, 0x2e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x4d, 0x6f, 0x62, 0x69,
+	0x6c, 0x65, 0x44, 0x61, 0x74, 0x61, 0x52, 0x04, 0x6c, 0x69, 0x73, 0x74, 0x22, 0xba, 0x01, 0x0a,
+	0x0a, 0x4d, 0x6f, 0x62, 0x69, 0x6c, 0x65, 0x44, 0x61, 0x74, 0x61, 0x12, 0x0e, 0x0a, 0x02, 0x69,
+	0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e,
+	0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12,
+	0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x03, 0x20,
+	0x01, 0x28, 0x0d, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x2a,
+	0x0a, 0x11, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x66, 0x6f, 0x72,
+	0x6d, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x63, 0x72, 0x65, 0x61, 0x74,
+	0x65, 0x64, 0x41, 0x74, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x12, 0x3d, 0x0a, 0x08, 0x63, 0x68,
+	0x69, 0x6c, 0x64, 0x72, 0x65, 0x6e, 0x18, 0x0f, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x62,
+	0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e,
+	0x2e, 0x41, 0x64, 0x64, 0x4d, 0x65, 0x6e, 0x75, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52,
+	0x08, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x72, 0x65, 0x6e, 0x42, 0x0f, 0x5a, 0x0d, 0x2e, 0x3b, 0x6f,
+	0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
+	0x6f, 0x33,
+}
+
+var (
+	file_backend_operation_mobile_proto_rawDescOnce sync.Once
+	file_backend_operation_mobile_proto_rawDescData = file_backend_operation_mobile_proto_rawDesc
+)
+
+func file_backend_operation_mobile_proto_rawDescGZIP() []byte {
+	file_backend_operation_mobile_proto_rawDescOnce.Do(func() {
+		file_backend_operation_mobile_proto_rawDescData = protoimpl.X.CompressGZIP(file_backend_operation_mobile_proto_rawDescData)
+	})
+	return file_backend_operation_mobile_proto_rawDescData
+}
+
+var file_backend_operation_mobile_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
+var file_backend_operation_mobile_proto_goTypes = []interface{}{
+	(*SearchMobileRequest)(nil),  // 0: backend.operation.SearchMobileRequest
+	(*SearchMobileResponse)(nil), // 1: backend.operation.SearchMobileResponse
+	(*SearchMobileData)(nil),     // 2: backend.operation.SearchMobileData
+	(*MobileData)(nil),           // 3: backend.operation.MobileData
+	(*PaginationModel)(nil),      // 4: backend.operation.PaginationModel
+	(*AddMenuRequest)(nil),       // 5: backend.operation.AddMenuRequest
+}
+var file_backend_operation_mobile_proto_depIdxs = []int32{
+	4, // 0: backend.operation.SearchMobileRequest.pagination:type_name -> backend.operation.PaginationModel
+	2, // 1: backend.operation.SearchMobileResponse.data:type_name -> backend.operation.SearchMobileData
+	3, // 2: backend.operation.SearchMobileData.list:type_name -> backend.operation.MobileData
+	5, // 3: backend.operation.MobileData.children:type_name -> backend.operation.AddMenuRequest
+	4, // [4:4] is the sub-list for method output_type
+	4, // [4:4] is the sub-list for method input_type
+	4, // [4:4] is the sub-list for extension type_name
+	4, // [4:4] is the sub-list for extension extendee
+	0, // [0:4] is the sub-list for field type_name
+}
+
+func init() { file_backend_operation_mobile_proto_init() }
+func file_backend_operation_mobile_proto_init() {
+	if File_backend_operation_mobile_proto != nil {
+		return
+	}
+	file_backend_operation_pagination_proto_init()
+	file_backend_operation_system_proto_init()
+	if !protoimpl.UnsafeEnabled {
+		file_backend_operation_mobile_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*SearchMobileRequest); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_backend_operation_mobile_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*SearchMobileResponse); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_backend_operation_mobile_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*SearchMobileData); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_backend_operation_mobile_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*MobileData); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+	}
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_backend_operation_mobile_proto_rawDesc,
+			NumEnums:      0,
+			NumMessages:   4,
+			NumExtensions: 0,
+			NumServices:   0,
+		},
+		GoTypes:           file_backend_operation_mobile_proto_goTypes,
+		DependencyIndexes: file_backend_operation_mobile_proto_depIdxs,
+		MessageInfos:      file_backend_operation_mobile_proto_msgTypes,
+	}.Build()
+	File_backend_operation_mobile_proto = out.File
+	file_backend_operation_mobile_proto_rawDesc = nil
+	file_backend_operation_mobile_proto_goTypes = nil
+	file_backend_operation_mobile_proto_depIdxs = nil
+}

File diff suppressed because it is too large
+ 2987 - 137
proto/go/backend/operation/pasture.pb.go


+ 1702 - 0
proto/go/backend/operation/statistic.pb.go

@@ -0,0 +1,1702 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.28.1
+// 	protoc        v3.21.9
+// source: backend/operation/statistic.proto
+
+package operationPb
+
+import (
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	reflect "reflect"
+	sync "sync"
+)
+
+const (
+	// Verify that this generated code is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+	// Verify that runtime/protoimpl is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+// 添加配方评估 具体字段含义参照formula_estimate表对应的字段
+type AddFormulaEstimateRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Id                   int32  `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
+	PastureId            int32  `protobuf:"varint,2,opt,name=pasture_id,json=pastureId,proto3" json:"pasture_id,omitempty"`
+	PastureName          string `protobuf:"bytes,3,opt,name=pasture_name,json=pastureName,proto3" json:"pasture_name,omitempty"`
+	BarnId               int32  `protobuf:"varint,4,opt,name=barn_id,json=barnId,proto3" json:"barn_id,omitempty"`
+	FeedFormulaId        int32  `protobuf:"varint,5,opt,name=feed_formula_id,json=feedFormulaId,proto3" json:"feed_formula_id,omitempty"`
+	FeedFormulaName      string `protobuf:"bytes,6,opt,name=feed_formula_name,json=feedFormulaName,proto3" json:"feed_formula_name,omitempty"`
+	CowNumber            int32  `protobuf:"varint,7,opt,name=cow_number,json=cowNumber,proto3" json:"cow_number,omitempty"`
+	DryFormulaNumber     int32  `protobuf:"varint,8,opt,name=dry_formula_number,json=dryFormulaNumber,proto3" json:"dry_formula_number,omitempty"`
+	DryTmrFeed           int32  `protobuf:"varint,9,opt,name=dry_tmr_feed,json=dryTmrFeed,proto3" json:"dry_tmr_feed,omitempty"`
+	DryFoodIntake        int32  `protobuf:"varint,10,opt,name=dry_food_intake,json=dryFoodIntake,proto3" json:"dry_food_intake,omitempty"`
+	MjFormulaNumber      int32  `protobuf:"varint,11,opt,name=mj_formula_number,json=mjFormulaNumber,proto3" json:"mj_formula_number,omitempty"`
+	MjTmrFeed            int32  `protobuf:"varint,12,opt,name=mj_tmr_feed,json=mjTmrFeed,proto3" json:"mj_tmr_feed,omitempty"`
+	MjFoodIntake         int32  `protobuf:"varint,13,opt,name=mj_food_intake,json=mjFoodIntake,proto3" json:"mj_food_intake,omitempty"`
+	NndFormulaNumber     int32  `protobuf:"varint,14,opt,name=nnd_formula_number,json=nndFormulaNumber,proto3" json:"nnd_formula_number,omitempty"`
+	NndTmrFeed           int32  `protobuf:"varint,15,opt,name=nnd_tmr_feed,json=nndTmrFeed,proto3" json:"nnd_tmr_feed,omitempty"`
+	NndFoodIntake        int32  `protobuf:"varint,16,opt,name=nnd_food_intake,json=nndFoodIntake,proto3" json:"nnd_food_intake,omitempty"`
+	CpgFormulaNumber     int32  `protobuf:"varint,17,opt,name=cpg_formula_number,json=cpgFormulaNumber,proto3" json:"cpg_formula_number,omitempty"`
+	CpgTmrFeed           int32  `protobuf:"varint,18,opt,name=cpg_tmr_feed,json=cpgTmrFeed,proto3" json:"cpg_tmr_feed,omitempty"`
+	CpgFoodIntake        int32  `protobuf:"varint,19,opt,name=cpg_food_intake,json=cpgFoodIntake,proto3" json:"cpg_food_intake,omitempty"`
+	PgFormulaNumber      int32  `protobuf:"varint,20,opt,name=pg_formula_number,json=pgFormulaNumber,proto3" json:"pg_formula_number,omitempty"`
+	PgTmrFeed            int32  `protobuf:"varint,21,opt,name=pg_tmr_feed,json=pgTmrFeed,proto3" json:"pg_tmr_feed,omitempty"`
+	PgFoodIntake         int32  `protobuf:"varint,22,opt,name=pg_food_intake,json=pgFoodIntake,proto3" json:"pg_food_intake,omitempty"`
+	DmFormulaNumber      int32  `protobuf:"varint,23,opt,name=dm_formula_number,json=dmFormulaNumber,proto3" json:"dm_formula_number,omitempty"`
+	DmTmrFeed            int32  `protobuf:"varint,24,opt,name=dm_tmr_feed,json=dmTmrFeed,proto3" json:"dm_tmr_feed,omitempty"`
+	DmFoodIntake         int32  `protobuf:"varint,25,opt,name=dm_food_intake,json=dmFoodIntake,proto3" json:"dm_food_intake,omitempty"`
+	CpdmFormulaNumber    int32  `protobuf:"varint,26,opt,name=cpdm_formula_number,json=cpdmFormulaNumber,proto3" json:"cpdm_formula_number,omitempty"`
+	CpdmTmrFeed          int32  `protobuf:"varint,27,opt,name=cpdm_tmr_feed,json=cpdmTmrFeed,proto3" json:"cpdm_tmr_feed,omitempty"`
+	CpdmFoodIntake       int32  `protobuf:"varint,28,opt,name=cpdm_food_intake,json=cpdmFoodIntake,proto3" json:"cpdm_food_intake,omitempty"`
+	FatFormulaNumber     int32  `protobuf:"varint,29,opt,name=fat_formula_number,json=fatFormulaNumber,proto3" json:"fat_formula_number,omitempty"`
+	FatTmrFeed           int32  `protobuf:"varint,30,opt,name=fat_tmr_feed,json=fatTmrFeed,proto3" json:"fat_tmr_feed,omitempty"`
+	FatFoodIntake        int32  `protobuf:"varint,31,opt,name=fat_food_intake,json=fatFoodIntake,proto3" json:"fat_food_intake,omitempty"`
+	StarchFormulaNumber  int32  `protobuf:"varint,32,opt,name=starch_formula_number,json=starchFormulaNumber,proto3" json:"starch_formula_number,omitempty"`
+	StarchTmrFeed        int32  `protobuf:"varint,33,opt,name=starch_tmr_feed,json=starchTmrFeed,proto3" json:"starch_tmr_feed,omitempty"`
+	StarchFoodIntake     int32  `protobuf:"varint,34,opt,name=starch_food_intake,json=starchFoodIntake,proto3" json:"starch_food_intake,omitempty"`
+	NdfFormulaNumber     int32  `protobuf:"varint,35,opt,name=ndf_formula_number,json=ndfFormulaNumber,proto3" json:"ndf_formula_number,omitempty"`
+	NdfTmrFeed           int32  `protobuf:"varint,36,opt,name=ndf_tmr_feed,json=ndfTmrFeed,proto3" json:"ndf_tmr_feed,omitempty"`
+	NdfFoodIntake        int32  `protobuf:"varint,37,opt,name=ndf_food_intake,json=ndfFoodIntake,proto3" json:"ndf_food_intake,omitempty"`
+	CpNdfFormulaNumber   int32  `protobuf:"varint,38,opt,name=cp_ndf_formula_number,json=cpNdfFormulaNumber,proto3" json:"cp_ndf_formula_number,omitempty"`
+	CpNdfTmrFeed         int32  `protobuf:"varint,39,opt,name=cp_ndf_tmr_feed,json=cpNdfTmrFeed,proto3" json:"cp_ndf_tmr_feed,omitempty"`
+	CpNdfFoodIntake      int32  `protobuf:"varint,40,opt,name=cp_ndf_food_intake,json=cpNdfFoodIntake,proto3" json:"cp_ndf_food_intake,omitempty"`
+	AdfFormulaNumber     int32  `protobuf:"varint,41,opt,name=adf_formula_number,json=adfFormulaNumber,proto3" json:"adf_formula_number,omitempty"`
+	AdfTmrFeed           int32  `protobuf:"varint,42,opt,name=adf_tmr_feed,json=adfTmrFeed,proto3" json:"adf_tmr_feed,omitempty"`
+	AdfFoodIntake        int32  `protobuf:"varint,43,opt,name=adf_food_intake,json=adfFoodIntake,proto3" json:"adf_food_intake,omitempty"`
+	CalciumFormulaNumber int32  `protobuf:"varint,44,opt,name=calcium_formula_number,json=calciumFormulaNumber,proto3" json:"calcium_formula_number,omitempty"`
+	CalciumTmrFeed       int32  `protobuf:"varint,45,opt,name=calcium_tmr_feed,json=calciumTmrFeed,proto3" json:"calcium_tmr_feed,omitempty"`
+	CalciumFoodIntake    int32  `protobuf:"varint,46,opt,name=calcium_food_intake,json=calciumFoodIntake,proto3" json:"calcium_food_intake,omitempty"`
+	PdmFormulaNumber     int32  `protobuf:"varint,47,opt,name=pdm_formula_number,json=pdmFormulaNumber,proto3" json:"pdm_formula_number,omitempty"`
+	PdmTmrFeed           int32  `protobuf:"varint,48,opt,name=pdm_tmr_feed,json=pdmTmrFeed,proto3" json:"pdm_tmr_feed,omitempty"`
+	PdmFoodIntake        int32  `protobuf:"varint,49,opt,name=pdm_food_intake,json=pdmFoodIntake,proto3" json:"pdm_food_intake,omitempty"`
+	CfRatioFormulaNumber int32  `protobuf:"varint,50,opt,name=cf_ratio_formula_number,json=cfRatioFormulaNumber,proto3" json:"cf_ratio_formula_number,omitempty"`
+	CfRatioTmrFeed       int32  `protobuf:"varint,51,opt,name=cf_ratio_tmr_feed,json=cfRatioTmrFeed,proto3" json:"cf_ratio_tmr_feed,omitempty"`
+	CfRatioFoodIntake    int32  `protobuf:"varint,52,opt,name=cf_ratio_food_intake,json=cfRatioFoodIntake,proto3" json:"cf_ratio_food_intake,omitempty"`
+	CreatedAt            int32  `protobuf:"varint,53,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"`
+	CreatedAtFormat      string `protobuf:"bytes,54,opt,name=created_at_format,json=createdAtFormat,proto3" json:"created_at_format,omitempty"`
+}
+
+func (x *AddFormulaEstimateRequest) Reset() {
+	*x = AddFormulaEstimateRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_backend_operation_statistic_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *AddFormulaEstimateRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*AddFormulaEstimateRequest) ProtoMessage() {}
+
+func (x *AddFormulaEstimateRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_backend_operation_statistic_proto_msgTypes[0]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use AddFormulaEstimateRequest.ProtoReflect.Descriptor instead.
+func (*AddFormulaEstimateRequest) Descriptor() ([]byte, []int) {
+	return file_backend_operation_statistic_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *AddFormulaEstimateRequest) GetId() int32 {
+	if x != nil {
+		return x.Id
+	}
+	return 0
+}
+
+func (x *AddFormulaEstimateRequest) GetPastureId() int32 {
+	if x != nil {
+		return x.PastureId
+	}
+	return 0
+}
+
+func (x *AddFormulaEstimateRequest) GetPastureName() string {
+	if x != nil {
+		return x.PastureName
+	}
+	return ""
+}
+
+func (x *AddFormulaEstimateRequest) GetBarnId() int32 {
+	if x != nil {
+		return x.BarnId
+	}
+	return 0
+}
+
+func (x *AddFormulaEstimateRequest) GetFeedFormulaId() int32 {
+	if x != nil {
+		return x.FeedFormulaId
+	}
+	return 0
+}
+
+func (x *AddFormulaEstimateRequest) GetFeedFormulaName() string {
+	if x != nil {
+		return x.FeedFormulaName
+	}
+	return ""
+}
+
+func (x *AddFormulaEstimateRequest) GetCowNumber() int32 {
+	if x != nil {
+		return x.CowNumber
+	}
+	return 0
+}
+
+func (x *AddFormulaEstimateRequest) GetDryFormulaNumber() int32 {
+	if x != nil {
+		return x.DryFormulaNumber
+	}
+	return 0
+}
+
+func (x *AddFormulaEstimateRequest) GetDryTmrFeed() int32 {
+	if x != nil {
+		return x.DryTmrFeed
+	}
+	return 0
+}
+
+func (x *AddFormulaEstimateRequest) GetDryFoodIntake() int32 {
+	if x != nil {
+		return x.DryFoodIntake
+	}
+	return 0
+}
+
+func (x *AddFormulaEstimateRequest) GetMjFormulaNumber() int32 {
+	if x != nil {
+		return x.MjFormulaNumber
+	}
+	return 0
+}
+
+func (x *AddFormulaEstimateRequest) GetMjTmrFeed() int32 {
+	if x != nil {
+		return x.MjTmrFeed
+	}
+	return 0
+}
+
+func (x *AddFormulaEstimateRequest) GetMjFoodIntake() int32 {
+	if x != nil {
+		return x.MjFoodIntake
+	}
+	return 0
+}
+
+func (x *AddFormulaEstimateRequest) GetNndFormulaNumber() int32 {
+	if x != nil {
+		return x.NndFormulaNumber
+	}
+	return 0
+}
+
+func (x *AddFormulaEstimateRequest) GetNndTmrFeed() int32 {
+	if x != nil {
+		return x.NndTmrFeed
+	}
+	return 0
+}
+
+func (x *AddFormulaEstimateRequest) GetNndFoodIntake() int32 {
+	if x != nil {
+		return x.NndFoodIntake
+	}
+	return 0
+}
+
+func (x *AddFormulaEstimateRequest) GetCpgFormulaNumber() int32 {
+	if x != nil {
+		return x.CpgFormulaNumber
+	}
+	return 0
+}
+
+func (x *AddFormulaEstimateRequest) GetCpgTmrFeed() int32 {
+	if x != nil {
+		return x.CpgTmrFeed
+	}
+	return 0
+}
+
+func (x *AddFormulaEstimateRequest) GetCpgFoodIntake() int32 {
+	if x != nil {
+		return x.CpgFoodIntake
+	}
+	return 0
+}
+
+func (x *AddFormulaEstimateRequest) GetPgFormulaNumber() int32 {
+	if x != nil {
+		return x.PgFormulaNumber
+	}
+	return 0
+}
+
+func (x *AddFormulaEstimateRequest) GetPgTmrFeed() int32 {
+	if x != nil {
+		return x.PgTmrFeed
+	}
+	return 0
+}
+
+func (x *AddFormulaEstimateRequest) GetPgFoodIntake() int32 {
+	if x != nil {
+		return x.PgFoodIntake
+	}
+	return 0
+}
+
+func (x *AddFormulaEstimateRequest) GetDmFormulaNumber() int32 {
+	if x != nil {
+		return x.DmFormulaNumber
+	}
+	return 0
+}
+
+func (x *AddFormulaEstimateRequest) GetDmTmrFeed() int32 {
+	if x != nil {
+		return x.DmTmrFeed
+	}
+	return 0
+}
+
+func (x *AddFormulaEstimateRequest) GetDmFoodIntake() int32 {
+	if x != nil {
+		return x.DmFoodIntake
+	}
+	return 0
+}
+
+func (x *AddFormulaEstimateRequest) GetCpdmFormulaNumber() int32 {
+	if x != nil {
+		return x.CpdmFormulaNumber
+	}
+	return 0
+}
+
+func (x *AddFormulaEstimateRequest) GetCpdmTmrFeed() int32 {
+	if x != nil {
+		return x.CpdmTmrFeed
+	}
+	return 0
+}
+
+func (x *AddFormulaEstimateRequest) GetCpdmFoodIntake() int32 {
+	if x != nil {
+		return x.CpdmFoodIntake
+	}
+	return 0
+}
+
+func (x *AddFormulaEstimateRequest) GetFatFormulaNumber() int32 {
+	if x != nil {
+		return x.FatFormulaNumber
+	}
+	return 0
+}
+
+func (x *AddFormulaEstimateRequest) GetFatTmrFeed() int32 {
+	if x != nil {
+		return x.FatTmrFeed
+	}
+	return 0
+}
+
+func (x *AddFormulaEstimateRequest) GetFatFoodIntake() int32 {
+	if x != nil {
+		return x.FatFoodIntake
+	}
+	return 0
+}
+
+func (x *AddFormulaEstimateRequest) GetStarchFormulaNumber() int32 {
+	if x != nil {
+		return x.StarchFormulaNumber
+	}
+	return 0
+}
+
+func (x *AddFormulaEstimateRequest) GetStarchTmrFeed() int32 {
+	if x != nil {
+		return x.StarchTmrFeed
+	}
+	return 0
+}
+
+func (x *AddFormulaEstimateRequest) GetStarchFoodIntake() int32 {
+	if x != nil {
+		return x.StarchFoodIntake
+	}
+	return 0
+}
+
+func (x *AddFormulaEstimateRequest) GetNdfFormulaNumber() int32 {
+	if x != nil {
+		return x.NdfFormulaNumber
+	}
+	return 0
+}
+
+func (x *AddFormulaEstimateRequest) GetNdfTmrFeed() int32 {
+	if x != nil {
+		return x.NdfTmrFeed
+	}
+	return 0
+}
+
+func (x *AddFormulaEstimateRequest) GetNdfFoodIntake() int32 {
+	if x != nil {
+		return x.NdfFoodIntake
+	}
+	return 0
+}
+
+func (x *AddFormulaEstimateRequest) GetCpNdfFormulaNumber() int32 {
+	if x != nil {
+		return x.CpNdfFormulaNumber
+	}
+	return 0
+}
+
+func (x *AddFormulaEstimateRequest) GetCpNdfTmrFeed() int32 {
+	if x != nil {
+		return x.CpNdfTmrFeed
+	}
+	return 0
+}
+
+func (x *AddFormulaEstimateRequest) GetCpNdfFoodIntake() int32 {
+	if x != nil {
+		return x.CpNdfFoodIntake
+	}
+	return 0
+}
+
+func (x *AddFormulaEstimateRequest) GetAdfFormulaNumber() int32 {
+	if x != nil {
+		return x.AdfFormulaNumber
+	}
+	return 0
+}
+
+func (x *AddFormulaEstimateRequest) GetAdfTmrFeed() int32 {
+	if x != nil {
+		return x.AdfTmrFeed
+	}
+	return 0
+}
+
+func (x *AddFormulaEstimateRequest) GetAdfFoodIntake() int32 {
+	if x != nil {
+		return x.AdfFoodIntake
+	}
+	return 0
+}
+
+func (x *AddFormulaEstimateRequest) GetCalciumFormulaNumber() int32 {
+	if x != nil {
+		return x.CalciumFormulaNumber
+	}
+	return 0
+}
+
+func (x *AddFormulaEstimateRequest) GetCalciumTmrFeed() int32 {
+	if x != nil {
+		return x.CalciumTmrFeed
+	}
+	return 0
+}
+
+func (x *AddFormulaEstimateRequest) GetCalciumFoodIntake() int32 {
+	if x != nil {
+		return x.CalciumFoodIntake
+	}
+	return 0
+}
+
+func (x *AddFormulaEstimateRequest) GetPdmFormulaNumber() int32 {
+	if x != nil {
+		return x.PdmFormulaNumber
+	}
+	return 0
+}
+
+func (x *AddFormulaEstimateRequest) GetPdmTmrFeed() int32 {
+	if x != nil {
+		return x.PdmTmrFeed
+	}
+	return 0
+}
+
+func (x *AddFormulaEstimateRequest) GetPdmFoodIntake() int32 {
+	if x != nil {
+		return x.PdmFoodIntake
+	}
+	return 0
+}
+
+func (x *AddFormulaEstimateRequest) GetCfRatioFormulaNumber() int32 {
+	if x != nil {
+		return x.CfRatioFormulaNumber
+	}
+	return 0
+}
+
+func (x *AddFormulaEstimateRequest) GetCfRatioTmrFeed() int32 {
+	if x != nil {
+		return x.CfRatioTmrFeed
+	}
+	return 0
+}
+
+func (x *AddFormulaEstimateRequest) GetCfRatioFoodIntake() int32 {
+	if x != nil {
+		return x.CfRatioFoodIntake
+	}
+	return 0
+}
+
+func (x *AddFormulaEstimateRequest) GetCreatedAt() int32 {
+	if x != nil {
+		return x.CreatedAt
+	}
+	return 0
+}
+
+func (x *AddFormulaEstimateRequest) GetCreatedAtFormat() string {
+	if x != nil {
+		return x.CreatedAtFormat
+	}
+	return ""
+}
+
+type SearchFormulaEstimateRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	StartTime  string           `protobuf:"bytes,1,opt,name=start_time,json=startTime,proto3" json:"start_time,omitempty"`     // 开始时间
+	EndTime    string           `protobuf:"bytes,2,opt,name=end_time,json=endTime,proto3" json:"end_time,omitempty"`           // 结束时间
+	SearchType int32            `protobuf:"varint,3,opt,name=search_type,json=searchType,proto3" json:"search_type,omitempty"` // 查询方式  1 安照配方 2 按照栏舍
+	Name       string           `protobuf:"bytes,4,opt,name=name,proto3" json:"name,omitempty"`                                // 名称
+	Pagination *PaginationModel `protobuf:"bytes,5,opt,name=pagination,proto3" json:"pagination,omitempty"`                    // 分页
+}
+
+func (x *SearchFormulaEstimateRequest) Reset() {
+	*x = SearchFormulaEstimateRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_backend_operation_statistic_proto_msgTypes[1]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *SearchFormulaEstimateRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*SearchFormulaEstimateRequest) ProtoMessage() {}
+
+func (x *SearchFormulaEstimateRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_backend_operation_statistic_proto_msgTypes[1]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use SearchFormulaEstimateRequest.ProtoReflect.Descriptor instead.
+func (*SearchFormulaEstimateRequest) Descriptor() ([]byte, []int) {
+	return file_backend_operation_statistic_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *SearchFormulaEstimateRequest) GetStartTime() string {
+	if x != nil {
+		return x.StartTime
+	}
+	return ""
+}
+
+func (x *SearchFormulaEstimateRequest) GetEndTime() string {
+	if x != nil {
+		return x.EndTime
+	}
+	return ""
+}
+
+func (x *SearchFormulaEstimateRequest) GetSearchType() int32 {
+	if x != nil {
+		return x.SearchType
+	}
+	return 0
+}
+
+func (x *SearchFormulaEstimateRequest) GetName() string {
+	if x != nil {
+		return x.Name
+	}
+	return ""
+}
+
+func (x *SearchFormulaEstimateRequest) GetPagination() *PaginationModel {
+	if x != nil {
+		return x.Pagination
+	}
+	return nil
+}
+
+type SearchFormulaEstimateResponse struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Code int32                  `protobuf:"varint,1,opt,name=code,proto3" json:"code,omitempty"`
+	Msg  string                 `protobuf:"bytes,2,opt,name=msg,proto3" json:"msg,omitempty"`
+	Data *SearchFormulaEstimate `protobuf:"bytes,3,opt,name=data,proto3" json:"data,omitempty"`
+}
+
+func (x *SearchFormulaEstimateResponse) Reset() {
+	*x = SearchFormulaEstimateResponse{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_backend_operation_statistic_proto_msgTypes[2]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *SearchFormulaEstimateResponse) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*SearchFormulaEstimateResponse) ProtoMessage() {}
+
+func (x *SearchFormulaEstimateResponse) ProtoReflect() protoreflect.Message {
+	mi := &file_backend_operation_statistic_proto_msgTypes[2]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use SearchFormulaEstimateResponse.ProtoReflect.Descriptor instead.
+func (*SearchFormulaEstimateResponse) Descriptor() ([]byte, []int) {
+	return file_backend_operation_statistic_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *SearchFormulaEstimateResponse) GetCode() int32 {
+	if x != nil {
+		return x.Code
+	}
+	return 0
+}
+
+func (x *SearchFormulaEstimateResponse) GetMsg() string {
+	if x != nil {
+		return x.Msg
+	}
+	return ""
+}
+
+func (x *SearchFormulaEstimateResponse) GetData() *SearchFormulaEstimate {
+	if x != nil {
+		return x.Data
+	}
+	return nil
+}
+
+type SearchFormulaEstimate struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Page     int32                        `protobuf:"varint,1,opt,name=page,proto3" json:"page,omitempty"`
+	Total    int32                        `protobuf:"varint,2,opt,name=total,proto3" json:"total,omitempty"`
+	PageSize int32                        `protobuf:"varint,3,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"`
+	List     []*AddFormulaEstimateRequest `protobuf:"bytes,4,rep,name=list,proto3" json:"list,omitempty"`
+}
+
+func (x *SearchFormulaEstimate) Reset() {
+	*x = SearchFormulaEstimate{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_backend_operation_statistic_proto_msgTypes[3]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *SearchFormulaEstimate) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*SearchFormulaEstimate) ProtoMessage() {}
+
+func (x *SearchFormulaEstimate) ProtoReflect() protoreflect.Message {
+	mi := &file_backend_operation_statistic_proto_msgTypes[3]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use SearchFormulaEstimate.ProtoReflect.Descriptor instead.
+func (*SearchFormulaEstimate) Descriptor() ([]byte, []int) {
+	return file_backend_operation_statistic_proto_rawDescGZIP(), []int{3}
+}
+
+func (x *SearchFormulaEstimate) GetPage() int32 {
+	if x != nil {
+		return x.Page
+	}
+	return 0
+}
+
+func (x *SearchFormulaEstimate) GetTotal() int32 {
+	if x != nil {
+		return x.Total
+	}
+	return 0
+}
+
+func (x *SearchFormulaEstimate) GetPageSize() int32 {
+	if x != nil {
+		return x.PageSize
+	}
+	return 0
+}
+
+func (x *SearchFormulaEstimate) GetList() []*AddFormulaEstimateRequest {
+	if x != nil {
+		return x.List
+	}
+	return nil
+}
+
+// 首页 dashboard 准确性分析
+type SearchAnalysisAccuracyRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	CattleParentCategoryId CattleCategoryParent_Kind `protobuf:"varint,1,opt,name=cattle_parent_category_id,json=cattleParentCategoryId,proto3,enum=backend.operation.CattleCategoryParent_Kind" json:"cattle_parent_category_id,omitempty"` // 牧畜分类id 泌乳牛
+	FeedFormulaId          int32                     `protobuf:"varint,2,opt,name=feed_formula_id,json=feedFormulaId,proto3" json:"feed_formula_id,omitempty"`                                                                               // 配方id
+	StartDate              string                    `protobuf:"bytes,3,opt,name=start_date,json=startDate,proto3" json:"start_date,omitempty"`                                                                                              // 开始时间
+	EndDate                string                    `protobuf:"bytes,4,opt,name=end_date,json=endDate,proto3" json:"end_date,omitempty"`                                                                                                    // 结束时间
+	PastureIds             []int32                   `protobuf:"varint,5,rep,packed,name=pasture_ids,json=pastureIds,proto3" json:"pasture_ids,omitempty"`                                                                                   //牧场ids
+}
+
+func (x *SearchAnalysisAccuracyRequest) Reset() {
+	*x = SearchAnalysisAccuracyRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_backend_operation_statistic_proto_msgTypes[4]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *SearchAnalysisAccuracyRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*SearchAnalysisAccuracyRequest) ProtoMessage() {}
+
+func (x *SearchAnalysisAccuracyRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_backend_operation_statistic_proto_msgTypes[4]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use SearchAnalysisAccuracyRequest.ProtoReflect.Descriptor instead.
+func (*SearchAnalysisAccuracyRequest) Descriptor() ([]byte, []int) {
+	return file_backend_operation_statistic_proto_rawDescGZIP(), []int{4}
+}
+
+func (x *SearchAnalysisAccuracyRequest) GetCattleParentCategoryId() CattleCategoryParent_Kind {
+	if x != nil {
+		return x.CattleParentCategoryId
+	}
+	return CattleCategoryParent_INVALID
+}
+
+func (x *SearchAnalysisAccuracyRequest) GetFeedFormulaId() int32 {
+	if x != nil {
+		return x.FeedFormulaId
+	}
+	return 0
+}
+
+func (x *SearchAnalysisAccuracyRequest) GetStartDate() string {
+	if x != nil {
+		return x.StartDate
+	}
+	return ""
+}
+
+func (x *SearchAnalysisAccuracyRequest) GetEndDate() string {
+	if x != nil {
+		return x.EndDate
+	}
+	return ""
+}
+
+func (x *SearchAnalysisAccuracyRequest) GetPastureIds() []int32 {
+	if x != nil {
+		return x.PastureIds
+	}
+	return nil
+}
+
+type SearchAnalysisAccuracyResponse struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Code int32             `protobuf:"varint,1,opt,name=code,proto3" json:"code,omitempty"`
+	Msg  string            `protobuf:"bytes,2,opt,name=msg,proto3" json:"msg,omitempty"`
+	Data *AnalysisAccuracy `protobuf:"bytes,3,opt,name=data,proto3" json:"data,omitempty"`
+}
+
+func (x *SearchAnalysisAccuracyResponse) Reset() {
+	*x = SearchAnalysisAccuracyResponse{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_backend_operation_statistic_proto_msgTypes[5]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *SearchAnalysisAccuracyResponse) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*SearchAnalysisAccuracyResponse) ProtoMessage() {}
+
+func (x *SearchAnalysisAccuracyResponse) ProtoReflect() protoreflect.Message {
+	mi := &file_backend_operation_statistic_proto_msgTypes[5]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use SearchAnalysisAccuracyResponse.ProtoReflect.Descriptor instead.
+func (*SearchAnalysisAccuracyResponse) Descriptor() ([]byte, []int) {
+	return file_backend_operation_statistic_proto_rawDescGZIP(), []int{5}
+}
+
+func (x *SearchAnalysisAccuracyResponse) GetCode() int32 {
+	if x != nil {
+		return x.Code
+	}
+	return 0
+}
+
+func (x *SearchAnalysisAccuracyResponse) GetMsg() string {
+	if x != nil {
+		return x.Msg
+	}
+	return ""
+}
+
+func (x *SearchAnalysisAccuracyResponse) GetData() *AnalysisAccuracy {
+	if x != nil {
+		return x.Data
+	}
+	return nil
+}
+
+type AnalysisAccuracy struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Chart *Chart `protobuf:"bytes,1,opt,name=chart,proto3" json:"chart,omitempty"`
+	Table *Table `protobuf:"bytes,2,opt,name=table,proto3" json:"table,omitempty"`
+}
+
+func (x *AnalysisAccuracy) Reset() {
+	*x = AnalysisAccuracy{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_backend_operation_statistic_proto_msgTypes[6]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *AnalysisAccuracy) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*AnalysisAccuracy) ProtoMessage() {}
+
+func (x *AnalysisAccuracy) ProtoReflect() protoreflect.Message {
+	mi := &file_backend_operation_statistic_proto_msgTypes[6]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use AnalysisAccuracy.ProtoReflect.Descriptor instead.
+func (*AnalysisAccuracy) Descriptor() ([]byte, []int) {
+	return file_backend_operation_statistic_proto_rawDescGZIP(), []int{6}
+}
+
+func (x *AnalysisAccuracy) GetChart() *Chart {
+	if x != nil {
+		return x.Chart
+	}
+	return nil
+}
+
+func (x *AnalysisAccuracy) GetTable() *Table {
+	if x != nil {
+		return x.Table
+	}
+	return nil
+}
+
+type Chart struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	MixedFodderAccurateRatio    *CommonValueRatio `protobuf:"bytes,4,opt,name=mixed_fodder_accurate_ratio,json=mixedFodderAccurateRatio,proto3" json:"mixed_fodder_accurate_ratio,omitempty"`          // 混料准确率
+	MixedFodderCorrectRatio     *CommonValueRatio `protobuf:"bytes,5,opt,name=mixed_fodder_correct_ratio,json=mixedFodderCorrectRatio,proto3" json:"mixed_fodder_correct_ratio,omitempty"`             // 混料正确率
+	SprinkleFodderAccurateRatio *CommonValueRatio `protobuf:"bytes,6,opt,name=sprinkle_fodder_accurate_ratio,json=sprinkleFodderAccurateRatio,proto3" json:"sprinkle_fodder_accurate_ratio,omitempty"` // 撒料准确率
+	SprinkleFodderCorrectRatio  *CommonValueRatio `protobuf:"bytes,7,opt,name=sprinkle_fodder_correct_ratio,json=sprinkleFodderCorrectRatio,proto3" json:"sprinkle_fodder_correct_ratio,omitempty"`    // 撒料正确率
+}
+
+func (x *Chart) Reset() {
+	*x = Chart{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_backend_operation_statistic_proto_msgTypes[7]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *Chart) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Chart) ProtoMessage() {}
+
+func (x *Chart) ProtoReflect() protoreflect.Message {
+	mi := &file_backend_operation_statistic_proto_msgTypes[7]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use Chart.ProtoReflect.Descriptor instead.
+func (*Chart) Descriptor() ([]byte, []int) {
+	return file_backend_operation_statistic_proto_rawDescGZIP(), []int{7}
+}
+
+func (x *Chart) GetMixedFodderAccurateRatio() *CommonValueRatio {
+	if x != nil {
+		return x.MixedFodderAccurateRatio
+	}
+	return nil
+}
+
+func (x *Chart) GetMixedFodderCorrectRatio() *CommonValueRatio {
+	if x != nil {
+		return x.MixedFodderCorrectRatio
+	}
+	return nil
+}
+
+func (x *Chart) GetSprinkleFodderAccurateRatio() *CommonValueRatio {
+	if x != nil {
+		return x.SprinkleFodderAccurateRatio
+	}
+	return nil
+}
+
+func (x *Chart) GetSprinkleFodderCorrectRatio() *CommonValueRatio {
+	if x != nil {
+		return x.SprinkleFodderCorrectRatio
+	}
+	return nil
+}
+
+type Table struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	TableList []*Table_TableList `protobuf:"bytes,1,rep,name=table_list,json=tableList,proto3" json:"table_list,omitempty"`
+}
+
+func (x *Table) Reset() {
+	*x = Table{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_backend_operation_statistic_proto_msgTypes[8]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *Table) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Table) ProtoMessage() {}
+
+func (x *Table) ProtoReflect() protoreflect.Message {
+	mi := &file_backend_operation_statistic_proto_msgTypes[8]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use Table.ProtoReflect.Descriptor instead.
+func (*Table) Descriptor() ([]byte, []int) {
+	return file_backend_operation_statistic_proto_rawDescGZIP(), []int{8}
+}
+
+func (x *Table) GetTableList() []*Table_TableList {
+	if x != nil {
+		return x.TableList
+	}
+	return nil
+}
+
+type CommonValueRatio struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	MaxValue    string        `protobuf:"bytes,1,opt,name=max_value,json=maxValue,proto3" json:"max_value,omitempty"`          // 最高值
+	MiddleValue string        `protobuf:"bytes,2,opt,name=middle_value,json=middleValue,proto3" json:"middle_value,omitempty"` // 中位值
+	MinValue    string        `protobuf:"bytes,3,opt,name=min_value,json=minValue,proto3" json:"min_value,omitempty"`          // 最低值
+	DataList    []*ValueRatio `protobuf:"bytes,4,rep,name=data_list,json=dataList,proto3" json:"data_list,omitempty"`          // 数据集合
+	PastureName []string      `protobuf:"bytes,5,rep,name=pasture_name,json=pastureName,proto3" json:"pasture_name,omitempty"` // 牧场名称集合
+	DateDay     []string      `protobuf:"bytes,6,rep,name=date_day,json=dateDay,proto3" json:"date_day,omitempty"`             // 日期集合
+}
+
+func (x *CommonValueRatio) Reset() {
+	*x = CommonValueRatio{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_backend_operation_statistic_proto_msgTypes[9]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *CommonValueRatio) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*CommonValueRatio) ProtoMessage() {}
+
+func (x *CommonValueRatio) ProtoReflect() protoreflect.Message {
+	mi := &file_backend_operation_statistic_proto_msgTypes[9]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use CommonValueRatio.ProtoReflect.Descriptor instead.
+func (*CommonValueRatio) Descriptor() ([]byte, []int) {
+	return file_backend_operation_statistic_proto_rawDescGZIP(), []int{9}
+}
+
+func (x *CommonValueRatio) GetMaxValue() string {
+	if x != nil {
+		return x.MaxValue
+	}
+	return ""
+}
+
+func (x *CommonValueRatio) GetMiddleValue() string {
+	if x != nil {
+		return x.MiddleValue
+	}
+	return ""
+}
+
+func (x *CommonValueRatio) GetMinValue() string {
+	if x != nil {
+		return x.MinValue
+	}
+	return ""
+}
+
+func (x *CommonValueRatio) GetDataList() []*ValueRatio {
+	if x != nil {
+		return x.DataList
+	}
+	return nil
+}
+
+func (x *CommonValueRatio) GetPastureName() []string {
+	if x != nil {
+		return x.PastureName
+	}
+	return nil
+}
+
+func (x *CommonValueRatio) GetDateDay() []string {
+	if x != nil {
+		return x.DateDay
+	}
+	return nil
+}
+
+type ValueRatio struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	ValueRatio []string `protobuf:"bytes,1,rep,name=value_ratio,json=valueRatio,proto3" json:"value_ratio,omitempty"`
+}
+
+func (x *ValueRatio) Reset() {
+	*x = ValueRatio{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_backend_operation_statistic_proto_msgTypes[10]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *ValueRatio) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ValueRatio) ProtoMessage() {}
+
+func (x *ValueRatio) ProtoReflect() protoreflect.Message {
+	mi := &file_backend_operation_statistic_proto_msgTypes[10]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use ValueRatio.ProtoReflect.Descriptor instead.
+func (*ValueRatio) Descriptor() ([]byte, []int) {
+	return file_backend_operation_statistic_proto_rawDescGZIP(), []int{10}
+}
+
+func (x *ValueRatio) GetValueRatio() []string {
+	if x != nil {
+		return x.ValueRatio
+	}
+	return nil
+}
+
+type Table_TableList struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Id   int32  `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
+	Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
+}
+
+func (x *Table_TableList) Reset() {
+	*x = Table_TableList{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_backend_operation_statistic_proto_msgTypes[11]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *Table_TableList) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Table_TableList) ProtoMessage() {}
+
+func (x *Table_TableList) ProtoReflect() protoreflect.Message {
+	mi := &file_backend_operation_statistic_proto_msgTypes[11]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use Table_TableList.ProtoReflect.Descriptor instead.
+func (*Table_TableList) Descriptor() ([]byte, []int) {
+	return file_backend_operation_statistic_proto_rawDescGZIP(), []int{8, 0}
+}
+
+func (x *Table_TableList) GetId() int32 {
+	if x != nil {
+		return x.Id
+	}
+	return 0
+}
+
+func (x *Table_TableList) GetName() string {
+	if x != nil {
+		return x.Name
+	}
+	return ""
+}
+
+var File_backend_operation_statistic_proto protoreflect.FileDescriptor
+
+var file_backend_operation_statistic_proto_rawDesc = []byte{
+	0x0a, 0x21, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74,
+	0x69, 0x6f, 0x6e, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, 0x2e, 0x70, 0x72,
+	0x6f, 0x74, 0x6f, 0x12, 0x11, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x6f, 0x70, 0x65,
+	0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x22, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2f,
+	0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61,
+	0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x62, 0x61, 0x63, 0x6b,
+	0x65, 0x6e, 0x64, 0x2f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x65, 0x6e,
+	0x75, 0x6d, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x94, 0x11, 0x0a, 0x19, 0x41, 0x64, 0x64,
+	0x46, 0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x52,
+	0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01,
+	0x28, 0x05, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x61, 0x73, 0x74, 0x75, 0x72,
+	0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x70, 0x61, 0x73, 0x74,
+	0x75, 0x72, 0x65, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x73, 0x74, 0x75, 0x72, 0x65,
+	0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x61, 0x73,
+	0x74, 0x75, 0x72, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x62, 0x61, 0x72, 0x6e,
+	0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x62, 0x61, 0x72, 0x6e, 0x49,
+	0x64, 0x12, 0x26, 0x0a, 0x0f, 0x66, 0x65, 0x65, 0x64, 0x5f, 0x66, 0x6f, 0x72, 0x6d, 0x75, 0x6c,
+	0x61, 0x5f, 0x69, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x66, 0x65, 0x65, 0x64,
+	0x46, 0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x49, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x66, 0x65, 0x65,
+	0x64, 0x5f, 0x66, 0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x06,
+	0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x66, 0x65, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x75, 0x6c,
+	0x61, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x6f, 0x77, 0x5f, 0x6e, 0x75, 0x6d,
+	0x62, 0x65, 0x72, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x63, 0x6f, 0x77, 0x4e, 0x75,
+	0x6d, 0x62, 0x65, 0x72, 0x12, 0x2c, 0x0a, 0x12, 0x64, 0x72, 0x79, 0x5f, 0x66, 0x6f, 0x72, 0x6d,
+	0x75, 0x6c, 0x61, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, 0x05,
+	0x52, 0x10, 0x64, 0x72, 0x79, 0x46, 0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x4e, 0x75, 0x6d, 0x62,
+	0x65, 0x72, 0x12, 0x20, 0x0a, 0x0c, 0x64, 0x72, 0x79, 0x5f, 0x74, 0x6d, 0x72, 0x5f, 0x66, 0x65,
+	0x65, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x64, 0x72, 0x79, 0x54, 0x6d, 0x72,
+	0x46, 0x65, 0x65, 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x64, 0x72, 0x79, 0x5f, 0x66, 0x6f, 0x6f, 0x64,
+	0x5f, 0x69, 0x6e, 0x74, 0x61, 0x6b, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x64,
+	0x72, 0x79, 0x46, 0x6f, 0x6f, 0x64, 0x49, 0x6e, 0x74, 0x61, 0x6b, 0x65, 0x12, 0x2a, 0x0a, 0x11,
+	0x6d, 0x6a, 0x5f, 0x66, 0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65,
+	0x72, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0f, 0x6d, 0x6a, 0x46, 0x6f, 0x72, 0x6d, 0x75,
+	0x6c, 0x61, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x1e, 0x0a, 0x0b, 0x6d, 0x6a, 0x5f, 0x74,
+	0x6d, 0x72, 0x5f, 0x66, 0x65, 0x65, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x6d,
+	0x6a, 0x54, 0x6d, 0x72, 0x46, 0x65, 0x65, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x6d, 0x6a, 0x5f, 0x66,
+	0x6f, 0x6f, 0x64, 0x5f, 0x69, 0x6e, 0x74, 0x61, 0x6b, 0x65, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x05,
+	0x52, 0x0c, 0x6d, 0x6a, 0x46, 0x6f, 0x6f, 0x64, 0x49, 0x6e, 0x74, 0x61, 0x6b, 0x65, 0x12, 0x2c,
+	0x0a, 0x12, 0x6e, 0x6e, 0x64, 0x5f, 0x66, 0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x5f, 0x6e, 0x75,
+	0x6d, 0x62, 0x65, 0x72, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x05, 0x52, 0x10, 0x6e, 0x6e, 0x64, 0x46,
+	0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x20, 0x0a, 0x0c,
+	0x6e, 0x6e, 0x64, 0x5f, 0x74, 0x6d, 0x72, 0x5f, 0x66, 0x65, 0x65, 0x64, 0x18, 0x0f, 0x20, 0x01,
+	0x28, 0x05, 0x52, 0x0a, 0x6e, 0x6e, 0x64, 0x54, 0x6d, 0x72, 0x46, 0x65, 0x65, 0x64, 0x12, 0x26,
+	0x0a, 0x0f, 0x6e, 0x6e, 0x64, 0x5f, 0x66, 0x6f, 0x6f, 0x64, 0x5f, 0x69, 0x6e, 0x74, 0x61, 0x6b,
+	0x65, 0x18, 0x10, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x6e, 0x6e, 0x64, 0x46, 0x6f, 0x6f, 0x64,
+	0x49, 0x6e, 0x74, 0x61, 0x6b, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x63, 0x70, 0x67, 0x5f, 0x66, 0x6f,
+	0x72, 0x6d, 0x75, 0x6c, 0x61, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x11, 0x20, 0x01,
+	0x28, 0x05, 0x52, 0x10, 0x63, 0x70, 0x67, 0x46, 0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x4e, 0x75,
+	0x6d, 0x62, 0x65, 0x72, 0x12, 0x20, 0x0a, 0x0c, 0x63, 0x70, 0x67, 0x5f, 0x74, 0x6d, 0x72, 0x5f,
+	0x66, 0x65, 0x65, 0x64, 0x18, 0x12, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x63, 0x70, 0x67, 0x54,
+	0x6d, 0x72, 0x46, 0x65, 0x65, 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x63, 0x70, 0x67, 0x5f, 0x66, 0x6f,
+	0x6f, 0x64, 0x5f, 0x69, 0x6e, 0x74, 0x61, 0x6b, 0x65, 0x18, 0x13, 0x20, 0x01, 0x28, 0x05, 0x52,
+	0x0d, 0x63, 0x70, 0x67, 0x46, 0x6f, 0x6f, 0x64, 0x49, 0x6e, 0x74, 0x61, 0x6b, 0x65, 0x12, 0x2a,
+	0x0a, 0x11, 0x70, 0x67, 0x5f, 0x66, 0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x5f, 0x6e, 0x75, 0x6d,
+	0x62, 0x65, 0x72, 0x18, 0x14, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0f, 0x70, 0x67, 0x46, 0x6f, 0x72,
+	0x6d, 0x75, 0x6c, 0x61, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x1e, 0x0a, 0x0b, 0x70, 0x67,
+	0x5f, 0x74, 0x6d, 0x72, 0x5f, 0x66, 0x65, 0x65, 0x64, 0x18, 0x15, 0x20, 0x01, 0x28, 0x05, 0x52,
+	0x09, 0x70, 0x67, 0x54, 0x6d, 0x72, 0x46, 0x65, 0x65, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x70, 0x67,
+	0x5f, 0x66, 0x6f, 0x6f, 0x64, 0x5f, 0x69, 0x6e, 0x74, 0x61, 0x6b, 0x65, 0x18, 0x16, 0x20, 0x01,
+	0x28, 0x05, 0x52, 0x0c, 0x70, 0x67, 0x46, 0x6f, 0x6f, 0x64, 0x49, 0x6e, 0x74, 0x61, 0x6b, 0x65,
+	0x12, 0x2a, 0x0a, 0x11, 0x64, 0x6d, 0x5f, 0x66, 0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x5f, 0x6e,
+	0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x17, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0f, 0x64, 0x6d, 0x46,
+	0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x1e, 0x0a, 0x0b,
+	0x64, 0x6d, 0x5f, 0x74, 0x6d, 0x72, 0x5f, 0x66, 0x65, 0x65, 0x64, 0x18, 0x18, 0x20, 0x01, 0x28,
+	0x05, 0x52, 0x09, 0x64, 0x6d, 0x54, 0x6d, 0x72, 0x46, 0x65, 0x65, 0x64, 0x12, 0x24, 0x0a, 0x0e,
+	0x64, 0x6d, 0x5f, 0x66, 0x6f, 0x6f, 0x64, 0x5f, 0x69, 0x6e, 0x74, 0x61, 0x6b, 0x65, 0x18, 0x19,
+	0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x64, 0x6d, 0x46, 0x6f, 0x6f, 0x64, 0x49, 0x6e, 0x74, 0x61,
+	0x6b, 0x65, 0x12, 0x2e, 0x0a, 0x13, 0x63, 0x70, 0x64, 0x6d, 0x5f, 0x66, 0x6f, 0x72, 0x6d, 0x75,
+	0x6c, 0x61, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x05, 0x52,
+	0x11, 0x63, 0x70, 0x64, 0x6d, 0x46, 0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x4e, 0x75, 0x6d, 0x62,
+	0x65, 0x72, 0x12, 0x22, 0x0a, 0x0d, 0x63, 0x70, 0x64, 0x6d, 0x5f, 0x74, 0x6d, 0x72, 0x5f, 0x66,
+	0x65, 0x65, 0x64, 0x18, 0x1b, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x63, 0x70, 0x64, 0x6d, 0x54,
+	0x6d, 0x72, 0x46, 0x65, 0x65, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x63, 0x70, 0x64, 0x6d, 0x5f, 0x66,
+	0x6f, 0x6f, 0x64, 0x5f, 0x69, 0x6e, 0x74, 0x61, 0x6b, 0x65, 0x18, 0x1c, 0x20, 0x01, 0x28, 0x05,
+	0x52, 0x0e, 0x63, 0x70, 0x64, 0x6d, 0x46, 0x6f, 0x6f, 0x64, 0x49, 0x6e, 0x74, 0x61, 0x6b, 0x65,
+	0x12, 0x2c, 0x0a, 0x12, 0x66, 0x61, 0x74, 0x5f, 0x66, 0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x5f,
+	0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x1d, 0x20, 0x01, 0x28, 0x05, 0x52, 0x10, 0x66, 0x61,
+	0x74, 0x46, 0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x20,
+	0x0a, 0x0c, 0x66, 0x61, 0x74, 0x5f, 0x74, 0x6d, 0x72, 0x5f, 0x66, 0x65, 0x65, 0x64, 0x18, 0x1e,
+	0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x66, 0x61, 0x74, 0x54, 0x6d, 0x72, 0x46, 0x65, 0x65, 0x64,
+	0x12, 0x26, 0x0a, 0x0f, 0x66, 0x61, 0x74, 0x5f, 0x66, 0x6f, 0x6f, 0x64, 0x5f, 0x69, 0x6e, 0x74,
+	0x61, 0x6b, 0x65, 0x18, 0x1f, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x66, 0x61, 0x74, 0x46, 0x6f,
+	0x6f, 0x64, 0x49, 0x6e, 0x74, 0x61, 0x6b, 0x65, 0x12, 0x32, 0x0a, 0x15, 0x73, 0x74, 0x61, 0x72,
+	0x63, 0x68, 0x5f, 0x66, 0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65,
+	0x72, 0x18, 0x20, 0x20, 0x01, 0x28, 0x05, 0x52, 0x13, 0x73, 0x74, 0x61, 0x72, 0x63, 0x68, 0x46,
+	0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x26, 0x0a, 0x0f,
+	0x73, 0x74, 0x61, 0x72, 0x63, 0x68, 0x5f, 0x74, 0x6d, 0x72, 0x5f, 0x66, 0x65, 0x65, 0x64, 0x18,
+	0x21, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x73, 0x74, 0x61, 0x72, 0x63, 0x68, 0x54, 0x6d, 0x72,
+	0x46, 0x65, 0x65, 0x64, 0x12, 0x2c, 0x0a, 0x12, 0x73, 0x74, 0x61, 0x72, 0x63, 0x68, 0x5f, 0x66,
+	0x6f, 0x6f, 0x64, 0x5f, 0x69, 0x6e, 0x74, 0x61, 0x6b, 0x65, 0x18, 0x22, 0x20, 0x01, 0x28, 0x05,
+	0x52, 0x10, 0x73, 0x74, 0x61, 0x72, 0x63, 0x68, 0x46, 0x6f, 0x6f, 0x64, 0x49, 0x6e, 0x74, 0x61,
+	0x6b, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x6e, 0x64, 0x66, 0x5f, 0x66, 0x6f, 0x72, 0x6d, 0x75, 0x6c,
+	0x61, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x23, 0x20, 0x01, 0x28, 0x05, 0x52, 0x10,
+	0x6e, 0x64, 0x66, 0x46, 0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72,
+	0x12, 0x20, 0x0a, 0x0c, 0x6e, 0x64, 0x66, 0x5f, 0x74, 0x6d, 0x72, 0x5f, 0x66, 0x65, 0x65, 0x64,
+	0x18, 0x24, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x6e, 0x64, 0x66, 0x54, 0x6d, 0x72, 0x46, 0x65,
+	0x65, 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x64, 0x66, 0x5f, 0x66, 0x6f, 0x6f, 0x64, 0x5f, 0x69,
+	0x6e, 0x74, 0x61, 0x6b, 0x65, 0x18, 0x25, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x6e, 0x64, 0x66,
+	0x46, 0x6f, 0x6f, 0x64, 0x49, 0x6e, 0x74, 0x61, 0x6b, 0x65, 0x12, 0x31, 0x0a, 0x15, 0x63, 0x70,
+	0x5f, 0x6e, 0x64, 0x66, 0x5f, 0x66, 0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x5f, 0x6e, 0x75, 0x6d,
+	0x62, 0x65, 0x72, 0x18, 0x26, 0x20, 0x01, 0x28, 0x05, 0x52, 0x12, 0x63, 0x70, 0x4e, 0x64, 0x66,
+	0x46, 0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x25, 0x0a,
+	0x0f, 0x63, 0x70, 0x5f, 0x6e, 0x64, 0x66, 0x5f, 0x74, 0x6d, 0x72, 0x5f, 0x66, 0x65, 0x65, 0x64,
+	0x18, 0x27, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x63, 0x70, 0x4e, 0x64, 0x66, 0x54, 0x6d, 0x72,
+	0x46, 0x65, 0x65, 0x64, 0x12, 0x2b, 0x0a, 0x12, 0x63, 0x70, 0x5f, 0x6e, 0x64, 0x66, 0x5f, 0x66,
+	0x6f, 0x6f, 0x64, 0x5f, 0x69, 0x6e, 0x74, 0x61, 0x6b, 0x65, 0x18, 0x28, 0x20, 0x01, 0x28, 0x05,
+	0x52, 0x0f, 0x63, 0x70, 0x4e, 0x64, 0x66, 0x46, 0x6f, 0x6f, 0x64, 0x49, 0x6e, 0x74, 0x61, 0x6b,
+	0x65, 0x12, 0x2c, 0x0a, 0x12, 0x61, 0x64, 0x66, 0x5f, 0x66, 0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61,
+	0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x29, 0x20, 0x01, 0x28, 0x05, 0x52, 0x10, 0x61,
+	0x64, 0x66, 0x46, 0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12,
+	0x20, 0x0a, 0x0c, 0x61, 0x64, 0x66, 0x5f, 0x74, 0x6d, 0x72, 0x5f, 0x66, 0x65, 0x65, 0x64, 0x18,
+	0x2a, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x61, 0x64, 0x66, 0x54, 0x6d, 0x72, 0x46, 0x65, 0x65,
+	0x64, 0x12, 0x26, 0x0a, 0x0f, 0x61, 0x64, 0x66, 0x5f, 0x66, 0x6f, 0x6f, 0x64, 0x5f, 0x69, 0x6e,
+	0x74, 0x61, 0x6b, 0x65, 0x18, 0x2b, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x61, 0x64, 0x66, 0x46,
+	0x6f, 0x6f, 0x64, 0x49, 0x6e, 0x74, 0x61, 0x6b, 0x65, 0x12, 0x34, 0x0a, 0x16, 0x63, 0x61, 0x6c,
+	0x63, 0x69, 0x75, 0x6d, 0x5f, 0x66, 0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x5f, 0x6e, 0x75, 0x6d,
+	0x62, 0x65, 0x72, 0x18, 0x2c, 0x20, 0x01, 0x28, 0x05, 0x52, 0x14, 0x63, 0x61, 0x6c, 0x63, 0x69,
+	0x75, 0x6d, 0x46, 0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12,
+	0x28, 0x0a, 0x10, 0x63, 0x61, 0x6c, 0x63, 0x69, 0x75, 0x6d, 0x5f, 0x74, 0x6d, 0x72, 0x5f, 0x66,
+	0x65, 0x65, 0x64, 0x18, 0x2d, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x63, 0x61, 0x6c, 0x63, 0x69,
+	0x75, 0x6d, 0x54, 0x6d, 0x72, 0x46, 0x65, 0x65, 0x64, 0x12, 0x2e, 0x0a, 0x13, 0x63, 0x61, 0x6c,
+	0x63, 0x69, 0x75, 0x6d, 0x5f, 0x66, 0x6f, 0x6f, 0x64, 0x5f, 0x69, 0x6e, 0x74, 0x61, 0x6b, 0x65,
+	0x18, 0x2e, 0x20, 0x01, 0x28, 0x05, 0x52, 0x11, 0x63, 0x61, 0x6c, 0x63, 0x69, 0x75, 0x6d, 0x46,
+	0x6f, 0x6f, 0x64, 0x49, 0x6e, 0x74, 0x61, 0x6b, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x70, 0x64, 0x6d,
+	0x5f, 0x66, 0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18,
+	0x2f, 0x20, 0x01, 0x28, 0x05, 0x52, 0x10, 0x70, 0x64, 0x6d, 0x46, 0x6f, 0x72, 0x6d, 0x75, 0x6c,
+	0x61, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x20, 0x0a, 0x0c, 0x70, 0x64, 0x6d, 0x5f, 0x74,
+	0x6d, 0x72, 0x5f, 0x66, 0x65, 0x65, 0x64, 0x18, 0x30, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x70,
+	0x64, 0x6d, 0x54, 0x6d, 0x72, 0x46, 0x65, 0x65, 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x64, 0x6d,
+	0x5f, 0x66, 0x6f, 0x6f, 0x64, 0x5f, 0x69, 0x6e, 0x74, 0x61, 0x6b, 0x65, 0x18, 0x31, 0x20, 0x01,
+	0x28, 0x05, 0x52, 0x0d, 0x70, 0x64, 0x6d, 0x46, 0x6f, 0x6f, 0x64, 0x49, 0x6e, 0x74, 0x61, 0x6b,
+	0x65, 0x12, 0x35, 0x0a, 0x17, 0x63, 0x66, 0x5f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x5f, 0x66, 0x6f,
+	0x72, 0x6d, 0x75, 0x6c, 0x61, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x32, 0x20, 0x01,
+	0x28, 0x05, 0x52, 0x14, 0x63, 0x66, 0x52, 0x61, 0x74, 0x69, 0x6f, 0x46, 0x6f, 0x72, 0x6d, 0x75,
+	0x6c, 0x61, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x29, 0x0a, 0x11, 0x63, 0x66, 0x5f, 0x72,
+	0x61, 0x74, 0x69, 0x6f, 0x5f, 0x74, 0x6d, 0x72, 0x5f, 0x66, 0x65, 0x65, 0x64, 0x18, 0x33, 0x20,
+	0x01, 0x28, 0x05, 0x52, 0x0e, 0x63, 0x66, 0x52, 0x61, 0x74, 0x69, 0x6f, 0x54, 0x6d, 0x72, 0x46,
+	0x65, 0x65, 0x64, 0x12, 0x2f, 0x0a, 0x14, 0x63, 0x66, 0x5f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x5f,
+	0x66, 0x6f, 0x6f, 0x64, 0x5f, 0x69, 0x6e, 0x74, 0x61, 0x6b, 0x65, 0x18, 0x34, 0x20, 0x01, 0x28,
+	0x05, 0x52, 0x11, 0x63, 0x66, 0x52, 0x61, 0x74, 0x69, 0x6f, 0x46, 0x6f, 0x6f, 0x64, 0x49, 0x6e,
+	0x74, 0x61, 0x6b, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f,
+	0x61, 0x74, 0x18, 0x35, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65,
+	0x64, 0x41, 0x74, 0x12, 0x2a, 0x0a, 0x11, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61,
+	0x74, 0x5f, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x18, 0x36, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f,
+	0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x22,
+	0xd1, 0x01, 0x0a, 0x1c, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x46, 0x6f, 0x72, 0x6d, 0x75, 0x6c,
+	0x61, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
+	0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x01,
+	0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12,
+	0x19, 0x0a, 0x08, 0x65, 0x6e, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
+	0x09, 0x52, 0x07, 0x65, 0x6e, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x65,
+	0x61, 0x72, 0x63, 0x68, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52,
+	0x0a, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e,
+	0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12,
+	0x42, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20,
+	0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x6f, 0x70,
+	0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x50, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69,
+	0x6f, 0x6e, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x52, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74,
+	0x69, 0x6f, 0x6e, 0x22, 0x83, 0x01, 0x0a, 0x1d, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x46, 0x6f,
+	0x72, 0x6d, 0x75, 0x6c, 0x61, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73,
+	0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20,
+	0x01, 0x28, 0x05, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x73, 0x67,
+	0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6d, 0x73, 0x67, 0x12, 0x3c, 0x0a, 0x04, 0x64,
+	0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x62, 0x61, 0x63, 0x6b,
+	0x65, 0x6e, 0x64, 0x2e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x53, 0x65,
+	0x61, 0x72, 0x63, 0x68, 0x46, 0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x45, 0x73, 0x74, 0x69, 0x6d,
+	0x61, 0x74, 0x65, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0xa0, 0x01, 0x0a, 0x15, 0x53, 0x65,
+	0x61, 0x72, 0x63, 0x68, 0x46, 0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x45, 0x73, 0x74, 0x69, 0x6d,
+	0x61, 0x74, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
+	0x05, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c,
+	0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x12, 0x1b, 0x0a,
+	0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05,
+	0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x40, 0x0a, 0x04, 0x6c, 0x69,
+	0x73, 0x74, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65,
+	0x6e, 0x64, 0x2e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x41, 0x64, 0x64,
+	0x46, 0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x45, 0x73, 0x74, 0x69, 0x6d, 0x61, 0x74, 0x65, 0x52,
+	0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x04, 0x6c, 0x69, 0x73, 0x74, 0x22, 0x8b, 0x02, 0x0a,
+	0x1d, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x41, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x41,
+	0x63, 0x63, 0x75, 0x72, 0x61, 0x63, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x67,
+	0x0a, 0x19, 0x63, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f,
+	0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28,
+	0x0e, 0x32, 0x2c, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x6f, 0x70, 0x65, 0x72,
+	0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x43, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x43, 0x61, 0x74, 0x65,
+	0x67, 0x6f, 0x72, 0x79, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x2e, 0x4b, 0x69, 0x6e, 0x64, 0x52,
+	0x16, 0x63, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x43, 0x61, 0x74,
+	0x65, 0x67, 0x6f, 0x72, 0x79, 0x49, 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x66, 0x65, 0x65, 0x64, 0x5f,
+	0x66, 0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05,
+	0x52, 0x0d, 0x66, 0x65, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x49, 0x64, 0x12,
+	0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20,
+	0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x44, 0x61, 0x74, 0x65, 0x12, 0x19,
+	0x0a, 0x08, 0x65, 0x6e, 0x64, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x07, 0x65, 0x6e, 0x64, 0x44, 0x61, 0x74, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x61, 0x73,
+	0x74, 0x75, 0x72, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x05, 0x52, 0x0a,
+	0x70, 0x61, 0x73, 0x74, 0x75, 0x72, 0x65, 0x49, 0x64, 0x73, 0x22, 0x7f, 0x0a, 0x1e, 0x53, 0x65,
+	0x61, 0x72, 0x63, 0x68, 0x41, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x41, 0x63, 0x63, 0x75,
+	0x72, 0x61, 0x63, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04,
+	0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65,
+	0x12, 0x10, 0x0a, 0x03, 0x6d, 0x73, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6d,
+	0x73, 0x67, 0x12, 0x37, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b,
+	0x32, 0x23, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x6f, 0x70, 0x65, 0x72, 0x61,
+	0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x41, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x41, 0x63, 0x63,
+	0x75, 0x72, 0x61, 0x63, 0x79, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x72, 0x0a, 0x10, 0x41,
+	0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x41, 0x63, 0x63, 0x75, 0x72, 0x61, 0x63, 0x79, 0x12,
+	0x2e, 0x0a, 0x05, 0x63, 0x68, 0x61, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18,
+	0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69,
+	0x6f, 0x6e, 0x2e, 0x43, 0x68, 0x61, 0x72, 0x74, 0x52, 0x05, 0x63, 0x68, 0x61, 0x72, 0x74, 0x12,
+	0x2e, 0x0a, 0x05, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18,
+	0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69,
+	0x6f, 0x6e, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x05, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x22,
+	0x9f, 0x03, 0x0a, 0x05, 0x43, 0x68, 0x61, 0x72, 0x74, 0x12, 0x62, 0x0a, 0x1b, 0x6d, 0x69, 0x78,
+	0x65, 0x64, 0x5f, 0x66, 0x6f, 0x64, 0x64, 0x65, 0x72, 0x5f, 0x61, 0x63, 0x63, 0x75, 0x72, 0x61,
+	0x74, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23,
+	0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69,
+	0x6f, 0x6e, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x61,
+	0x74, 0x69, 0x6f, 0x52, 0x18, 0x6d, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x64, 0x64, 0x65, 0x72,
+	0x41, 0x63, 0x63, 0x75, 0x72, 0x61, 0x74, 0x65, 0x52, 0x61, 0x74, 0x69, 0x6f, 0x12, 0x60, 0x0a,
+	0x1a, 0x6d, 0x69, 0x78, 0x65, 0x64, 0x5f, 0x66, 0x6f, 0x64, 0x64, 0x65, 0x72, 0x5f, 0x63, 0x6f,
+	0x72, 0x72, 0x65, 0x63, 0x74, 0x5f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x18, 0x05, 0x20, 0x01, 0x28,
+	0x0b, 0x32, 0x23, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x6f, 0x70, 0x65, 0x72,
+	0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x56, 0x61, 0x6c, 0x75,
+	0x65, 0x52, 0x61, 0x74, 0x69, 0x6f, 0x52, 0x17, 0x6d, 0x69, 0x78, 0x65, 0x64, 0x46, 0x6f, 0x64,
+	0x64, 0x65, 0x72, 0x43, 0x6f, 0x72, 0x72, 0x65, 0x63, 0x74, 0x52, 0x61, 0x74, 0x69, 0x6f, 0x12,
+	0x68, 0x0a, 0x1e, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x6b, 0x6c, 0x65, 0x5f, 0x66, 0x6f, 0x64, 0x64,
+	0x65, 0x72, 0x5f, 0x61, 0x63, 0x63, 0x75, 0x72, 0x61, 0x74, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x69,
+	0x6f, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e,
+	0x64, 0x2e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x43, 0x6f, 0x6d, 0x6d,
+	0x6f, 0x6e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x61, 0x74, 0x69, 0x6f, 0x52, 0x1b, 0x73, 0x70,
+	0x72, 0x69, 0x6e, 0x6b, 0x6c, 0x65, 0x46, 0x6f, 0x64, 0x64, 0x65, 0x72, 0x41, 0x63, 0x63, 0x75,
+	0x72, 0x61, 0x74, 0x65, 0x52, 0x61, 0x74, 0x69, 0x6f, 0x12, 0x66, 0x0a, 0x1d, 0x73, 0x70, 0x72,
+	0x69, 0x6e, 0x6b, 0x6c, 0x65, 0x5f, 0x66, 0x6f, 0x64, 0x64, 0x65, 0x72, 0x5f, 0x63, 0x6f, 0x72,
+	0x72, 0x65, 0x63, 0x74, 0x5f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b,
+	0x32, 0x23, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x6f, 0x70, 0x65, 0x72, 0x61,
+	0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x56, 0x61, 0x6c, 0x75, 0x65,
+	0x52, 0x61, 0x74, 0x69, 0x6f, 0x52, 0x1a, 0x73, 0x70, 0x72, 0x69, 0x6e, 0x6b, 0x6c, 0x65, 0x46,
+	0x6f, 0x64, 0x64, 0x65, 0x72, 0x43, 0x6f, 0x72, 0x72, 0x65, 0x63, 0x74, 0x52, 0x61, 0x74, 0x69,
+	0x6f, 0x22, 0x7b, 0x0a, 0x05, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x41, 0x0a, 0x0a, 0x74, 0x61,
+	0x62, 0x6c, 0x65, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22,
+	0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69,
+	0x6f, 0x6e, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x2e, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x4c, 0x69,
+	0x73, 0x74, 0x52, 0x09, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x1a, 0x2f, 0x0a,
+	0x09, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64,
+	0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61,
+	0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0xe9,
+	0x01, 0x0a, 0x10, 0x43, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x61,
+	0x74, 0x69, 0x6f, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x61, 0x78, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65,
+	0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6d, 0x61, 0x78, 0x56, 0x61, 0x6c, 0x75, 0x65,
+	0x12, 0x21, 0x0a, 0x0c, 0x6d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65,
+	0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6d, 0x69, 0x64, 0x64, 0x6c, 0x65, 0x56, 0x61,
+	0x6c, 0x75, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x6e, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65,
+	0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6d, 0x69, 0x6e, 0x56, 0x61, 0x6c, 0x75, 0x65,
+	0x12, 0x3a, 0x0a, 0x09, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x04, 0x20,
+	0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x6f, 0x70,
+	0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x61, 0x74,
+	0x69, 0x6f, 0x52, 0x08, 0x64, 0x61, 0x74, 0x61, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c,
+	0x70, 0x61, 0x73, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x03,
+	0x28, 0x09, 0x52, 0x0b, 0x70, 0x61, 0x73, 0x74, 0x75, 0x72, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12,
+	0x19, 0x0a, 0x08, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x64, 0x61, 0x79, 0x18, 0x06, 0x20, 0x03, 0x28,
+	0x09, 0x52, 0x07, 0x64, 0x61, 0x74, 0x65, 0x44, 0x61, 0x79, 0x22, 0x2d, 0x0a, 0x0a, 0x56, 0x61,
+	0x6c, 0x75, 0x65, 0x52, 0x61, 0x74, 0x69, 0x6f, 0x12, 0x1f, 0x0a, 0x0b, 0x76, 0x61, 0x6c, 0x75,
+	0x65, 0x5f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x76,
+	0x61, 0x6c, 0x75, 0x65, 0x52, 0x61, 0x74, 0x69, 0x6f, 0x42, 0x0f, 0x5a, 0x0d, 0x2e, 0x3b, 0x6f,
+	0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
+	0x6f, 0x33,
+}
+
+var (
+	file_backend_operation_statistic_proto_rawDescOnce sync.Once
+	file_backend_operation_statistic_proto_rawDescData = file_backend_operation_statistic_proto_rawDesc
+)
+
+func file_backend_operation_statistic_proto_rawDescGZIP() []byte {
+	file_backend_operation_statistic_proto_rawDescOnce.Do(func() {
+		file_backend_operation_statistic_proto_rawDescData = protoimpl.X.CompressGZIP(file_backend_operation_statistic_proto_rawDescData)
+	})
+	return file_backend_operation_statistic_proto_rawDescData
+}
+
+var file_backend_operation_statistic_proto_msgTypes = make([]protoimpl.MessageInfo, 12)
+var file_backend_operation_statistic_proto_goTypes = []interface{}{
+	(*AddFormulaEstimateRequest)(nil),      // 0: backend.operation.AddFormulaEstimateRequest
+	(*SearchFormulaEstimateRequest)(nil),   // 1: backend.operation.SearchFormulaEstimateRequest
+	(*SearchFormulaEstimateResponse)(nil),  // 2: backend.operation.SearchFormulaEstimateResponse
+	(*SearchFormulaEstimate)(nil),          // 3: backend.operation.SearchFormulaEstimate
+	(*SearchAnalysisAccuracyRequest)(nil),  // 4: backend.operation.SearchAnalysisAccuracyRequest
+	(*SearchAnalysisAccuracyResponse)(nil), // 5: backend.operation.SearchAnalysisAccuracyResponse
+	(*AnalysisAccuracy)(nil),               // 6: backend.operation.AnalysisAccuracy
+	(*Chart)(nil),                          // 7: backend.operation.Chart
+	(*Table)(nil),                          // 8: backend.operation.Table
+	(*CommonValueRatio)(nil),               // 9: backend.operation.CommonValueRatio
+	(*ValueRatio)(nil),                     // 10: backend.operation.ValueRatio
+	(*Table_TableList)(nil),                // 11: backend.operation.Table.TableList
+	(*PaginationModel)(nil),                // 12: backend.operation.PaginationModel
+	(CattleCategoryParent_Kind)(0),         // 13: backend.operation.CattleCategoryParent.Kind
+}
+var file_backend_operation_statistic_proto_depIdxs = []int32{
+	12, // 0: backend.operation.SearchFormulaEstimateRequest.pagination:type_name -> backend.operation.PaginationModel
+	3,  // 1: backend.operation.SearchFormulaEstimateResponse.data:type_name -> backend.operation.SearchFormulaEstimate
+	0,  // 2: backend.operation.SearchFormulaEstimate.list:type_name -> backend.operation.AddFormulaEstimateRequest
+	13, // 3: backend.operation.SearchAnalysisAccuracyRequest.cattle_parent_category_id:type_name -> backend.operation.CattleCategoryParent.Kind
+	6,  // 4: backend.operation.SearchAnalysisAccuracyResponse.data:type_name -> backend.operation.AnalysisAccuracy
+	7,  // 5: backend.operation.AnalysisAccuracy.chart:type_name -> backend.operation.Chart
+	8,  // 6: backend.operation.AnalysisAccuracy.table:type_name -> backend.operation.Table
+	9,  // 7: backend.operation.Chart.mixed_fodder_accurate_ratio:type_name -> backend.operation.CommonValueRatio
+	9,  // 8: backend.operation.Chart.mixed_fodder_correct_ratio:type_name -> backend.operation.CommonValueRatio
+	9,  // 9: backend.operation.Chart.sprinkle_fodder_accurate_ratio:type_name -> backend.operation.CommonValueRatio
+	9,  // 10: backend.operation.Chart.sprinkle_fodder_correct_ratio:type_name -> backend.operation.CommonValueRatio
+	11, // 11: backend.operation.Table.table_list:type_name -> backend.operation.Table.TableList
+	10, // 12: backend.operation.CommonValueRatio.data_list:type_name -> backend.operation.ValueRatio
+	13, // [13:13] is the sub-list for method output_type
+	13, // [13:13] is the sub-list for method input_type
+	13, // [13:13] is the sub-list for extension type_name
+	13, // [13:13] is the sub-list for extension extendee
+	0,  // [0:13] is the sub-list for field type_name
+}
+
+func init() { file_backend_operation_statistic_proto_init() }
+func file_backend_operation_statistic_proto_init() {
+	if File_backend_operation_statistic_proto != nil {
+		return
+	}
+	file_backend_operation_pagination_proto_init()
+	file_backend_operation_enum_proto_init()
+	if !protoimpl.UnsafeEnabled {
+		file_backend_operation_statistic_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*AddFormulaEstimateRequest); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_backend_operation_statistic_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*SearchFormulaEstimateRequest); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_backend_operation_statistic_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*SearchFormulaEstimateResponse); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_backend_operation_statistic_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*SearchFormulaEstimate); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_backend_operation_statistic_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*SearchAnalysisAccuracyRequest); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_backend_operation_statistic_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*SearchAnalysisAccuracyResponse); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_backend_operation_statistic_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*AnalysisAccuracy); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_backend_operation_statistic_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*Chart); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_backend_operation_statistic_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*Table); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_backend_operation_statistic_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*CommonValueRatio); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_backend_operation_statistic_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*ValueRatio); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_backend_operation_statistic_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*Table_TableList); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+	}
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_backend_operation_statistic_proto_rawDesc,
+			NumEnums:      0,
+			NumMessages:   12,
+			NumExtensions: 0,
+			NumServices:   0,
+		},
+		GoTypes:           file_backend_operation_statistic_proto_goTypes,
+		DependencyIndexes: file_backend_operation_statistic_proto_depIdxs,
+		MessageInfos:      file_backend_operation_statistic_proto_msgTypes,
+	}.Build()
+	File_backend_operation_statistic_proto = out.File
+	file_backend_operation_statistic_proto_rawDesc = nil
+	file_backend_operation_statistic_proto_goTypes = nil
+	file_backend_operation_statistic_proto_depIdxs = nil
+}

File diff suppressed because it is too large
+ 648 - 79
proto/go/backend/operation/system.pb.go


+ 36 - 0
service/excel/interface.go

@@ -0,0 +1,36 @@
+package excel
+
+import (
+	"fmt"
+	"kpt-tmr-group/config"
+	"kpt-tmr-group/model"
+	"math/rand"
+	"time"
+
+	"github.com/gin-gonic/gin"
+
+	"github.com/xuri/excelize/v2"
+)
+
+type SheetService interface {
+	ExportToPath(params []map[string]string, data []map[string]interface{}, path string) (string, error)
+	ExportToWeb(params []map[string]string, data []map[string]interface{}, c *gin.Context)
+	writeTop(params []map[string]string)
+	writeData(params []map[string]string, data []map[string]interface{})
+	Export(params []map[string]string, data []map[string]interface{})
+}
+
+func createFile(cfg *config.AppConfig) *excelize.File {
+	f := excelize.NewFile()
+	// 创建一个默认工作表
+	index, _ := f.NewSheet(cfg.ExcelSetting.SheetName)
+	// 设置工作簿的默认工作表
+	f.SetActiveSheet(index)
+	return f
+}
+
+func createFileName() string {
+	name := time.Now().Format(model.LayoutTime)
+	rand.Seed(time.Now().UnixNano())
+	return fmt.Sprintf("excle-%v-%v.xlsx", name, rand.Int63n(time.Now().Unix()))
+}

+ 1 - 1
service/sso/cache.go

@@ -26,7 +26,7 @@ func NewCache(cfg *config.AppConfig) *Cache {
 	}
 }
 
-func (c *Cache) Auth(userAuth *operationPb.UserAuth) (string, error) {
+func (c *Cache) Auth(userAuth *operationPb.UserAuthData) (string, error) {
 	return c.get(fmt.Sprintf("sso:auth:%s", tool.Md5String(fmt.Sprintf("%s-%s", userAuth.UserName, userAuth.Password))))
 }
 

+ 1 - 1
service/sso/sso.go

@@ -11,7 +11,7 @@ import (
 var Module = di.Provide(NewSSOClient)
 
 type ClientInterface interface {
-	Auth(userAuth *operationPb.UserAuth) (string, error)
+	Auth(userAuth *operationPb.UserAuthData) (string, error)
 	CacheAuth(token string, res interface{}) error
 	CacheSetAccount(token string, res interface{}) error
 	GetAccount(token string) (interface{}, error)

+ 77 - 0
service/wechat/http.go

@@ -0,0 +1,77 @@
+package wechat
+
+import (
+	"bytes"
+	"encoding/json"
+	"io/ioutil"
+	"kpt-tmr-group/pkg/logger/zaplog"
+	"kpt-tmr-group/pkg/xerr"
+	"net/http"
+	"time"
+
+	"go.uber.org/zap"
+)
+
+type ClientService struct {
+	AppID      string
+	Secret     string
+	authClient *http.Client
+}
+
+func NewClientService(appid, secret string) *ClientService {
+	return &ClientService{
+		AppID:  appid,
+		Secret: secret,
+		authClient: &http.Client{
+			Timeout: time.Duration(5) * time.Second,
+		},
+	}
+}
+
+func (c *ClientService) doRequest(req *http.Request) ([]byte, error) {
+	resp, err := c.authClient.Do(req)
+	if err != nil {
+		zaplog.Error("ClientService", zap.Any("authClient.Do", err))
+		return nil, xerr.WithStack(err)
+	}
+	b, err := ioutil.ReadAll(resp.Body)
+	if err != nil {
+		zaplog.Error("ClientService", zap.Any("ioutil.ReadAll", err))
+		return nil, xerr.WithStack(err)
+	}
+	if resp.StatusCode != http.StatusOK {
+		if len(b) > 0 {
+			return nil, xerr.Customf("err:%v,body:%s", err, string(b))
+		} else {
+			return nil, xerr.Customf("err:%v", err)
+		}
+	}
+	return b, nil
+}
+
+func (c *ClientService) DoGet(url string) ([]byte, error) {
+	req, err := http.NewRequest(http.MethodGet, url, nil)
+	if err != nil {
+		zaplog.Error("ClientService", zap.Any("DoGet", err))
+		return nil, err
+	}
+	req.Header.Add("Accept", "application/json")
+	req.Header.Add("Content-Type", "application/json")
+	return c.doRequest(req)
+}
+
+func (c *ClientService) DoPost(url string, body interface{}) ([]byte, error) {
+	b, err := json.Marshal(body)
+	if err != nil {
+		zaplog.Error("ClientService", zap.Any("DoPost-Marshal", err))
+		return nil, xerr.WithStack(err)
+	}
+	req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(b))
+	if err != nil {
+		zaplog.Error("ClientService", zap.Any("NewRequest", err))
+		return nil, xerr.WithStack(err)
+	}
+	req.Header.Add("Accept", "application/json")
+	req.Header.Add("Content-Type", "application/json")
+	return c.doRequest(req)
+}

+ 19 - 0
service/wechat/interface.go

@@ -0,0 +1,19 @@
+package wechat
+
+import (
+	"kpt-tmr-group/config"
+	"kpt-tmr-group/pkg/di"
+	"net/http"
+)
+
+type Service interface {
+	doRequest(req *http.Request) ([]byte, error)
+	DoGet(url string) ([]byte, error)
+	DoPost(url string, body interface{}) ([]byte, error)
+}
+
+var Module = di.Provide(NewService)
+
+func NewService(cfg *config.AppConfig) *ClientService {
+	return NewClientService(cfg.WechatSetting.Appid, cfg.WechatSetting.Secret)
+}

+ 22 - 0
test/mock/mock.go

@@ -0,0 +1,22 @@
+package mock
+
+import (
+	kptservicemock "kpt-tmr-group/module/backend/mock"
+	"kpt-tmr-group/pkg/di"
+
+	"github.com/golang/mock/gomock"
+)
+
+func GetMock(ctrl *gomock.Controller, f interface{}) {
+	container, err := di.New(di.Provide(func() *gomock.Controller { return ctrl }), deps)
+	if err != nil {
+		panic(err)
+	}
+	if err = container.Invoke(f); err != nil {
+		panic(err)
+	}
+}
+
+var deps = di.Provide(
+	kptservicemock.NewMockKptService,
+)

Some files were not shown because too many files changed in this diff