Преглед изворни кода

Merge branch 'develop' of xuyiping/kpt-tmr-group into master

xuyiping пре 2 година
родитељ
комит
853835fdec
79 измењених фајлова са 9654 додато и 1280 уклоњено
  1. 44 7
      .drone.yml
  2. 30 0
      Dockerfile
  3. 1 1
      Makefile
  4. 6 3
      README.md
  5. 11 2
      backend/operation/enum.proto
  6. 43 2
      backend/operation/feed_formula.proto
  7. 2 0
      backend/operation/mobile.proto
  8. 104 32
      backend/operation/pasture.proto
  9. 243 67
      backend/operation/statistic.proto
  10. 9 0
      backend/operation/system.proto
  11. 28 0
      config/app.develop.yaml
  12. 1 4
      config/app.go
  13. 4 4
      config/app.test.yaml
  14. 6 1
      config/load_config.go
  15. 2 2
      config/load_config_test.go
  16. 3 2
      go.mod
  17. 2 3
      go.sum
  18. 2 0
      http/debug/debug.go
  19. 22 0
      http/debug/debug_test.go
  20. 46 0
      http/debug/x_main_test.go
  21. 112 0
      http/handler/dashboard/dashboard.go
  22. 48 2
      http/handler/feed/feed_formula.go
  23. 0 13
      http/handler/pasture/cattle_forage_category.go
  24. 45 9
      http/handler/pasture/forage_list.go
  25. 69 0
      http/handler/pasture/pasture.go
  26. 2 0
      http/handler/pasture/pasture_list.go
  27. 463 3
      http/handler/statistic/analysis.go
  28. 9 0
      http/handler/system/user.go
  29. 9 1
      http/middleware/cors.go
  30. 2 2
      http/middleware/sso.go
  31. 1 0
      http/route/api_debug_route.go
  32. 28 52
      http/route/ops_api.go
  33. 18 0
      http/route/pasture.go
  34. 1 0
      http/route/root.go
  35. 3 1
      http/route/route.go
  36. 63 0
      http/route/system_api.go
  37. 70 0
      http/util/httptt/http.go
  38. 100 0
      http/util/httptt/query_encoder.go
  39. 1 1
      main.go
  40. 167 0
      model/analysis_accuracy.go
  41. 16 0
      model/cattle_category.go
  42. 7 0
      model/feed_formula.go
  43. 38 0
      model/feed_formula_distribute_log.go
  44. 28 11
      model/forage.go
  45. 24 8
      model/forage_category.go
  46. 244 129
      model/formula_estimate.go
  47. 214 21
      model/group_pasture.go
  48. 70 0
      model/pasture_data.go
  49. 26 0
      model/pasture_data_log.go
  50. 20 2
      model/system_role.go
  51. 13 0
      model/unique_data.go
  52. 573 0
      module/backend/dashboard_service.go
  53. 196 11
      module/backend/feed_service.go
  54. 54 17
      module/backend/interface.go
  55. 1139 0
      module/backend/mock/kptservice.go
  56. 501 85
      module/backend/pasture_service.go
  57. 104 0
      module/backend/pasture_sync_service.go
  58. 800 26
      module/backend/statistic_service.go
  59. 38 8
      module/backend/system_permissions.go
  60. 74 11
      module/backend/system_service.go
  61. 2 2
      module/backend/wx_applet_service.go
  62. 56 0
      module/backend/x_suite_test.go
  63. 18 22
      pkg/logger/zaplog/log.go
  64. 33 0
      pkg/stringutil/random.go
  65. 68 0
      pkg/tool/tool.go
  66. 0 42
      pkg/tool/tool_test.go
  67. 1 0
      pkg/xstore/database/dbtest/mysql.go
  68. 50 0
      pkg/xstore/database/gorm.go
  69. 1 0
      pkg/xstore/database/migrator/migrate.go
  70. 126 16
      proto/go/backend/operation/enum.pb.go
  71. 591 55
      proto/go/backend/operation/feed_formula.pb.go
  72. 36 19
      proto/go/backend/operation/mobile.pb.go
  73. 620 201
      proto/go/backend/operation/pasture.pb.go
  74. 1841 303
      proto/go/backend/operation/statistic.pb.go
  75. 140 34
      proto/go/backend/operation/system.pb.go
  76. 0 41
      scripts/consumer-images.sh
  77. 2 2
      scripts/http-images.sh
  78. 48 0
      service/excel/excel_export.go
  79. 22 0
      test/mock/mock.go

+ 44 - 7
.drone.yml

@@ -1,12 +1,49 @@
 kind: pipeline
 type: docker
-name: default
+name: kptTmrGroup
+
+#clone:
+#  depth: 1
+#  disable: true
 
 steps:
+  #- name: clone
+  #  image: alpine/git
+  #  commands:
+  #    - git clone -b develop http://kpt.kptyun.cn:3000/xuyiping/kpt-tmr-group.git
+  #    - ls -l
+  #    - pwd
   - name: build
-    image: golang:1.17.1
-    environment:
-      GOPROXY: "https://goproxy.cn,direct"
-    commands:
-      - env
-      - CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o kpt-gogs-demo
+    image: plugins/docker:20.14.2
+    volumes:
+      - name: hosts
+        path: /etc/hosts
+      - name: docker-ca
+        path: /etc/docker
+      - name: docker-sock
+        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.3,latest ]
+
+trigger:
+  branch:
+    include:
+    - develop
+  event:
+    include:
+    - push
+
+volumes:
+  - name: docker-ca
+    host:
+      path: /etc/docker
+  - name: docker-sock
+    host:
+      path: /var/run/docker.sock

+ 30 - 0
Dockerfile

