Parcourir la source

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

xuyiping il y a 1 an
Parent
commit
d13e1fa00d
48 fichiers modifiés avec 5240 ajouts et 1141 suppressions
  1. 15 15
      .drone.yml
  2. 0 58
      .drone.yml.bak
  3. 10 11
      Dockerfile
  4. 2 3
      README.md
  5. 1 1
      backend/operation/enum.proto
  6. 23 1
      backend/operation/feed_formula.proto
  7. 16 9
      backend/operation/pasture.proto
  8. 192 67
      backend/operation/statistic.proto
  9. 28 0
      config/app.develop.yaml
  10. 1 4
      config/app.go
  11. 3 3
      config/app.test.yaml
  12. 2 2
      config/load_config_test.go
  13. 1 2
      go.mod
  14. 0 3
      go.sum
  15. 112 0
      http/handler/dashboard/dashboard.go
  16. 28 1
      http/handler/feed/feed_formula.go
  17. 0 1
      http/handler/pasture/forage_list.go
  18. 446 12
      http/handler/statistic/analysis.go
  19. 5 1
      http/middleware/cors.go
  20. 2 2
      http/middleware/sso.go
  21. 23 4
      http/route/app_api.go
  22. 1 0
      http/route/root.go
  23. 1 1
      main.go
  24. 103 14
      model/analysis_accuracy.go
  25. 5 0
      model/feed_formula.go
  26. 211 129
      model/formula_estimate.go
  27. 163 2
      model/group_pasture.go
  28. 38 0
      model/pasture_data.go
  29. 26 0
      model/pasture_data_log.go
  30. 4 1
      model/system_role.go
  31. 13 0
      model/unique_data.go
  32. 552 0
      module/backend/dashboard_service.go
  33. 118 7
      module/backend/feed_service.go
  34. 29 7
      module/backend/interface.go
  35. 255 2
      module/backend/mock/kptservice.go
  36. 115 31
      module/backend/pasture_service.go
  37. 563 118
      module/backend/statistic_service.go
  38. 2 2
      module/backend/wx_applet_service.go
  39. 18 22
      pkg/logger/zaplog/log.go
  40. 33 0
      pkg/stringutil/random.go
  41. 68 0
      pkg/tool/tool.go
  42. 0 42
      pkg/tool/tool_test.go
  43. 361 52
      proto/go/backend/operation/feed_formula.pb.go
  44. 351 257
      proto/go/backend/operation/pasture.pb.go
  45. 1250 211
      proto/go/backend/operation/statistic.pb.go
  46. 0 41
      scripts/consumer-images.sh
  47. 2 2
      scripts/http-images.sh
  48. 48 0
      service/excel/excel_export.go

+ 15 - 15
.drone.yml

@@ -6,22 +6,11 @@ clone:
   depth: 1
   disable: true
 
-environment:
-  APP_ENVIRONMENT: test
-  GO_WORK_DIR: /bin/kptTmrGroup
-
 steps:
   - name: clone
     image: alpine/git
     commands:
       - git clone -b develop http://kpt.kptyun.cn:3000/xuyiping/kpt-tmr-group.git
-  - name: test
-    image: golang:1.17
-    commands:
-      - cd /drone/src/kpt-tmr-group
-      - go test `go list ./... | grep -v -E "mock|store|test|fake|cmd|bin|backend|google|logger|proto"` -coverprofile .cover.txt
-      - go tool cover -func .cover.txt
-      - rm .cover.txt
   - name: build
     image: plugins/docker
     volumes:
@@ -39,15 +28,26 @@ steps:
         from_secret: aliyuncs_password
       repo: registry.cn-hangzhou.aliyuncs.com/kpt-event/kpt-tmr-group
       registry: registry.cn-hangzhou.aliyuncs.com
-      tags: [1.0.0,latest]
+      tags: [ 1.0.0,latest ]
+  - name: remove-local-image
+    image: appleboy/drone-ssh
+    settings:
+      host: 192.168.1.70
+      username: tmrwatch
+      password:
+        from_secret: tmrwatch_password
+      port: 22
+      script:
+        - docker rm registry.cn-hangzhou.aliyuncs.com/kpt-event/kpt-tmr-group:1.0.0
+
 
 trigger:
   branch:
-   include:
-     - develop
+    include:
+    - develop
   event:
+    include:
     - push
-    - merge
 
 volumes:
   - name: docker-ca

+ 0 - 58
.drone.yml.bak

@@ -1,58 +0,0 @@
-kind: pipeline
-type: docker
-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
-  - name: build
-    image: plugins/docker
-    volumes:
-      - name: hosts
-        path: /etc/hosts
-      - name: docker-ca
-        path: /etc/docker
-      - name: dockersock
-        path: /var/run/docker.sock
-    settings:
-      dockerfile: /drone/src/kpt-tmr-group/Dockerfile
-      username:
-        from_secret: aliyuncs_username
-      password:
-        from_secret: aliyuncs_password
-      repo: registry.cn-hangzhou.aliyuncs.com/kpt-event/kpt-tmr-group
-      registry: registry.cn-hangzhou.aliyuncs.com
-      tags: [ 1.0.0,latest ]
-  - name: remove-local-image
-    image: appleboy/drone-ssh
-    settings:
-      host: 192.168.1.70
-      username: tmrwatch
-      password:
-        from_secret: tmrwatch_password
-      port: 22
-      script:
-        - docker rm registry.cn-hangzhou.aliyuncs.com/kpt-event/kpt-tmr-group:1.0.0
-
-
-trigger:
-  branch:
-    include:
-    - develop
-  event:
-    include:
-    - push
-
-volumes:
-  - name: docker-ca
-    host:
-      path: /etc/docker
-  - name: dockersock
-    host:
-      path: /var/run/docker.sock

+ 10 - 11
Dockerfile

@@ -1,5 +1,4 @@
-FROM golang:1.17-alpine3.16 as builder
-WORKDIR /go/src/kpt-tmr-group
+FROM alpine:latest
 
 LABEL name="kpt-tmr-group" \
 description="pt service" \
@@ -7,17 +6,17 @@ owner="yiping.xu"
 
 WORKDIR /bin
 