@@ -0,0 +1,30 @@
+FROM golang:1.17-alpine as build
+WORKDIR /app/kpt-tmr-group
+
+COPY . .
+
+RUN mkdir -p ./bin
+
+RUN go env -w GO111MODULE=on && \
+    go env -w GOPROXY=https://goproxy.cn,direct && \
+    go env -w CGO_ENABLED=0 && \
+    go env -w GOARCH=amd64 && \
+    go env -w GOOS=linux && \
+    go build -o ./bin/kptTmrGroup -ldflags "-X kpt.kptyun.cn:3000/kpt-event/kpt-tmr-group/pod.appVersion=tmrGroup" main.go
+
+
+FROM alpine:latest
+LABEL name="kpt-tmr-group" \
+description="pt service" \
+owner="yiping.xu"
+
+WORKDIR /app/kpt-tmr-group
+
+
+COPY --from=0 /app/kpt-tmr-group/config/*.yaml /app/kpt-tmr-group/bin/config/
+COPY --from=0  /app/kpt-tmr-group/bin/kptTmrGroup /app/kpt-tmr-group/bin/kptTmrGroup
+
+EXPOSE 8090
+VOLUME /app/kpt-tmr-group/logger
+
+CMD ["/app/kpt-tmr-group/bin/kptTmrGroup","http"]

+ 1 - 1
Makefile

@@ -18,4 +18,4 @@ lint:
 build:
 	rm -rf bin
 	mkdir -p bin
-	GOARCH=amd64 GOOS=linux CGO_ENABLED=0 go build -o bin/kptEvent -ldflags "-X kpt.kptyun.cn:3000/kpt-event/pod.appVersion=${version}" main.go
+	GOARCH=amd64 GOOS=linux CGO_ENABLED=0 go build -o bin/kptTmrGroup -ldflags "-X kpt.kptyun.cn:3000/kpt-event/kpt-tmr-group/pod.appVersion=${version}" main.go

+ 6 - 3
README.md

@@ -16,7 +16,7 @@ kpt-tmr-group - 科湃腾TMR集团版
 需要设置的环境变量:
 
 - export APP_ENVIRONMENT=test
-
+- export GO_WORK_DIR=D:\project\golangNew\kpt-tmr-group
 
 然后你可以尝试编译:
 - make build
@@ -33,6 +33,9 @@ lint:
 
 TODO 列表
 - proto3 int64 jsonpb处理后自动转成string
-  * 现在处理的方式是把int64类型改成int32类型
+  * ~~现在处理的方式是把int64类型改成int32类型~~
 - 用户登出没有用redis做缓存,所以后端没有提供登出接口,有以下弊端
-  * 用户的token没有过期,如被人劫持,会被使用到直到token过期
+  * 用户的token没有过期,如被人劫持,会被使用到直到token过期
+  
+- 饲料列表--小料车下拉框问题
+- ~~权限列表排序的问题~~

+ 11 - 2
backend/operation/enum.proto

@@ -15,12 +15,12 @@ message IsShow {
 
 message CattleCategoryParent {
   enum Kind {
-    INVALID = 0;           // 无效
+    INVALID = 0;           // 所有
     LACTATION_CAW = 1;     // 泌乳牛
     FATTEN_CAW = 2;        // 育肥牛
     RESERVE_CAW  = 3;      // 后备牛
     DRY_CAW = 4;           // 干奶牛
-    PERINATAL_CAW  = 5;    // 泌乳
+    PERINATAL_CAW  = 5;    // 围产
     OTHER_CAW = 6;         // 其他
   }
 }
@@ -72,3 +72,12 @@ message DataSource {
     FROM_PASTURE = 3;     // 来自牧场
   }
 }
+
+message FormulaType {
+  enum Kind {
+    INVALID = 0;                    // 无
+    FEED_FORMULA = 1;              // 饲喂配方
+    PREMIXED_FORMULA = 2;          // 预混配方
+    SUPPLEMENTARY_FORMULA = 3;     // 补料配方
+  }
+}

+ 43 - 2
backend/operation/feed_formula.proto

@@ -23,7 +23,7 @@ message AddFeedFormulaRequest {
   IsShow.Kind is_modify = 14;        // 是否可修改
   int32 created_at = 15;             // 创建时间
   string created_at_format = 16;     // 创建时间格式化
-  string Pasture_name = 17;          // 牧场名称
+  string pasture_name = 17;          // 牧场名称
 }
 
 message SearchFeedFormulaRequest {
@@ -37,6 +37,12 @@ message SearchFeedFormulaRequest {
 }
 
 message SearchFeedFormulaListResponse {
+  int32 code = 1;
+  string msg = 2;
+  SearchFeedFormulaListData data = 3;
+}
+
+message SearchFeedFormulaListData {
   int32 page = 1;
   int32 page_size = 2;
   int32 total = 3;
@@ -47,5 +53,40 @@ message SearchFeedFormulaListResponse {
 message IsShowModifyFeedFormula {
   int32 feed_formula_id = 1;
   IsShow.Kind is_show = 2;
-  int32 edit_type = 3;                  // 1 更新是否启用 2 更新 modify
+  int32 edit_type = 3;      // 1 更新是否启用 2 更新 modify
+}
+
+// 配方编码
+message UniqueID {
+  int32 code = 1;
+  string msg = 2;
+  message UniqueData {
+    string encode_number = 1;
+  }
+  UniqueData data = 3;
+}
+
+// DistributeFeedFormulaRequest 饲料配方下发
+message DistributeFeedFormulaRequest {
+  repeated int32 pasture_ids = 1;          // 牧场ids集合
+  repeated int32 feed_formula_ids = 2;     // 配方ids集合
+}
+
+// 配方使用概况
+message FeedFormulaUsageRequest {
+    int32 feed_formula_id = 1;        // 饲料配方id
+    string start_time = 2;            // 开始时间
+    string end_time = 3;              // 结束时间
+}
+
+// 配方使用概况
+message FeedFormulaUsageResponse {
+  repeated FeedFormulaUsageList list = 1;
+}
+
+// 配方使用概况
+message FeedFormulaUsageList {
+  int32 pasture_id = 1;
+  string pasture_name = 2;
+
 }

+ 2 - 0
backend/operation/mobile.proto

@@ -4,6 +4,7 @@ package backend.operation;
 option go_package = ".;operationPb";
 
 import "backend/operation/pagination.proto";
+import "backend/operation/system.proto";
 
 message SearchMobileRequest {
   string name = 1;       // 名称
@@ -28,6 +29,7 @@ message MobileData {
   string name = 2;
   uint32 created_at = 3;
   string created_at_format = 4;
+  repeated AddMenuRequest children = 15;   // 子分类
 }
 
 

+ 104 - 32
backend/operation/pasture.proto

@@ -16,6 +16,8 @@ message AddPastureRequest {
   IsShow.Kind is_show = 7;    // 是否启用
   int32 created_at = 8;    // 创建时间
   string created_at_format = 9;    // 创建时间格式化
+  string domain = 10;    // 牧场端域名或者ip
+  int32 pasture_id = 11;  // 牧场端id (兼容历史数据)
 }
 
 message SearchPastureRequest {
@@ -70,7 +72,7 @@ message IsShowCattleCategory {
 
 // 畜牧分类查询列表
 message SearchCattleCategoryRequest {
-  string parent_name = 1;
+  int32 parent_id = 1;
   IsShow.Kind is_show = 2;
   string name = 3;
   PaginationModel pagination = 4; // 分页
@@ -85,7 +87,8 @@ message SearchCattleCategoryResponse {
 message SearchCattleCategoryData {
   int32 page = 1;
   int32 total = 2;
-  repeated AddCattleCategoryRequest list = 3;
+  int32 page_size = 3;
+  repeated AddCattleCategoryRequest list = 4;
 }
 
 // 添加饲料分类
@@ -108,54 +111,58 @@ message IsShowForageCategory {
 
 // 饲料分类查询列表
 message SearchForageCategoryRequest {
-  string parent_name = 1;
+  int32 parent_id = 1;
   IsShow.Kind is_show = 2;
   string name = 3;
-  PaginationModel pagination = 4; // 分页
+  string number = 4;
+  PaginationModel pagination = 5; // 分页
 }
 
 message SearchForageCategoryResponse {
   int32 code = 1;
   string msg = 2;
-    SearchForageCategoryData data = 3;
+  SearchForageCategoryData data = 3;
 }
 
 message SearchForageCategoryData {
   int32 page = 1;
   int32 total = 2;
-  repeated AddForageCategoryRequest list = 3;
+  int32 page_size = 3;
+  repeated AddForageCategoryRequest list = 4;
 }
 
 // 饲料列表
 message AddForageRequest {
-  uint32 id = 1;
+  int32 id = 1;
   string name = 2;                // 饲料名称
-  uint32 category_id = 3;          // 饲料分类id
+  int32 category_id = 3;          // 饲料分类id
   string category_name = 4;       // 饲料分类名称
-  uint32 material_type = 5;        // 物料类型
+  int32 material_type = 5;        // 物料类型
   string unique_encode = 7;       // 唯一编码
-  ForageSource.Kind forage_source_id = 8;       // 饲料来源
-  ForagePlanType.Kind plan_type_id = 9;       // 计划类型
-  string  small_material_scale = 10;           // 小料称
-  uint32 allow_error = 11;                     // 允许误差 (单位kg)
-  uint32 package_weight = 12;                  // 包装重量 (单位kg)
-  uint32 price = 13;                          // 单价(单位分)
-  uint32 jump_weight = 14;                    // 跳转重量域(单位kg)
-  JumpDelaType.Kind jump_delay = 15;         // 跳转延迟
-  IsShow.Kind confirm_start = 16;            // 确认开始
-  uint32  relay_locations = 17;               // 继电器位置
-  IsShow.Kind jmp = 18;                     // 无上域
-  string backup1 = 19;                      // 备用字段1
-  string backup2 = 20;                      // 备用字段2
-  string backup3 = 21;                      // 备用字段3
-  uint32 created_at = 22;                    // 创建时间
-  string created_at_format = 23;            // 创建时间格式化
-  IsShow.Kind is_show = 24;                  // 是否启用
+  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
+  int32 category_id = 2;   // 饲料分类id
   uint32 forage_source_id = 3;   // 饲料来源
   IsShow.Kind is_show = 4;    // 是否启用
   uint32 allow_error = 5;      // 允许误差
@@ -164,7 +171,28 @@ message SearchForageListRequest {
   PaginationModel pagination = 8; // 分页
 }
 
+message ForageListSortRequest {
+  repeated ForageListSort list = 1;
+}
+
+message ForageListSort {
+  int64 id = 1;
+  int64 sort = 2;
+}
+
+message SmallMaterialRequest {
+  string api_name = 1;        // 牧场端接口标识名称
+  int32 pasture_id = 2;       // 牧场id
+  string info_name = 3;       //
+}
+
 message SearchForageListResponse {
+ int32 code = 1;
+ string msg = 2;
+ SearchForageList data = 3;
+}
+
+message SearchForageList {
   int32 page = 1;
   int32 page_size = 2;
   int32 total = 3;
@@ -177,13 +205,28 @@ message IsShowForage {
   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 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;                          // 确认开始
+  repeated FormulaOptionEnum formulation_evaluation = 10;         // 配方评估查询方式
+  repeated FormulaOptionEnum use_materials_list = 11;             // 用料分析-列表显示
+  repeated FormulaOptionEnum use_materials_type = 12;             // 用料分析-统计类型
+  repeated FormulaOptionEnum price_materials_type = 13;           // 价格分析-统计类型
+  repeated FormulaOptionEnum jump_type = 14;                      // 准确性-跳转方式
+  repeated FormulaOptionEnum statistics_type = 15;                // 准确性-统计类型
 }
 
 message ForageSourceEnum {
@@ -214,4 +257,33 @@ message ForageParentCategoryEnum {
 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;
+}
+// 牧场端分类数据同步
+message CategorySyncRequest {
+  string key_word = 1;         // 关键字
+  int32 pasture_id = 2;        // 牧场id
+  int32 parent_id = 3;         // 一类id
+  string parent_name = 4;      // 一类名称
+  string name = 5;             // 分类名称
+  string number = 6;           // 分类编号
+  IsShow.Kind is_show = 7;     // 是否展示
+  IsShow.Kind is_delete = 8;   // 是否删除
+  int32 id = 9;                // 牧场端数据id
+}
+
+// 牧场端分类数据删除
+message CategoryDeleteRequest {
+  string key_word = 1;    // 关键字
+  int32 pasture_id = 2;   // 牧场id
+  int32 data_id = 3;
 }

+ 243 - 67
backend/operation/statistic.proto

@@ -5,82 +5,258 @@ option go_package = ".;operationPb";
 
 
 import "backend/operation/pagination.proto";
+import "backend/operation/enum.proto";
+import "backend/operation/pasture.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;
+// SearchFormulaEstimateRequest 配方评估
+message SearchFormulaEstimateRequest {
+  string start_time  = 1;     // 开始时间
+  string end_time    = 2;     // 结束时间
+  int32 search_type = 3;      // 查询方式  0 安照配方 1 按照栏舍
+  string api_name = 4;        // 牧场端接口标识名称
+  int32 pasture_id = 5;       // 牧场id
+  int32 template_id = 6;     // 配方模板id
+  int32 barn_id = 7;          // 栏舍id
+  PaginationModel pagination = 8; // 分页
 }
 
-message SearchFormulaEstimateRequest {
+// SearchInventoryStatisticsRequest 库存管理-库存统计
+message SearchInventoryStatisticsRequest {
   string start_time  = 1;     // 开始时间
   string end_time    = 2;     // 结束时间
-  int32 search_type = 3;      // 查询方式  1 安照配方 2 按照栏舍
-  string name = 4;            // 名称
-  PaginationModel pagination = 5; // 分页
+  string api_name = 3;        // 牧场端接口标识名称
+  string feed_name = 4;       // 饲料名称
+  int32 pasture_id = 5;       // 牧场id
+  PaginationModel pagination = 6; // 分页
+}
+
+// SearchUserMaterialsStatisticsRequest 库存管理-用料分析
+message SearchUserMaterialsStatisticsRequest {
+  string start_time  = 1;     // 开始时间
+  string end_time    = 2;     // 结束时间
+  string api_name = 3;        // 牧场端接口标识名称
+  int32 pasture_id = 4;       // 牧场id
+  int32 error_check = 5;       // 误差是否选中 0 未选中 1 选中
+  int32 type_check = 6;        // 返回实际或理论 1 理论 2 实际
+  string feed_name = 7;        // 名称
+  PaginationModel pagination = 8; // 分页
+}
+
+// SearchPriceStatisticsRequest 库存管理-价格分析
+message SearchPriceStatisticsRequest {
+  string start_time  = 1;     // 开始时间
+  string end_time    = 2;     // 结束时间
+  string api_name = 3;        // 牧场端接口标识名称
+  int32 pasture_id = 4;       // 牧场id
+  string feed_name = 5;        // 名称
+  PaginationModel pagination = 6; // 分页
+}
+
+// SearchFeedStatisticsRequest 饲喂效率-效率统计
+message SearchFeedStatisticsRequest {
+  string start_time  = 1;            // 开始时间
+  string api_name = 2;               // 牧场端接口标识名称
+  repeated int32 pasture_id = 3;              // 牧场id
+  string formula_template = 4;       // 配方模板名称
+  string barn_name = 5;               // 栏舍名称
+  string cattle_category_name = 6;   // 畜牧类别名称
+  int32 cattle_category_id = 7;     // 畜牧类别id
+  int32 class_number = 8;            // 班次
+  PaginationModel pagination = 9;   // 分页
+}
+
+// FeedChartStatisticsRequest 饲喂效率chart图表
+message FeedChartStatisticsRequest {
+  string start_time = 1;     // 开始时间
+  string end_time = 2;       // 结束时间
+  int32 pasture_id = 3;      // 牧场id
+  int32 status = 4;
+  string api_type = 5;       // mr 泌乳牛干物质采食量 sl 牛栏剩料率 hl 混料时间统计 zh 转化率 cbft 成本分析
+}
+
+// CowsAnalysisRequest 饲喂效率-牛群评估
+message CowsAnalysisRequest {
+  string start_time = 1;     // 开始时间
+  string api_name = 2;       // 牧场端接口标识名称
+  int32 pasture_id = 3;      // 牧场id
+  PaginationModel pagination = 4;   // 分页
+}
+
+// AccuracyAggStatisticsRequest 准确性分析-汇总统计
+message AccuracyAggStatisticsRequest {
+  string start_time = 1;     // 开始时间
+  string end_time = 2;       // 结束时间
+  int32 pasture_id = 3;      // 牧场id
+  string fname = 4;         // 查询名称
+  string sort = 5;
+  string status = 6;
+  int32 genre = 7;
+  int32 isdate = 8;
+  int32 hlwc1 = 9;
+  int32 hlwc2 = 10;
+  int32 hlzq1 = 11;
+  int32 hlzq2 = 12;
+  int32 hlzql1 = 13;
+  int32 hlzql2 = 14;
+  int32 slwc1 = 15;
+  int32 slwc2 = 16;
+  int32 slzq1 = 17;
+  int32 slzq2 = 18;
+  int32 slzql1 = 19;
+  int32 slzql2 = 20;
+  string projname = 21;
+  string Times = 22;
+  bool is_error = 23;
+}
+
+// MixFeedStatisticsRequest 准确性分析-混料统计
+message MixFeedStatisticsRequest {
+  string start_time  = 1;            // 开始时间
+  string end_time = 2;               // 结束时间
+  string api_name = 3;               // 牧场端接口标识名称
+  int32 pasture_id = 4;              // 牧场id
+  string equipment_name = 5;         // 设备名称
+  string train_number = 6;           // 车次
+  int32 class_number = 7;            // 班次
+  string formulation_name = 8;       // 配方名称
+  int32 jump_type = 9;               // 跳转方式 0 手动跳转 1 自动跳转
+  int32 hlwc1 = 10;                  // 混料误差值1
+  int32 hlwc2 = 11;                  // 混料误差值2
+  int32 hlzq1 = 12;                  // 混料准确率1
+  int32 hlzq2 = 13;                  // 混料准确率2
+  int32 hlzql1 = 14;                 // 混料正确率1
+  int32 hlzql2 = 15;                 // 混料正确率2
+  bool is_error = 16;                // 只看超出预设值数据
+  string button_type = 17;
+  string is_use = 18;
+  PaginationModel pagination = 19;   // 分页
+}
+
+// SprinkleStatisticsRequest 准确性分析-撒料统计
+message SprinkleStatisticsRequest {
+  string start_time  = 1;            // 开始时间
+  string end_time = 2;               // 结束时间
+  string api_name = 3;               // 牧场端接口标识名称
+  int32 pasture_id = 4;              // 牧场id
+  string equipment_name = 5;         // tmr设备名称
+  string train_number = 6;           // 车次
+  int32 class_number = 7;            // 班次
+  string formulation_name = 8;       // 配方名称
+  string barn_name = 9;              // 栏舍名称
+  int32 jump_type = 10;               // 跳转方式 0 手动跳转 1 自动跳转
+  int32 slwc1 = 11;                  // 撒料误差值1
+  int32 slwc2 = 12;                  // 撒料误差值2
+  int32 slzq1 = 13;                  // 撒料准确率1
+  int32 slzq2 = 14;                  // 撒料准确率2
+  int32 slzql1 = 15;                 // 撒料正确率1
+  int32 slzql2 = 16;                 // 撒料正确率2
+  bool is_error = 17;                // 只看超出预设值数据
+  string button_type = 18;
+  string is_use = 19;
+  PaginationModel pagination = 20;   // 分页
+}
+
+// GetDataByNameRequest
+message GetDataByNameRequest {
+  string start_time  = 1;            // 开始时间
+  string end_time = 2;               // 结束时间
+  string api_name = 3;               // 牧场端接口标识名称
+  int32 pasture_id = 4;              // 牧场id
+}
+
+// ProcessAnalysisRequest 过程分析
+message ProcessAnalysisRequest {
+  string start_time  = 1;            // 开始时间
+  string end_time = 2;               // 结束时间
+  string api_name = 3;               // 牧场端接口标识名称
+  int32 pasture_id = 4;              // 牧场id
+  int32 plan_type = 5;               // 计划类型
+  repeated string tmr_name = 6;      // TMR名称
+  string error_range = 7;            // 误差筛选范围
+  string work_status = 8;            // 工作状态
+  string mix_feed_type = 9;          // 混料类别
+  int32 hlwc1 = 10;                  // 混料误差值1
+  int32 hlwc2 = 11;                  // 混料误差值2
+  int32 hlzq1 = 12;                  // 混料准确率1
+  int32 hlzq2 = 13;                  // 混料准确率2
+  int32 slwc1 = 14;                  // 撒料误差值1
+  int32 slwc2 = 15;                  // 撒料误差值2
+  int32 slzq1 = 16;                  // 撒料准确率1
+  int32 slzq2 = 17;                  // 撒料准确率2
+  PaginationModel pagination = 18;   // 分页
 }
 
-message SearchFormulaEstimateResponse {
+message TrainNumberRequest {
+  string api_name = 1;               // 牧场端接口标识名称
+  int32 pasture_id = 2;              // 牧场id
+  string info_name = 3;
+  PaginationModel pagination = 4;   // 分页
+}
+
+message TrainNumberResponse {
   int32 code = 1;
   string msg = 2;
-  SearchFormulaEstimate data = 3;
+  TrainNumberData data = 3;
+}
+
+message TrainNumberData {
+  repeated FormulaOptionEnum list = 1;
+}
+
+
+// 首页 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;
 }
 
-message SearchFormulaEstimate {
-  int32 page = 1;
-  int32 total = 2;
-  int32 page_size = 3;
-  repeated AddFormulaEstimateRequest list = 4;
+// 首页 dashboard 撒料时间统计分析
+message SprinkleFeedTimeRequest {
+  int32 feed_formula_id = 1;    // 配方id
+  string start_date = 2;       // 开始时间
+  string end_date = 3;         // 结束时间
+  repeated int32 pasture_ids = 4;   //牧场ids
 }

+ 9 - 0
backend/operation/system.proto

@@ -33,6 +33,9 @@ message AddRoleRequest {
   string create_user = 8;          // 创建用户
   uint32 created_at = 9;             // 创建时间
   string created_at_format = 10;     // 创建时间格式化
+
+  repeated string pasture_list = 11;   // 负责的牧场
+  repeated string menu_list = 12; // 权限列表
 }
 
 message SearchRoleRequest {
@@ -202,6 +205,12 @@ 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;
 }
 
 // 移动端

+ 28 - 0
config/app.develop.yaml

@@ -0,0 +1,28 @@
+app_name: kpt-tmr-group
+app_environment: test
+debug: true
+http_server_addr: ':8090'
+http_metrics_addr: ':23332'
+
+store:
+  show_sql: true
+  driver_name: mysql
+  kpt_tmr_group_rw: "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"
+  kpt_tmr_group_migr: "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"
+
+redis_setting:
+  sso_cache:
+    addr: 192.168.1.70:6379
+    db: 12
+    requirepass: ""
+    expiry: 120
+
+jwt_secret: "sUd7j%UfJMt59ywh"
+
+excel_setting:
+  sheet_name: "Sheet1"
+  height: 25.0
+
+wechat_setting:
+  appid: wxd6e17d5709ce9c80
+  secret: 13b93e6c1cda8b6e46adb358a04f9f8f

+ 1 - 4
config/app.go

@@ -7,8 +7,6 @@ import (
 	"sync"
 )
 
-const AppName = "kptEvent"
-
 var (
 	Module   = di.Provide(Options)
 	options  *AppConfig
@@ -71,14 +69,13 @@ func Options() *AppConfig {
 
 func init() {
 	appEnv = strings.ToLower(os.Getenv("APP_ENVIRONMENT"))
-
 	cfg := &AppConfig{}
 	var err error
 	initOnce.Do(func() {
 		switch appEnv {
 		default:
 			err = Initialize("app.test.yaml", cfg)
-		case "development":
+		case "develop":
 			err = Initialize("app.develop.yaml", cfg)
 		case "production":
 			err = Initialize("app.production.yaml", cfg)

+ 4 - 4
config/app.test.yaml

@@ -1,14 +1,14 @@
-app_name: kpt-event
+app_name: kpt-tmr-group
 app_environment: test
 debug: true
-http_server_addr: ':8000'
+http_server_addr: ':8090'
 http_metrics_addr: ':23332'
 
 store:
   show_sql: true
   driver_name: mysql
-  kpt_tmr_group_rw: "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"
-  kpt_tmr_group_migr: "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"
+  kpt_tmr_group_rw: "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"
+  kpt_tmr_group_migr: "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"
 
 redis_setting:
   sso_cache:

+ 6 - 1
config/load_config.go

@@ -2,13 +2,18 @@ package config
 
 import (
 	"fmt"
+	"os"
 
 	"github.com/mitchellh/mapstructure"
 	"github.com/spf13/viper"
 )
 
 func Initialize(path string, cfgStruct interface{}) error {
-	dir := fmt.Sprintf("./config/%s", path)
+	workDir := os.Getenv("GO_WORK_DIR_TMR_GROUP")
+	if workDir == "" {
+		workDir = "."
+	}
+	dir := fmt.Sprintf("%s/config/%s", workDir, path)
 	viper.SetConfigType("yaml")
 	viper.SetConfigFile(dir)
 	if err := viper.ReadInConfig(); err != nil {

+ 2 - 2
config/load_config_test.go

@@ -13,9 +13,9 @@ func TestInitialize(t *testing.T) {
 		wantErr bool
 	}{
 		{
-			name: AppName,
+			name: "AppName",
 			args: args{
-				path:      "",
+				path:      "app.test.yaml",
 				cfgStruct: nil,
 			},
 			wantErr: false,

+ 3 - 2
go.mod

@@ -12,6 +12,7 @@ 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
@@ -21,7 +22,6 @@ require (
 	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
@@ -30,9 +30,11 @@ require (
 	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/sync v0.1.0
 	google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef
 	google.golang.org/grpc v1.52.0
 	google.golang.org/protobuf v1.30.0
+	gopkg.in/natefinch/lumberjack.v2 v2.2.1
 	gorm.io/driver/mysql v1.5.0
 	gorm.io/gorm v1.25.0
 )
@@ -91,6 +93,5 @@ require (
 	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
 )

+ 2 - 3
go.sum

@@ -37,7 +37,6 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9
 cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 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=
@@ -129,6 +128,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=
@@ -248,8 +248,6 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
 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=
@@ -468,6 +466,7 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
 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 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
 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=

+ 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
+}

+ 112 - 0
http/handler/dashboard/dashboard.go

@@ -0,0 +1,112 @@
+package dashboard
+
+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"
+)
+
+// AnalysisAccuracy 首页仪表盘-准确性分析
+func AnalysisAccuracy(c *gin.Context) {
+	var req operationPb.SearchAnalysisAccuracyRequest
+	if err := ginutil.BindProto(c, &req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := valid.ValidateStruct(&req,
+		valid.Field(&req.PastureIds, valid.Required),
+		valid.Field(&req.StartDate, valid.Required),
+		valid.Field(&req.EndDate, 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)
+}
+
+// TopPasture 牧场排名
+func TopPasture(c *gin.Context) {
+	var req operationPb.SearchAnalysisAccuracyRequest
+	if err := ginutil.BindProto(c, &req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := valid.ValidateStruct(&req,
+		valid.Field(&req.PastureIds, valid.Required),
+		valid.Field(&req.StartDate, valid.Required),
+		valid.Field(&req.EndDate, valid.Required),
+	); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	res, err := middleware.BackendOperation(c).OpsService.TopPasture(c, &req)
+	if err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+	c.JSON(http.StatusOK, res)
+}
+
+// ExecutionTime 执行时间
+func ExecutionTime(c *gin.Context) {
+	var req operationPb.SearchAnalysisAccuracyRequest
+	if err := ginutil.BindProto(c, &req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := valid.ValidateStruct(&req,
+		valid.Field(&req.PastureIds, valid.Required),
+		valid.Field(&req.StartDate, valid.Required),
+		valid.Field(&req.EndDate, valid.Required),
+	); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	res, err := middleware.BackendOperation(c).OpsService.ExecutionTime(c, &req)
+	if err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+	c.JSON(http.StatusOK, res)
+}
+
+// SprinkleFeedTime 撒料时间
+func SprinkleFeedTime(c *gin.Context) {
+	var req operationPb.SprinkleFeedTimeRequest
+	if err := ginutil.BindProto(c, &req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := valid.ValidateStruct(&req,
+		valid.Field(&req.PastureIds, valid.Required),
+		valid.Field(&req.StartDate, valid.Required),
+		valid.Field(&req.EndDate, valid.Required),
+	); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	res, err := middleware.BackendOperation(c).OpsService.SprinkleFeedTime(c, &req)
+	if err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+	c.JSON(http.StatusOK, res)
+}

+ 48 - 2
http/handler/feed/feed_formula.go

@@ -34,7 +34,6 @@ func AddFeedFormula(c *gin.Context) {
 		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 {
@@ -71,7 +70,6 @@ func EditFeedFormula(c *gin.Context) {
 		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 {
@@ -248,3 +246,51 @@ func ExcelTemplateFeedFormula(c *gin.Context) {
 		Data: &operationPb.Success{Success: true},
 	})
 }
+
+func EncodeNumber(c *gin.Context) {
+	ginutil.JSONResp(c, &operationPb.UniqueID{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.UniqueID_UniqueData{EncodeNumber: middleware.BackendOperation(c).OpsService.EncodeNumber(c)},
+	})
+}
+
+// DistributeFeedFormula 饲料配方下发
+func DistributeFeedFormula(c *gin.Context) {
+	req := &operationPb.DistributeFeedFormulaRequest{}
+	if err := ginutil.BindProto(c, req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := middleware.BackendOperation(c).OpsService.DistributeFeedFormula(c, req); err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+
+	ginutil.JSONResp(c, &operationPb.CommonOK{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.Success{Success: true},
+	})
+}
+
+// Usage 配方使用情况
+func Usage(c *gin.Context) {
+	req := &operationPb.FeedFormulaUsageRequest{}
+	if err := ginutil.BindProto(c, req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := middleware.BackendOperation(c).OpsService.FeedFormulaUsage(c, req); err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+
+	ginutil.JSONResp(c, &operationPb.CommonOK{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.Success{Success: true},
+	})
+}

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

@@ -12,13 +12,6 @@ import (
 	"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 {
@@ -145,12 +138,6 @@ func SearchCattleCategory(c *gin.Context) {
 	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 {

+ 45 - 9
http/handler/pasture/forage_list.go

@@ -18,7 +18,7 @@ import (
 
 func AddForage(c *gin.Context) {
 	var req operationPb.AddForageRequest
-	if err := c.BindJSON(&req); err != nil {
+	if err := ginutil.BindProto(c, &req); err != nil {
 		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
 		return
 	}
@@ -28,8 +28,11 @@ func AddForage(c *gin.Context) {
 		valid.Field(&req.CategoryId, valid.Required),
 		valid.Field(&req.UniqueEncode, valid.Required),
 		valid.Field(&req.ForageSourceId, valid.Required),
-		valid.Field(&req.PlanTypeId, valid.Required),
-		valid.Field(&req.JumpWeight, valid.Required, valid.Min(0), valid.Max(50)),
+		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.MaterialType, valid.Required),
+		//valid.Field(&req.JumpWeight, valid.Required, valid.Min(0), valid.Max(50)),
 	); err != nil {
 		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
 		return
@@ -48,7 +51,7 @@ func AddForage(c *gin.Context) {
 
 func EditForage(c *gin.Context) {
 	var req operationPb.AddForageRequest
-	if err := c.BindJSON(&req); err != nil {
+	if err := ginutil.BindProto(c, &req); err != nil {
 		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
 		return
 	}
@@ -57,10 +60,10 @@ func EditForage(c *gin.Context) {
 		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),
-		valid.Field(&req.PlanTypeId, valid.Required),
-		valid.Field(&req.JumpWeight, valid.Required, valid.Min(0), valid.Max(50)),
+		valid.Field(&req.MaterialType, valid.Required),
 	); err != nil {
 		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
 		return
@@ -98,6 +101,40 @@ func SearchForageList(c *gin.Context) {
 	ginutil.JSONResp(c, res)
 }
 
+func ForageListSort(c *gin.Context) {
+	req := &operationPb.ForageListSortRequest{}
+	if err := ginutil.BindProto(c, req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := middleware.BackendOperation(c).OpsService.ForageListSort(c, req); err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+
+	ginutil.JSONResp(c, &operationPb.CommonOK{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.Success{Success: true},
+	})
+}
+
+// SmallMaterial 小料字段是否展示
+func SmallMaterial(c *gin.Context) {
+	var req operationPb.SmallMaterialRequest
+	if err := ginutil.BindProto(c, &req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+	res, err := middleware.BackendOperation(c).OpsService.SmallMaterial(c, &req)
+	if err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+	c.JSON(http.StatusOK, res)
+}
+
 // SearchForageEnumList 饲料列表公共枚举
 func SearchForageEnumList(c *gin.Context) {
 	res := middleware.BackendOperation(c).OpsService.ForageEnumList(c)
@@ -129,7 +166,7 @@ func DeleteForageList(c *gin.Context) {
 
 func IsShowForage(c *gin.Context) {
 	var req operationPb.IsShowForage
-	if err := c.BindJSON(&req); err != nil {
+	if err := ginutil.BindProto(c, &req); err != nil {
 		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
 		return
 	}
@@ -193,9 +230,8 @@ func ExcelImportForage(c *gin.Context) {
 }
 
 func ExcelExportForage(c *gin.Context) {
-
 	req := &operationPb.SearchForageListRequest{}
-	if err := c.BindJSON(req); err != nil {
+	if err := ginutil.BindProto(c, req); err != nil {
 		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
 		return
 	}

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

@@ -0,0 +1,69 @@
+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"
+
+	"github.com/gin-gonic/gin"
+)
+
+// CategorySync 牧场分类数据同步
+func CategorySync(c *gin.Context) {
+	var req operationPb.CategorySyncRequest
+	if err := ginutil.BindProto(c, &req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := valid.ValidateStruct(&req,
+		valid.Field(&req.KeyWord, valid.Required),
+		valid.Field(&req.PastureId, valid.Required),
+		valid.Field(&req.ParentId, valid.Required),
+	); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := middleware.Dependency(c).StoreEventHub.OpsService.CategorySyncData(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 CategoryDelete(c *gin.Context) {
+	var req operationPb.CategoryDeleteRequest
+	if err := ginutil.BindProto(c, &req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := valid.ValidateStruct(&req,
+		valid.Field(&req.KeyWord, valid.Required),
+		valid.Field(&req.PastureId, valid.Required),
+		valid.Field(&req.DataId, valid.Required),
+	); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := middleware.Dependency(c).StoreEventHub.OpsService.CategoryDeleteData(c, &req); err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+
+	ginutil.JSONResp(c, &operationPb.CommonOK{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.Success{Success: true},
+	})
+}

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

@@ -25,6 +25,7 @@ func AddGroupPasture(c *gin.Context) {
 		valid.Field(&req.ManagerPhone, valid.Required),
 		valid.Field(&req.Account, valid.Required),
 		valid.Field(&req.Address, valid.Required),
+		valid.Field(&req.Domain, valid.Required),
 	); err != nil {
 		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
 		return
@@ -55,6 +56,7 @@ func EditGroupPasture(c *gin.Context) {
 		valid.Field(&req.ManagerPhone, valid.Required),
 		valid.Field(&req.Account, valid.Required),
 		valid.Field(&req.Address, valid.Required),
+		valid.Field(&req.Domain, valid.Required),
 	); err != nil {
 		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
 		return

+ 463 - 3
http/handler/statistic/analysis.go

@@ -1,19 +1,479 @@
 package statistic
 
 import (
+	"fmt"
 	"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"
+	"time"
 
 	"github.com/gin-gonic/gin"
 )
 
 // SearchFormulaEstimateList 配方评估
 func SearchFormulaEstimateList(c *gin.Context) {
-	req := &operationPb.SearchFormulaEstimateRequest{}
-	if err := c.BindJSON(req); err != nil {
+	var req operationPb.SearchFormulaEstimateRequest
+	if err := ginutil.BindProto(c, &req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := valid.ValidateStruct(&req,
+		valid.Field(&req.ApiName, valid.Required),
+		valid.Field(&req.PastureId, valid.Required),
+		valid.Field(&req.StartTime, valid.Required),
+		valid.Field(&req.EndTime, valid.Required),
+	); 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
+	}
+	c.JSON(http.StatusOK, res)
+}
+
+// SearchInventoryStatisticsExcelExport 库存管理-库存统计-报表导出
+func SearchInventoryStatisticsExcelExport(c *gin.Context) {
+	var req operationPb.SearchInventoryStatisticsRequest
+	if err := ginutil.BindProto(c, &req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := valid.ValidateStruct(&req,
+		valid.Field(&req.ApiName, valid.Required),
+		valid.Field(&req.PastureId, valid.Required),
+		valid.Field(&req.StartTime, valid.Required),
+		valid.Field(&req.EndTime, valid.Required),
+	); 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.InventoryStatisticsExcelExport(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},
+	})
+}
+
+// SearchInventoryStatistics 库存管理-库存统计
+func SearchInventoryStatistics(c *gin.Context) {
+	var req operationPb.SearchInventoryStatisticsRequest
+	if err := ginutil.BindProto(c, &req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := valid.ValidateStruct(&req,
+		valid.Field(&req.ApiName, valid.Required),
+		valid.Field(&req.PastureId, valid.Required),
+		valid.Field(&req.StartTime, valid.Required),
+		valid.Field(&req.EndTime, valid.Required),
+	); 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.SearchInventoryStatistics(c, &req)
+	if err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+	c.JSON(http.StatusOK, res)
+}
+
+// SearchUserMaterialsStatistics 库存管理-用料分析
+func SearchUserMaterialsStatistics(c *gin.Context) {
+	var req operationPb.SearchUserMaterialsStatisticsRequest
+	if err := ginutil.BindProto(c, &req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := valid.ValidateStruct(&req,
+		valid.Field(&req.ApiName, valid.Required),
+		valid.Field(&req.PastureId, valid.Required),
+		valid.Field(&req.StartTime, valid.Required),
+		valid.Field(&req.EndTime, valid.Required),
+	); 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.SearchUserMaterialsStatistics(c, &req)
+	if err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+	c.JSON(http.StatusOK, res)
+}
+
+// SearchUserMaterialsStatisticsExcelExport 库存管理-用料分析-报表导出
+func SearchUserMaterialsStatisticsExcelExport(c *gin.Context) {
+	var req operationPb.SearchUserMaterialsStatisticsRequest
+	if err := ginutil.BindProto(c, &req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := valid.ValidateStruct(&req,
+		valid.Field(&req.ApiName, valid.Required),
+		valid.Field(&req.PastureId, valid.Required),
+		valid.Field(&req.StartTime, valid.Required),
+		valid.Field(&req.EndTime, valid.Required),
+	); 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.UserMaterialsStatisticsExcelExport(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},
+	})
+}
+
+// SearchPriceStatistics 库存管理-价格分析
+func SearchPriceStatistics(c *gin.Context) {
+	var req operationPb.SearchPriceStatisticsRequest
+	if err := ginutil.BindProto(c, &req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := valid.ValidateStruct(&req,
+		valid.Field(&req.ApiName, valid.Required),
+		valid.Field(&req.PastureId, valid.Required),
+		valid.Field(&req.StartTime, valid.Required),
+		valid.Field(&req.EndTime, valid.Required),
+	); 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.SearchPriceStatistics(c, &req)
+	if err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+	c.JSON(http.StatusOK, res)
+}
+
+// SearchFeedStatistics 饲喂效率-效率统计
+func SearchFeedStatistics(c *gin.Context) {
+	var req operationPb.SearchFeedStatisticsRequest
+	if err := ginutil.BindProto(c, &req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := valid.ValidateStruct(&req,
+		valid.Field(&req.ApiName, valid.Required),
+		valid.Field(&req.PastureId, valid.Required),
+		valid.Field(&req.StartTime, valid.Required),
+	); 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.SearchFeedStatistics(c, &req)
+	if err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+	c.JSON(http.StatusOK, res)
+}
+
+// SearchCowsAnalysis 饲喂效率-牛群评估
+func SearchCowsAnalysis(c *gin.Context) {
+	var req operationPb.CowsAnalysisRequest
+	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.Field(&req.StartTime, valid.Required),
+	); 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.CowsAnalysis(c, &req)
+	if err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+	c.JSON(http.StatusOK, res)
+}
+
+// SearchFeedChartStatistics 饲喂效率-chart图表分析
+func SearchFeedChartStatistics(c *gin.Context) {
+	var req operationPb.FeedChartStatisticsRequest
+	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.Field(&req.StartTime, valid.Required),
+		valid.Field(&req.EndTime, valid.Required),
+		valid.Field(&req.ApiType, valid.Required),
+	); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	res, err := middleware.BackendOperation(c).OpsService.FeedChartStatistics(c, &req)
+	if err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+	c.JSON(http.StatusOK, res)
+}
+
+// SearchAccuracyAggStatistics 准确性分析-汇总统计
+func SearchAccuracyAggStatistics(c *gin.Context) {
+	var req operationPb.AccuracyAggStatisticsRequest
+	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.Field(&req.StartTime, valid.Required),
+		valid.Field(&req.EndTime, valid.Required),
+	); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	res, err := middleware.BackendOperation(c).OpsService.SearchAccuracyAggStatistics(c, &req)
+	if err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+	c.JSON(http.StatusOK, res)
+}
+
+// SearchMixFeedStatistics 准确性分析-混料统计
+func SearchMixFeedStatistics(c *gin.Context) {
+	var req operationPb.MixFeedStatisticsRequest
+	if err := ginutil.BindProto(c, &req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := valid.ValidateStruct(&req,
+		valid.Field(&req.ApiName, valid.Required),
+		valid.Field(&req.PastureId, valid.Required),
+		valid.Field(&req.StartTime, valid.Required),
+		valid.Field(&req.EndTime, valid.Required),
+	); 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.SearchMixFeedStatistics(c, &req)
+	if err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+	c.JSON(http.StatusOK, res)
+}
+
+// SearchSprinkleStatistics 准确性分析-撒料统计
+func SearchSprinkleStatistics(c *gin.Context) {
+	var req operationPb.SprinkleStatisticsRequest
+	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.Field(&req.ApiName, valid.Required),
+		valid.Field(&req.StartTime, valid.Required),
+		valid.Field(&req.EndTime, valid.Required),
+	); 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.SearchSprinkleStatistics(c, &req)
+	if err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+	c.JSON(http.StatusOK, res)
+}
+
+// GetDataByName 共同接口
+func GetDataByName(c *gin.Context) {
+	var req operationPb.GetDataByNameRequest
+	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.Field(&req.ApiName, valid.Required),
+		valid.Field(&req.StartTime, valid.Required),
+		valid.Field(&req.EndTime, valid.Required),
+	); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	res, err := middleware.BackendOperation(c).OpsService.GetDataByName(c, &req)
+	if err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+	c.JSON(http.StatusOK, res)
+}
+
+// SearchProcessAnalysis 过程分析
+func SearchProcessAnalysis(c *gin.Context) {
+	var req operationPb.ProcessAnalysisRequest
+	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.Field(&req.ApiName, valid.Required),
+		valid.Field(&req.StartTime, valid.Required),
+		valid.Field(&req.EndTime, valid.Required),
+	); 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.SearchProcessAnalysis(c, &req)
+	if err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+	c.JSON(http.StatusOK, res)
+}
+
+// TrainNumber 班次
+func TrainNumber(c *gin.Context) {
+	var req operationPb.TrainNumberRequest
+	if err := ginutil.BindProto(c, &req); err != nil {
+		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
+		return
+	}
+
+	if err := valid.ValidateStruct(&req,
+		valid.Field(&req.ApiName, valid.Required, valid.Length(1, 100)),
+		valid.Field(&req.PastureId, valid.Required),
+		valid.Field(&req.InfoName, valid.Required, valid.Length(1, 30)),
+	); err != nil {
 		apierr.AbortBadRequest(c, http.StatusBadRequest, err)
 		return
 	}
@@ -24,7 +484,7 @@ func SearchFormulaEstimateList(c *gin.Context) {
 		PageOffset: int32(c.GetInt(middleware.PageOffset)),
 	}
 
-	res, err := middleware.BackendOperation(c).OpsService.SearchFormulaEstimateList(c, req)
+	res, err := middleware.BackendOperation(c).OpsService.GetTrainNumber(c, &req)
 	if err != nil {
 		apierr.ClassifiedAbort(c, err)
 		return

+ 9 - 0
http/handler/system/user.go

@@ -262,3 +262,12 @@ func ResetPasswordSystemUser(c *gin.Context) {
 		Data: &operationPb.Success{Success: true},
 	})
 }
+
+// LogoutSystemUser 用户登出,
+func LogoutSystemUser(c *gin.Context) {
+	ginutil.JSONResp(c, &operationPb.CommonOK{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.Success{Success: true},
+	})
+}

+ 9 - 1
http/middleware/cors.go

@@ -1,6 +1,7 @@
 package middleware
 
 import (
+	"io/ioutil"
 	"kpt-tmr-group/pkg/logger/zaplog"
 	"net/http"
 
@@ -36,11 +37,18 @@ func CORS(configs ...cors.Config) gin.HandlerFunc {
 		//允许类型校验
 		if method == "OPTIONS" {
 			c.JSON(http.StatusOK, "ok!")
+			return
 		}
 
 		defer func() {
 			if err := recover(); err != nil {
-				zaplog.Error("cors", zap.Any("recover", err))
+				body, _ := ioutil.ReadAll(c.Request.Body)
+				zaplog.Error("cors",
+					zap.Any("recover", err),
+					zap.Any("url", c.Request.URL),
+					zap.Any("method", method),
+					zap.Any("request", string(body)),
+				)
 			}
 		}()
 

+ 2 - 2
http/middleware/sso.go

@@ -41,13 +41,13 @@ func RequireAdmin() gin.HandlerFunc {
 		token := GetToken(c)
 		if token == "" {
 			unauthorized(c)
-			c.Abort()
+			return
 		}
 
 		claims, err := jwt.ParseToken(token)
 		if err != nil || claims == nil || claims.Username == "" {
 			unauthorized(c)
-			c.Abort()
+			return
 		}
 
 		c.Set(UserName, claims.Username)

+ 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)

+ 28 - 52
http/route/app_api.go → http/route/ops_api.go

@@ -1,59 +1,19 @@
 package route
 
 import (
-	"kpt-tmr-group/http/handler"
+	"kpt-tmr-group/http/handler/dashboard"
 	"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"
 
 	"github.com/gin-gonic/gin"
 )
 
-func AppAPI(opts ...func(engine *gin.Engine)) func(s *gin.Engine) {
+func OpsAPI(opts ...func(engine *gin.Engine)) func(s *gin.Engine) {
 	return func(s *gin.Engine) {
 		for _, opt := range opts {
 			opt(s)
 		}
-		// Not Found
-		s.NoRoute(handler.Handle404)
-		// Health Check
-		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("/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)
-
-		// 系统菜单权限
-		systemRoute.POST("/menu/add", system.AddSystemMenu)
-		systemRoute.POST("/menu/edit", system.EditSystemMenu)
-		systemRoute.POST("/menu/is_show", system.IsShowSystemMenu)
-		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/")
@@ -65,7 +25,6 @@ func AppAPI(opts ...func(engine *gin.Engine)) func(s *gin.Engine) {
 		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)
@@ -73,7 +32,6 @@ func AppAPI(opts ...func(engine *gin.Engine)) func(s *gin.Engine) {
 		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)
@@ -89,26 +47,44 @@ func AppAPI(opts ...func(engine *gin.Engine)) func(s *gin.Engine) {
 		opsRoute.POST("forage/excel_import", pasture.ExcelImportForage)
 		opsRoute.POST("forage/excel_export", pasture.ExcelExportForage)
 		opsRoute.POST("forage/excel_template", pasture.ExcelTemplateForage)
+		opsRoute.POST("/forage/small_material", pasture.SmallMaterial)
+		opsRoute.POST("/forage/sort", pasture.ForageListSort)
 		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.POST("/feed_formula/delete/:feed_formula_id", feed.DeleteFeedFormula)
+		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)
+		opsRoute.GET("/feed_formula/encode_number", feed.EncodeNumber)
+		opsRoute.POST("/feed_formula/distribute", feed.DistributeFeedFormula)
+		opsRoute.POST("/feed_formula/usage", feed.Usage)
 
 		//统计分析 statistic analysis
 		opsRoute.POST("/feed_estimate/list", statistic.SearchFormulaEstimateList)
-	}
-}
+		opsRoute.POST("/inventory/statistics", statistic.SearchInventoryStatistics)
+		opsRoute.POST("/inventory/statistics/excel_export", statistic.SearchInventoryStatisticsExcelExport)
+		opsRoute.POST("/inventory/user_materials_statistics", statistic.SearchUserMaterialsStatistics)
+		opsRoute.POST("/inventory/user_materials_statistics/excel_export", statistic.SearchUserMaterialsStatisticsExcelExport)
+		opsRoute.POST("/inventory/price_statistics", statistic.SearchPriceStatistics)
+		opsRoute.POST("/feed_efficiency/statistics", statistic.SearchFeedStatistics)
+		opsRoute.POST("/feed_efficiency/chart_statistics", statistic.SearchFeedChartStatistics)
+		opsRoute.POST("/feed_efficiency/cows_analysis", statistic.SearchCowsAnalysis)
+		opsRoute.POST("/accuracy/agg_statistics", statistic.SearchAccuracyAggStatistics)
+		opsRoute.POST("/accuracy/mixed_statistics", statistic.SearchMixFeedStatistics)
+		opsRoute.POST("/accuracy/sprinkle_statistics", statistic.SearchSprinkleStatistics)
+		opsRoute.POST("/accuracy/data_by_name", statistic.GetDataByName)
+		opsRoute.POST("/process/analysis", statistic.SearchProcessAnalysis)
+		opsRoute.POST("/statistics/train_number", statistic.TrainNumber)
 
-func authRouteGroup(s *gin.Engine, relativePath string) *gin.RouterGroup {
-	group := s.Group(relativePath)
-	// 中间件鉴权
-	group.Use(middleware.RequireAdmin(), middleware.CORS())
-	return group
+		// 首页仪表盘
+		opsRoute.POST("/dashboard/accuracy", dashboard.AnalysisAccuracy)
+		opsRoute.POST("/dashboard/top_pasture", dashboard.TopPasture)
+		opsRoute.POST("/dashboard/exec_time", dashboard.ExecutionTime)
+		opsRoute.POST("/dashboard/sprinkle_time", dashboard.SprinkleFeedTime)
+	}
 }

+ 18 - 0
http/route/pasture.go

@@ -0,0 +1,18 @@
+package route
+
+import (
+	"kpt-tmr-group/http/handler/pasture"
+
+	"github.com/gin-gonic/gin"
+)
+
+func PastureAPI(opts ...func(engine *gin.Engine)) func(s *gin.Engine) {
+	return func(s *gin.Engine) {
+		for _, opt := range opts {
+			opt(s)
+		}
+
+		s.POST("/group/class/sync", pasture.CategorySync)
+		s.POST("/group/class/delete", pasture.CategoryDelete)
+	}
+}

+ 1 - 0
http/route/root.go

@@ -20,6 +20,7 @@ func Root(opts ...func(engine *gin.Engine)) func(s *gin.Engine) {
 			middleware.Pagination(),
 			requestid.New(),
 			gzip.Gzip(gzip.DefaultCompression),
+			gin.Recovery(),
 		)
 	}
 }

+ 3 - 1
http/route/route.go

@@ -6,8 +6,10 @@ import "github.com/gin-gonic/gin"
 func HTTPServerRoute(opts ...func(engine *gin.Engine)) func(s *gin.Engine) {
 	routes := []func(s *gin.Engine){
 		Root(opts...),
-		AppAPI(opts...),
+		SystemAPI(opts...),
 		DebugAPI(opts...),
+		OpsAPI(opts...),
+		PastureAPI(opts...),
 	}
 
 	return func(s *gin.Engine) {

+ 63 - 0
http/route/system_api.go

@@ -0,0 +1,63 @@
+package route
+
+import (
+	"kpt-tmr-group/http/handler"
+	"kpt-tmr-group/http/handler/mobile"
+	"kpt-tmr-group/http/handler/system"
+	"kpt-tmr-group/http/middleware"
+
+	"github.com/gin-gonic/gin"
+)
+
+func SystemAPI(opts ...func(engine *gin.Engine)) func(s *gin.Engine) {
+	return func(s *gin.Engine) {
+		for _, opt := range opts {
+			opt(s)
+		}
+		// Not Found
+		s.NoRoute(handler.Handle404)
+		// Health Check
+		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)
+
+		// 系统菜单权限
+		systemRoute.POST("/menu/add", system.AddSystemMenu)
+		systemRoute.POST("/menu/edit", system.EditSystemMenu)
+		systemRoute.POST("/menu/is_show", system.IsShowSystemMenu)
+		systemRoute.POST("/menu/list", system.SearchSystemMenuList)
+		systemRoute.DELETE("/menu/:menu_id", system.DeleteSystemMenu)
+
+		// 移动端
+		systemRoute.POST("/mobile/list", mobile.SearchMobileList)
+	}
+}
+
+func authRouteGroup(s *gin.Engine, relativePath string) *gin.RouterGroup {
+	group := s.Group(relativePath)
+	// 中间件鉴权
+	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)
+}

+ 1 - 1
main.go

@@ -10,7 +10,7 @@ import (
 )
 
 func main() {
-	logrus.Info("kpe-event: is starting")
+	logrus.Info("kpt-tmr-group: is starting")
 	cmd.Execute()
 	logrus.Error("kpt-tmr-group: is shut down")
 }

+ 167 - 0
model/analysis_accuracy.go

@@ -0,0 +1,167 @@
+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 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  *DataList    `json:"data_list"`
+}
+
+type TableList struct {
+	Name  string `json:"name"`
+	Value string `json:"value"`
+}
+
+type DataList struct {
+	MixedFodderAccurateRatio    []map[string]string `json:"mixed_fodder_accurate_ratio"`
+	MixedFodderCorrectRatio     []map[string]string `json:"mixed_fodder_correct_ratio"`
+	SprinkleFodderAccurateRatio []map[string]string `json:"sprinkle_fodder_accurate_ratio"`
+	SprinkleFodderCorrectRatio  []map[string]string `json:"sprinkle_fodder_correct_ratio"`
+}
+
+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"`    // 最低值
+	TopOneName  string     `json:"top_one_name"` // 最高值牧场名称
+	DataList    [][]string `json:"data_list"`    // 数据集合
+	PastureName []string   `json:"pasture_name"` // 牧场名称集合
+	PastureIds  []int32    `json:"pasture_ids"`
+	DateDay     []string   `json:"date_day"` // 日期集合
+}
+
+type GetPastureTopResponse struct {
+	Code int32       `json:"code"`
+	Msg  string      `json:"msg"`
+	Data *PastureTop `json:"data"`
+}
+
+type PastureTop struct {
+	MixedFodderAccurateRatio    []*PastureTopData `json:"mixed_fodder_accurate_ratio"`
+	MixedFodderCorrectRatio     []*PastureTopData `json:"mixed_fodder_correct_ratio"`
+	SprinkleFodderAccurateRatio []*PastureTopData `json:"sprinkle_fodder_accurate_ratio"`
+	SprinkleFodderCorrectRatio  []*PastureTopData `json:"sprinkle_fodder_correct_ratio"`
+}
+
+type PastureTopData struct {
+	PastureName string  `json:"pasture_name"`
+	Ratio       float64 `json:"ratio"`
+}
+
+type ExecTimeResponse struct {
+	Code int32             `json:"code"`
+	Msg  string            `json:"msg"`
+	Data *ExecTimeDataList `json:"data"`
+}
+
+type ExecTimeDataList struct {
+	Chart     *ExecTimeDataListChart `json:"chart"`
+	TableList []map[string]string    `json:"table_list"`
+}
+
+type ExecTimeDataListChart struct {
+	Title        []string   `json:"title"`
+	AddFeedTime  [][]string `json:"add_feed_time"`
+	SprinkleTime [][]string `json:"sprinkle_time"`
+	StirTime     [][]string `json:"stir_time"`
+}
+
+type PastureExecTimeData struct {
+	Code int32         `json:"code"`
+	Msg  string        `json:"msg"`
+	Data *ExecTimeData `json:"data"`
+}
+
+type ExecTimeData struct {
+	AddFeedTime  *ExecTimeDetail `json:"add_feed_time"`
+	SprinkleTime *ExecTimeDetail `json:"sprinkle_time"`
+	StirTime     *ExecTimeDetail `json:"stir_time"`
+}
+
+type ExecTimeDetail struct {
+	MaxValue        string `json:"max_value"`
+	MinValue        string `json:"min_value"`
+	MiddleValue     string `json:"middle_value"`
+	DownMiddleValue string `json:"down_middle_value"`
+	UpMiddleValue   string `json:"up_middle_value"`
+}
+
+type SprinkleFeedTimeResponse struct {
+	Code int32                 `json:"code"`
+	Msg  string                `json:"msg"`
+	Data *SprinkleFeedTimeData `json:"data"`
+}
+
+type SprinkleFeedTimeData struct {
+	Chart     *SprinkleFeedTimeChart   `json:"chart"`
+	TableList []*SprinkleFeedTimeTable `json:"table_list"`
+}
+
+type SprinkleFeedTimeChart struct {
+	Title              []string  `json:"title"`
+	SprinkleNumberList [][]int32 `json:"sprinkle_number_list"`
+}
+
+type SprinkleFeedTimeTable struct {
+	PastureName             string `json:"pasture_name"`               // 牧场名称
+	BarnName                string `json:"barn_name"`                  // 栏舍名称
+	ClassNumber             string `json:"class_number"`               // 班次名称
+	RealitySprinkleFeedTime string `json:"reality_sprinkle_feed_time"` // 时间撒料时间
+}
+
+type PastureSprinkleStatisticsDataList struct {
+	Code int32                         `json:"code"`
+	Msg  string                        `json:"msg"`
+	Data []*SprinkleStatisticsDataList `json:"data"`
+}
+
+type SprinkleStatisticsDataList struct {
+	FBarId      int32  `json:"f_bar_id"`
+	FName       string `json:"f_name"`
+	InTime      string `json:"in_time"`
+	ProcessTime string `json:"process_time"`
+	Times       int32  `json:"times"`
+}

+ 16 - 0
model/cattle_category.go

@@ -11,6 +11,7 @@ type CattleCategory struct {
 	ParentName  string                                `json:"parent_name"`
 	PastureId   int64                                 `json:"pasture_id"`
 	PastureName string                                `json:"pasture_name"`
+	DataId      int64                                 `json:"data_id"`
 	Name        string                                `json:"name"`
 	Number      string                                `json:"number"`
 	IsShow      operationPb.IsShow_Kind               `json:"is_show"`
@@ -68,3 +69,18 @@ func (c *CattleCategory) ToPb() *operationPb.AddCattleCategoryRequest {
 		CreatedAt:  uint32(c.CreatedAt),
 	}
 }
+
+func NewPastureCattleCategory(req *operationPb.CategorySyncRequest, pastureDetail *GroupPasture) *CattleCategory {
+	return &CattleCategory{
+		ParentId:    operationPb.CattleCategoryParent_Kind(req.ParentId),
+		ParentName:  req.ParentName,
+		PastureId:   int64(req.PastureId),
+		PastureName: pastureDetail.Name,
+		Name:        req.Name,
+		Number:      req.Number,
+		IsShow:      req.IsShow,
+		IsDelete:    operationPb.IsShow_NO,
+		DataSource:  operationPb.DataSource_FROM_PASTURE,
+		Remarks:     "牧场端数据同步",
+	}
+}

+ 7 - 0
model/feed_formula.go

@@ -60,6 +60,8 @@ func (f FeedFormulaSlice) ToPB() []*operationPb.AddFeedFormulaRequest {
 		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,
@@ -77,3 +79,8 @@ func (f FeedFormulaSlice) ToPB() []*operationPb.AddFeedFormulaRequest {
 	}
 	return res
 }
+
+type DistributeData struct {
+	PastureList     []*GroupPasture
+	FeedFormulaList []*FeedFormula
+}

+ 38 - 0
model/feed_formula_distribute_log.go

@@ -0,0 +1,38 @@
+package model
+
+import operationPb "kpt-tmr-group/proto/go/backend/operation"
+
+type FeedFormulaDistributeLog struct {
+	Id            int64                   `json:"id"`
+	FeedFormulaId int64                   `json:"feed_formula_id"`
+	PastureId     int64                   `json:"pasture_id"`
+	PastureName   string                  `json:"pasture_name"`
+	IsShow        operationPb.IsShow_Kind `json:"is_show"`
+	CreatedAt     int64                   `json:"created_at"`
+	UpdatedAt     int64                   `json:"updated_at"`
+}
+
+func (f *FeedFormulaDistributeLog) TableName() string {
+	return "feed_formula_distribute_log"
+}
+
+func NewFeedFormulaDistributeLog(feedFormulaId, pastureId int64, isShow operationPb.IsShow_Kind) *FeedFormulaDistributeLog {
+	return &FeedFormulaDistributeLog{
+		FeedFormulaId: feedFormulaId,
+		PastureId:     pastureId,
+		IsShow:        isShow,
+	}
+}
+
+func NewFeedFormulaDistributeLogList(feedFormulaList []*FeedFormula, pastureId int64, pastureName string, isShow operationPb.IsShow_Kind) []*FeedFormulaDistributeLog {
+	res := make([]*FeedFormulaDistributeLog, 0)
+	for _, v := range feedFormulaList {
+		res = append(res, &FeedFormulaDistributeLog{
+			FeedFormulaId: v.Id,
+			PastureId:     pastureId,
+			PastureName:   pastureName,
+			IsShow:        isShow,
+		})
+	}
+	return res
+}

+ 28 - 11
model/forage.go

@@ -6,13 +6,18 @@ import (
 )
 
 type Forage struct {
-	Id                 int64                           `json:"int_64"`
+	Id                 int64                           `json:"id"`
+	PastureId          int64                           `json:"pasture_id"`
+	PastureName        string                          `json:"pasture_name"`
 	Name               string                          `json:"name"`
 	CategoryId         int64                           `json:"category_id"`
-	MaterialType       int64                           `json:"material_type"`
+	CategoryName       string                          `json:"category_name"`
+	MaterialType       int32                           `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"`
@@ -23,6 +28,7 @@ type Forage struct {
 	RelayLocations     int64                           `json:"relay_locations"`
 	Jmp                operationPb.IsShow_Kind         `json:"jmp"`
 	DataSource         operationPb.DataSource_Kind     `json:"data_source"`
+	Sort               int64                           `json:"sort"`
 	Backup1            string                          `json:"backup1"`
 	Backup2            string                          `json:"backup2"`
 	Backup3            string                          `json:"backup3"`
@@ -40,9 +46,13 @@ 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,
+		MaterialType:       req.MaterialType,
 		SmallMaterialScale: req.SmallMaterialScale,
 		AllowError:         int64(req.AllowError),
 		PackageWeight:      int64(req.PackageWeight),
@@ -67,23 +77,30 @@ func (f ForageSlice) ToPB() []*operationPb.AddForageRequest {
 	res := make([]*operationPb.AddForageRequest, len(f))
 	for i, v := range f {
 		res[i] = &operationPb.AddForageRequest{
-			Id:                 uint32(v.Id),
+			Id:                 int32(v.Id),
 			Name:               v.Name,
-			CategoryId:         uint32(v.CategoryId),
-			MaterialType:       uint32(v.MaterialType),
+			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:         uint32(v.AllowError),
-			PackageWeight:      uint32(v.PackageWeight),
-			Price:              uint32(v.Price),
-			JumpWeight:         uint32(v.JumpWeight),
+			AllowError:         int32(v.AllowError),
+			PackageWeight:      int32(v.PackageWeight),
+			Price:              int32(v.Price),
+			JumpWeight:         int32(v.JumpWeight),
 			JumpDelay:          v.JumpDelay,
 			ConfirmStart:       v.ConfirmStart,
-			RelayLocations:     uint32(v.RelayLocations),
+			RelayLocations:     int32(v.RelayLocations),
+			Jmp:                v.Jmp,
 			IsShow:             v.IsShow,
-			CreatedAt:          uint32(v.CreatedAt),
+			Backup1:            v.Backup1,
+			Backup2:            v.Backup2,
+			Backup3:            v.Backup3,
+			CreatedAt:          int32(v.CreatedAt),
 			CreatedAtFormat:    time.Unix(v.CreatedAt, 0).Format(LayoutTime),
 		}
 	}

+ 24 - 8
model/forage_category.go

@@ -11,6 +11,7 @@ type ForageCategory struct {
 	ParentName  string                                `json:"parent_name"`
 	PastureId   int64                                 `json:"pasture_id"`
 	PastureName string                                `json:"pasture_name"`
+	DataId      int64                                 `json:"data_id"`
 	Name        string                                `json:"name"`
 	Number      string                                `json:"number"`
 	IsShow      operationPb.IsShow_Kind               `json:"is_show"`
@@ -57,14 +58,29 @@ func (f ForageCategorySlice) ToPB() []*operationPb.AddForageCategoryRequest {
 	return res
 }
 
-func (c *ForageCategory) ToPb() *operationPb.AddForageCategoryRequest {
+func (f *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),
+		Id:         uint32(f.Id),
+		Name:       f.Name,
+		Number:     f.Number,
+		ParentId:   f.ParentId,
+		ParentName: f.ParentName,
+		IsShow:     f.IsShow,
+		CreatedAt:  uint32(f.CreatedAt),
+	}
+}
+
+func NewPastureForageCategory(req *operationPb.CategorySyncRequest, pastureDetail *GroupPasture) *ForageCategory {
+	return &ForageCategory{
+		ParentId:    operationPb.ForageCategoryParent_Kind(req.ParentId),
+		ParentName:  req.ParentName,
+		PastureId:   int64(req.PastureId),
+		PastureName: pastureDetail.Name,
+		Name:        req.Name,
+		Number:      req.Number,
+		IsShow:      req.IsShow,
+		IsDelete:    operationPb.IsShow_NO,
+		DataSource:  operationPb.DataSource_FROM_PASTURE,
+		Remarks:     "牧场端数据同步",
 	}
 }

+ 244 - 129
model/formula_estimate.go

@@ -1,132 +1,247 @@
 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
+type PastureCommonRequest struct {
+	Name       string      `json:"name"`
+	Page       int32       `json:"page,omitempty"`
+	Offset     int32       `json:"offset"`
+	PageCount  int32       `json:"pagecount,omitempty"`
+	ReturnType string      `json:"returntype,omitempty"`
+	Checked    int32       `json:"checked,omitempty"`
+	ParamMaps  interface{} `json:"parammaps"`
+}
+
+type FormulaEstimateParams struct {
+	PastureId string `json:"pastureid"`
+	StartTime string `json:"startTime"`
+	StopTime  string `json:"stopTime"`
+	Search    string `json:"search"`
+	TempletId string `json:"templetid"`
+	Barid     string `json:"barid"`
+	ImforName string `json:"inforname,omitempty"`
+}
+
+type PastureCommonResponse struct {
+	Code int32              `json:"code"`
+	Msg  string             `json:"msg"`
+	Data *PastureCommonData `json:"data"`
+}
+
+type PastureCommonData struct {
+	List     interface{} `json:"list,omitempty"`
+	Data     interface{} `json:"data,omitempty"`
+	PageSize int32       `json:"pageSize"`
+	Total    int32       `json:"total"`
+	PageNum  int32       `json:"pageNum"`
+}
+
+type InventoryStatisticsParams struct {
+	PastureId string `json:"pastureid"`
+	StartTime string `json:"startTime"`
+	StopTime  string `json:"stopTime"`
+	FeedName  string `json:"feedname"`
+}
+
+type InventoryStatisticsList struct {
+	FeedName   string `json:"feedname"`
+	LaidSum    string `json:"laidsum"`    // 入库重量
+	StartPrice string `json:"startprice"` // 期初价格
+	StartSum   string `json:"startsum"`   // 期初库存
+	StopPrice  string `json:"stopprice"`  // 期末价格
+	StopSum    string `json:"stopsum"`    // 期末库存
+	UseSumRG   string `json:"usesumRG"`   // 人工用料重量
+	UseSumXH   string `json:"usesumXH"`   // 损耗重量
+	UseSumXT   string `json:"usesumXT"`   // 系统出库重量
+}
+
+type UserMaterialsStatisticsParams struct {
+	PastureId string `json:"pastureid"`
+	StartTime string `json:"startTime"`
+	StopTime  string `json:"stopTime"`
+	FeedName  string `json:"fname"`
+	Typea     string `json:"typea"`
+}
+
+type UserMaterialsList struct {
+	Data1 []interface{}             `json:"data1"`
+	Data2 []*UserMaterialsListData2 `json:"data2"`
+}
+
+type UserMaterialsListData2 struct {
+	Children []*Children `json:"children"`
+	Label    string      `json:"label"`
+}
+
+type Children struct {
+	Label string `json:"label"`
+	Prop  string `json:"prop"`
+}
+
+type PriceStatisticsParams struct {
+	PastureId string `json:"pastureid"`
+	StartTime string `json:"startTime"`
+	StopTime  string `json:"stopTime"`
+	FeedName  string `json:"fname"`
+}
+
+// FeedStatisticsParams 饲喂效率-效率统计
+type FeedStatisticsParams struct {
+	PastureId string `json:"pastureid"`
+	StartTime string `json:"startTime"`
+	StopTime  string `json:"stopTime"`
+	Date      string `json:"date"`
+	FeedTName string `json:"ftname"`
+	BarName   string `json:"barname"`
+	CowClass  string `json:"cowclass"`
+	Times     string `json:"times"`
+}
+
+// FeedChartParams 饲喂效率-图形统计
+type FeedChartParams struct {
+	ParamMaps interface{} `json:"parammaps"`
+}
+
+type ParamMaps struct {
+	PastureId string `json:"pastureid"`
+	StartTime string `json:"startTime"`
+	StopTime  string `json:"stopTime"`
+	Status    int32  `json:"status"`
+}
+
+// MixFeedStatisticsParams 准确性分析-混料统计
+type MixFeedStatisticsParams struct {
+	PastureId   string `json:"pastureid"`
+	StartTime   string `json:"startTime"`
+	StopTime    string `json:"stopTime"`
+	TmrTName    string `json:"tmrTName"`
+	ProjName    string `json:"projName"`
+	Times       string `json:"times"`
+	ButtonType  string `json:"buttonType"`
+	TempletName string `json:"templetName"`
+	Isuse       string `json:"isuse"`
+	Hlwc1       int32  `json:"hlwc1,omitempty"`
+	Hlwc2       int32  `json:"hlwc2,omitempty"`
+	Hlzq1       int32  `json:"hlzq1,omitempty"`
+	Hlzq2       int32  `json:"hlzq2,omitempty"`
+	Hlzql1      int32  `json:"hlzql1,omitempty"`
+	Hlzql2      int32  `json:"hlzql2,omitempty"`
+	Error       bool   `json:"error,omitempty"`
+}
+
+// SprinkleStatisticsParams 准确性分析-撒料统计
+type SprinkleStatisticsParams struct {
+	PastureId   string `json:"pastureid"`
+	StartTime   string `json:"startTime"`
+	StopTime    string `json:"stopTime"`
+	TmrTName    string `json:"tmrTName"`
+	ProjName    string `json:"projName"`
+	Times       string `json:"times"`
+	Fname       string `json:"fname"`
+	ButtonType  string `json:"buttontype"`
+	TempletName string `json:"templetname"`
+	Isuse       string `json:"isuse,omitempty"`
+	Slwc1       int32  `json:"slwc1,omitempty"`
+	Slwc2       int32  `json:"slwc2,omitempty"`
+	Slzq1       int32  `json:"slzq1,omitempty"`
+	Slzq2       int32  `json:"slzq2,omitempty"`
+	Slzql1      int32  `json:"slzql1,omitempty"`
+	Slzql2      int32  `json:"slzql2,omitempty"`
+	Error       bool   `json:"error,omitempty"`
+}
+
+// ProcessAnalysisParams 过程分析
+type ProcessAnalysisParams struct {
+	PastureId   string   `json:"pastureid"`
+	StartTime   string   `json:"startTime"`
+	StopTime    string   `json:"stopTime"`
+	TmrTName    []string `json:"tmrTName"`
+	IsCompleted string   `json:"iscompleted"`
+	LpPlanType  string   `json:"lpplantype"`
+	FClassId    string   `json:"fclassid"`
+	Hlwc1       int32    `json:"hlwc1,omitempty"`
+	Hlwc2       int32    `json:"hlwc2,omitempty"`
+	Hlzq1       int32    `json:"hlzq1,omitempty"`
+	Hlzq2       int32    `json:"hlzq2,omitempty"`
+	Slwc1       int32    `json:"slwc1,omitempty"`
+	Slwc2       int32    `json:"slwc2,omitempty"`
+	Slzq1       int32    `json:"slzq1,omitempty"`
+	Slzq2       int32    `json:"slzq2,omitempty"`
+	Error       string   `json:"error,omitempty"`
+}
+
+// AccuracyAggParams 准确性分析-汇总统计
+type AccuracyAggParams struct {
+	PastureId string `json:"pastureid"`
+	StartTime string `json:"startTime"`
+	StopTime  string `json:"stopTime"`
+	FName     string `json:"fname"`
+	Sort      string `json:"sort"`
+	Times     string `json:"times"`
+	Status    string `json:"status"`
+	Genre     int32  `json:"genre"`
+	IsDate    int32  `json:"isdate"`
+	Hlwc1     int32  `json:"hlwc1,omitempty"`
+	Hlwc2     int32  `json:"hlwc2,omitempty"`
+	Hlzq1     int32  `json:"hlzq1,omitempty"`
+	Hlzq2     int32  `json:"hlzq2,omitempty"`
+	Hlzql1    int32  `json:"hlzql1,omitempty"`
+	Hlzql2    int32  `json:"hlzql2,omitempty"`
+	Slwc1     int32  `json:"slwc1,omitempty"`
+	Slwc2     int32  `json:"slwc2,omitempty"`
+	Slzq1     int32  `json:"slzq1,omitempty"`
+	Slzq2     int32  `json:"slzq2,omitempty"`
+	Slzql1    int32  `json:"slzql1,omitempty"`
+	Slzql2    int32  `json:"slzql2,omitempty"`
+	Error     bool   `json:"error,omitempty"`
+}
+
+// TrainNumberParams 班次
+type TrainNumberParams struct {
+	PastureId string `json:"pastureid"`
+	InfoRName string `json:"inforname"`
+}
+
+// TrainNumberList 班次
+type TrainNumberList struct {
+	InfoName  string `json:"inforname"`
+	InfoValue string `json:"inforvalue"`
+}
+
+type GetDataByNameParams struct {
+	PastureId string `json:"pastureid"`
+	StartTime string `json:"startTime"`
+	StopTime  string `json:"stopTime"`
+}
+
+type FeedStatisticsResponse struct {
+	Code int32               `json:"code"`
+	Msg  string              `json:"msg"`
+	Data *FeedStatisticsData `json:"data"`
+}
+
+type FeedStatisticsData struct {
+	List map[string]interface{} `json:"list,omitempty"`
+}
+
+var DefaultSheetName = "Sheet1"
+
+type FeedStatisticsConversions struct {
+	PastureName               string  `json:"pasture_name"`                 // 牧场名称
+	TmrDryMatter              string  `json:"tmr_dry_matter"`               // TMR干物质
+	BarName                   string  `json:"bar_name"`                     // 栏舍名称
+	MilkYield                 string  `json:"milk_yield"`                   // 产奶量
+	TodayLeftovers            string  `json:"today_leftovers"`              // 今日剩料量
+	FeedCosts                 float64 `json:"feed_costs"`                   // 公斤奶饲料成本
+	LeftoverRate              string  `json:"leftover_rate"`                // 剩料率
+	ActualDryMatterIntake     string  `json:"actual_dry_matter_intake"`     // 实际干物质采食量
+	ActualCost                string  `json:"actual_cost"`                  // 实际成本
+	ActualNumberOfCattle      string  `json:"actual_number_of_cattle"`      // 实际牛头数
+	AmountToBeMixed           string  `json:"amount_to_be_mixed"`           // 应混料量
+	MixingTime                string  `json:"mixing_time"`                  // 混料时间
+	CategoryCattleName        string  `json:"category_cattle_name"`         // 牲畜类别
+	TheoreticalDryMatter      string  `json:"theoretical_dry_matter"`       // 理论干物质
+	LeftoverMaterial          float64 `json:"leftover_material"`            // 转投剩料量
+	FormulatedDryMatterIntake string  `json:"formulated_dry_matter_intake"` // 配方干物质采食量
+	CostOfFormulation         string  `json:"cost_of_formulation"`          // 配方成本
+	FoodIntakeRate            string  `json:"food_intake_rate"`             // 采食率
+	FeedConversionRatio       float64 `json:"feed_conversion_ratio"`        // 饲料转化率
 }

+ 214 - 21
model/group_pasture.go

@@ -1,45 +1,237 @@
 package model
 
 import (
+	"bytes"
+	"encoding/json"
+	"io/ioutil"
+	"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"
+	"strings"
 	"time"
+
+	"go.uber.org/zap"
 )
 
 type GroupPasture struct {
-	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"`
+	Id             int64                   `json:"id,omitempty"`
+	Name           string                  `json:"name,omitempty"`
+	PastureId      int64                   `json:"pasture_id"`
+	Account        string                  `json:"account,omitempty"`
+	Password       string                  `json:"password"`
+	ManagerUser    string                  `json:"manager_user"`
+	ManagerPhone   string                  `json:"manager_phone"`
+	Domain         string                  `json:"domain"`
+	Extranet       string                  `json:"extranet"`
+	Intranet       string                  `json:"intranet"`
+	IsShow         operationPb.IsShow_Kind `json:"is_show,omitempty"`
+	IsDelete       operationPb.IsShow_Kind `json:"is_delete,omitempty"`
+	IsDistribution operationPb.IsShow_Kind `json:"is_distribution"`
+	Address        string                  `json:"address"`
+	CreatedAt      int64                   `json:"created_at,omitempty"`
+	UpdatedAt      int64                   `json:"updated_at,omitempty"`
 }
 
-func (s *GroupPasture) TableName() string {
+func (g *GroupPasture) TableName() string {
 	return "group_pasture"
 }
 
-const InitManagerPassword = "123456"
+// GetPastureId 获取牧场id
+func (g *GroupPasture) GetPastureId() int64 {
+	if g.PastureId > 0 {
+		return g.PastureId
+	}
+	return g.Id
+}
+
+const (
+	InitManagerPassword = "123456"
+	UrlDataByName       = "authdata/GetDataByName"
+	UrlReportForm       = "authdata/GetReportform"
+	UrlSummary          = "authdata/summary"
+	UrlProcess          = "authdata/processAnalysist"
+)
+
+const (
+	FeedFormulaDistributeUrl      = "pasture/feed_formula/distribute"
+	FeedFormulaIsModifyUrl        = "pasture/feed_formula/is_modify"
+	DashboardAccuracyUrl          = "pasture/dashboard/accuracy_data"
+	DashboardExecTimeUrl          = "pasture/dashboard/process_analysis"
+	DashboardSprinkleFeedTimeUrl  = "pasture/dashboard/sprinkle_statistics"
+	PastureAccountDistributionURl = "pasture/account/distribute"
+	ForageCategoryDistributionURl = "pasture/forage_category/distribute"
+	CattleCategoryDistributionURl = "pasture/cattle_category/distribute"
+	CattleCategoryDeleteURl       = "pasture/cattle_category/delete"
+	ForageCategoryDeleteURl       = "pasture/cattle_category/delete"
+)
+
+var (
+	UrlChart = map[string]string{
+		"mr":    "authdata/chart/feedEffMR",     // 饲喂效率-效率统计 mr 泌乳牛干物质采食量
+		"sl":    "authdata/chart/feedEffSL",     // 饲喂效率-效率统计 sl 牛栏剩料率
+		"hl":    "authdata/chart/feedEffHL",     // 饲喂效率-效率统计 hl 混料时间统计
+		"zh":    "authdata/chart/feedEffZH",     // 饲喂效率-效率统计 zh 转化率
+		"cbft":  "authdata/chart/feedEffCBFT",   // 饲喂效率-效率统计 cbft 成本分析
+		"jh":    "authdata/chart/accuracyAllJH", // 准确性分析-汇总统计-计划统计
+		"ft":    "authdata/chart/accuracyAllFT", // 准确性分析-汇总统计-配方准确率
+		"nq":    "authdata/chart/accuracyAllNQ", // 准确性分析-汇总统计-牛群准确率
+		"cc":    "authdata/chart/accuracyAllCC", // 准确性分析-汇总统计-车辆准确率(重量)
+		"allhl": "authdata/chart/accuracyAllHL", // 准确性分析-汇总统计-混料统计
+		"qx":    "authdata/chart/accuracyAllQX", // 准确性分析-汇总统计-混料计划取消次数
+		"ls":    "authdata/chart/accuracyAllLS", // 准确性分析-汇总统计-栏舍撒料时间统计
+	}
+)
+
+type PastureTokenRequest struct {
+	UserName string `json:"username"`
+	Password string `json:"password"`
+}
+
+type PastureTokenResponse struct {
+	Code int32             `json:"code"`
+	Msg  string            `json:"msg"`
+	Data *PastureTokenData `json:"data"`
+}
+
+type PastureTokenData struct {
+	Token string `json:"token"`
+}
 
 func NewGroupPasture(req *operationPb.AddPastureRequest) *GroupPasture {
 	groupPasture := &GroupPasture{
-		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,
+		Name:           req.Name,
+		PastureId:      int64(req.PastureId),
+		Account:        req.Account,
+		Password:       tool.Md5String(InitManagerPassword),
+		ManagerUser:    req.ManagerUser,
+		ManagerPhone:   req.ManagerPhone,
+		IsShow:         operationPb.IsShow_OK,
+		IsDelete:       operationPb.IsShow_OK,
+		Address:        req.Address,
+		Domain:         req.Domain,
+		IsDistribution: operationPb.IsShow_NO,
 	}
 	return groupPasture
 }
 
+type PastureClient struct {
+	Token      string        `json:"token"`
+	Detail     *GroupPasture `json:"detail"`
+	authClient *http.Client
+}
+
+func NewPastureClient(g *GroupPasture) *PastureClient {
+	return &PastureClient{
+		Detail: g,
+		authClient: &http.Client{
+			Timeout: time.Duration(5) * time.Second,
+		},
+	}
+}
+
+func (p *PastureClient) doRequest(req *http.Request) ([]byte, error) {
+	resp, err := p.authClient.Do(req)
+	if err != nil {
+		zaplog.Error("PastureClient", zap.Any("authClient.Do", err))
+		return nil, xerr.WithStack(err)
+	}
+	b, err := ioutil.ReadAll(resp.Body)
+	if err != nil {
+		zaplog.Error("PastureClient", 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 (p *PastureClient) DoGet(url string) ([]byte, error) {
+	// 获取token
+	if err := p.GetToken(); err != nil {
+		zaplog.Error("PastureClient", zap.Any("DoGet GetToken Err", err), zap.Any("detail", p.Detail))
+		return nil, xerr.WithStack(err)
+	}
+
+	req, err := http.NewRequest(http.MethodGet, url, nil)
+	if err != nil {
+		zaplog.Error("PastureClient", zap.Any("DoGet", err))
+		return nil, err
+	}
+	req.Header.Add("Accept", "application/json")
+	req.Header.Add("token", p.Token)
+	req.Header.Add("Content-Type", "application/json")
+	return p.doRequest(req)
+}
+
+func (p *PastureClient) DoPost(url string, body interface{}) ([]byte, error) {
+	b, err := json.Marshal(body)
+	if err != nil {
+		zaplog.Error("PastureClient", zap.Any("DoPost-Marshal", err))
+		return nil, xerr.WithStack(err)
+	}
+	// 获取token
+	if !strings.Contains(url, PastureAccountDistributionURl) {
+		if err = p.GetToken(); err != nil {
+			zaplog.Error("PastureClient", zap.Any("DoPost GetToken Err", err), zap.Any("detail", p.Detail))
+			return nil, xerr.WithStack(err)
+		}
+	}
+
+	req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(b))
+	if err != nil {
+		zaplog.Error("PastureClient", zap.Any("NewRequest", err))
+		return nil, xerr.WithStack(err)
+	}
+	req.Header.Add("Accept", "application/json")
+	req.Header.Add("token", p.Token)
+	req.Header.Add("Content-Type", "application/json")
+	return p.doRequest(req)
+}
+
+func (p *PastureClient) GetToken() error {
+	/*url := fmt.Sprintf("%s/%s", p.Detail.Domain, "auth")
+	body := &PastureTokenRequest{
+		UserName: p.Detail.Account,
+		Password: p.Detail.Password,
+	}
+	b, err := json.Marshal(body)
+	if err != nil {
+		return xerr.WithStack(err)
+	}
+
+	req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(b))
+	if err != nil {
+		zaplog.Error("PastureClient", zap.Any("GetToken", err))
+		return xerr.WithStack(err)
+	}
+	req.Header.Add("Accept", "application/json")
+	req.Header.Add("Content-Type", "application/json")
+	result, err := p.doRequest(req)
+	if err != nil {
+		zaplog.Error("PastureClient", zap.Any("GetToken-doRequest", err))
+		return xerr.WithStack(err)
+	}
+	response := &PastureTokenResponse{}
+	if err = json.Unmarshal(result, response); err != nil {
+		zaplog.Error("PastureClient", zap.String("GetToken", string(result)))
+		return xerr.WithStack(err)
+	}
+	if response.Code != http.StatusOK || response.Data == nil || response.Data.Token == "" {
+		zaplog.Error("PastureClient", zap.Any("response", response))
+		return xerr.Customf("获取牧场端token失败 Err:%s", err)
+	}
+	p.Token = response.Data.Token*/
+	p.Token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwicGFzc3dvcmQiOiJlMTBhZGMzOTQ5YmE1OWFiYmU1NmUwNTdmMjBmODgzZSIsImV4cCI6MTY4OTI2ODIxOCwiaXNzIjoiaHR0cHM6Ly9naXRodWIuY29tL2twdHl1bi9nby1hZG1pbi8ifQ.Q2ztGs66HbIz4IpTy9pH7bZVsJEzVQBwpjJSLBzKn4c"
+	return nil
+}
+
 type GroupPastureSlice []*GroupPasture
 
 func (g GroupPastureSlice) ToPB() []*operationPb.AddPastureRequest {
@@ -52,6 +244,7 @@ func (g GroupPastureSlice) ToPB() []*operationPb.AddPastureRequest {
 			ManagerUser:     v.ManagerUser,
 			ManagerPhone:    v.ManagerPhone,
 			Address:         v.Address,
+			Domain:          v.Domain,
 			IsShow:          v.IsShow,
 			CreatedAt:       int32(v.CreatedAt),
 			CreatedAtFormat: time.Unix(v.CreatedAt, 0).Format(LayoutTime),

+ 70 - 0
model/pasture_data.go

@@ -0,0 +1,70 @@
+package model
+
+type DistributeFeedFormulaRequest struct {
+	PastureId int64          `json:"pasture_id"`
+	Body      []*FeedFormula `json:"body"`
+}
+
+type PastureResponse struct {
+	Code int32       `json:"code"`
+	Msg  string      `json:"msg"`
+	Data interface{} `json:"data"`
+}
+
+type DashboardAccuracyRequest struct {
+	CattleParentCategoryId int32  `json:"cattle_parent_category_id,omitempty"`
+	FeedFormulaId          int32  `json:"feed_formula_id"`
+	StartDate              string `json:"start_date"`
+	EndDate                string `json:"end_date"`
+	PastureId              int32  `json:"pasture_id"`
+}
+
+type FeedFormulaIsModifyRequest struct {
+	PastureId     int64 `json:"pasture_id"`
+	FeedFormulaId int64 `json:"feed_formula_id"`
+	IsModify      int32 `json:"is_modify"`
+}
+
+type PastureAnalysisAccuracyResponse struct {
+	Code int32                        `json:"code"`
+	Msg  string                       `json:"msg"`
+	Data *PastureAnalysisAccuracyData `json:"data"`
+}
+
+type PastureAnalysisAccuracyData struct {
+	MixedFodderAccurateRatio    []*PastureAnalysisAccuracyDataValue `json:"mixed_fodder_accurate_ratio"`    // 混料准确率
+	MixedFodderCorrectRatio     []*PastureAnalysisAccuracyDataValue `json:"mixed_fodder_correct_ratio"`     // 混料正确率
+	SprinkleFodderAccurateRatio []*PastureAnalysisAccuracyDataValue `json:"sprinkle_fodder_accurate_ratio"` // 撒料准确率
+	SprinkleFodderCorrectRatio  []*PastureAnalysisAccuracyDataValue `json:"sprinkle_fodder_correct_ratio"`  // 撒料正确率
+}
+
+type PastureAnalysisAccuracyDataValue struct {
+	DayTime string  `json:"day_time"`
+	Ratio   float64 `json:"ratio"`
+}
+
+type AccountDistribution struct {
+	Account     string `json:"account"`
+	UserName    string `json:"user_name"`
+	Password    string `json:"password"`
+	Phone       string `json:"phone"`
+	PastureId   int32  `json:"pasture_id"`
+	PastureName string `json:"pasture_name"`
+	Address     string `json:"address"`
+}
+
+type CategoryRequest struct {
+	PastureId  int32  `json:"pasture_id"`
+	ParentId   int32  `json:"parent_id"`
+	ParentName string `json:"parent_name"`
+	Name       string `json:"name"`
+	Number     string `json:"number"`
+	IsShow     int32  `json:"is_show"`
+	GroupId    int32  `json:"group_id"`
+}
+
+type CategoryDeleteRequest struct {
+	PastureId int32 `json:"pasture_id"`
+	GroupId   int32 `json:"group_id"`
+	IsDelete  int32 `json:"is_delete"`
+}

+ 26 - 0
model/pasture_data_log.go

@@ -0,0 +1,26 @@
+package model
+
+type PastureDataLog struct {
+	Id        int64  `json:"id"`
+	LogType   int32  `json:"log_type"`
+	PastureId int64  `json:"pasture_id"`
+	Body      string `json:"body"`
+	Url       string `json:"url"`
+	Response  string `json:"response"`
+	CreatedAt int64  `json:"created_at"`
+	UpdatedAt int64  `json:"updated_at"`
+}
+
+func (p *PastureDataLog) TableName() string {
+	return "pasture_data_log"
+}
+
+func NewPastureDataLog(pastureId int64, logType int32, url, body, response string) *PastureDataLog {
+	return &PastureDataLog{
+		LogType:   logType,
+		PastureId: pastureId,
+		Body:      body,
+		Url:       url,
+		Response:  response,
+	}
+}

+ 20 - 2
model/system_role.go

@@ -19,7 +19,10 @@ func (s *SystemRole) TableName() string {
 	return "system_role"
 }
 
-const LayoutTime = "2006-01-02 15:04:05"
+const (
+	LayoutTime = "2006-01-02 15:04:05"
+	LayoutDate = "20060102"
+)
 
 func NewSystemRole(req *operationPb.AddRoleRequest) *SystemRole {
 	systemRole := &SystemRole{
@@ -33,7 +36,7 @@ func NewSystemRole(req *operationPb.AddRoleRequest) *SystemRole {
 
 type SystemRoleSlice []*SystemRole
 
-func (s SystemRoleSlice) ToPB() []*operationPb.AddRoleRequest {
+func (s SystemRoleSlice) ToPB(groupPastureList map[int64][]*GroupPasture, systemMenuList map[int64][]*SystemMenu) []*operationPb.AddRoleRequest {
 	res := make([]*operationPb.AddRoleRequest, len(s))
 	for i, v := range s {
 		res[i] = &operationPb.AddRoleRequest{
@@ -45,6 +48,21 @@ func (s SystemRoleSlice) ToPB() []*operationPb.AddRoleRequest {
 			CreatedAt:       uint32(v.CreatedAt),
 			CreatedAtFormat: time.Unix(v.CreatedAt, 0).Format(LayoutTime),
 		}
+		groupPasture, ok := groupPastureList[v.Id]
+		if ok {
+			for _, g := range groupPasture {
+				res[i].PastureList = append(res[i].PastureList, g.Name)
+				res[i].PastureId = append(res[i].PastureId, uint32(g.Id))
+			}
+		}
+
+		systemMenu, ok := systemMenuList[v.Id]
+		if ok {
+			for _, m := range systemMenu {
+				res[i].MenuList = append(res[i].MenuList, m.Name)
+				res[i].MenuId = append(res[i].MenuId, uint32(m.Id))
+			}
+		}
 	}
 	return res
 }

+ 13 - 0
model/unique_data.go

@@ -0,0 +1,13 @@
+package model
+
+type UniqueData struct {
+	Id        int64  `json:"id"`
+	Prefix    string `json:"prefix"`
+	Data      int64  `json:"data"`
+	CreatedAt int64  `json:"created_at"`
+	UpdatedAt int64  `json:"updated_at"`
+}
+
+func (s *UniqueData) TableName() string {
+	return "unique_data"
+}

+ 573 - 0
module/backend/dashboard_service.go

@@ -0,0 +1,573 @@
+package backend
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"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"
+	"sort"
+	"sync"
+	"time"
+
+	"go.uber.org/multierr"
+	"go.uber.org/zap"
+)
+
+const compareTime = 10 * 60
+
+// PasturePrefAnalysisData  PasturePrefExecTimeData PastureSprinkleFeedTime TODO  后期三个函数封装一下
+func (s *StoreEntry) PasturePrefAnalysisData(ctx context.Context, req *operationPb.SearchAnalysisAccuracyRequest) (map[int64]*model.PastureAnalysisAccuracyData, error) {
+	res := make(map[int64]*model.PastureAnalysisAccuracyData, 0)
+
+	wg := sync.WaitGroup{}
+	wg.Add(len(req.PastureIds))
+
+	for _, pastureId := range req.PastureIds {
+		go func(pId int32) {
+			defer wg.Done()
+
+			response := &model.PastureAnalysisAccuracyResponse{}
+			groupPasture, err := s.GetGroupPastureListById(ctx, int64(pId))
+			if err != nil {
+				zaplog.Error("PasturePrefAnalysisData",
+					zap.Any("GetGroupPastureListById", err),
+					zap.Any("pId", pId))
+				return
+			}
+
+			body := &model.DashboardAccuracyRequest{
+				PastureId:              int32(groupPasture.PastureId),
+				FeedFormulaId:          req.FeedFormulaId,
+				CattleParentCategoryId: int32(req.CattleParentCategoryId),
+				StartDate:              req.StartDate,
+				EndDate:                req.EndDate,
+			}
+			_, err = s.PastureHttpClient(ctx, model.DashboardAccuracyUrl, int64(pId), body, response)
+			if err != nil {
+				zaplog.Error("DistributeFeedFormula",
+					zap.String("url", model.DashboardAccuracyUrl),
+					zap.Any("pasture", groupPasture), zap.Any("body", body),
+					zap.Any("err", err), zap.Any("response", response))
+				b, _ := json.Marshal(body)
+				resB, _ := json.Marshal(response)
+				pastureDataLog := model.NewPastureDataLog(groupPasture.Id, PastureDataLogType["FeedFormula_Distribute"], model.DashboardAccuracyUrl, string(b), string(resB))
+				s.DB.Create(pastureDataLog)
+				return
+			}
+			if response.Code != http.StatusOK {
+				zaplog.Error("DistributeFeedFormula-response",
+					zap.String("url", model.DashboardAccuracyUrl),
+					zap.Any("pasture", groupPasture), zap.Any("body", body),
+					zap.Any("response", response), zap.Any("response", response))
+				return
+			}
+			res[groupPasture.Id] = response.Data
+
+		}(pastureId)
+	}
+	wg.Wait()
+	return res, nil
+}
+
+func (s *StoreEntry) PasturePrefExecTimeData(ctx context.Context, req *operationPb.SearchAnalysisAccuracyRequest) (map[string]*model.ExecTimeData, error) {
+
+	res := make(map[string]*model.ExecTimeData, 0)
+	wg := sync.WaitGroup{}
+	wg.Add(len(req.PastureIds))
+
+	for _, pastureId := range req.PastureIds {
+		go func(pId int32) {
+			defer wg.Done()
+
+			groupPasture, err := s.GetGroupPastureListById(ctx, int64(pId))
+			if err != nil {
+				return
+			}
+
+			response := &model.PastureExecTimeData{}
+			body := &model.DashboardAccuracyRequest{
+				PastureId:              int32(groupPasture.PastureId),
+				FeedFormulaId:          req.FeedFormulaId,
+				CattleParentCategoryId: int32(req.CattleParentCategoryId),
+				StartDate:              req.StartDate,
+				EndDate:                req.EndDate,
+			}
+			if _, err = s.PastureHttpClient(ctx, model.DashboardExecTimeUrl, int64(pId), body, response); err != nil {
+				zaplog.Error("PasturePrefExecTimeData",
+					zap.String("url", model.DashboardExecTimeUrl),
+					zap.Any("pasture", groupPasture), zap.Any("body", body),
+					zap.Any("err", err), zap.Any("response", response))
+				b, _ := json.Marshal(body)
+				resB, _ := json.Marshal(response)
+				pastureDataLog := model.NewPastureDataLog(groupPasture.Id, PastureDataLogType["PasturePrefExecTimeData"], model.DashboardExecTimeUrl, string(b), string(resB))
+				s.DB.Create(pastureDataLog)
+				return
+			}
+			if response.Code != http.StatusOK {
+				zaplog.Error("PasturePrefExecTimeData-response",
+					zap.String("url", model.DashboardExecTimeUrl),
+					zap.Any("pasture", groupPasture), zap.Any("body", body),
+					zap.Any("err", err), zap.Any("response", response))
+				return
+			}
+			res[groupPasture.Name] = response.Data
+
+		}(pastureId)
+	}
+	wg.Wait()
+	return res, nil
+}
+
+func (s *StoreEntry) PastureSprinkleFeedTime(ctx context.Context, req *operationPb.SprinkleFeedTimeRequest) (map[string][]*model.SprinkleStatisticsDataList, error) {
+
+	res := make(map[string][]*model.SprinkleStatisticsDataList, 0)
+	wg := sync.WaitGroup{}
+	wg.Add(len(req.PastureIds))
+	var muError error
+	for _, pasture := range req.PastureIds {
+		go func(pId int32) {
+			defer wg.Done()
+
+			groupPasture, err := s.GetGroupPastureListById(ctx, int64(pId))
+			if err != nil {
+				zaplog.Error("PastureSprinkleFeedTime", zap.Any("GetGroupPastureListById", err))
+				return
+			}
+
+			response := &model.PastureSprinkleStatisticsDataList{}
+			body := &model.DashboardAccuracyRequest{
+				PastureId:     int32(groupPasture.PastureId),
+				FeedFormulaId: req.FeedFormulaId,
+				StartDate:     req.StartDate,
+				EndDate:       req.EndDate,
+			}
+			if _, err = s.PastureHttpClient(ctx, model.DashboardSprinkleFeedTimeUrl, int64(pId), body, response); err != nil {
+				muError = multierr.Append(muError, err)
+				zaplog.Error("PastureSprinkleFeedTime",
+					zap.String("url", model.DashboardSprinkleFeedTimeUrl),
+					zap.Any("pasture", groupPasture), zap.Any("body", body),
+					zap.Any("err", err), zap.Any("response", response))
+				b, _ := json.Marshal(body)
+				resB, _ := json.Marshal(response)
+				pastureDataLog := model.NewPastureDataLog(groupPasture.Id, PastureDataLogType["PasturePrefExecTimeData"], model.DashboardSprinkleFeedTimeUrl, string(b), string(resB))
+				s.DB.Create(pastureDataLog)
+			}
+			if response.Code != http.StatusOK {
+				muError = multierr.Append(muError, xerr.Custom(response.Msg))
+			}
+			res[groupPasture.Name] = response.Data
+		}(pasture)
+	}
+	wg.Wait()
+	return res, nil
+}
+
+func (s *StoreEntry) SearchAnalysisAccuracy(ctx context.Context, req *operationPb.SearchAnalysisAccuracyRequest) (*model.SearchAnalysisAccuracyResponse, error) {
+	res := &model.SearchAnalysisAccuracyResponse{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &model.AnalysisAccuracyData{
+			Chart: &model.Chart{},
+			Table: &model.Table{
+				TitleList: make([]*model.TableList, 0),
+				DataList:  &model.DataList{},
+			},
+		},
+	}
+	res.Data.Table.TitleList = append(res.Data.Table.TitleList, &model.TableList{
+		Name:  "title",
+		Value: "牧场",
+	})
+	pastureAnalysisAccuracy, err := s.PasturePrefAnalysisData(ctx, req)
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	mixedFodderAccurateRatio, mixedFodderCorrectRatio, sprinkleFodderAccurateRatio, sprinkleFodderCorrectRatio :=
+		&model.CommonValueRatio{}, &model.CommonValueRatio{}, &model.CommonValueRatio{}, &model.CommonValueRatio{}
+	maTitleValueList, mcTitleValueList, saTitleValueList, scTitleValueList := make([]float64, 0), make([]float64, 0), make([]float64, 0), make([]float64, 0)
+	mTopOneName := ""
+	for pastureId, data := range pastureAnalysisAccuracy {
+		groupPasture, err := s.GetGroupPastureListById(ctx, pastureId)
+		if err != nil {
+			zaplog.Error("SearchAnalysisAccuracy GetGroupPastureListById",
+				zap.Any("pastureId", pastureId), zap.Any("error", err))
+			continue
+		}
+		if data == nil {
+			continue
+		}
+
+		mixedFodderAccurateRatioDataList := make([]string, 0)
+		for _, v := range data.MixedFodderAccurateRatio {
+			mixedFodderAccurateRatioDataList = append(mixedFodderAccurateRatioDataList, fmt.Sprintf("%.2f", v.Ratio))
+			mixedFodderAccurateRatio.DateDay = append(mixedFodderAccurateRatio.DateDay, v.DayTime)
+			maTitleValueList = append(maTitleValueList, v.Ratio)
+		}
+
+		mixedFodderAccurateRatio.DataList = append(mixedFodderAccurateRatio.DataList, mixedFodderAccurateRatioDataList)
+		mixedFodderAccurateRatio.PastureIds = append(mixedFodderAccurateRatio.PastureIds, int32(pastureId))
+		mixedFodderAccurateRatio.PastureName = append(mixedFodderAccurateRatio.PastureName, groupPasture.Name)
+
+		mixedFodderCorrectRatioDataList := make([]string, 0)
+		for _, v := range data.MixedFodderCorrectRatio {
+			mixedFodderCorrectRatioDataList = append(mixedFodderCorrectRatioDataList, fmt.Sprintf("%.2f", v.Ratio))
+			mixedFodderCorrectRatio.DateDay = append(mixedFodderCorrectRatio.DateDay, v.DayTime)
+			mcTitleValueList = append(mcTitleValueList, v.Ratio)
+		}
+
+		mixedFodderCorrectRatio.DataList = append(mixedFodderCorrectRatio.DataList, mixedFodderCorrectRatioDataList)
+		mixedFodderCorrectRatio.PastureIds = append(mixedFodderCorrectRatio.PastureIds, int32(pastureId))
+		mixedFodderCorrectRatio.PastureName = append(mixedFodderCorrectRatio.PastureName, groupPasture.Name)
+
+		sprinkleFodderRatioDataList := make([]string, 0)
+		for _, v := range data.SprinkleFodderAccurateRatio {
+			sprinkleFodderRatioDataList = append(sprinkleFodderRatioDataList, fmt.Sprintf("%.2f", v.Ratio))
+			sprinkleFodderAccurateRatio.DateDay = append(sprinkleFodderAccurateRatio.DateDay, v.DayTime)
+			saTitleValueList = append(saTitleValueList, v.Ratio)
+		}
+
+		sprinkleFodderAccurateRatio.DataList = append(sprinkleFodderAccurateRatio.DataList, sprinkleFodderRatioDataList)
+		sprinkleFodderAccurateRatio.PastureIds = append(sprinkleFodderAccurateRatio.PastureIds, int32(pastureId))
+		sprinkleFodderAccurateRatio.PastureName = append(sprinkleFodderAccurateRatio.PastureName, groupPasture.Name)
+
+		sprinkleFodderCorrectRatioDataList := make([]string, 0)
+		for _, v := range data.SprinkleFodderCorrectRatio {
+			sprinkleFodderCorrectRatioDataList = append(sprinkleFodderCorrectRatioDataList, fmt.Sprintf("%.2f", v.Ratio))
+			sprinkleFodderCorrectRatio.DateDay = append(sprinkleFodderCorrectRatio.DateDay, v.DayTime)
+			scTitleValueList = append(scTitleValueList, v.Ratio)
+		}
+
+		sprinkleFodderCorrectRatio.DataList = append(sprinkleFodderCorrectRatio.DataList, sprinkleFodderCorrectRatioDataList)
+		sprinkleFodderCorrectRatio.PastureIds = append(sprinkleFodderCorrectRatio.PastureIds, int32(pastureId))
+		sprinkleFodderCorrectRatio.PastureName = append(sprinkleFodderCorrectRatio.PastureName, groupPasture.Name)
+	}
+
+	sort.Float64s(maTitleValueList)
+	mixedFodderAccurateRatio.MaxValue = fmt.Sprintf("%.2f", maTitleValueList[len(maTitleValueList)-1])
+	mixedFodderAccurateRatio.MinValue = fmt.Sprintf("%.2f", maTitleValueList[0])
+	mixedFodderAccurateRatio.MiddleValue = fmt.Sprintf("%.2f", tool.Median(maTitleValueList))
+	mixedFodderAccurateRatio.TopOneName = mTopOneName
+
+	sort.Float64s(mcTitleValueList)
+	mixedFodderCorrectRatio.MaxValue = fmt.Sprintf("%.2f", mcTitleValueList[len(mcTitleValueList)-1])
+	mixedFodderCorrectRatio.MinValue = fmt.Sprintf("%.2f", mcTitleValueList[0])
+	mixedFodderCorrectRatio.MiddleValue = fmt.Sprintf("%.2f", tool.Median(mcTitleValueList))
+	mixedFodderCorrectRatio.TopOneName = mTopOneName
+
+	sort.Float64s(saTitleValueList)
+	sprinkleFodderAccurateRatio.MaxValue = fmt.Sprintf("%.2f", saTitleValueList[len(saTitleValueList)-1])
+	sprinkleFodderAccurateRatio.MinValue = fmt.Sprintf("%.2f", saTitleValueList[0])
+	sprinkleFodderAccurateRatio.MiddleValue = fmt.Sprintf("%.2f", tool.Median(saTitleValueList))
+	sprinkleFodderAccurateRatio.TopOneName = mTopOneName
+
+	sort.Float64s(scTitleValueList)
+	sprinkleFodderCorrectRatio.MaxValue = fmt.Sprintf("%.2f", scTitleValueList[len(scTitleValueList)-1])
+	sprinkleFodderCorrectRatio.MinValue = fmt.Sprintf("%.2f", scTitleValueList[0])
+	sprinkleFodderCorrectRatio.MiddleValue = fmt.Sprintf("%.2f", tool.Median(scTitleValueList))
+	sprinkleFodderCorrectRatio.TopOneName = mTopOneName
+
+	chart := &model.Chart{
+		MixedFodderAccurateRatio:    mixedFodderAccurateRatio,
+		MixedFodderCorrectRatio:     mixedFodderCorrectRatio,
+		SprinkleFodderAccurateRatio: sprinkleFodderAccurateRatio,
+		SprinkleFodderCorrectRatio:  sprinkleFodderCorrectRatio,
+	}
+
+	res.Data.Chart = chart
+	res.Data.Table = s.TitleList(ctx, pastureAnalysisAccuracy)
+	return res, nil
+}
+
+// TopPasture 牧场排名
+func (s *StoreEntry) TopPasture(ctx context.Context, req *operationPb.SearchAnalysisAccuracyRequest) (*model.GetPastureTopResponse, error) {
+	res := &model.GetPastureTopResponse{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &model.PastureTop{
+			MixedFodderAccurateRatio:    make([]*model.PastureTopData, 0),
+			MixedFodderCorrectRatio:     make([]*model.PastureTopData, 0),
+			SprinkleFodderAccurateRatio: make([]*model.PastureTopData, 0),
+			SprinkleFodderCorrectRatio:  make([]*model.PastureTopData, 0),
+		},
+	}
+	analysisAccuracy, err := s.PasturePrefAnalysisData(ctx, req)
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	mixedFodderAccurateRatio := make([]*model.PastureTopData, 0)
+	mixedFodderCorrectRatio := make([]*model.PastureTopData, 0)
+	sprinkleFodderAccurateRatio := make([]*model.PastureTopData, 0)
+	sprinkleFodderCorrectRatio := make([]*model.PastureTopData, 0)
+	for pastureId, data := range analysisAccuracy {
+		groupPasture, err := s.GetGroupPastureListById(ctx, pastureId)
+		if err != nil {
+			zaplog.Error("TopPasture", zap.Any("GetGroupPastureListById", pastureId), zap.Any("err", err))
+			continue
+		}
+		if data == nil {
+			continue
+		}
+
+		allMaRatio, allMcRatio, allSaRatio, allScRatio := 0.0, 0.0, 0.0, 0.0
+		for _, v := range data.MixedFodderAccurateRatio {
+			allMaRatio += v.Ratio
+		}
+		mixedFodderAccurateRatio = append(mixedFodderAccurateRatio, &model.PastureTopData{
+			PastureName: groupPasture.Name,
+			Ratio:       allMaRatio / float64(len(data.MixedFodderAccurateRatio)),
+		})
+
+		for _, v := range data.MixedFodderCorrectRatio {
+			allMcRatio += v.Ratio
+		}
+		mixedFodderCorrectRatio = append(mixedFodderCorrectRatio, &model.PastureTopData{
+			PastureName: groupPasture.Name,
+			Ratio:       allMaRatio / float64(len(data.MixedFodderCorrectRatio)),
+		})
+
+		for _, v := range data.SprinkleFodderAccurateRatio {
+			allSaRatio += v.Ratio
+		}
+		sprinkleFodderAccurateRatio = append(sprinkleFodderAccurateRatio, &model.PastureTopData{
+			PastureName: groupPasture.Name,
+			Ratio:       allSaRatio / float64(len(data.SprinkleFodderAccurateRatio)),
+		})
+
+		for _, v := range data.SprinkleFodderCorrectRatio {
+			allScRatio += v.Ratio
+		}
+		sprinkleFodderCorrectRatio = append(sprinkleFodderCorrectRatio, &model.PastureTopData{
+			PastureName: groupPasture.Name,
+			Ratio:       allScRatio / float64(len(data.SprinkleFodderCorrectRatio)),
+		})
+	}
+
+	sort.Slice(mixedFodderAccurateRatio, func(i, j int) bool {
+		return mixedFodderAccurateRatio[i].Ratio > mixedFodderAccurateRatio[j].Ratio
+	})
+	sort.Slice(mixedFodderCorrectRatio, func(i, j int) bool {
+		return mixedFodderCorrectRatio[i].Ratio > mixedFodderCorrectRatio[j].Ratio
+	})
+	sort.Slice(sprinkleFodderAccurateRatio, func(i, j int) bool {
+		return sprinkleFodderAccurateRatio[i].Ratio > sprinkleFodderAccurateRatio[j].Ratio
+	})
+	sort.Slice(sprinkleFodderCorrectRatio, func(i, j int) bool {
+		return sprinkleFodderCorrectRatio[i].Ratio > sprinkleFodderCorrectRatio[j].Ratio
+	})
+
+	res.Data.MixedFodderAccurateRatio = mixedFodderAccurateRatio
+	res.Data.MixedFodderCorrectRatio = mixedFodderCorrectRatio
+	res.Data.SprinkleFodderAccurateRatio = sprinkleFodderAccurateRatio
+	res.Data.SprinkleFodderCorrectRatio = sprinkleFodderCorrectRatio
+	return res, nil
+}
+
+func (s *StoreEntry) TitleList(ctx context.Context, pastureAnalysisList map[int64]*model.PastureAnalysisAccuracyData) *model.Table {
+	res := &model.Table{
+		TitleList: make([]*model.TableList, 0),
+		DataList: &model.DataList{
+			MixedFodderAccurateRatio:    make([]map[string]string, 0),
+			MixedFodderCorrectRatio:     make([]map[string]string, 0),
+			SprinkleFodderAccurateRatio: make([]map[string]string, 0),
+			SprinkleFodderCorrectRatio:  make([]map[string]string, 0),
+		},
+	}
+
+	for pastureId, data := range pastureAnalysisList {
+		groupPasture, err := s.GetGroupPastureListById(ctx, pastureId)
+		if err != nil {
+			zaplog.Info("TitleList", zap.Any("GetGroupPastureListById", pastureId), zap.Any("err", err))
+			continue
+		}
+
+		if len(res.TitleList) <= len(data.MixedFodderAccurateRatio) {
+			res.TitleList = append(res.TitleList, &model.TableList{
+				Name:  "title",
+				Value: "牧场",
+			})
+		}
+
+		maMap := map[string]string{
+			"title": groupPasture.Name,
+		}
+		for i, v := range data.MixedFodderCorrectRatio {
+			maMap[fmt.Sprintf("data%d", i+1)] = fmt.Sprintf("%.2f", v.Ratio)
+			if len(res.TitleList) <= len(data.MixedFodderAccurateRatio) {
+				res.TitleList = append(res.TitleList, &model.TableList{
+					Name:  fmt.Sprintf("data%d", i+1),
+					Value: v.DayTime,
+				})
+			}
+		}
+		res.DataList.MixedFodderAccurateRatio = append(res.DataList.MixedFodderAccurateRatio, maMap)
+
+		mcMap := map[string]string{
+			"title": groupPasture.Name,
+		}
+		for i, v := range data.MixedFodderCorrectRatio {
+			mcMap[fmt.Sprintf("data%d", i+1)] = fmt.Sprintf("%.2f", v.Ratio)
+		}
+		res.DataList.MixedFodderCorrectRatio = append(res.DataList.MixedFodderCorrectRatio, mcMap)
+
+		saMap := map[string]string{
+			"title": groupPasture.Name,
+		}
+		for i, v := range data.SprinkleFodderAccurateRatio {
+			saMap[fmt.Sprintf("data%d", i+1)] = fmt.Sprintf("%.2f", v.Ratio)
+		}
+		res.DataList.SprinkleFodderAccurateRatio = append(res.DataList.SprinkleFodderAccurateRatio, saMap)
+
+		scMap := map[string]string{
+			"title": groupPasture.Name,
+		}
+		for i, v := range data.SprinkleFodderCorrectRatio {
+			scMap[fmt.Sprintf("data%d", i+1)] = fmt.Sprintf("%.2f", v.Ratio)
+		}
+		res.DataList.SprinkleFodderCorrectRatio = append(res.DataList.SprinkleFodderCorrectRatio, scMap)
+	}
+
+	return res
+}
+
+func (s *StoreEntry) ExecutionTime(ctx context.Context, req *operationPb.SearchAnalysisAccuracyRequest) (*model.ExecTimeResponse, error) {
+	res := &model.ExecTimeResponse{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &model.ExecTimeDataList{
+			Chart: &model.ExecTimeDataListChart{
+				Title:        make([]string, 0),
+				AddFeedTime:  make([][]string, 0),
+				SprinkleTime: make([][]string, 0),
+				StirTime:     make([][]string, 0),
+			},
+			TableList: make([]map[string]string, 0),
+		},
+	}
+
+	pastureExecTime, err := s.PasturePrefExecTimeData(ctx, req)
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	for pastureName, execTime := range pastureExecTime {
+		res.Data.Chart.Title = append(res.Data.Chart.Title, pastureName)
+		addFeedTimeStr, sprinkleTimeStr, stirTimeStr := make([]string, 0), make([]string, 0), make([]string, 0)
+		if execTime != nil {
+			addFeedTimeStr = append(addFeedTimeStr, execTime.AddFeedTime.MaxValue, execTime.AddFeedTime.UpMiddleValue,
+				execTime.AddFeedTime.MiddleValue, execTime.AddFeedTime.DownMiddleValue, execTime.AddFeedTime.MinValue)
+
+			sprinkleTimeStr = append(sprinkleTimeStr, execTime.SprinkleTime.MaxValue, execTime.SprinkleTime.UpMiddleValue,
+				execTime.SprinkleTime.MiddleValue, execTime.SprinkleTime.DownMiddleValue, execTime.SprinkleTime.MinValue)
+
+			stirTimeStr = append(stirTimeStr, execTime.StirTime.MaxValue, execTime.StirTime.UpMiddleValue,
+				execTime.StirTime.MiddleValue, execTime.StirTime.DownMiddleValue, execTime.StirTime.MinValue)
+		}
+
+		res.Data.Chart.AddFeedTime = append(res.Data.Chart.AddFeedTime, addFeedTimeStr)
+		res.Data.Chart.SprinkleTime = append(res.Data.Chart.SprinkleTime, sprinkleTimeStr)
+		res.Data.Chart.StirTime = append(res.Data.Chart.StirTime, stirTimeStr)
+		if execTime == nil {
+			continue
+		}
+		tableList := map[string]string{
+			"title":                           pastureName,
+			"add_feed_time":                   "加料时间",
+			"add_feed_time_max_value":         execTime.AddFeedTime.MaxValue,
+			"add_feed_time_up_middle_value":   execTime.AddFeedTime.UpMiddleValue,
+			"add_feed_time_middle_value":      execTime.AddFeedTime.MiddleValue,
+			"add_feed_time_down_middle_value": execTime.AddFeedTime.DownMiddleValue,
+			"add_feed_time_min_value":         execTime.AddFeedTime.MinValue,
+			"sprinkle_time":                   "撒料时间",
+			"sprinkle_time_max_value":         execTime.SprinkleTime.MaxValue,
+			"sprinkle_time_up_middle_value":   execTime.SprinkleTime.UpMiddleValue,
+			"sprinkle_time_middle_value":      execTime.SprinkleTime.MiddleValue,
+			"sprinkle_time_down_middle_value": execTime.SprinkleTime.DownMiddleValue,
+			"sprinkle_time_min_value":         execTime.SprinkleTime.MinValue,
+			"stir_time":                       "搅拌延迟时间",
+			"stir_time_max_value":             execTime.StirTime.MaxValue,
+			"stir_time_up_middle_value":       execTime.StirTime.UpMiddleValue,
+			"stir_time_middle_value":          execTime.StirTime.MiddleValue,
+			"stir_time_down_middle_value":     execTime.StirTime.DownMiddleValue,
+			"stir_time_min_value":             execTime.StirTime.MinValue,
+		}
+
+		res.Data.TableList = append(res.Data.TableList, tableList)
+	}
+
+	return res, nil
+}
+
+func (s *StoreEntry) SprinkleFeedTime(ctx context.Context, req *operationPb.SprinkleFeedTimeRequest) (*model.SprinkleFeedTimeResponse, error) {
+	res := &model.SprinkleFeedTimeResponse{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &model.SprinkleFeedTimeData{
+			Chart: &model.SprinkleFeedTimeChart{
+				Title:              make([]string, 0),
+				SprinkleNumberList: make([][]int32, 0),
+			},
+			TableList: make([]*model.SprinkleFeedTimeTable, 0),
+		},
+	}
+	pastureSprinkleDataList, err := s.PastureSprinkleFeedTime(ctx, req)
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	tableList := make([]*model.SprinkleFeedTimeTable, 0)
+	infoSprinkleNumber, errorSprinkleNumber := make([]int32, 0), make([]int32, 0)
+	for pastureName, data := range pastureSprinkleDataList {
+		sprinkleFeedTimeList := make(map[int32]map[int32][]int64, 0)
+		for _, v := range data {
+			tableList = append(tableList, &model.SprinkleFeedTimeTable{
+				PastureName:             pastureName,
+				BarnName:                v.FName,
+				ClassNumber:             fmt.Sprintf("%d", v.Times),
+				RealitySprinkleFeedTime: tool.TimeSub(v.InTime, v.ProcessTime),
+			})
+			realityTime := tool.TimeSub(v.InTime, v.ProcessTime)
+			realityTimeUnix, _ := time.Parse(model.LayoutTime, realityTime)
+			if sprinkleFeedTimeList[v.FBarId] == nil {
+				sprinkleFeedTimeList[v.FBarId] = make(map[int32][]int64, 0)
+			}
+			sprinkleFeedTimeList[v.FBarId][v.Times] = append(sprinkleFeedTimeList[v.FBarId][v.Times], realityTimeUnix.Unix())
+		}
+		res.Data.Chart.Title = append(res.Data.Chart.Title, pastureName)
+
+		infoNumber, errNumber := sprinkleExecTimeAnalysis(sprinkleFeedTimeList)
+		infoSprinkleNumber = append(infoSprinkleNumber, infoNumber)
+		errorSprinkleNumber = append(errorSprinkleNumber, errNumber)
+	}
+	res.Data.Chart.SprinkleNumberList = append(res.Data.Chart.SprinkleNumberList, infoSprinkleNumber, errorSprinkleNumber)
+	res.Data.TableList = tableList
+	return res, nil
+}
+
+func sprinkleExecTimeAnalysis(sprinkleFeedTimeList map[int32]map[int32][]int64) (int32, int32) {
+	var infoSprinkleNumber, errorSprinkleNumber int32 = 0, 0
+	if len(sprinkleFeedTimeList) <= 0 {
+		return infoSprinkleNumber, errorSprinkleNumber
+	} else {
+		for _, value := range sprinkleFeedTimeList {
+			for _, execTimeList := range value {
+				middleValue := tool.MedianInt64(execTimeList)
+				for _, v := range execTimeList {
+					if v >= middleValue-int64(compareTime) && v <= middleValue+int64(compareTime) {
+						infoSprinkleNumber += 1
+					} else {
+						errorSprinkleNumber += 1
+					}
+				}
+			}
+		}
+	}
+
+	return infoSprinkleNumber, errorSprinkleNumber
+}

+ 196 - 11
module/backend/feed_service.go

@@ -3,6 +3,7 @@ package backend
 import (
 	"bytes"
 	"context"
+	"encoding/json"
 	"errors"
 	"fmt"
 	"io"
@@ -10,13 +11,25 @@ import (
 	"kpt-tmr-group/pkg/logger/zaplog"
 	"kpt-tmr-group/pkg/xerr"
 	operationPb "kpt-tmr-group/proto/go/backend/operation"
+	"net/http"
+	"strconv"
+	"sync"
+	"time"
+
+	"go.uber.org/multierr"
 
 	"github.com/xuri/excelize/v2"
 	"go.uber.org/zap"
-
 	"gorm.io/gorm"
 )
 
+const EncodeNumberPrefix = "encode_number"
+
+var PastureDataLogType = map[string]int32{
+	"FeedFormula_Distribute": 1,
+	"FeedFormula_IsModify":   2,
+}
+
 // CreateFeedFormula 添加数据
 func (s *StoreEntry) CreateFeedFormula(ctx context.Context, req *operationPb.AddFeedFormulaRequest) error {
 	forage := model.NewFeedFormula(req)
@@ -30,7 +43,7 @@ func (s *StoreEntry) CreateFeedFormula(ctx context.Context, req *operationPb.Add
 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 err := s.DB.Where("is_delete = ?", operationPb.IsShow_OK).First(&forage).Error; err != nil {
 		if errors.Is(err, gorm.ErrRecordNotFound) {
 			return xerr.Custom("该数据不存在")
 		}
@@ -50,7 +63,7 @@ func (s *StoreEntry) EditFeedFormula(ctx context.Context, req *operationPb.AddFe
 		IsShow:             req.IsShow,
 	}
 
-	if err := s.DB.Model(new(model.Forage)).
+	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 {
@@ -65,7 +78,7 @@ func (s *StoreEntry) SearchFeedFormulaList(ctx context.Context, req *operationPb
 	feedFormula := make([]*model.FeedFormula, 0)
 	var count int64 = 0
 
-	pref := s.DB.Model(new(model.Forage)).Where("is_delete = ?", operationPb.IsShow_OK)
+	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, "%"))
 	}
@@ -96,10 +109,14 @@ func (s *StoreEntry) SearchFeedFormulaList(ctx context.Context, req *operationPb
 	}
 
 	return &operationPb.SearchFeedFormulaListResponse{
-		Page:     req.Pagination.Page,
-		PageSize: req.Pagination.PageSize,
-		Total:    int32(count),
-		List:     model.FeedFormulaSlice(feedFormula).ToPB(),
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.SearchFeedFormulaListData{
+			Page:     req.Pagination.Page,
+			PageSize: req.Pagination.PageSize,
+			Total:    int32(count),
+			List:     model.FeedFormulaSlice(feedFormula).ToPB(),
+		},
 	}, nil
 }
 
@@ -122,6 +139,8 @@ func (s *StoreEntry) IsShowFeedFormula(ctx context.Context, req *operationPb.IsS
 	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)
+		} else {
+			s.PastureFeedFormulaIsModify(ctx, req.FeedFormulaId, req.IsShow)
 		}
 	}
 	return nil
@@ -137,7 +156,7 @@ func (s *StoreEntry) DeleteFeedFormula(ctx context.Context, feedFormulaId int64)
 		return xerr.WithStack(err)
 	}
 
-	if err := s.DB.Model(new(model.FeedFormula)).Where("id = ?", feedFormula).Update("is_delete", operationPb.IsShow_NO).Error; err != nil {
+	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
@@ -228,7 +247,7 @@ func (s *StoreEntry) ExcelExportFeedFormula(ctx context.Context, req *operationP
 	if err != nil {
 		return nil, xerr.WithStack(err)
 	}
-	if len(res.List) <= 0 {
+	if len(res.Data.List) <= 0 {
 		return nil, xerr.Custom("数据为空")
 	}
 
@@ -244,7 +263,7 @@ func (s *StoreEntry) ExcelExportFeedFormula(ctx context.Context, req *operationP
 	if err = streamWriter.SetRow("A1", titles); err != nil {
 		return nil, xerr.WithStack(err)
 	}
-	for i, item := range res.List {
+	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))
@@ -286,3 +305,169 @@ func (s *StoreEntry) ExcelTemplateFeedFormula(ctx context.Context) (*bytes.Buffe
 
 	return file.WriteToBuffer()
 }
+
+// EncodeNumber 配方编码
+func (s *StoreEntry) EncodeNumber(ctx context.Context) string {
+	currTime := time.Now().Format(model.LayoutDate)
+	prefix := fmt.Sprintf("%s_%s", EncodeNumberPrefix, currTime)
+	data := &model.UniqueData{}
+	if err := s.DB.Order("id desc").Where("prefix = ?", prefix).First(data).Error; err != nil {
+		if !errors.Is(err, gorm.ErrRecordNotFound) {
+			return ""
+		}
+		ud, _ := strconv.Atoi(currTime)
+		result := ud*100 + 1
+		newData := &model.UniqueData{
+			Prefix: prefix,
+			Data:   int64(result),
+		}
+		if err = s.DB.Create(newData).Error; err != nil {
+			zaplog.Error("EncodeNumber Create", zap.Any("data", newData), zap.Any("Err", err))
+			return ""
+		}
+		return fmt.Sprintf("%d", newData.Data)
+	}
+
+	data.Data += 1
+	if err := s.DB.Model(new(model.UniqueData)).Where("prefix = ?", prefix).Update("data", data.Data).Error; err != nil {
+		return ""
+	} else {
+		return fmt.Sprintf("%d", data.Data)
+	}
+}
+
+// DistributeFeedFormula 配方下发牧场
+func (s *StoreEntry) DistributeFeedFormula(ctx context.Context, req *operationPb.DistributeFeedFormulaRequest) error {
+	distributeData, err := s.checkoutDistributeData(ctx, req)
+	if err != nil {
+		return xerr.WithStack(err)
+	}
+	if len(distributeData.PastureList) <= 0 {
+		return nil
+	}
+
+	wg := sync.WaitGroup{}
+	wg.Add(len(distributeData.PastureList))
+	var muError error
+
+	for _, pasture := range distributeData.PastureList {
+		go func(p *model.GroupPasture) {
+			// 过滤已下发的
+			body := make([]*model.FeedFormula, 0)
+			for _, v := range distributeData.FeedFormulaList {
+				if ok := s.checkoutDistributeLog(ctx, p.Id, v.Id); !ok {
+					body = append(body, v)
+				}
+			}
+			if len(body) <= 0 {
+				return
+			}
+
+			request := &model.DistributeFeedFormulaRequest{
+				PastureId: p.Id,
+				Body:      body,
+			}
+			response := &model.PastureResponse{}
+			defer func() {
+				if response.Code == http.StatusOK {
+					s.DB.Create(model.NewFeedFormulaDistributeLogList(distributeData.FeedFormulaList, p.Id, p.Name, operationPb.IsShow_OK))
+				} else {
+					muError = multierr.Append(muError, xerr.Custom(response.Msg))
+				}
+				wg.Done()
+			}()
+
+			if _, err = s.PastureHttpClient(ctx, model.FeedFormulaDistributeUrl, p.Id, request, response); err != nil {
+				muError = multierr.Append(muError, err)
+				zaplog.Error("DistributeFeedFormula", zap.Any("pasture", p), zap.Any("body", distributeData.FeedFormulaList), zap.Any("err", err), zap.Any("response", response))
+				b, _ := json.Marshal(request)
+				res, _ := json.Marshal(response)
+				pastureDataLog := model.NewPastureDataLog(p.Id, PastureDataLogType["FeedFormula_Distribute"], model.FeedFormulaDistributeUrl, string(b), string(res))
+				s.DB.Create(pastureDataLog)
+			}
+
+		}(pasture)
+	}
+	wg.Wait()
+	return muError
+}
+
+// FeedFormulaUsage 配方使用概况
+func (s *StoreEntry) FeedFormulaUsage(ctx context.Context, req *operationPb.FeedFormulaUsageRequest) error {
+	feedFormulaDistributeLogList := make([]*model.FeedFormulaDistributeLog, 0)
+	if err := s.DB.Model(new(model.FeedFormulaDistributeLog)).
+		Where("feed_formula_id = ?", req.FeedFormulaId).
+		Where("is_show = ?", operationPb.IsShow_OK).
+		Find(&feedFormulaDistributeLogList).Error; err != nil {
+		return xerr.WithStack(err)
+	}
+
+	wg := sync.WaitGroup{}
+	wg.Add(len(feedFormulaDistributeLogList))
+
+	for _, list := range feedFormulaDistributeLogList {
+		go func(l *model.FeedFormulaDistributeLog) {
+			defer wg.Done()
+		}(list)
+	}
+
+	wg.Wait()
+	return nil
+}
+
+func (s *StoreEntry) PastureFeedFormulaIsModify(ctx context.Context, feedFormulaId int32, isModify operationPb.IsShow_Kind) {
+	feedFormulaDistributeLogList := make([]*model.FeedFormulaDistributeLog, 0)
+	if err := s.DB.Where("is_show = ?", operationPb.IsShow_OK).
+		Where("feed_formula_id = ?", feedFormulaId).
+		Group("pasture_id").Find(&feedFormulaDistributeLogList).Error; err != nil {
+		zaplog.Error("PastureFeedFormulaIsModify", zap.Any("err", err), zap.Any("feed_formula_id", feedFormulaId))
+		return
+	}
+	for _, v := range feedFormulaDistributeLogList {
+		response := &model.PastureResponse{}
+		request := &model.FeedFormulaIsModifyRequest{
+			PastureId:     v.PastureId,
+			FeedFormulaId: v.FeedFormulaId,
+			IsModify:      int32(isModify),
+		}
+		if _, err := s.PastureHttpClient(ctx, model.FeedFormulaIsModifyUrl, v.Id, request, response); err != nil {
+			zaplog.Error("PastureFeedFormulaIsModify", zap.Any("request", request), zap.Any("err", err), zap.Any("response", response))
+			b, _ := json.Marshal(request)
+			res, _ := json.Marshal(response)
+			pastureDataLog := model.NewPastureDataLog(v.PastureId, PastureDataLogType["FeedFormula_IsModify"], model.FeedFormulaIsModifyUrl, string(b), string(res))
+			s.DB.Create(pastureDataLog)
+		}
+	}
+}
+func (s *StoreEntry) checkoutDistributeData(ctx context.Context, req *operationPb.DistributeFeedFormulaRequest) (*model.DistributeData, error) {
+	result := &model.DistributeData{
+		PastureList:     make([]*model.GroupPasture, 0),
+		FeedFormulaList: make([]*model.FeedFormula, 0),
+	}
+
+	if err := s.DB.Where("id IN ?", req.PastureIds).Where("is_delete = ?", operationPb.IsShow_OK).Find(&result.PastureList).Error; err != nil {
+		return result, xerr.WithStack(err)
+	}
+
+	if err := s.DB.Where("id IN ?", req.FeedFormulaIds).Find(&result.FeedFormulaList).Error; err != nil {
+		return result, xerr.WithStack(err)
+	}
+
+	if len(result.PastureList) <= 0 || len(result.FeedFormulaList) <= 0 {
+		return result, xerr.Customf("数据错误")
+	}
+
+	return result, nil
+}
+
+func (s *StoreEntry) checkoutDistributeLog(ctx context.Context, pastureId, feedFormulaId int64) bool {
+	res := &model.FeedFormulaDistributeLog{}
+	if err := s.DB.Model(new(model.FeedFormulaDistributeLog)).Where("feed_formula_id = ?", feedFormulaId).
+		Where("pasture_id = ?", pastureId).Where("is_show = ?", operationPb.IsShow_OK).First(res).Error; err != nil {
+		return false
+	}
+	if res.IsShow == operationPb.IsShow_OK {
+		return true
+	}
+	return false
+}

+ 54 - 17
module/backend/interface.go

@@ -5,6 +5,7 @@ import (
 	"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"
@@ -23,23 +24,30 @@ type Hub struct {
 type StoreEntry struct {
 	dig.In
 
-	Cfg *config.AppConfig
-	DB  *kptstore.DB
-	//SSO *sso.Cache
-	// AsynqClient asynqsvc.Client
-	// Cache *redis.Client
-	WxClient *wechat.ClientService
+	Cfg        *config.AppConfig
+	DB         *kptstore.DB
+	HttpClient *wechat.ClientService
 }
 
 func NewStore(store StoreEntry) KptService {
 	return &store
 }
 
+func NewStoreEntry(cfg *config.AppConfig, Db *kptstore.DB) *StoreEntry {
+	return &StoreEntry{
+		Cfg:        cfg,
+		DB:         Db,
+		HttpClient: nil,
+	}
+}
+
+//go:generate mockgen -destination mock/kptservice.go -package kptservicemock kpt-tmr-group/module/backend KptService
 type KptService interface {
-	PastureService   // 牧场相关操作
-	SystemOperation  // 系统相关操作
-	WxAppletService  // 小程序相关
-	StatisticService // 统计分析
+	PastureService     // 牧场相关操作
+	SystemService      // 系统相关操作
+	WxAppletService    // 小程序相关
+	StatisticService   // 统计分析
+	PastureSyncService // 牧场端数据同步
 }
 
 type PastureService interface {
@@ -51,16 +59,14 @@ type PastureService interface {
 	ResetPasswordGroupPasture(ctx context.Context, pastureId int64) error
 	IsShowGroupPasture(ctx context.Context, req *operationPb.IsShowGroupPasture) error
 
-	// ParentCattleCategoryList 牧畜类别
-	ParentCattleCategoryList(ctx context.Context) map[operationPb.CattleCategoryParent_Kind]string
+	// AddCattleCategory 牧畜类别
 	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)
 
-	// ParentForageCategoryList 饲料类别相关
-	ParentForageCategoryList(ctx context.Context) map[operationPb.ForageCategoryParent_Kind]string
+	// AddForageCategory 饲料类别相关
 	AddForageCategory(ctx context.Context, req *operationPb.AddForageCategoryRequest) error
 	EditForageCategory(ctx context.Context, req *operationPb.AddForageCategoryRequest) error
 	IsShowForageCategory(ctx context.Context, req *operationPb.IsShowForageCategory) error
@@ -71,12 +77,14 @@ type PastureService interface {
 	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.ForageEnumList
+	ForageListSort(ctx context.Context, req *operationPb.ForageListSortRequest) 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)
+	SmallMaterial(ctx context.Context, req *operationPb.SmallMaterialRequest) (*model.PastureCommonResponse, error)
 
 	// CreateFeedFormula 饲料配方
 	CreateFeedFormula(ctx context.Context, req *operationPb.AddFeedFormulaRequest) error
@@ -87,9 +95,12 @@ type PastureService interface {
 	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)
+	EncodeNumber(ctx context.Context) string
+	DistributeFeedFormula(ctx context.Context, req *operationPb.DistributeFeedFormulaRequest) error
+	FeedFormulaUsage(ctx context.Context, req *operationPb.FeedFormulaUsageRequest) error
 }
 
-type SystemOperation interface {
+type SystemService interface {
 	// Auth 系统用户相关
 	Auth(ctx context.Context, auth *operationPb.UserAuthData) (*operationPb.SystemToken, error)
 	GetUserInfo(ctx context.Context, token string) (*operationPb.UserAuth, error)
@@ -121,9 +132,35 @@ type SystemOperation interface {
 }
 
 type StatisticService interface {
-	SearchFormulaEstimateList(ctx context.Context, req *operationPb.SearchFormulaEstimateRequest) (*operationPb.SearchFormulaEstimateResponse, error)
+	SearchFormulaEstimateList(ctx context.Context, req *operationPb.SearchFormulaEstimateRequest) (*model.PastureCommonResponse, error)
+	SearchInventoryStatistics(ctx context.Context, req *operationPb.SearchInventoryStatisticsRequest) (*model.PastureCommonResponse, error)
+	InventoryStatisticsExcelExport(ctx context.Context, req *operationPb.SearchInventoryStatisticsRequest) (*bytes.Buffer, error)
+
+	SearchUserMaterialsStatistics(ctx context.Context, req *operationPb.SearchUserMaterialsStatisticsRequest) (*model.PastureCommonResponse, error)
+	UserMaterialsStatisticsExcelExport(ctx context.Context, req *operationPb.SearchUserMaterialsStatisticsRequest) (*bytes.Buffer, error)
+	SearchPriceStatistics(ctx context.Context, req *operationPb.SearchPriceStatisticsRequest) (*model.PastureCommonResponse, error)
+	SearchFeedStatistics(ctx context.Context, req *operationPb.SearchFeedStatisticsRequest) (*model.FeedStatisticsResponse, error)
+	FeedChartStatistics(ctx context.Context, req *operationPb.FeedChartStatisticsRequest) (*model.PastureCommonResponse, error)
+	CowsAnalysis(ctx context.Context, req *operationPb.CowsAnalysisRequest) (*model.PastureCommonResponse, error)
+
+	SearchAccuracyAggStatistics(ctx context.Context, req *operationPb.AccuracyAggStatisticsRequest) (*model.PastureCommonResponse, error)
+	SearchMixFeedStatistics(ctx context.Context, req *operationPb.MixFeedStatisticsRequest) (*model.PastureCommonResponse, error)
+	SearchSprinkleStatistics(ctx context.Context, req *operationPb.SprinkleStatisticsRequest) (*model.PastureCommonResponse, error)
+	SearchProcessAnalysis(ctx context.Context, req *operationPb.ProcessAnalysisRequest) (*model.PastureCommonResponse, error)
+	GetDataByName(ctx context.Context, req *operationPb.GetDataByNameRequest) (*model.PastureCommonResponse, error)
+
+	GetTrainNumber(ctx context.Context, req *operationPb.TrainNumberRequest) (*operationPb.TrainNumberResponse, error)
+	SearchAnalysisAccuracy(ctx context.Context, req *operationPb.SearchAnalysisAccuracyRequest) (*model.SearchAnalysisAccuracyResponse, error)
+	TopPasture(ctx context.Context, req *operationPb.SearchAnalysisAccuracyRequest) (*model.GetPastureTopResponse, error)
+	ExecutionTime(ctx context.Context, req *operationPb.SearchAnalysisAccuracyRequest) (*model.ExecTimeResponse, error)
+	SprinkleFeedTime(ctx context.Context, req *operationPb.SprinkleFeedTimeRequest) (*model.SprinkleFeedTimeResponse, error)
 }
 
 type WxAppletService interface {
 	GetOpenId(ctx context.Context, jsCode string) (*operationPb.WxOpenId, error)
 }
+
+type PastureSyncService interface {
+	CategorySyncData(ctx context.Context, req *operationPb.CategorySyncRequest) error
+	CategoryDeleteData(ctx context.Context, req *operationPb.CategoryDeleteRequest) error
+}

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

@@ -0,0 +1,1139 @@
+// 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"
+	model "kpt-tmr-group/model"
+	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)
+}
+
+// CowsAnalysis mocks base method.
+func (m *MockKptService) CowsAnalysis(arg0 context.Context, arg1 *operationPb.CowsAnalysisRequest) (*model.PastureCommonResponse, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "CowsAnalysis", arg0, arg1)
+	ret0, _ := ret[0].(*model.PastureCommonResponse)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// CowsAnalysis indicates an expected call of CowsAnalysis.
+func (mr *MockKptServiceMockRecorder) CowsAnalysis(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CowsAnalysis", reflect.TypeOf((*MockKptService)(nil).CowsAnalysis), 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)
+}
+
+// DistributeFeedFormula mocks base method.
+func (m *MockKptService) DistributeFeedFormula(arg0 context.Context, arg1 *operationPb.DistributeFeedFormulaRequest) error {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "DistributeFeedFormula", arg0, arg1)
+	ret0, _ := ret[0].(error)
+	return ret0
+}
+
+// DistributeFeedFormula indicates an expected call of DistributeFeedFormula.
+func (mr *MockKptServiceMockRecorder) DistributeFeedFormula(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DistributeFeedFormula", reflect.TypeOf((*MockKptService)(nil).DistributeFeedFormula), 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)
+}
+
+// EncodeNumber mocks base method.
+func (m *MockKptService) EncodeNumber(arg0 context.Context) string {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "EncodeNumber", arg0)
+	ret0, _ := ret[0].(string)
+	return ret0
+}
+
+// EncodeNumber indicates an expected call of EncodeNumber.
+func (mr *MockKptServiceMockRecorder) EncodeNumber(arg0 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EncodeNumber", reflect.TypeOf((*MockKptService)(nil).EncodeNumber), arg0)
+}
+
+// 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)
+}
+
+// FeedChartStatistics mocks base method.
+func (m *MockKptService) FeedChartStatistics(arg0 context.Context, arg1 *operationPb.FeedChartStatisticsRequest) (*model.PastureCommonResponse, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "FeedChartStatistics", arg0, arg1)
+	ret0, _ := ret[0].(*model.PastureCommonResponse)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// FeedChartStatistics indicates an expected call of FeedChartStatistics.
+func (mr *MockKptServiceMockRecorder) FeedChartStatistics(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FeedChartStatistics", reflect.TypeOf((*MockKptService)(nil).FeedChartStatistics), arg0, arg1)
+}
+
+// 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)
+}
+
+// GetDataByName mocks base method.
+func (m *MockKptService) GetDataByName(arg0 context.Context, arg1 *operationPb.GetDataByNameRequest) (*model.PastureCommonResponse, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetDataByName", arg0, arg1)
+	ret0, _ := ret[0].(*model.PastureCommonResponse)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// GetDataByName indicates an expected call of GetDataByName.
+func (mr *MockKptServiceMockRecorder) GetDataByName(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDataByName", reflect.TypeOf((*MockKptService)(nil).GetDataByName), arg0, arg1)
+}
+
+// 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)
+}
+
+// GetTrainNumber mocks base method.
+func (m *MockKptService) GetTrainNumber(arg0 context.Context, arg1 *operationPb.TrainNumberRequest) (*operationPb.TrainNumberResponse, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "GetTrainNumber", arg0, arg1)
+	ret0, _ := ret[0].(*operationPb.TrainNumberResponse)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// GetTrainNumber indicates an expected call of GetTrainNumber.
+func (mr *MockKptServiceMockRecorder) GetTrainNumber(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTrainNumber", reflect.TypeOf((*MockKptService)(nil).GetTrainNumber), 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)
+}
+
+// InventoryStatisticsExcelExport mocks base method.
+func (m *MockKptService) InventoryStatisticsExcelExport(arg0 context.Context, arg1 *operationPb.SearchInventoryStatisticsRequest) (*bytes.Buffer, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "InventoryStatisticsExcelExport", arg0, arg1)
+	ret0, _ := ret[0].(*bytes.Buffer)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// InventoryStatisticsExcelExport indicates an expected call of InventoryStatisticsExcelExport.
+func (mr *MockKptServiceMockRecorder) InventoryStatisticsExcelExport(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InventoryStatisticsExcelExport", reflect.TypeOf((*MockKptService)(nil).InventoryStatisticsExcelExport), 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)
+}
+
+// SearchAccuracyAggStatistics mocks base method.
+func (m *MockKptService) SearchAccuracyAggStatistics(arg0 context.Context, arg1 *operationPb.AccuracyAggStatisticsRequest) (*model.PastureCommonResponse, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "SearchAccuracyAggStatistics", arg0, arg1)
+	ret0, _ := ret[0].(*model.PastureCommonResponse)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// SearchAccuracyAggStatistics indicates an expected call of SearchAccuracyAggStatistics.
+func (mr *MockKptServiceMockRecorder) SearchAccuracyAggStatistics(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SearchAccuracyAggStatistics", reflect.TypeOf((*MockKptService)(nil).SearchAccuracyAggStatistics), arg0, arg1)
+}
+
+// SearchAnalysisAccuracy mocks base method.
+func (m *MockKptService) SearchAnalysisAccuracy(arg0 context.Context, arg1 *operationPb.SearchAnalysisAccuracyRequest) (*model.SearchAnalysisAccuracyResponse, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "SearchAnalysisAccuracy", arg0, arg1)
+	ret0, _ := ret[0].(*model.SearchAnalysisAccuracyResponse)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// SearchAnalysisAccuracy indicates an expected call of SearchAnalysisAccuracy.
+func (mr *MockKptServiceMockRecorder) SearchAnalysisAccuracy(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SearchAnalysisAccuracy", reflect.TypeOf((*MockKptService)(nil).SearchAnalysisAccuracy), 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)
+}
+
+// SearchFeedStatistics mocks base method.
+func (m *MockKptService) SearchFeedStatistics(arg0 context.Context, arg1 *operationPb.SearchFeedStatisticsRequest) (*model.PastureCommonResponse, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "SearchFeedStatistics", arg0, arg1)
+	ret0, _ := ret[0].(*model.PastureCommonResponse)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// SearchFeedStatistics indicates an expected call of SearchFeedStatistics.
+func (mr *MockKptServiceMockRecorder) SearchFeedStatistics(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SearchFeedStatistics", reflect.TypeOf((*MockKptService)(nil).SearchFeedStatistics), 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) (*model.PastureCommonResponse, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "SearchFormulaEstimateList", arg0, arg1)
+	ret0, _ := ret[0].(*model.PastureCommonResponse)
+	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)
+}
+
+// SearchInventoryStatistics mocks base method.
+func (m *MockKptService) SearchInventoryStatistics(arg0 context.Context, arg1 *operationPb.SearchInventoryStatisticsRequest) (*model.PastureCommonResponse, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "SearchInventoryStatistics", arg0, arg1)
+	ret0, _ := ret[0].(*model.PastureCommonResponse)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// SearchInventoryStatistics indicates an expected call of SearchInventoryStatistics.
+func (mr *MockKptServiceMockRecorder) SearchInventoryStatistics(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SearchInventoryStatistics", reflect.TypeOf((*MockKptService)(nil).SearchInventoryStatistics), arg0, arg1)
+}
+
+// SearchMixFeedStatistics mocks base method.
+func (m *MockKptService) SearchMixFeedStatistics(arg0 context.Context, arg1 *operationPb.MixFeedStatisticsRequest) (*model.PastureCommonResponse, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "SearchMixFeedStatistics", arg0, arg1)
+	ret0, _ := ret[0].(*model.PastureCommonResponse)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// SearchMixFeedStatistics indicates an expected call of SearchMixFeedStatistics.
+func (mr *MockKptServiceMockRecorder) SearchMixFeedStatistics(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SearchMixFeedStatistics", reflect.TypeOf((*MockKptService)(nil).SearchMixFeedStatistics), 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)
+}
+
+// SearchPriceStatistics mocks base method.
+func (m *MockKptService) SearchPriceStatistics(arg0 context.Context, arg1 *operationPb.SearchPriceStatisticsRequest) (*model.PastureCommonResponse, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "SearchPriceStatistics", arg0, arg1)
+	ret0, _ := ret[0].(*model.PastureCommonResponse)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// SearchPriceStatistics indicates an expected call of SearchPriceStatistics.
+func (mr *MockKptServiceMockRecorder) SearchPriceStatistics(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SearchPriceStatistics", reflect.TypeOf((*MockKptService)(nil).SearchPriceStatistics), arg0, arg1)
+}
+
+// SearchProcessAnalysis mocks base method.
+func (m *MockKptService) SearchProcessAnalysis(arg0 context.Context, arg1 *operationPb.ProcessAnalysisRequest) (*model.PastureCommonResponse, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "SearchProcessAnalysis", arg0, arg1)
+	ret0, _ := ret[0].(*model.PastureCommonResponse)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// SearchProcessAnalysis indicates an expected call of SearchProcessAnalysis.
+func (mr *MockKptServiceMockRecorder) SearchProcessAnalysis(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SearchProcessAnalysis", reflect.TypeOf((*MockKptService)(nil).SearchProcessAnalysis), arg0, arg1)
+}
+
+// SearchSprinkleStatistics mocks base method.
+func (m *MockKptService) SearchSprinkleStatistics(arg0 context.Context, arg1 *operationPb.SprinkleStatisticsRequest) (*model.PastureCommonResponse, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "SearchSprinkleStatistics", arg0, arg1)
+	ret0, _ := ret[0].(*model.PastureCommonResponse)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// SearchSprinkleStatistics indicates an expected call of SearchSprinkleStatistics.
+func (mr *MockKptServiceMockRecorder) SearchSprinkleStatistics(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SearchSprinkleStatistics", reflect.TypeOf((*MockKptService)(nil).SearchSprinkleStatistics), 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)
+}
+
+// SearchUserMaterialsStatistics mocks base method.
+func (m *MockKptService) SearchUserMaterialsStatistics(arg0 context.Context, arg1 *operationPb.SearchUserMaterialsStatisticsRequest) (*model.PastureCommonResponse, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "SearchUserMaterialsStatistics", arg0, arg1)
+	ret0, _ := ret[0].(*model.PastureCommonResponse)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// SearchUserMaterialsStatistics indicates an expected call of SearchUserMaterialsStatistics.
+func (mr *MockKptServiceMockRecorder) SearchUserMaterialsStatistics(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SearchUserMaterialsStatistics", reflect.TypeOf((*MockKptService)(nil).SearchUserMaterialsStatistics), arg0, arg1)
+}
+
+// TopPasture mocks base method.
+func (m *MockKptService) TopPasture(arg0 context.Context, arg1 *operationPb.SearchAnalysisAccuracyRequest) (*model.GetPastureTopResponse, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "TopPasture", arg0, arg1)
+	ret0, _ := ret[0].(*model.GetPastureTopResponse)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// TopPasture indicates an expected call of TopPasture.
+func (mr *MockKptServiceMockRecorder) TopPasture(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TopPasture", reflect.TypeOf((*MockKptService)(nil).TopPasture), arg0, arg1)
+}
+
+// UserMaterialsStatisticsExcelExport mocks base method.
+func (m *MockKptService) UserMaterialsStatisticsExcelExport(arg0 context.Context, arg1 *operationPb.SearchUserMaterialsStatisticsRequest) (*bytes.Buffer, error) {
+	m.ctrl.T.Helper()
+	ret := m.ctrl.Call(m, "UserMaterialsStatisticsExcelExport", arg0, arg1)
+	ret0, _ := ret[0].(*bytes.Buffer)
+	ret1, _ := ret[1].(error)
+	return ret0, ret1
+}
+
+// UserMaterialsStatisticsExcelExport indicates an expected call of UserMaterialsStatisticsExcelExport.
+func (mr *MockKptServiceMockRecorder) UserMaterialsStatisticsExcelExport(arg0, arg1 interface{}) *gomock.Call {
+	mr.mock.ctrl.T.Helper()
+	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UserMaterialsStatisticsExcelExport", reflect.TypeOf((*MockKptService)(nil).UserMaterialsStatisticsExcelExport), arg0, arg1)
+}

+ 501 - 85
module/backend/pasture_service.go

@@ -13,6 +13,7 @@ import (
 	operationPb "kpt-tmr-group/proto/go/backend/operation"
 	"net/http"
 	"strconv"
+	"sync"
 
 	"go.uber.org/zap"
 
@@ -21,51 +22,74 @@ import (
 	"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 {
+	// 账号同步牧场端
+	groupPasture := model.NewGroupPasture(req)
+	if err := s.DB.Create(groupPasture).Error; err != nil {
+		return xerr.WithStack(err)
+	}
+	if err := s.PastureAccountDistribution(ctx, groupPasture); err != nil {
+		s.DB.Model(new(model.GroupPasture)).Where("id = ?", groupPasture.Id).Update("is_delete", operationPb.IsShow_NO)
+		return xerr.WithStack(err)
+	} else {
+		s.DB.Model(new(model.GroupPasture)).Where("id = ?", groupPasture.Id).Update("is_distribution", operationPb.IsShow_OK)
+	}
+	return nil
+}
+
+// PastureAccountDistribution 账号同步牧场端
+func (s *StoreEntry) PastureAccountDistribution(ctx context.Context, groupPasture *model.GroupPasture) error {
+	if groupPasture.Domain == "" {
+		return nil
+	}
+	body := &model.AccountDistribution{
+		Account:     groupPasture.Account,
+		UserName:    groupPasture.ManagerUser,
+		Password:    groupPasture.Password,
+		Phone:       groupPasture.ManagerPhone,
+		PastureId:   int32(groupPasture.Id),
+		PastureName: groupPasture.Name,
+		Address:     groupPasture.Address,
+	}
+	res := &model.PastureCommonResponse{}
+	if _, err := s.PastureHttpClient(ctx, model.PastureAccountDistributionURl, groupPasture.GetPastureId(), body, res); err != nil {
+		zaplog.Error("AccountDistribution", zap.Any("url", model.PastureAccountDistributionURl), zap.Any("err", err), zap.Any("body", body), zap.Any("res", res))
 		return xerr.WithStack(err)
 	}
+
+	if res.Code != http.StatusOK {
+		if err := s.DB.Model(new(model.GroupPasture)).Where("id = ?", groupPasture.Id).Update("is_distribution", operationPb.IsShow_OK).Error; err != nil {
+			zaplog.Error("AccountDistribution-Update", zap.Any("url", model.PastureAccountDistributionURl), zap.Any("err", err), zap.Any("body", body), zap.Any("res", res))
+			return xerr.Customf("%s", res.Msg)
+		}
+	}
 	return nil
 }
 
+func (s *StoreEntry) FindGroupPastureListByIds(ctx context.Context, pastureIds []int32) ([]*model.GroupPasture, error) {
+	groupPastureList := make([]*model.GroupPasture, 0)
+	if err := s.DB.Model(new(model.GroupPasture)).Where("is_delete = ? ", operationPb.IsShow_OK).
+		Where("id IN ?", pastureIds).
+		Find(&groupPastureList).Error; err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	return groupPastureList, nil
+}
+
+func (s *StoreEntry) GetGroupPastureListById(ctx context.Context, pastureId int64) (*model.GroupPasture, error) {
+	groupPasture := &model.GroupPasture{
+		Id: pastureId,
+	}
+	if err := s.DB.Model(new(model.GroupPasture)).Where("is_delete = ? ", operationPb.IsShow_OK).
+		First(&groupPasture).Error; err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	return groupPasture, nil
+}
+
 // EditGroupPasture 创建集团牧场
 func (s *StoreEntry) EditGroupPasture(ctx context.Context, req *operationPb.AddPastureRequest) error {
 	groupPasture := &model.GroupPasture{Id: int64(req.Id)}
@@ -82,8 +106,8 @@ func (s *StoreEntry) EditGroupPasture(ctx context.Context, req *operationPb.AddP
 		ManagerUser:  req.ManagerUser,
 		ManagerPhone: req.ManagerPhone,
 		Address:      req.Address,
+		Domain:       req.Domain,
 	}
-
 	if err := s.DB.Model(new(model.GroupPasture)).Omit("is_show", "password").
 		Where("id = ?", req.Id).
 		Updates(updateData).Error; err != nil {
@@ -114,7 +138,7 @@ func (s *StoreEntry) SearchGroupPastureList(ctx context.Context, req *operationP
 	}
 
 	if err := pref.Order("id desc").Count(&count).Limit(int(req.Pagination.PageSize)).Offset(int(req.Pagination.PageOffset)).
-		Find(&groupPasture).Debug().Error; err != nil {
+		Find(&groupPasture).Error; err != nil {
 		return nil, xerr.WithStack(err)
 	}
 
@@ -130,6 +154,19 @@ func (s *StoreEntry) SearchGroupPastureList(ctx context.Context, req *operationP
 	}, nil
 }
 
+// DataSyncGroupPastureList 用户数据下发的牧场
+func (s *StoreEntry) DataSyncGroupPastureList(ctx context.Context) ([]*model.GroupPasture, error) {
+	groupPastureList := make([]*model.GroupPasture, 0)
+	if err := s.DB.Model(new(model.GroupPasture)).Where("is_delete = ? ", operationPb.IsShow_OK).
+		Where("is_show = ?", operationPb.IsShow_OK).Where("domain != ''").
+		Where("is_distribution = ?", operationPb.IsShow_OK).
+		Find(&groupPastureList).Error; err != nil {
+		return nil, xerr.WithStack(err)
+	}
+	zaplog.Info("GroupPastureList", zap.Any("data", groupPastureList))
+	return groupPastureList, nil
+}
+
 func (s *StoreEntry) DeleteGroupPasture(ctx context.Context, pastureId int64) error {
 	groupPasture := &model.GroupPasture{
 		Id: pastureId,
@@ -181,16 +218,23 @@ func (s *StoreEntry) IsShowGroupPasture(ctx context.Context, req *operationPb.Is
 	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)
+	} else {
+		body := &model.CategoryRequest{
+			ParentId:   int32(req.ParentId),
+			ParentName: req.ParentName,
+			Name:       req.Name,
+			Number:     req.Number,
+			IsShow:     int32(req.IsShow),
+			GroupId:    int32(cattleCategory.Id),
+		}
+		if err = s.CategoryDistribution(ctx, model.CattleCategoryDistributionURl, body); err != nil {
+			zaplog.Error("AddCattleCategory", zap.Any("CategoryDistribution", err))
+		}
 	}
 	return nil
 }
@@ -215,6 +259,18 @@ func (s *StoreEntry) EditCattleCategory(ctx context.Context, req *operationPb.Ad
 		Where("id = ?", req.Id).
 		Updates(updateData).Error; err != nil {
 		return xerr.WithStack(err)
+	} else {
+		body := &model.CategoryRequest{
+			ParentId:   int32(req.ParentId),
+			ParentName: req.ParentName,
+			Name:       req.Name,
+			Number:     req.Number,
+			IsShow:     int32(req.IsShow),
+			GroupId:    int32(req.Id),
+		}
+		if err = s.CategoryDistribution(ctx, model.CattleCategoryDistributionURl, body); err != nil {
+			zaplog.Error("EditCattleCategory", zap.Any("CategoryDistribution", err))
+		}
 	}
 	return nil
 }
@@ -231,6 +287,18 @@ func (s *StoreEntry) IsShowCattleCategory(ctx context.Context, req *operationPb.
 
 	if err := s.DB.Model(new(model.CattleCategory)).Where("id = ?", req.CattleCategoryId).Update("is_show", req.IsShow).Error; err != nil {
 		return xerr.WithStack(err)
+	} else {
+		body := &model.CategoryRequest{
+			ParentId:   int32(cattleCategory.ParentId),
+			ParentName: cattleCategory.ParentName,
+			Name:       cattleCategory.Name,
+			Number:     cattleCategory.Number,
+			IsShow:     int32(req.IsShow),
+			GroupId:    int32(cattleCategory.Id),
+		}
+		if err = s.CategoryDistribution(ctx, model.CattleCategoryDistributionURl, body); err != nil {
+			zaplog.Error("IsShowCattleCategory", zap.Any("CategoryDistribution", err), zap.Any("body", body))
+		}
 	}
 	return nil
 }
@@ -247,6 +315,14 @@ func (s *StoreEntry) DeleteCattleCategory(ctx context.Context, cattleCategoryId
 
 	if err := s.DB.Model(new(model.CattleCategory)).Where("id = ?", cattleCategoryId).Update("is_delete", operationPb.IsShow_NO).Error; err != nil {
 		return xerr.WithStack(err)
+	} else {
+		body := &model.CategoryDeleteRequest{
+			GroupId:  int32(cattleCategory.Id),
+			IsDelete: int32(operationPb.IsShow_OK),
+		}
+		if err = s.CategoryDelete(ctx, model.CattleCategoryDeleteURl, body); err != nil {
+			zaplog.Error("DeleteCattleCategory", zap.Any("CategoryDelete", err), zap.Any("body", body))
+		}
 	}
 	return nil
 }
@@ -261,8 +337,8 @@ func (s *StoreEntry) SearchCattleCategoryList(ctx context.Context, req *operatio
 		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.ParentId > 0 {
+		pref.Where("parent_id = ?", req.ParentId)
 	}
 
 	if req.IsShow > 0 {
@@ -278,23 +354,31 @@ func (s *StoreEntry) SearchCattleCategoryList(ctx context.Context, req *operatio
 		Code: http.StatusOK,
 		Msg:  "ok",
 		Data: &operationPb.SearchCattleCategoryData{
-			Page:  req.Pagination.Page,
-			Total: int32(count),
-			List:  model.CattleCategorySlice(cattleCategory).ToPB(),
+			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)
+	} else {
+		body := &model.CategoryRequest{
+			ParentId:   int32(req.ParentId),
+			ParentName: req.ParentName,
+			Name:       req.Name,
+			Number:     req.Number,
+			IsShow:     int32(req.IsShow),
+			GroupId:    int32(forageCategory.Id),
+		}
+		if err = s.CategoryDistribution(ctx, model.ForageCategoryDistributionURl, body); err != nil {
+			zaplog.Error("AddForageCategory", zap.Any("CategoryDistribution", err), zap.Any("body", body))
+		}
 	}
 	return nil
 }
@@ -314,12 +398,24 @@ func (s *StoreEntry) EditForageCategory(ctx context.Context, req *operationPb.Ad
 		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)
+	} else {
+		body := &model.CategoryRequest{
+			ParentId:   int32(req.ParentId),
+			ParentName: req.ParentName,
+			Name:       req.Name,
+			Number:     req.Number,
+			IsShow:     int32(req.IsShow),
+			GroupId:    int32(req.Id),
+		}
+		if err = s.CategoryDistribution(ctx, model.ForageCategoryDistributionURl, body); err != nil {
+			zaplog.Error("EditForageCategory", zap.Any("CategoryDistribution", err), zap.Any("body", body))
+		}
 	}
+
 	return nil
 }
 
@@ -335,6 +431,18 @@ func (s *StoreEntry) IsShowForageCategory(ctx context.Context, req *operationPb.
 
 	if err := s.DB.Model(new(model.ForageCategory)).Where("id = ?", req.ForageCategoryId).Update("is_show", req.IsShow).Error; err != nil {
 		return xerr.WithStack(err)
+	} else {
+		body := &model.CategoryRequest{
+			ParentId:   int32(forageCategory.ParentId),
+			ParentName: forageCategory.ParentName,
+			Name:       forageCategory.Name,
+			Number:     forageCategory.Number,
+			IsShow:     int32(req.IsShow),
+			GroupId:    int32(forageCategory.Id),
+		}
+		if err = s.CategoryDistribution(ctx, model.ForageCategoryDistributionURl, body); err != nil {
+			zaplog.Error("IsShowForageCategory", zap.Any("CategoryDistribution", err), zap.Any("body", body))
+		}
 	}
 	return nil
 }
@@ -351,6 +459,14 @@ func (s *StoreEntry) DeleteForageCategory(ctx context.Context, forageCategoryId
 
 	if err := s.DB.Model(new(model.ForageCategory)).Where("id = ?", forageCategoryId).Update("is_delete", operationPb.IsShow_NO).Error; err != nil {
 		return xerr.WithStack(err)
+	} else {
+		body := &model.CategoryDeleteRequest{
+			GroupId:  int32(forageCategory.Id),
+			IsDelete: int32(operationPb.IsShow_OK),
+		}
+		if err = s.CategoryDelete(ctx, model.ForageCategoryDeleteURl, body); err != nil {
+			zaplog.Error("DeleteForageCategory", zap.Any("CategoryDelete", err), zap.Any("body", body))
+		}
 	}
 	return nil
 }
@@ -365,8 +481,12 @@ func (s *StoreEntry) SearchForageCategoryList(ctx context.Context, req *operatio
 		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.ParentId > 0 {
+		pref.Where("parent_id = ?", req.ParentId)
+	}
+
+	if req.Number != "" {
+		pref.Where("number like ?", fmt.Sprintf("%s%s%s", "%", req.Number, "%"))
 	}
 
 	if req.IsShow > 0 {
@@ -382,13 +502,86 @@ func (s *StoreEntry) SearchForageCategoryList(ctx context.Context, req *operatio
 		Code: http.StatusOK,
 		Msg:  "ok",
 		Data: &operationPb.SearchForageCategoryData{
-			Page:  req.Pagination.Page,
-			Total: int32(count),
-			List:  model.ForageCategorySlice(forageCategory).ToPB(),
+			Page:     req.Pagination.Page,
+			PageSize: req.Pagination.PageSize,
+			Total:    int32(count),
+			List:     model.ForageCategorySlice(forageCategory).ToPB(),
 		},
 	}, nil
 }
 
+// CategoryDistribution 饲料分类和畜牧分类下发
+func (s *StoreEntry) CategoryDistribution(ctx context.Context, url string, req *model.CategoryRequest) error {
+	groupList, err := s.DataSyncGroupPastureList(ctx)
+	if err != nil {
+		return xerr.WithStack(err)
+	}
+	wg := sync.WaitGroup{}
+	wg.Add(len(groupList))
+	for _, v := range groupList {
+		go func(data *model.GroupPasture) {
+			defer wg.Done()
+			res := &model.PastureCommonResponse{}
+			req.PastureId = int32(data.Id)
+			if _, err = s.PastureHttpClient(ctx, url, int64(data.Id), req, res); err != nil {
+				zaplog.Error("CategoryDistribution",
+					zap.Any("url", url),
+					zap.Any("err", err),
+					zap.Any("body", req),
+					zap.Any("res", res),
+				)
+			}
+
+			if res.Code != http.StatusOK {
+				zaplog.Error("CategoryDistribution-http",
+					zap.Any("url", url),
+					zap.Any("body", req),
+					zap.Any("res", res),
+				)
+			}
+
+		}(v)
+	}
+	wg.Wait()
+	return nil
+}
+
+// CategoryDelete 饲料分类和畜牧分类删除
+func (s *StoreEntry) CategoryDelete(ctx context.Context, url string, req *model.CategoryDeleteRequest) error {
+	groupList, err := s.DataSyncGroupPastureList(ctx)
+	if err != nil {
+		return xerr.WithStack(err)
+	}
+	wg := sync.WaitGroup{}
+	wg.Add(len(groupList))
+	for _, v := range groupList {
+		go func(data *model.GroupPasture) {
+			defer wg.Done()
+			res := &model.PastureCommonResponse{}
+			req.PastureId = int32(data.Id)
+			if _, err = s.PastureHttpClient(ctx, url, int64(data.Id), req, res); err != nil {
+				zaplog.Error("CategoryDelete",
+					zap.Any("url", url),
+					zap.Any("err", err),
+					zap.Any("body", req),
+					zap.Any("res", res),
+				)
+			}
+
+			if res.Code != http.StatusOK {
+				zaplog.Error("CategoryDelete-http",
+					zap.Any("url", url),
+					zap.Any("body", req),
+					zap.Any("res", res),
+				)
+			}
+
+		}(v)
+	}
+	wg.Wait()
+	return nil
+}
+
 // CreateForage 创建饲料
 func (s *StoreEntry) CreateForage(ctx context.Context, req *operationPb.AddForageRequest) error {
 	forage := model.NewForage(req)
@@ -401,7 +594,7 @@ func (s *StoreEntry) CreateForage(ctx context.Context, req *operationPb.AddForag
 // 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 err := s.DB.Where("is_delete = ?", operationPb.IsShow_OK).First(&forage).Error; err != nil {
 		if errors.Is(err, gorm.ErrRecordNotFound) {
 			return xerr.Custom("该数据不存在")
 		}
@@ -411,6 +604,8 @@ func (s *StoreEntry) EditForage(ctx context.Context, req *operationPb.AddForageR
 	updateData := &model.Forage{
 		Name:               req.Name,
 		CategoryId:         int64(req.CategoryId),
+		CategoryName:       req.CategoryName,
+		MaterialType:       req.MaterialType,
 		UniqueEncode:       req.UniqueEncode,
 		ForageSourceId:     req.ForageSourceId,
 		PlanTypeId:         req.PlanTypeId,
@@ -445,7 +640,7 @@ func (s *StoreEntry) SearchForageList(ctx context.Context, req *operationPb.Sear
 		pref.Where("name like ?", fmt.Sprintf("%s%s%s", "%", req.Name, "%"))
 	}
 
-	if req.CategoryId != "" {
+	if req.CategoryId > 0 {
 		pref.Where("category_id = ?", req.CategoryId)
 	}
 
@@ -461,10 +656,6 @@ func (s *StoreEntry) SearchForageList(ctx context.Context, req *operationPb.Sear
 		pref.Where("allow_error = ?", req.AllowError)
 	}
 
-	if req.AllowError > 0 {
-		pref.Where("allow_error = ?", req.AllowError)
-	}
-
 	if req.JumpWeight > 0 {
 		pref.Where("jump_weight = ?", req.JumpWeight)
 	}
@@ -473,30 +664,59 @@ func (s *StoreEntry) SearchForageList(ctx context.Context, req *operationPb.Sear
 		pref.Where("jump_delay = ?", req.JumpDelay)
 	}
 
-	if err := pref.Order("id desc").Count(&count).Limit(int(req.Pagination.PageSize)).Offset(int(req.Pagination.PageOffset)).
+	if err := pref.Order("sort").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{
-		Page:     req.Pagination.Page,
-		PageSize: req.Pagination.PageSize,
-		Total:    int32(count),
-		List:     model.ForageSlice(forage).ToPB(),
+		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.ForageEnumList {
-	res := &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),
+func (s *StoreEntry) ForageListSort(ctx context.Context, req *operationPb.ForageListSortRequest) error {
+
+	for _, v := range req.List {
+		if err := s.DB.Model(new(model.Forage)).Select("sort").Where("id = ?", v.Id).Updates(map[string]interface{}{
+			"sort": v.Sort,
+		}).Error; err != nil {
+			zaplog.Error("ForageListSort", zap.Any("err", err), zap.Any("id", v.Id), zap.Any("sort", v.Sort))
+		}
 	}
 
-	res.JumpDelaType = append(res.JumpDelaType, &operationPb.JumpDelaTypeEnum{
+	return 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),
+			FormulationEvaluation: make([]*operationPb.FormulaOptionEnum, 0),
+			UseMaterialsType:      make([]*operationPb.FormulaOptionEnum, 0),
+			UseMaterialsList:      make([]*operationPb.FormulaOptionEnum, 0),
+			PriceMaterialsType:    make([]*operationPb.FormulaOptionEnum, 0),
+		},
+	}
+
+	// 跳转延迟
+	res.Data.JumpDelaType = append(res.Data.JumpDelaType, &operationPb.JumpDelaTypeEnum{
 		Value: operationPb.JumpDelaType_INVALID,
 		Label: "禁用",
 	}, &operationPb.JumpDelaTypeEnum{
@@ -507,6 +727,186 @@ func (s *StoreEntry) ForageEnumList(ctx context.Context) *operationPb.ForageEnum
 		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_INVALID,
+			Label: "所有",
+		},
+		&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.FormulationEvaluation = append(res.Data.FormulationEvaluation, &operationPb.FormulaOptionEnum{
+		Value: 0,
+		Label: "按配方查询",
+	}, &operationPb.FormulaOptionEnum{
+		Value: 1,
+		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.UseMaterialsList = append(res.Data.UseMaterialsList, &operationPb.FormulaOptionEnum{
+		Value: 1,
+		Label: "理论",
+	}, &operationPb.FormulaOptionEnum{
+		Value: 2,
+		Label: "实际",
+	})
+
+	res.Data.PriceMaterialsType = append(res.Data.PriceMaterialsType, &operationPb.FormulaOptionEnum{
+		Value: 1,
+		Label: "畜牧类别",
+	}, &operationPb.FormulaOptionEnum{
+		Value: 2,
+		Label: "栏舍名称",
+	}, &operationPb.FormulaOptionEnum{
+		Value: 3,
+		Label: "日期",
+	}, &operationPb.FormulaOptionEnum{
+		Value: 4,
+		Label: "TMR设备号",
+	})
+
+	res.Data.UseMaterialsType = append(res.Data.PriceMaterialsType, &operationPb.FormulaOptionEnum{
+		Value: 5,
+		Label: "TMR班次",
+	}, &operationPb.FormulaOptionEnum{
+		Value: 6,
+		Label: "车次",
+	})
+
+	res.Data.JumpType = append(res.Data.JumpType, &operationPb.FormulaOptionEnum{
+		Value: 0,
+		Label: "手动跳转",
+	}, &operationPb.FormulaOptionEnum{
+		Value: 1,
+		Label: "自动跳转",
+	})
+
+	res.Data.StatisticsType = append(res.Data.StatisticsType, &operationPb.FormulaOptionEnum{
+		Value: 7,
+		Label: "无分类",
+	}, &operationPb.FormulaOptionEnum{
+		Value: 0,
+		Label: "驾驶员",
+	}, &operationPb.FormulaOptionEnum{
+		Value: 1,
+		Label: "配方名称",
+	}, &operationPb.FormulaOptionEnum{
+		Value: 2,
+		Label: "栏舍名称",
+	}, &operationPb.FormulaOptionEnum{
+		Value: 3,
+		Label: "牧畜类别",
+	}, &operationPb.FormulaOptionEnum{
+		Value: 4,
+		Label: "车次",
+	}, &operationPb.FormulaOptionEnum{
+		Value: 5,
+		Label: "TMR名称",
+	}, &operationPb.FormulaOptionEnum{
+		Value: 6,
+		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
 }
 
@@ -514,7 +914,7 @@ func (s *StoreEntry) DeleteForageList(ctx context.Context, ids []int64) error {
 	if len(ids) == 0 {
 		return xerr.Custom("参数错误")
 	}
-	if err := s.DB.Where("id IN ?", ids).Update("is_delete", operationPb.IsShow_NO).Error; err != nil {
+	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
@@ -648,14 +1048,14 @@ func (s *StoreEntry) ExcelExportForage(ctx context.Context, req *operationPb.Sea
 	if err != nil {
 		return nil, xerr.WithStack(err)
 	}
-	if len(res.List) <= 0 {
+	if len(res.Data.List) <= 0 {
 		return nil, xerr.Custom("数据为空")
 	}
 
 	file := excelize.NewFile()
 	defer file.Close()
 
-	streamWriter, err := file.NewStreamWriter("Sheet1")
+	streamWriter, err := file.NewStreamWriter(model.DefaultSheetName)
 	if err != nil {
 		return nil, xerr.WithStack(err)
 	}
@@ -664,7 +1064,7 @@ func (s *StoreEntry) ExcelExportForage(ctx context.Context, req *operationPb.Sea
 	if err = streamWriter.SetRow("A1", titles); err != nil {
 		return nil, xerr.WithStack(err)
 	}
-	for i, item := range res.List {
+	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))
@@ -707,3 +1107,19 @@ func (s *StoreEntry) ExcelTemplateForage(ctx context.Context) (*bytes.Buffer, er
 
 	return file.WriteToBuffer()
 }
+
+func (s *StoreEntry) SmallMaterial(ctx context.Context, req *operationPb.SmallMaterialRequest) (*model.PastureCommonResponse, error) {
+	body := &model.PastureCommonRequest{
+		Name:       req.ApiName,
+		ReturnType: "Map",
+		ParamMaps: &model.FormulaEstimateParams{
+			PastureId: fmt.Sprintf("%d", req.PastureId),
+			ImforName: req.InfoName,
+		},
+	}
+	response := &model.PastureCommonResponse{Data: &model.PastureCommonData{}}
+	if _, err := s.PastureHttpClient(ctx, model.UrlDataByName, int64(req.PastureId), body, response); err != nil {
+		return nil, xerr.WithStack(err)
+	}
+	return response, nil
+}

+ 104 - 0
module/backend/pasture_sync_service.go

@@ -0,0 +1,104 @@
+package backend
+
+import (
+	"context"
+	"errors"
+	"kpt-tmr-group/model"
+	"kpt-tmr-group/pkg/xerr"
+	operationPb "kpt-tmr-group/proto/go/backend/operation"
+
+	"gorm.io/gorm"
+)
+
+const (
+	FeedCategory       = "feed"
+	FeedCategoryDelete = "feed_delete"
+	CowCategory        = "cow"
+	CowCategoryDelete  = "cow_delete"
+)
+
+func (s *StoreEntry) CategorySyncData(ctx context.Context, req *operationPb.CategorySyncRequest) error {
+	pastureDetail, err := s.GetGroupPastureListById(ctx, int64(req.PastureId))
+	if err != nil {
+		return xerr.WithStack(err)
+	}
+	switch req.KeyWord {
+	case FeedCategory:
+		history := &model.ForageCategory{}
+		if err = s.DB.Model(new(model.ForageCategory)).
+			Where("pasture_id = ?", req.PastureId).
+			Where("data_id = ?", req.Id).
+			Where("data_source = ? ", operationPb.DataSource_FROM_PASTURE).
+			First(history).Error; err != nil {
+			if !errors.Is(err, gorm.ErrRecordNotFound) {
+				return xerr.WithStack(err)
+			}
+		}
+		if history.IsShow == operationPb.IsShow_OK && history.Id > 0 {
+			if err = s.DB.Model(new(model.ForageCategory)).Where("id = ?", history.Id).Updates(map[string]interface{}{
+				"number":      req.Number,
+				"is_show":     req.IsShow,
+				"parent_id":   req.ParentId,
+				"parent_name": req.ParentName,
+				"name":        req.Name,
+			}).Error; err != nil {
+				return xerr.WithStack(err)
+			}
+		}
+
+		newFeedData := model.NewPastureForageCategory(req, pastureDetail)
+		if err = s.DB.Model(new(model.ForageCategory)).Create(newFeedData).Error; err != nil {
+			return xerr.WithStack(err)
+		}
+	case CowCategory:
+		history := &model.CattleCategory{}
+		if err = s.DB.Model(new(model.CattleCategory)).
+			Where("pasture_id = ?", req.PastureId).
+			Where("data_id = ?", req.Id).
+			Where("data_source = ? ", operationPb.DataSource_FROM_PASTURE).
+			First(history).Error; err != nil {
+			if !errors.Is(err, gorm.ErrRecordNotFound) {
+				return xerr.WithStack(err)
+			}
+		}
+		if history.IsShow == operationPb.IsShow_OK && history.Id > 0 {
+			if err = s.DB.Model(new(model.CattleCategory)).Where("id = ?", history.Id).Updates(map[string]interface{}{
+				"number":      req.Number,
+				"is_show":     req.IsShow,
+				"parent_id":   req.ParentId,
+				"parent_name": req.ParentName,
+				"name":        req.Name,
+			}).Error; err != nil {
+				return xerr.WithStack(err)
+			}
+		}
+		newCattleData := model.NewPastureCattleCategory(req, pastureDetail)
+		if err = s.DB.Model(new(model.CattleCategory)).Create(newCattleData).Error; err != nil {
+			return xerr.WithStack(err)
+		}
+	}
+
+	return nil
+}
+
+func (s *StoreEntry) CategoryDeleteData(ctx context.Context, req *operationPb.CategoryDeleteRequest) error {
+	var modelValue interface{}
+	switch req.KeyWord {
+	case FeedCategoryDelete:
+		modelValue = new(model.ForageCategory)
+	case CowCategoryDelete:
+		modelValue = new(model.CattleCategory)
+	}
+	if modelValue == nil {
+		return nil
+	}
+
+	if err := s.DB.Model(modelValue).Where("data_id = ?", req.DataId).
+		Where("pasture_id = ?", req.PastureId).Where("data_source = ?", operationPb.DataSource_FROM_PASTURE).
+		Updates(map[string]interface{}{
+			"is_delete": operationPb.IsShow_OK,
+		}).Error; err != nil {
+		return xerr.WithStack(err)
+	}
+	return nil
+}

+ 800 - 26
module/backend/statistic_service.go

@@ -1,59 +1,833 @@
 package backend
 
 import (
+	"bytes"
 	"context"
+	"encoding/json"
+	"errors"
 	"fmt"
 	"kpt-tmr-group/model"
+	"kpt-tmr-group/pkg/logger/zaplog"
 	"kpt-tmr-group/pkg/xerr"
 	operationPb "kpt-tmr-group/proto/go/backend/operation"
 	"net/http"
-	"time"
+	"sort"
+	"sync"
+
+	"gorm.io/gorm"
+
+	"github.com/xuri/excelize/v2"
+
+	"go.uber.org/zap"
 )
 
-func (s *StoreEntry) SearchFormulaEstimateList(ctx context.Context, req *operationPb.SearchFormulaEstimateRequest) (*operationPb.SearchFormulaEstimateResponse, error) {
-	startTime, err := time.Parse(model.LayoutTime, req.StartTime)
+type PastureClientHandler func(ctx context.Context, pastureId int64, body interface{}) error
+
+// PastureDetailById 获取指定牧场详情
+func (s *StoreEntry) PastureDetailById(ctx context.Context, pastureId int64) (*model.GroupPasture, error) {
+	result := &model.GroupPasture{Id: pastureId}
+	if err := s.DB.Where("is_delete = ? and is_show = ?", operationPb.IsShow_OK, operationPb.IsShow_OK).First(result).Error; err != nil {
+		return nil, xerr.WithStack(err)
+	}
+	return result, nil
+}
+
+func (s *StoreEntry) PastureHttpClient(ctx context.Context, apiUrl string, pastureId int64, body, response interface{}) (*model.GroupPasture, error) {
+	pastureDetail, err := s.PastureDetailById(ctx, pastureId)
+	if err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			return nil, xerr.Customf("该牧场不存在")
+		}
+		zaplog.Error("PastureHttpClient", zap.Any("Err", err), zap.Int64("pastureId", pastureId))
+		return nil, xerr.Customf("该牧场数据错误,Err:%s", err)
+	}
+	pastureClient := model.NewPastureClient(pastureDetail)
+	url := fmt.Sprintf("%s/%s", pastureDetail.Domain, apiUrl)
+	result, err := pastureClient.DoPost(url, body)
+	if err != nil {
+		return pastureDetail, xerr.WithStack(err)
+	}
+
+	zaplog.Info("PastureHttpClient", zap.String("url", url), zap.Any("request", body), zap.String("response", string(result)))
+	if err = json.Unmarshal(result, response); err != nil {
+		return pastureDetail, xerr.WithStack(err)
+	}
+	return pastureDetail, nil
+}
+
+// SearchFormulaEstimateList 配方评估
+func (s *StoreEntry) SearchFormulaEstimateList(ctx context.Context, req *operationPb.SearchFormulaEstimateRequest) (*model.PastureCommonResponse, error) {
+	groupPasture, err := s.GetGroupPastureListById(ctx, int64(req.PastureId))
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+	if groupPasture.PastureId > 0 {
+		req.PastureId = int32(groupPasture.PastureId)
+	}
+
+	body := &model.PastureCommonRequest{
+		Name:       req.ApiName,
+		Page:       req.Pagination.Page,
+		Offset:     req.Pagination.PageOffset,
+		PageCount:  req.Pagination.PageSize,
+		ReturnType: "Map",
+		ParamMaps: &model.FormulaEstimateParams{
+			PastureId: fmt.Sprintf("%d", req.PastureId),
+			StartTime: req.StartTime,
+			StopTime:  req.EndTime,
+			Search:    fmt.Sprintf("%d", req.SearchType),
+			TempletId: fmt.Sprintf("%d", req.TemplateId),
+			Barid:     fmt.Sprintf("%d", req.BarnId),
+		},
+	}
+	response := &model.PastureCommonResponse{Data: &model.PastureCommonData{}}
+	if _, err := s.PastureHttpClient(ctx, model.UrlDataByName, int64(req.PastureId), body, response); err != nil {
+		return nil, xerr.WithStack(err)
+	}
+	return response, nil
+}
+
+// SearchInventoryStatistics 库存管理-库存统计
+func (s *StoreEntry) SearchInventoryStatistics(ctx context.Context, req *operationPb.SearchInventoryStatisticsRequest) (*model.PastureCommonResponse, error) {
+	groupPasture, err := s.GetGroupPastureListById(ctx, int64(req.PastureId))
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+	if groupPasture.PastureId > 0 {
+		req.PastureId = int32(groupPasture.PastureId)
+	}
+
+	body := &model.PastureCommonRequest{
+		Name:       req.ApiName,
+		Page:       req.Pagination.Page,
+		Offset:     req.Pagination.PageOffset,
+		PageCount:  req.Pagination.PageSize,
+		ReturnType: "Map",
+		ParamMaps: &model.InventoryStatisticsParams{
+			PastureId: fmt.Sprintf("%d", req.PastureId),
+			StartTime: req.StartTime,
+			StopTime:  req.EndTime,
+			FeedName:  req.FeedName,
+		},
+	}
+	response := &model.PastureCommonResponse{
+		Data: &model.PastureCommonData{
+			List: make([]*model.InventoryStatisticsList, 0),
+		},
+	}
+	if _, err := s.PastureHttpClient(ctx, model.UrlDataByName, int64(req.PastureId), body, response); err != nil {
+		return nil, xerr.WithStack(err)
+	}
+	return response, nil
+}
+
+// InventoryStatisticsExcelExport 库存管理-库存统计-报表导出
+func (s *StoreEntry) InventoryStatisticsExcelExport(ctx context.Context, req *operationPb.SearchInventoryStatisticsRequest) (*bytes.Buffer, error) {
+	result, err := s.SearchInventoryStatistics(ctx, req)
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	b, _ := json.Marshal(result.Data.List)
+	inventoryStatisticsList := make([]*model.InventoryStatisticsList, 0)
+	if err = json.Unmarshal(b, &inventoryStatisticsList); err != nil {
+		return nil, xerr.Customf("牧场端返回数据错误")
+	}
+
+	file := excelize.NewFile()
+	defer file.Close()
+
+	streamWriter, err := file.NewStreamWriter(model.DefaultSheetName)
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	// 表头
+	titles := map[string][]interface{}{
+		"A1": {"饲料名称", "期初", nil, "用料", nil, nil, nil, "期末"},
+		"A2": {nil, "期初库存(kg)", "期初金额(元)", "入库重量(kg)", "系统出库重量(kg)", "人工用料重量(kg)", "损耗重量", "期末库存(kg)", "期末金额(kg)"},
+	}
+	for cell, values := range titles {
+		if err = streamWriter.SetRow(cell, values); err != nil {
+			return nil, xerr.WithStack(err)
+		}
+	}
+
+	for i, item := range inventoryStatisticsList {
+		cell, err := excelize.CoordinatesToCellName(1, i+3)
+		if err != nil {
+			zaplog.Error("InventoryStatisticsExcelExport CoordinatesToCellName", zap.Any("Err", err))
+			continue
+		}
+		row := make([]interface{}, 0)
+		row = append(row, item.FeedName, item.StartSum, item.StartPrice, item.LaidSum, item.UseSumXT, item.UseSumRG, item.UseSumXH, item.StopSum, item.StopPrice)
+
+		if err = streamWriter.SetRow(cell, row); err != nil {
+			return nil, xerr.WithStack(err)
+		}
+	}
+
+	hvCell := map[string]string{
+		"A1": "A2",
+		"B1": "C1",
+		"D1": "G1",
+		"H1": "I1",
+	}
+	// 合并单元格
+	for h, v := range hvCell {
+		if err = streamWriter.MergeCell(h, v); err != nil {
+			return nil, xerr.WithStack(err)
+		}
+	}
+
+	// 修改样式
+	/*style1, err := file.NewStyle(&excelize.Style{
+		Fill:      excelize.Fill{Type: "pattern", Color: []string{"#DFEBF6"}, Pattern: 1},
+		Alignment: &excelize.Alignment{Horizontal: "center"},
+	})
 	if err != nil {
 		return nil, xerr.WithStack(err)
 	}
 
-	endTime, err := time.Parse(model.LayoutTime, req.EndTime)
+	style1, err := file.NewStyle(&excelize.Style{
+		//Fill:      excelize.Fill{Type: "pattern", Color: []string{"#DFEBF6"}, Pattern: 1},
+		Alignment: &excelize.Alignment{Horizontal: "center"},
+	})
 	if err != nil {
 		return nil, xerr.WithStack(err)
 	}
 
-	startTimeUnix := startTime.Unix()
-	endTimeUnix := endTime.Unix()
+	if err = file.SetCellStyle(model.DefaultSheetName, "A1", "A1", style1); err != nil {
+		return nil, xerr.WithStack(err)
+	}*/
+
+	if err = streamWriter.Flush(); err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	return file.WriteToBuffer()
+}
 
-	formulaEstimate := make([]*model.FormulaEstimate, 0)
-	var count int64 = 0
+// SearchUserMaterialsStatistics 库存管理-用料分析
+func (s *StoreEntry) SearchUserMaterialsStatistics(ctx context.Context, req *operationPb.SearchUserMaterialsStatisticsRequest) (*model.PastureCommonResponse, error) {
+	groupPasture, err := s.GetGroupPastureListById(ctx, int64(req.PastureId))
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+	if groupPasture.PastureId > 0 {
+		req.PastureId = int32(groupPasture.PastureId)
+	}
 
-	pref := s.DB.Model(new(model.FormulaEstimate))
-	if req.Name != "" {
-		pref.Where("name like ?", fmt.Sprintf("%s%s%s", "%", req.Name, "%"))
+	body := &model.PastureCommonRequest{
+		Name:       req.ApiName,
+		Page:       req.Pagination.Page,
+		Offset:     req.Pagination.PageOffset,
+		PageCount:  req.Pagination.PageSize,
+		ReturnType: "Map",
+		Checked:    req.ErrorCheck,
+		ParamMaps: &model.UserMaterialsStatisticsParams{
+			PastureId: fmt.Sprintf("%d", req.PastureId),
+			StartTime: req.StartTime,
+			StopTime:  req.EndTime,
+			FeedName:  req.FeedName,
+			Typea:     fmt.Sprintf("%d", req.TypeCheck),
+		},
+	}
+	response := &model.PastureCommonResponse{Data: &model.PastureCommonData{
+		List: &model.UserMaterialsList{},
+	}}
+	if _, err := s.PastureHttpClient(ctx, model.UrlReportForm, int64(req.PastureId), body, response); err != nil {
+		return nil, xerr.WithStack(err)
 	}
-	if startTimeUnix > 0 && endTimeUnix > 0 && endTimeUnix >= startTimeUnix {
-		pref.Where("created_at BETWEEN ? AND ?", startTimeUnix, endTimeUnix)
+	return response, nil
+}
+
+func (s *StoreEntry) UserMaterialsStatisticsExcelExport(ctx context.Context, req *operationPb.SearchUserMaterialsStatisticsRequest) (*bytes.Buffer, error) {
+	result, err := s.SearchUserMaterialsStatistics(ctx, req)
+	if err != nil {
+		return nil, xerr.WithStack(err)
 	}
 
-	if req.SearchType == 1 {
-		pref.Where("feed_formula_name = ?", req.Name)
-	} else {
-		pref.Where("barn_id = ?", req.Name)
+	b, _ := json.Marshal(result.Data.List)
+	userMaterialsList := &model.UserMaterialsList{}
+	if err = json.Unmarshal(b, userMaterialsList); err != nil {
+		return nil, xerr.Customf("牧场端返回数据错误")
 	}
 
-	if err = pref.Order("id desc").Count(&count).Limit(int(req.Pagination.PageSize)).Offset(int(req.Pagination.PageOffset)).
-		Find(&formulaEstimate).Error; err != nil {
+	file := excelize.NewFile()
+	defer file.Close()
+
+	streamWriter, err := file.NewStreamWriter(model.DefaultSheetName)
+	if err != nil {
 		return nil, xerr.WithStack(err)
 	}
 
-	return &operationPb.SearchFormulaEstimateResponse{
+	// 表数据
+	excelValuesList := map[int][]interface{}{}
+
+	cProp := make([]string, 0)
+	for _, data2 := range userMaterialsList.Data2 {
+		excelValuesList[1] = append(excelValuesList[1], data2.Label)
+		for _, c := range data2.Children {
+			excelValuesList[2] = append(excelValuesList[2], c.Label)
+			cProp = append(cProp, c.Prop)
+		}
+	}
+
+	for cell, values := range excelValuesList {
+		if err = streamWriter.SetRow(fmt.Sprintf("A%d", cell), values); err != nil {
+			return nil, xerr.WithStack(err)
+		}
+		delete(excelValuesList, cell)
+	}
+
+	for i, data1 := range userMaterialsList.Data1 {
+		data1Map, ok := data1.(map[string]interface{})
+		if ok {
+			for _, prop := range cProp {
+				newValue := ""
+				if value, yes := data1Map[prop]; yes {
+					if value != nil {
+						switch v := value.(type) {
+						case string:
+							newValue = v
+						case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
+							newValue = fmt.Sprintf("%d", v)
+						default:
+							newValue = fmt.Sprintf("%v", v)
+						}
+					}
+					excelValuesList[i+3] = append(excelValuesList[i+3], newValue)
+				}
+			}
+		}
+	}
+
+	excelValuesKeys := make([]int, 0)
+	for k, _ := range excelValuesList {
+		excelValuesKeys = append(excelValuesKeys, k)
+	}
+	sort.Ints(excelValuesKeys)
+	for _, v := range excelValuesKeys {
+		if err = streamWriter.SetRow(fmt.Sprintf("A%d", v), excelValuesList[v]); err != nil {
+			return nil, xerr.WithStack(err)
+		}
+	}
+
+	if err = streamWriter.Flush(); err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	return file.WriteToBuffer()
+}
+
+// SearchPriceStatistics 库存管理-价格分析
+func (s *StoreEntry) SearchPriceStatistics(ctx context.Context, req *operationPb.SearchPriceStatisticsRequest) (*model.PastureCommonResponse, error) {
+	groupPasture, err := s.GetGroupPastureListById(ctx, int64(req.PastureId))
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+	if groupPasture.PastureId > 0 {
+		req.PastureId = int32(groupPasture.PastureId)
+	}
+
+	body := &model.PastureCommonRequest{
+		Name:       req.ApiName,
+		Page:       req.Pagination.Page,
+		Offset:     req.Pagination.PageOffset,
+		PageCount:  req.Pagination.PageSize,
+		ReturnType: "Map",
+		ParamMaps: &model.PriceStatisticsParams{
+			PastureId: fmt.Sprintf("%d", req.PastureId),
+			StartTime: req.StartTime,
+			StopTime:  req.EndTime,
+			FeedName:  req.FeedName,
+		},
+	}
+	response := &model.PastureCommonResponse{Data: &model.PastureCommonData{}}
+	if _, err := s.PastureHttpClient(ctx, model.UrlReportForm, int64(req.PastureId), body, response); err != nil {
+		return nil, xerr.WithStack(err)
+	}
+	return response, nil
+}
+
+// SearchFeedStatistics 饲喂效率-效率统计
+func (s *StoreEntry) SearchFeedStatistics(ctx context.Context, req *operationPb.SearchFeedStatisticsRequest) (*model.FeedStatisticsResponse, error) {
+	res := &model.FeedStatisticsResponse{
 		Code: http.StatusOK,
 		Msg:  "ok",
-		Data: &operationPb.SearchFormulaEstimate{
-			Page:     req.Pagination.Page,
-			Total:    int32(count),
-			PageSize: req.Pagination.PageSize,
-			List:     model.FormulaEstimateSlice(formulaEstimate).ToPB(),
+		Data: &model.FeedStatisticsData{
+			List: make(map[string]interface{}),
+		},
+	}
+
+	if len(req.PastureId) <= 0 {
+		return res, nil
+	}
+	if req.CattleCategoryId > 0 {
+		cattleList := s.ForageEnumList(ctx).Data.CattleParentCategory
+		for _, v := range cattleList {
+			if v.Value == operationPb.CattleCategoryParent_Kind(req.CattleCategoryId) {
+				req.CattleCategoryName = v.Label
+				break
+			}
+		}
+	}
+
+	times := ""
+	if req.ClassNumber > 0 {
+		times = fmt.Sprintf("%d", req.ClassNumber)
+	}
+
+	wg := sync.WaitGroup{}
+	wg.Add(len(req.PastureId))
+	for _, v := range req.PastureId {
+		go func(pastureId int32) {
+			defer wg.Done()
+			body := &model.PastureCommonRequest{
+				Name:       req.ApiName,
+				Page:       req.Pagination.Page,
+				Offset:     req.Pagination.PageOffset,
+				PageCount:  req.Pagination.PageSize,
+				ReturnType: "Map",
+				ParamMaps: &model.FeedStatisticsParams{
+					PastureId: fmt.Sprintf("%d", pastureId),
+					StartTime: req.StartTime,
+					StopTime:  req.StartTime,
+					Date:      req.StartTime,
+					FeedTName: req.FormulaTemplate,
+					BarName:   req.BarnName,
+					CowClass:  req.CattleCategoryName,
+					Times:     times,
+				},
+			}
+			response := &model.PastureCommonResponse{
+				Data: &model.PastureCommonData{},
+			}
+			groupPasture, err := s.PastureHttpClient(ctx, model.UrlDataByName, int64(pastureId), body, response)
+			if err != nil {
+				zaplog.Error("SearchFeedStatistics",
+					zap.Any("pastureId", pastureId),
+					zap.Any("url", model.UrlDataByName),
+					zap.Any("body", body),
+					zap.Any("response", response))
+				return
+			}
+			if response.Code == http.StatusOK && response.Data.List != nil {
+				if req.ApiName == "getFeedEfficiencySC" {
+					feedStatisticsConversions := FeedStatisticsConversions(response.Data.List)
+					feedStatisticsConversions.PastureName = groupPasture.Name
+					res.Data.List[groupPasture.Name] = FeedStatisticsConversions(response.Data.List)
+				} else {
+					res.Data.List[groupPasture.Name] = response.Data.List
+				}
+			}
+		}(v)
+	}
+
+	wg.Wait()
+	return res, nil
+}
+
+// FeedStatisticsConversions 数据转换
+func FeedStatisticsConversions(req interface{}) *model.FeedStatisticsConversions {
+	feedStatisticsList := &model.FeedStatisticsConversions{}
+	if data, ok := req.([]interface{}); ok && len(data) > 0 {
+		value := data[0]
+		if v, yes := value.(map[string]interface{}); yes {
+			if d, t := v["TMR干物质"]; t {
+				feedStatisticsList.TmrDryMatter = d.(string)
+			}
+			if d, t := v["barname"]; t {
+				feedStatisticsList.BarName = d.(string)
+			}
+			if d, t := v["产奶量"]; t {
+				feedStatisticsList.MilkYield = d.(string)
+			}
+			if d, t := v["今日剩料量"]; t {
+				feedStatisticsList.TodayLeftovers = d.(string)
+			}
+			if d, t := v["公斤奶饲料成本"]; t {
+				feedStatisticsList.FeedCosts = d.(float64)
+			}
+			if d, t := v["剩料率"]; t {
+				feedStatisticsList.LeftoverRate = d.(string)
+			}
+			if d, t := v["实际干物质采食量"]; t {
+				feedStatisticsList.ActualDryMatterIntake = d.(string)
+			}
+			if d, t := v["实际成本"]; t {
+				feedStatisticsList.ActualCost = d.(string)
+			}
+			if d, t := v["实际牛头数"]; t {
+				feedStatisticsList.ActualNumberOfCattle = d.(string)
+			}
+			if d, t := v["应混料量"]; t {
+				feedStatisticsList.AmountToBeMixed = d.(string)
+			}
+			if d, t := v["混料时间"]; t {
+				feedStatisticsList.MixingTime = d.(string)
+			}
+			if d, t := v["牲畜类别"]; t {
+				feedStatisticsList.CategoryCattleName = d.(string)
+			}
+			if d, t := v["理论干物质"]; t {
+				feedStatisticsList.TheoreticalDryMatter = d.(string)
+			}
+			if d, t := v["转投剩料量"]; t {
+				feedStatisticsList.LeftoverMaterial = d.(float64)
+			}
+			if d, t := v["配方干物质采食量"]; t {
+				feedStatisticsList.FormulatedDryMatterIntake = d.(string)
+			}
+			if d, t := v["配方成本"]; t {
+				feedStatisticsList.CostOfFormulation = d.(string)
+			}
+			if d, t := v["采食率"]; t {
+				feedStatisticsList.FoodIntakeRate = d.(string)
+			}
+			if d, t := v["饲料转化率"]; t {
+				feedStatisticsList.FeedConversionRatio = d.(float64)
+			}
+		}
+	}
+	return feedStatisticsList
+}
+
+// FeedChartStatistics 饲喂效率图表分析
+func (s *StoreEntry) FeedChartStatistics(ctx context.Context, req *operationPb.FeedChartStatisticsRequest) (*model.PastureCommonResponse, error) {
+	groupPasture, err := s.GetGroupPastureListById(ctx, int64(req.PastureId))
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+	if groupPasture.PastureId > 0 {
+		req.PastureId = int32(groupPasture.PastureId)
+	}
+
+	body := &model.FeedChartParams{
+		ParamMaps: &model.ParamMaps{
+			PastureId: fmt.Sprintf("%d", req.PastureId),
+			StartTime: req.StartTime,
+			StopTime:  req.StartTime,
+			Status:    req.Status,
+		},
+	}
+	url, ok := model.UrlChart[req.ApiType]
+	if !ok {
+		return nil, xerr.Customf("错误的接口类型:%s", req.ApiType)
+	}
+	response := &model.PastureCommonResponse{Data: &model.PastureCommonData{}}
+	if _, err := s.PastureHttpClient(ctx, url, int64(req.PastureId), body, response); err != nil {
+		return nil, xerr.WithStack(err)
+	}
+	return response, nil
+}
+
+// CowsAnalysis 饲喂效率-牛群评估
+func (s *StoreEntry) CowsAnalysis(ctx context.Context, req *operationPb.CowsAnalysisRequest) (*model.PastureCommonResponse, error) {
+	groupPasture, err := s.GetGroupPastureListById(ctx, int64(req.PastureId))
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+	if groupPasture.PastureId > 0 {
+		req.PastureId = int32(groupPasture.PastureId)
+	}
+
+	body := &model.PastureCommonRequest{
+		Name:       req.ApiName,
+		Page:       req.Pagination.Page,
+		Offset:     req.Pagination.PageOffset,
+		PageCount:  req.Pagination.PageSize,
+		ReturnType: "Map",
+		ParamMaps: &model.MixFeedStatisticsParams{
+			PastureId: fmt.Sprintf("%d", req.PastureId),
+			StartTime: req.StartTime,
+			StopTime:  req.StartTime,
+		},
+	}
+	response := &model.PastureCommonResponse{Data: &model.PastureCommonData{}}
+	if _, err := s.PastureHttpClient(ctx, model.UrlDataByName, int64(req.PastureId), body, response); err != nil {
+		return nil, xerr.WithStack(err)
+	}
+	return response, nil
+}
+
+// SearchAccuracyAggStatistics 准确性分析-汇总分析
+func (s *StoreEntry) SearchAccuracyAggStatistics(ctx context.Context, req *operationPb.AccuracyAggStatisticsRequest) (*model.PastureCommonResponse, error) {
+	groupPasture, err := s.GetGroupPastureListById(ctx, int64(req.PastureId))
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+	if groupPasture.PastureId > 0 {
+		req.PastureId = int32(groupPasture.PastureId)
+	}
+
+	body := &model.FeedChartParams{
+		ParamMaps: &model.AccuracyAggParams{
+			PastureId: fmt.Sprintf("%d", req.PastureId),
+			StartTime: req.StartTime,
+			StopTime:  req.EndTime,
+			FName:     req.Fname,
+			Sort:      req.Sort,
+			Status:    req.Status,
+			Times:     req.Times,
+			Genre:     req.Genre,
+			IsDate:    req.Isdate,
+			Hlwc1:     req.Hlwc1,
+			Hlwc2:     req.Hlwc2,
+			Hlzq1:     req.Hlzq1,
+			Hlzq2:     req.Hlzq2,
+			Hlzql1:    req.Hlzql1,
+			Hlzql2:    req.Hlzql2,
+			Slwc1:     req.Slwc1,
+			Slwc2:     req.Slwc2,
+			Slzq1:     req.Slzq1,
+			Slzq2:     req.Slzq2,
+			Slzql1:    req.Slzql1,
+			Slzql2:    req.Slzql2,
+			Error:     req.IsError,
+		},
+	}
+	response := &model.PastureCommonResponse{Data: &model.PastureCommonData{}}
+	if _, err := s.PastureHttpClient(ctx, model.UrlSummary, int64(req.PastureId), body, response); err != nil {
+		return nil, xerr.WithStack(err)
+	}
+	return response, nil
+}
+
+// SearchMixFeedStatistics 准确性分析-混料统计
+func (s *StoreEntry) SearchMixFeedStatistics(ctx context.Context, req *operationPb.MixFeedStatisticsRequest) (*model.PastureCommonResponse, error) {
+	groupPasture, err := s.GetGroupPastureListById(ctx, int64(req.PastureId))
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+	if groupPasture.PastureId > 0 {
+		req.PastureId = int32(groupPasture.PastureId)
+	}
+
+	times := ""
+	if req.ClassNumber > 0 {
+		times = fmt.Sprintf("%d", req.ClassNumber)
+	}
+
+	body := &model.PastureCommonRequest{
+		Name:       req.ApiName,
+		Page:       req.Pagination.Page,
+		Offset:     req.Pagination.PageOffset,
+		PageCount:  req.Pagination.PageSize,
+		ReturnType: "Map",
+		ParamMaps: &model.MixFeedStatisticsParams{
+			PastureId:   fmt.Sprintf("%d", req.PastureId),
+			StartTime:   req.StartTime,
+			StopTime:    req.StartTime,
+			TmrTName:    req.EquipmentName,
+			ProjName:    req.TrainNumber,
+			Times:       times,
+			ButtonType:  req.ButtonType,
+			TempletName: req.FormulationName,
+			Isuse:       req.IsUse,
+			Hlwc1:       req.Hlwc1,
+			Hlwc2:       req.Hlwc2,
+			Hlzq1:       req.Hlzq1,
+			Hlzq2:       req.Hlzq2,
+			Hlzql1:      req.Hlzql1,
+			Hlzql2:      req.Hlzql2,
+			Error:       req.IsError,
+		},
+	}
+	response := &model.PastureCommonResponse{Data: &model.PastureCommonData{}}
+	if _, err := s.PastureHttpClient(ctx, model.UrlDataByName, int64(req.PastureId), body, response); err != nil {
+		return nil, xerr.WithStack(err)
+	}
+	return response, nil
+}
+
+// SearchSprinkleStatistics 准确性分析-撒料统计
+func (s *StoreEntry) SearchSprinkleStatistics(ctx context.Context, req *operationPb.SprinkleStatisticsRequest) (*model.PastureCommonResponse, error) {
+	groupPasture, err := s.GetGroupPastureListById(ctx, int64(req.PastureId))
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+	if groupPasture.PastureId > 0 {
+		req.PastureId = int32(groupPasture.PastureId)
+	}
+
+	times := ""
+	if req.ClassNumber > 0 {
+		times = fmt.Sprintf("%d", req.ClassNumber)
+	}
+	body := &model.PastureCommonRequest{
+		Name:       req.ApiName,
+		Page:       req.Pagination.Page,
+		Offset:     req.Pagination.PageOffset,
+		PageCount:  req.Pagination.PageSize,
+		ReturnType: "Map",
+		ParamMaps: &model.SprinkleStatisticsParams{
+			PastureId:   fmt.Sprintf("%d", req.PastureId),
+			StartTime:   req.StartTime,
+			StopTime:    req.StartTime,
+			TmrTName:    req.EquipmentName,
+			ProjName:    req.TrainNumber,
+			Times:       times,
+			ButtonType:  req.ButtonType,
+			TempletName: req.FormulationName,
+			Isuse:       req.IsUse,
+			Fname:       req.BarnName,
+			Slwc1:       req.Slwc1,
+			Slwc2:       req.Slwc2,
+			Slzq2:       req.Slzq2,
+			Slzq1:       req.Slzq1,
+			Slzql1:      req.Slzql1,
+			Slzql2:      req.Slzql2,
+			Error:       req.IsError,
+		},
+	}
+	response := &model.PastureCommonResponse{Data: &model.PastureCommonData{}}
+	if _, err := s.PastureHttpClient(ctx, model.UrlDataByName, int64(req.PastureId), body, response); err != nil {
+		return nil, xerr.WithStack(err)
+	}
+	return response, nil
+}
+
+// SearchProcessAnalysis 过程分析
+func (s *StoreEntry) SearchProcessAnalysis(ctx context.Context, req *operationPb.ProcessAnalysisRequest) (*model.PastureCommonResponse, error) {
+	groupPasture, err := s.GetGroupPastureListById(ctx, int64(req.PastureId))
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+	if groupPasture.PastureId > 0 {
+		req.PastureId = int32(groupPasture.PastureId)
+	}
+
+	body := &model.PastureCommonRequest{
+		Name:       req.ApiName,
+		Page:       req.Pagination.Page,
+		Offset:     req.Pagination.PageOffset,
+		PageCount:  req.Pagination.PageSize,
+		ReturnType: "Map",
+		ParamMaps: &model.ProcessAnalysisParams{
+			PastureId:   fmt.Sprintf("%d", req.PastureId),
+			StartTime:   req.StartTime,
+			StopTime:    req.StartTime,
+			TmrTName:    req.TmrName,
+			IsCompleted: "",
+			LpPlanType:  fmt.Sprintf("%d", req.PlanType),
+			FClassId:    req.MixFeedType,
+			Hlzq1:       req.Hlzq1,
+			Hlzq2:       req.Hlzq2,
+			Hlwc1:       req.Hlwc1,
+			Hlwc2:       req.Hlwc2,
+			Slwc1:       req.Slwc1,
+			Slwc2:       req.Slwc2,
+			Slzq2:       req.Slzq2,
+			Slzq1:       req.Slzq1,
+			Error:       req.ErrorRange,
 		},
-	}, nil
+	}
+	response := &model.PastureCommonResponse{Data: &model.PastureCommonData{}}
+	if _, err := s.PastureHttpClient(ctx, model.UrlProcess, int64(req.PastureId), body, response); err != nil {
+		return nil, xerr.WithStack(err)
+	}
+	return response, nil
+}
+
+// GetDataByName 共同接口
+func (s *StoreEntry) GetDataByName(ctx context.Context, req *operationPb.GetDataByNameRequest) (*model.PastureCommonResponse, error) {
+	groupPasture, err := s.GetGroupPastureListById(ctx, int64(req.PastureId))
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+	if groupPasture.PastureId > 0 {
+		req.PastureId = int32(groupPasture.PastureId)
+	}
+
+	body := &model.PastureCommonRequest{
+		Name: req.ApiName,
+		ParamMaps: &model.GetDataByNameParams{
+			PastureId: fmt.Sprintf("%d", req.PastureId),
+			StartTime: req.StartTime,
+			StopTime:  req.StartTime,
+		},
+	}
+	response := &model.PastureCommonResponse{Data: &model.PastureCommonData{}}
+	if _, err := s.PastureHttpClient(ctx, model.UrlDataByName, int64(req.PastureId), body, response); err != nil {
+		return nil, xerr.WithStack(err)
+	}
+	return response, nil
+}
+
+// GetTrainNumber 获取班次
+func (s *StoreEntry) GetTrainNumber(ctx context.Context, req *operationPb.TrainNumberRequest) (*operationPb.TrainNumberResponse, error) {
+	groupPasture, err := s.GetGroupPastureListById(ctx, int64(req.PastureId))
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+	if groupPasture.PastureId > 0 {
+		req.PastureId = int32(groupPasture.PastureId)
+	}
+
+	body := &model.PastureCommonRequest{
+		Name:       req.ApiName,
+		Page:       req.Pagination.Page,
+		Offset:     req.Pagination.PageOffset,
+		PageCount:  req.Pagination.PageSize,
+		ReturnType: "Map",
+		ParamMaps: &model.TrainNumberParams{
+			PastureId: fmt.Sprintf("%d", req.PastureId),
+			InfoRName: req.InfoName,
+		},
+	}
+	response := &model.PastureCommonResponse{Data: &model.PastureCommonData{}}
+	if _, err := s.PastureHttpClient(ctx, model.UrlDataByName, int64(req.PastureId), body, response); err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	result := &operationPb.TrainNumberResponse{
+		Code: http.StatusOK,
+		Msg:  "ok",
+		Data: &operationPb.TrainNumberData{List: make([]*operationPb.FormulaOptionEnum, 0)},
+	}
+
+	if response.Data.List == nil {
+		return result, nil
+	}
+	b, _ := json.Marshal(response.Data.List)
+	trainNumberList := make([]*model.TrainNumberList, 0)
+	if err := json.Unmarshal(b, &trainNumberList); err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	formulaOption := make([]*operationPb.FormulaOptionEnum, 0)
+	if len(trainNumberList) > 0 {
+		infoValue := trainNumberList[0].InfoValue
+		switch infoValue {
+		case "1":
+			formulaOption = append(formulaOption, &operationPb.FormulaOptionEnum{
+				Value: 1,
+				Label: "第一班",
+			})
+		case "2":
+			formulaOption = append(formulaOption, &operationPb.FormulaOptionEnum{
+				Value: 1,
+				Label: "第一班",
+			}, &operationPb.FormulaOptionEnum{
+				Value: 2,
+				Label: "第二班",
+			})
+		case "3":
+			formulaOption = append(formulaOption, &operationPb.FormulaOptionEnum{
+				Value: 1,
+				Label: "第一班",
+			}, &operationPb.FormulaOptionEnum{
+				Value: 2,
+				Label: "第二班",
+			}, &operationPb.FormulaOptionEnum{
+				Value: 3,
+				Label: "第三班",
+			})
+		}
+	}
+
+	result.Data.List = formulaOption
+	return result, nil
 }

+ 38 - 8
module/backend/system_permissions.go

@@ -7,6 +7,8 @@ import (
 	"kpt-tmr-group/pkg/xerr"
 	operationPb "kpt-tmr-group/proto/go/backend/operation"
 	"net/http"
+	"sort"
+	"strings"
 	"sync"
 	"time"
 
@@ -32,24 +34,24 @@ func (s *StoreEntry) AllPermissionsListToRolePermissions(req *SystemAllPermissio
 	wg := sync.WaitGroup{}
 	wg.Add(3)
 	go func() {
+		defer wg.Done()
 		for _, v := range req.MobileList {
 			res.Data.MobileList = append(res.Data.MobileList, uint32(v.MobileId))
 		}
-		wg.Done()
 	}()
 
 	go func() {
+		defer wg.Done()
 		for _, v := range req.MenuList {
 			res.Data.MenuList = append(res.Data.MenuList, uint32(v.MenuId))
 		}
-		wg.Done()
 	}()
 
 	go func() {
+		defer wg.Done()
 		for _, v := range req.PastureList {
 			res.Data.PastureList = append(res.Data.PastureList, uint32(v.PastureId))
 		}
-		wg.Done()
 	}()
 
 	wg.Wait()
@@ -73,9 +75,10 @@ func (s *StoreEntry) SystemPermissionsFormatPb(pastureList []*model.GroupPasture
 		Code: http.StatusOK,
 		Msg:  "ok",
 		Data: &operationPb.SystemUserMenuData{
-			PastureList: make([]*operationPb.AddPastureRequest, 0),
-			MenuList:    make([]*operationPb.AddMenuRequest, 0),
-			MobileList:  make([]*operationPb.AddMobileRequest, 0),
+			PastureList:     make([]*operationPb.AddPastureRequest, 0),
+			MenuList:        make([]*operationPb.AddMenuRequest, 0),
+			MobileList:      make([]*operationPb.AddMobileRequest, 0),
+			MenuButtonsPath: make([]*operationPb.MenuButtonsPath, 0),
 		},
 	}
 
@@ -120,10 +123,21 @@ func (s *StoreEntry) SystemPermissionsFormatPb(pastureList []*model.GroupPasture
 				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[model.Level3] {
-			for _, leve2Data := range level[model.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)
@@ -133,7 +147,19 @@ func (s *StoreEntry) SystemPermissionsFormatPb(pastureList []*model.GroupPasture
 			}
 		}
 
+		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 {
@@ -144,6 +170,10 @@ func (s *StoreEntry) SystemPermissionsFormatPb(pastureList []*model.GroupPasture
 			}
 		}
 
+		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()
 	}()

+ 74 - 11
module/backend/system_service.go

@@ -6,6 +6,7 @@ import (
 	"fmt"
 	"kpt-tmr-group/model"
 	"kpt-tmr-group/pkg/jwt"
+	"kpt-tmr-group/pkg/logger/zaplog"
 	"kpt-tmr-group/pkg/tool"
 	"kpt-tmr-group/pkg/xerr"
 	operationPb "kpt-tmr-group/proto/go/backend/operation"
@@ -13,6 +14,8 @@ import (
 	"strconv"
 	"strings"
 
+	"go.uber.org/zap"
+
 	"gorm.io/gorm"
 )
 
@@ -26,6 +29,9 @@ func (s *StoreEntry) Auth(ctx context.Context, auth *operationPb.UserAuthData) (
 	if systemUser.Password != auth.Password {
 		return nil, xerr.Customf("密码错误,来自用户:%s", auth.UserName)
 	}
+	if systemUser.IsShow == operationPb.IsShow_NO {
+		return nil, xerr.Customf("该账号已被禁用,请联系管理员")
+	}
 
 	token, err := jwt.GenerateToken(systemUser.Name, systemUser.Password)
 	if err != nil {
@@ -157,11 +163,10 @@ func (s *StoreEntry) EditSystemUser(ctx context.Context, req *operationPb.AddSys
 		Name:         req.Name,
 		EmployeeName: req.EmployeeName,
 		Phone:        req.Phone,
-		CreateUser:   req.CreateUser,
 	}
 	updateData.SystemUserRoleFormat(req)
 
-	if err := s.DB.Model(new(model.SystemUser)).Omit("is_show", "password", "is_delete").
+	if err := s.DB.Model(new(model.SystemUser)).Omit("is_show", "password", "is_delete", "create_user").
 		Where("id = ?", systemUser.Id).
 		Updates(updateData).Error; err != nil {
 		return xerr.WithStack(err)
@@ -421,7 +426,7 @@ func (s *StoreEntry) DeleteSystemRole(ctx context.Context, roleId int64) error {
 
 // SearchSystemRoleList 查询系统角色
 func (s *StoreEntry) SearchSystemRoleList(ctx context.Context, req *operationPb.SearchRoleRequest) (*operationPb.SearchRoleResponse, error) {
-	systemRole := make([]*model.SystemRole, 0)
+	systemRoleList := make([]*model.SystemRole, 0)
 	var count int64 = 0
 
 	pref := s.DB.Model(new(model.SystemRole)).Where("is_show = ?", operationPb.IsShow_OK)
@@ -429,11 +434,35 @@ func (s *StoreEntry) SearchSystemRoleList(ctx context.Context, req *operationPb.
 		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(&systemRole).Debug().Error; err != nil {
+	if err := pref.Order("id desc").Count(&count).Limit(int(req.Pagination.PageSize)).
+		Offset(int(req.Pagination.PageOffset)).Find(&systemRoleList).Error; err != nil {
 		return nil, xerr.WithStack(err)
 	}
 
+	permissionsPastureMap := make(map[int64][]*model.GroupPasture, 0)
+	permissionsMenuMap := make(map[int64][]*model.SystemMenu, 0)
+	for _, role := range systemRoleList {
+		groupPastureList := make([]*model.GroupPasture, 0)
+		if err := s.DB.Table(new(model.GroupPasture).TableName()).Select(fmt.Sprintf("%s.*", new(model.GroupPasture).TableName())).
+			Joins(fmt.Sprintf("left join %s on %s = %s and %s", new(model.SystemGroupPasturePermissions).TableName(), fmt.Sprintf("%s.pasture_id", new(model.SystemGroupPasturePermissions).TableName()),
+				fmt.Sprintf("%s.id", new(model.GroupPasture).TableName()), fmt.Sprintf("%s.is_show = %d", new(model.SystemGroupPasturePermissions).TableName(), operationPb.IsShow_OK))).
+			Where(fmt.Sprintf("%s.role_id = ?", new(model.SystemGroupPasturePermissions).TableName()), role.Id).
+			Find(&groupPastureList).Error; err != nil {
+			zaplog.Error("SearchSystemRoleList", zap.Any("SystemGroupPasturePermissions", err))
+		}
+		permissionsPastureMap[role.Id] = groupPastureList
+
+		systemMenuList := make([]*model.SystemMenu, 0)
+		if err := s.DB.Table(new(model.SystemMenu).TableName()).Select(fmt.Sprintf("%s.*", new(model.SystemMenu).TableName())).
+			Joins(fmt.Sprintf("left join %s on %s = %s and %s", new(model.SystemMenuPermissions).TableName(), fmt.Sprintf("%s.menu_id", new(model.SystemMenuPermissions).TableName()),
+				fmt.Sprintf("%s.id", new(model.SystemMenu).TableName()), fmt.Sprintf("%s.is_show = %d", new(model.SystemMenuPermissions).TableName(), operationPb.IsShow_OK))).
+			Where(fmt.Sprintf("%s.role_id = ?", new(model.SystemMenuPermissions).TableName()), role.Id).Where(fmt.Sprintf("%s.level = ?", new(model.SystemMenu).TableName()), model.Level2).
+			Find(&systemMenuList).Error; err != nil {
+			zaplog.Error("SearchSystemRoleList", zap.Any("SystemMenuPermissions", err))
+		}
+		permissionsMenuMap[role.Id] = systemMenuList
+	}
+
 	return &operationPb.SearchRoleResponse{
 		Code: http.StatusOK,
 		Msg:  "ok",
@@ -441,7 +470,7 @@ func (s *StoreEntry) SearchSystemRoleList(ctx context.Context, req *operationPb.
 			Page:     req.Pagination.Page,
 			Total:    int32(count),
 			PageSize: req.Pagination.PageSize,
-			List:     model.SystemRoleSlice(systemRole).ToPB(),
+			List:     model.SystemRoleSlice(systemRoleList).ToPB(permissionsPastureMap, permissionsMenuMap),
 		},
 	}, nil
 }
@@ -540,30 +569,64 @@ func (s *StoreEntry) IsShowSystemMenu(ctx context.Context, req *operationPb.IsSh
 
 // SearchSystemMenuList 菜单列表查询
 func (s *StoreEntry) SearchSystemMenuList(ctx context.Context, req *operationPb.SearchMenuRequest) (*operationPb.SearchMenuResponse, error) {
-	systemMenu := make([]*model.SystemMenu, 0)
+	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)
 	}
 
+	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(systemMenu).ToPB(),
+			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}

+ 2 - 2
module/backend/wx_applet_service.go

@@ -18,8 +18,8 @@ const (
 )
 
 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)
+	url := fmt.Sprintf("%s?appid=%s&secret=%s&js_code=%s&grant_type=%s", OPENID_URL, s.HttpClient.AppID, s.HttpClient.Secret, jsCode, GRANT_TYPE)
+	res, err := s.HttpClient.DoGet(url)
 	if err != nil {
 		zaplog.Error("GetOpenId", zap.Any("DoGet", err), zap.String("url", url))
 		return nil, xerr.WithStack(err)

+ 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)
+	}
+}

+ 18 - 22
pkg/logger/zaplog/log.go

@@ -2,20 +2,19 @@ package zaplog
 
 import (
 	"fmt"
-	"kpt-tmr-group/pkg/tool"
 	"path"
 	"runtime"
-	"time"
-
-	"github.com/natefinch/lumberjack"
+	"strings"
 
+	rotatelogs "github.com/lestrrat-go/file-rotatelogs"
 	"go.uber.org/zap"
 	"go.uber.org/zap/zapcore"
 )
 
 var (
-	Logger      *zap.Logger
-	logFileName = fmt.Sprintf("./logger/zap-%s.log", time.Now().Format(tool.DateTime))
+	Logger          *zap.Logger
+	logInfoFileName = fmt.Sprintf("./logger/zap-info.log")
+	logErrFileName  = fmt.Sprintf("./logger/zap-err.log")
 )
 
 func init() {
@@ -26,25 +25,28 @@ func init() {
 	encoder := zapcore.NewJSONEncoder(encoderConfig)
 
 	// topicErrors := zapcore.AddSync(ioutil.Discard)  //kafka topic
-
-	fileWriteSyncer := getFileLogWriter(logFileName)
+	fileInfoWriteSyncer := getFileLogWriter(logInfoFileName)
+	fileErrWriteSyncer := getFileLogWriter(logErrFileName)
 	core := zapcore.NewTee(
 		// zapcore.NewCore(encoder, zapcore.AddSync(os.Stdout), zapcore.DebugLevel),  // 打印到控制台
 		// zapcore.NewCore(zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()), topicErrors, zapcore.ErrorLevel),   // 打印到kafka 待验证
-		zapcore.NewCore(encoder, fileWriteSyncer, zapcore.DebugLevel), // 打印到指定的日志文件
+		zapcore.NewCore(encoder, fileInfoWriteSyncer, zapcore.InfoLevel), // 打印到指定的日志文件
+		zapcore.NewCore(encoder, fileErrWriteSyncer, zapcore.ErrorLevel), // 打印到指定的日志文件
 	)
 	Logger = zap.New(core)
 }
 
 func getFileLogWriter(logFileName string) zapcore.WriteSyncer {
-	lumberJackLogger := &lumberjack.Logger{
-		Filename:   logFileName,
-		MaxSize:    100, // 单个文件最大100M
-		MaxBackups: 10,  // 大于60个日志文件后,清理比较旧的日志文件
-		MaxAge:     1,   // 一天切割1次
-		Compress:   false,
+	lumberRotateLogs, err := rotatelogs.New(
+		strings.Replace(logFileName, ".log", "", -1)+"-%Y%m%d.log",
+		rotatelogs.WithLinkName(logFileName),
+		rotatelogs.WithRotationCount(2),        // 2天的日志
+		rotatelogs.WithRotationSize(210000000), // 一个文件200MB
+	)
+	if err != nil {
+		panic(err)
 	}
-	return zapcore.AddSync(lumberJackLogger)
+	return zapcore.AddSync(lumberRotateLogs)
 }
 
 func getCallerInfoForLog() (callerFields []zap.Field) {
@@ -76,9 +78,3 @@ func Error(message string, fields ...zap.Field) {
 	fields = append(fields, callerFields...)
 	Logger.Error(message, fields...)
 }
-
-func Warn(message string, fields ...zap.Field) {
-	callerFields := getCallerInfoForLog()
-	fields = append(fields, callerFields...)
-	Logger.Warn(message, fields...)
-}

+ 33 - 0
pkg/stringutil/random.go

@@ -0,0 +1,33 @@
+package stringutil
+
+import (
+	"fmt"
+	"math/rand"
+	"strconv"
+	"time"
+)
+
+func init() {
+	rand.Seed(time.Now().UnixNano())
+}
+
+const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
+
+// UniqueID returns randomly generated string with prefix
+func UniqueID(prefix string, l uint) string {
+	return fmt.Sprintf("%s%s", prefix, RandomString(l))
+}
+
+func UniqueIDBaseTime(prefix string) string {
+	timeStr := strconv.FormatUint(uint64(time.Now().UnixNano()), 36)
+	return fmt.Sprintf("%s%s%s", prefix, timeStr, RandomString(2))
+}
+
+// RandomString returns randomly generated string
+func RandomString(l uint) string {
+	s := make([]byte, l)
+	for i := 0; i < int(l); i++ {
+		s[i] = chars[rand.Intn(len(chars))]
+	}
+	return string(s)
+}

+ 68 - 0
pkg/tool/tool.go

@@ -4,6 +4,7 @@ import (
 	"crypto/md5"
 	"encoding/hex"
 	"fmt"
+	"sort"
 	"strconv"
 	"strings"
 	"time"
@@ -67,3 +68,70 @@ func Md5String(input string) string {
 	s.Write([]byte(digest))
 	return hex.EncodeToString(s.Sum(nil))
 }
+
+// TimeBetween 获取指定时间范围内天数
+// eg startTime => "2023-06-01" endTime => "2023-06-03"
+// return ["2023-06-01","2023-06-02","2023-06-03"]
+func TimeBetween(startTime, endTime string) []string {
+	startDate, _ := time.ParseInLocation("2006-01-02", startTime, time.Local)
+	endDate, _ := time.ParseInLocation("2006-01-02", endTime, time.Local)
+	timeList := make([]string, 0)
+	for endDate.After(startDate) {
+		timeList = append(timeList, startDate.Format("2006-01-02"))
+		startDate = startDate.AddDate(0, 0, 1)
+	}
+	timeList = append(timeList, endTime)
+	return timeList
+}
+
+// Median 获取切片的中位值
+func Median(nums []float64) float64 {
+	sort.Float64s(nums)
+	n := len(nums)
+	if n%2 == 0 {
+		return (nums[n/2-1] + nums[n/2]) / float64(2)
+	} else {
+		return nums[n/2]
+	}
+}
+
+// MedianInt64 获取切片的中位值
+func MedianInt64(nums []int64) int64 {
+	sort.Slice(nums, func(i, j int) bool {
+		return nums[i] < nums[j]
+	})
+	n := len(nums)
+	if n%2 == 0 {
+		return (nums[n/2-1] + nums[n/2]) / 2
+	} else {
+		return nums[n/2]
+	}
+}
+
+// TimeSub 计算两个时间的差值
+// eg startTime => "2022-05-01 09:07:08"
+// eg endTime => "00:06:35"
+// eg return =>  2022-05-01 09:00:33
+func TimeSub(startTime, endTime string) string {
+	str := strings.Split(endTime, ":")
+	if len(str) < 3 {
+		return ""
+	}
+	durationStr := fmt.Sprintf("%sh%sm%ss", str[0], str[1], str[2])
+
+	timeValue, err := time.Parse(time.RFC3339, startTime)
+	if err != nil {
+		fmt.Println("解析时间出错:", err)
+		return ""
+	}
+
+	// 解析持续时间字符串为 time.Duration 类型
+	durationValue, err := time.ParseDuration(durationStr)
+	if err != nil {
+		fmt.Println("解析持续时间出错:", err)
+		return ""
+	}
+
+	// 计算时间之间的差异
+	return timeValue.Add(-durationValue).Format(Layout)
+}

+ 0 - 42
pkg/tool/tool_test.go

@@ -8,48 +8,6 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
-func TestConditionsInterpreter(t *testing.T) {
-	conditions := `[
-    {
-        "condition_item": [
-            {
-                "condition_name": "aaa",
-                "conditions_kind": 2,
-                "conditions_value": "11111"
-            },
-            {
-                "condition_name": "bbb",
-                "conditions_kind": 1,
-                "conditions_value": "22222"
-            }
-        ]
-    },
-    {
-        "condition_item": [
-            {
-                "condition_name": "ccc",
-                "conditions_kind": 4,
-                "conditions_value": "3333"
-            },
-            {
-                "condition_name": "ddddd",
-                "conditions_kind": 1,
-                "conditions_value": "4444"
-            }
-        ]
-    }
-]`
-	t.Run("ok", func(t *testing.T) {
-		sql, err := ConditionsInterpreter(conditions)
-		if err != nil {
-			t.Error(err)
-		}
-		want := "( aaa != '11111'  AND bbb = '22222' ) OR ( ccc <= '3333'  AND ddddd = '4444' )"
-		assert.Equal(t, sql, want)
-	})
-
-}
-
 func TestTimeParseLocalUnix(t *testing.T) {
 	t.Run("ok", func(t *testing.T) {
 		testMap := map[string]int64{

+ 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

+ 126 - 16
proto/go/backend/operation/enum.pb.go

@@ -72,12 +72,12 @@ func (IsShow_Kind) EnumDescriptor() ([]byte, []int) {
 type CattleCategoryParent_Kind int32
 
 const (
-	CattleCategoryParent_INVALID       CattleCategoryParent_Kind = 0 // 无效
+	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_PERINATAL_CAW CattleCategoryParent_Kind = 5 // 围产
 	CattleCategoryParent_OTHER_CAW     CattleCategoryParent_Kind = 6 // 其他
 )
 
@@ -387,6 +387,58 @@ 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
@@ -658,6 +710,44 @@ 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{
@@ -702,9 +792,15 @@ var file_backend_operation_enum_proto_rawDesc = []byte{
 	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, 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,
+	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 (
@@ -719,8 +815,8 @@ 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, 7)
-var file_backend_operation_enum_proto_msgTypes = make([]protoimpl.MessageInfo, 7)
+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
 	(CattleCategoryParent_Kind)(0), // 1: backend.operation.CattleCategoryParent.Kind
@@ -729,13 +825,15 @@ var file_backend_operation_enum_proto_goTypes = []interface{}{
 	(JumpDelaType_Kind)(0),         // 4: backend.operation.JumpDelaType.Kind
 	(ForagePlanType_Kind)(0),       // 5: backend.operation.ForagePlanType.Kind
 	(DataSource_Kind)(0),           // 6: backend.operation.DataSource.Kind
-	(*IsShow)(nil),                 // 7: backend.operation.IsShow
-	(*CattleCategoryParent)(nil),   // 8: backend.operation.CattleCategoryParent
-	(*ForageCategoryParent)(nil),   // 9: backend.operation.ForageCategoryParent
-	(*ForageSource)(nil),           // 10: backend.operation.ForageSource
-	(*JumpDelaType)(nil),           // 11: backend.operation.JumpDelaType
-	(*ForagePlanType)(nil),         // 12: backend.operation.ForagePlanType
-	(*DataSource)(nil),             // 13: backend.operation.DataSource
+	(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
@@ -835,14 +933,26 @@ func file_backend_operation_enum_proto_init() {
 				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:      7,
-			NumMessages:   7,
+			NumEnums:      8,
+			NumMessages:   8,
 			NumExtensions: 0,
 			NumServices:   0,
 		},

+ 591 - 55
proto/go/backend/operation/feed_formula.pb.go

@@ -41,7 +41,7 @@ type AddFeedFormulaRequest struct {
 	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"`                                                                   // 牧场名称
+	PastureName        string                    `protobuf:"bytes,17,opt,name=pasture_name,json=pastureName,proto3" json:"pasture_name,omitempty"`                                                                   // 牧场名称
 }
 
 func (x *AddFeedFormulaRequest) Reset() {
@@ -295,10 +295,9 @@ type SearchFeedFormulaListResponse struct {
 	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"`
+	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 *SearchFeedFormulaListData `protobuf:"bytes,3,opt,name=data,proto3" json:"data,omitempty"`
 }
 
 func (x *SearchFeedFormulaListResponse) Reset() {
@@ -333,28 +332,92 @@ func (*SearchFeedFormulaListResponse) Descriptor() ([]byte, []int) {
 	return file_backend_operation_feed_formula_proto_rawDescGZIP(), []int{2}
 }
 
-func (x *SearchFeedFormulaListResponse) GetPage() int32 {
+func (x *SearchFeedFormulaListResponse) GetCode() int32 {
+	if x != nil {
+		return x.Code
+	}
+	return 0
+}
+
+func (x *SearchFeedFormulaListResponse) GetMsg() string {
+	if x != nil {
+		return x.Msg
+	}
+	return ""
+}
+
+func (x *SearchFeedFormulaListResponse) GetData() *SearchFeedFormulaListData {
+	if x != nil {
+		return x.Data
+	}
+	return nil
+}
+
+type SearchFeedFormulaListData 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 *SearchFeedFormulaListData) Reset() {
+	*x = SearchFeedFormulaListData{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_backend_operation_feed_formula_proto_msgTypes[3]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *SearchFeedFormulaListData) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*SearchFeedFormulaListData) ProtoMessage() {}
+
+func (x *SearchFeedFormulaListData) 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 SearchFeedFormulaListData.ProtoReflect.Descriptor instead.
+func (*SearchFeedFormulaListData) Descriptor() ([]byte, []int) {
+	return file_backend_operation_feed_formula_proto_rawDescGZIP(), []int{3}
+}
+
+func (x *SearchFeedFormulaListData) GetPage() int32 {
 	if x != nil {
 		return x.Page
 	}
 	return 0
 }
 
-func (x *SearchFeedFormulaListResponse) GetPageSize() int32 {
+func (x *SearchFeedFormulaListData) GetPageSize() int32 {
 	if x != nil {
 		return x.PageSize
 	}
 	return 0
 }
 
-func (x *SearchFeedFormulaListResponse) GetTotal() int32 {
+func (x *SearchFeedFormulaListData) GetTotal() int32 {
 	if x != nil {
 		return x.Total
 	}
 	return 0
 }
 
-func (x *SearchFeedFormulaListResponse) GetList() []*AddFeedFormulaRequest {
+func (x *SearchFeedFormulaListData) GetList() []*AddFeedFormulaRequest {
 	if x != nil {
 		return x.List
 	}
@@ -375,7 +438,7 @@ type IsShowModifyFeedFormula struct {
 func (x *IsShowModifyFeedFormula) Reset() {
 	*x = IsShowModifyFeedFormula{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_backend_operation_feed_formula_proto_msgTypes[3]
+		mi := &file_backend_operation_feed_formula_proto_msgTypes[4]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -388,7 +451,7 @@ func (x *IsShowModifyFeedFormula) String() string {
 func (*IsShowModifyFeedFormula) ProtoMessage() {}
 
 func (x *IsShowModifyFeedFormula) ProtoReflect() protoreflect.Message {
-	mi := &file_backend_operation_feed_formula_proto_msgTypes[3]
+	mi := &file_backend_operation_feed_formula_proto_msgTypes[4]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -401,7 +464,7 @@ func (x *IsShowModifyFeedFormula) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use IsShowModifyFeedFormula.ProtoReflect.Descriptor instead.
 func (*IsShowModifyFeedFormula) Descriptor() ([]byte, []int) {
-	return file_backend_operation_feed_formula_proto_rawDescGZIP(), []int{3}
+	return file_backend_operation_feed_formula_proto_rawDescGZIP(), []int{4}
 }
 
 func (x *IsShowModifyFeedFormula) GetFeedFormulaId() int32 {
@@ -425,6 +488,341 @@ func (x *IsShowModifyFeedFormula) GetEditType() int32 {
 	return 0
 }
 
+// 配方编码
+type UniqueID 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 *UniqueID_UniqueData `protobuf:"bytes,3,opt,name=data,proto3" json:"data,omitempty"`
+}
+
+func (x *UniqueID) Reset() {
+	*x = UniqueID{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_backend_operation_feed_formula_proto_msgTypes[5]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *UniqueID) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*UniqueID) ProtoMessage() {}
+
+func (x *UniqueID) ProtoReflect() protoreflect.Message {
+	mi := &file_backend_operation_feed_formula_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 UniqueID.ProtoReflect.Descriptor instead.
+func (*UniqueID) Descriptor() ([]byte, []int) {
+	return file_backend_operation_feed_formula_proto_rawDescGZIP(), []int{5}
+}
+
+func (x *UniqueID) GetCode() int32 {
+	if x != nil {
+		return x.Code
+	}
+	return 0
+}
+
+func (x *UniqueID) GetMsg() string {
+	if x != nil {
+		return x.Msg
+	}
+	return ""
+}
+
+func (x *UniqueID) GetData() *UniqueID_UniqueData {
+	if x != nil {
+		return x.Data
+	}
+	return nil
+}
+
+// DistributeFeedFormulaRequest 饲料配方下发
+type DistributeFeedFormulaRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	PastureIds     []int32 `protobuf:"varint,1,rep,packed,name=pasture_ids,json=pastureIds,proto3" json:"pasture_ids,omitempty"`               // 牧场ids集合
+	FeedFormulaIds []int32 `protobuf:"varint,2,rep,packed,name=feed_formula_ids,json=feedFormulaIds,proto3" json:"feed_formula_ids,omitempty"` // 配方ids集合
+}
+
+func (x *DistributeFeedFormulaRequest) Reset() {
+	*x = DistributeFeedFormulaRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_backend_operation_feed_formula_proto_msgTypes[6]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *DistributeFeedFormulaRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*DistributeFeedFormulaRequest) ProtoMessage() {}
+
+func (x *DistributeFeedFormulaRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_backend_operation_feed_formula_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 DistributeFeedFormulaRequest.ProtoReflect.Descriptor instead.
+func (*DistributeFeedFormulaRequest) Descriptor() ([]byte, []int) {
+	return file_backend_operation_feed_formula_proto_rawDescGZIP(), []int{6}
+}
+
+func (x *DistributeFeedFormulaRequest) GetPastureIds() []int32 {
+	if x != nil {
+		return x.PastureIds
+	}
+	return nil
+}
+
+func (x *DistributeFeedFormulaRequest) GetFeedFormulaIds() []int32 {
+	if x != nil {
+		return x.FeedFormulaIds
+	}
+	return nil
+}
+
+// 配方使用概况
+type FeedFormulaUsageRequest 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"` // 饲料配方id
+	StartTime     string `protobuf:"bytes,2,opt,name=start_time,json=startTime,proto3" json:"start_time,omitempty"`                // 开始时间
+	EndTime       string `protobuf:"bytes,3,opt,name=end_time,json=endTime,proto3" json:"end_time,omitempty"`                      // 结束时间
+}
+
+func (x *FeedFormulaUsageRequest) Reset() {
+	*x = FeedFormulaUsageRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_backend_operation_feed_formula_proto_msgTypes[7]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *FeedFormulaUsageRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*FeedFormulaUsageRequest) ProtoMessage() {}
+
+func (x *FeedFormulaUsageRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_backend_operation_feed_formula_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 FeedFormulaUsageRequest.ProtoReflect.Descriptor instead.
+func (*FeedFormulaUsageRequest) Descriptor() ([]byte, []int) {
+	return file_backend_operation_feed_formula_proto_rawDescGZIP(), []int{7}
+}
+
+func (x *FeedFormulaUsageRequest) GetFeedFormulaId() int32 {
+	if x != nil {
+		return x.FeedFormulaId
+	}
+	return 0
+}
+
+func (x *FeedFormulaUsageRequest) GetStartTime() string {
+	if x != nil {
+		return x.StartTime
+	}
+	return ""
+}
+
+func (x *FeedFormulaUsageRequest) GetEndTime() string {
+	if x != nil {
+		return x.EndTime
+	}
+	return ""
+}
+
+// 配方使用概况
+type FeedFormulaUsageResponse struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	List []*FeedFormulaUsageList `protobuf:"bytes,1,rep,name=list,proto3" json:"list,omitempty"`
+}
+
+func (x *FeedFormulaUsageResponse) Reset() {
+	*x = FeedFormulaUsageResponse{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_backend_operation_feed_formula_proto_msgTypes[8]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *FeedFormulaUsageResponse) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*FeedFormulaUsageResponse) ProtoMessage() {}
+
+func (x *FeedFormulaUsageResponse) ProtoReflect() protoreflect.Message {
+	mi := &file_backend_operation_feed_formula_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 FeedFormulaUsageResponse.ProtoReflect.Descriptor instead.
+func (*FeedFormulaUsageResponse) Descriptor() ([]byte, []int) {
+	return file_backend_operation_feed_formula_proto_rawDescGZIP(), []int{8}
+}
+
+func (x *FeedFormulaUsageResponse) GetList() []*FeedFormulaUsageList {
+	if x != nil {
+		return x.List
+	}
+	return nil
+}
+
+// 配方使用概况
+type FeedFormulaUsageList struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	PastureId   int32  `protobuf:"varint,1,opt,name=pasture_id,json=pastureId,proto3" json:"pasture_id,omitempty"`
+	PastureName string `protobuf:"bytes,2,opt,name=pasture_name,json=pastureName,proto3" json:"pasture_name,omitempty"`
+}
+
+func (x *FeedFormulaUsageList) Reset() {
+	*x = FeedFormulaUsageList{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_backend_operation_feed_formula_proto_msgTypes[9]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *FeedFormulaUsageList) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*FeedFormulaUsageList) ProtoMessage() {}
+
+func (x *FeedFormulaUsageList) ProtoReflect() protoreflect.Message {
+	mi := &file_backend_operation_feed_formula_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 FeedFormulaUsageList.ProtoReflect.Descriptor instead.
+func (*FeedFormulaUsageList) Descriptor() ([]byte, []int) {
+	return file_backend_operation_feed_formula_proto_rawDescGZIP(), []int{9}
+}
+
+func (x *FeedFormulaUsageList) GetPastureId() int32 {
+	if x != nil {
+		return x.PastureId
+	}
+	return 0
+}
+
+func (x *FeedFormulaUsageList) GetPastureName() string {
+	if x != nil {
+		return x.PastureName
+	}
+	return ""
+}
+
+type UniqueID_UniqueData struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	EncodeNumber string `protobuf:"bytes,1,opt,name=encode_number,json=encodeNumber,proto3" json:"encode_number,omitempty"`
+}
+
+func (x *UniqueID_UniqueData) Reset() {
+	*x = UniqueID_UniqueData{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_backend_operation_feed_formula_proto_msgTypes[10]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *UniqueID_UniqueData) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*UniqueID_UniqueData) ProtoMessage() {}
+
+func (x *UniqueID_UniqueData) ProtoReflect() protoreflect.Message {
+	mi := &file_backend_operation_feed_formula_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 UniqueID_UniqueData.ProtoReflect.Descriptor instead.
+func (*UniqueID_UniqueData) Descriptor() ([]byte, []int) {
+	return file_backend_operation_feed_formula_proto_rawDescGZIP(), []int{5, 0}
+}
+
+func (x *UniqueID_UniqueData) GetEncodeNumber() string {
+	if x != nil {
+		return x.EncodeNumber
+	}
+	return ""
+}
+
 var File_backend_operation_feed_formula_proto protoreflect.FileDescriptor
 
 var file_backend_operation_feed_formula_proto_rawDesc = []byte{
@@ -480,8 +878,8 @@ var file_backend_operation_feed_formula_proto_rawDesc = []byte{
 	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, 0x50, 0x61, 0x73, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x6e, 0x61, 0x6d,
-	0x65, 0x18, 0x11, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x50, 0x61, 0x73, 0x74, 0x75, 0x72, 0x65,
+	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,
@@ -502,28 +900,72 @@ var file_backend_operation_feed_formula_proto_rawDesc = []byte{
 	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,
+	0x69, 0x6f, 0x6e, 0x22, 0x87, 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,
+	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, 0x40, 0x0a, 0x04, 0x64,
+	0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 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, 0x65, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x4c,
+	0x69, 0x73, 0x74, 0x44, 0x61, 0x74, 0x61, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0xa0, 0x01,
+	0x0a, 0x19, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x46, 0x65, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d,
+	0x75, 0x6c, 0x61, 0x4c, 0x69, 0x73, 0x74, 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,
+	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, 0x22, 0x9f, 0x01, 0x0a, 0x08, 0x55,
+	0x6e, 0x69, 0x71, 0x75, 0x65, 0x49, 0x44, 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, 0x3a, 0x0a,
+	0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x62, 0x61,
+	0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e,
+	0x55, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x49, 0x44, 0x2e, 0x55, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x44,
+	0x61, 0x74, 0x61, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x1a, 0x31, 0x0a, 0x0a, 0x55, 0x6e, 0x69,
+	0x71, 0x75, 0x65, 0x44, 0x61, 0x74, 0x61, 0x12, 0x23, 0x0a, 0x0d, 0x65, 0x6e, 0x63, 0x6f, 0x64,
+	0x65, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c,
+	0x65, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x22, 0x69, 0x0a, 0x1c,
+	0x44, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x46, 0x65, 0x65, 0x64, 0x46, 0x6f,
+	0x72, 0x6d, 0x75, 0x6c, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b,
+	0x70, 0x61, 0x73, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28,
+	0x05, 0x52, 0x0a, 0x70, 0x61, 0x73, 0x74, 0x75, 0x72, 0x65, 0x49, 0x64, 0x73, 0x12, 0x28, 0x0a,
+	0x10, 0x66, 0x65, 0x65, 0x64, 0x5f, 0x66, 0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x5f, 0x69, 0x64,
+	0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x05, 0x52, 0x0e, 0x66, 0x65, 0x65, 0x64, 0x46, 0x6f, 0x72,
+	0x6d, 0x75, 0x6c, 0x61, 0x49, 0x64, 0x73, 0x22, 0x7b, 0x0a, 0x17, 0x46, 0x65, 0x65, 0x64, 0x46,
+	0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65,
+	0x73, 0x74, 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, 0x1d, 0x0a, 0x0a, 0x73, 0x74,
+	0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 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, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x65, 0x6e, 0x64,
+	0x54, 0x69, 0x6d, 0x65, 0x22, 0x57, 0x0a, 0x18, 0x46, 0x65, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d,
+	0x75, 0x6c, 0x61, 0x55, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
+	0x12, 0x3b, 0x0a, 0x04, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27,
+	0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69,
+	0x6f, 0x6e, 0x2e, 0x46, 0x65, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x55, 0x73,
+	0x61, 0x67, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x04, 0x6c, 0x69, 0x73, 0x74, 0x22, 0x58, 0x0a,
+	0x14, 0x46, 0x65, 0x65, 0x64, 0x46, 0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x55, 0x73, 0x61, 0x67,
+	0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x61, 0x73, 0x74, 0x75, 0x72, 0x65,
+	0x5f, 0x69, 0x64, 0x18, 0x01, 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, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x61, 0x73, 0x74,
+	0x75, 0x72, 0x65, 0x4e, 0x61, 0x6d, 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 (
@@ -538,31 +980,41 @@ func file_backend_operation_feed_formula_proto_rawDescGZIP() []byte {
 	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_msgTypes = make([]protoimpl.MessageInfo, 11)
 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
+	(*SearchFeedFormulaListData)(nil),     // 3: backend.operation.SearchFeedFormulaListData
+	(*IsShowModifyFeedFormula)(nil),       // 4: backend.operation.IsShowModifyFeedFormula
+	(*UniqueID)(nil),                      // 5: backend.operation.UniqueID
+	(*DistributeFeedFormulaRequest)(nil),  // 6: backend.operation.DistributeFeedFormulaRequest
+	(*FeedFormulaUsageRequest)(nil),       // 7: backend.operation.FeedFormulaUsageRequest
+	(*FeedFormulaUsageResponse)(nil),      // 8: backend.operation.FeedFormulaUsageResponse
+	(*FeedFormulaUsageList)(nil),          // 9: backend.operation.FeedFormulaUsageList
+	(*UniqueID_UniqueData)(nil),           // 10: backend.operation.UniqueID.UniqueData
+	(CattleCategoryParent_Kind)(0),        // 11: backend.operation.CattleCategoryParent.Kind
+	(DataSource_Kind)(0),                  // 12: backend.operation.DataSource.Kind
+	(IsShow_Kind)(0),                      // 13: backend.operation.IsShow.Kind
+	(*PaginationModel)(nil),               // 14: 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
+	11, // 0: backend.operation.AddFeedFormulaRequest.cattle_category_id:type_name -> backend.operation.CattleCategoryParent.Kind
+	12, // 1: backend.operation.AddFeedFormulaRequest.data_source_id:type_name -> backend.operation.DataSource.Kind
+	13, // 2: backend.operation.AddFeedFormulaRequest.is_show:type_name -> backend.operation.IsShow.Kind
+	13, // 3: backend.operation.AddFeedFormulaRequest.is_modify:type_name -> backend.operation.IsShow.Kind
+	13, // 4: backend.operation.SearchFeedFormulaRequest.is_show:type_name -> backend.operation.IsShow.Kind
+	14, // 5: backend.operation.SearchFeedFormulaRequest.pagination:type_name -> backend.operation.PaginationModel
+	3,  // 6: backend.operation.SearchFeedFormulaListResponse.data:type_name -> backend.operation.SearchFeedFormulaListData
+	0,  // 7: backend.operation.SearchFeedFormulaListData.list:type_name -> backend.operation.AddFeedFormulaRequest
+	13, // 8: backend.operation.IsShowModifyFeedFormula.is_show:type_name -> backend.operation.IsShow.Kind
+	10, // 9: backend.operation.UniqueID.data:type_name -> backend.operation.UniqueID.UniqueData
+	9,  // 10: backend.operation.FeedFormulaUsageResponse.list:type_name -> backend.operation.FeedFormulaUsageList
+	11, // [11:11] is the sub-list for method output_type
+	11, // [11:11] is the sub-list for method input_type
+	11, // [11:11] is the sub-list for extension type_name
+	11, // [11:11] is the sub-list for extension extendee
+	0,  // [0:11] is the sub-list for field type_name
 }
 
 func init() { file_backend_operation_feed_formula_proto_init() }
@@ -610,6 +1062,18 @@ func file_backend_operation_feed_formula_proto_init() {
 			}
 		}
 		file_backend_operation_feed_formula_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*SearchFeedFormulaListData); 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[4].Exporter = func(v interface{}, i int) interface{} {
 			switch v := v.(*IsShowModifyFeedFormula); i {
 			case 0:
 				return &v.state
@@ -621,6 +1085,78 @@ func file_backend_operation_feed_formula_proto_init() {
 				return nil
 			}
 		}
+		file_backend_operation_feed_formula_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*UniqueID); 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[6].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*DistributeFeedFormulaRequest); 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[7].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*FeedFormulaUsageRequest); 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[8].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*FeedFormulaUsageResponse); 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[9].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*FeedFormulaUsageList); 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[10].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*UniqueID_UniqueData); 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{
@@ -628,7 +1164,7 @@ func file_backend_operation_feed_formula_proto_init() {
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			RawDescriptor: file_backend_operation_feed_formula_proto_rawDesc,
 			NumEnums:      0,
-			NumMessages:   4,
+			NumMessages:   11,
 			NumExtensions: 0,
 			NumServices:   0,
 		},

+ 36 - 19
proto/go/backend/operation/mobile.pb.go

@@ -214,10 +214,11 @@ type MobileData struct {
 	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"`
+	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() {
@@ -280,6 +281,13 @@ func (x *MobileData) GetCreatedAtFormat() string {
 	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{
@@ -288,7 +296,9 @@ var file_backend_operation_mobile_proto_rawDesc = []byte{
 	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, 0x22, 0x6d, 0x0a, 0x13, 0x53, 0x65, 0x61, 0x72, 0x63,
+	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,
@@ -311,15 +321,19 @@ var file_backend_operation_mobile_proto_rawDesc = []byte{
 	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, 0x7b, 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, 0x42, 0x0f, 0x5a, 0x0d, 0x2e, 0x3b, 0x6f,
+	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,
 }
@@ -343,16 +357,18 @@ var file_backend_operation_mobile_proto_goTypes = []interface{}{
 	(*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
-	3, // [3:3] is the sub-list for method output_type
-	3, // [3:3] is the sub-list for method input_type
-	3, // [3:3] is the sub-list for extension type_name
-	3, // [3:3] is the sub-list for extension extendee
-	0, // [0:3] is the sub-list for field type_name
+	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() }
@@ -361,6 +377,7 @@ func file_backend_operation_mobile_proto_init() {
 		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 {

Разлика између датотеке није приказан због своје велике величине
+ 620 - 201
proto/go/backend/operation/pasture.pb.go


Разлика између датотеке није приказан због своје велике величине
+ 1841 - 303
proto/go/backend/operation/statistic.pb.go


+ 140 - 34
proto/go/backend/operation/system.pb.go

@@ -193,6 +193,8 @@ type AddRoleRequest struct {
 	CreateUser      string      `protobuf:"bytes,8,opt,name=create_user,json=createUser,proto3" json:"create_user,omitempty"`                         // 创建用户
 	CreatedAt       uint32      `protobuf:"varint,9,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"`                           // 创建时间
 	CreatedAtFormat string      `protobuf:"bytes,10,opt,name=created_at_format,json=createdAtFormat,proto3" json:"created_at_format,omitempty"`       // 创建时间格式化
+	PastureList     []string    `protobuf:"bytes,11,rep,name=pasture_list,json=pastureList,proto3" json:"pasture_list,omitempty"`                     // 负责的牧场
+	MenuList        []string    `protobuf:"bytes,12,rep,name=menu_list,json=menuList,proto3" json:"menu_list,omitempty"`                              // 权限列表
 }
 
 func (x *AddRoleRequest) Reset() {
@@ -297,6 +299,20 @@ func (x *AddRoleRequest) GetCreatedAtFormat() string {
 	return ""
 }
 
+func (x *AddRoleRequest) GetPastureList() []string {
+	if x != nil {
+		return x.PastureList
+	}
+	return nil
+}
+
+func (x *AddRoleRequest) GetMenuList() []string {
+	if x != nil {
+		return x.MenuList
+	}
+	return nil
+}
+
 type SearchRoleRequest struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
@@ -1880,9 +1896,10 @@ type SystemUserMenuData struct {
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	PastureList []*AddPastureRequest `protobuf:"bytes,1,rep,name=pasture_list,json=pastureList,proto3" json:"pasture_list,omitempty"` // 牧场列表
-	MenuList    []*AddMenuRequest    `protobuf:"bytes,2,rep,name=menu_list,json=menuList,proto3" json:"menu_list,omitempty"`          // 菜单列表
-	MobileList  []*AddMobileRequest  `protobuf:"bytes,3,rep,name=mobile_list,json=mobileList,proto3" json:"mobile_list,omitempty"`    // 移动端权限
+	PastureList     []*AddPastureRequest `protobuf:"bytes,1,rep,name=pasture_list,json=pastureList,proto3" json:"pasture_list,omitempty"`               // 牧场列表
+	MenuList        []*AddMenuRequest    `protobuf:"bytes,2,rep,name=menu_list,json=menuList,proto3" json:"menu_list,omitempty"`                        // 菜单列表
+	MobileList      []*AddMobileRequest  `protobuf:"bytes,3,rep,name=mobile_list,json=mobileList,proto3" json:"mobile_list,omitempty"`                  // 移动端权限
+	MenuButtonsPath []*MenuButtonsPath   `protobuf:"bytes,4,rep,name=menu_buttons_path,json=menuButtonsPath,proto3" json:"menu_buttons_path,omitempty"` // 按钮级别权限path,前端需要特别处理
 }
 
 func (x *SystemUserMenuData) Reset() {
@@ -1938,6 +1955,68 @@ func (x *SystemUserMenuData) GetMobileList() []*AddMobileRequest {
 	return nil
 }
 
+func (x *SystemUserMenuData) GetMenuButtonsPath() []*MenuButtonsPath {
+	if x != nil {
+		return x.MenuButtonsPath
+	}
+	return nil
+}
+
+type MenuButtonsPath struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Path   string `protobuf:"bytes,1,opt,name=path,proto3" json:"path,omitempty"`
+	MenuId int32  `protobuf:"varint,2,opt,name=menu_id,json=menuId,proto3" json:"menu_id,omitempty"`
+}
+
+func (x *MenuButtonsPath) Reset() {
+	*x = MenuButtonsPath{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_backend_operation_system_proto_msgTypes[27]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *MenuButtonsPath) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*MenuButtonsPath) ProtoMessage() {}
+
+func (x *MenuButtonsPath) ProtoReflect() protoreflect.Message {
+	mi := &file_backend_operation_system_proto_msgTypes[27]
+	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 MenuButtonsPath.ProtoReflect.Descriptor instead.
+func (*MenuButtonsPath) Descriptor() ([]byte, []int) {
+	return file_backend_operation_system_proto_rawDescGZIP(), []int{27}
+}
+
+func (x *MenuButtonsPath) GetPath() string {
+	if x != nil {
+		return x.Path
+	}
+	return ""
+}
+
+func (x *MenuButtonsPath) GetMenuId() int32 {
+	if x != nil {
+		return x.MenuId
+	}
+	return 0
+}
+
 // 移动端
 type AddMobileRequest struct {
 	state         protoimpl.MessageState
@@ -1951,7 +2030,7 @@ type AddMobileRequest struct {
 func (x *AddMobileRequest) Reset() {
 	*x = AddMobileRequest{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_backend_operation_system_proto_msgTypes[27]
+		mi := &file_backend_operation_system_proto_msgTypes[28]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -1964,7 +2043,7 @@ func (x *AddMobileRequest) String() string {
 func (*AddMobileRequest) ProtoMessage() {}
 
 func (x *AddMobileRequest) ProtoReflect() protoreflect.Message {
-	mi := &file_backend_operation_system_proto_msgTypes[27]
+	mi := &file_backend_operation_system_proto_msgTypes[28]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -1977,7 +2056,7 @@ func (x *AddMobileRequest) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use AddMobileRequest.ProtoReflect.Descriptor instead.
 func (*AddMobileRequest) Descriptor() ([]byte, []int) {
-	return file_backend_operation_system_proto_rawDescGZIP(), []int{27}
+	return file_backend_operation_system_proto_rawDescGZIP(), []int{28}
 }
 
 func (x *AddMobileRequest) GetId() uint32 {
@@ -2017,7 +2096,7 @@ var file_backend_operation_system_proto_rawDesc = []byte{
 	0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x22, 0x22, 0x0a,
 	0x08, 0x57, 0x78, 0x4f, 0x70, 0x65, 0x6e, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x70, 0x65,
 	0x6e, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x70, 0x65, 0x6e, 0x69,
-	0x64, 0x22, 0xc8, 0x02, 0x0a, 0x0e, 0x41, 0x64, 0x64, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x71,
+	0x64, 0x22, 0x88, 0x03, 0x0a, 0x0e, 0x41, 0x64, 0x64, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x71,
 	0x75, 0x65, 0x73, 0x74, 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, 0x18, 0x0a, 0x07, 0x72, 0x65, 0x6d, 0x61,
@@ -2037,7 +2116,11 @@ var file_backend_operation_system_proto_rawDesc = []byte{
 	0x09, 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, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x63, 0x72, 0x65,
-	0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x22, 0x6b, 0x0a, 0x11,
+	0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x12, 0x21, 0x0a, 0x0c,
+	0x70, 0x61, 0x73, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x0b, 0x20, 0x03,
+	0x28, 0x09, 0x52, 0x0b, 0x70, 0x61, 0x73, 0x74, 0x75, 0x72, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12,
+	0x1b, 0x0a, 0x09, 0x6d, 0x65, 0x6e, 0x75, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x0c, 0x20, 0x03,
+	0x28, 0x09, 0x52, 0x08, 0x6d, 0x65, 0x6e, 0x75, 0x4c, 0x69, 0x73, 0x74, 0x22, 0x6b, 0x0a, 0x11,
 	0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x6f, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
 	0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52,
 	0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x42, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74,
@@ -2249,7 +2332,7 @@ var file_backend_operation_system_proto_rawDesc = []byte{
 	0x12, 0x39, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25,
 	0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69,
 	0x6f, 0x6e, 0x2e, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x55, 0x73, 0x65, 0x72, 0x4d, 0x65, 0x6e,
-	0x75, 0x44, 0x61, 0x74, 0x61, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0xe3, 0x01, 0x0a, 0x12,
+	0x75, 0x44, 0x61, 0x74, 0x61, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0xb3, 0x02, 0x0a, 0x12,
 	0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x55, 0x73, 0x65, 0x72, 0x4d, 0x65, 0x6e, 0x75, 0x44, 0x61,
 	0x74, 0x61, 0x12, 0x47, 0x0a, 0x0c, 0x70, 0x61, 0x73, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x6c, 0x69,
 	0x73, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65,
@@ -2264,7 +2347,16 @@ var file_backend_operation_system_proto_rawDesc = []byte{
 	0x32, 0x23, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x6f, 0x70, 0x65, 0x72, 0x61,
 	0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x41, 0x64, 0x64, 0x4d, 0x6f, 0x62, 0x69, 0x6c, 0x65, 0x52, 0x65,
 	0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x0a, 0x6d, 0x6f, 0x62, 0x69, 0x6c, 0x65, 0x4c, 0x69, 0x73,
-	0x74, 0x22, 0x36, 0x0a, 0x10, 0x41, 0x64, 0x64, 0x4d, 0x6f, 0x62, 0x69, 0x6c, 0x65, 0x52, 0x65,
+	0x74, 0x12, 0x4e, 0x0a, 0x11, 0x6d, 0x65, 0x6e, 0x75, 0x5f, 0x62, 0x75, 0x74, 0x74, 0x6f, 0x6e,
+	0x73, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x04, 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, 0x4d, 0x65, 0x6e, 0x75, 0x42, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x73, 0x50, 0x61, 0x74, 0x68,
+	0x52, 0x0f, 0x6d, 0x65, 0x6e, 0x75, 0x42, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x73, 0x50, 0x61, 0x74,
+	0x68, 0x22, 0x3e, 0x0a, 0x0f, 0x4d, 0x65, 0x6e, 0x75, 0x42, 0x75, 0x74, 0x74, 0x6f, 0x6e, 0x73,
+	0x50, 0x61, 0x74, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01,
+	0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x17, 0x0a, 0x07, 0x6d, 0x65, 0x6e, 0x75,
+	0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x6d, 0x65, 0x6e, 0x75, 0x49,
+	0x64, 0x22, 0x36, 0x0a, 0x10, 0x41, 0x64, 0x64, 0x4d, 0x6f, 0x62, 0x69, 0x6c, 0x65, 0x52, 0x65,
 	0x71, 0x75, 0x65, 0x73, 0x74, 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, 0x42, 0x0f, 0x5a, 0x0d, 0x2e, 0x3b, 0x6f,
@@ -2284,7 +2376,7 @@ func file_backend_operation_system_proto_rawDescGZIP() []byte {
 	return file_backend_operation_system_proto_rawDescData
 }
 
-var file_backend_operation_system_proto_msgTypes = make([]protoimpl.MessageInfo, 28)
+var file_backend_operation_system_proto_msgTypes = make([]protoimpl.MessageInfo, 29)
 var file_backend_operation_system_proto_goTypes = []interface{}{
 	(*CommonOK)(nil),                  // 0: backend.operation.CommonOK
 	(*Success)(nil),                   // 1: backend.operation.Success
@@ -2313,46 +2405,48 @@ var file_backend_operation_system_proto_goTypes = []interface{}{
 	(*SearchMenuData)(nil),            // 24: backend.operation.SearchMenuData
 	(*SystemUserMenuPermissions)(nil), // 25: backend.operation.SystemUserMenuPermissions
 	(*SystemUserMenuData)(nil),        // 26: backend.operation.SystemUserMenuData
-	(*AddMobileRequest)(nil),          // 27: backend.operation.AddMobileRequest
-	(IsShow_Kind)(0),                  // 28: backend.operation.IsShow.Kind
-	(*PaginationModel)(nil),           // 29: backend.operation.PaginationModel
-	(*UserPasture)(nil),               // 30: backend.operation.UserPasture
-	(*AddPastureRequest)(nil),         // 31: backend.operation.AddPastureRequest
+	(*MenuButtonsPath)(nil),           // 27: backend.operation.MenuButtonsPath
+	(*AddMobileRequest)(nil),          // 28: backend.operation.AddMobileRequest
+	(IsShow_Kind)(0),                  // 29: backend.operation.IsShow.Kind
+	(*PaginationModel)(nil),           // 30: backend.operation.PaginationModel
+	(*UserPasture)(nil),               // 31: backend.operation.UserPasture
+	(*AddPastureRequest)(nil),         // 32: backend.operation.AddPastureRequest
 }
 var file_backend_operation_system_proto_depIdxs = []int32{
 	1,  // 0: backend.operation.CommonOK.data:type_name -> backend.operation.Success
-	28, // 1: backend.operation.AddRoleRequest.is_show:type_name -> backend.operation.IsShow.Kind
-	29, // 2: backend.operation.SearchRoleRequest.pagination:type_name -> backend.operation.PaginationModel
+	29, // 1: backend.operation.AddRoleRequest.is_show:type_name -> backend.operation.IsShow.Kind
+	30, // 2: backend.operation.SearchRoleRequest.pagination:type_name -> backend.operation.PaginationModel
 	6,  // 3: backend.operation.SearchRoleResponse.data:type_name -> backend.operation.SearchRoleData
 	3,  // 4: backend.operation.SearchRoleData.list:type_name -> backend.operation.AddRoleRequest
 	8,  // 5: backend.operation.RolePermissionsList.data:type_name -> backend.operation.RolePermissionsData
 	10, // 6: backend.operation.SystemToken.data:type_name -> backend.operation.TokenData
 	12, // 7: backend.operation.UserAuth.data:type_name -> backend.operation.UserAuthData
 	13, // 8: backend.operation.UserAuthData.roles:type_name -> backend.operation.UserRole
-	30, // 9: backend.operation.UserAuthData.pastures:type_name -> backend.operation.UserPasture
+	31, // 9: backend.operation.UserAuthData.pastures:type_name -> backend.operation.UserPasture
 	15, // 10: backend.operation.UserDetails.data:type_name -> backend.operation.AddSystemUser
 	13, // 11: backend.operation.AddSystemUser.roles:type_name -> backend.operation.UserRole
-	28, // 12: backend.operation.AddSystemUser.is_show:type_name -> backend.operation.IsShow.Kind
-	28, // 13: backend.operation.SearchUserRequest.is_show:type_name -> backend.operation.IsShow.Kind
-	29, // 14: backend.operation.SearchUserRequest.pagination:type_name -> backend.operation.PaginationModel
+	29, // 12: backend.operation.AddSystemUser.is_show:type_name -> backend.operation.IsShow.Kind
+	29, // 13: backend.operation.SearchUserRequest.is_show:type_name -> backend.operation.IsShow.Kind
+	30, // 14: backend.operation.SearchUserRequest.pagination:type_name -> backend.operation.PaginationModel
 	18, // 15: backend.operation.SearchUserResponse.data:type_name -> backend.operation.SearchUserData
 	15, // 16: backend.operation.SearchUserData.list:type_name -> backend.operation.AddSystemUser
-	28, // 17: backend.operation.IsShowSystemUserRequest.is_show:type_name -> backend.operation.IsShow.Kind
-	28, // 18: backend.operation.AddMenuRequest.is_show:type_name -> backend.operation.IsShow.Kind
+	29, // 17: backend.operation.IsShowSystemUserRequest.is_show:type_name -> backend.operation.IsShow.Kind
+	29, // 18: backend.operation.AddMenuRequest.is_show:type_name -> backend.operation.IsShow.Kind
 	20, // 19: backend.operation.AddMenuRequest.children:type_name -> backend.operation.AddMenuRequest
-	28, // 20: backend.operation.IsShowSystemMenuRequest.is_show:type_name -> backend.operation.IsShow.Kind
-	29, // 21: backend.operation.SearchMenuRequest.pagination:type_name -> backend.operation.PaginationModel
+	29, // 20: backend.operation.IsShowSystemMenuRequest.is_show:type_name -> backend.operation.IsShow.Kind
+	30, // 21: backend.operation.SearchMenuRequest.pagination:type_name -> backend.operation.PaginationModel
 	24, // 22: backend.operation.SearchMenuResponse.data:type_name -> backend.operation.SearchMenuData
 	20, // 23: backend.operation.SearchMenuData.list:type_name -> backend.operation.AddMenuRequest
 	26, // 24: backend.operation.SystemUserMenuPermissions.data:type_name -> backend.operation.SystemUserMenuData
-	31, // 25: backend.operation.SystemUserMenuData.pasture_list:type_name -> backend.operation.AddPastureRequest
+	32, // 25: backend.operation.SystemUserMenuData.pasture_list:type_name -> backend.operation.AddPastureRequest
 	20, // 26: backend.operation.SystemUserMenuData.menu_list:type_name -> backend.operation.AddMenuRequest
-	27, // 27: backend.operation.SystemUserMenuData.mobile_list:type_name -> backend.operation.AddMobileRequest
-	28, // [28:28] is the sub-list for method output_type
-	28, // [28:28] is the sub-list for method input_type
-	28, // [28:28] is the sub-list for extension type_name
-	28, // [28:28] is the sub-list for extension extendee
-	0,  // [0:28] is the sub-list for field type_name
+	28, // 27: backend.operation.SystemUserMenuData.mobile_list:type_name -> backend.operation.AddMobileRequest
+	27, // 28: backend.operation.SystemUserMenuData.menu_buttons_path:type_name -> backend.operation.MenuButtonsPath
+	29, // [29:29] is the sub-list for method output_type
+	29, // [29:29] is the sub-list for method input_type
+	29, // [29:29] is the sub-list for extension type_name
+	29, // [29:29] is the sub-list for extension extendee
+	0,  // [0:29] is the sub-list for field type_name
 }
 
 func init() { file_backend_operation_system_proto_init() }
@@ -2689,6 +2783,18 @@ func file_backend_operation_system_proto_init() {
 			}
 		}
 		file_backend_operation_system_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*MenuButtonsPath); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_backend_operation_system_proto_msgTypes[28].Exporter = func(v interface{}, i int) interface{} {
 			switch v := v.(*AddMobileRequest); i {
 			case 0:
 				return &v.state
@@ -2707,7 +2813,7 @@ func file_backend_operation_system_proto_init() {
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			RawDescriptor: file_backend_operation_system_proto_rawDesc,
 			NumEnums:      0,
-			NumMessages:   28,
+			NumMessages:   29,
 			NumExtensions: 0,
 			NumServices:   0,
 		},

+ 0 - 41
scripts/consumer-images.sh

@@ -1,41 +0,0 @@
-#!/bin/bash
-set -e
-
-tag=$1
-if [ -n "$tag" ]
-then
-  echo "当前镜像tag: $tag"
-else
-  echo "请输入当前镜像tag"
-  exit
-fi
-
-### go testing
-echo "============ go test start ================"
-make ci-test
-echo "============  go test end  ================"
-
-### go build
-echo "============ go build start ================"
-make build version=${tag}
-echo "============  go build end  ================"
-
-echo "============push images start================"
-
-export aliYunDockerDNS=registry.cn-hangzhou.aliyuncs.com
-export aliYunDockerUsername=kptzhu@163.com
-export aliYunDockerPassword=zhuz1898
-export images=kpt-event/event-consumer
-
-docker build -t ${images}:${tag} -f ./docker/consumer/Dockerfile .
-docker login ${aliYunDockerDNS} --username ${aliYunDockerUsername} --password ${aliYunDockerPassword}
-
-docker tag ${images}:${tag}  ${aliYunDockerDNS}/${images}:${tag}
-docker tag ${images}:${tag}  ${aliYunDockerDNS}/${images}:latest
-
-docker push ${aliYunDockerDNS}/${images}:${tag}
-docker push ${aliYunDockerDNS}/${images}:latest
-
-docker rmi ${images}:${tag} ${aliYunDockerDNS}/${images}:${tag} ${aliYunDockerDNS}/${images}:latest
-
-echo "============push images end ================"

+ 2 - 2
scripts/http-images.sh

@@ -25,9 +25,9 @@ echo "============push images start================"
 export aliYunDockerDNS=registry.cn-hangzhou.aliyuncs.com
 export aliYunDockerUsername=kptzhu@163.com
 export aliYunDockerPassword=zhuz1898
-export images=kpt-event/event-http
+export images=kpt-event/kpt-tmr-group
 
-docker build -t ${images}:${tag} -f ./docker/http/Dockerfile .
+docker build -t ${images}:${tag} -f ./Dockerfile .
 docker login ${aliYunDockerDNS} --username ${aliYunDockerUsername} --password ${aliYunDockerPassword}
 
 docker tag ${images}:${tag}  ${aliYunDockerDNS}/${images}:${tag}

+ 48 - 0
service/excel/excel_export.go

@@ -0,0 +1,48 @@
+package excel
+
+import (
+	"bytes"
+	"fmt"
+	"kpt-tmr-group/pkg/logger/zaplog"
+	"kpt-tmr-group/pkg/xerr"
+
+	"github.com/xuri/excelize/v2"
+	"go.uber.org/zap"
+)
+
+func Export(titles []interface{}, rows []interface{}) (*bytes.Buffer, error) {
+	file := excelize.NewFile()
+	defer file.Close()
+
+	streamWriter, err := file.NewStreamWriter("Sheet1")
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	if err = streamWriter.SetRow("A1", titles); err != nil {
+		return nil, xerr.WithStack(err)
+	}
+	for i, item := range rows {
+		cell, err := excelize.CoordinatesToCellName(1, i+2)
+		if err != nil {
+			zaplog.Error("excel.CoordinatesToCellName", zap.Any("Err", err))
+			continue
+		}
+		row := make([]interface{}, 0)
+		// TODO 待封装
+		fmt.Println(item)
+		/*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()
+}

+ 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,
+)

Неке датотеке нису приказане због велике количине промена