-RUN cd /drone/src/kpt-tmr-group \
-    && go env -w GO111MODULE=on \
-    && go env -w GOPROXY=https://goproxy.cn,direct \
-    && go env -w CGO_ENABLED=0 \
-    && go build -o ./bin/kptTmrGroup \
-    && ls -l ./bin
+COPY  /drone/src/kpt-tmr-group ./
+
+RUN rm -rf bin && \
+    mkdir -p bin && \
+    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
+
 
 ADD ./bin/kptTmrGroup /bin/kptTmrGroup
 COPY config/*.yaml /bin/config/
 
-EXPOSE 8080
-VOLUME /var/Logger
+EXPOSE 8090
+VOLUME /bin/logger
 
-CMD ["/go/src/kpt-tmr-group/bin/kptTmrGroup","http"]
+CMD ["/bin/kptTmrGroup","http"]

+ 2 - 3
README.md

@@ -33,10 +33,9 @@ lint:
 
 TODO 列表
 - proto3 int64 jsonpb处理后自动转成string
-  * 现在处理的方式是把int64类型改成int32类型
+  * ~~现在处理的方式是把int64类型改成int32类型~~
 - 用户登出没有用redis做缓存,所以后端没有提供登出接口,有以下弊端
   * 用户的token没有过期,如被人劫持,会被使用到直到token过期
   
 - 饲料列表--小料车下拉框问题
-- 权限列表排序的问题
-- test
+- ~~权限列表排序的问题~~

+ 1 - 1
backend/operation/enum.proto

@@ -80,4 +80,4 @@ message FormulaType {
     PREMIXED_FORMULA = 2;          // 预混配方
     SUPPLEMENTARY_FORMULA = 3;     // 补料配方
   }
-}
+}

+ 23 - 1
backend/operation/feed_formula.proto

@@ -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,21 @@ 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集合
 }

+ 16 - 9
backend/operation/pasture.proto

@@ -112,7 +112,8 @@ message SearchForageCategoryRequest {
   string parent_name = 1;
   IsShow.Kind is_show = 2;
   string name = 3;
-  PaginationModel pagination = 4; // 分页
+  string number = 4;
+  PaginationModel pagination = 5; // 分页
 }
 
 message SearchForageCategoryResponse {
@@ -194,15 +195,21 @@ message ForageEnumListResponse {
 }
 
 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 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 {

+ 192 - 67
backend/operation/statistic.proto

@@ -6,86 +6,203 @@ 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; // 分页
 }
 
-message SearchFormulaEstimateResponse {
+// 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;               // 牧场端接口标识名称
+  int32 pasture_id = 3;              // 牧场id
+  string formula_template = 4;       // 配方模板名称
+  string barn_name = 5;               // 栏舍名称
+  string cattle_category_name = 6;   // 畜牧类别名称
+  int32 class_number = 7;            // 班次
+  PaginationModel pagination = 8;   // 分页
+}
+
+// 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 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 SearchFormulaEstimate {
-  int32 page = 1;
-  int32 total = 2;
-  int32 page_size = 3;
-  repeated AddFormulaEstimateRequest list = 4;
+message TrainNumberData {
+  repeated FormulaOptionEnum list = 1;
 }
 
+
 // 首页 dashboard 准确性分析
 message SearchAnalysisAccuracyRequest {
   CattleCategoryParent.Kind cattle_parent_category_id = 1;   // 牧畜分类id 泌乳牛
@@ -133,4 +250,12 @@ message CommonValueRatio {
 
 message ValueRatio {
   repeated string value_ratio = 1;
+}
+
+// 首页 dashboard 撒料时间统计分析
+message SprinkleFeedTimeRequest {
+  int32 feed_formula_id = 1;    // 配方id
+  string start_date = 2;       // 开始时间
+  string end_date = 3;         // 结束时间
+  repeated int32 pasture_ids = 4;   //牧场ids
 }

+ 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 = "kptTmrGroup"
-
 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)

+ 3 - 3
config/app.test.yaml

@@ -1,4 +1,4 @@
-app_name: kpt-event
+app_name: kpt-tmr-group
 app_environment: test
 debug: true
 http_server_addr: ':8000'
@@ -7,8 +7,8 @@ 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:

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

+ 1 - 2
go.mod

@@ -22,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
@@ -35,6 +34,7 @@ require (
 	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
 )
@@ -93,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
 )

+ 0 - 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=
@@ -249,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=

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

+ 28 - 1
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 {
@@ -248,3 +247,31 @@ 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},
+	})
+}

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

@@ -194,7 +194,6 @@ func ExcelImportForage(c *gin.Context) {
 }
 
 func ExcelExportForage(c *gin.Context) {
-
 	req := &operationPb.SearchForageListRequest{}
 	if err := ginutil.BindProto(c, req); err != nil {
 		apierr.AbortBadRequest(c, http.StatusBadRequest, err)

+ 446 - 12
http/handler/statistic/analysis.go

@@ -1,20 +1,32 @@
 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
 	}
@@ -25,35 +37,457 @@ 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.SearchFormulaEstimateList(c, &req)
 	if err != nil {
 		apierr.ClassifiedAbort(c, err)
 		return
 	}
-	ginutil.JSONResp(c, res)
+	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)
 }
 
-// AnalysisAccuracy 准备性分析
-func AnalysisAccuracy(c *gin.Context) {
-	var req operationPb.SearchAnalysisAccuracyRequest
-	if err := c.BindJSON(&req); err != nil {
+// 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.PastureIds, valid.Required),
-		valid.Field(&req.CattleParentCategoryId, valid.Required),
+		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
 	}
 
-	res, err := middleware.BackendOperation(c).OpsService.SearchAnalysisAccuracy(c, &req)
+	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)
-	//ginutil.JSONResp(c, 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
+	}
+
+	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.GetTrainNumber(c, &req)
+	if err != nil {
+		apierr.ClassifiedAbort(c, err)
+		return
+	}
+	ginutil.JSONResp(c, res)
 }

+ 5 - 1
http/middleware/cors.go

@@ -41,7 +41,11 @@ func CORS(configs ...cors.Config) gin.HandlerFunc {
 
 		defer func() {
 			if err := recover(); err != nil {
-				zaplog.Error("cors", zap.Any("recover", err))
+				zaplog.Error("cors",
+					zap.Any("recover", err),
+					zap.Any("url", c.Request.URL),
+					zap.Any("method", method),
+				)
 			}
 		}()
 

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

+ 23 - 4
http/route/app_api.go

@@ -2,6 +2,7 @@ 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"
@@ -101,13 +102,31 @@ func AppAPI(opts ...func(engine *gin.Engine)) func(s *gin.Engine) {
 		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)
 
 		//统计分析 statistic analysis
 		opsRoute.POST("/feed_estimate/list", statistic.SearchFormulaEstimateList)
-
-		opsRoute.GET("/analysis/accuracy", statistic.AnalysisAccuracy)
-
-		// 其他
+		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)
+
+		// 首页仪表盘
+		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)
 	}
 }
 

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

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

+ 103 - 14
model/analysis_accuracy.go

@@ -27,18 +27,6 @@ func (c *AnalysisAccuracy) TableName() string {
 	return "analysis_accuracy"
 }
 
-type OptionsAnalysisAccuracy struct {
-	PastureId            int64  `json:"pasture_id"`
-	PastureName          string `json:"pasture_name"`
-	AllIweight           int64  `json:"all_iweight"`
-	AllLweight           int64  `json:"all_lweight"`
-	AllOweight           int64  `json:"all_oweight"`
-	AllActualWeightMinus int64  `json:"all_actual_weight_minus"`
-	AllAllowRatio        int64  `json:"all_allow_ratio"`
-	AllAlweight          int64  `json:"all_alweight"`
-	DateDay              string `json:"date_day"`
-}
-
 type SearchAnalysisAccuracyResponse struct {
 	Code int32                 `json:"code"`
 	Msg  string                `json:"msg"`
@@ -51,8 +39,8 @@ type AnalysisAccuracyData struct {
 }
 
 type Table struct {
-	TitleList []*TableList   `json:"title_list"`
-	DataList  []*interface{} `json:"data_list"`
+	TitleList []*TableList `json:"title_list"`
+	DataList  *DataList    `json:"data_list"`
 }
 
 type TableList struct {
@@ -60,6 +48,13 @@ type TableList struct {
 	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"`
@@ -71,8 +66,102 @@ 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"`
+}

+ 5 - 0
model/feed_formula.go

@@ -79,3 +79,8 @@ func (f FeedFormulaSlice) ToPB() []*operationPb.AddFeedFormulaRequest {
 	}
 	return res
 }
+
+type DistributeData struct {
+	PastureList     []*GroupPasture
+	FeedFormulaList []*FeedFormula
+}

+ 211 - 129
model/formula_estimate.go

@@ -1,132 +1,214 @@
 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"`
+	Offset     int32       `json:"offset"`
+	PageCount  int32       `json:"pagecount"`
+	ReturnType string      `json:"returntype"`
+	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"`
+}
+
+type PastureCommonResponse struct {
+	Code int32              `json:"code"`
+	Msg  string             `json:"msg"`
+	Data *PastureCommonData `json:"data"`
+}
+
+type PastureCommonData struct {
+	List     interface{} `json:"list"`
+	Data     interface{} `json:"data"`
+	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"`
+}
+
+var DefaultSheetName = "Sheet1"

+ 163 - 2
model/group_pasture.go

@@ -1,9 +1,18 @@
 package model
 
 import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"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"
 	"time"
+
+	"go.uber.org/zap"
 )
 
 type GroupPasture struct {
@@ -13,6 +22,9 @@ type GroupPasture struct {
 	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"`
 	Address      string                  `json:"address"`
@@ -20,11 +32,49 @@ type GroupPasture struct {
 	UpdatedAt    int64                   `json:"updated_at,omitempty"`
 }
 
-func (s *GroupPasture) TableName() string {
+func (g *GroupPasture) TableName() string {
 	return "group_pasture"
 }
 
-const InitManagerPassword = "123456"
+const (
+	InitManagerPassword = "123456"
+	UrlDataByName       = "authdata/GetDataByName"
+	UrlReportForm       = "authdata/GetReportform"
+	UrlSummary          = "authdata/summary"
+	UrlProcess          = "authdata/processAnalysist"
+)
+
+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{
@@ -40,6 +90,117 @@ func NewGroupPasture(req *operationPb.AddPastureRequest) *GroupPasture {
 	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 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
+	return nil
+}
+
 type GroupPastureSlice []*GroupPasture
 
 func (g GroupPastureSlice) ToPB() []*operationPb.AddPastureRequest {

+ 38 - 0
model/pasture_data.go

@@ -0,0 +1,38 @@
+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 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"`
+}

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

+ 4 - 1
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{

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

+ 552 - 0
module/backend/dashboard_service.go

@@ -0,0 +1,552 @@
+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) {
+	groupPastureList, err := s.FindGroupPastureListByIds(ctx, req.PastureIds)
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	res := make(map[int64]*model.PastureAnalysisAccuracyData, 0)
+	wg := sync.WaitGroup{}
+	wg.Add(len(groupPastureList))
+	var muError error
+	for _, pasture := range groupPastureList {
+		go func(p *model.GroupPasture) {
+			response := &model.PastureAnalysisAccuracyResponse{}
+			body := &model.DashboardAccuracyRequest{
+				PastureId:              int32(p.Id),
+				FeedFormulaId:          req.FeedFormulaId,
+				CattleParentCategoryId: int32(req.CattleParentCategoryId),
+				StartDate:              req.StartDate,
+				EndDate:                req.EndDate,
+			}
+			if err = s.PastureHttpClient(ctx, DashboardAccuracyUrl, p.Id, body, response); err != nil {
+				muError = multierr.Append(muError, err)
+				zaplog.Error("DistributeFeedFormula",
+					zap.String("url", DashboardAccuracyUrl),
+					zap.Any("pasture", p), zap.Any("body", body),
+					zap.Any("err", err), zap.Any("response", response))
+				b, _ := json.Marshal(body)
+				resB, _ := json.Marshal(response)
+				pastureDataLog := model.NewPastureDataLog(p.Id, PastureDataLogType["FeedFormula_Distribute"], FeedFormulaDistributeUrl, string(b), string(resB))
+				s.DB.Create(pastureDataLog)
+			}
+			if response.Code != http.StatusOK {
+				muError = multierr.Append(muError, xerr.Custom(response.Msg))
+			}
+			res[p.Id] = response.Data
+			wg.Done()
+		}(pasture)
+	}
+	wg.Wait()
+	return res, nil
+}
+
+func (s *StoreEntry) PasturePrefExecTimeData(ctx context.Context, req *operationPb.SearchAnalysisAccuracyRequest) (map[string]*model.ExecTimeData, error) {
+	groupPastureList, err := s.FindGroupPastureListByIds(ctx, req.PastureIds)
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	res := make(map[string]*model.ExecTimeData, 0)
+	wg := sync.WaitGroup{}
+	wg.Add(len(groupPastureList))
+	var muError error
+	for _, pasture := range groupPastureList {
+		go func(p *model.GroupPasture) {
+			response := &model.PastureExecTimeData{}
+			body := &model.DashboardAccuracyRequest{
+				PastureId:              int32(p.Id),
+				FeedFormulaId:          req.FeedFormulaId,
+				CattleParentCategoryId: int32(req.CattleParentCategoryId),
+				StartDate:              req.StartDate,
+				EndDate:                req.EndDate,
+			}
+			if err = s.PastureHttpClient(ctx, DashboardExecTimeUrl, p.Id, body, response); err != nil {
+				muError = multierr.Append(muError, err)
+				zaplog.Error("PasturePrefExecTimeData",
+					zap.String("url", DashboardExecTimeUrl),
+					zap.Any("pasture", p), zap.Any("body", body),
+					zap.Any("err", err), zap.Any("response", response))
+				b, _ := json.Marshal(body)
+				resB, _ := json.Marshal(response)
+				pastureDataLog := model.NewPastureDataLog(p.Id, PastureDataLogType["PasturePrefExecTimeData"], DashboardExecTimeUrl, string(b), string(resB))
+				s.DB.Create(pastureDataLog)
+			}
+			if response.Code != http.StatusOK {
+				muError = multierr.Append(muError, xerr.Custom(response.Msg))
+			}
+			res[p.Name] = response.Data
+			wg.Done()
+		}(pasture)
+	}
+	wg.Wait()
+	return res, nil
+}
+
+func (s *StoreEntry) PastureSprinkleFeedTime(ctx context.Context, req *operationPb.SprinkleFeedTimeRequest) (map[string][]*model.SprinkleStatisticsDataList, error) {
+	groupPastureList, err := s.FindGroupPastureListByIds(ctx, req.PastureIds)
+	if err != nil {
+		return nil, xerr.WithStack(err)
+	}
+
+	res := make(map[string][]*model.SprinkleStatisticsDataList, 0)
+	wg := sync.WaitGroup{}
+	wg.Add(len(groupPastureList))
+	var muError error
+	for _, pasture := range groupPastureList {
+		go func(p *model.GroupPasture) {
+			response := &model.PastureSprinkleStatisticsDataList{}
+			body := &model.DashboardAccuracyRequest{
+				PastureId:     int32(p.Id),
+				FeedFormulaId: req.FeedFormulaId,
+				StartDate:     req.StartDate,
+				EndDate:       req.EndDate,
+			}
+			if err = s.PastureHttpClient(ctx, DashboardSprinkleFeedTimeUrl, p.Id, body, response); err != nil {
+				muError = multierr.Append(muError, err)
+				zaplog.Error("PastureSprinkleFeedTime",
+					zap.String("url", DashboardSprinkleFeedTimeUrl),
+					zap.Any("pasture", p), zap.Any("body", body),
+					zap.Any("err", err), zap.Any("response", response))
+				b, _ := json.Marshal(body)
+				resB, _ := json.Marshal(response)
+				pastureDataLog := model.NewPastureDataLog(p.Id, PastureDataLogType["PasturePrefExecTimeData"], DashboardSprinkleFeedTimeUrl, string(b), string(resB))
+				s.DB.Create(pastureDataLog)
+			}
+			if response.Code != http.StatusOK {
+				muError = multierr.Append(muError, xerr.Custom(response.Msg))
+			}
+			res[p.Name] = response.Data
+			wg.Done()
+		}(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)
+	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)
+		res.Data.Chart.SprinkleNumberList = append(res.Data.Chart.SprinkleNumberList, sprinkleExecTimeAnalysis(sprinkleFeedTimeList))
+	}
+
+	res.Data.TableList = tableList
+	return res, nil
+}
+
+func sprinkleExecTimeAnalysis(sprinkleFeedTimeList map[int32]map[int32][]int64) []int32 {
+	res := make([]int32, 0)
+	var infoSprinkleNumber, errorSprinkleNumber int32 = 0, 0
+	zaplog.Info("sprinkleFeedTimeList", zap.Any("ok", sprinkleFeedTimeList))
+	if len(sprinkleFeedTimeList) <= 0 {
+		return res
+	} 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
+					}
+				}
+			}
+		}
+		res = append(res, infoSprinkleNumber, errorSprinkleNumber)
+	}
+
+	return res
+}

+ 118 - 7
module/backend/feed_service.go

@@ -3,6 +3,7 @@ package backend
 import (
 	"bytes"
 	"context"
+	"encoding/json"
 	"errors"
 	"fmt"
 	"io"
@@ -10,13 +11,24 @@ 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,
+}
+
 // CreateFeedFormula 添加数据
 func (s *StoreEntry) CreateFeedFormula(ctx context.Context, req *operationPb.AddFeedFormulaRequest) error {
 	forage := model.NewFeedFormula(req)
@@ -96,10 +108,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
 }
 
@@ -228,7 +244,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 +260,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 +302,98 @@ 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
+	}
+
+	feedFormulaList := make([]*model.FeedFormula, 0)
+	if err = s.DB.Where("id IN ?", req.FeedFormulaIds).Find(&feedFormulaList).Error; err != nil {
+		return xerr.WithStack(err)
+	}
+
+	wg := sync.WaitGroup{}
+	wg.Add(len(distributeData.PastureList))
+	var muError error
+	for _, pasture := range distributeData.PastureList {
+		go func(p *model.GroupPasture) {
+			response := &model.PastureResponse{}
+			body := &model.DistributeFeedFormulaRequest{
+				PastureId: p.Id,
+				Body:      feedFormulaList,
+			}
+			if err = s.PastureHttpClient(ctx, FeedFormulaDistributeUrl, p.Id, body, response); err != nil {
+				muError = multierr.Append(muError, err)
+				zaplog.Error("DistributeFeedFormula", zap.Any("pasture", p), zap.Any("body", feedFormulaList), zap.Any("err", err), zap.Any("response", response))
+				b, _ := json.Marshal(body)
+				res, _ := json.Marshal(response)
+				pastureDataLog := model.NewPastureDataLog(p.Id, PastureDataLogType["FeedFormula_Distribute"], FeedFormulaDistributeUrl, string(b), string(res))
+				s.DB.Create(pastureDataLog)
+			}
+			if response.Code != http.StatusOK {
+				muError = multierr.Append(muError, xerr.Custom(response.Msg))
+			}
+
+			wg.Done()
+		}(pasture)
+	}
+	wg.Wait()
+	return muError
+}
+
+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
+}

+ 29 - 7
module/backend/interface.go

@@ -24,9 +24,9 @@ type Hub struct {
 type StoreEntry struct {
 	dig.In
 
-	Cfg      *config.AppConfig
-	DB       *kptstore.DB
-	WxClient *wechat.ClientService
+	Cfg        *config.AppConfig
+	DB         *kptstore.DB
+	HttpClient *wechat.ClientService
 }
 
 func NewStore(store StoreEntry) KptService {
@@ -35,9 +35,9 @@ func NewStore(store StoreEntry) KptService {
 
 func NewStoreEntry(cfg *config.AppConfig, Db *kptstore.DB) *StoreEntry {
 	return &StoreEntry{
-		Cfg:      cfg,
-		DB:       Db,
-		WxClient: nil,
+		Cfg:        cfg,
+		DB:         Db,
+		HttpClient: nil,
 	}
 }
 
@@ -94,6 +94,8 @@ 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
 }
 
 type SystemService interface {
@@ -128,8 +130,28 @@ type SystemService 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.PastureCommonResponse, 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 {

+ 255 - 2
module/backend/mock/kptservice.go

@@ -81,6 +81,21 @@ func (mr *MockKptServiceMockRecorder) Auth(arg0, arg1 interface{}) *gomock.Call
 	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()
@@ -292,6 +307,20 @@ func (mr *MockKptServiceMockRecorder) DetailsSystemUser(arg0, arg1 interface{})
 	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()
@@ -404,6 +433,20 @@ func (mr *MockKptServiceMockRecorder) EditSystemUser(arg0, arg1 interface{}) *go
 	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()
@@ -492,6 +535,21 @@ func (mr *MockKptServiceMockRecorder) ExcelTemplateForage(arg0 interface{}) *gom
 	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()
@@ -506,6 +564,21 @@ func (mr *MockKptServiceMockRecorder) ForageEnumList(arg0 interface{}) *gomock.C
 	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()
@@ -551,6 +624,21 @@ func (mr *MockKptServiceMockRecorder) GetSystemUserPermissions(arg0, arg1 interf
 	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()
@@ -566,6 +654,21 @@ func (mr *MockKptServiceMockRecorder) GetUserInfo(arg0, arg1 interface{}) *gomoc
 	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()
@@ -720,6 +823,21 @@ func (mr *MockKptServiceMockRecorder) ResetPasswordSystemUser(arg0, arg1 interfa
 	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()
@@ -765,6 +883,21 @@ func (mr *MockKptServiceMockRecorder) SearchFeedFormulaList(arg0, arg1 interface
 	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()
@@ -796,10 +929,10 @@ func (mr *MockKptServiceMockRecorder) SearchForageList(arg0, arg1 interface{}) *
 }
 
 // SearchFormulaEstimateList mocks base method.
-func (m *MockKptService) SearchFormulaEstimateList(arg0 context.Context, arg1 *operationPb.SearchFormulaEstimateRequest) (*operationPb.SearchFormulaEstimateResponse, error) {
+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].(*operationPb.SearchFormulaEstimateResponse)
+	ret0, _ := ret[0].(*model.PastureCommonResponse)
 	ret1, _ := ret[1].(error)
 	return ret0, ret1
 }
@@ -825,6 +958,36 @@ func (mr *MockKptServiceMockRecorder) SearchGroupPastureList(arg0, arg1 interfac
 	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()
@@ -840,6 +1003,51 @@ func (mr *MockKptServiceMockRecorder) SearchMobileList(arg0, arg1 interface{}) *
 	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()
@@ -884,3 +1092,48 @@ func (mr *MockKptServiceMockRecorder) SearchSystemUserList(arg0, arg1 interface{
 	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)
+}

+ 115 - 31
module/backend/pasture_service.go

@@ -36,25 +36,6 @@ var (
 		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 创建集团牧场
@@ -66,6 +47,29 @@ func (s *StoreEntry) CreateGroupPasture(ctx context.Context, req *operationPb.Ad
 	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: int64(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)}
@@ -114,7 +118,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)
 	}
 
@@ -367,7 +371,11 @@ func (s *StoreEntry) SearchForageCategoryList(ctx context.Context, req *operatio
 	}
 
 	if req.ParentName != "" {
-		pref.Where("parent_name like ?", fmt.Sprintf("%s%s%s", "%", req.ParentName, "%"))
+		pref.Where("parent_name = ?", fmt.Sprintf("%s%s%s", "%", req.ParentName, "%"))
+	}
+
+	if req.Number != "" {
+		pref.Where("number = ?", fmt.Sprintf("%s%s%s", "%", req.ParentName, "%"))
 	}
 
 	if req.IsShow > 0 {
@@ -493,15 +501,19 @@ func (s *StoreEntry) ForageEnumList(ctx context.Context) *operationPb.ForageEnum
 		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),
+			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),
 		},
 	}
 
@@ -584,6 +596,14 @@ func (s *StoreEntry) ForageEnumList(ctx context.Context) *operationPb.ForageEnum
 		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: "饲喂配方",
@@ -603,6 +623,70 @@ func (s *StoreEntry) ForageEnumList(ctx context.Context) *operationPb.ForageEnum
 		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: "所有",
@@ -768,7 +852,7 @@ func (s *StoreEntry) ExcelExportForage(ctx context.Context, req *operationPb.Sea
 	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)
 	}

+ 563 - 118
module/backend/statistic_service.go

@@ -1,177 +1,622 @@
 package backend
 
 import (
+	"bytes"
 	"context"
+	"encoding/json"
 	"fmt"
 	"kpt-tmr-group/model"
+	"kpt-tmr-group/pkg/logger/zaplog"
 	"kpt-tmr-group/pkg/xerr"
 	operationPb "kpt-tmr-group/proto/go/backend/operation"
 	"net/http"
-	"strconv"
-	"strings"
-	"time"
+	"sort"
+
+	"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)
-	if err != nil {
+type PastureClientHandler func(ctx context.Context, pastureId int64, body interface{}) error
+
+const (
+	FeedFormulaDistributeUrl     = "pasture/feed_formula/distribute"
+	DashboardAccuracyUrl         = "pasture/dashboard/accuracy_data"
+	DashboardExecTimeUrl         = "pasture/dashboard/process_analysis"
+	DashboardSprinkleFeedTimeUrl = "pasture/dashboard/sprinkle_statistics"
+)
+
+// 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
+}
 
-	endTime, err := time.Parse(model.LayoutTime, req.EndTime)
+func (s *StoreEntry) PastureHttpClient(ctx context.Context, apiUrl string, pastureId int64, body, response interface{}) error {
+	pastureDetail, err := s.PastureDetailById(ctx, pastureId)
 	if err != nil {
-		return nil, xerr.WithStack(err)
+		zaplog.Error("SearchFormulaEstimateList", zap.Any("Err", err), zap.Int64("pastureId", pastureId))
+		return xerr.Customf("该牧场数据错误,Err:%s", err)
 	}
 
-	startTimeUnix := startTime.Unix()
-	endTimeUnix := endTime.Unix()
+	pastureClient := model.NewPastureClient(pastureDetail)
+	url := fmt.Sprintf("%s/%s", pastureDetail.Domain, apiUrl)
 
-	formulaEstimate := make([]*model.FormulaEstimate, 0)
-	var count int64 = 0
-
-	pref := s.DB.Model(new(model.FormulaEstimate))
-	if req.Name != "" {
-		pref.Where("name like ?", fmt.Sprintf("%s%s%s", "%", req.Name, "%"))
-	}
-	if startTimeUnix > 0 && endTimeUnix > 0 && endTimeUnix >= startTimeUnix {
-		pref.Where("created_at BETWEEN ? AND ?", startTimeUnix, endTimeUnix)
+	result, err := pastureClient.DoPost(url, body)
+	if err != nil {
+		return xerr.WithStack(err)
 	}
 
-	if req.SearchType == 1 {
-		pref.Where("feed_formula_name = ?", req.Name)
-	} else {
-		pref.Where("barn_id = ?", req.Name)
+	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 xerr.WithStack(err)
 	}
+	return nil
+
+}
 
-	if err = pref.Order("id desc").Count(&count).Limit(int(req.Pagination.PageSize)).Offset(int(req.Pagination.PageOffset)).
-		Find(&formulaEstimate).Error; err != nil {
+// SearchFormulaEstimateList 配方评估
+func (s *StoreEntry) SearchFormulaEstimateList(ctx context.Context, req *operationPb.SearchFormulaEstimateRequest) (*model.PastureCommonResponse, error) {
+	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
+}
 
-	return &operationPb.SearchFormulaEstimateResponse{
-		Code: http.StatusOK,
-		Msg:  "ok",
-		Data: &operationPb.SearchFormulaEstimate{
-			Page:     req.Pagination.Page,
-			Total:    int32(count),
-			PageSize: req.Pagination.PageSize,
-			List:     model.FormulaEstimateSlice(formulaEstimate).ToPB(),
+// SearchInventoryStatistics 库存管理-库存统计
+func (s *StoreEntry) SearchInventoryStatistics(ctx context.Context, req *operationPb.SearchInventoryStatisticsRequest) (*model.PastureCommonResponse, error) {
+	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),
 		},
-	}, nil
+	}
+	if err := s.PastureHttpClient(ctx, model.UrlDataByName, int64(req.PastureId), body, response); err != nil {
+		return nil, xerr.WithStack(err)
+	}
+	return response, nil
 }
 
-func (s *StoreEntry) SearchAnalysisAccuracy(ctx context.Context, req *operationPb.SearchAnalysisAccuracyRequest) (*model.SearchAnalysisAccuracyResponse, error) {
+// 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)
+	}
 
-	dataList := &model.CommonValueRatio{
-		MaxValue:    "",
-		MiddleValue: "",
-		MinValue:    "",
-		PastureName: make([]string, 0),
-		DateDay:     make([]string, 0),
-		DataList:    make([][]string, 0),
-		PastureIds:  make([]int32, 0),
+	b, _ := json.Marshal(result.Data.List)
+	inventoryStatisticsList := make([]*model.InventoryStatisticsList, 0)
+	if err = json.Unmarshal(b, &inventoryStatisticsList); err != nil {
+		return nil, xerr.Customf("牧场端返回数据错误")
 	}
-	res := &model.SearchAnalysisAccuracyResponse{
-		Code: http.StatusOK,
-		Msg:  "ok",
-		Data: &model.AnalysisAccuracyData{
-			Chart: &model.Chart{
-				MixedFodderAccurateRatio:    dataList,
-				MixedFodderCorrectRatio:     dataList,
-				SprinkleFodderAccurateRatio: dataList,
-				SprinkleFodderCorrectRatio:  dataList,
-			},
-			Table: &model.Table{
-				TitleList: make([]*model.TableList, 0),
-				DataList:  make([]*interface{}, 0),
-			},
-		},
+
+	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)"},
 	}
-	res.Data.Table.TitleList = append(res.Data.Table.TitleList, &model.TableList{
-		Name:  "title",
-		Value: "牧场",
+	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)
+	}
 
-	analysisAccuracy := make([]*model.OptionsAnalysisAccuracy, 0)
-	pref := s.DB.Model(new(model.AnalysisAccuracy))
-	if req.EndDate != "" && req.StartDate != "" {
-		pref.Where("date_day BETWEEN ? AND ?", req.StartDate, req.EndDate)
+	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)
+	}
+
+	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)
 	}
 
-	if req.CattleParentCategoryId > 0 {
-		pref.Where("cattle_parent_category_id = ?", req.CattleParentCategoryId)
+	return file.WriteToBuffer()
+}
+
+// SearchUserMaterialsStatistics 库存管理-用料分析
+func (s *StoreEntry) SearchUserMaterialsStatistics(ctx context.Context, req *operationPb.SearchUserMaterialsStatisticsRequest) (*model.PastureCommonResponse, error) {
+	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 req.FeedFormulaId > 0 {
-		pref.Where("feed_formula_id = ?", req.FeedFormulaId)
+	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 len(req.PastureIds) > 0 {
-		pref.Where("pasture_id IN ?", req.PastureIds)
+	b, _ := json.Marshal(result.Data.List)
+	userMaterialsList := &model.UserMaterialsList{}
+	if err = json.Unmarshal(b, userMaterialsList); err != nil {
+		return nil, xerr.Customf("牧场端返回数据错误")
 	}
 
-	if err := pref.Select("pasture_id,pasture_name,date_day,sum(iweight) as all_iweight,sum(lweight) as all_lweight,sum(oweight) as all_oweight,sum(actual_weight_minus) as all_actual_weight_minus,sum(allow_ratio) as all_allow_ratio,sum(alweight) as all_alweight").
-		Group("pasture_id,pasture_name,date_day").Order("pasture_name,date_day").Find(&analysisAccuracy).Debug().Error; err != nil {
+	file := excelize.NewFile()
+	defer file.Close()
+
+	streamWriter, err := file.NewStreamWriter(model.DefaultSheetName)
+	if err != nil {
 		return nil, xerr.WithStack(err)
 	}
 
-	mixedFodderAccurateRatio := make([]float64, 0)
-	mapPastureName := make(map[string]bool, 0)
-	mapDateDay := make(map[string]bool, 0)
-	mapRatio := make(map[string]bool, 0)
-	tableList := make([]*model.TableList, 0)
-	for k, v := range analysisAccuracy {
-		if _, ok := mapPastureName[v.PastureName]; !ok {
-			res.Data.Chart.MixedFodderAccurateRatio.PastureName = append(res.Data.Chart.MixedFodderAccurateRatio.PastureName, v.PastureName)
-			res.Data.Chart.MixedFodderAccurateRatio.PastureIds = append(res.Data.Chart.MixedFodderAccurateRatio.PastureIds, int32(v.PastureId))
-			mapPastureName[v.PastureName] = true
+	// 表数据
+	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)
 		}
+	}
 
-		dataDayFormat := strings.TrimRight(v.DateDay, "T00:00:00+08:00")
-		if _, ok := mapDateDay[v.DateDay]; !ok {
-			res.Data.Chart.MixedFodderAccurateRatio.DateDay = append(res.Data.Chart.MixedFodderAccurateRatio.DateDay, dataDayFormat)
-			mapDateDay[v.DateDay] = true
+	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)
+	}
 
-		valueRatio1 := float64(v.AllIweight/v.AllLweight) / 100.0
-		if _, ok := mapRatio[v.DateDay]; !ok {
-			valueRatio := make([]string, 0)
-			for _, a := range analysisAccuracy {
-				if a.DateDay == v.DateDay {
-					valueRatio = append(valueRatio, strconv.FormatFloat(float64(a.AllIweight/a.AllLweight)/100.0, 'f', 2, 64))
+	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)
 				}
 			}
-			res.Data.Chart.MixedFodderAccurateRatio.DataList = append(res.Data.Chart.MixedFodderAccurateRatio.DataList, valueRatio)
-			mapRatio[v.DateDay] = true
-			tableList = append(tableList, &model.TableList{
-				Name:  fmt.Sprintf("date%d", k),
-				Value: dataDayFormat,
-			})
 		}
-		mixedFodderAccurateRatio = append(mixedFodderAccurateRatio, valueRatio1)
 	}
 
-	mixedFodderAccurateRatioMaxValue, mixedFodderAccurateRatioMiddleValue, mixedFodderAccurateRatioMinValue := calculateRatio(mixedFodderAccurateRatio)
-	res.Data.Chart.MixedFodderAccurateRatio.MaxValue = strconv.FormatFloat(mixedFodderAccurateRatioMaxValue, 'f', 2, 64)
-	res.Data.Chart.MixedFodderAccurateRatio.MiddleValue = strconv.FormatFloat(mixedFodderAccurateRatioMiddleValue, 'f', 2, 64)
-	res.Data.Chart.MixedFodderAccurateRatio.MinValue = strconv.FormatFloat(mixedFodderAccurateRatioMinValue, 'f', 2, 64)
+	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)
+	}
 
-	res.Data.Table.TitleList = append(res.Data.Table.TitleList, tableList...)
+	return file.WriteToBuffer()
+}
 
-	return res, nil
+// SearchPriceStatistics 库存管理-价格分析
+func (s *StoreEntry) SearchPriceStatistics(ctx context.Context, req *operationPb.SearchPriceStatisticsRequest) (*model.PastureCommonResponse, error) {
+	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
 }
 
-func calculateRatio(res []float64) (float64, float64, float64) {
-	var maxValue, middleValue, minValue, allValue float64 = 0, 0, 0, 0
-	for _, v := range res {
-		if v > maxValue {
-			maxValue = v
-		}
-		if v <= maxValue {
-			minValue = v
-		}
+// SearchFeedStatistics 饲喂效率-效率统计
+func (s *StoreEntry) SearchFeedStatistics(ctx context.Context, req *operationPb.SearchFeedStatisticsRequest) (*model.PastureCommonResponse, error) {
+	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", req.PastureId),
+			StartTime: req.StartTime,
+			StopTime:  req.StartTime,
+			Date:      req.StartTime,
+			FeedTName: req.FormulaTemplate,
+			BarName:   req.BarnName,
+			CowClass:  req.CattleCategoryName,
+			Times:     fmt.Sprintf("%d", req.ClassNumber),
+		},
+	}
+	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
+}
 
-		allValue += v
+// FeedChartStatistics 饲喂效率图表分析
+func (s *StoreEntry) FeedChartStatistics(ctx context.Context, req *operationPb.FeedChartStatisticsRequest) (*model.PastureCommonResponse, error) {
+	body := &model.FeedChartParams{
+		ParamMaps: &model.ParamMaps{
+			PastureId: fmt.Sprintf("%d", req.PastureId),
+			StartTime: req.StartTime,
+			StopTime:  req.StartTime,
+			Status:    req.Status,
+		},
 	}
-	middleValue = allValue / float64(len(res))
-	return maxValue, middleValue, minValue
+	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) {
+	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) {
+	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) {
+	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) {
+	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) {
+	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,
+		},
+	}
+	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) {
+	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) {
+	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
 }

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

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

+ 361 - 52
proto/go/backend/operation/feed_formula.pb.go

@@ -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,173 @@ 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 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[7]
+		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[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 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{
@@ -502,28 +732,53 @@ 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, 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 +793,37 @@ 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, 8)
 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
+	(*UniqueID_UniqueData)(nil),           // 7: backend.operation.UniqueID.UniqueData
+	(CattleCategoryParent_Kind)(0),        // 8: backend.operation.CattleCategoryParent.Kind
+	(DataSource_Kind)(0),                  // 9: backend.operation.DataSource.Kind
+	(IsShow_Kind)(0),                      // 10: backend.operation.IsShow.Kind
+	(*PaginationModel)(nil),               // 11: 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
+	8,  // 0: backend.operation.AddFeedFormulaRequest.cattle_category_id:type_name -> backend.operation.CattleCategoryParent.Kind
+	9,  // 1: backend.operation.AddFeedFormulaRequest.data_source_id:type_name -> backend.operation.DataSource.Kind
+	10, // 2: backend.operation.AddFeedFormulaRequest.is_show:type_name -> backend.operation.IsShow.Kind
+	10, // 3: backend.operation.AddFeedFormulaRequest.is_modify:type_name -> backend.operation.IsShow.Kind
+	10, // 4: backend.operation.SearchFeedFormulaRequest.is_show:type_name -> backend.operation.IsShow.Kind
+	11, // 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
+	10, // 8: backend.operation.IsShowModifyFeedFormula.is_show:type_name -> backend.operation.IsShow.Kind
+	7,  // 9: backend.operation.UniqueID.data:type_name -> backend.operation.UniqueID.UniqueData
+	10, // [10:10] is the sub-list for method output_type
+	10, // [10:10] is the sub-list for method input_type
+	10, // [10:10] is the sub-list for extension type_name
+	10, // [10:10] is the sub-list for extension extendee
+	0,  // [0:10] is the sub-list for field type_name
 }
 
 func init() { file_backend_operation_feed_formula_proto_init() }
@@ -610,6 +871,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 +894,42 @@ 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.(*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 +937,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:   8,
 			NumExtensions: 0,
 			NumServices:   0,
 		},

+ 351 - 257
proto/go/backend/operation/pasture.pb.go

@@ -997,7 +997,8 @@ type SearchForageCategoryRequest struct {
 	ParentName string           `protobuf:"bytes,1,opt,name=parent_name,json=parentName,proto3" json:"parent_name,omitempty"`
 	IsShow     IsShow_Kind      `protobuf:"varint,2,opt,name=is_show,json=isShow,proto3,enum=backend.operation.IsShow_Kind" json:"is_show,omitempty"`
 	Name       string           `protobuf:"bytes,3,opt,name=name,proto3" json:"name,omitempty"`
-	Pagination *PaginationModel `protobuf:"bytes,4,opt,name=pagination,proto3" json:"pagination,omitempty"` // 分页
+	Number     string           `protobuf:"bytes,4,opt,name=number,proto3" json:"number,omitempty"`
+	Pagination *PaginationModel `protobuf:"bytes,5,opt,name=pagination,proto3" json:"pagination,omitempty"` // 分页
 }
 
 func (x *SearchForageCategoryRequest) Reset() {
@@ -1053,6 +1054,13 @@ func (x *SearchForageCategoryRequest) GetName() string {
 	return ""
 }
 
+func (x *SearchForageCategoryRequest) GetNumber() string {
+	if x != nil {
+		return x.Number
+	}
+	return ""
+}
+
 func (x *SearchForageCategoryRequest) GetPagination() *PaginationModel {
 	if x != nil {
 		return x.Pagination
@@ -1795,15 +1803,21 @@ type ForageEnumList struct {
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	ForageSource         []*ForageSourceEnum         `protobuf:"bytes,1,rep,name=forage_source,json=forageSource,proto3" json:"forage_source,omitempty"`
-	ForagePlanType       []*ForagePlanTypeEnum       `protobuf:"bytes,2,rep,name=forage_plan_type,json=foragePlanType,proto3" json:"forage_plan_type,omitempty"`
-	JumpDelaType         []*JumpDelaTypeEnum         `protobuf:"bytes,3,rep,name=jump_dela_type,json=jumpDelaType,proto3" json:"jump_dela_type,omitempty"`
-	CattleParentCategory []*CattleParentCategoryEnum `protobuf:"bytes,4,rep,name=cattle_parent_category,json=cattleParentCategory,proto3" json:"cattle_parent_category,omitempty"`
-	ForageParentCategory []*ForageParentCategoryEnum `protobuf:"bytes,5,rep,name=forage_parent_category,json=forageParentCategory,proto3" json:"forage_parent_category,omitempty"`
-	IsShow               []*IsShowEnum               `protobuf:"bytes,6,rep,name=is_show,json=isShow,proto3" json:"is_show,omitempty"`
-	FormulaType          []*FormulaTypeEnum          `protobuf:"bytes,7,rep,name=formula_type,json=formulaType,proto3" json:"formula_type,omitempty"`
-	FormulaList          []*FormulaOptionEnum        `protobuf:"bytes,8,rep,name=formula_list,json=formulaList,proto3" json:"formula_list,omitempty"`
-	ConfirmStart         []*IsShowEnum               `protobuf:"bytes,9,rep,name=confirm_start,json=confirmStart,proto3" json:"confirm_start,omitempty"`
+	ForageSource          []*ForageSourceEnum         `protobuf:"bytes,1,rep,name=forage_source,json=forageSource,proto3" json:"forage_source,omitempty"`                           // 饲料来源
+	ForagePlanType        []*ForagePlanTypeEnum       `protobuf:"bytes,2,rep,name=forage_plan_type,json=foragePlanType,proto3" json:"forage_plan_type,omitempty"`                   // 饲料计划类型
+	JumpDelaType          []*JumpDelaTypeEnum         `protobuf:"bytes,3,rep,name=jump_dela_type,json=jumpDelaType,proto3" json:"jump_dela_type,omitempty"`                         // 跳转延迟类型
+	CattleParentCategory  []*CattleParentCategoryEnum `protobuf:"bytes,4,rep,name=cattle_parent_category,json=cattleParentCategory,proto3" json:"cattle_parent_category,omitempty"` // 畜牧分类
+	ForageParentCategory  []*ForageParentCategoryEnum `protobuf:"bytes,5,rep,name=forage_parent_category,json=forageParentCategory,proto3" json:"forage_parent_category,omitempty"` // 饲料分类
+	IsShow                []*IsShowEnum               `protobuf:"bytes,6,rep,name=is_show,json=isShow,proto3" json:"is_show,omitempty"`
+	FormulaType           []*FormulaTypeEnum          `protobuf:"bytes,7,rep,name=formula_type,json=formulaType,proto3" json:"formula_type,omitempty"`                                // 配方类型
+	FormulaList           []*FormulaOptionEnum        `protobuf:"bytes,8,rep,name=formula_list,json=formulaList,proto3" json:"formula_list,omitempty"`                                // 所有配方列表
+	ConfirmStart          []*IsShowEnum               `protobuf:"bytes,9,rep,name=confirm_start,json=confirmStart,proto3" json:"confirm_start,omitempty"`                             // 确认开始
+	FormulationEvaluation []*FormulaOptionEnum        `protobuf:"bytes,10,rep,name=formulation_evaluation,json=formulationEvaluation,proto3" json:"formulation_evaluation,omitempty"` // 配方评估查询方式
+	UseMaterialsList      []*FormulaOptionEnum        `protobuf:"bytes,11,rep,name=use_materials_list,json=useMaterialsList,proto3" json:"use_materials_list,omitempty"`              // 用料分析-列表显示
+	UseMaterialsType      []*FormulaOptionEnum        `protobuf:"bytes,12,rep,name=use_materials_type,json=useMaterialsType,proto3" json:"use_materials_type,omitempty"`              // 用料分析-统计类型
+	PriceMaterialsType    []*FormulaOptionEnum        `protobuf:"bytes,13,rep,name=price_materials_type,json=priceMaterialsType,proto3" json:"price_materials_type,omitempty"`        // 价格分析-统计类型
+	JumpType              []*FormulaOptionEnum        `protobuf:"bytes,14,rep,name=jump_type,json=jumpType,proto3" json:"jump_type,omitempty"`                                        // 准确性-跳转方式
+	StatisticsType        []*FormulaOptionEnum        `protobuf:"bytes,15,rep,name=statistics_type,json=statisticsType,proto3" json:"statistics_type,omitempty"`                      // 准确性-统计类型
 }
 
 func (x *ForageEnumList) Reset() {
@@ -1901,6 +1915,48 @@ func (x *ForageEnumList) GetConfirmStart() []*IsShowEnum {
 	return nil
 }
 
+func (x *ForageEnumList) GetFormulationEvaluation() []*FormulaOptionEnum {
+	if x != nil {
+		return x.FormulationEvaluation
+	}
+	return nil
+}
+
+func (x *ForageEnumList) GetUseMaterialsList() []*FormulaOptionEnum {
+	if x != nil {
+		return x.UseMaterialsList
+	}
+	return nil
+}
+
+func (x *ForageEnumList) GetUseMaterialsType() []*FormulaOptionEnum {
+	if x != nil {
+		return x.UseMaterialsType
+	}
+	return nil
+}
+
+func (x *ForageEnumList) GetPriceMaterialsType() []*FormulaOptionEnum {
+	if x != nil {
+		return x.PriceMaterialsType
+	}
+	return nil
+}
+
+func (x *ForageEnumList) GetJumpType() []*FormulaOptionEnum {
+	if x != nil {
+		return x.JumpType
+	}
+	return nil
+}
+
+func (x *ForageEnumList) GetStatisticsType() []*FormulaOptionEnum {
+	if x != nil {
+		return x.StatisticsType
+	}
+	return nil
+}
+
 type ForageSourceEnum struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
@@ -2502,7 +2558,7 @@ var file_backend_operation_pasture_proto_rawDesc = []byte{
 	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, 0x22, 0xcf, 0x01, 0x0a, 0x1b, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x46, 0x6f,
+	0x68, 0x6f, 0x77, 0x22, 0xe7, 0x01, 0x0a, 0x1b, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x46, 0x6f,
 	0x72, 0x61, 0x67, 0x65, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75,
 	0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x6e, 0x61,
 	0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74,
@@ -2511,247 +2567,279 @@ var file_backend_operation_pasture_proto_rawDesc = []byte{
 	0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x49, 0x73, 0x53, 0x68, 0x6f, 0x77,
 	0x2e, 0x4b, 0x69, 0x6e, 0x64, 0x52, 0x06, 0x69, 0x73, 0x53, 0x68, 0x6f, 0x77, 0x12, 0x12, 0x0a,
 	0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d,
-	0x65, 0x12, 0x42, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18,
-	0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e,
-	0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x50, 0x61, 0x67, 0x69, 0x6e, 0x61,
-	0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x52, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e,
-	0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x85, 0x01, 0x0a, 0x1c, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68,
-	0x46, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x52, 0x65,
-	0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01,
-	0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x73,
-	0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6d, 0x73, 0x67, 0x12, 0x3f, 0x0a, 0x04,
-	0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x62, 0x61, 0x63,
-	0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x53,
-	0x65, 0x61, 0x72, 0x63, 0x68, 0x46, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x43, 0x61, 0x74, 0x65, 0x67,
-	0x6f, 0x72, 0x79, 0x44, 0x61, 0x74, 0x61, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0xa2, 0x01,
-	0x0a, 0x18, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x46, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x43, 0x61,
-	0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x44, 0x61, 0x74, 0x61, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61,
-	0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x14,
-	0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x74,
-	0x6f, 0x74, 0x61, 0x6c, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a,
-	0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a,
-	0x65, 0x12, 0x3f, 0x0a, 0x04, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32,
-	0x2b, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74,
-	0x69, 0x6f, 0x6e, 0x2e, 0x41, 0x64, 0x64, 0x46, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x43, 0x61, 0x74,
-	0x65, 0x67, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x04, 0x6c, 0x69,
-	0x73, 0x74, 0x22, 0x9c, 0x08, 0x0a, 0x10, 0x41, 0x64, 0x64, 0x46, 0x6f, 0x72, 0x61, 0x67, 0x65,
-	0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20,
-	0x01, 0x28, 0x05, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18,
-	0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x63,
-	0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05,
-	0x52, 0x0a, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d,
-	0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20,
-	0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x4e, 0x61, 0x6d,
-	0x65, 0x12, 0x23, 0x0a, 0x0d, 0x6d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x5f, 0x74, 0x79,
-	0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x6d, 0x61, 0x74, 0x65, 0x72, 0x69,
-	0x61, 0x6c, 0x54, 0x79, 0x70, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x75, 0x6e, 0x69, 0x71, 0x75, 0x65,
-	0x5f, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x75,
-	0x6e, 0x69, 0x71, 0x75, 0x65, 0x45, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x4e, 0x0a, 0x10, 0x66,
-	0x6f, 0x72, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18,
-	0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x24, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e,
-	0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x46, 0x6f, 0x72, 0x61, 0x67, 0x65,
-	0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4b, 0x69, 0x6e, 0x64, 0x52, 0x0e, 0x66, 0x6f, 0x72,
-	0x61, 0x67, 0x65, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x12, 0x2c, 0x0a, 0x12, 0x66,
-	0x6f, 0x72, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d,
-	0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x66, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x53,
-	0x6f, 0x75, 0x72, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x48, 0x0a, 0x0c, 0x70, 0x6c, 0x61,
-	0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0e, 0x32,
-	0x26, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74,
-	0x69, 0x6f, 0x6e, 0x2e, 0x46, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x50, 0x6c, 0x61, 0x6e, 0x54, 0x79,
-	0x70, 0x65, 0x2e, 0x4b, 0x69, 0x6e, 0x64, 0x52, 0x0a, 0x70, 0x6c, 0x61, 0x6e, 0x54, 0x79, 0x70,
-	0x65, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x70, 0x6c, 0x61, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65,
-	0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x6c, 0x61,
-	0x6e, 0x54, 0x79, 0x70, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x30, 0x0a, 0x14, 0x73, 0x6d, 0x61,
-	0x6c, 0x6c, 0x5f, 0x6d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x5f, 0x73, 0x63, 0x61, 0x6c,
-	0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x73, 0x6d, 0x61, 0x6c, 0x6c, 0x4d, 0x61,
-	0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x61,
-	0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x05,
-	0x52, 0x0a, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x25, 0x0a, 0x0e,
-	0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x5f, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x0e,
-	0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x57, 0x65, 0x69,
-	0x67, 0x68, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x0f, 0x20, 0x01,
-	0x28, 0x05, 0x52, 0x05, 0x70, 0x72, 0x69, 0x63, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6a, 0x75, 0x6d,
-	0x70, 0x5f, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x10, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a,
-	0x6a, 0x75, 0x6d, 0x70, 0x57, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x43, 0x0a, 0x0a, 0x6a, 0x75,
-	0x6d, 0x70, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x24,
+	0x65, 0x12, 0x16, 0x0a, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28,
+	0x09, 0x52, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x42, 0x0a, 0x0a, 0x70, 0x61, 0x67,
+	0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e,
+	0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f,
+	0x6e, 0x2e, 0x50, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x6f, 0x64, 0x65,
+	0x6c, 0x52, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x85, 0x01,
+	0x0a, 0x1c, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x46, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x43, 0x61,
+	0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12,
+	0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x63, 0x6f,
+	0x64, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x73, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
+	0x03, 0x6d, 0x73, 0x67, 0x12, 0x3f, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01,
+	0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x6f, 0x70, 0x65,
+	0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x46, 0x6f, 0x72,
+	0x61, 0x67, 0x65, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x44, 0x61, 0x74, 0x61, 0x52,
+	0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0xa2, 0x01, 0x0a, 0x18, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68,
+	0x46, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x44, 0x61,
+	0x74, 0x61, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05,
+	0x52, 0x04, 0x70, 0x61, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18,
+	0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x12, 0x1b, 0x0a, 0x09,
+	0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52,
+	0x08, 0x70, 0x61, 0x67, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x3f, 0x0a, 0x04, 0x6c, 0x69, 0x73,
+	0x74, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e,
+	0x64, 0x2e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x41, 0x64, 0x64, 0x46,
+	0x6f, 0x72, 0x61, 0x67, 0x65, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71,
+	0x75, 0x65, 0x73, 0x74, 0x52, 0x04, 0x6c, 0x69, 0x73, 0x74, 0x22, 0x9c, 0x08, 0x0a, 0x10, 0x41,
+	0x64, 0x64, 0x46, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
+	0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x69, 0x64, 0x12,
+	0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e,
+	0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x5f,
+	0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f,
+	0x72, 0x79, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79,
+	0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x61, 0x74,
+	0x65, 0x67, 0x6f, 0x72, 0x79, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x6d, 0x61, 0x74,
+	0x65, 0x72, 0x69, 0x61, 0x6c, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05,
+	0x52, 0x0c, 0x6d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x54, 0x79, 0x70, 0x65, 0x12, 0x23,
+	0x0a, 0x0d, 0x75, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x5f, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x18,
+	0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x75, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x45, 0x6e, 0x63,
+	0x6f, 0x64, 0x65, 0x12, 0x4e, 0x0a, 0x10, 0x66, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x6f,
+	0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x24, 0x2e,
+	0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f,
+	0x6e, 0x2e, 0x46, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4b,
+	0x69, 0x6e, 0x64, 0x52, 0x0e, 0x66, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x53, 0x6f, 0x75, 0x72, 0x63,
+	0x65, 0x49, 0x64, 0x12, 0x2c, 0x0a, 0x12, 0x66, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x6f,
+	0x75, 0x72, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52,
+	0x10, 0x66, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4e, 0x61, 0x6d,
+	0x65, 0x12, 0x48, 0x0a, 0x0c, 0x70, 0x6c, 0x61, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x69,
+	0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x26, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e,
+	0x64, 0x2e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x46, 0x6f, 0x72, 0x61,
+	0x67, 0x65, 0x50, 0x6c, 0x61, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x2e, 0x4b, 0x69, 0x6e, 0x64, 0x52,
+	0x0a, 0x70, 0x6c, 0x61, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x70,
+	0x6c, 0x61, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x0b, 0x20,
+	0x01, 0x28, 0x09, 0x52, 0x0c, 0x70, 0x6c, 0x61, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x4e, 0x61, 0x6d,
+	0x65, 0x12, 0x30, 0x0a, 0x14, 0x73, 0x6d, 0x61, 0x6c, 0x6c, 0x5f, 0x6d, 0x61, 0x74, 0x65, 0x72,
+	0x69, 0x61, 0x6c, 0x5f, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52,
+	0x12, 0x73, 0x6d, 0x61, 0x6c, 0x6c, 0x4d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x53, 0x63,
+	0x61, 0x6c, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x65, 0x72, 0x72,
+	0x6f, 0x72, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x45,
+	0x72, 0x72, 0x6f, 0x72, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x5f,
+	0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x70, 0x61,
+	0x63, 0x6b, 0x61, 0x67, 0x65, 0x57, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x70,
+	0x72, 0x69, 0x63, 0x65, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x70, 0x72, 0x69, 0x63,
+	0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6a, 0x75, 0x6d, 0x70, 0x5f, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74,
+	0x18, 0x10, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x6a, 0x75, 0x6d, 0x70, 0x57, 0x65, 0x69, 0x67,
+	0x68, 0x74, 0x12, 0x43, 0x0a, 0x0a, 0x6a, 0x75, 0x6d, 0x70, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79,
+	0x18, 0x11, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x24, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64,
+	0x2e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x4a, 0x75, 0x6d, 0x70, 0x44,
+	0x65, 0x6c, 0x61, 0x54, 0x79, 0x70, 0x65, 0x2e, 0x4b, 0x69, 0x6e, 0x64, 0x52, 0x09, 0x6a, 0x75,
+	0x6d, 0x70, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x12, 0x43, 0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69,
+	0x72, 0x6d, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x12, 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, 0x4a, 0x75, 0x6d, 0x70, 0x44, 0x65, 0x6c, 0x61, 0x54, 0x79, 0x70, 0x65, 0x2e,
-	0x4b, 0x69, 0x6e, 0x64, 0x52, 0x09, 0x6a, 0x75, 0x6d, 0x70, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x12,
-	0x43, 0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74,
-	0x18, 0x12, 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, 0x0c, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x53,
-	0x74, 0x61, 0x72, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x5f, 0x6c, 0x6f,
-	0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x13, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x72,
-	0x65, 0x6c, 0x61, 0x79, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x30, 0x0a,
-	0x03, 0x6a, 0x6d, 0x70, 0x18, 0x14, 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, 0x03, 0x6a, 0x6d, 0x70, 0x12,
-	0x18, 0x0a, 0x07, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x31, 0x18, 0x15, 0x20, 0x01, 0x28, 0x09,
-	0x52, 0x07, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x31, 0x12, 0x18, 0x0a, 0x07, 0x62, 0x61, 0x63,
-	0x6b, 0x75, 0x70, 0x32, 0x18, 0x16, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x62, 0x61, 0x63, 0x6b,
-	0x75, 0x70, 0x32, 0x12, 0x18, 0x0a, 0x07, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x33, 0x18, 0x17,
-	0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x33, 0x12, 0x1d, 0x0a,
-	0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x18, 0x20, 0x01, 0x28,
-	0x05, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x2a, 0x0a, 0x11,
-	0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x5f, 0x66, 0x6f, 0x72, 0x6d, 0x61,
-	0x74, 0x18, 0x19, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64,
-	0x41, 0x74, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x12, 0x37, 0x0a, 0x07, 0x69, 0x73, 0x5f, 0x73,
-	0x68, 0x6f, 0x77, 0x18, 0x1a, 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, 0x22, 0xfc, 0x02, 0x0a, 0x17, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x46, 0x6f, 0x72, 0x61,
-	0x67, 0x65, 0x4c, 0x69, 0x73, 0x74, 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, 0x1f, 0x0a, 0x0b, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x5f, 0x69, 0x64,
-	0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79,
-	0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x66, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x6f, 0x75,
-	0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x66, 0x6f,
-	0x72, 0x61, 0x67, 0x65, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x12, 0x37, 0x0a, 0x07,
-	0x69, 0x73, 0x5f, 0x73, 0x68, 0x6f, 0x77, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1e, 0x2e,
+	0x6f, 0x6e, 0x2e, 0x49, 0x73, 0x53, 0x68, 0x6f, 0x77, 0x2e, 0x4b, 0x69, 0x6e, 0x64, 0x52, 0x0c,
+	0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x53, 0x74, 0x61, 0x72, 0x74, 0x12, 0x27, 0x0a, 0x0f,
+	0x72, 0x65, 0x6c, 0x61, 0x79, 0x5f, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18,
+	0x13, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x4c, 0x6f, 0x63, 0x61,
+	0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x30, 0x0a, 0x03, 0x6a, 0x6d, 0x70, 0x18, 0x14, 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, 0x03, 0x6a, 0x6d, 0x70, 0x12, 0x18, 0x0a, 0x07, 0x62, 0x61, 0x63, 0x6b, 0x75,
+	0x70, 0x31, 0x18, 0x15, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70,
+	0x31, 0x12, 0x18, 0x0a, 0x07, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x32, 0x18, 0x16, 0x20, 0x01,
+	0x28, 0x09, 0x52, 0x07, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x32, 0x12, 0x18, 0x0a, 0x07, 0x62,
+	0x61, 0x63, 0x6b, 0x75, 0x70, 0x33, 0x18, 0x17, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x62, 0x61,
+	0x63, 0x6b, 0x75, 0x70, 0x33, 0x12, 0x1d, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64,
+	0x5f, 0x61, 0x74, 0x18, 0x18, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74,
+	0x65, 0x64, 0x41, 0x74, 0x12, 0x2a, 0x0a, 0x11, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f,
+	0x61, 0x74, 0x5f, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x18, 0x19, 0x20, 0x01, 0x28, 0x09, 0x52,
+	0x0f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74,
+	0x12, 0x37, 0x0a, 0x07, 0x69, 0x73, 0x5f, 0x73, 0x68, 0x6f, 0x77, 0x18, 0x1a, 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, 0x22, 0xfc, 0x02, 0x0a, 0x17, 0x53, 0x65,
+	0x61, 0x72, 0x63, 0x68, 0x46, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4c, 0x69, 0x73, 0x74, 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, 0x1f, 0x0a, 0x0b, 0x63, 0x61, 0x74,
+	0x65, 0x67, 0x6f, 0x72, 0x79, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a,
+	0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x66, 0x6f,
+	0x72, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x03,
+	0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x66, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x53, 0x6f, 0x75, 0x72,
+	0x63, 0x65, 0x49, 0x64, 0x12, 0x37, 0x0a, 0x07, 0x69, 0x73, 0x5f, 0x73, 0x68, 0x6f, 0x77, 0x18,
+	0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1e, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e,
+	0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x49, 0x73, 0x53, 0x68, 0x6f, 0x77,
+	0x2e, 0x4b, 0x69, 0x6e, 0x64, 0x52, 0x06, 0x69, 0x73, 0x53, 0x68, 0x6f, 0x77, 0x12, 0x1f, 0x0a,
+	0x0b, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01,
+	0x28, 0x0d, 0x52, 0x0a, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x1f,
+	0x0a, 0x0b, 0x6a, 0x75, 0x6d, 0x70, 0x5f, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x06, 0x20,
+	0x01, 0x28, 0x0d, 0x52, 0x0a, 0x6a, 0x75, 0x6d, 0x70, 0x57, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12,
+	0x43, 0x0a, 0x0a, 0x6a, 0x75, 0x6d, 0x70, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x79, 0x18, 0x07, 0x20,
+	0x01, 0x28, 0x0e, 0x32, 0x24, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x6f, 0x70,
+	0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x4a, 0x75, 0x6d, 0x70, 0x44, 0x65, 0x6c, 0x61,
+	0x54, 0x79, 0x70, 0x65, 0x2e, 0x4b, 0x69, 0x6e, 0x64, 0x52, 0x09, 0x6a, 0x75, 0x6d, 0x70, 0x44,
+	0x65, 0x6c, 0x61, 0x79, 0x12, 0x42, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69,
+	0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65,
+	0x6e, 0x64, 0x2e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x50, 0x61, 0x67,
+	0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x52, 0x0a, 0x70, 0x61,
+	0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x79, 0x0a, 0x18, 0x53, 0x65, 0x61, 0x72,
+	0x63, 0x68, 0x46, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70,
+	0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01,
+	0x28, 0x05, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x73, 0x67, 0x18,
+	0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6d, 0x73, 0x67, 0x12, 0x37, 0x0a, 0x04, 0x64, 0x61,
+	0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65,
+	0x6e, 0x64, 0x2e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x61,
+	0x72, 0x63, 0x68, 0x46, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x04, 0x64,
+	0x61, 0x74, 0x61, 0x22, 0x92, 0x01, 0x0a, 0x10, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x46, 0x6f,
+	0x72, 0x61, 0x67, 0x65, 0x4c, 0x69, 0x73, 0x74, 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,
+	0x37, 0x0a, 0x04, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x23, 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, 0x1f, 0x0a, 0x0b, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x65,
-	0x72, 0x72, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x61, 0x6c, 0x6c, 0x6f,
-	0x77, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x6a, 0x75, 0x6d, 0x70, 0x5f, 0x77,
-	0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x6a, 0x75, 0x6d,
-	0x70, 0x57, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x43, 0x0a, 0x0a, 0x6a, 0x75, 0x6d, 0x70, 0x5f,
-	0x64, 0x65, 0x6c, 0x61, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x24, 0x2e, 0x62, 0x61,
+	0x6e, 0x2e, 0x41, 0x64, 0x64, 0x46, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65,
+	0x73, 0x74, 0x52, 0x04, 0x6c, 0x69, 0x73, 0x74, 0x22, 0x64, 0x0a, 0x0c, 0x49, 0x73, 0x53, 0x68,
+	0x6f, 0x77, 0x46, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x66, 0x6f, 0x72, 0x61,
+	0x67, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x66, 0x6f, 0x72,
+	0x61, 0x67, 0x65, 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, 0x22, 0x75,
+	0x0a, 0x16, 0x46, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x45, 0x6e, 0x75, 0x6d, 0x4c, 0x69, 0x73, 0x74,
+	0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65,
+	0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x10, 0x0a, 0x03,
+	0x6d, 0x73, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6d, 0x73, 0x67, 0x12, 0x35,
+	0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x62,
+	0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e,
+	0x2e, 0x46, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x45, 0x6e, 0x75, 0x6d, 0x4c, 0x69, 0x73, 0x74, 0x52,
+	0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0xb7, 0x09, 0x0a, 0x0e, 0x46, 0x6f, 0x72, 0x61, 0x67, 0x65,
+	0x45, 0x6e, 0x75, 0x6d, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x48, 0x0a, 0x0d, 0x66, 0x6f, 0x72, 0x61,
+	0x67, 0x65, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32,
+	0x23, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74,
+	0x69, 0x6f, 0x6e, 0x2e, 0x46, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65,
+	0x45, 0x6e, 0x75, 0x6d, 0x52, 0x0c, 0x66, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x53, 0x6f, 0x75, 0x72,
+	0x63, 0x65, 0x12, 0x4f, 0x0a, 0x10, 0x66, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x5f, 0x70, 0x6c, 0x61,
+	0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x62,
+	0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e,
+	0x2e, 0x46, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x50, 0x6c, 0x61, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x45,
+	0x6e, 0x75, 0x6d, 0x52, 0x0e, 0x66, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x50, 0x6c, 0x61, 0x6e, 0x54,
+	0x79, 0x70, 0x65, 0x12, 0x49, 0x0a, 0x0e, 0x6a, 0x75, 0x6d, 0x70, 0x5f, 0x64, 0x65, 0x6c, 0x61,
+	0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x62, 0x61,
 	0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e,
-	0x4a, 0x75, 0x6d, 0x70, 0x44, 0x65, 0x6c, 0x61, 0x54, 0x79, 0x70, 0x65, 0x2e, 0x4b, 0x69, 0x6e,
-	0x64, 0x52, 0x09, 0x6a, 0x75, 0x6d, 0x70, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x12, 0x42, 0x0a, 0x0a,
-	0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b,
-	0x32, 0x22, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x6f, 0x70, 0x65, 0x72, 0x61,
-	0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x50, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4d,
-	0x6f, 0x64, 0x65, 0x6c, 0x52, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e,
-	0x22, 0x79, 0x0a, 0x18, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x46, 0x6f, 0x72, 0x61, 0x67, 0x65,
-	0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04,
-	0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65,
-	0x12, 0x10, 0x0a, 0x03, 0x6d, 0x73, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6d,
-	0x73, 0x67, 0x12, 0x37, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b,
-	0x32, 0x23, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x6f, 0x70, 0x65, 0x72, 0x61,
-	0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x46, 0x6f, 0x72, 0x61, 0x67,
-	0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x92, 0x01, 0x0a, 0x10,
-	0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x46, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x4c, 0x69, 0x73, 0x74,
-	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, 0x37, 0x0a, 0x04, 0x6c, 0x69, 0x73, 0x74, 0x18,
-	0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e,
-	0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x41, 0x64, 0x64, 0x46, 0x6f, 0x72,
-	0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x04, 0x6c, 0x69, 0x73, 0x74,
-	0x22, 0x64, 0x0a, 0x0c, 0x49, 0x73, 0x53, 0x68, 0x6f, 0x77, 0x46, 0x6f, 0x72, 0x61, 0x67, 0x65,
-	0x12, 0x1b, 0x0a, 0x09, 0x66, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20,
-	0x01, 0x28, 0x05, 0x52, 0x08, 0x66, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x49, 0x64, 0x12, 0x37, 0x0a,
-	0x07, 0x69, 0x73, 0x5f, 0x73, 0x68, 0x6f, 0x77, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1e,
+	0x4a, 0x75, 0x6d, 0x70, 0x44, 0x65, 0x6c, 0x61, 0x54, 0x79, 0x70, 0x65, 0x45, 0x6e, 0x75, 0x6d,
+	0x52, 0x0c, 0x6a, 0x75, 0x6d, 0x70, 0x44, 0x65, 0x6c, 0x61, 0x54, 0x79, 0x70, 0x65, 0x12, 0x61,
+	0x0a, 0x16, 0x63, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f,
+	0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b,
 	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, 0x22, 0x75, 0x0a, 0x16, 0x46, 0x6f, 0x72, 0x61, 0x67, 0x65,
-	0x45, 0x6e, 0x75, 0x6d, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
-	0x12, 0x12, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04,
-	0x63, 0x6f, 0x64, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6d, 0x73, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28,
-	0x09, 0x52, 0x03, 0x6d, 0x73, 0x67, 0x12, 0x35, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03,
-	0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x6f,
-	0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x46, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x45,
-	0x6e, 0x75, 0x6d, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0xc8, 0x05,
-	0x0a, 0x0e, 0x46, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x45, 0x6e, 0x75, 0x6d, 0x4c, 0x69, 0x73, 0x74,
-	0x12, 0x48, 0x0a, 0x0d, 0x66, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63,
-	0x65, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e,
-	0x64, 0x2e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x46, 0x6f, 0x72, 0x61,
-	0x67, 0x65, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x45, 0x6e, 0x75, 0x6d, 0x52, 0x0c, 0x66, 0x6f,
-	0x72, 0x61, 0x67, 0x65, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x4f, 0x0a, 0x10, 0x66, 0x6f,
-	0x72, 0x61, 0x67, 0x65, 0x5f, 0x70, 0x6c, 0x61, 0x6e, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02,
-	0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x6f,
-	0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x46, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x50,
-	0x6c, 0x61, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x45, 0x6e, 0x75, 0x6d, 0x52, 0x0e, 0x66, 0x6f, 0x72,
-	0x61, 0x67, 0x65, 0x50, 0x6c, 0x61, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x49, 0x0a, 0x0e, 0x6a,
-	0x75, 0x6d, 0x70, 0x5f, 0x64, 0x65, 0x6c, 0x61, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20,
-	0x03, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x6f, 0x70,
-	0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x4a, 0x75, 0x6d, 0x70, 0x44, 0x65, 0x6c, 0x61,
-	0x54, 0x79, 0x70, 0x65, 0x45, 0x6e, 0x75, 0x6d, 0x52, 0x0c, 0x6a, 0x75, 0x6d, 0x70, 0x44, 0x65,
-	0x6c, 0x61, 0x54, 0x79, 0x70, 0x65, 0x12, 0x61, 0x0a, 0x16, 0x63, 0x61, 0x74, 0x74, 0x6c, 0x65,
-	0x5f, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79,
-	0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64,
-	0x2e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x43, 0x61, 0x74, 0x74, 0x6c,
-	0x65, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x45,
-	0x6e, 0x75, 0x6d, 0x52, 0x14, 0x63, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x50, 0x61, 0x72, 0x65, 0x6e,
-	0x74, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x12, 0x61, 0x0a, 0x16, 0x66, 0x6f, 0x72,
-	0x61, 0x67, 0x65, 0x5f, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x61, 0x74, 0x65, 0x67,
-	0x6f, 0x72, 0x79, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x62, 0x61, 0x63, 0x6b,
+	0x6f, 0x6e, 0x2e, 0x43, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x43,
+	0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x45, 0x6e, 0x75, 0x6d, 0x52, 0x14, 0x63, 0x61, 0x74,
+	0x74, 0x6c, 0x65, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72,
+	0x79, 0x12, 0x61, 0x0a, 0x16, 0x66, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x5f, 0x70, 0x61, 0x72, 0x65,
+	0x6e, 0x74, 0x5f, 0x63, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x18, 0x05, 0x20, 0x03, 0x28,
+	0x0b, 0x32, 0x2b, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x6f, 0x70, 0x65, 0x72,
+	0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x46, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x50, 0x61, 0x72, 0x65,
+	0x6e, 0x74, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x45, 0x6e, 0x75, 0x6d, 0x52, 0x14,
+	0x66, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x43, 0x61, 0x74, 0x65,
+	0x67, 0x6f, 0x72, 0x79, 0x12, 0x36, 0x0a, 0x07, 0x69, 0x73, 0x5f, 0x73, 0x68, 0x6f, 0x77, 0x18,
+	0x06, 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, 0x49, 0x73, 0x53, 0x68, 0x6f, 0x77,
+	0x45, 0x6e, 0x75, 0x6d, 0x52, 0x06, 0x69, 0x73, 0x53, 0x68, 0x6f, 0x77, 0x12, 0x45, 0x0a, 0x0c,
+	0x66, 0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x07, 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, 0x46, 0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x54, 0x79,
+	0x70, 0x65, 0x45, 0x6e, 0x75, 0x6d, 0x52, 0x0b, 0x66, 0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x54,
+	0x79, 0x70, 0x65, 0x12, 0x47, 0x0a, 0x0c, 0x66, 0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x5f, 0x6c,
+	0x69, 0x73, 0x74, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x62, 0x61, 0x63, 0x6b,
 	0x65, 0x6e, 0x64, 0x2e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x46, 0x6f,
-	0x72, 0x61, 0x67, 0x65, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f,
-	0x72, 0x79, 0x45, 0x6e, 0x75, 0x6d, 0x52, 0x14, 0x66, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x50, 0x61,
-	0x72, 0x65, 0x6e, 0x74, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x12, 0x36, 0x0a, 0x07,
-	0x69, 0x73, 0x5f, 0x73, 0x68, 0x6f, 0x77, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e,
+	0x72, 0x6d, 0x75, 0x6c, 0x61, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x75, 0x6d, 0x52,
+	0x0b, 0x66, 0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x42, 0x0a, 0x0d,
+	0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x09, 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, 0x49, 0x73, 0x53, 0x68, 0x6f, 0x77, 0x45, 0x6e,
+	0x75, 0x6d, 0x52, 0x0c, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x53, 0x74, 0x61, 0x72, 0x74,
+	0x12, 0x5b, 0x0a, 0x16, 0x66, 0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f,
+	0x65, 0x76, 0x61, 0x6c, 0x75, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b,
+	0x32, 0x24, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x6f, 0x70, 0x65, 0x72, 0x61,
+	0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x46, 0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x4f, 0x70, 0x74, 0x69,
+	0x6f, 0x6e, 0x45, 0x6e, 0x75, 0x6d, 0x52, 0x15, 0x66, 0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x74,
+	0x69, 0x6f, 0x6e, 0x45, 0x76, 0x61, 0x6c, 0x75, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x52, 0x0a,
+	0x12, 0x75, 0x73, 0x65, 0x5f, 0x6d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x73, 0x5f, 0x6c,
+	0x69, 0x73, 0x74, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x62, 0x61, 0x63, 0x6b,
+	0x65, 0x6e, 0x64, 0x2e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x46, 0x6f,
+	0x72, 0x6d, 0x75, 0x6c, 0x61, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x75, 0x6d, 0x52,
+	0x10, 0x75, 0x73, 0x65, 0x4d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x73, 0x4c, 0x69, 0x73,
+	0x74, 0x12, 0x52, 0x0a, 0x12, 0x75, 0x73, 0x65, 0x5f, 0x6d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61,
+	0x6c, 0x73, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 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, 0x45, 0x6e, 0x75, 0x6d, 0x52, 0x06, 0x69, 0x73,
-	0x53, 0x68, 0x6f, 0x77, 0x12, 0x45, 0x0a, 0x0c, 0x66, 0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x5f,
-	0x74, 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x62, 0x61, 0x63,
+	0x6e, 0x2e, 0x46, 0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x45,
+	0x6e, 0x75, 0x6d, 0x52, 0x10, 0x75, 0x73, 0x65, 0x4d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c,
+	0x73, 0x54, 0x79, 0x70, 0x65, 0x12, 0x56, 0x0a, 0x14, 0x70, 0x72, 0x69, 0x63, 0x65, 0x5f, 0x6d,
+	0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x73, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x0d, 0x20,
+	0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x6f, 0x70,
+	0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x46, 0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x4f,
+	0x70, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x75, 0x6d, 0x52, 0x12, 0x70, 0x72, 0x69, 0x63, 0x65,
+	0x4d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x73, 0x54, 0x79, 0x70, 0x65, 0x12, 0x41, 0x0a,
+	0x09, 0x6a, 0x75, 0x6d, 0x70, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x0e, 0x20, 0x03, 0x28, 0x0b,
+	0x32, 0x24, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x6f, 0x70, 0x65, 0x72, 0x61,
+	0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x46, 0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x4f, 0x70, 0x74, 0x69,
+	0x6f, 0x6e, 0x45, 0x6e, 0x75, 0x6d, 0x52, 0x08, 0x6a, 0x75, 0x6d, 0x70, 0x54, 0x79, 0x70, 0x65,
+	0x12, 0x4d, 0x0a, 0x0f, 0x73, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, 0x73, 0x5f, 0x74,
+	0x79, 0x70, 0x65, 0x18, 0x0f, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x62, 0x61, 0x63, 0x6b,
+	0x65, 0x6e, 0x64, 0x2e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x46, 0x6f,
+	0x72, 0x6d, 0x75, 0x6c, 0x61, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x75, 0x6d, 0x52,
+	0x0e, 0x73, 0x74, 0x61, 0x74, 0x69, 0x73, 0x74, 0x69, 0x63, 0x73, 0x54, 0x79, 0x70, 0x65, 0x22,
+	0x64, 0x0a, 0x10, 0x46, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x45,
+	0x6e, 0x75, 0x6d, 0x12, 0x3a, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01,
+	0x28, 0x0e, 0x32, 0x24, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x6f, 0x70, 0x65,
+	0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x46, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x53, 0x6f, 0x75,
+	0x72, 0x63, 0x65, 0x2e, 0x4b, 0x69, 0x6e, 0x64, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12,
+	0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05,
+	0x6c, 0x61, 0x62, 0x65, 0x6c, 0x22, 0x68, 0x0a, 0x12, 0x46, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x50,
+	0x6c, 0x61, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x45, 0x6e, 0x75, 0x6d, 0x12, 0x3c, 0x0a, 0x05, 0x76,
+	0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x26, 0x2e, 0x62, 0x61, 0x63,
 	0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x46,
-	0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x54, 0x79, 0x70, 0x65, 0x45, 0x6e, 0x75, 0x6d, 0x52, 0x0b,
-	0x66, 0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x54, 0x79, 0x70, 0x65, 0x12, 0x47, 0x0a, 0x0c, 0x66,
-	0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x08, 0x20, 0x03, 0x28,
-	0x0b, 0x32, 0x24, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x6f, 0x70, 0x65, 0x72,
-	0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x46, 0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x4f, 0x70, 0x74,
-	0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x75, 0x6d, 0x52, 0x0b, 0x66, 0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61,
-	0x4c, 0x69, 0x73, 0x74, 0x12, 0x42, 0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x72, 0x6d, 0x5f,
-	0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x09, 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,
-	0x49, 0x73, 0x53, 0x68, 0x6f, 0x77, 0x45, 0x6e, 0x75, 0x6d, 0x52, 0x0c, 0x63, 0x6f, 0x6e, 0x66,
-	0x69, 0x72, 0x6d, 0x53, 0x74, 0x61, 0x72, 0x74, 0x22, 0x64, 0x0a, 0x10, 0x46, 0x6f, 0x72, 0x61,
-	0x67, 0x65, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x45, 0x6e, 0x75, 0x6d, 0x12, 0x3a, 0x0a, 0x05,
-	0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x24, 0x2e, 0x62, 0x61,
-	0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e,
-	0x46, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4b, 0x69, 0x6e,
-	0x64, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65,
-	0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x22, 0x68,
-	0x0a, 0x12, 0x46, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x50, 0x6c, 0x61, 0x6e, 0x54, 0x79, 0x70, 0x65,
-	0x45, 0x6e, 0x75, 0x6d, 0x12, 0x3c, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20,
-	0x01, 0x28, 0x0e, 0x32, 0x26, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x6f, 0x70,
-	0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x46, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x50, 0x6c,
-	0x61, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x2e, 0x4b, 0x69, 0x6e, 0x64, 0x52, 0x05, 0x76, 0x61, 0x6c,
-	0x75, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28,
-	0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x22, 0x64, 0x0a, 0x10, 0x4a, 0x75, 0x6d, 0x70,
-	0x44, 0x65, 0x6c, 0x61, 0x54, 0x79, 0x70, 0x65, 0x45, 0x6e, 0x75, 0x6d, 0x12, 0x3a, 0x0a, 0x05,
-	0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x24, 0x2e, 0x62, 0x61,
-	0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e,
-	0x4a, 0x75, 0x6d, 0x70, 0x44, 0x65, 0x6c, 0x61, 0x54, 0x79, 0x70, 0x65, 0x2e, 0x4b, 0x69, 0x6e,
-	0x64, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65,
-	0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x22, 0x74,
-	0x0a, 0x18, 0x43, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x43, 0x61,
-	0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x45, 0x6e, 0x75, 0x6d, 0x12, 0x42, 0x0a, 0x05, 0x76, 0x61,
-	0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2c, 0x2e, 0x62, 0x61, 0x63, 0x6b,
-	0x65, 0x6e, 0x64, 0x2e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x43, 0x61,
-	0x74, 0x74, 0x6c, 0x65, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x50, 0x61, 0x72, 0x65,
-	0x6e, 0x74, 0x2e, 0x4b, 0x69, 0x6e, 0x64, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x14,
-	0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c,
-	0x61, 0x62, 0x65, 0x6c, 0x22, 0x74, 0x0a, 0x18, 0x46, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x50, 0x61,
-	0x72, 0x65, 0x6e, 0x74, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x45, 0x6e, 0x75, 0x6d,
-	0x12, 0x42, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32,
-	0x2c, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74,
-	0x69, 0x6f, 0x6e, 0x2e, 0x46, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f,
-	0x72, 0x79, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x2e, 0x4b, 0x69, 0x6e, 0x64, 0x52, 0x05, 0x76,
-	0x61, 0x6c, 0x75, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x02, 0x20,
-	0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x22, 0x58, 0x0a, 0x0a, 0x49, 0x73,
-	0x53, 0x68, 0x6f, 0x77, 0x45, 0x6e, 0x75, 0x6d, 0x12, 0x34, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75,
-	0x65, 0x18, 0x01, 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, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x14,
-	0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c,
-	0x61, 0x62, 0x65, 0x6c, 0x22, 0x62, 0x0a, 0x0f, 0x46, 0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x54,
-	0x79, 0x70, 0x65, 0x45, 0x6e, 0x75, 0x6d, 0x12, 0x39, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,
-	0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x23, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64,
-	0x2e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x46, 0x6f, 0x72, 0x6d, 0x75,
-	0x6c, 0x61, 0x54, 0x79, 0x70, 0x65, 0x2e, 0x4b, 0x69, 0x6e, 0x64, 0x52, 0x05, 0x76, 0x61, 0x6c,
-	0x75, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28,
-	0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x22, 0x3f, 0x0a, 0x11, 0x46, 0x6f, 0x72, 0x6d,
-	0x75, 0x6c, 0x61, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x75, 0x6d, 0x12, 0x14, 0x0a,
-	0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x76, 0x61,
-	0x6c, 0x75, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01,
-	0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 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,
+	0x6f, 0x72, 0x61, 0x67, 0x65, 0x50, 0x6c, 0x61, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x2e, 0x4b, 0x69,
+	0x6e, 0x64, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62,
+	0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x22,
+	0x64, 0x0a, 0x10, 0x4a, 0x75, 0x6d, 0x70, 0x44, 0x65, 0x6c, 0x61, 0x54, 0x79, 0x70, 0x65, 0x45,
+	0x6e, 0x75, 0x6d, 0x12, 0x3a, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01,
+	0x28, 0x0e, 0x32, 0x24, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x6f, 0x70, 0x65,
+	0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x4a, 0x75, 0x6d, 0x70, 0x44, 0x65, 0x6c, 0x61, 0x54,
+	0x79, 0x70, 0x65, 0x2e, 0x4b, 0x69, 0x6e, 0x64, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12,
+	0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05,
+	0x6c, 0x61, 0x62, 0x65, 0x6c, 0x22, 0x74, 0x0a, 0x18, 0x43, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x50,
+	0x61, 0x72, 0x65, 0x6e, 0x74, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x45, 0x6e, 0x75,
+	0x6d, 0x12, 0x42, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e,
+	0x32, 0x2c, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x6f, 0x70, 0x65, 0x72, 0x61,
+	0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x43, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x43, 0x61, 0x74, 0x65, 0x67,
+	0x6f, 0x72, 0x79, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x2e, 0x4b, 0x69, 0x6e, 0x64, 0x52, 0x05,
+	0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x02,
+	0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x22, 0x74, 0x0a, 0x18, 0x46,
+	0x6f, 0x72, 0x61, 0x67, 0x65, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x43, 0x61, 0x74, 0x65, 0x67,
+	0x6f, 0x72, 0x79, 0x45, 0x6e, 0x75, 0x6d, 0x12, 0x42, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,
+	0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2c, 0x2e, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64,
+	0x2e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x46, 0x6f, 0x72, 0x61, 0x67,
+	0x65, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x79, 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x2e,
+	0x4b, 0x69, 0x6e, 0x64, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6c,
+	0x61, 0x62, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65,
+	0x6c, 0x22, 0x58, 0x0a, 0x0a, 0x49, 0x73, 0x53, 0x68, 0x6f, 0x77, 0x45, 0x6e, 0x75, 0x6d, 0x12,
+	0x34, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 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, 0x05,
+	0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, 0x02,
+	0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x22, 0x62, 0x0a, 0x0f, 0x46,
+	0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x54, 0x79, 0x70, 0x65, 0x45, 0x6e, 0x75, 0x6d, 0x12, 0x39,
+	0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x23, 0x2e,
+	0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x2e, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f,
+	0x6e, 0x2e, 0x46, 0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x54, 0x79, 0x70, 0x65, 0x2e, 0x4b, 0x69,
+	0x6e, 0x64, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61, 0x62,
+	0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x22,
+	0x3f, 0x0a, 0x11, 0x46, 0x6f, 0x72, 0x6d, 0x75, 0x6c, 0x61, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e,
+	0x45, 0x6e, 0x75, 0x6d, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20,
+	0x01, 0x28, 0x05, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x61,
+	0x62, 0x65, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c,
+	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 (
@@ -2850,18 +2938,24 @@ var file_backend_operation_pasture_proto_depIdxs = []int32{
 	29, // 38: backend.operation.ForageEnumList.formula_type:type_name -> backend.operation.FormulaTypeEnum
 	30, // 39: backend.operation.ForageEnumList.formula_list:type_name -> backend.operation.FormulaOptionEnum
 	28, // 40: backend.operation.ForageEnumList.confirm_start:type_name -> backend.operation.IsShowEnum
-	35, // 41: backend.operation.ForageSourceEnum.value:type_name -> backend.operation.ForageSource.Kind
-	36, // 42: backend.operation.ForagePlanTypeEnum.value:type_name -> backend.operation.ForagePlanType.Kind
-	37, // 43: backend.operation.JumpDelaTypeEnum.value:type_name -> backend.operation.JumpDelaType.Kind
-	33, // 44: backend.operation.CattleParentCategoryEnum.value:type_name -> backend.operation.CattleCategoryParent.Kind
-	34, // 45: backend.operation.ForageParentCategoryEnum.value:type_name -> backend.operation.ForageCategoryParent.Kind
-	31, // 46: backend.operation.IsShowEnum.value:type_name -> backend.operation.IsShow.Kind
-	38, // 47: backend.operation.FormulaTypeEnum.value:type_name -> backend.operation.FormulaType.Kind
-	48, // [48:48] is the sub-list for method output_type
-	48, // [48:48] is the sub-list for method input_type
-	48, // [48:48] is the sub-list for extension type_name
-	48, // [48:48] is the sub-list for extension extendee
-	0,  // [0:48] is the sub-list for field type_name
+	30, // 41: backend.operation.ForageEnumList.formulation_evaluation:type_name -> backend.operation.FormulaOptionEnum
+	30, // 42: backend.operation.ForageEnumList.use_materials_list:type_name -> backend.operation.FormulaOptionEnum
+	30, // 43: backend.operation.ForageEnumList.use_materials_type:type_name -> backend.operation.FormulaOptionEnum
+	30, // 44: backend.operation.ForageEnumList.price_materials_type:type_name -> backend.operation.FormulaOptionEnum
+	30, // 45: backend.operation.ForageEnumList.jump_type:type_name -> backend.operation.FormulaOptionEnum
+	30, // 46: backend.operation.ForageEnumList.statistics_type:type_name -> backend.operation.FormulaOptionEnum
+	35, // 47: backend.operation.ForageSourceEnum.value:type_name -> backend.operation.ForageSource.Kind
+	36, // 48: backend.operation.ForagePlanTypeEnum.value:type_name -> backend.operation.ForagePlanType.Kind
+	37, // 49: backend.operation.JumpDelaTypeEnum.value:type_name -> backend.operation.JumpDelaType.Kind
+	33, // 50: backend.operation.CattleParentCategoryEnum.value:type_name -> backend.operation.CattleCategoryParent.Kind
+	34, // 51: backend.operation.ForageParentCategoryEnum.value:type_name -> backend.operation.ForageCategoryParent.Kind
+	31, // 52: backend.operation.IsShowEnum.value:type_name -> backend.operation.IsShow.Kind
+	38, // 53: backend.operation.FormulaTypeEnum.value:type_name -> backend.operation.FormulaType.Kind
+	54, // [54:54] is the sub-list for method output_type
+	54, // [54:54] is the sub-list for method input_type
+	54, // [54:54] is the sub-list for extension type_name
+	54, // [54:54] is the sub-list for extension extendee
+	0,  // [0:54] is the sub-list for field type_name
 }
 
 func init() { file_backend_operation_pasture_proto_init() }

Fichier diff supprimé car celui-ci est trop grand
+ 1250 - 211
proto/go/backend/operation/statistic.pb.go


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

Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff